Ben/search rrf (#179)
* chore: fill out missing metadata inputs in python sdk * feat: add get_peer_config to python sdk, thoroughly document ts sdk and remove bad client usage * feat: zod chore: update tests chore: bump version, changelog * chore: python sdk version bump and changelog * [WIP] feat: combine search methods and rework endpoint to include limit param * chore: test new stainless config with library * nits: coderabbit * Merge branch 'ben/sdk-improvements' into ben/search-rrf * chore: pre-commit hooks cleanup * feat: thoroughly document observation config * Update sdks/python/src/honcho/peer.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: v1.3.0 * feat: update version to 2.2.0 and enhance search functionality with arbitrary filters - Remove unused config variables - Added arbitrary filters to all search endpoints. - Pluralize `filters` everywhere in SDKs for consistency - Updated documentation and changelog to reflect these changes. * expose core client in TS and Python SDKs (#150) * expose core client from sdks * align text * fix: resolve get_effective_observe me race condition, default peer config (#176) * fix: resolve get_effective_observe me race condition, default peer config * fix: preserve custom config even after leaving * chore: test cases, enqueue types * Update sdks/typescript/package.json Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: doria <93405247+dr-frmr@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: formatting * chore: revert undesired changes to v1 spec, clean up docs, coderabbit * feat: better search docs, fix worker.ts * fix: correctly make ts params optional in cases, update docs * chore: coderabbit * chore: remove spurious package-lock * fix: asyncify examples, use limit properly in search * fix(tests): handle 4 return values in test_get_session_peer_configuration --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Rajat Ahuja <rahuja445@gmail.com>
This commit is contained in:
parent
ddc16cfc21
commit
7b174dd34b
|
|
@ -8,8 +8,6 @@
|
|||
# Application Settings
|
||||
# =============================================================================
|
||||
LOG_LEVEL=INFO
|
||||
FASTAPI_HOST=0.0.0.0
|
||||
FASTAPI_PORT=8000
|
||||
# SESSION_PEERS_LIMIT=10
|
||||
# GET_CONTEXT_MAX_TOKENS=100000
|
||||
|
||||
|
|
@ -86,8 +84,6 @@ LLM_ANTHROPIC_API_KEY=your-anthropic-api-key-here
|
|||
# MAX_OUTPUT_TOKENS=2500
|
||||
# only applied when using Anthropic as provider
|
||||
# THINKING_BUDGET_TOKENS=1024
|
||||
# DERIVER_DEDUCTIVE_OBSERVATIONS_COUNT=6
|
||||
# DERIVER_EXPLICIT_OBSERVATIONS_COUNT=10
|
||||
|
||||
# =============================================================================
|
||||
# Dialectic Settings
|
||||
|
|
|
|||
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [2.2.0] - 2025-08-07
|
||||
|
||||
### Added
|
||||
|
||||
- Arbitrary filters now available on all search endpoints
|
||||
- Search combines full-text and semantic using reciprocal rank fusion
|
||||
|
||||
### Changed
|
||||
|
||||
- Search has 10 results by default, max 100 results
|
||||
|
||||
## [2.1.2] - 2025-07-30
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ All API routes follow the pattern: `/v1/{resource}/{id}/{action}`
|
|||
- **Peers**: Create, list, update, chat (dialectic), messages, representation
|
||||
- **Sessions**: Create, list, update, delete, clone, manage peers, get context
|
||||
- **Messages**: Create (batch up to 100), list, get, update
|
||||
- **Keys**: Create scoped JWT tokens
|
||||
- **Keys**: Create scoped JWTs
|
||||
|
||||
### Key Features
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ src/
|
|||
2. **Flexible Theory of Mind**: Pluggable ToM implementations (conversational, single_prompt, long_term)
|
||||
3. **Background Processing**: Async queue system for expensive operations
|
||||
4. **Provider Abstraction**: Model client supports multiple LLM providers
|
||||
5. **Scoped Authentication**: JWT tokens can be scoped to workspace, peer, or session level
|
||||
5. **Scoped Authentication**: JWTs can be scoped to workspace, peer, or session level
|
||||
6. **Batch Operations**: Support for bulk message creation (up to 100 messages)
|
||||
7. **Session History**: Two-tier summarization (short every 20 messages, long every 60)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 🫡 Honcho
|
||||
|
||||

|
||||

|
||||
[](https://discord.gg/plasticlabs)
|
||||
[](https://arxiv.org/abs/2310.06983)
|
||||

|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
# Application-level settings
|
||||
[app]
|
||||
LOG_LEVEL = "INFO"
|
||||
FASTAPI_HOST = "0.0.0.0"
|
||||
FASTAPI_PORT = 8000
|
||||
SESSION_PEERS_LIMIT = 10
|
||||
GET_CONTEXT_MAX_TOKENS = 100000
|
||||
EMBED_MESSAGES = true
|
||||
|
|
@ -66,8 +64,6 @@ PROVIDER = "google"
|
|||
MODEL = "gemini-2.0-flash-lite"
|
||||
MAX_OUTPUT_TOKENS = 2500
|
||||
THINKING_BUDGET_TOKENS = 1024 # only applied when using Anthropic
|
||||
DEDUCTIVE_OBSERVATIONS_COUNT = 6
|
||||
EXPLICIT_OBSERVATIONS_COUNT = 10
|
||||
|
||||
# Dialectic settings
|
||||
[dialectic]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,28 @@ This guide helps you understand which versions of Honcho's API are compatible wi
|
|||
|
||||
## Version Compatibility
|
||||
|
||||
### Honcho API v2.1.2 (Current)
|
||||
### Honcho API v2.2.0 (Current)
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="TypeScript SDK" icon="js">
|
||||
**Compatible Version:** v1.3.0
|
||||
|
||||
Install with:
|
||||
```bash
|
||||
npm install @honcho-ai/sdk@1.3.0
|
||||
```
|
||||
</Card>
|
||||
<Card title="Python SDK" icon="python">
|
||||
**Compatible Version:** v1.3.0
|
||||
|
||||
Install with:
|
||||
```bash
|
||||
pip install honcho-ai==1.3.0
|
||||
```
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Honcho API v2.1.2
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="TypeScript SDK" icon="js">
|
||||
|
|
@ -35,7 +56,8 @@ This guide helps you understand which versions of Honcho's API are compatible wi
|
|||
|
||||
| Honcho API Version | TypeScript SDK | Python SDK |
|
||||
|-------------------|---------------|------------|
|
||||
| v2.1.2 (Current) | v1.2.1 | v1.2.2 |
|
||||
| v2.2.0 (Current) | v1.3.0 | v1.3.0 |
|
||||
| v2.1.2 | v1.2.1 | v1.2.2 |
|
||||
| v2.1.1 | v1.2.1 | v1.2.2 |
|
||||
| v2.1.0 | v1.2.1 | v1.2.2 |
|
||||
| v2.0.5 | v1.1.0 | v1.1.0 |
|
||||
|
|
|
|||
|
|
@ -27,7 +27,20 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
### Honcho API and SDK Changelogs
|
||||
<Tabs>
|
||||
<Tab title="Honcho API">
|
||||
<Update label="v2.1.2 (Current)">
|
||||
<Update label="v2.2.0 (Current)">
|
||||
### Added
|
||||
|
||||
- Arbitrary filters now available on all search endpoints
|
||||
- Search combines full-text and semantic using [reciprocal rank fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf)
|
||||
|
||||
### Changed
|
||||
|
||||
- API uses 'filters' instead of 'filter' everywhere for consistency and better behavior with Python native types (breaking change)
|
||||
- Search has `limit` parameter: 10 results by default, max 100 results
|
||||
</Update>
|
||||
|
||||
|
||||
<Update label="v2.1.2">
|
||||
### Fixed
|
||||
|
||||
- Summarizer module to ignore empty summaries and pass appropriate one to get_context
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"navigation": {
|
||||
"versions": [
|
||||
{
|
||||
"version": "v2.1.2",
|
||||
"version": "v2.2.0",
|
||||
"api": {
|
||||
"openapi": [
|
||||
"openapi.documented.yml"
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ def get_session(user_id, location_id, create=False):
|
|||
|
||||
# Query for *active* sessions with both user_id and location_id
|
||||
sessions_iter = honcho.apps.users.sessions.list(
|
||||
app_id=app.id, user_id=user_id, reverse=True, filter={"is_active": True}
|
||||
app_id=app.id, user_id=user_id, reverse=True, filters={"is_active": True}
|
||||
)
|
||||
sessions = list(session for session in sessions_iter)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"apps"
|
||||
],
|
||||
"summary": "Get App",
|
||||
"description": "Get an App by ID.\n\nIf app_id is provided as a query parameter, it uses that (must match JWT app_id).\nOtherwise, it uses the app_id from the JWT token.",
|
||||
"description": "Get an App by ID.\n\nIf app_id is provided as a query parameter, it uses that (must match JWT app_id).\nOtherwise, it uses the app_id from the JWT.",
|
||||
"operationId": "get_app_v1_apps_get",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -54,10 +54,10 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "App ID to retrieve. If not provided, uses JWT token",
|
||||
"description": "App ID to retrieve. If not provided, uses JWT",
|
||||
"title": "App Id"
|
||||
},
|
||||
"description": "App ID to retrieve. If not provided, uses JWT token"
|
||||
"description": "App ID to retrieve. If not provided, uses JWT"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -535,7 +535,7 @@
|
|||
"users"
|
||||
],
|
||||
"summary": "Get User",
|
||||
"description": "Get a User by ID\n\nIf user_id is provided as a query parameter, it uses that (must match JWT app_id).\nOtherwise, it uses the user_id from the JWT token.",
|
||||
"description": "Get a User by ID\n\nIf user_id is provided as a query parameter, it uses that (must match JWT app_id).\nOtherwise, it uses the user_id from the JWT.",
|
||||
"operationId": "get_user_v1_apps__app_id__users_get",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -568,10 +568,10 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "User ID to retrieve. If not provided, users JWT token",
|
||||
"description": "User ID to retrieve. If not provided, uses JWT",
|
||||
"title": "User Id"
|
||||
},
|
||||
"description": "User ID to retrieve. If not provided, users JWT token"
|
||||
"description": "User ID to retrieve. If not provided, uses JWT"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -959,7 +959,7 @@
|
|||
"sessions"
|
||||
],
|
||||
"summary": "Get Session",
|
||||
"description": "Get a specific session for a user.\n\nIf session_id is provided as a query parameter, it uses that (must match JWT session_id).\nOtherwise, it uses the session_id from the JWT token.",
|
||||
"description": "Get a specific session for a user.\n\nIf session_id is provided as a query parameter, it uses that (must match JWT session_id).\nOtherwise, it uses the session_id from the JWT.",
|
||||
"operationId": "get_session_v1_apps__app_id__users__user_id__sessions_get",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -1003,10 +1003,10 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Session ID to retrieve. If not provided, uses JWT token",
|
||||
"description": "Session ID to retrieve. If not provided, uses JWT",
|
||||
"title": "Session Id"
|
||||
},
|
||||
"description": "Session ID to retrieve. If not provided, uses JWT token"
|
||||
"description": "Session ID to retrieve. If not provided, uses JWT"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -2577,7 +2577,7 @@
|
|||
"collections"
|
||||
],
|
||||
"summary": "Get Collection",
|
||||
"description": "Get a specific collection for a user.\n\nIf collection_id is provided as a query parameter, it uses that (must match JWT collection_id).\nOtherwise, it uses the collection_id from the JWT token.",
|
||||
"description": "Get a specific collection for a user.\n\nIf collection_id is provided as a query parameter, it uses that (must match JWT collection_id).\nOtherwise, it uses the collection_id from the JWT.",
|
||||
"operationId": "get_collection_v1_apps__app_id__users__user_id__collections_get",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -2621,10 +2621,10 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Collection ID to retrieve. If not provided, uses JWT token",
|
||||
"description": "Collection ID to retrieve. If not provided, uses JWT",
|
||||
"title": "Collection Id"
|
||||
},
|
||||
"description": "Collection ID to retrieve. If not provided, uses JWT token"
|
||||
"description": "Collection ID to retrieve. If not provided, uses JWT"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
|||
|
|
@ -111,8 +111,6 @@ The application will use the production connection URI while keeping the pool si
|
|||
```bash
|
||||
# Logging and server settings
|
||||
LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR
|
||||
FASTAPI_HOST=0.0.0.0
|
||||
FASTAPI_PORT=8000
|
||||
SESSION_PEERS_LIMIT=10
|
||||
GET_CONTEXT_MAX_TOKENS=100000
|
||||
|
||||
|
|
@ -256,10 +254,6 @@ DERIVER_MODEL=gemini-2.0-flash-lite
|
|||
DERIVER_WORKERS=1
|
||||
DERIVER_STALE_SESSION_TIMEOUT_MINUTES=5
|
||||
DERIVER_POLLING_SLEEP_INTERVAL_SECONDS=1.0
|
||||
|
||||
# Observation retrieval counts
|
||||
DERIVER_DEDUCTIVE_OBSERVATIONS_COUNT=6
|
||||
DERIVER_EXPLICIT_OBSERVATIONS_COUNT=10
|
||||
```
|
||||
|
||||
**Summary Generation:**
|
||||
|
|
@ -310,8 +304,6 @@ SENTRY_PROFILES_SAMPLE_RATE=0.1
|
|||
```toml
|
||||
[app]
|
||||
LOG_LEVEL = "DEBUG"
|
||||
FASTAPI_HOST = "127.0.0.1"
|
||||
FASTAPI_PORT = 8000
|
||||
SESSION_PEERS_LIMIT = 10
|
||||
EMBED_MESSAGES = false
|
||||
|
||||
|
|
@ -357,8 +349,6 @@ ANTHROPIC_API_KEY=your-dev-anthropic-key
|
|||
```toml
|
||||
[app]
|
||||
LOG_LEVEL = "WARNING"
|
||||
FASTAPI_HOST = "0.0.0.0"
|
||||
FASTAPI_PORT = 8000
|
||||
SESSION_PEERS_LIMIT = 10
|
||||
EMBED_MESSAGES = true
|
||||
|
||||
|
|
|
|||
|
|
@ -295,8 +295,7 @@ const client = new Honcho({
|
|||
- Check that the keys have sufficient credits/quota
|
||||
|
||||
**Port Already in Use**
|
||||
- Change the port in your configuration: `FASTAPI_PORT=8001`
|
||||
- Or stop other services using port 8000
|
||||
- Pass a different port to FastAPI or stop other services using port 8000
|
||||
|
||||
**Docker Issues**
|
||||
- Ensure Docker is running
|
||||
|
|
|
|||
|
|
@ -211,9 +211,11 @@ backchannel directly with Honcho, via MCP or a direct API call.
|
|||
Developers should frame the Dialectic as talking to an expert on the Peer rather than addressing the Peer itself, meaning:
|
||||
|
||||
```python
|
||||
alice.chat("What is alice's mood like") # ✅ Correct
|
||||
alice.chat("What is the user's mood today?") # ✅ Ideal
|
||||
|
||||
alice.chat("What is your mood like") # ❌ Wrong
|
||||
alice.chat("What is alice's mood today?") # ✅ Works -- but make sure to consider what peer "Alice" has been saying in their messages about name/identity.
|
||||
|
||||
alice.chat("What is your mood today?") # ❌ Likely to fail -- the dialectic agent may conflate itself and the user.
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
---
|
||||
title: 'Configuration'
|
||||
description: 'Customizing how Honcho handles peers and sessions'
|
||||
icon: 'wrench'
|
||||
---
|
||||
|
||||
Entities in Honcho can sometimes be configured to change the behavior of the deriver, which is responsible for generating and storing facts, summaries, and user representations.
|
||||
|
||||
These configurations can be set at the peer, session, and session-peer level (AKA the state of a peer within a specific session).
|
||||
|
||||
### Peer Configuration
|
||||
|
||||
By default, all peers are "observed" by Honcho. This means that Honcho will derive facts from messages sent by the peer and generate a representation of them. In most cases, this is why you use Honcho! However, sometimes an application requires a peer that should not be observed: for example, an assistant or game NPC that your program will never need to ask questions about.
|
||||
|
||||
You may therefore disable observation of a peer by setting the `observe_me` flag in their configuration to `false`.
|
||||
|
||||
If the peer has a session-level configuration, it will override this configuration. If the flag is not set, or is set to `true`, the peer will be observed.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from honcho import Honcho
|
||||
|
||||
# Initialize client
|
||||
honcho = Honcho()
|
||||
|
||||
# Create peer with configuration
|
||||
peer = honcho.peer("my-peer", config={"observe_me": False})
|
||||
|
||||
# Change peer's configuration
|
||||
peer.set_peer_config({"observe_me": True})
|
||||
|
||||
# Note: creating the same peer again will also replace the configuration
|
||||
peer = honcho.peer("my-peer", config={"observe_me": False})
|
||||
```
|
||||
```typescript TypeScript
|
||||
import { Honcho } from "@honcho-ai/sdk";
|
||||
|
||||
(async () => {
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
|
||||
// Create peer with configuration
|
||||
const peer = await honcho.peer("my-peer", { config: { observe_me: false } });
|
||||
|
||||
// Change peer's configuration
|
||||
await peer.setPeerConfig({ observe_me: true });
|
||||
|
||||
// Note: creating the same peer again will also replace the configuration
|
||||
await honcho.peer("my-peer", { config: { observe_me: false } });
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Session Configuration
|
||||
|
||||
By default, all sessions have the deriver enabled, much like peers. You may create a session that escapes the deriver's watchful eye by setting the `deriver_disabled` flag to `true`. You can update the flag by calling `get_or_create` on the session with a new configuration.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from honcho import Honcho
|
||||
|
||||
# Initialize client
|
||||
honcho = Honcho()
|
||||
|
||||
# Create session with configuration
|
||||
session = honcho.session("my-session", config={"deriver_disabled": True})
|
||||
```
|
||||
```typescript TypeScript
|
||||
import { Honcho } from "@honcho-ai/sdk";
|
||||
|
||||
(async () => {
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
|
||||
// Create session with configuration
|
||||
const session = await honcho.session("my-session", { config: { deriver_disabled: true } });
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Session-Peer Configuration
|
||||
|
||||
Configuration at the session-peer level is the most common use case for configuration flags. You will often want to arrange a session such that certain peers observe others in order to form "local representations" of them. There are two flags that can be set at the session-peer level:
|
||||
|
||||
- `observe_me`: Whether this peer should *be observed* by others in the session. By default, this is `true`. This overrides the peer-level `observe_me` flag.
|
||||
|
||||
- `observe_others`: Whether this peer should produce local representations of others in the session. By default, this is `false`. Other peers will only be observed if their `observe_me` flag is `true`.
|
||||
|
||||
You can combine these flags across multiple peers to arrange any possible permutation of directional observation. Note that in the default case, no local representations are produced. To produce local representations, you must set the `observe_others` flag to `true` for at least one peer in the session and at least one other peer must have their `observe_me` flag set to `true`.
|
||||
|
||||
Many applications will work best without local representations, preferring to chat with Honcho's top-down representation of each peer. Only enable local representations via the `observe_others` flag if you are doing advanced reasoning on user perspectives.
|
||||
|
||||
<img src="/images/local-vs-global-reps.png" alt="Peer Representations" />
|
||||
|
||||
You can dynamically change the configuration of a session-peer by calling `set_peer_config` on the session with the peer and the configuration you want to set.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from honcho import Honcho
|
||||
|
||||
# Initialize client
|
||||
honcho = Honcho()
|
||||
|
||||
# Create session
|
||||
session = honcho.session("my-session")
|
||||
|
||||
# Create peers
|
||||
alice = honcho.peer("alice")
|
||||
bob = honcho.peer("bob")
|
||||
|
||||
# Add peers to session with default configuration
|
||||
session.add_peers([alice, bob])
|
||||
|
||||
# Add another peer to the session with a custom configuration
|
||||
charlie = honcho.peer("charlie")
|
||||
session.add_peers([charlie, {"observe_me": False, "observe_others": True}])
|
||||
|
||||
# Set session-peer configuration
|
||||
session.set_peer_config(alice, {"observe_others": True})
|
||||
session.set_peer_config(bob, {"observe_me": False})
|
||||
|
||||
# Get session-peer configuration
|
||||
charlie_config = session.get_peer_config(charlie)
|
||||
print(charlie_config)
|
||||
```
|
||||
```typescript TypeScript
|
||||
import { Honcho } from "@honcho-ai/sdk";
|
||||
|
||||
(async () => {
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
|
||||
// Create session
|
||||
const session = await honcho.session("my-session");
|
||||
|
||||
// Create peers
|
||||
const alice = await honcho.peer("alice");
|
||||
const bob = await honcho.peer("bob");
|
||||
|
||||
// Add peers to session
|
||||
await session.addPeers([alice, bob]);
|
||||
|
||||
// Add another peer to the session with a custom configuration
|
||||
const charlie = await honcho.peer("charlie");
|
||||
await session.addPeers([charlie, { observe_me: false, observe_others: true }]);
|
||||
|
||||
// Set session-peer configuration
|
||||
await session.setPeerConfig(alice, { observe_others: true });
|
||||
await session.setPeerConfig(bob, { observe_me: false });
|
||||
|
||||
// Get session-peer configuration
|
||||
const charlieConfig = await session.getPeerConfig(charlie);
|
||||
console.log(charlieConfig);
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
|
@ -19,9 +19,17 @@ To help developers understand when a Peer's representation is fully up to date,
|
|||
- If a Session is specified, the queue status reflects pending work for the Peer's working representation in that Session.
|
||||
|
||||
### Search
|
||||
Honcho supports full-text search across message content in different scopes.
|
||||
- You can search within a specific Session, Peer, or across all messages in a Workspace.
|
||||
- Search results are ordered by relevance, making it easy to quickly retrieve important past messages.
|
||||
Honcho implements a powerful search endpoint that allows you to search for messages across a workspace, session, or peer with complex [filters](/v2/guides/using-filters).
|
||||
|
||||
The search process combines full-text and semantic search using reciprocal rank fusion. By default, all messages ingested into Honcho have embeddings generated and stored in the database, enabling semantic search -- if this feature is disabled, the search process will only use full-text search.
|
||||
|
||||
Results are returned in the form of a list of Message objects, and you may choose how many results to return. The default is 10 results, with a maximum of 100.
|
||||
|
||||
In the SDK, search is available on `Workspace`, `Session`, and `Peer` objects, and an optional `filters` parameter may be used to apply a narrower search scope such as a time range or developer-defined metadata attached to messages.
|
||||
|
||||
Note that results are not ordered by recency, only relevance. Results can be sorted by timestamp or a filter on the `created_at` field can limit results to recent messages.
|
||||
|
||||
[Look here for examples of how to use search in the SDK](/v2/guides/search).
|
||||
|
||||
### Scoped API Keys
|
||||
Builders can create scoped API keys to control access to different resources within Honcho.
|
||||
|
|
|
|||
|
|
@ -67,4 +67,4 @@ cognitive functions.
|
|||
continually generating & updating internal world models to anticipate sensory input, rather than
|
||||
passively receiving it--closely linked to Bayesian brain hypotheses, which hold that the brain
|
||||
interprets the world probabilistically, weighing prior knowledge against new evidence to minimize
|
||||
uncertainty.
|
||||
uncertainty.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ Almost all agents require, in addition to personalization and memory, a way to q
|
|||
|
||||
### Creating Summaries
|
||||
|
||||
Honcho already has an asynchronous task queue for the purpose of deriving facts from messages. This is the ideal place to create summaries where they won't add latency to a message. Currently, Honcho has two configurable summary types:
|
||||
Honcho already has an asynchronous task queue for the purpose of deriving facts from messages. This is the ideal place to create summaries where they won't add latency to a message. Currently, Honcho has two configurable summary types:
|
||||
|
||||
* Short summaries: by default, enqueued every 20 messages and given a token limit of 1000
|
||||
* Long summaries: by default, enqueued every 60 messages and given a token limit of 4000
|
||||
|
|
@ -46,4 +46,4 @@ The above scenarios indicate where summarization is not possible -- therefore, t
|
|||
|
||||
Sometimes, gaps in context aren't an issue. In these cases, it's best to pass a reasonable token limit depending on your needs. Other cases demand exhaustive context -- don't pass a token limit and just let Honcho retrieve the ideal combination of summary and recent messages. Finally, if you don't care about the conversation at large and just want the last few messages, set `summary` to false and `tokens` to some multiple of your desired message count. Note that context messages are not paginated, so there's a hard limit on the number of messages that can be retrieved (currently 100,000 tokens).
|
||||
|
||||
As a final note, remember that summaries are generated asynchronously and therefore may not be available immediately. If you batch-save a large number of messages, assume that summaries will not be available until those messages are processed, which can take seconds to minutes depending on the number of messages and the configured LLM provider. Exhaustive `get_context` calls performed during this time will likely just return the messages in the session.
|
||||
As a final note, remember that summaries are generated asynchronously and therefore may not be available immediately. If you batch-save a large number of messages, assume that summaries will not be available until those messages are processed, which can take seconds to minutes depending on the number of messages and the configured LLM provider. Exhaustive `get_context` calls performed during this time will likely just return the messages in the session.
|
||||
|
|
|
|||
|
|
@ -143,15 +143,17 @@ print(response)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Ask what Bob is like
|
||||
const response = await alice.chat("Tell me about Bob's interests and habits");
|
||||
console.log(response);
|
||||
(async () => {
|
||||
// Ask what Bob is like
|
||||
const response = await alice.chat("Tell me about Bob's interests and habits");
|
||||
console.log(response);
|
||||
|
||||
// Returns rich context like:
|
||||
// "Bob is health-conscious and has been working on getting back in shape.
|
||||
// He regularly goes to the gym, particularly in the evenings, and finds
|
||||
// exercise helps him relax. He's encouraging about fitness and willing
|
||||
// to share advice about workout routines."
|
||||
// Returns rich context like:
|
||||
// "Bob is health-conscious and has been working on getting back in shape.
|
||||
// He regularly goes to the gym, particularly in the evenings, and finds
|
||||
// exercise helps him relax. He's encouraging about fitness and willing
|
||||
// to share advice about workout routines."
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -574,8 +574,8 @@ session.add_messages([
|
|||
])
|
||||
|
||||
# Filter messages by metadata
|
||||
finance_messages = session.get_messages(filter={"metadata": {"topic": "finance"}})
|
||||
action_items = session.get_messages(filter={"metadata": {"action_item": True}})
|
||||
finance_messages = session.get_messages(filters={"metadata": {"topic": "finance"}})
|
||||
action_items = session.get_messages(filters={"metadata": {"action_item": True}})
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
|
|
@ -597,10 +597,10 @@ await session.addMessages([
|
|||
|
||||
// Filter messages by metadata
|
||||
const financeMessages = await session.getMessages({
|
||||
filter: { metadata: { topic: "finance" } }
|
||||
filters: { metadata: { topic: "finance" } }
|
||||
});
|
||||
const actionItems = await session.getMessages({
|
||||
filter: { metadata: { action_item: true } }
|
||||
filters: { metadata: { action_item: true } }
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ session = honcho.session("demo-session")
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
import Honcho from '@honcho-ai/sdk';
|
||||
import { Honcho } from '@honcho-ai/sdk';
|
||||
|
||||
// use the default workspace
|
||||
const honcho = new Honcho();
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ async def dialectic(ctx, query: str):
|
|||
session = honcho_client.session(id=str(ctx.channel.id))
|
||||
|
||||
response = peer.chat(
|
||||
queries=query,
|
||||
query=query,
|
||||
session_id=session.id,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ Files are processed in memory and not stored on disk. Only the extracted text co
|
|||
|
||||
## Basic Usage
|
||||
|
||||
### Upload a Single File
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
from honcho import Honcho
|
||||
|
|
@ -59,45 +57,20 @@ print(f"Created {len(messages)} messages from the PDF")
|
|||
import { Honcho } from "@honcho-ai/sdk";
|
||||
import fs from "fs";
|
||||
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
(async () => {
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
|
||||
// Create session and peer
|
||||
const session = honcho.session("research-session");
|
||||
const user = honcho.peer("researcher");
|
||||
// Create session and peer
|
||||
const session = await honcho.session("research-session");
|
||||
const user = await honcho.peer("researcher");
|
||||
|
||||
// Upload a PDF to a session
|
||||
const fileStream = fs.createReadStream("research_paper.pdf");
|
||||
const messages = await session.uploadFile({
|
||||
file: fileStream,
|
||||
peerId: user.id,
|
||||
});
|
||||
// Upload a PDF to a session
|
||||
const fileStream = fs.createReadStream("research_paper.pdf");
|
||||
const messages = await session.uploadFile(fileStream, user.id);
|
||||
|
||||
console.log(`Created ${messages.length} messages from the PDF`);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Upload to Peer's Global Representation
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
# Upload files directly to a peer's global representation
|
||||
with open("personal_notes.pdf", "rb") as file:
|
||||
messages = user.upload_file(
|
||||
file=file,
|
||||
)
|
||||
|
||||
print(f"Added {len(messages)} messages to {user.id}'s global representation")
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Upload files directly to a peer's global representation
|
||||
const fileStream = fs.createReadStream("personal_notes.pdf");
|
||||
const messages = await user.uploadFile({
|
||||
file: fileStream,
|
||||
});
|
||||
|
||||
console.log(`Added ${messages.length} messages to ${user.id}'s global representation`);
|
||||
console.log(`Created ${messages.length} messages from the PDF`);
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -108,7 +81,7 @@ The upload methods accept the following parameters:
|
|||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `file` | File | Yes | File to upload |
|
||||
| `peer_id` | String | Session only | ID of the peer creating the messages |
|
||||
| `peer_id` | String | Yes | ID of the peer creating the messages |
|
||||
|
||||
## File Processing Details
|
||||
|
||||
|
|
@ -160,17 +133,19 @@ messages = context.to_openai(assistant=assistant)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Query what was learned from the uploaded documents
|
||||
const response = await user.chat("What are the key findings from the research papers I uploaded?");
|
||||
console.log(response);
|
||||
(async () => {
|
||||
// Query what was learned from the uploaded documents
|
||||
const response = await user.chat("What are the key findings from the research papers I uploaded?");
|
||||
console.log(response);
|
||||
|
||||
// Ask about specific documents
|
||||
const response2 = await user.chat("What does the quarterly report say about revenue growth?");
|
||||
console.log(response2);
|
||||
// Ask about specific documents
|
||||
const response2 = await user.chat("What does the quarterly report say about revenue growth?");
|
||||
console.log(response2);
|
||||
|
||||
// Get context from the uploaded documents for LLM integration
|
||||
const context = await session.getContext({ tokens: 3000 });
|
||||
const messages = context.toOpenAI(assistant);
|
||||
// Get context from the uploaded documents for LLM integration
|
||||
const context = await session.getContext({ tokens: 3000 });
|
||||
const messages = context.toOpenAI(assistant);
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -262,52 +237,51 @@ print("Document Analysis:", analysis)
|
|||
import { Honcho } from "@honcho-ai/sdk";
|
||||
import fs from "fs";
|
||||
|
||||
// Initialize
|
||||
const honcho = new Honcho({});
|
||||
const session = honcho.session("document-analysis");
|
||||
const user = honcho.peer("analyst");
|
||||
const assistant = honcho.peer("analysis-bot");
|
||||
(async () => {
|
||||
// Initialize
|
||||
const honcho = new Honcho({});
|
||||
const session = await honcho.session("document-analysis");
|
||||
const user = await honcho.peer("analyst");
|
||||
const assistant = await honcho.peer("analysis-bot");
|
||||
|
||||
async function uploadDocument(filePath: string, description: string) {
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
const messages = await session.uploadFile({
|
||||
file: fileStream,
|
||||
peerId: user.id,
|
||||
});
|
||||
return messages;
|
||||
}
|
||||
async function uploadDocument(filePath: string, description: string) {
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
const messages = await session.uploadFile(fileStream, user.id);
|
||||
return messages;
|
||||
}
|
||||
|
||||
async function analyzeDocuments() {
|
||||
const context = await session.getContext({ tokens: 4000 });
|
||||
const messages = context.toOpenAI(assistant);
|
||||
// Add analysis request
|
||||
messages.push({
|
||||
role: "user",
|
||||
content: "Please analyze all the documents I've uploaded and provide a comprehensive summary of the key findings, trends, and recommendations."
|
||||
});
|
||||
async function analyzeDocuments() {
|
||||
const context = await session.getContext({ tokens: 4000 });
|
||||
const messages = context.toOpenAI(assistant);
|
||||
// Add analysis request
|
||||
messages.push({
|
||||
role: "user",
|
||||
content: "Please analyze all the documents I've uploaded and provide a comprehensive summary of the key findings, trends, and recommendations."
|
||||
});
|
||||
|
||||
// Call OpenAI (or your preferred LLM)
|
||||
// const response = await openai.chat.completions.create({ model: "gpt-4", messages });
|
||||
// return response.choices[0].message.content;
|
||||
// Call OpenAI (or your preferred LLM)
|
||||
// const response = await openai.chat.completions.create({ model: "gpt-4", messages });
|
||||
// return response.choices[0].message.content;
|
||||
|
||||
return "Analysis would be generated here";
|
||||
}
|
||||
return "Analysis would be generated here";
|
||||
}
|
||||
|
||||
// Upload multiple documents
|
||||
const documents = [
|
||||
["quarterly_report.pdf", "Q3 2024 Quarterly Financial Report"],
|
||||
["market_research.pdf", "Market Analysis and Competitive Landscape"],
|
||||
["product_roadmap.pdf", "Product Development Roadmap 2024-2025"]
|
||||
];
|
||||
// Upload multiple documents
|
||||
const documents = [
|
||||
["quarterly_report.pdf", "Q3 2024 Quarterly Financial Report"],
|
||||
["market_research.pdf", "Market Analysis and Competitive Landscape"],
|
||||
["product_roadmap.pdf", "Product Development Roadmap 2024-2025"]
|
||||
];
|
||||
|
||||
for (const [filePath, description] of documents) {
|
||||
const messages = await uploadDocument(filePath, description);
|
||||
console.log(`Uploaded ${filePath}: ${messages.length} messages created`);
|
||||
}
|
||||
for (const [filePath, description] of documents) {
|
||||
const messages = await uploadDocument(filePath, description);
|
||||
console.log(`Uploaded ${filePath}: ${messages.length} messages created`);
|
||||
}
|
||||
|
||||
// Get AI analysis
|
||||
const analysis = await analyzeDocuments();
|
||||
console.log("Document Analysis:", analysis);
|
||||
// Get AI analysis
|
||||
const analysis = await analyzeDocuments();
|
||||
console.log("Document Analysis:", analysis);
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -27,12 +27,14 @@ context = session.get_context()
|
|||
```typescript TypeScript
|
||||
import { Honcho } from "@honcho-ai/sdk";
|
||||
|
||||
// Initialize client and create session
|
||||
const honcho = new Honcho({});
|
||||
const session = honcho.session("conversation-1");
|
||||
(async () => {
|
||||
// Initialize client and create session
|
||||
const honcho = new Honcho({});
|
||||
const session = await honcho.session("conversation-1");
|
||||
|
||||
// Get basic context (not very useful before adding any messages!)
|
||||
const context = await session.getContext();
|
||||
// Get basic context (not very useful before adding any messages!)
|
||||
const context = await session.getContext();
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -54,11 +56,13 @@ context = session.get_context(tokens=3000)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Limit context to 1500 tokens
|
||||
const context = await session.getContext({ tokens: 1500 });
|
||||
(async () => {
|
||||
// Limit context to 1500 tokens
|
||||
const context = await session.getContext({ tokens: 1500 });
|
||||
|
||||
// Limit context to 3000 tokens for larger conversations
|
||||
const context = await session.getContext({ tokens: 3000 });
|
||||
// Limit context to 3000 tokens for larger conversations
|
||||
const context = await session.getContext({ tokens: 3000 });
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -76,14 +80,16 @@ context = session.get_context(summary=False, tokens=2000)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Get context with summary enabled -- will contain both summary and messages
|
||||
const context = await session.getContext({ summary: true });
|
||||
(async () => {
|
||||
// Get context with summary enabled -- will contain both summary and messages
|
||||
const context = await session.getContext({ summary: true });
|
||||
|
||||
// Combine summary=False with token limits to get more messages
|
||||
const context = await session.getContext({
|
||||
summary: false,
|
||||
tokens: 2000
|
||||
});
|
||||
// Combine summary=False with token limits to get more messages
|
||||
const context = await session.getContext({
|
||||
summary: false,
|
||||
tokens: 2000
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -120,26 +126,28 @@ print(openai_messages)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Create peers
|
||||
const alice = honcho.peer("alice");
|
||||
const assistant = honcho.peer("assistant");
|
||||
(async () => {
|
||||
// Create peers
|
||||
const alice = await honcho.peer("alice");
|
||||
const assistant = await honcho.peer("assistant");
|
||||
|
||||
// Add some conversation
|
||||
await session.addMessages([
|
||||
alice.message("What's the weather like today?"),
|
||||
assistant.message("It's sunny and 75°F outside!")
|
||||
]);
|
||||
// Add some conversation
|
||||
await session.addMessages([
|
||||
alice.message("What's the weather like today?"),
|
||||
assistant.message("It's sunny and 75°F outside!")
|
||||
]);
|
||||
|
||||
// Get context and convert to OpenAI format
|
||||
const context = await session.getContext();
|
||||
const openaiMessages = context.toOpenAI(assistant);
|
||||
// Get context and convert to OpenAI format
|
||||
const context = await session.getContext();
|
||||
const openaiMessages = context.toOpenAI(assistant);
|
||||
|
||||
// The messages are now ready for OpenAI API
|
||||
console.log(openaiMessages);
|
||||
// [
|
||||
// {"role": "user", "content": "What's the weather like today?"},
|
||||
// {"role": "assistant", "content": "It's sunny and 75°F outside!"}
|
||||
// ]
|
||||
// The messages are now ready for OpenAI API
|
||||
console.log(openaiMessages);
|
||||
// [
|
||||
// {"role": "user", "content": "What's the weather like today?"},
|
||||
// {"role": "assistant", "content": "It's sunny and 75°F outside!"}
|
||||
// ]
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -158,12 +166,14 @@ print(anthropic_messages)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Get context and convert to Anthropic format
|
||||
const context = await session.getContext();
|
||||
const anthropicMessages = context.toAnthropic(assistant);
|
||||
(async () => {
|
||||
// Get context and convert to Anthropic format
|
||||
const context = await session.getContext();
|
||||
const anthropicMessages = context.toAnthropic(assistant);
|
||||
|
||||
// Ready for Anthropic API
|
||||
console.log(anthropicMessages);
|
||||
// Ready for Anthropic API
|
||||
console.log(anthropicMessages);
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -217,39 +227,41 @@ session.add_messages([
|
|||
import OpenAI from 'openai';
|
||||
import { Honcho } from "@honcho-ai/sdk";
|
||||
|
||||
// Initialize clients
|
||||
const honcho = new Honcho({});
|
||||
const openai = new OpenAI();
|
||||
(async () => {
|
||||
// Initialize clients
|
||||
const honcho = new Honcho({});
|
||||
const openai = new OpenAI();
|
||||
|
||||
// Set up conversation
|
||||
const session = honcho.session("support-chat");
|
||||
const user = honcho.peer("user-123");
|
||||
const assistant = honcho.peer("support-bot");
|
||||
// Set up conversation
|
||||
const session = honcho.session("support-chat");
|
||||
const user = honcho.peer("user-123");
|
||||
const assistant = honcho.peer("support-bot");
|
||||
|
||||
// Add conversation history
|
||||
await session.addMessages([
|
||||
user.message("I'm having trouble with my account login"),
|
||||
assistant.message("I can help you with that. What error message are you seeing?"),
|
||||
user.message("It says 'Invalid credentials' but I'm sure my password is correct")
|
||||
]);
|
||||
// Add conversation history
|
||||
await session.addMessages([
|
||||
user.message("I'm having trouble with my account login"),
|
||||
assistant.message("I can help you with that. What error message are you seeing?"),
|
||||
user.message("It says 'Invalid credentials' but I'm sure my password is correct")
|
||||
]);
|
||||
|
||||
// Get context for LLM
|
||||
const messages = await session.getContext({ tokens: 2000 }).toOpenAI(assistant);
|
||||
// Get context for LLM
|
||||
const messages = await session.getContext({ tokens: 2000 }).toOpenAI(assistant);
|
||||
|
||||
// Add new user message and get AI response
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
messages: [
|
||||
...messages,
|
||||
{ role: "user", content: "Can you reset my password?" }
|
||||
]
|
||||
});
|
||||
// Add new user message and get AI response
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
messages: [
|
||||
...messages,
|
||||
{ role: "user", content: "Can you reset my password?" }
|
||||
]
|
||||
});
|
||||
|
||||
// Add AI response back to session
|
||||
await session.addMessages([
|
||||
user.message("Can you reset my password?"),
|
||||
assistant.message(response.choices[0].message.content)
|
||||
]);
|
||||
// Add AI response back to session
|
||||
await session.addMessages([
|
||||
user.message("Can you reset my password?"),
|
||||
assistant.message(response.choices[0].message.content)
|
||||
]);
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -294,44 +306,46 @@ chat_loop()
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
async function chatLoop() {
|
||||
const session = honcho.session("chat-session");
|
||||
const user = honcho.peer("user");
|
||||
const assistant = honcho.peer("ai-assistant");
|
||||
(async () => {
|
||||
async function chatLoop() {
|
||||
const session = await honcho.session("chat-session");
|
||||
const user = await honcho.peer("user");
|
||||
const assistant = await honcho.peer("ai-assistant");
|
||||
|
||||
// This would be replaced with actual user input handling in a real app
|
||||
const userInputs = [
|
||||
"Hello, how are you?",
|
||||
"What's the weather like?",
|
||||
"Tell me a joke"
|
||||
];
|
||||
// This would be replaced with actual user input handling in a real app
|
||||
const userInputs = [
|
||||
"Hello, how are you?",
|
||||
"What's the weather like?",
|
||||
"Tell me a joke"
|
||||
];
|
||||
|
||||
for (const userInput of userInputs) {
|
||||
console.log(`You: ${userInput}`);
|
||||
for (const userInput of userInputs) {
|
||||
console.log(`You: ${userInput}`);
|
||||
|
||||
// Add user message to session
|
||||
await session.addMessages([user.message(userInput)]);
|
||||
// Add user message to session
|
||||
await session.addMessages([user.message(userInput)]);
|
||||
|
||||
// Get conversation context
|
||||
const context = await session.getContext({ tokens: 2000 });
|
||||
const messages = context.toOpenAI(assistant);
|
||||
// Get conversation context
|
||||
const context = await session.getContext({ tokens: 2000 });
|
||||
const messages = context.toOpenAI(assistant);
|
||||
|
||||
// Get AI response
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
messages: messages
|
||||
});
|
||||
// Get AI response
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
messages: messages
|
||||
});
|
||||
|
||||
const aiResponse = response.choices[0].message.content;
|
||||
console.log(`Assistant: ${aiResponse}`);
|
||||
const aiResponse = response.choices[0].message.content;
|
||||
console.log(`Assistant: ${aiResponse}`);
|
||||
|
||||
// Add AI response to session
|
||||
await session.addMessages([assistant.message(aiResponse)]);
|
||||
}
|
||||
}
|
||||
// Add AI response to session
|
||||
await session.addMessages([assistant.message(aiResponse)]);
|
||||
}
|
||||
}
|
||||
|
||||
// Start the chat loop
|
||||
await chatLoop();
|
||||
// Start the chat loop
|
||||
await chatLoop();
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -355,18 +369,20 @@ print(f"Context contains {len(messages)} formatted messages")
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// For long conversations, use summary mode
|
||||
const longSession = honcho.session("long-conversation");
|
||||
(async () => {
|
||||
// For long conversations, use summary mode
|
||||
const longSession = await honcho.session("long-conversation");
|
||||
|
||||
// Get summarized context to fit within token limits
|
||||
const context = await longSession.getContext({
|
||||
summary: true,
|
||||
tokens: 1500
|
||||
});
|
||||
const messages = context.toOpenAI(assistant);
|
||||
// Get summarized context to fit within token limits
|
||||
const context = await longSession.getContext({
|
||||
summary: true,
|
||||
tokens: 1500
|
||||
});
|
||||
const messages = context.toOpenAI(assistant);
|
||||
|
||||
// This will include a summary of older messages and recent full messages
|
||||
console.log(`Context contains ${messages.length} formatted messages`);
|
||||
// This will include a summary of older messages and recent full messages
|
||||
console.log(`Context contains ${messages.length} formatted messages`);
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -390,18 +406,20 @@ moderator_context = session.get_context().to_openai(assistant=moderator)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Create different assistant peers
|
||||
const chatbot = honcho.peer("chatbot");
|
||||
const analyzer = honcho.peer("data-analyzer");
|
||||
const moderator = honcho.peer("moderator");
|
||||
(async () => {
|
||||
// Create different assistant peers
|
||||
const chatbot = await honcho.peer("chatbot");
|
||||
const analyzer = await honcho.peer("data-analyzer");
|
||||
const moderator = await honcho.peer("moderator");
|
||||
|
||||
// Get context formatted for each assistant type
|
||||
const context = await session.getContext();
|
||||
const chatbotContext = context.toOpenAI(chatbot);
|
||||
const analyzerContext = context.toOpenAI(analyzer);
|
||||
const moderatorContext = context.toOpenAI(moderator);
|
||||
// Get context formatted for each assistant type
|
||||
const context = await session.getContext();
|
||||
const chatbotContext = context.toOpenAI(chatbot);
|
||||
const analyzerContext = context.toOpenAI(analyzer);
|
||||
const moderatorContext = context.toOpenAI(moderator);
|
||||
|
||||
// Each context will format the conversation from that assistant's perspective
|
||||
// Each context will format the conversation from that assistant's perspective
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -422,12 +440,14 @@ context = session.get_context(summary=True, tokens=2000)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Good: Set reasonable token limits based on your model
|
||||
const context = await session.getContext({ tokens: 3000 }); // For GPT-4
|
||||
const context = await session.getContext({ tokens: 1500 }); // For smaller models
|
||||
(async () => {
|
||||
// Good: Set reasonable token limits based on your model
|
||||
const context = await session.getContext({ tokens: 3000 }); // For GPT-4
|
||||
const context = await session.getContext({ tokens: 1500 }); // For smaller models
|
||||
|
||||
// Good: Use summaries for very long conversations
|
||||
const context = await session.getContext({ summary: true, tokens: 2000 });
|
||||
// Good: Use summaries for very long conversations
|
||||
const context = await session.getContext({ summary: true, tokens: 2000 });
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -446,12 +466,14 @@ anthropic_messages = context.to_anthropic(assistant=assistant)
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Cache context for multiple LLM calls within the same request
|
||||
const context = await session.getContext({ tokens: 2000 });
|
||||
const openaiMessages = context.toOpenAI(assistant);
|
||||
const anthropicMessages = context.toAnthropic(assistant);
|
||||
(async () => {
|
||||
// Cache context for multiple LLM calls within the same request
|
||||
const context = await session.getContext({ tokens: 2000 });
|
||||
const openaiMessages = context.toOpenAI(assistant);
|
||||
const anthropicMessages = context.toAnthropic(assistant);
|
||||
|
||||
// Use the same context object for multiple format conversions
|
||||
// Use the same context object for multiple format conversions
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -477,20 +499,22 @@ except Exception as e:
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
try {
|
||||
const context = await session.getContext({ tokens: 2000 });
|
||||
const messages = context.toOpenAI(assistant);
|
||||
(async () => {
|
||||
try {
|
||||
const context = await session.getContext({ tokens: 2000 });
|
||||
const messages = context.toOpenAI(assistant);
|
||||
|
||||
// Use messages with LLM API
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
messages: messages
|
||||
});
|
||||
// Use messages with LLM API
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
messages: messages
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error getting context: ${error}`);
|
||||
// Handle error appropriately
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error getting context: ${error}`);
|
||||
// Handle error appropriately
|
||||
}
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,6 @@ icon: 'magnifying-glass'
|
|||
|
||||
Honcho's search functionality allows you to find relevant messages and conversations across different scopes - from entire workspaces down to specific peers or sessions.
|
||||
|
||||
## How Search Works
|
||||
|
||||
Search in Honcho is implemented with a two-tier approach:
|
||||
1. **Primary**: PostgreSQL English language full-text search index for intelligent matching
|
||||
2. **Fallback**: Simple string matching for broader coverage
|
||||
|
||||
All search results are returned in a paginated format, making it easy to handle large result sets efficiently.
|
||||
|
||||
## Search Scopes
|
||||
|
||||
### Workspace Search
|
||||
|
|
@ -38,16 +30,18 @@ for result in results:
|
|||
```typescript TypeScript
|
||||
import { Honcho } from "@honcho-ai/sdk";
|
||||
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
(async () => {
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
|
||||
// Search across entire workspace
|
||||
const results = await honcho.search("budget planning");
|
||||
// Search across entire workspace
|
||||
const results = await honcho.search("budget planning");
|
||||
|
||||
// Iterate through all results
|
||||
for await (const result of results) {
|
||||
console.log(`Found: ${result}`);
|
||||
}
|
||||
// Iterate through all results
|
||||
for (const result of results) {
|
||||
console.log(`Found: ${result}`);
|
||||
}
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -69,16 +63,18 @@ for result in results:
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Create or get a session
|
||||
const session = honcho.session("team-meeting-jan");
|
||||
(async () => {
|
||||
// Create or get a session
|
||||
const session = await honcho.session("team-meeting-jan");
|
||||
|
||||
// Search within this session only
|
||||
const results = await session.search("action items");
|
||||
// Search within this session only
|
||||
const results = await session.search("action items");
|
||||
|
||||
// Process results
|
||||
for await (const result of results) {
|
||||
console.log(`Session result: ${result}`);
|
||||
}
|
||||
// Process results
|
||||
for (const result of results) {
|
||||
console.log(`Session result: ${result}`);
|
||||
}
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -100,160 +96,90 @@ for result in results:
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Create or get a peer
|
||||
const alice = honcho.peer("alice");
|
||||
import { Message } from "@honcho-ai/sdk";
|
||||
|
||||
// Search across all of Alice's messages and interactions
|
||||
const results = await alice.search("programming");
|
||||
(async () => {
|
||||
// Create or get a peer
|
||||
const alice = await honcho.peer("alice");
|
||||
|
||||
// View results
|
||||
for await (const result of results) {
|
||||
console.log(`Alice's content: ${result}`);
|
||||
}
|
||||
// Search across all of Alice's messages and interactions
|
||||
const results: Message[] = await alice.search("programming");
|
||||
|
||||
// View results
|
||||
for (const result of results) {
|
||||
console.log(`Alice's content: ${result.content}`);
|
||||
}
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Working with Search Results
|
||||
## Filters and Limits
|
||||
|
||||
### Basic Result Processing
|
||||
### Get a specific number of results
|
||||
|
||||
You can specify the number of results you want to return by passing the `limit` parameter to the search method. The default is 10 results, with a maximum of 100.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
# Search returns a paginated iterator
|
||||
results = honcho.search("customer feedback")
|
||||
|
||||
# Simple iteration processes all results automatically
|
||||
for result in results:
|
||||
# Each result contains the matched content and context
|
||||
print(f"Match: {result}")
|
||||
|
||||
# Check if there are any results
|
||||
results = honcho.search("nonexistent topic")
|
||||
result_list = list(results)
|
||||
if not result_list:
|
||||
print("No results found")
|
||||
results = honcho.search("budget planning", limit=20)
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Search returns a paginated Page object
|
||||
const results = await honcho.search("customer feedback");
|
||||
|
||||
// Iterate through all results
|
||||
for await (const result of results) {
|
||||
// Each result contains the matched content and context
|
||||
console.log(`Match: ${result}`);
|
||||
}
|
||||
|
||||
// Check if there are any results
|
||||
const emptyResults = await honcho.search("nonexistent topic");
|
||||
const resultData = await emptyResults.data();
|
||||
if (resultData.length === 0) {
|
||||
console.log("No results found");
|
||||
}
|
||||
(async () => {
|
||||
const results = await honcho.search("budget planning", { limit: 20 });
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Manual Pagination
|
||||
### Get messages from a Peer in a specific Session
|
||||
|
||||
Combine Peer-level search with a `session_id` filter to get messages from a Peer in a specific Session.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
# For manual pagination control, you can work with pages directly
|
||||
results = honcho.search("project updates")
|
||||
|
||||
# The iterator handles pagination automatically, but you can also
|
||||
# work with individual batches if needed
|
||||
count = 0
|
||||
for result in results:
|
||||
count += 1
|
||||
print(f"Result {count}: {result}")
|
||||
|
||||
# Stop after first 10 results
|
||||
if count >= 10:
|
||||
break
|
||||
my_peer = honcho.peer("my-peer")
|
||||
my_session = honcho.session("team-meeting-jan")
|
||||
results = my_peer.search("budget planning", filters={"session_id": my_session.id})
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Manual pagination with TypeScript
|
||||
let currentPage = await honcho.search("project updates");
|
||||
|
||||
while (currentPage) {
|
||||
const data = await currentPage.data();
|
||||
console.log(`Processing ${data.length} results`);
|
||||
|
||||
for (const result of data) {
|
||||
console.log(`Result: ${result}`);
|
||||
}
|
||||
|
||||
// Get next page
|
||||
currentPage = await currentPage.nextPage();
|
||||
}
|
||||
(async () => {
|
||||
const my_peer = await honcho.peer("my-peer");
|
||||
const my_session = await honcho.session("team-meeting-jan");
|
||||
const results = await my_peer.search("budget planning", { filters: { session_id: my_session.id } });
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Search with Context Building
|
||||
### Filter results by time range
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
# Use search results to build context for LLM interactions
|
||||
def build_context_from_search(query: str, session_id: str):
|
||||
session = honcho.session(session_id)
|
||||
|
||||
# Search for relevant past discussions
|
||||
search_results = list(session.search(query))
|
||||
|
||||
if search_results:
|
||||
# Use search results to inform context
|
||||
context_summary = f"Found {len(search_results)} relevant past discussions about '{query}'"
|
||||
|
||||
# Get normal session context
|
||||
session_context = session.get_context(tokens=1500)
|
||||
|
||||
return {
|
||||
"search_summary": context_summary,
|
||||
"session_context": session_context,
|
||||
"search_results": search_results[:3] # Top 3 results
|
||||
}
|
||||
|
||||
return {"message": "No relevant past discussions found"}
|
||||
|
||||
# Build context for a new question
|
||||
context_data = build_context_from_search("user authentication", "support-session-1")
|
||||
print(f"Context: {context_data.get('search_summary', 'No context')}")
|
||||
results = honcho.search("budget planning", filters={"created_at": {"gte": "2024-01-01", "lte": "2024-01-31"}})
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Use search results to build context for LLM interactions
|
||||
async function buildContextFromSearch(query: string, sessionId: string) {
|
||||
const session = honcho.session(sessionId);
|
||||
|
||||
// Search for relevant past discussions
|
||||
const searchResults = await session.search(query);
|
||||
const searchData = await searchResults.data();
|
||||
|
||||
if (searchData.length > 0) {
|
||||
// Use search results to inform context
|
||||
const contextSummary = `Found ${searchData.length} relevant past discussions about '${query}'`;
|
||||
|
||||
// Get normal session context
|
||||
const sessionContext = await session.getContext({ tokens: 1500 });
|
||||
|
||||
return {
|
||||
searchSummary: contextSummary,
|
||||
sessionContext: sessionContext,
|
||||
searchResults: searchData.slice(0, 3) // Top 3 results
|
||||
};
|
||||
}
|
||||
|
||||
return { message: "No relevant past discussions found" };
|
||||
}
|
||||
|
||||
// Build context for a new question
|
||||
const contextData = await buildContextFromSearch("user authentication", "support-session-1");
|
||||
console.log(`Context: ${contextData.searchSummary || contextData.message}`);
|
||||
(async () => {
|
||||
const results = await honcho.search("budget planning", { filters: { created_at: { gte: "2024-01-01", lte: "2024-01-31" } } });
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best Practices
|
||||
### Filter results by metadata
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
results = honcho.search("budget planning", filters={"metadata": {"key": "value"}})
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
(async () => {
|
||||
const results = await honcho.search("budget planning", { filters: { metadata: { key: "value" } } });
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Best Practices
|
||||
|
||||
### Handle Empty Results Gracefully
|
||||
|
||||
|
|
@ -272,18 +198,21 @@ else:
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Always check for empty results
|
||||
const results = await honcho.search("very specific query");
|
||||
const resultData = await results.data();
|
||||
import { Message } from "@honcho-ai/sdk";
|
||||
|
||||
if (resultData.length > 0) {
|
||||
console.log(`Found ${resultData.length} results`);
|
||||
for (const result of resultData) {
|
||||
console.log(`- ${result}`);
|
||||
}
|
||||
} else {
|
||||
console.log("No results found - try a broader search");
|
||||
}
|
||||
(async () => {
|
||||
// Always check for empty results
|
||||
const results: Message[] = await honcho.search("very specific query");
|
||||
|
||||
if (results.length > 0) {
|
||||
console.log(`Found ${results.length} results`);
|
||||
for (const result of results) {
|
||||
console.log(`- ${result.content}`);
|
||||
}
|
||||
} else {
|
||||
console.log("No results found - try a broader search");
|
||||
}
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -48,23 +48,25 @@ session.add_messages([
|
|||
```typescript TypeScript
|
||||
import { Honcho } from '@honcho-ai/sdk';
|
||||
|
||||
// Initialize client (using the default workspace)
|
||||
const honcho = new Honcho();
|
||||
(async () => {
|
||||
// Initialize client (using the default workspace)
|
||||
const honcho = new Honcho();
|
||||
|
||||
// Create or get peers
|
||||
const user = honcho.peer('demo-user');
|
||||
const assistant = honcho.peer('assistant');
|
||||
// Create or get peers
|
||||
const user = await honcho.peer('demo-user');
|
||||
const assistant = await honcho.peer('assistant');
|
||||
|
||||
// Create a new session
|
||||
const session = honcho.session('demo-session');
|
||||
// Create a new session
|
||||
const session = await honcho.session('demo-session');
|
||||
|
||||
// Add peers to the session
|
||||
await session.addPeers([user, assistant]);
|
||||
// Add peers to the session
|
||||
await session.addPeers([user, assistant]);
|
||||
|
||||
// Store some messages for context (optional)
|
||||
await session.addMessages([
|
||||
user.message("Hello, I'm testing the streaming functionality")
|
||||
]);
|
||||
// Store some messages for context (optional)
|
||||
await session.addMessages([
|
||||
user.message("Hello, I'm testing the streaming functionality")
|
||||
]);
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -83,15 +85,17 @@ for chunk in response_stream.iter_text():
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Basic streaming example
|
||||
const responseStream = await user.chat("What can you tell me about this user?", {
|
||||
stream: true
|
||||
});
|
||||
(async () => {
|
||||
// Basic streaming example
|
||||
const responseStream = await user.chat("What can you tell me about this user?", {
|
||||
stream: true
|
||||
});
|
||||
|
||||
// Process the stream
|
||||
for await (const chunk of responseStream.iter_text()) {
|
||||
process.stdout.write(chunk); // Write to console without newlines
|
||||
}
|
||||
// Process the stream
|
||||
for await (const chunk of responseStream.iter_text()) {
|
||||
process.stdout.write(chunk); // Write to console without newlines
|
||||
}
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -171,63 +175,65 @@ if __name__ == "__main__":
|
|||
```typescript TypeScript
|
||||
import { Honcho } from '@honcho-ai/sdk';
|
||||
|
||||
async function restaurantRecommendationChat() {
|
||||
// Initialize client
|
||||
const honcho = new Honcho();
|
||||
(async () => {
|
||||
async function restaurantRecommendationChat() {
|
||||
// Initialize client
|
||||
const honcho = new Honcho();
|
||||
|
||||
// Create peers
|
||||
const user = honcho.peer('food-lover');
|
||||
const assistant = honcho.peer('restaurant-assistant');
|
||||
// Create peers
|
||||
const user = await honcho.peer('food-lover');
|
||||
const assistant = await honcho.peer('restaurant-assistant');
|
||||
|
||||
// Create session
|
||||
const session = honcho.session('food-preferences-session');
|
||||
// Create session
|
||||
const session = await honcho.session('food-preferences-session');
|
||||
|
||||
// Add peers to session
|
||||
await session.addPeers([user, assistant]);
|
||||
// Add peers to session
|
||||
await session.addPeers([user, assistant]);
|
||||
|
||||
// Store multiple user messages about food preferences
|
||||
const userMessages = [
|
||||
"I absolutely love spicy Thai food, especially curries with coconut milk.",
|
||||
"Italian cuisine is another favorite - fresh pasta and wood-fired pizza are my weakness!",
|
||||
"I try to eat vegetarian most of the time, but occasionally enjoy seafood.",
|
||||
"I can't handle overly sweet desserts, but love something with dark chocolate."
|
||||
];
|
||||
// Store multiple user messages about food preferences
|
||||
const userMessages = [
|
||||
"I absolutely love spicy Thai food, especially curries with coconut milk.",
|
||||
"Italian cuisine is another favorite - fresh pasta and wood-fired pizza are my weakness!",
|
||||
"I try to eat vegetarian most of the time, but occasionally enjoy seafood.",
|
||||
"I can't handle overly sweet desserts, but love something with dark chocolate."
|
||||
];
|
||||
|
||||
// Add the user's messages to the session
|
||||
const sessionMessages = userMessages.map(message => user.message(message));
|
||||
await session.addMessages(sessionMessages);
|
||||
// Add the user's messages to the session
|
||||
const sessionMessages = userMessages.map(message => user.message(message));
|
||||
await session.addMessages(sessionMessages);
|
||||
|
||||
// Print the user messages
|
||||
for (const message of userMessages) {
|
||||
console.log(`User: ${message}`);
|
||||
}
|
||||
// Print the user messages
|
||||
for (const message of userMessages) {
|
||||
console.log(`User: ${message}`);
|
||||
}
|
||||
|
||||
// Ask for restaurant recommendations based on preferences
|
||||
console.log("\nRequesting restaurant recommendations...");
|
||||
process.stdout.write("Assistant: ");
|
||||
let fullResponse = "";
|
||||
// Ask for restaurant recommendations based on preferences
|
||||
console.log("\nRequesting restaurant recommendations...");
|
||||
process.stdout.write("Assistant: ");
|
||||
let fullResponse = "";
|
||||
|
||||
// Stream the response using the user's peer to get recommendations
|
||||
const responseStream = await user.chat(
|
||||
"Based on this user's food preferences, recommend 3 restaurants they might enjoy in the Lower East Side.",
|
||||
{
|
||||
stream: true,
|
||||
sessionId: session.id
|
||||
// Stream the response using the user's peer to get recommendations
|
||||
const responseStream = await user.chat(
|
||||
"Based on this user's food preferences, recommend 3 restaurants they might enjoy in the Lower East Side.",
|
||||
{
|
||||
stream: true,
|
||||
sessionId: session.id
|
||||
}
|
||||
);
|
||||
|
||||
for await (const chunk of responseStream.iter_text()) {
|
||||
process.stdout.write(chunk);
|
||||
fullResponse += chunk;
|
||||
}
|
||||
|
||||
// Store the assistant's complete response
|
||||
await session.addMessages([
|
||||
assistant.message(fullResponse)
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
for await (const chunk of responseStream.iter_text()) {
|
||||
process.stdout.write(chunk);
|
||||
fullResponse += chunk;
|
||||
}
|
||||
|
||||
// Store the assistant's complete response
|
||||
await session.addMessages([
|
||||
assistant.message(fullResponse)
|
||||
]);
|
||||
}
|
||||
|
||||
restaurantRecommendationChat().catch(console.error);
|
||||
await restaurantRecommendationChat();
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ async def dialectic_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||
session = honcho_client.session(id=str(update.effective_chat.id))
|
||||
|
||||
response = peer.chat(
|
||||
queries=query,
|
||||
query=query,
|
||||
session_id=session.id,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,15 +22,15 @@ from honcho import Honcho
|
|||
honcho = Honcho()
|
||||
|
||||
# Simple peer filter
|
||||
peers = honcho.get_peers(filter={"peer_id": "alice"})
|
||||
peers = honcho.get_peers(filters={"peer_id": "alice"})
|
||||
|
||||
# Simple session filter with metadata
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"metadata": {"type": "support"}
|
||||
})
|
||||
|
||||
# Simple message filter
|
||||
messages = honcho.get_messages(filter={
|
||||
messages = honcho.get_messages(filters={
|
||||
"session_id": "support-chat-1",
|
||||
"peer_id": "alice"
|
||||
})
|
||||
|
|
@ -39,28 +39,30 @@ messages = honcho.get_messages(filter={
|
|||
```typescript TypeScript
|
||||
import { Honcho } from "@honcho-ai/sdk";
|
||||
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
(async () => {
|
||||
// Initialize client
|
||||
const honcho = new Honcho({});
|
||||
|
||||
// Simple peer filter
|
||||
const peers = await honcho.getPeers({
|
||||
filter: { peerId: "alice" }
|
||||
});
|
||||
// Simple peer filter
|
||||
const peers = await honcho.getPeers({
|
||||
filters: { peerId: "alice" }
|
||||
});
|
||||
|
||||
// Simple session filter with metadata
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
metadata: { type: "support" }
|
||||
}
|
||||
});
|
||||
// Simple session filter with metadata
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
metadata: { type: "support" }
|
||||
}
|
||||
});
|
||||
|
||||
// Simple message filter
|
||||
const messages = await honcho.getMessages({
|
||||
filter: {
|
||||
sessionId: "support-chat-1",
|
||||
peerId: "alice"
|
||||
}
|
||||
});
|
||||
// Simple message filter
|
||||
const messages = await honcho.getMessages({
|
||||
filters: {
|
||||
sessionId: "support-chat-1",
|
||||
peerId: "alice"
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -74,7 +76,7 @@ Use AND to require all conditions to be true:
|
|||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
messages = honcho.get_messages(filter={
|
||||
messages = honcho.get_messages(filters={
|
||||
"AND": [
|
||||
{"session_id": "chat-1"},
|
||||
{"created_at": {"gte": "2024-01-01"}}
|
||||
|
|
@ -83,14 +85,16 @@ messages = honcho.get_messages(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
const messages = await honcho.getMessages({
|
||||
filter: {
|
||||
AND: [
|
||||
{ sessionId: "chat-1" },
|
||||
{ createdAt: { gte: "2024-01-01" } }
|
||||
]
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
const messages = await honcho.getMessages({
|
||||
filters: {
|
||||
AND: [
|
||||
{ sessionId: "chat-1" },
|
||||
{ createdAt: { gte: "2024-01-01" } }
|
||||
]
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -101,7 +105,7 @@ Use OR to match any of the specified conditions:
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Find messages from either alice or bob
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"OR": [
|
||||
{"peer_id": "alice"},
|
||||
{"peer_id": "bob"}
|
||||
|
|
@ -109,7 +113,7 @@ messages = session.get_messages(filter={
|
|||
})
|
||||
|
||||
# Complex OR with metadata conditions
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"OR": [
|
||||
{"metadata": {"priority": "high"}},
|
||||
{"metadata": {"urgent": True}},
|
||||
|
|
@ -119,26 +123,28 @@ sessions = honcho.get_sessions(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Find messages from either alice or bob
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
OR: [
|
||||
{ peerId: "alice" },
|
||||
{ peerId: "bob" }
|
||||
]
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
// Find messages from either alice or bob
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
OR: [
|
||||
{ peerId: "alice" },
|
||||
{ peerId: "bob" }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Complex OR with metadata conditions
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
OR: [
|
||||
{ metadata: { priority: "high" } },
|
||||
{ metadata: { urgent: true } },
|
||||
{ metadata: { escalated: true } }
|
||||
]
|
||||
}
|
||||
});
|
||||
// Complex OR with metadata conditions
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
OR: [
|
||||
{ metadata: { priority: "high" } },
|
||||
{ metadata: { urgent: true } },
|
||||
{ metadata: { escalated: true } }
|
||||
]
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -149,14 +155,14 @@ Use NOT to exclude specific conditions:
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Find all peers except alice
|
||||
peers = honcho.get_peers(filter={
|
||||
peers = honcho.get_peers(filters={
|
||||
"NOT": [
|
||||
{"peer_id": "alice"}
|
||||
]
|
||||
})
|
||||
|
||||
# Find sessions that are NOT completed
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"NOT": [
|
||||
{"metadata": {"status": "completed"}}
|
||||
]
|
||||
|
|
@ -164,23 +170,25 @@ sessions = honcho.get_sessions(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Find all peers except alice
|
||||
const peers = await honcho.getPeers({
|
||||
filter: {
|
||||
NOT: [
|
||||
{ peerId: "alice" }
|
||||
]
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
// Find all peers except alice
|
||||
const peers = await honcho.getPeers({
|
||||
filters: {
|
||||
NOT: [
|
||||
{ peerId: "alice" }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Find sessions that are NOT completed
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
NOT: [
|
||||
{ metadata: { status: "completed" } }
|
||||
]
|
||||
}
|
||||
});
|
||||
// Find sessions that are NOT completed
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
NOT: [
|
||||
{ metadata: { status: "completed" } }
|
||||
]
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -191,7 +199,7 @@ Create sophisticated queries by combining different logical operators:
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Find messages from alice OR bob, but NOT where message has archived set to true in metadata
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"AND": [
|
||||
{
|
||||
"OR": [
|
||||
|
|
@ -209,24 +217,26 @@ messages = session.get_messages(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Find messages from alice OR bob, but NOT where message has archived set to true in metadata
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{ peerId: "alice" },
|
||||
{ peerId: "bob" }
|
||||
]
|
||||
},
|
||||
{
|
||||
NOT: [
|
||||
{ metadata: { archived: true } }
|
||||
(async () => {
|
||||
// Find messages from alice OR bob, but NOT where message has archived set to true in metadata
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{ peerId: "alice" },
|
||||
{ peerId: "bob" }
|
||||
]
|
||||
},
|
||||
{
|
||||
NOT: [
|
||||
{ metadata: { archived: true } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -239,12 +249,12 @@ Use comparison operators for range queries and advanced matching:
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Find sessions created after a specific date
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"created_at": {"gte": "2024-01-01"}
|
||||
})
|
||||
|
||||
# Find messages within a date range
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"created_at": {
|
||||
"gte": "2024-01-01",
|
||||
"lte": "2024-12-31"
|
||||
|
|
@ -252,7 +262,7 @@ messages = session.get_messages(filter={
|
|||
})
|
||||
|
||||
# Metadata numeric comparisons
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"metadata": {
|
||||
"score": {"gt": 8.5},
|
||||
"duration": {"lte": 3600}
|
||||
|
|
@ -261,32 +271,34 @@ sessions = honcho.get_sessions(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Find sessions created after a specific date
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
createdAt: { gte: "2024-01-01" }
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
// Find sessions created after a specific date
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
createdAt: { gte: "2024-01-01" }
|
||||
}
|
||||
});
|
||||
|
||||
// Find messages within a date range
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
createdAt: {
|
||||
gte: "2024-01-01",
|
||||
lte: "2024-12-31"
|
||||
}
|
||||
}
|
||||
});
|
||||
// Find messages within a date range
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
createdAt: {
|
||||
gte: "2024-01-01",
|
||||
lte: "2024-12-31"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Metadata numeric comparisons
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
metadata: {
|
||||
score: { gt: 8.5 },
|
||||
duration: { lte: 3600 }
|
||||
}
|
||||
}
|
||||
});
|
||||
// Metadata numeric comparisons
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
metadata: {
|
||||
score: { gt: 8.5 },
|
||||
duration: { lte: 3600 }
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -295,19 +307,19 @@ const sessions = await honcho.getSessions({
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Find messages from specific peers in a session
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"peer_id": {"in": ["alice", "bob", "charlie"]}
|
||||
})
|
||||
|
||||
# Find sessions with specific tags
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"metadata": {
|
||||
"tag": {"in": ["important", "urgent", "follow-up"]}
|
||||
}
|
||||
})
|
||||
|
||||
# Not equal comparisons
|
||||
peers = honcho.get_peers(filter={
|
||||
peers = honcho.get_peers(filters={
|
||||
"metadata": {
|
||||
"status": {"ne": "inactive"}
|
||||
}
|
||||
|
|
@ -315,30 +327,32 @@ peers = honcho.get_peers(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Find messages from specific peers in a session
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
peerId: { in: ["alice", "bob", "charlie"] }
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
// Find messages from specific peers in a session
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
peerId: { in: ["alice", "bob", "charlie"] }
|
||||
}
|
||||
});
|
||||
|
||||
// Find sessions with specific tags
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
metadata: {
|
||||
tag: { in: ["important", "urgent", "follow-up"] }
|
||||
}
|
||||
}
|
||||
});
|
||||
// Find sessions with specific tags
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
metadata: {
|
||||
tag: { in: ["important", "urgent", "follow-up"] }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Not equal comparisons
|
||||
const peers = await honcho.getPeers({
|
||||
filter: {
|
||||
metadata: {
|
||||
status: { ne: "inactive" }
|
||||
}
|
||||
}
|
||||
});
|
||||
// Not equal comparisons
|
||||
const peers = await honcho.getPeers({
|
||||
filters: {
|
||||
metadata: {
|
||||
status: { ne: "inactive" }
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -351,7 +365,7 @@ Metadata filtering is particularly powerful in Honcho, supporting nested conditi
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Simple metadata equality
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"metadata": {
|
||||
"type": "customer_support",
|
||||
"priority": "high"
|
||||
|
|
@ -359,7 +373,7 @@ sessions = honcho.get_sessions(filter={
|
|||
})
|
||||
|
||||
# Nested metadata objects
|
||||
peers = honcho.get_peers(filter={
|
||||
peers = honcho.get_peers(filters={
|
||||
"metadata": {
|
||||
"profile": {
|
||||
"role": "admin",
|
||||
|
|
@ -370,27 +384,29 @@ peers = honcho.get_peers(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Simple metadata equality
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
metadata: {
|
||||
type: "customer_support",
|
||||
priority: "high"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Nested metadata objects
|
||||
const peers = await honcho.getPeers({
|
||||
filter: {
|
||||
metadata: {
|
||||
profile: {
|
||||
role: "admin",
|
||||
department: "engineering"
|
||||
(async () => {
|
||||
// Simple metadata equality
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
metadata: {
|
||||
type: "customer_support",
|
||||
priority: "high"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Nested metadata objects
|
||||
const peers = await honcho.getPeers({
|
||||
filters: {
|
||||
metadata: {
|
||||
profile: {
|
||||
role: "admin",
|
||||
department: "engineering"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -403,7 +419,7 @@ If you want to do advanced queries like these, make sure not to create metadata
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Metadata with comparison operators
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"metadata": {
|
||||
"score": {"gte": 4.0, "lte": 5.0},
|
||||
"created_by": {"ne": "system"},
|
||||
|
|
@ -412,7 +428,7 @@ sessions = honcho.get_sessions(filter={
|
|||
})
|
||||
|
||||
# Complex metadata conditions
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"AND": [
|
||||
{"metadata": {"sentiment": {"in": ["positive", "neutral"]}}},
|
||||
{"metadata": {"confidence": {"gt": 0.8}}},
|
||||
|
|
@ -422,27 +438,29 @@ messages = session.get_messages(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Metadata with comparison operators
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
metadata: {
|
||||
score: { gte: 4.0, lte: 5.0 },
|
||||
createdBy: { ne: "system" },
|
||||
tags: { contains: "important" }
|
||||
}
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
// Metadata with comparison operators
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
metadata: {
|
||||
score: { gte: 4.0, lte: 5.0 },
|
||||
createdBy: { ne: "system" },
|
||||
tags: { contains: "important" }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Complex metadata conditions
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
AND: [
|
||||
{ metadata: { sentiment: { in: ["positive", "neutral"] } } },
|
||||
{ metadata: { confidence: { gt: 0.8 } } },
|
||||
{ content: { icontains: "thank" } }
|
||||
]
|
||||
}
|
||||
});
|
||||
// Complex metadata conditions
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
AND: [
|
||||
{ metadata: { sentiment: { in: ["positive", "neutral"] } } },
|
||||
{ metadata: { confidence: { gt: 0.8 } } },
|
||||
{ content: { icontains: "thank" } }
|
||||
]
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -453,17 +471,17 @@ Use wildcards (*) to match any value for a field:
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Find all sessions with any peer_id (essentially all sessions)
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"peer_id": "*"
|
||||
})
|
||||
|
||||
# Wildcard in lists - matches everything
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"peer_id": {"in": ["alice", "bob", "*"]}
|
||||
})
|
||||
|
||||
# Metadata wildcards
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"metadata": {
|
||||
"type": "*", # Any type
|
||||
"status": "active" # But status must be active
|
||||
|
|
@ -472,29 +490,31 @@ sessions = honcho.get_sessions(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Find all sessions with any peer_id (essentially all sessions)
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
peerId: "*"
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
// Find all sessions with any peer_id (essentially all sessions)
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
peerId: "*"
|
||||
}
|
||||
});
|
||||
|
||||
// Wildcard in lists - matches everything
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
peerId: { in: ["alice", "bob", "*"] }
|
||||
}
|
||||
});
|
||||
// Wildcard in lists - matches everything
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
peerId: { in: ["alice", "bob", "*"] }
|
||||
}
|
||||
});
|
||||
|
||||
// Metadata wildcards
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
metadata: {
|
||||
type: "*", // Any type
|
||||
status: "active" // But status must be active
|
||||
}
|
||||
}
|
||||
});
|
||||
// Metadata wildcards
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
metadata: {
|
||||
type: "*", // Any type
|
||||
status: "active" // But status must be active
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -505,12 +525,12 @@ const sessions = await honcho.getSessions({
|
|||
<CodeGroup>
|
||||
```python Python
|
||||
# Find workspaces by name pattern
|
||||
workspaces = honcho.get_workspaces(filter={
|
||||
workspaces = honcho.get_workspaces(filters={
|
||||
"name": {"contains": "prod"}
|
||||
})
|
||||
|
||||
# Filter by metadata
|
||||
workspaces = honcho.get_workspaces(filter={
|
||||
workspaces = honcho.get_workspaces(filters={
|
||||
"metadata": {
|
||||
"environment": "production",
|
||||
"team": {"in": ["backend", "frontend", "devops"]}
|
||||
|
|
@ -519,22 +539,24 @@ workspaces = honcho.get_workspaces(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Find workspaces by name pattern
|
||||
const workspaces = await honcho.getWorkspaces({
|
||||
filter: {
|
||||
name: { contains: "prod" }
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
// Find workspaces by name pattern
|
||||
const workspaces = await honcho.getWorkspaces({
|
||||
filters: {
|
||||
name: { contains: "prod" }
|
||||
}
|
||||
});
|
||||
|
||||
// Filter by metadata
|
||||
const workspaces = await honcho.getWorkspaces({
|
||||
filter: {
|
||||
metadata: {
|
||||
environment: "production",
|
||||
team: { in: ["backend", "frontend", "devops"] }
|
||||
}
|
||||
}
|
||||
});
|
||||
// Filter by metadata
|
||||
const workspaces = await honcho.getWorkspaces({
|
||||
filters: {
|
||||
metadata: {
|
||||
environment: "production",
|
||||
team: { in: ["backend", "frontend", "devops"] }
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -546,7 +568,7 @@ const workspaces = await honcho.getWorkspaces({
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
week_ago = (datetime.now() - timedelta(days=7)).isoformat()
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"AND": [
|
||||
{"content": {"icontains": "error"}},
|
||||
{"created_at": {"gte": week_ago}},
|
||||
|
|
@ -555,7 +577,7 @@ messages = session.get_messages(filter={
|
|||
})
|
||||
|
||||
# Find messages in specific sessions with sentiment analysis
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"AND": [
|
||||
{"session_id": {"in": ["support-1", "support-2", "support-3"]}},
|
||||
{"metadata": {"sentiment": "negative"}},
|
||||
|
|
@ -565,28 +587,30 @@ messages = session.get_messages(filter={
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
// Find error messages from the last week
|
||||
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
AND: [
|
||||
{ content: { icontains: "error" } },
|
||||
{ createdAt: { gte: weekAgo } },
|
||||
{ metadata: { level: { in: ["error", "critical"] } } }
|
||||
]
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
// Find error messages from the last week
|
||||
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
AND: [
|
||||
{ content: { icontains: "error" } },
|
||||
{ createdAt: { gte: weekAgo } },
|
||||
{ metadata: { level: { in: ["error", "critical"] } } }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Find messages in specific sessions with sentiment analysis
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
AND: [
|
||||
{ sessionId: { in: ["support-1", "support-2", "support-3"] } },
|
||||
{ metadata: { sentiment: "negative" } },
|
||||
{ metadata: { confidence: { gte: 0.7 } } }
|
||||
]
|
||||
}
|
||||
});
|
||||
// Find messages in specific sessions with sentiment analysis
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
AND: [
|
||||
{ sessionId: { in: ["support-1", "support-2", "support-3"] } },
|
||||
{ metadata: { sentiment: "negative" } },
|
||||
{ metadata: { confidence: { gte: 0.7 } } }
|
||||
]
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
@ -600,7 +624,7 @@ from honcho.exceptions import FilterError
|
|||
|
||||
try:
|
||||
# Invalid filter - unsupported operator
|
||||
messages = session.get_messages(filter={
|
||||
messages = session.get_messages(filters={
|
||||
"created_at": {"invalid_operator": "2024-01-01"}
|
||||
})
|
||||
except FilterError as e:
|
||||
|
|
@ -609,7 +633,7 @@ except FilterError as e:
|
|||
|
||||
try:
|
||||
# Invalid column name
|
||||
sessions = honcho.get_sessions(filter={
|
||||
sessions = honcho.get_sessions(filters={
|
||||
"nonexistent_field": "value"
|
||||
})
|
||||
except FilterError as e:
|
||||
|
|
@ -617,30 +641,32 @@ except FilterError as e:
|
|||
```
|
||||
|
||||
```typescript TypeScript
|
||||
try {
|
||||
// Invalid filter - unsupported operator
|
||||
const messages = await session.getMessages({
|
||||
filter: {
|
||||
createdAt: { invalidOperator: "2024-01-01" }
|
||||
(async () => {
|
||||
try {
|
||||
// Invalid filter - unsupported operator
|
||||
const messages = await session.getMessages({
|
||||
filters: {
|
||||
createdAt: { invalidOperator: "2024-01-01" }
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes("filters")) {
|
||||
console.error(`Filter error: ${error.message}`);
|
||||
// Handle the error appropriately
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes("filter")) {
|
||||
console.error(`Filter error: ${error.message}`);
|
||||
// Handle the error appropriately
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Invalid column name
|
||||
const sessions = await honcho.getSessions({
|
||||
filter: {
|
||||
nonexistentField: "value"
|
||||
try {
|
||||
// Invalid column name
|
||||
const sessions = await honcho.getSessions({
|
||||
filters: {
|
||||
nonexistentField: "value"
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Invalid field: ${error.message}`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Invalid field: ${error.message}`);
|
||||
}
|
||||
})();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ paths:
|
|||
Get a Workspace by ID.
|
||||
|
||||
If workspace_id is provided as a query parameter, it uses that (must match JWT workspace_id).
|
||||
Otherwise, it uses the workspace_id from the JWT token.
|
||||
Otherwise, it uses the workspace_id from the JWT.
|
||||
operationId: get_or_create_workspace_v2_workspaces_post
|
||||
requestBody:
|
||||
content:
|
||||
|
|
@ -480,7 +480,7 @@ paths:
|
|||
Get a Peer by ID
|
||||
|
||||
If peer_id is provided as a query parameter, it uses that (must match JWT workspace_id).
|
||||
Otherwise, it uses the peer_id from the JWT token.
|
||||
Otherwise, it uses the peer_id from the JWT.
|
||||
operationId: get_or_create_peer_v2_workspaces__workspace_id__peers_post
|
||||
security:
|
||||
- HTTPBearer: []
|
||||
|
|
@ -972,7 +972,7 @@ paths:
|
|||
Get a specific session in a workspace.
|
||||
|
||||
If session_id is provided as a query parameter, it verifies the session is in the workspace.
|
||||
Otherwise, it uses the session_id from the JWT token for verification.
|
||||
Otherwise, it uses the session_id from the JWT for verification.
|
||||
operationId: get_or_create_session_v2_workspaces__workspace_id__sessions_post
|
||||
security:
|
||||
- HTTPBearer: []
|
||||
|
|
@ -2114,7 +2114,7 @@ paths:
|
|||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
title: >-
|
||||
Response Create Messages With File V2 Workspaces Workspace Id Sessions Session Id
|
||||
Response Create Messages With File V2 Workspaces Workspace Id Sessions Session Id
|
||||
Messages Upload Post
|
||||
'422':
|
||||
description: Validation Error
|
||||
|
|
@ -2677,7 +2677,7 @@ components:
|
|||
title: MessageCreate
|
||||
MessageGet:
|
||||
properties:
|
||||
filter:
|
||||
filters:
|
||||
anyOf:
|
||||
- additionalProperties: true
|
||||
type: object
|
||||
|
|
@ -2879,7 +2879,7 @@ components:
|
|||
title: PeerCreate
|
||||
PeerGet:
|
||||
properties:
|
||||
filter:
|
||||
filters:
|
||||
anyOf:
|
||||
- additionalProperties: true
|
||||
type: object
|
||||
|
|
@ -3032,7 +3032,7 @@ components:
|
|||
title: SessionDeriverStatus
|
||||
SessionGet:
|
||||
properties:
|
||||
filter:
|
||||
filters:
|
||||
anyOf:
|
||||
- additionalProperties: true
|
||||
type: object
|
||||
|
|
@ -3142,7 +3142,7 @@ components:
|
|||
title: WorkspaceCreate
|
||||
WorkspaceGet:
|
||||
properties:
|
||||
filter:
|
||||
filters:
|
||||
anyOf:
|
||||
- additionalProperties: true
|
||||
type: object
|
||||
|
|
@ -3169,4 +3169,4 @@ components:
|
|||
securitySchemes:
|
||||
HTTPBearer:
|
||||
type: http
|
||||
scheme: bearer
|
||||
scheme: bearer
|
||||
|
|
|
|||
|
|
@ -24,4 +24,4 @@ dist/
|
|||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
*.log
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
## Quickstart: Use the Hosted MCP Server
|
||||
|
||||
Go to https://app.honcho.dev and get an API key. Then go to Claude Desktop and navigate to custom MCP servers.
|
||||
Go to <https://app.honcho.dev> and get an API key. Then go to Claude Desktop and navigate to custom MCP servers.
|
||||
|
||||
If you don't have node/bun installed you will need to do that. You can also use npm if you already have that installed. If not, Claude Desktop or Claude Code can help!
|
||||
|
||||
Add Honcho to your Claude desktop config. You must provide a username for Honcho to refer to you as -- preferably what you want Claude to actually call you.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
|
|
@ -62,18 +63,22 @@ You may customize your assistant name and/or workspace ID. Both are optional.
|
|||
## Available Tools
|
||||
|
||||
### start_conversation
|
||||
|
||||
Start a new conversation session with Honcho. This initializes a session for tracking conversation history and context.
|
||||
|
||||
**Returns:** A session ID that you must store and use for all subsequent interactions in this conversation.
|
||||
|
||||
### add_turn
|
||||
|
||||
Add a conversation turn (user and assistant messages) to the current session. This stores the conversation in Honcho for context tracking.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to add the turn to
|
||||
- `messages`: Array of message objects with `role` ("user" or "assistant") and `content`
|
||||
|
||||
**Example usage:**
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "session-uuid",
|
||||
|
|
@ -83,7 +88,7 @@ Add a conversation turn (user and assistant messages) to the current session. Th
|
|||
"content": "Hello, how are you?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"role": "assistant",
|
||||
"content": "I'm doing well, thank you!"
|
||||
}
|
||||
]
|
||||
|
|
@ -91,153 +96,195 @@ Add a conversation turn (user and assistant messages) to the current session. Th
|
|||
```
|
||||
|
||||
### get_personalization_insights
|
||||
|
||||
Get personalization insights from Honcho based on conversation history. This queries the user's conversation context to provide personalized responses.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session for context
|
||||
- `query`: The question about the user's preferences, habits, etc.
|
||||
|
||||
**Example queries:**
|
||||
|
||||
- "What does this message reveal about the user's communication preferences?"
|
||||
- "How formal or casual should I be with the user based on our history?"
|
||||
- "What emotional state might the user be in right now?"
|
||||
|
||||
### search_workspace
|
||||
|
||||
Search for messages across the entire workspace.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `query`: The search query to use
|
||||
|
||||
### get_workspace_metadata
|
||||
|
||||
Get metadata for the current workspace.
|
||||
|
||||
**Parameters:** None
|
||||
|
||||
### set_workspace_metadata
|
||||
|
||||
Set metadata for the current workspace.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `metadata`: A dictionary of metadata to associate with the workspace
|
||||
|
||||
### create_peer
|
||||
|
||||
Create or get a peer with the specified ID and optional configuration.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `peer_id`: Unique identifier for the peer
|
||||
- `config`: Optional configuration dictionary for the peer
|
||||
|
||||
### get_peer_metadata
|
||||
|
||||
Get metadata for a specific peer.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `peer_id`: The ID of the peer to get metadata for
|
||||
|
||||
### set_peer_metadata
|
||||
|
||||
Set metadata for a specific peer.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `peer_id`: The ID of the peer to set metadata for
|
||||
- `metadata`: A dictionary of metadata to associate with the peer
|
||||
|
||||
### search_peer_messages
|
||||
|
||||
Search for messages sent by a peer.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `peer_id`: The ID of the peer to search messages for
|
||||
- `query`: The search query to use
|
||||
|
||||
### chat
|
||||
|
||||
Query a peer's representation with natural language questions.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `peer_id`: The ID of the peer to query
|
||||
- `query`: The natural language question to ask
|
||||
- `target_peer_id`: Optional target peer ID for local representation queries
|
||||
- `session_id`: Optional session ID to scope the query to a specific session
|
||||
|
||||
### list_peers
|
||||
|
||||
Get all peers in the current workspace.
|
||||
|
||||
**Parameters:** None
|
||||
|
||||
### create_session
|
||||
|
||||
Create or get a session with the specified ID and optional configuration.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: Unique identifier for the session
|
||||
- `config`: Optional configuration dictionary for the session
|
||||
|
||||
### get_session_metadata
|
||||
|
||||
Get metadata for a specific session.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to get metadata for
|
||||
|
||||
### set_session_metadata
|
||||
|
||||
Set metadata for a specific session.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to set metadata for
|
||||
- `metadata`: A dictionary of metadata to associate with the session
|
||||
|
||||
### add_peers_to_session
|
||||
|
||||
Add peers to a session.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to add peers to
|
||||
- `peer_ids`: List of peer IDs to add to the session
|
||||
|
||||
### remove_peers_from_session
|
||||
|
||||
Remove peers from a session.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to remove peers from
|
||||
- `peer_ids`: List of peer IDs to remove from the session
|
||||
|
||||
### get_session_peers
|
||||
|
||||
Get all peer IDs in a session.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to get peers from
|
||||
|
||||
### add_messages_to_session
|
||||
|
||||
Add messages to a session.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to add messages to
|
||||
- `messages`: List of message dictionaries with `peer_id`, `content`, and optional `metadata`
|
||||
|
||||
### get_session_messages
|
||||
|
||||
Get messages from a session with optional filtering.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to get messages from
|
||||
- `filters`: Optional dictionary of filter criteria
|
||||
|
||||
### get_session_context
|
||||
|
||||
Get optimized context for a session within a token limit.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to get context for
|
||||
- `summary`: Whether to include summary information (default: true)
|
||||
- `tokens`: Maximum number of tokens to include in the context
|
||||
|
||||
### search_session_messages
|
||||
|
||||
Search for messages in a specific session.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session to search messages in
|
||||
- `query`: The search query to use
|
||||
|
||||
### get_working_representation
|
||||
|
||||
Get the current working representation of a peer in a session.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `session_id`: The ID of the session
|
||||
- `peer_id`: The ID of the peer to get the working representation of
|
||||
- `target_peer_id`: Optional target peer ID to get the representation of what peer_id knows about target_peer_id
|
||||
|
||||
### list_sessions
|
||||
|
||||
Get all sessions in the current workspace.
|
||||
|
||||
**Parameters:** None
|
||||
|
|
@ -249,11 +296,13 @@ A Cloudflare Worker that implements the Model Context Protocol (MCP) to provide
|
|||
### Deploy MCP Worker
|
||||
|
||||
1. **Install dependencies:**
|
||||
|
||||
```bash
|
||||
bun i
|
||||
```
|
||||
|
||||
2. **Login to Cloudflare (if not already done):**
|
||||
|
||||
```bash
|
||||
bun wrangler login
|
||||
```
|
||||
|
|
@ -263,11 +312,13 @@ A Cloudflare Worker that implements the Model Context Protocol (MCP) to provide
|
|||
- Update the worker names in the `[env.production]` and `[env.staging]` sections
|
||||
|
||||
4. **Test locally:**
|
||||
|
||||
```bash
|
||||
bun dev
|
||||
```
|
||||
|
||||
5. **Deploy to production:**
|
||||
|
||||
```bash
|
||||
bun run deploy
|
||||
```
|
||||
|
|
@ -277,13 +328,14 @@ A Cloudflare Worker that implements the Model Context Protocol (MCP) to provide
|
|||
You can customize the behavior using HTTP headers:
|
||||
|
||||
**Available Configuration:**
|
||||
|
||||
- `apiKey`: Your Honcho API key
|
||||
- `baseUrl`: Custom Honcho API base URL (default: https://api.honcho.dev)
|
||||
- `baseUrl`: Custom Honcho API base URL (default: <https://api.honcho.dev>)
|
||||
- `workspaceId`: Workspace ID (default: "default")
|
||||
- `userName`: User identifier (default: "User")
|
||||
- `assistantName`: Assistant identifier (default: "Assistant")
|
||||
|
||||
#### Using HTTP Headers:
|
||||
#### Using HTTP Headers
|
||||
|
||||
Pass configuration to mcp-remote via custom headers:
|
||||
|
||||
|
|
@ -297,6 +349,7 @@ bunx mcp-remote https://YOUR_WORKER_NAME.YOUR_SUBDOMAIN.workers.dev \
|
|||
```
|
||||
|
||||
**Supported Custom Headers:**
|
||||
|
||||
- `Authorization: Bearer YOUR_API_KEY` - Your Honcho API key
|
||||
- `X-Honcho-Base-URL` - Custom Honcho API base URL
|
||||
- `X-Honcho-Workspace-ID` - Workspace identifier
|
||||
|
|
@ -326,6 +379,7 @@ The server provides proper JSON-RPC 2.0 error responses:
|
|||
- `-32603`: Internal error
|
||||
|
||||
Common issues:
|
||||
|
||||
- **Missing API key**: Ensure you provide a valid Honcho API key via header or URL parameter
|
||||
- **Invalid tool parameters**: Check that required parameters are provided and properly formatted
|
||||
- **Network errors**: Verify the worker is deployed and accessible
|
||||
- **Network errors**: Verify the worker is deployed and accessible
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ For subsequent messages in the same conversation:
|
|||
session_id: "session_abc123"
|
||||
messages: [
|
||||
{
|
||||
"role": "user",
|
||||
"role": "user",
|
||||
"content": "Thanks for listening. Can you help me prioritize my tasks?"
|
||||
},
|
||||
{
|
||||
|
|
@ -153,4 +153,4 @@ Ask theory-of-mind questions that reveal:
|
|||
3. **Use `get_personalization_insights` strategically for better responses**
|
||||
4. **Ask thoughtful theory-of-mind questions**
|
||||
5. **Never expose technical details to the user**
|
||||
6. **The system maintains context automatically between sessions**
|
||||
6. **The system maintains context automatically between sessions**
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@
|
|||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1341,4 +1341,4 @@ export default {
|
|||
return createErrorResponse(requestData.id ?? null, -32603, errorMessage);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ compatibility_flags = ["nodejs_compat"]
|
|||
name = "honcho-mcp"
|
||||
|
||||
[env.staging]
|
||||
name = "honcho-mcp-staging"
|
||||
name = "honcho-mcp-staging"
|
||||
|
||||
[observability.logs]
|
||||
enabled = true
|
||||
enabled = true
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ def upgrade() -> None:
|
|||
# Step 2: Get unique workspace-peer combinations that have orphaned messages
|
||||
workspace_peer_result = conn.execute(
|
||||
sa.text(f"""
|
||||
SELECT DISTINCT workspace_name, peer_name
|
||||
FROM {schema}.messages
|
||||
SELECT DISTINCT workspace_name, peer_name
|
||||
FROM {schema}.messages
|
||||
WHERE session_name IS NULL
|
||||
""")
|
||||
)
|
||||
|
|
@ -109,9 +109,9 @@ def upgrade() -> None:
|
|||
# Step 4: Assign orphaned messages for this peer to the default session
|
||||
op.execute(
|
||||
sa.text(f"""
|
||||
UPDATE {schema}.messages
|
||||
UPDATE {schema}.messages
|
||||
SET session_name = '{default_session_name}'
|
||||
WHERE workspace_name = '{workspace_name}'
|
||||
WHERE workspace_name = '{workspace_name}'
|
||||
AND peer_name = '{peer_name}'
|
||||
AND session_name IS NULL
|
||||
""")
|
||||
|
|
@ -120,9 +120,9 @@ def upgrade() -> None:
|
|||
# Step 4.5: Handle orphaned message embeddings for this peer
|
||||
op.execute(
|
||||
sa.text(f"""
|
||||
UPDATE {schema}.message_embeddings
|
||||
UPDATE {schema}.message_embeddings
|
||||
SET session_name = '{default_session_name}'
|
||||
WHERE workspace_name = '{workspace_name}'
|
||||
WHERE workspace_name = '{workspace_name}'
|
||||
AND peer_name = '{peer_name}'
|
||||
AND session_name IS NULL
|
||||
""")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "honcho"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
description = "Honcho Server"
|
||||
authors = [
|
||||
{name = "Plastic Labs", email = "hello@plasticlabs.ai"},
|
||||
|
|
@ -31,8 +31,8 @@ dependencies = [
|
|||
]
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"honcho-core>=1.2.0",
|
||||
"honcho-ai>=1.2.1",
|
||||
"honcho-core==1.3.0",
|
||||
"honcho-ai==1.2.2",
|
||||
"pytest>=8.2.2",
|
||||
"sqlalchemy-utils>=0.41.2",
|
||||
"pytest-asyncio>=0.23.7",
|
||||
|
|
|
|||
|
|
@ -168,10 +168,13 @@ TYPESCRIPT_VERSION=
|
|||
for section in sections:
|
||||
if line.strip() == f"### {section}":
|
||||
# If we have a previous section, decide whether to keep it
|
||||
if current_section is not None and section_start_idx != -1:
|
||||
if section_has_content:
|
||||
# Keep the section
|
||||
cleaned_lines.extend(lines[section_start_idx:i])
|
||||
if (
|
||||
current_section is not None
|
||||
and section_start_idx != -1
|
||||
and section_has_content
|
||||
):
|
||||
# Keep the section
|
||||
cleaned_lines.extend(lines[section_start_idx:i])
|
||||
# Start tracking new section
|
||||
current_section = section
|
||||
section_start_idx = i
|
||||
|
|
@ -179,15 +182,21 @@ TYPESCRIPT_VERSION=
|
|||
is_section_header = True
|
||||
break
|
||||
|
||||
if not is_section_header and current_section is not None:
|
||||
# Check if this line has content (not empty and not just whitespace)
|
||||
if line.strip() and not line.strip().startswith("#"):
|
||||
section_has_content = True
|
||||
if (
|
||||
not is_section_header
|
||||
and current_section is not None
|
||||
and line.strip()
|
||||
and not line.strip().startswith("#")
|
||||
):
|
||||
section_has_content = True
|
||||
|
||||
# Handle the last section
|
||||
if current_section is not None and section_start_idx != -1:
|
||||
if section_has_content:
|
||||
cleaned_lines.extend(lines[section_start_idx:])
|
||||
if (
|
||||
current_section is not None
|
||||
and section_start_idx != -1
|
||||
and section_has_content
|
||||
):
|
||||
cleaned_lines.extend(lines[section_start_idx:])
|
||||
|
||||
# If no sections were found, return original
|
||||
if not cleaned_lines and "###" not in changelog:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [1.3.0] - 2025-08-04
|
||||
|
||||
### Added
|
||||
|
||||
- Added get_peer_config to sessions module
|
||||
|
||||
### Changed
|
||||
|
||||
- Summaries are now included in `toOpenAI` and `toAnthropic` functions
|
||||
- `SessionContext.__len__` now counts the summary in its total
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added missing metadata inputs in many places
|
||||
- Better documentation all over
|
||||
|
||||
## [1.2.2] - 2025-07-21
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "honcho-ai"
|
||||
version = "1.2.2"
|
||||
version = "1.3.0"
|
||||
description = "Official DX Optimized Python SDK for Honcho"
|
||||
dynamic = ["readme"]
|
||||
license = "Apache-2.0"
|
||||
|
|
@ -8,7 +8,7 @@ authors = [
|
|||
{ name = "Plastic Labs", email = "hello@plasticlabs.ai" },
|
||||
]
|
||||
dependencies = [
|
||||
"honcho-core>=1.2.0",
|
||||
"honcho-core==1.3.0",
|
||||
"httpx>=0.28.0, <1",
|
||||
"pydantic>=2.0.0, <3",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -27,10 +27,14 @@ class AsyncHoncho(BaseModel):
|
|||
from environment variables or explicit parameters. This is the primary entry
|
||||
point for interacting with the Honcho conversational memory platform asynchronously.
|
||||
|
||||
For advanced usage, the underlying honcho_core client can be accessed via the
|
||||
`core` property to use functionality not exposed through this SDK.
|
||||
|
||||
Attributes:
|
||||
api_key: API key for authentication
|
||||
base_url: Base URL for the Honcho API
|
||||
workspace_id: Workspace ID for scoping operations
|
||||
core: Access to the underlying honcho_core client for advanced usage
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="allow") # pyright: ignore
|
||||
|
|
@ -42,6 +46,26 @@ class AsyncHoncho(BaseModel):
|
|||
)
|
||||
_client: AsyncHonchoCore = PrivateAttr()
|
||||
|
||||
@property
|
||||
def core(self) -> AsyncHonchoCore:
|
||||
"""
|
||||
Access the underlying honcho_core client. The honcho_core client is the raw Stainless-generated client,
|
||||
allowing users to access functionality that is not exposed through this SDK.
|
||||
|
||||
Returns:
|
||||
The underlying AsyncHonchoCore client instance
|
||||
|
||||
Example:
|
||||
```python
|
||||
from honcho import AsyncHoncho
|
||||
|
||||
client = AsyncHoncho()
|
||||
|
||||
workspace = await client.core.workspaces.get_or_create(id="custom-workspace-id")
|
||||
```
|
||||
"""
|
||||
return self._client
|
||||
|
||||
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -138,6 +162,10 @@ class AsyncHoncho(BaseModel):
|
|||
..., min_length=1, description="Unique identifier for the peer"
|
||||
),
|
||||
*,
|
||||
metadata: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional metadata dictionary to associate with this peer. If set, will get/create peer immediately with metadata.",
|
||||
),
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this peer. If set, will get/create peer immediately with flags.",
|
||||
|
|
@ -147,14 +175,16 @@ class AsyncHoncho(BaseModel):
|
|||
Get or create a peer with the given ID.
|
||||
|
||||
Creates an AsyncPeer object that can be used to interact with the specified peer.
|
||||
This method does not make an API call - the peer is created lazily when
|
||||
its methods are first used.
|
||||
This method does not make an API call unless `config` or `metadata` is
|
||||
provided.
|
||||
|
||||
Args:
|
||||
id: Unique identifier for the peer within the workspace. Should be a
|
||||
stable identifier that can be used consistently across sessions
|
||||
config:
|
||||
Optional configuration to set for this peer. If set, will get/create peer immediately with flags.
|
||||
stable identifier that can be used consistently across sessions
|
||||
metadata: Optional metadata dictionary to associate with this peer.
|
||||
If set, will get/create peer immediately with metadata.
|
||||
config: Optional configuration to set for this peer.
|
||||
If set, will get/create peer immediately with flags.
|
||||
|
||||
Returns:
|
||||
An AsyncPeer object that can be used to send messages, join sessions, and
|
||||
|
|
@ -163,14 +193,14 @@ class AsyncHoncho(BaseModel):
|
|||
Raises:
|
||||
ValidationError: If the peer ID is empty or invalid
|
||||
"""
|
||||
if config:
|
||||
if config or metadata:
|
||||
return await AsyncPeer.create(
|
||||
id, self.workspace_id, self._client, config=config
|
||||
id, self.workspace_id, self._client, config=config, metadata=metadata
|
||||
)
|
||||
return AsyncPeer(id, self.workspace_id, self._client)
|
||||
|
||||
async def get_peers(
|
||||
self, filter: dict[str, object] | None = None
|
||||
self, filters: dict[str, object] | None = None
|
||||
) -> AsyncPage[AsyncPeer]:
|
||||
"""
|
||||
Get all peers in the current workspace.
|
||||
|
|
@ -180,11 +210,10 @@ class AsyncHoncho(BaseModel):
|
|||
inner client Peer objects to SDK AsyncPeer objects as they are consumed.
|
||||
|
||||
Returns:
|
||||
An AsyncPage of AsyncPeer objects representing all peers in the workspace.
|
||||
The page preserves pagination functionality while transforming objects
|
||||
An AsyncPage of AsyncPeer objects representing all peers in the workspace
|
||||
"""
|
||||
peers_page = await self._client.workspaces.peers.list(
|
||||
workspace_id=self.workspace_id, filter=filter
|
||||
workspace_id=self.workspace_id, filters=filters
|
||||
)
|
||||
return AsyncPage(
|
||||
peers_page, lambda peer: AsyncPeer(peer.id, self.workspace_id, self._client)
|
||||
|
|
@ -197,6 +226,10 @@ class AsyncHoncho(BaseModel):
|
|||
..., min_length=1, description="Unique identifier for the session"
|
||||
),
|
||||
*,
|
||||
metadata: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional metadata dictionary to associate with this session. If set, will get/create session immediately with metadata.",
|
||||
),
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this session. If set, will get/create session immediately with flags.",
|
||||
|
|
@ -206,15 +239,17 @@ class AsyncHoncho(BaseModel):
|
|||
Get or create a session with the given ID.
|
||||
|
||||
Creates an AsyncSession object that can be used to manage conversations between
|
||||
multiple peers. This method does not make an API call - the session is
|
||||
created lazily when its methods are first used.
|
||||
multiple peers. This method does not make an API call unless `config` or
|
||||
`metadata` is provided.
|
||||
|
||||
Args:
|
||||
id: Unique identifier for the session within the workspace. Should be a
|
||||
stable identifier that can be used consistently to reference the
|
||||
same conversation
|
||||
config:
|
||||
Optional configuration to set for this session. If set, will get/create session immediately with flags.
|
||||
stable identifier that can be used consistently to reference the
|
||||
same conversation
|
||||
metadata: Optional metadata dictionary to associate with this session.
|
||||
If set, will get/create session immediately with metadata.
|
||||
config: Optional configuration to set for this session.
|
||||
If set, will get/create session immediately with flags.
|
||||
Returns:
|
||||
An AsyncSession object that can be used to add peers, send messages, and
|
||||
manage conversation context
|
||||
|
|
@ -222,14 +257,14 @@ class AsyncHoncho(BaseModel):
|
|||
Raises:
|
||||
ValidationError: If the session ID is empty or invalid
|
||||
"""
|
||||
if config:
|
||||
if config or metadata:
|
||||
return await AsyncSession.create(
|
||||
id, self.workspace_id, self._client, config=config
|
||||
id, self.workspace_id, self._client, config=config, metadata=metadata
|
||||
)
|
||||
return AsyncSession(id, self.workspace_id, self._client)
|
||||
|
||||
async def get_sessions(
|
||||
self, filter: dict[str, object] | None = None
|
||||
self, filters: dict[str, object] | None = None
|
||||
) -> AsyncPage[AsyncSession]:
|
||||
"""
|
||||
Get all sessions in the current workspace.
|
||||
|
|
@ -242,7 +277,7 @@ class AsyncHoncho(BaseModel):
|
|||
Returns an empty page if no sessions exist
|
||||
"""
|
||||
sessions_page = await self._client.workspaces.sessions.list(
|
||||
workspace_id=self.workspace_id, filter=filter
|
||||
workspace_id=self.workspace_id, filters=filters
|
||||
)
|
||||
return AsyncPage(
|
||||
sessions_page,
|
||||
|
|
@ -282,7 +317,7 @@ class AsyncHoncho(BaseModel):
|
|||
await self._client.workspaces.update(self.workspace_id, metadata=metadata)
|
||||
|
||||
async def get_workspaces(
|
||||
self, filter: dict[str, object] | None = None
|
||||
self, filters: dict[str, object] | None = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Get all workspace IDs from the Honcho instance.
|
||||
|
|
@ -294,7 +329,7 @@ class AsyncHoncho(BaseModel):
|
|||
A list of workspace ID strings. Returns an empty list if no workspaces
|
||||
are accessible or none exist
|
||||
"""
|
||||
workspaces_page = await self._client.workspaces.list(filter=filter)
|
||||
workspaces_page = await self._client.workspaces.list(filters=filters)
|
||||
workspace_ids: list[str] = []
|
||||
async for workspace in workspaces_page:
|
||||
workspace_ids.append(workspace.id)
|
||||
|
|
@ -304,7 +339,13 @@ class AsyncHoncho(BaseModel):
|
|||
async def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> AsyncPage[Message]:
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Filters to scope the search"
|
||||
),
|
||||
limit: int = Field(
|
||||
default=10, ge=1, le=100, description="Number of results to return"
|
||||
),
|
||||
) -> list[Message]:
|
||||
"""
|
||||
Search for messages in the current workspace.
|
||||
|
||||
|
|
@ -312,15 +353,19 @@ class AsyncHoncho(BaseModel):
|
|||
|
||||
Args:
|
||||
query: The search query to use
|
||||
filters: Filters to scope the search. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
limit: Number of results to return (1-100, default: 10)
|
||||
|
||||
Returns:
|
||||
An AsyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
A list of Message objects representing the search results.
|
||||
Returns an empty list if no messages are found.
|
||||
"""
|
||||
messages_page = await self._client.workspaces.search(
|
||||
self.workspace_id, body=query
|
||||
return await self._client.workspaces.search(
|
||||
self.workspace_id,
|
||||
query=query,
|
||||
filters=filters,
|
||||
limit=limit,
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
@validate_call
|
||||
async def get_deriver_status(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from honcho_core import AsyncHoncho as AsyncHonchoCore
|
||||
from honcho_core._types import NOT_GIVEN
|
||||
from honcho_core.types.workspaces.sessions import MessageCreateParam
|
||||
from honcho_core.types.workspaces.sessions.message import Message
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, validate_call
|
||||
|
|
@ -65,28 +66,35 @@ class AsyncPeer(BaseModel):
|
|||
workspace_id: str,
|
||||
client: AsyncHonchoCore,
|
||||
*,
|
||||
metadata: dict[str, object] | None = None,
|
||||
config: dict[str, object] | None = None,
|
||||
) -> AsyncPeer:
|
||||
"""
|
||||
Create a new AsyncPeer with optional configuration.
|
||||
|
||||
Provided metadata and configuration will overwrite any existing data in those
|
||||
locations if given.
|
||||
|
||||
Args:
|
||||
peer_id: Unique identifier for this peer within the workspace
|
||||
workspace_id: Workspace ID for scoping operations
|
||||
client: Reference to the parent AsyncHoncho client instance
|
||||
metadata: Optional metadata dictionary to associate with this peer.
|
||||
If set, will get/create peer immediately with metadata.
|
||||
config: Optional configuration to set for this peer.
|
||||
If set, will get/create peer immediately with flags.
|
||||
If set, will get/create peer immediately with flags.
|
||||
|
||||
Returns:
|
||||
A new AsyncPeer instance
|
||||
"""
|
||||
peer = cls(peer_id, workspace_id, client)
|
||||
|
||||
if config:
|
||||
if config or metadata:
|
||||
await client.workspaces.peers.get_or_create(
|
||||
workspace_id=workspace_id,
|
||||
id=peer_id,
|
||||
configuration=config,
|
||||
configuration=config if config is not None else NOT_GIVEN,
|
||||
metadata=metadata if metadata is not None else NOT_GIVEN,
|
||||
)
|
||||
|
||||
return peer
|
||||
|
|
@ -133,7 +141,7 @@ class AsyncPeer(BaseModel):
|
|||
return response.content
|
||||
|
||||
async def get_sessions(
|
||||
self, filter: dict[str, object] | None = None
|
||||
self, filters: dict[str, object] | None = None
|
||||
) -> AsyncPage[AsyncSession]:
|
||||
"""
|
||||
Get all sessions this peer is a member of.
|
||||
|
|
@ -150,7 +158,7 @@ class AsyncPeer(BaseModel):
|
|||
sessions_page = await self._client.workspaces.peers.sessions.list(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
filter=filter,
|
||||
filters=filters,
|
||||
)
|
||||
return AsyncPage(
|
||||
sessions_page,
|
||||
|
|
@ -224,11 +232,58 @@ class AsyncPeer(BaseModel):
|
|||
metadata=metadata,
|
||||
)
|
||||
|
||||
async def get_peer_config(self) -> dict[str, object]:
|
||||
"""
|
||||
Get the current workspace-level configuration for this peer.
|
||||
|
||||
Makes an API call to retrieve configuration associated with this peer.
|
||||
Configuration currently includes one optional flag, `observe_me`.
|
||||
|
||||
Returns:
|
||||
A dictionary containing the peer's configuration
|
||||
"""
|
||||
peer = await self._client.workspaces.peers.get_or_create(
|
||||
workspace_id=self.workspace_id,
|
||||
id=self.id,
|
||||
)
|
||||
return peer.configuration or {}
|
||||
|
||||
@validate_call
|
||||
async def set_peer_config(
|
||||
self,
|
||||
config: dict[str, object] = Field(
|
||||
..., description="Configuration dictionary to associate with this peer"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Set the configuration for this peer. Currently the only supported config
|
||||
value is the `observe_me` flag, which controls whether derivation tasks
|
||||
should be created for this peer's global representation. Default is True.
|
||||
|
||||
Makes an API call to update the configuration associated with this peer.
|
||||
This will overwrite any existing configuration with the provided values.
|
||||
|
||||
Args:
|
||||
config: A dictionary of configuration to associate with this peer.
|
||||
Keys must be strings, values can be any JSON-serializable type
|
||||
"""
|
||||
await self._client.workspaces.peers.update(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
configuration=config,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> AsyncPage[Message]:
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Filters to scope the search"
|
||||
),
|
||||
limit: int = Field(
|
||||
default=10, ge=1, le=100, description="Number of results to return"
|
||||
),
|
||||
) -> list[Message]:
|
||||
"""
|
||||
Search across all messages in the workspace with this peer as author.
|
||||
|
||||
|
|
@ -236,15 +291,20 @@ class AsyncPeer(BaseModel):
|
|||
|
||||
Args:
|
||||
query: The search query to use
|
||||
filters: Filters to scope the search. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
limit: Number of results to return (1-100, default: 10)
|
||||
|
||||
Returns:
|
||||
An AsyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
A list of Message objects representing the search results.
|
||||
Returns an empty list if no messages are found.
|
||||
"""
|
||||
messages_page = await self._client.workspaces.peers.search(
|
||||
self.id, workspace_id=self.workspace_id, query=query
|
||||
return await self._client.workspaces.peers.search(
|
||||
self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
query=query,
|
||||
filters=filters,
|
||||
limit=limit,
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from honcho_core import AsyncHoncho as AsyncHonchoCore
|
||||
|
|
@ -17,13 +16,6 @@ if TYPE_CHECKING:
|
|||
from .peer import AsyncPeer
|
||||
|
||||
|
||||
try:
|
||||
env_val = os.getenv("HONCHO_DEFAULT_CONTEXT_TOKENS")
|
||||
_default_context_tokens = int(env_val) if env_val else None
|
||||
except (ValueError, TypeError):
|
||||
_default_context_tokens = None
|
||||
|
||||
|
||||
class SessionPeerConfig(BaseModel):
|
||||
observe_others: bool | None = Field(
|
||||
None,
|
||||
|
|
@ -90,28 +82,35 @@ class AsyncSession(BaseModel):
|
|||
workspace_id: str,
|
||||
client: AsyncHonchoCore,
|
||||
*,
|
||||
metadata: dict[str, object] | None = None,
|
||||
config: dict[str, object] | None = None,
|
||||
) -> AsyncSession:
|
||||
"""
|
||||
Create a new AsyncSession with optional configuration.
|
||||
|
||||
Provided metadata and configuration will overwrite any existing data in those
|
||||
locations if given.
|
||||
|
||||
Args:
|
||||
session_id: Unique identifier for this session within the workspace
|
||||
workspace_id: Workspace ID for scoping operations
|
||||
client: Reference to the parent AsyncHoncho client instance
|
||||
config:
|
||||
Optional configuration to set for this session. If set, will get/create session immediately with flags.
|
||||
metadata: Optional metadata dictionary to associate with this session.
|
||||
If set, will get/create session immediately with metadata.
|
||||
config: Optional configuration to set for this session.
|
||||
If set, will get/create session immediately with flags.
|
||||
|
||||
Returns:
|
||||
A new AsyncSession instance
|
||||
"""
|
||||
session = cls(session_id, workspace_id, client)
|
||||
|
||||
if config:
|
||||
if config or metadata:
|
||||
await client.workspaces.sessions.get_or_create(
|
||||
workspace_id=workspace_id,
|
||||
id=session_id,
|
||||
configuration=config,
|
||||
configuration=config if config is not None else NOT_GIVEN,
|
||||
metadata=metadata if metadata is not None else NOT_GIVEN,
|
||||
)
|
||||
|
||||
return session
|
||||
|
|
@ -137,13 +136,13 @@ class AsyncSession(BaseModel):
|
|||
|
||||
Args:
|
||||
peers: Peers to add to the session. Can be:
|
||||
- str: Single peer ID
|
||||
- AsyncPeer: Single AsyncPeer object
|
||||
- List[Union[AsyncPeer, str]]: List of AsyncPeer objects and/or peer IDs
|
||||
- tuple[str, SessionPeerConfig]: Single peer ID and SessionPeerConfig
|
||||
- tuple[AsyncPeer, SessionPeerConfig]: Single AsyncPeer object and SessionPeerConfig
|
||||
- List[tuple[Union[AsyncPeer, str], SessionPeerConfig]]: List of AsyncPeer objects and/or peer IDs and SessionPeerConfig
|
||||
- Mixed lists with peers and tuples/lists containing peer+config combinations
|
||||
- str: Single peer ID
|
||||
- AsyncPeer: Single AsyncPeer object
|
||||
- List[Union[AsyncPeer, str]]: List of AsyncPeer objects and/or peer IDs
|
||||
- tuple[str, SessionPeerConfig]: Single peer ID and SessionPeerConfig
|
||||
- tuple[AsyncPeer, SessionPeerConfig]: Single AsyncPeer object and SessionPeerConfig
|
||||
- List[tuple[Union[AsyncPeer, str], SessionPeerConfig]]: List of AsyncPeer objects and/or peer IDs and SessionPeerConfig
|
||||
- Mixed lists with peers and tuples/lists containing peer+config combinations
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
|
@ -186,13 +185,13 @@ class AsyncSession(BaseModel):
|
|||
|
||||
Args:
|
||||
peers: Peers to set for the session. Can be:
|
||||
- str: Single peer ID
|
||||
- AsyncPeer: Single AsyncPeer object
|
||||
- List[Union[AsyncPeer, str]]: List of AsyncPeer objects and/or peer IDs
|
||||
- tuple[str, SessionPeerConfig]: Single peer ID and SessionPeerConfig
|
||||
- tuple[AsyncPeer, SessionPeerConfig]: Single AsyncPeer object and SessionPeerConfig
|
||||
- List[tuple[Union[AsyncPeer, str], SessionPeerConfig]]: List of AsyncPeer objects and/or peer IDs and SessionPeerConfig
|
||||
- Mixed lists with peers and tuples/lists containing peer+config combinations
|
||||
- str: Single peer ID
|
||||
- AsyncPeer: Single AsyncPeer object
|
||||
- List[Union[AsyncPeer, str]]: List of AsyncPeer objects and/or peer IDs
|
||||
- tuple[str, SessionPeerConfig]: Single peer ID and SessionPeerConfig
|
||||
- tuple[AsyncPeer, SessionPeerConfig]: Single AsyncPeer object and SessionPeerConfig
|
||||
- List[tuple[Union[AsyncPeer, str], SessionPeerConfig]]: List of AsyncPeer objects and/or peer IDs and SessionPeerConfig
|
||||
- Mixed lists with peers and tuples/lists containing peer+config combinations
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
|
@ -359,7 +358,7 @@ class AsyncSession(BaseModel):
|
|||
messages_page = await self._client.workspaces.sessions.messages.list(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
filter=filters,
|
||||
filters=filters,
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
|
|
@ -422,24 +421,22 @@ class AsyncSession(BaseModel):
|
|||
|
||||
Args:
|
||||
summary: Whether to include summary information
|
||||
tokens: Maximum number of tokens to include in the context.
|
||||
Defaults to HONCHO_default_context_tokens environment
|
||||
variable if it exists.
|
||||
tokens: Maximum number of tokens to include in the context. Will default
|
||||
to Honcho server configuration if not provided.
|
||||
|
||||
Returns:
|
||||
A SessionContext object containing the optimized message history
|
||||
that maximizes conversational context while respecting the token limit
|
||||
A SessionContext object containing the optimized message history and
|
||||
summary, if available, that maximizes conversational context while
|
||||
respecting the token limit
|
||||
|
||||
Note:
|
||||
Token counting is performed using tiktoken. For models using different
|
||||
tokenizers, you may need to adjust the token limit accordingly.
|
||||
"""
|
||||
if not tokens:
|
||||
tokens = _default_context_tokens
|
||||
context = await self._client.workspaces.sessions.get_context(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
tokens=tokens,
|
||||
tokens=tokens if tokens is not None else NOT_GIVEN,
|
||||
summary=summary,
|
||||
)
|
||||
|
||||
|
|
@ -451,7 +448,13 @@ class AsyncSession(BaseModel):
|
|||
async def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> AsyncPage[Message]:
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Filters to scope the search"
|
||||
),
|
||||
limit: int = Field(
|
||||
default=10, ge=1, le=100, description="Number of results to return"
|
||||
),
|
||||
) -> list[Message]:
|
||||
"""
|
||||
Search for messages in this session.
|
||||
|
||||
|
|
@ -459,15 +462,20 @@ class AsyncSession(BaseModel):
|
|||
|
||||
Args:
|
||||
query: The search query to use
|
||||
filters: Filters to scope the search. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
limit: Number of results to return (1-100, default: 10)
|
||||
|
||||
Returns:
|
||||
An AsyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
A list of Message objects representing the search results.
|
||||
Returns an empty list if no messages are found.
|
||||
"""
|
||||
messages_page = await self._client.workspaces.sessions.search(
|
||||
self.id, workspace_id=self.workspace_id, query=query
|
||||
return await self._client.workspaces.sessions.search(
|
||||
self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
query=query,
|
||||
filters=filters,
|
||||
limit=limit,
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
@validate_call
|
||||
async def upload_file(
|
||||
|
|
@ -529,7 +537,7 @@ class AsyncSession(BaseModel):
|
|||
Args:
|
||||
peer: Peer to get the working representation of.
|
||||
target: Optional target peer to get the representation of. If provided,
|
||||
queries what `peer` knows about the `target`.
|
||||
queries what `peer` knows about the `target`.
|
||||
|
||||
Returns:
|
||||
A dictionary containing information about the peer.
|
||||
|
|
|
|||
|
|
@ -25,10 +25,14 @@ class Honcho(BaseModel):
|
|||
from environment variables or explicit parameters. This is the primary entry
|
||||
point for interacting with the Honcho conversational memory platform.
|
||||
|
||||
For advanced usage, the underlying honcho_core client can be accessed via the
|
||||
`core` property to use functionality not exposed through this SDK.
|
||||
|
||||
Attributes:
|
||||
api_key: API key for authentication
|
||||
base_url: Base URL for the Honcho API
|
||||
workspace_id: Workspace ID for scoping operations
|
||||
core: Access to the underlying honcho_core client for advanced usage
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="allow") # pyright: ignore
|
||||
|
|
@ -40,6 +44,26 @@ class Honcho(BaseModel):
|
|||
)
|
||||
_client: HonchoCore = PrivateAttr()
|
||||
|
||||
@property
|
||||
def core(self) -> HonchoCore:
|
||||
"""
|
||||
Access the underlying honcho_core client. The honcho_core client is the raw Stainless-generated client,
|
||||
allowing users to access functionality that is not exposed through this SDK.
|
||||
|
||||
Returns:
|
||||
The underlying HonchoCore client instance
|
||||
|
||||
Example:
|
||||
```python
|
||||
from honcho import Honcho
|
||||
|
||||
client = Honcho()
|
||||
|
||||
workspace = client.core.workspaces.get_or_create(id="custom-workspace-id")
|
||||
```
|
||||
"""
|
||||
return self._client
|
||||
|
||||
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -126,6 +150,10 @@ class Honcho(BaseModel):
|
|||
..., min_length=1, description="Unique identifier for the peer"
|
||||
),
|
||||
*,
|
||||
metadata: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional metadata dictionary to associate with this peer. If set, will get/create peer immediately with metadata.",
|
||||
),
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this peer. If set, will get/create peer immediately with flags.",
|
||||
|
|
@ -135,14 +163,16 @@ class Honcho(BaseModel):
|
|||
Get or create a peer with the given ID.
|
||||
|
||||
Creates a Peer object that can be used to interact with the specified peer.
|
||||
This method does not make an API call - the peer is created lazily when
|
||||
its methods are first used.
|
||||
This method does not make an API call unless `config` or `metadata` is
|
||||
provided.
|
||||
|
||||
Args:
|
||||
id: Unique identifier for the peer within the workspace. Should be a
|
||||
stable identifier that can be used consistently across sessions
|
||||
config:
|
||||
Optional configuration to set for this peer. If set, will get/create peer immediately with flags.
|
||||
stable identifier that can be used consistently across sessions.
|
||||
metadata: Optional metadata dictionary to associate with this peer.
|
||||
If set, will get/create peer immediately with metadata.
|
||||
config: Optional configuration to set for this peer.
|
||||
If set, will get/create peer immediately with flags.
|
||||
|
||||
Returns:
|
||||
A Peer object that can be used to send messages, join sessions, and
|
||||
|
|
@ -151,9 +181,11 @@ class Honcho(BaseModel):
|
|||
Raises:
|
||||
ValidationError: If the peer ID is empty or invalid
|
||||
"""
|
||||
return Peer(id, self.workspace_id, self._client, config=config)
|
||||
return Peer(
|
||||
id, self.workspace_id, self._client, config=config, metadata=metadata
|
||||
)
|
||||
|
||||
def get_peers(self, filter: dict[str, object] | None = None) -> SyncPage[Peer]:
|
||||
def get_peers(self, filters: dict[str, object] | None = None) -> SyncPage[Peer]:
|
||||
"""
|
||||
Get all peers in the current workspace.
|
||||
|
||||
|
|
@ -162,11 +194,10 @@ class Honcho(BaseModel):
|
|||
inner client Peer objects to SDK Peer objects as they are consumed.
|
||||
|
||||
Returns:
|
||||
A SyncPage of Peer objects representing all peers in the workspace.
|
||||
The page preserves pagination functionality while transforming objects
|
||||
A SyncPage of Peer objects representing all peers in the workspace
|
||||
"""
|
||||
peers_page = self._client.workspaces.peers.list(
|
||||
workspace_id=self.workspace_id, filter=filter
|
||||
workspace_id=self.workspace_id, filters=filters
|
||||
)
|
||||
return SyncPage(
|
||||
peers_page, lambda peer: Peer(peer.id, self.workspace_id, self._client)
|
||||
|
|
@ -179,6 +210,10 @@ class Honcho(BaseModel):
|
|||
..., min_length=1, description="Unique identifier for the session"
|
||||
),
|
||||
*,
|
||||
metadata: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional metadata dictionary to associate with this session. If set, will get/create session immediately with metadata.",
|
||||
),
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this session. If set, will get/create session immediately with flags.",
|
||||
|
|
@ -188,15 +223,17 @@ class Honcho(BaseModel):
|
|||
Get or create a session with the given ID.
|
||||
|
||||
Creates a Session object that can be used to manage conversations between
|
||||
multiple peers. This method does not make an API call - the session is
|
||||
created lazily when its methods are first used.
|
||||
multiple peers. This method does not make an API call unless `config` or
|
||||
`metadata` is provided.
|
||||
|
||||
Args:
|
||||
id: Unique identifier for the session within the workspace. Should be a
|
||||
stable identifier that can be used consistently to reference the
|
||||
same conversation
|
||||
config:
|
||||
Optional configuration to set for this session. If set, will get/create session immediately with flags.
|
||||
stable identifier that can be used consistently to reference the
|
||||
same conversation
|
||||
metadata: Optional metadata dictionary to associate with this session.
|
||||
If set, will get/create session immediately with metadata.
|
||||
config: Optional configuration to set for this session.
|
||||
If set, will get/create session immediately with flags.
|
||||
Returns:
|
||||
A Session object that can be used to add peers, send messages, and
|
||||
manage conversation context
|
||||
|
|
@ -204,10 +241,12 @@ class Honcho(BaseModel):
|
|||
Raises:
|
||||
ValidationError: If the session ID is empty or invalid
|
||||
"""
|
||||
return Session(id, self.workspace_id, self._client, config=config)
|
||||
return Session(
|
||||
id, self.workspace_id, self._client, config=config, metadata=metadata
|
||||
)
|
||||
|
||||
def get_sessions(
|
||||
self, filter: dict[str, object] | None = None
|
||||
self, filters: dict[str, object] | None = None
|
||||
) -> SyncPage[Session]:
|
||||
"""
|
||||
Get all sessions in the current workspace.
|
||||
|
|
@ -220,7 +259,7 @@ class Honcho(BaseModel):
|
|||
Returns an empty page if no sessions exist
|
||||
"""
|
||||
sessions_page = self._client.workspaces.sessions.list(
|
||||
workspace_id=self.workspace_id, filter=filter
|
||||
workspace_id=self.workspace_id, filters=filters
|
||||
)
|
||||
return SyncPage(
|
||||
sessions_page,
|
||||
|
|
@ -259,7 +298,7 @@ class Honcho(BaseModel):
|
|||
"""
|
||||
self._client.workspaces.update(self.workspace_id, metadata=metadata)
|
||||
|
||||
def get_workspaces(self, filter: dict[str, object] | None = None) -> list[str]:
|
||||
def get_workspaces(self, filters: dict[str, object] | None = None) -> list[str]:
|
||||
"""
|
||||
Get all workspace IDs from the Honcho instance.
|
||||
|
||||
|
|
@ -270,14 +309,20 @@ class Honcho(BaseModel):
|
|||
A list of workspace ID strings. Returns an empty list if no workspaces
|
||||
are accessible or none exist
|
||||
"""
|
||||
workspaces = self._client.workspaces.list(filter=filter)
|
||||
workspaces = self._client.workspaces.list(filters=filters)
|
||||
return [workspace.id for workspace in workspaces]
|
||||
|
||||
@validate_call
|
||||
def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> SyncPage[Message]:
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Filters to scope the search"
|
||||
),
|
||||
limit: int = Field(
|
||||
default=10, ge=1, le=100, description="Number of results to return"
|
||||
),
|
||||
) -> list[Message]:
|
||||
"""
|
||||
Search for messages in the current workspace.
|
||||
|
||||
|
|
@ -285,13 +330,16 @@ class Honcho(BaseModel):
|
|||
|
||||
Args:
|
||||
query: The search query to use
|
||||
filters: Filters to scope the search. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
limit: Number of results to return (1-100, default: 10)
|
||||
|
||||
Returns:
|
||||
A SyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
A list of Message objects representing the search results.
|
||||
Returns an empty list if no messages are found.
|
||||
"""
|
||||
messages_page = self._client.workspaces.search(self.workspace_id, body=query)
|
||||
return SyncPage(messages_page)
|
||||
return self._client.workspaces.search(
|
||||
self.workspace_id, query=query, filters=filters, limit=limit
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def get_deriver_status(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from honcho_core import Honcho as HonchoCore
|
||||
from honcho_core._types import NOT_GIVEN
|
||||
from honcho_core.types.workspaces.sessions import MessageCreateParam
|
||||
from honcho_core.types.workspaces.sessions.message import Message
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, validate_call
|
||||
|
|
@ -47,6 +48,10 @@ class Peer(BaseModel):
|
|||
..., description="Reference to the parent Honcho client instance"
|
||||
),
|
||||
*,
|
||||
metadata: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional metadata dictionary to associate with this peer. If set, will get/create peer immediately with metadata.",
|
||||
),
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this peer. If set, will get/create peer immediately with flags.",
|
||||
|
|
@ -55,21 +60,27 @@ class Peer(BaseModel):
|
|||
"""
|
||||
Initialize a new Peer.
|
||||
|
||||
Provided metadata and configuration will overwrite any existing data in those
|
||||
locations if given.
|
||||
|
||||
Args:
|
||||
peer_id: Unique identifier for this peer within the workspace
|
||||
workspace_id: Workspace ID for scoping operations
|
||||
client: Reference to the parent Honcho client instance
|
||||
metadata: Optional metadata dictionary to associate with this peer.
|
||||
If set, will get/create peer immediately with metadata.
|
||||
config: Optional configuration to set for this peer.
|
||||
If set, will get/create peer immediately with flags.
|
||||
If set, will get/create peer immediately with flags.
|
||||
"""
|
||||
super().__init__(id=peer_id, workspace_id=workspace_id)
|
||||
self._client = client
|
||||
|
||||
if config:
|
||||
if config or metadata:
|
||||
self._client.workspaces.peers.get_or_create(
|
||||
workspace_id=workspace_id,
|
||||
id=peer_id,
|
||||
configuration=config,
|
||||
configuration=config if config is not None else NOT_GIVEN,
|
||||
metadata=metadata if metadata is not None else NOT_GIVEN,
|
||||
)
|
||||
|
||||
def chat(
|
||||
|
|
@ -113,7 +124,7 @@ class Peer(BaseModel):
|
|||
return response.content
|
||||
|
||||
def get_sessions(
|
||||
self, filter: dict[str, object] | None = None
|
||||
self, filters: dict[str, object] | None = None
|
||||
) -> SyncPage[Session]:
|
||||
"""
|
||||
Get all sessions this peer is a member of.
|
||||
|
|
@ -130,7 +141,7 @@ class Peer(BaseModel):
|
|||
sessions_page = self._client.workspaces.peers.sessions.list(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
filter=filter,
|
||||
filters=filters,
|
||||
)
|
||||
return SyncPage(
|
||||
sessions_page,
|
||||
|
|
@ -196,7 +207,7 @@ class Peer(BaseModel):
|
|||
|
||||
Args:
|
||||
metadata: A dictionary of metadata to associate with this peer.
|
||||
Keys must be strings, values can be any JSON-serializable type
|
||||
Keys must be strings, values can be any JSON-serializable type
|
||||
"""
|
||||
self._client.workspaces.peers.update(
|
||||
peer_id=self.id,
|
||||
|
|
@ -204,11 +215,58 @@ class Peer(BaseModel):
|
|||
metadata=metadata,
|
||||
)
|
||||
|
||||
def get_peer_config(self) -> dict[str, object]:
|
||||
"""
|
||||
Get the current workspace-level configuration for this peer.
|
||||
|
||||
Makes an API call to retrieve configuration associated with this peer.
|
||||
Configuration currently includes one optional flag, `observe_me`.
|
||||
|
||||
Returns:
|
||||
A dictionary containing the peer's configuration
|
||||
"""
|
||||
peer = self._client.workspaces.peers.get_or_create(
|
||||
workspace_id=self.workspace_id,
|
||||
id=self.id,
|
||||
)
|
||||
return peer.configuration or {}
|
||||
|
||||
@validate_call
|
||||
def set_peer_config(
|
||||
self,
|
||||
config: dict[str, object] = Field(
|
||||
..., description="Configuration dictionary to associate with this peer"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Set the configuration for this peer. Currently the only supported config
|
||||
value is the `observe_me` flag, which controls whether derivation tasks
|
||||
should be created for this peer's global representation. Default is True.
|
||||
|
||||
Makes an API call to update the configuration associated with this peer.
|
||||
This will overwrite any existing configuration with the provided values.
|
||||
|
||||
Args:
|
||||
config: A dictionary of configuration to associate with this peer.
|
||||
Keys must be strings, values can be any JSON-serializable type
|
||||
"""
|
||||
self._client.workspaces.peers.update(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
configuration=config,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> SyncPage[Message]:
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Filters to scope the search"
|
||||
),
|
||||
limit: int = Field(
|
||||
default=10, ge=1, le=100, description="Number of results to return"
|
||||
),
|
||||
) -> list[Message]:
|
||||
"""
|
||||
Search across all messages in the workspace with this peer as author.
|
||||
|
||||
|
|
@ -216,15 +274,20 @@ class Peer(BaseModel):
|
|||
|
||||
Args:
|
||||
query: The search query to use
|
||||
filters: Filters to scope the search. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
limit: Number of results to return (1-100, default: 10)
|
||||
|
||||
Returns:
|
||||
A SyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
A list of Message objects representing the search results.
|
||||
Returns an empty list if no messages are found.
|
||||
"""
|
||||
messages_page = self._client.workspaces.peers.search(
|
||||
self.id, workspace_id=self.workspace_id, query=query
|
||||
return self._client.workspaces.peers.search(
|
||||
self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
query=query,
|
||||
filters=filters,
|
||||
limit=limit,
|
||||
)
|
||||
return SyncPage(messages_page)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from honcho_core import Honcho as HonchoCore
|
||||
|
|
@ -17,13 +16,6 @@ if TYPE_CHECKING:
|
|||
from .peer import Peer
|
||||
|
||||
|
||||
try:
|
||||
env_val = os.getenv("HONCHO_DEFAULT_CONTEXT_TOKENS")
|
||||
_default_context_tokens = int(env_val) if env_val else None
|
||||
except (ValueError, TypeError):
|
||||
_default_context_tokens = None
|
||||
|
||||
|
||||
class SessionPeerConfig(BaseModel):
|
||||
observe_others: bool | None = Field(
|
||||
None,
|
||||
|
|
@ -69,6 +61,10 @@ class Session(BaseModel):
|
|||
..., description="Reference to the parent Honcho client instance"
|
||||
),
|
||||
*,
|
||||
metadata: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional metadata dictionary to associate with this session. If set, will get/create session immediately with metadata.",
|
||||
),
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this session. If set, will get/create session immediately with flags.",
|
||||
|
|
@ -77,12 +73,17 @@ class Session(BaseModel):
|
|||
"""
|
||||
Initialize a new Session.
|
||||
|
||||
Provided metadata and configuration will overwrite any existing data in those
|
||||
locations if given.
|
||||
|
||||
Args:
|
||||
session_id: Unique identifier for this session within the workspace
|
||||
workspace_id: Workspace ID for scoping operations
|
||||
client: Reference to the parent Honcho client instance
|
||||
config:
|
||||
Optional configuration to set for this session. If set, will get/create session immediately with flags.
|
||||
metadata: Optional metadata dictionary to associate with this session.
|
||||
If set, will get/create session immediately with metadata.
|
||||
config: Optional configuration to set for this session.
|
||||
If set, will get/create session immediately with flags.
|
||||
"""
|
||||
super().__init__(
|
||||
id=session_id,
|
||||
|
|
@ -90,11 +91,12 @@ class Session(BaseModel):
|
|||
)
|
||||
self._client = client
|
||||
|
||||
if config:
|
||||
if config or metadata:
|
||||
self._client.workspaces.sessions.get_or_create(
|
||||
workspace_id=workspace_id,
|
||||
id=session_id,
|
||||
configuration=config,
|
||||
configuration=config if config is not None else NOT_GIVEN,
|
||||
metadata=metadata if metadata is not None else NOT_GIVEN,
|
||||
)
|
||||
|
||||
def add_peers(
|
||||
|
|
@ -118,13 +120,13 @@ class Session(BaseModel):
|
|||
|
||||
Args:
|
||||
peers: Peers to add to the session. Can be:
|
||||
- str: Single peer ID
|
||||
- Peer: Single Peer object
|
||||
- List[Union[Peer, str]]: List of Peer objects and/or peer IDs
|
||||
- tuple[str, SessionPeerConfig]: Single peer ID and SessionPeerConfig
|
||||
- tuple[Peer, SessionPeerConfig]: Single Peer object and SessionPeerConfig
|
||||
- List[tuple[Union[Peer, str], SessionPeerConfig]]: List of Peer objects and/or peer IDs and SessionPeerConfig
|
||||
- Mixed lists with peers and tuples/lists containing peer+config combinations
|
||||
- str: Single peer ID
|
||||
- Peer: Single Peer object
|
||||
- List[Union[Peer, str]]: List of Peer objects and/or peer IDs
|
||||
- tuple[str, SessionPeerConfig]: Single peer ID and SessionPeerConfig
|
||||
- tuple[Peer, SessionPeerConfig]: Single Peer object and SessionPeerConfig
|
||||
- List[tuple[Union[Peer, str], SessionPeerConfig]]: List of Peer objects and/or peer IDs and SessionPeerConfig
|
||||
- Mixed lists with peers and tuples/lists containing peer+config combinations
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
|
@ -167,13 +169,13 @@ class Session(BaseModel):
|
|||
|
||||
Args:
|
||||
peers: Peers to set for the session. Can be:
|
||||
- str: Single peer ID
|
||||
- Peer: Single Peer object
|
||||
- List[Union[Peer, str]]: List of Peer objects and/or peer IDs
|
||||
- tuple[str, SessionPeerConfig]: Single peer ID and SessionPeerConfig
|
||||
- tuple[Peer, SessionPeerConfig]: Single Peer object and SessionPeerConfig
|
||||
- List[tuple[Union[Peer, str], SessionPeerConfig]]: List of Peer objects and/or peer IDs and SessionPeerConfig
|
||||
- Mixed lists with peers and tuples/lists containing peer+config combinations
|
||||
- str: Single peer ID
|
||||
- Peer: Single Peer object
|
||||
- List[Union[Peer, str]]: List of Peer objects and/or peer IDs
|
||||
- tuple[str, SessionPeerConfig]: Single peer ID and SessionPeerConfig
|
||||
- tuple[Peer, SessionPeerConfig]: Single Peer object and SessionPeerConfig
|
||||
- List[tuple[Union[Peer, str], SessionPeerConfig]]: List of Peer objects and/or peer IDs and SessionPeerConfig
|
||||
- Mixed lists with peers and tuples/lists containing peer+config combinations
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
|
@ -335,7 +337,7 @@ class Session(BaseModel):
|
|||
messages_page = self._client.workspaces.sessions.messages.list(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
filter=filters,
|
||||
filters=filters,
|
||||
)
|
||||
return SyncPage(messages_page)
|
||||
|
||||
|
|
@ -400,23 +402,22 @@ class Session(BaseModel):
|
|||
|
||||
Args:
|
||||
summary: Whether to include summary information
|
||||
tokens: Maximum number of tokens to include in the context.
|
||||
Defaults to HONCHO_default_context_tokens env var
|
||||
tokens: Maximum number of tokens to include in the context. Will default
|
||||
to Honcho server configuration if not provided.
|
||||
|
||||
Returns:
|
||||
A SessionContext object containing the optimized message history
|
||||
that maximizes conversational context while respecting the token limit
|
||||
A SessionContext object containing the optimized message history and
|
||||
summary, if available, that maximizes conversational context while
|
||||
respecting the token limit
|
||||
|
||||
Note:
|
||||
Token counting is performed using tiktoken. For models using different
|
||||
tokenizers, you may need to adjust the token limit accordingly.
|
||||
"""
|
||||
if not tokens:
|
||||
tokens = _default_context_tokens
|
||||
context = self._client.workspaces.sessions.get_context(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
tokens=tokens,
|
||||
tokens=tokens if tokens is not None else NOT_GIVEN,
|
||||
summary=summary,
|
||||
)
|
||||
|
||||
|
|
@ -428,7 +429,13 @@ class Session(BaseModel):
|
|||
def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> SyncPage[Message]:
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Filters to scope the search"
|
||||
),
|
||||
limit: int = Field(
|
||||
default=10, ge=1, le=100, description="Number of results to return"
|
||||
),
|
||||
) -> list[Message]:
|
||||
"""
|
||||
Search for messages in this session.
|
||||
|
||||
|
|
@ -436,15 +443,20 @@ class Session(BaseModel):
|
|||
|
||||
Args:
|
||||
query: The search query to use
|
||||
filters: Filters to scope the search. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
limit: Number of results to return (1-100, default: 10)
|
||||
|
||||
Returns:
|
||||
A SyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
A list of Message objects representing the search results.
|
||||
Returns an empty list if no messages are found.
|
||||
"""
|
||||
messages_page = self._client.workspaces.sessions.search(
|
||||
self.id, workspace_id=self.workspace_id, query=query
|
||||
return self._client.workspaces.sessions.search(
|
||||
self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
query=query,
|
||||
filters=filters,
|
||||
limit=limit,
|
||||
)
|
||||
return SyncPage(messages_page)
|
||||
|
||||
@validate_call
|
||||
def upload_file(
|
||||
|
|
@ -506,7 +518,7 @@ class Session(BaseModel):
|
|||
Args:
|
||||
peer: Peer to get the working representation of.
|
||||
target: Optional target peer to get the representation of. If provided,
|
||||
queries what `peer` knows about the `target`.
|
||||
queries what `peer` knows about the `target`.
|
||||
|
||||
Returns:
|
||||
A dictionary containing information about the peer.
|
||||
|
|
|
|||
|
|
@ -61,18 +61,18 @@ class SessionContext(BaseModel):
|
|||
self,
|
||||
*,
|
||||
assistant: str | Peer,
|
||||
) -> list[dict[str, object]]:
|
||||
) -> list[dict[str, str]]:
|
||||
"""
|
||||
Convert the context to OpenAI-compatible message format.
|
||||
|
||||
Transforms the message history into the format expected by OpenAI's
|
||||
Chat Completions API, with proper role assignments based on the
|
||||
Transforms the message history and summary into the format expected by
|
||||
OpenAI's Chat Completions API, with proper role assignments based on the
|
||||
assistant's identity.
|
||||
|
||||
Args:
|
||||
assistant: The assistant peer (Peer object or peer ID string) to use
|
||||
for determining message roles. Messages from this peer will
|
||||
be marked as "assistant", others as "user"
|
||||
for determining message roles. Messages from this peer will be marked
|
||||
as "assistant", others as "user"
|
||||
|
||||
Returns:
|
||||
A list of dictionaries in OpenAI format, where each dictionary contains
|
||||
|
|
@ -80,26 +80,30 @@ class SessionContext(BaseModel):
|
|||
"""
|
||||
|
||||
assistant_id = assistant if isinstance(assistant, str) else assistant.id
|
||||
return [
|
||||
summary_message = {
|
||||
"role": "system",
|
||||
"content": f"<summary>{self.summary}</summary>",
|
||||
}
|
||||
messages = [
|
||||
{
|
||||
"role": "assistant" if message.peer_id == assistant_id else "user",
|
||||
"name": message.peer_id,
|
||||
"content": message.content,
|
||||
}
|
||||
for message in self.messages
|
||||
]
|
||||
return [summary_message, *messages] if self.summary else messages
|
||||
|
||||
def to_anthropic(
|
||||
self,
|
||||
*,
|
||||
assistant: str | Peer,
|
||||
) -> list[dict[str, object]]:
|
||||
) -> list[dict[str, str]]:
|
||||
"""
|
||||
Convert the context to Anthropic-compatible message format.
|
||||
|
||||
Transforms the message history into the format expected by Anthropic's
|
||||
Claude API. TODO: Anthropic requires messages to alternate between
|
||||
user and assistant roles, so this method may need to handle role
|
||||
consolidation or filtering in the future.
|
||||
Claude API, with proper role assignments based on the assistant's identity.
|
||||
|
||||
Args:
|
||||
assistant: The assistant peer (Peer object or peer ID string) to use
|
||||
|
|
@ -116,13 +120,23 @@ class SessionContext(BaseModel):
|
|||
"""
|
||||
|
||||
assistant_id = assistant if isinstance(assistant, str) else assistant.id
|
||||
return [
|
||||
summary_message = {
|
||||
"role": "user",
|
||||
"content": f"<summary>{self.summary}</summary>",
|
||||
}
|
||||
messages = [
|
||||
{
|
||||
"role": "assistant" if message.peer_id == assistant_id else "user",
|
||||
"role": "assistant",
|
||||
"content": message.content,
|
||||
}
|
||||
if message.peer_id == assistant_id
|
||||
else {
|
||||
"role": "user",
|
||||
"content": f"{message.peer_id}: {message.content}",
|
||||
}
|
||||
for message in self.messages
|
||||
]
|
||||
return [summary_message, *messages] if self.summary else messages
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
|
|
@ -131,7 +145,7 @@ class SessionContext(BaseModel):
|
|||
Returns:
|
||||
The number of messages in this context
|
||||
"""
|
||||
return len(self.messages)
|
||||
return len(self.messages) + (1 if self.summary else 0)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
|
|
@ -140,4 +154,4 @@ class SessionContext(BaseModel):
|
|||
Returns:
|
||||
A string representation suitable for debugging
|
||||
"""
|
||||
return f"SessionContext(messages={len(self.messages)})"
|
||||
return f"SessionContext(messages={len(self.messages)}, summary={self.summary})"
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "honcho-ai"
|
||||
version = "1.2.2"
|
||||
version = "1.3.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "honcho-core" },
|
||||
|
|
@ -112,7 +112,7 @@ dev = [
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "honcho-core", specifier = ">=1.2.0" },
|
||||
{ name = "honcho-core", git = "https://github.com/plastic-labs/honcho-python-core.git?rev=2fc998040ba1781762584664b6e308f135792852" },
|
||||
{ name = "httpx", specifier = ">=0.28.0,<1" },
|
||||
{ name = "pydantic", specifier = ">=2.0.0,<3" },
|
||||
]
|
||||
|
|
@ -122,8 +122,8 @@ dev = [{ name = "ruff", specifier = ">=0.11.13" }]
|
|||
|
||||
[[package]]
|
||||
name = "honcho-core"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
version = "1.3.0"
|
||||
source = { git = "https://github.com/plastic-labs/honcho-python-core.git?rev=2fc998040ba1781762584664b6e308f135792852#2fc998040ba1781762584664b6e308f135792852" }
|
||||
dependencies = [
|
||||
{ name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "anyio", version = "4.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
||||
|
|
@ -135,10 +135,6 @@ dependencies = [
|
|||
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "typing-extensions", version = "4.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/60/e870902c5d247b5a0fb38401d6f3730e11eaf038f5a15c68afc4465e332f/honcho_core-1.2.0.tar.gz", hash = "sha256:1f16fd9ecd236bfc4c30ecc33354baf4bdd9a4206e84f92ec785ecd61b25d193", size = 122450, upload-time = "2025-07-16T19:59:06.362Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/99/f435c093ea2067d7da50545cfbe2037e28370541ba161f5c8df6e33030b0/honcho_core-1.2.0-py3-none-any.whl", hash = "sha256:d9260e1a2a1254c26aeec464f8bca7ebb6c2fa9b5ae568a9344b5458467d78ab", size = 110721, upload-time = "2025-07-16T19:59:05.189Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [1.3.0] - 2025-08-04
|
||||
|
||||
### Added
|
||||
|
||||
- Zod validation
|
||||
- Added getPeerConfig to Session object
|
||||
|
||||
### Changed
|
||||
|
||||
- Moved parameters out of random `opts` dictionaries in many places
|
||||
- Peer and Session objects now use inner client like python SDK
|
||||
|
||||
### Fixed
|
||||
|
||||
- Enabled missing `metadata` options in many places
|
||||
- Proper default behavior for SessionPeerConfig
|
||||
|
||||
## [1.2.1] - 2025-07-21
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -23,10 +23,13 @@ export default class MockHonchoCore {
|
|||
set: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
list: jest.fn(),
|
||||
getConfig: jest.fn(),
|
||||
setConfig: jest.fn(),
|
||||
},
|
||||
messages: {
|
||||
create: jest.fn(),
|
||||
list: jest.fn(),
|
||||
upload: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
|
|
@ -37,6 +40,7 @@ export default class MockHonchoCore {
|
|||
update: jest.fn(),
|
||||
list: jest.fn(),
|
||||
search: jest.fn(),
|
||||
deriverStatus: jest.fn(),
|
||||
};
|
||||
|
||||
constructor(options?: any) {
|
||||
|
|
|
|||
|
|
@ -9,14 +9,17 @@ jest.mock('@honcho-ai/core', () => {
|
|||
workspaces: {
|
||||
peers: {
|
||||
list: jest.fn(),
|
||||
getOrCreate: jest.fn(),
|
||||
},
|
||||
sessions: {
|
||||
list: jest.fn(),
|
||||
getOrCreate: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn().mockResolvedValue({ id: 'test-workspace', metadata: {} }),
|
||||
update: jest.fn(),
|
||||
list: jest.fn(),
|
||||
search: jest.fn(),
|
||||
deriverStatus: jest.fn(),
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
|
@ -89,21 +92,33 @@ describe('Honcho Client', () => {
|
|||
});
|
||||
|
||||
describe('peer', () => {
|
||||
it('should create a new Peer instance', () => {
|
||||
const peer = honcho.peer('test-peer');
|
||||
it('should create a new Peer instance', async () => {
|
||||
const peer = await honcho.peer('test-peer');
|
||||
|
||||
expect(peer).toBeInstanceOf(Peer);
|
||||
expect(peer.id).toBe('test-peer');
|
||||
});
|
||||
|
||||
it('should throw error for empty peer ID', () => {
|
||||
expect(() => honcho.peer('')).toThrow('Peer ID must be a non-empty string');
|
||||
it('should create peer with metadata and config', async () => {
|
||||
const metadata = { name: 'Test Peer' };
|
||||
const config = { observe_me: false };
|
||||
|
||||
await honcho.peer('test-peer', { metadata, config });
|
||||
|
||||
expect(mockClient.workspaces.peers.getOrCreate).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
{ id: 'test-peer', metadata: metadata, configuration: config }
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for non-string peer ID', () => {
|
||||
expect(() => honcho.peer(null as any)).toThrow('Peer ID must be a non-empty string');
|
||||
expect(() => honcho.peer(undefined as any)).toThrow('Peer ID must be a non-empty string');
|
||||
expect(() => honcho.peer(123 as any)).toThrow('Peer ID must be a non-empty string');
|
||||
it('should throw error for empty peer ID', async () => {
|
||||
await expect(honcho.peer('')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for non-string peer ID', async () => {
|
||||
await expect(honcho.peer(null as any)).rejects.toThrow();
|
||||
await expect(honcho.peer(undefined as any)).rejects.toThrow();
|
||||
await expect(honcho.peer(123 as any)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -123,7 +138,7 @@ describe('Honcho Client', () => {
|
|||
const peersPage = await honcho.getPeers();
|
||||
|
||||
expect(peersPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.peers.list).toHaveBeenCalledWith('test-workspace', { filter: undefined });
|
||||
expect(mockClient.workspaces.peers.list).toHaveBeenCalledWith('test-workspace', { filters: undefined });
|
||||
});
|
||||
|
||||
it('should handle empty peers list', async () => {
|
||||
|
|
@ -138,32 +153,44 @@ describe('Honcho Client', () => {
|
|||
const peersPage = await honcho.getPeers();
|
||||
|
||||
expect(peersPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.peers.list).toHaveBeenCalledWith('test-workspace', { filter: undefined });
|
||||
expect(mockClient.workspaces.peers.list).toHaveBeenCalledWith('test-workspace', { filters: undefined });
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.list.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
await expect(honcho.getPeers()).rejects.toThrow('API Error');
|
||||
await expect(honcho.getPeers()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('session', () => {
|
||||
it('should create a new Session instance', () => {
|
||||
const session = honcho.session('test-session');
|
||||
it('should create a new Session instance', async () => {
|
||||
const session = await honcho.session('test-session');
|
||||
|
||||
expect(session).toBeInstanceOf(Session);
|
||||
expect(session.id).toBe('test-session');
|
||||
});
|
||||
|
||||
it('should throw error for empty session ID', () => {
|
||||
expect(() => honcho.session('')).toThrow('Session ID must be a non-empty string');
|
||||
it('should create session with metadata and config', async () => {
|
||||
const metadata = { name: 'Test Session' };
|
||||
const config = { anonymous: true };
|
||||
|
||||
await honcho.session('test-session', { metadata, config });
|
||||
|
||||
expect(mockClient.workspaces.sessions.getOrCreate).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
{ id: 'test-session', metadata: metadata, configuration: config }
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for non-string session ID', () => {
|
||||
expect(() => honcho.session(null as any)).toThrow('Session ID must be a non-empty string');
|
||||
expect(() => honcho.session(undefined as any)).toThrow('Session ID must be a non-empty string');
|
||||
expect(() => honcho.session(123 as any)).toThrow('Session ID must be a non-empty string');
|
||||
it('should throw error for empty session ID', async () => {
|
||||
await expect(honcho.session('')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for non-string session ID', async () => {
|
||||
await expect(honcho.session(null as any)).rejects.toThrow();
|
||||
await expect(honcho.session(undefined as any)).rejects.toThrow();
|
||||
await expect(honcho.session(123 as any)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -183,7 +210,7 @@ describe('Honcho Client', () => {
|
|||
const sessionsPage = await honcho.getSessions();
|
||||
|
||||
expect(sessionsPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.sessions.list).toHaveBeenCalledWith('test-workspace', { filter: undefined });
|
||||
expect(mockClient.workspaces.sessions.list).toHaveBeenCalledWith('test-workspace', { filters: undefined });
|
||||
});
|
||||
|
||||
it('should handle empty sessions list', async () => {
|
||||
|
|
@ -203,7 +230,7 @@ describe('Honcho Client', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.list.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
await expect(honcho.getSessions()).rejects.toThrow('API Error');
|
||||
await expect(honcho.getSessions()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -236,7 +263,7 @@ describe('Honcho Client', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.getOrCreate.mockRejectedValue(new Error('Workspace not found'));
|
||||
|
||||
await expect(honcho.getMetadata()).rejects.toThrow('Workspace not found');
|
||||
await expect(honcho.getMetadata()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -276,7 +303,7 @@ describe('Honcho Client', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.update.mockRejectedValue(new Error('Update failed'));
|
||||
|
||||
await expect(honcho.setMetadata({ key: 'value' })).rejects.toThrow('Update failed');
|
||||
await expect(honcho.setMetadata({ key: 'value' })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -313,73 +340,145 @@ describe('Honcho Client', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.list.mockRejectedValue(new Error('Failed to list workspaces'));
|
||||
|
||||
await expect(honcho.getWorkspaces()).rejects.toThrow('Failed to list workspaces');
|
||||
await expect(honcho.getWorkspaces()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should search for messages and return Page', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [
|
||||
{ id: 'msg1', content: 'Hello world', peer_id: 'peer1' },
|
||||
{ id: 'msg2', content: 'Hello there', peer_id: 'peer2' },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockSearchResults = [
|
||||
{ id: 'msg1', content: 'Hello world', peer_id: 'peer1' },
|
||||
{ id: 'msg2', content: 'Hello there', peer_id: 'peer2' },
|
||||
];
|
||||
mockClient.workspaces.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await honcho.search('hello');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.search).toHaveBeenCalledWith('test-workspace', { body: 'hello' });
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
expect(mockClient.workspaces.search).toHaveBeenCalledWith('test-workspace', { query: 'hello', limit: undefined });
|
||||
});
|
||||
|
||||
it('should handle empty search results', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockSearchResults: any[] = [];
|
||||
mockClient.workspaces.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await honcho.search('nonexistent');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw error for empty query', async () => {
|
||||
await expect(honcho.search('')).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(honcho.search(' ')).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(honcho.search('')).rejects.toThrow();
|
||||
await expect(honcho.search(' ')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for non-string query', async () => {
|
||||
await expect(honcho.search(null as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(honcho.search(undefined as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(honcho.search(123 as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(honcho.search(null as any)).rejects.toThrow();
|
||||
await expect(honcho.search(undefined as any)).rejects.toThrow();
|
||||
await expect(honcho.search(123 as any)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle complex search queries', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockSearchResults: any[] = [];
|
||||
mockClient.workspaces.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const complexQuery = 'complex query with "quotes" and special characters!@#$%';
|
||||
await honcho.search(complexQuery);
|
||||
|
||||
expect(mockClient.workspaces.search).toHaveBeenCalledWith('test-workspace', { body: complexQuery });
|
||||
expect(mockClient.workspaces.search).toHaveBeenCalledWith('test-workspace', { query: complexQuery, limit: undefined });
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.search.mockRejectedValue(new Error('Search failed'));
|
||||
|
||||
await expect(honcho.search('test')).rejects.toThrow('Search failed');
|
||||
await expect(honcho.search('test')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDeriverStatus', () => {
|
||||
it('should return deriver status without options', async () => {
|
||||
const mockStatus = {
|
||||
total_work_units: 10,
|
||||
completed_work_units: 5,
|
||||
in_progress_work_units: 3,
|
||||
pending_work_units: 2,
|
||||
sessions: { 'session1': { status: 'active' } },
|
||||
};
|
||||
mockClient.workspaces.deriverStatus.mockResolvedValue(mockStatus);
|
||||
|
||||
const status = await honcho.getDeriverStatus();
|
||||
|
||||
expect(status).toEqual({
|
||||
totalWorkUnits: 10,
|
||||
completedWorkUnits: 5,
|
||||
inProgressWorkUnits: 3,
|
||||
pendingWorkUnits: 2,
|
||||
sessions: { 'session1': { status: 'active' } },
|
||||
});
|
||||
expect(mockClient.workspaces.deriverStatus).toHaveBeenCalledWith('test-workspace', {});
|
||||
});
|
||||
|
||||
it('should return deriver status with options', async () => {
|
||||
const mockStatus = {
|
||||
total_work_units: 5,
|
||||
completed_work_units: 3,
|
||||
in_progress_work_units: 1,
|
||||
pending_work_units: 1,
|
||||
};
|
||||
mockClient.workspaces.deriverStatus.mockResolvedValue(mockStatus);
|
||||
|
||||
const status = await honcho.getDeriverStatus({
|
||||
observerId: 'observer1',
|
||||
senderId: 'sender1',
|
||||
sessionId: 'session1',
|
||||
});
|
||||
|
||||
expect(status).toEqual({
|
||||
totalWorkUnits: 5,
|
||||
completedWorkUnits: 3,
|
||||
inProgressWorkUnits: 1,
|
||||
pendingWorkUnits: 1,
|
||||
sessions: undefined,
|
||||
});
|
||||
expect(mockClient.workspaces.deriverStatus).toHaveBeenCalledWith('test-workspace', {
|
||||
observer_id: 'observer1',
|
||||
sender_id: 'sender1',
|
||||
session_id: 'session1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pollDeriverStatus', () => {
|
||||
it('should poll until processing is complete', async () => {
|
||||
const mockStatusComplete = {
|
||||
total_work_units: 5,
|
||||
completed_work_units: 5,
|
||||
in_progress_work_units: 0,
|
||||
pending_work_units: 0,
|
||||
};
|
||||
mockClient.workspaces.deriverStatus.mockResolvedValue(mockStatusComplete);
|
||||
|
||||
const status = await honcho.pollDeriverStatus();
|
||||
|
||||
expect(status).toEqual({
|
||||
totalWorkUnits: 5,
|
||||
completedWorkUnits: 5,
|
||||
inProgressWorkUnits: 0,
|
||||
pendingWorkUnits: 0,
|
||||
sessions: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should timeout if processing takes too long', async () => {
|
||||
const mockStatusPending = {
|
||||
total_work_units: 5,
|
||||
completed_work_units: 2,
|
||||
in_progress_work_units: 2,
|
||||
pending_work_units: 1,
|
||||
};
|
||||
mockClient.workspaces.deriverStatus.mockResolvedValue(mockStatusPending);
|
||||
|
||||
await expect(honcho.pollDeriverStatus({ timeoutMs: 100 })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
const mockPeerData = { id: 'assistant', metadata: { role: 'ai' } };
|
||||
const mockSessionData = { id: 'chat-session', metadata: { topic: 'general' } };
|
||||
const mockMessages = [
|
||||
{ id: 'msg1', content: 'Hello', peer_name: 'user' },
|
||||
{ id: 'msg2', content: 'Hi there!', peer_name: 'assistant' },
|
||||
{ id: 'msg1', content: 'Hello', peer_id: 'user' },
|
||||
{ id: 'msg2', content: 'Hi there!', peer_id: 'assistant' },
|
||||
];
|
||||
const mockContextData = { messages: mockMessages, summary: 'Friendly greeting' };
|
||||
|
||||
|
|
@ -71,8 +71,8 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
mockWorkspacesApi.workspaces.peers.chat.mockResolvedValue({ content: 'AI response' });
|
||||
|
||||
// Step 1: Create peers
|
||||
const user = honcho.peer('user');
|
||||
const assistant = honcho.peer('assistant');
|
||||
const user = await honcho.peer('user');
|
||||
const assistant = await honcho.peer('assistant');
|
||||
|
||||
expect(user).toBeInstanceOf(Peer);
|
||||
expect(assistant).toBeInstanceOf(Peer);
|
||||
|
|
@ -80,7 +80,7 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
expect(assistant.id).toBe('assistant');
|
||||
|
||||
// Step 2: Create session
|
||||
const session = honcho.session('chat-session');
|
||||
const session = await honcho.session('chat-session');
|
||||
expect(session).toBeInstanceOf(Session);
|
||||
expect(session.id).toBe('chat-session');
|
||||
|
||||
|
|
@ -90,8 +90,8 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
'integration-test-workspace',
|
||||
'chat-session',
|
||||
{
|
||||
'user': { observe_me: true, observe_others: false },
|
||||
'assistant': { observe_me: true, observe_others: false }
|
||||
'user': {},
|
||||
'assistant': {}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -123,12 +123,14 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
const anthropicFormat = context.toAnthropic('assistant');
|
||||
|
||||
expect(openAIFormat).toEqual([
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'assistant', content: 'Hi there!' },
|
||||
{ role: 'system', content: '<summary>Friendly greeting</summary>' },
|
||||
{ role: 'user', content: 'Hello', name: 'user' },
|
||||
{ role: 'assistant', content: 'Hi there!', name: 'assistant' },
|
||||
]);
|
||||
|
||||
expect(anthropicFormat).toEqual([
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'user', content: '<summary>Friendly greeting</summary>' },
|
||||
{ role: 'user', content: 'user: Hello' },
|
||||
{ role: 'assistant', content: 'Hi there!' },
|
||||
]);
|
||||
|
||||
|
|
@ -193,32 +195,17 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
|
||||
it('should handle search functionality across different scopes', async () => {
|
||||
// Setup mock responses
|
||||
const mockWorkspaceSearchResults = {
|
||||
items: [
|
||||
{ id: 'msg1', content: 'workspace message', peer_id: 'peer1' },
|
||||
],
|
||||
total: 1,
|
||||
size: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockWorkspaceSearchResults = [
|
||||
{ id: 'msg1', content: 'workspace message', peer_id: 'peer1' },
|
||||
];
|
||||
|
||||
const mockPeerSearchResults = {
|
||||
items: [
|
||||
{ id: 'msg2', content: 'peer message', peer_id: 'peer1' },
|
||||
],
|
||||
total: 1,
|
||||
size: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockPeerSearchResults = [
|
||||
{ id: 'msg2', content: 'peer message', peer_id: 'peer1' },
|
||||
];
|
||||
|
||||
const mockSessionSearchResults = {
|
||||
items: [
|
||||
{ id: 'msg3', content: 'session message', peer_id: 'peer1' },
|
||||
],
|
||||
total: 1,
|
||||
size: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockSessionSearchResults = [
|
||||
{ id: 'msg3', content: 'session message', peer_id: 'peer1' },
|
||||
];
|
||||
|
||||
mockWorkspacesApi.workspaces.search.mockResolvedValue(mockWorkspaceSearchResults);
|
||||
mockWorkspacesApi.workspaces.peers.search.mockResolvedValue(mockPeerSearchResults);
|
||||
|
|
@ -226,30 +213,30 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
|
||||
// Step 1: Search workspace
|
||||
const workspaceResults = await honcho.search('test query');
|
||||
expect(workspaceResults).toBeInstanceOf(Page);
|
||||
expect(Array.isArray(workspaceResults)).toBe(true);
|
||||
expect(mockWorkspacesApi.workspaces.search).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
{ body: 'test query' }
|
||||
{ query: 'test query', limit: undefined }
|
||||
);
|
||||
|
||||
// Step 2: Search peer
|
||||
const peer = honcho.peer('test-peer');
|
||||
const peer = await honcho.peer('test-peer');
|
||||
const peerResults = await peer.search('peer query');
|
||||
expect(peerResults).toBeInstanceOf(Page);
|
||||
expect(Array.isArray(peerResults)).toBe(true);
|
||||
expect(mockWorkspacesApi.workspaces.peers.search).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'test-peer',
|
||||
{ query: 'peer query' }
|
||||
{ query: 'peer query', limit: undefined }
|
||||
);
|
||||
|
||||
// Step 3: Search session
|
||||
const session = honcho.session('test-session');
|
||||
const session = await honcho.session('test-session');
|
||||
const sessionResults = await session.search('session query');
|
||||
expect(sessionResults).toBeInstanceOf(Page);
|
||||
expect(Array.isArray(sessionResults)).toBe(true);
|
||||
expect(mockWorkspacesApi.workspaces.sessions.search).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'test-session',
|
||||
{ query: 'session query' }
|
||||
{ query: 'session query', limit: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -258,14 +245,14 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
mockWorkspacesApi.workspaces.peers.chat.mockRejectedValue(new Error('Chat API failed'));
|
||||
mockWorkspacesApi.workspaces.sessions.getContext.mockRejectedValue(new Error('Context API failed'));
|
||||
|
||||
const assistant = honcho.peer('assistant');
|
||||
const session = honcho.session('error-session');
|
||||
const assistant = await honcho.peer('assistant');
|
||||
const session = await honcho.session('error-session');
|
||||
|
||||
// Test error handling in chat
|
||||
await expect(assistant.chat('Hello')).rejects.toThrow('Chat API failed');
|
||||
await expect(assistant.chat('Hello')).rejects.toThrow();
|
||||
|
||||
// Test error handling in context
|
||||
await expect(session.getContext()).rejects.toThrow('Context API failed');
|
||||
await expect(session.getContext()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle pagination correctly', async () => {
|
||||
|
|
@ -329,9 +316,9 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
|
||||
mockWorkspacesApi.workspaces.peers.workingRepresentation.mockResolvedValue(mockWorkingRep);
|
||||
|
||||
const session = honcho.session('working-rep-session');
|
||||
const alice = honcho.peer('alice');
|
||||
const bob = honcho.peer('bob');
|
||||
const session = await honcho.session('working-rep-session');
|
||||
const alice = await honcho.peer('alice');
|
||||
const bob = await honcho.peer('bob');
|
||||
|
||||
// Test working representation without target
|
||||
const globalRep = await session.workingRep('alice');
|
||||
|
|
@ -359,8 +346,8 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
mockWorkspacesApi.workspaces.peers.list.mockResolvedValue({ items: [], total: 0, hasNextPage: false });
|
||||
mockWorkspacesApi.workspaces.sessions.getContext.mockResolvedValue({ messages: [] });
|
||||
|
||||
const peer = honcho.peer('empty-peer');
|
||||
const session = honcho.session('empty-session');
|
||||
const peer = await honcho.peer('empty-peer');
|
||||
const session = await honcho.session('empty-session');
|
||||
|
||||
// Test null chat response
|
||||
const chatResult = await peer.chat('Hello');
|
||||
|
|
@ -379,20 +366,20 @@ describe('Honcho SDK Integration Tests', () => {
|
|||
|
||||
it('should maintain type safety throughout the workflow', async () => {
|
||||
// This test verifies TypeScript types are maintained correctly
|
||||
const peer: Peer = honcho.peer('typed-peer');
|
||||
const session: Session = honcho.session('typed-session');
|
||||
const peer: Peer = await honcho.peer('typed-peer');
|
||||
const session: Session = await honcho.session('typed-session');
|
||||
|
||||
expect(typeof peer.id).toBe('string');
|
||||
expect(typeof session.id).toBe('string');
|
||||
|
||||
const message = peer.message('typed message', { metadata: { type: 'test' } });
|
||||
expect(typeof message.peerId).toBe('string');
|
||||
expect(typeof message.peer_id).toBe('string');
|
||||
expect(typeof message.content).toBe('string');
|
||||
expect(typeof message.metadata).toBe('object');
|
||||
|
||||
// Mock successful operations
|
||||
mockWorkspacesApi.workspaces.sessions.getContext.mockResolvedValue({
|
||||
messages: [{ id: 'msg1', content: 'Hello', peer_name: 'typed-peer' }],
|
||||
messages: [{ id: 'msg1', content: 'Hello', peer_id: 'typed-peer' }],
|
||||
summary: 'Test summary',
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ describe('Page', () => {
|
|||
mockOriginalPage.nextPage.mockRejectedValue(new Error('Failed to get next page'));
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
await expect(page.nextPage()).rejects.toThrow('Failed to get next page');
|
||||
await expect(page.nextPage()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -352,7 +352,7 @@ describe('Page', () => {
|
|||
for await (const item of page) {
|
||||
// This should throw
|
||||
}
|
||||
}).rejects.toThrow('Transform error');
|
||||
}).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle transform function returning null', async () => {
|
||||
|
|
|
|||
|
|
@ -42,16 +42,17 @@ describe('Peer', () => {
|
|||
environment: 'local',
|
||||
});
|
||||
|
||||
peer = new Peer('test-peer', honcho);
|
||||
peer = new Peer('test-peer', 'test-workspace', (honcho as any)._client);
|
||||
mockClient = (honcho as any)._client;
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with correct properties', () => {
|
||||
const newPeer = new Peer('peer-id', honcho);
|
||||
const newPeer = new Peer('peer-id', 'test-workspace', mockClient);
|
||||
|
||||
expect(newPeer.id).toBe('peer-id');
|
||||
expect(newPeer['_honcho']).toBe(honcho);
|
||||
expect(newPeer.workspaceId).toBe('test-workspace');
|
||||
expect(newPeer['_client']).toBe(mockClient);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -102,7 +103,7 @@ describe('Peer', () => {
|
|||
});
|
||||
|
||||
it('should handle chat with target peer', async () => {
|
||||
const targetPeer = new Peer('target-peer', honcho);
|
||||
const targetPeer = new Peer('target-peer', 'test-workspace', mockClient);
|
||||
const mockResponse = { content: 'Targeted response' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
|
|
@ -142,15 +143,11 @@ describe('Peer', () => {
|
|||
});
|
||||
|
||||
it('should handle all options together', async () => {
|
||||
const targetPeer = new Peer('target-peer', honcho);
|
||||
const targetPeer = new Peer('target-peer', 'test-workspace', mockClient);
|
||||
const mockResponse = { content: 'Full options response' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
await peer.chat('Hello', {
|
||||
stream: true,
|
||||
target: targetPeer,
|
||||
sessionId: 'session-456'
|
||||
});
|
||||
await peer.chat('Hello', { stream: true, target: targetPeer, sessionId: 'session-456' });
|
||||
|
||||
expect(mockClient.workspaces.peers.chat).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
|
|
@ -162,7 +159,7 @@ describe('Peer', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.chat.mockRejectedValue(new Error('Chat failed'));
|
||||
|
||||
await expect(peer.chat('Hello')).rejects.toThrow('Chat failed');
|
||||
await expect(peer.chat('Hello')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -185,7 +182,7 @@ describe('Peer', () => {
|
|||
expect(mockClient.workspaces.peers.sessions.list).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ filter: undefined }
|
||||
{ filters: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -206,7 +203,7 @@ describe('Peer', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.sessions.list.mockRejectedValue(new Error('Failed to get sessions'));
|
||||
|
||||
await expect(peer.getSessions()).rejects.toThrow('Failed to get sessions');
|
||||
await expect(peer.getSessions()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -215,7 +212,7 @@ describe('Peer', () => {
|
|||
const message = peer.message('Test content');
|
||||
|
||||
expect(message).toEqual({
|
||||
peerId: 'test-peer',
|
||||
peer_id: 'test-peer',
|
||||
content: 'Test content',
|
||||
metadata: undefined,
|
||||
});
|
||||
|
|
@ -226,7 +223,7 @@ describe('Peer', () => {
|
|||
const message = peer.message('Hello there', { metadata });
|
||||
|
||||
expect(message).toEqual({
|
||||
peerId: 'test-peer',
|
||||
peer_id: 'test-peer',
|
||||
content: 'Hello there',
|
||||
metadata: { importance: 'high', category: 'greeting' },
|
||||
});
|
||||
|
|
@ -236,7 +233,7 @@ describe('Peer', () => {
|
|||
const message = peer.message('');
|
||||
|
||||
expect(message).toEqual({
|
||||
peerId: 'test-peer',
|
||||
peer_id: 'test-peer',
|
||||
content: '',
|
||||
metadata: undefined,
|
||||
});
|
||||
|
|
@ -275,7 +272,7 @@ describe('Peer', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.getOrCreate.mockRejectedValue(new Error('Peer not found'));
|
||||
|
||||
await expect(peer.getMetadata()).rejects.toThrow('Peer not found');
|
||||
await expect(peer.getMetadata()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -325,26 +322,66 @@ describe('Peer', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.update.mockRejectedValue(new Error('Update failed'));
|
||||
|
||||
await expect(peer.setMetadata({ key: 'value' })).rejects.toThrow('Update failed');
|
||||
await expect(peer.setMetadata({ key: 'value' })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPeerConfig', () => {
|
||||
it('should return peer configuration', async () => {
|
||||
const mockPeer = {
|
||||
id: 'test-peer',
|
||||
configuration: { observe_me: true, observe_others: false },
|
||||
};
|
||||
mockClient.workspaces.peers.getOrCreate.mockResolvedValue(mockPeer);
|
||||
|
||||
const config = await peer.getPeerConfig();
|
||||
|
||||
expect(config).toEqual({ observe_me: true, observe_others: false });
|
||||
expect(mockClient.workspaces.peers.getOrCreate).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
{ id: 'test-peer' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty object when no configuration exists', async () => {
|
||||
const mockPeer = {
|
||||
id: 'test-peer',
|
||||
configuration: null,
|
||||
};
|
||||
mockClient.workspaces.peers.getOrCreate.mockResolvedValue(mockPeer);
|
||||
|
||||
const config = await peer.getPeerConfig();
|
||||
|
||||
expect(config).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPeerConfig', () => {
|
||||
it('should update peer configuration', async () => {
|
||||
const config = { observe_me: false, observe_others: true };
|
||||
mockClient.workspaces.peers.update.mockResolvedValue({});
|
||||
|
||||
await peer.setPeerConfig(config);
|
||||
|
||||
expect(mockClient.workspaces.peers.update).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ configuration: config }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should search peer messages and return Page', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [
|
||||
{ id: 'msg1', content: 'Hello world', peer_id: 'test-peer' },
|
||||
{ id: 'msg2', content: 'Hello there', peer_id: 'test-peer' },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
it('should search peer messages and return array', async () => {
|
||||
const mockSearchResults = [
|
||||
{ id: 'msg1', content: 'Hello world', peer_id: 'test-peer' },
|
||||
{ id: 'msg2', content: 'Hello there', peer_id: 'test-peer' },
|
||||
];
|
||||
mockClient.workspaces.peers.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await peer.search('hello');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
expect(mockClient.workspaces.peers.search).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
|
|
@ -353,37 +390,27 @@ describe('Peer', () => {
|
|||
});
|
||||
|
||||
it('should handle empty search results', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockSearchResults: any[] = [];
|
||||
mockClient.workspaces.peers.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await peer.search('nonexistent');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw error for empty query', async () => {
|
||||
await expect(peer.search('')).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(peer.search(' ')).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(peer.search('')).rejects.toThrow();
|
||||
await expect(peer.search(' ')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for non-string query', async () => {
|
||||
await expect(peer.search(null as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(peer.search(undefined as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(peer.search(123 as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(peer.search(null as any)).rejects.toThrow();
|
||||
await expect(peer.search(undefined as any)).rejects.toThrow();
|
||||
await expect(peer.search(123 as any)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle complex search queries', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockSearchResults: any[] = [];
|
||||
mockClient.workspaces.peers.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const complexQuery = 'complex query with "quotes" and special characters!@#$%';
|
||||
|
|
@ -399,7 +426,7 @@ describe('Peer', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.search.mockRejectedValue(new Error('Search failed'));
|
||||
|
||||
await expect(peer.search('test')).rejects.toThrow('Search failed');
|
||||
await expect(peer.search('test')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,10 +14,13 @@ jest.mock('@honcho-ai/core', () => {
|
|||
set: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
list: jest.fn(),
|
||||
getConfig: jest.fn(),
|
||||
setConfig: jest.fn(),
|
||||
},
|
||||
messages: {
|
||||
create: jest.fn(),
|
||||
list: jest.fn(),
|
||||
upload: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
|
|
@ -49,25 +52,17 @@ describe('Session', () => {
|
|||
environment: 'local',
|
||||
});
|
||||
|
||||
session = new Session('test-session', honcho);
|
||||
session = new Session('test-session', 'test-workspace', (honcho as any)._client);
|
||||
mockClient = (honcho as any)._client;
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with correct properties', () => {
|
||||
const newSession = new Session('session-id', honcho);
|
||||
|
||||
expect(newSession.id).toBe('session-id');
|
||||
expect(newSession['_honcho']).toBe(honcho);
|
||||
});
|
||||
|
||||
it('should handle constructor options', () => {
|
||||
const newSession = new Session('session-id', honcho, {
|
||||
anonymous: true,
|
||||
summarize: false
|
||||
});
|
||||
const newSession = new Session('session-id', 'test-workspace', mockClient);
|
||||
|
||||
expect(newSession.id).toBe('session-id');
|
||||
expect(newSession.workspaceId).toBe('test-workspace');
|
||||
expect(newSession['_client']).toBe(mockClient);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -80,12 +75,12 @@ describe('Session', () => {
|
|||
expect(mockClient.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ 'peer1': { observe_me: true, observe_others: false } }
|
||||
{ 'peer1': {} }
|
||||
);
|
||||
});
|
||||
|
||||
it('should add single peer by Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
const peer = new Peer('peer1', 'test-workspace', mockClient);
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
await session.addPeers(peer);
|
||||
|
|
@ -93,7 +88,7 @@ describe('Session', () => {
|
|||
expect(mockClient.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ 'peer1': { observe_me: true, observe_others: false } }
|
||||
{ 'peer1': {} }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -106,18 +101,18 @@ describe('Session', () => {
|
|||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
'peer1': { observe_me: true, observe_others: false },
|
||||
'peer2': { observe_me: true, observe_others: false },
|
||||
'peer3': { observe_me: true, observe_others: false }
|
||||
'peer1': {},
|
||||
'peer2': {},
|
||||
'peer3': {}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should add array of Peer objects', async () => {
|
||||
const peers = [
|
||||
new Peer('peer1', honcho),
|
||||
new Peer('peer2', honcho),
|
||||
new Peer('peer3', honcho),
|
||||
new Peer('peer1', 'test-workspace', mockClient),
|
||||
new Peer('peer2', 'test-workspace', mockClient),
|
||||
new Peer('peer3', 'test-workspace', mockClient),
|
||||
];
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
|
|
@ -127,9 +122,9 @@ describe('Session', () => {
|
|||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
'peer1': { observe_me: true, observe_others: false },
|
||||
'peer2': { observe_me: true, observe_others: false },
|
||||
'peer3': { observe_me: true, observe_others: false }
|
||||
'peer1': {},
|
||||
'peer2': {},
|
||||
'peer3': {}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -137,7 +132,7 @@ describe('Session', () => {
|
|||
it('should add mixed array of strings and Peer objects', async () => {
|
||||
const peers = [
|
||||
'string-peer',
|
||||
new Peer('object-peer', honcho),
|
||||
new Peer('object-peer', 'test-workspace', mockClient),
|
||||
];
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
|
|
@ -147,8 +142,24 @@ describe('Session', () => {
|
|||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
'string-peer': { observe_me: true, observe_others: false },
|
||||
'object-peer': { observe_me: true, observe_others: false }
|
||||
'string-peer': {},
|
||||
'object-peer': {}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should add peer with SessionPeerConfig', async () => {
|
||||
const { SessionPeerConfig } = require('../src/session');
|
||||
const config = new SessionPeerConfig(false, true);
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
await session.addPeers([['peer1', config]]);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
'peer1': { observe_me: false, observe_others: true }
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -156,7 +167,7 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.peers.add.mockRejectedValue(new Error('Failed to add peers'));
|
||||
|
||||
await expect(session.addPeers('peer1')).rejects.toThrow('Failed to add peers');
|
||||
await expect(session.addPeers('peer1')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -169,12 +180,12 @@ describe('Session', () => {
|
|||
expect(mockClient.workspaces.sessions.peers.set).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ 'peer1': { observe_me: true, observe_others: false } }
|
||||
{ 'peer1': {} }
|
||||
);
|
||||
});
|
||||
|
||||
it('should set single peer by Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
const peer = new Peer('peer1', 'test-workspace', mockClient);
|
||||
mockClient.workspaces.sessions.peers.set.mockResolvedValue({});
|
||||
|
||||
await session.setPeers(peer);
|
||||
|
|
@ -182,12 +193,12 @@ describe('Session', () => {
|
|||
expect(mockClient.workspaces.sessions.peers.set).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ 'peer1': { observe_me: true, observe_others: false } }
|
||||
{ 'peer1': {} }
|
||||
);
|
||||
});
|
||||
|
||||
it('should set array of peers', async () => {
|
||||
const peers = ['peer1', new Peer('peer2', honcho)];
|
||||
const peers = ['peer1', new Peer('peer2', 'test-workspace', mockClient)];
|
||||
mockClient.workspaces.sessions.peers.set.mockResolvedValue({});
|
||||
|
||||
await session.setPeers(peers);
|
||||
|
|
@ -196,8 +207,8 @@ describe('Session', () => {
|
|||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
'peer1': { observe_me: true, observe_others: false },
|
||||
'peer2': { observe_me: true, observe_others: false }
|
||||
'peer1': {},
|
||||
'peer2': {}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -205,7 +216,7 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.peers.set.mockRejectedValue(new Error('Failed to set peers'));
|
||||
|
||||
await expect(session.setPeers(['peer1'])).rejects.toThrow('Failed to set peers');
|
||||
await expect(session.setPeers(['peer1'])).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -223,7 +234,7 @@ describe('Session', () => {
|
|||
});
|
||||
|
||||
it('should remove single peer by Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
const peer = new Peer('peer1', 'test-workspace', mockClient);
|
||||
mockClient.workspaces.sessions.peers.remove.mockResolvedValue({});
|
||||
|
||||
await session.removePeers(peer);
|
||||
|
|
@ -236,7 +247,7 @@ describe('Session', () => {
|
|||
});
|
||||
|
||||
it('should remove array of peers', async () => {
|
||||
const peers = ['peer1', new Peer('peer2', honcho)];
|
||||
const peers = ['peer1', new Peer('peer2', 'test-workspace', mockClient)];
|
||||
mockClient.workspaces.sessions.peers.remove.mockResolvedValue({});
|
||||
|
||||
await session.removePeers(peers);
|
||||
|
|
@ -251,12 +262,12 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.peers.remove.mockRejectedValue(new Error('Failed to remove peers'));
|
||||
|
||||
await expect(session.removePeers(['peer1'])).rejects.toThrow('Failed to remove peers');
|
||||
await expect(session.removePeers(['peer1'])).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPeers', () => {
|
||||
it('should return Page of Peer instances', async () => {
|
||||
it('should return array of Peer instances', async () => {
|
||||
const mockPeersData = {
|
||||
items: [
|
||||
{ id: 'peer1', metadata: {} },
|
||||
|
|
@ -271,6 +282,9 @@ describe('Session', () => {
|
|||
const peers = await session.getPeers();
|
||||
|
||||
expect(peers).toBeInstanceOf(Array);
|
||||
expect(peers).toHaveLength(2);
|
||||
expect(peers[0]).toBeInstanceOf(Peer);
|
||||
expect(peers[1]).toBeInstanceOf(Peer);
|
||||
expect(mockClient.workspaces.sessions.peers.list).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session'
|
||||
|
|
@ -295,14 +309,78 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.peers.list.mockRejectedValue(new Error('Failed to get peers'));
|
||||
|
||||
await expect(session.getPeers()).rejects.toThrow('Failed to get peers');
|
||||
await expect(session.getPeers()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPeerConfig', () => {
|
||||
it('should return peer configuration', async () => {
|
||||
const mockConfig = { observe_me: true, observe_others: false };
|
||||
mockClient.workspaces.sessions.peers.getConfig.mockResolvedValue(mockConfig);
|
||||
|
||||
const config = await session.getPeerConfig('peer1');
|
||||
|
||||
expect(config).toEqual(mockConfig);
|
||||
expect(mockClient.workspaces.sessions.peers.getConfig).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
'peer1'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Peer object input', async () => {
|
||||
const peer = new Peer('peer1', 'test-workspace', mockClient);
|
||||
const mockConfig = { observe_me: false, observe_others: true };
|
||||
mockClient.workspaces.sessions.peers.getConfig.mockResolvedValue(mockConfig);
|
||||
|
||||
const config = await session.getPeerConfig(peer);
|
||||
|
||||
expect(config).toEqual(mockConfig);
|
||||
expect(mockClient.workspaces.sessions.peers.getConfig).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
'peer1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPeerConfig', () => {
|
||||
it('should set peer configuration', async () => {
|
||||
const { SessionPeerConfig } = require('../src/session');
|
||||
const config = new SessionPeerConfig(false, true);
|
||||
mockClient.workspaces.sessions.peers.setConfig.mockResolvedValue({});
|
||||
|
||||
await session.setPeerConfig('peer1', config);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.setConfig).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
'peer1',
|
||||
{ observe_me: false, observe_others: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Peer object input', async () => {
|
||||
const peer = new Peer('peer1', 'test-workspace', mockClient);
|
||||
const { SessionPeerConfig } = require('../src/session');
|
||||
const config = new SessionPeerConfig(true, false);
|
||||
mockClient.workspaces.sessions.peers.setConfig.mockResolvedValue({});
|
||||
|
||||
await session.setPeerConfig(peer, config);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.setConfig).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
'peer1',
|
||||
{ observe_me: true, observe_others: false }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addMessages', () => {
|
||||
it('should add single message', async () => {
|
||||
const message = {
|
||||
peerId: 'peer1',
|
||||
peer_id: 'peer1',
|
||||
content: 'Hello world',
|
||||
metadata: { type: 'greeting' },
|
||||
};
|
||||
|
|
@ -325,8 +403,8 @@ describe('Session', () => {
|
|||
|
||||
it('should add array of messages', async () => {
|
||||
const messages = [
|
||||
{ peerId: 'peer1', content: 'Message 1', metadata: { order: 1 } },
|
||||
{ peerId: 'peer2', content: 'Message 2', metadata: { order: 2 } },
|
||||
{ peer_id: 'peer1', content: 'Message 1', metadata: { order: 1 } },
|
||||
{ peer_id: 'peer2', content: 'Message 2', metadata: { order: 2 } },
|
||||
];
|
||||
mockClient.workspaces.sessions.messages.create.mockResolvedValue({});
|
||||
|
||||
|
|
@ -346,7 +424,7 @@ describe('Session', () => {
|
|||
|
||||
it('should handle messages without metadata', async () => {
|
||||
const message = {
|
||||
peerId: 'peer1',
|
||||
peer_id: 'peer1',
|
||||
content: 'Simple message',
|
||||
};
|
||||
mockClient.workspaces.sessions.messages.create.mockResolvedValue({});
|
||||
|
|
@ -356,7 +434,7 @@ describe('Session', () => {
|
|||
expect(mockClient.workspaces.sessions.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ messages: [{ peer_id: 'peer1', content: 'Simple message', metadata: undefined }] }
|
||||
{ messages: [{ peer_id: 'peer1', content: 'Simple message' }] }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -375,12 +453,12 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.messages.create.mockRejectedValue(new Error('Failed to add messages'));
|
||||
|
||||
await expect(session.addMessages({ peerId: 'peer1', content: 'test' })).rejects.toThrow('Failed to add messages');
|
||||
await expect(session.addMessages({ peer_id: 'peer1', content: 'test' })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessages', () => {
|
||||
it('should get messages without options', async () => {
|
||||
it('should get messages without filter', async () => {
|
||||
const mockMessagesData = {
|
||||
items: [
|
||||
{ id: 'msg1', content: 'Message 1', peer_id: 'peer1' },
|
||||
|
|
@ -411,22 +489,20 @@ describe('Session', () => {
|
|||
};
|
||||
mockClient.workspaces.sessions.messages.list.mockResolvedValue(mockMessagesData);
|
||||
|
||||
const options = {
|
||||
filter: { peer_id: 'peer1', type: 'important' }
|
||||
};
|
||||
await session.getMessages(options);
|
||||
const filter = { peer_id: { value: 'peer1' }, type: { value: 'important' } };
|
||||
await session.getMessages(filter);
|
||||
|
||||
expect(mockClient.workspaces.sessions.messages.list).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ peer_id: 'peer1', type: 'important' }
|
||||
{ peer_id: { value: 'peer1' }, type: { value: 'important' } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.messages.list.mockRejectedValue(new Error('Failed to get messages'));
|
||||
|
||||
await expect(session.getMessages()).rejects.toThrow('Failed to get messages');
|
||||
await expect(session.getMessages()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -462,7 +538,7 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.getOrCreate.mockRejectedValue(new Error('Session not found'));
|
||||
|
||||
await expect(session.getMetadata()).rejects.toThrow('Session not found');
|
||||
await expect(session.getMetadata()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -495,7 +571,7 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.update.mockRejectedValue(new Error('Update failed'));
|
||||
|
||||
await expect(session.setMetadata({ key: 'value' })).rejects.toThrow('Update failed');
|
||||
await expect(session.setMetadata({ key: 'value' })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -503,8 +579,8 @@ describe('Session', () => {
|
|||
it('should get session context without options', async () => {
|
||||
const mockContext = {
|
||||
messages: [
|
||||
{ id: 'msg1', content: 'Hello', peer_name: 'peer1' },
|
||||
{ id: 'msg2', content: 'Hi there', peer_name: 'peer2' },
|
||||
{ id: 'msg1', content: 'Hello', peer_id: 'peer1' },
|
||||
{ id: 'msg2', content: 'Hi there', peer_id: 'peer2' },
|
||||
],
|
||||
summary: 'Conversation summary',
|
||||
};
|
||||
|
|
@ -525,13 +601,12 @@ describe('Session', () => {
|
|||
|
||||
it('should get session context with options', async () => {
|
||||
const mockContext = {
|
||||
messages: [{ id: 'msg1', content: 'Hello', peer_name: 'peer1' }],
|
||||
messages: [{ id: 'msg1', content: 'Hello', peer_id: 'peer1' }],
|
||||
summary: 'Brief summary',
|
||||
};
|
||||
mockClient.workspaces.sessions.getContext.mockResolvedValue(mockContext);
|
||||
|
||||
const options = { summary: true, tokens: 1000 };
|
||||
const context = await session.getContext(options);
|
||||
const context = await session.getContext({ summary: true, tokens: 1000 });
|
||||
|
||||
expect(context).toBeInstanceOf(SessionContext);
|
||||
expect(mockClient.workspaces.sessions.getContext).toHaveBeenCalledWith(
|
||||
|
|
@ -543,7 +618,7 @@ describe('Session', () => {
|
|||
|
||||
it('should handle context without summary', async () => {
|
||||
const mockContext = {
|
||||
messages: [{ id: 'msg1', content: 'Hello', peer_name: 'peer1' }],
|
||||
messages: [{ id: 'msg1', content: 'Hello', peer_id: 'peer1' }],
|
||||
};
|
||||
mockClient.workspaces.sessions.getContext.mockResolvedValue(mockContext);
|
||||
|
||||
|
|
@ -555,62 +630,71 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.getContext.mockRejectedValue(new Error('Failed to get context'));
|
||||
|
||||
await expect(session.getContext()).rejects.toThrow('Failed to get context');
|
||||
await expect(session.getContext()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should search session messages and return Page', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [
|
||||
{ id: 'msg1', content: 'Hello world', peer_id: 'peer1' },
|
||||
{ id: 'msg2', content: 'Hello there', peer_id: 'peer2' },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockSearchResults = [
|
||||
{ id: 'msg1', content: 'Hello world', peer_id: 'peer1' },
|
||||
{ id: 'msg2', content: 'Hello there', peer_id: 'peer2' },
|
||||
];
|
||||
mockClient.workspaces.sessions.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await session.search('hello');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
expect(mockClient.workspaces.sessions.search).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ query: 'hello' }
|
||||
{ query: 'hello', limit: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty search results', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const mockSearchResults: any[] = [];
|
||||
mockClient.workspaces.sessions.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await session.search('nonexistent');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw error for empty query', async () => {
|
||||
await expect(session.search('')).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(session.search(' ')).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(session.search('')).rejects.toThrow();
|
||||
await expect(session.search(' ')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for non-string query', async () => {
|
||||
await expect(session.search(null as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(session.search(undefined as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(session.search(123 as any)).rejects.toThrow('Search query must be a non-empty string');
|
||||
await expect(session.search(null as any)).rejects.toThrow();
|
||||
await expect(session.search(undefined as any)).rejects.toThrow();
|
||||
await expect(session.search(123 as any)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.search.mockRejectedValue(new Error('Search failed'));
|
||||
|
||||
await expect(session.search('test')).rejects.toThrow('Search failed');
|
||||
await expect(session.search('test')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadFile', () => {
|
||||
it('should upload file and return messages', async () => {
|
||||
const mockFile = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
||||
const mockMessages = [
|
||||
{ id: 'msg1', content: 'test content', peer_id: 'peer1' }
|
||||
];
|
||||
mockClient.workspaces.sessions.messages.upload.mockResolvedValue(mockMessages);
|
||||
|
||||
const messages = await session.uploadFile(mockFile, 'peer1');
|
||||
|
||||
expect(messages).toEqual(mockMessages);
|
||||
expect(mockClient.workspaces.sessions.messages.upload).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ file: mockFile, peer_id: 'peer1' }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -634,7 +718,7 @@ describe('Session', () => {
|
|||
});
|
||||
|
||||
it('should get working representation with Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
const peer = new Peer('peer1', 'test-workspace', mockClient);
|
||||
const mockRepresentation = {
|
||||
peer_id: 'peer1',
|
||||
knowledge: 'Some knowledge',
|
||||
|
|
@ -669,8 +753,8 @@ describe('Session', () => {
|
|||
});
|
||||
|
||||
it('should get working representation with target Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
const target = new Peer('target-peer', honcho);
|
||||
const peer = new Peer('peer1', 'test-workspace', mockClient);
|
||||
const target = new Peer('target-peer', 'test-workspace', mockClient);
|
||||
const mockRepresentation = {
|
||||
peer_id: 'peer1',
|
||||
target_knowledge: 'What peer1 knows about target',
|
||||
|
|
@ -690,7 +774,7 @@ describe('Session', () => {
|
|||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.workingRepresentation.mockRejectedValue(new Error('Failed to get working representation'));
|
||||
|
||||
await expect(session.workingRep('peer1')).rejects.toThrow('Failed to get working representation');
|
||||
await expect(session.workingRep('peer1')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,26 +1,42 @@
|
|||
import { SessionContext } from '../src/session_context';
|
||||
import { Peer } from '../src/peer';
|
||||
|
||||
/**
|
||||
* Helper function to create a proper Message object for testing
|
||||
*/
|
||||
function createTestMessage(id: string, content: string, peer_id: string, additionalProps: any = {}): any {
|
||||
return {
|
||||
id,
|
||||
content,
|
||||
peer_id,
|
||||
created_at: new Date().toISOString(),
|
||||
session_id: 'test-session',
|
||||
token_count: 0,
|
||||
workspace_id: 'test-workspace',
|
||||
...additionalProps
|
||||
};
|
||||
}
|
||||
|
||||
describe('SessionContext', () => {
|
||||
let sessionContext: SessionContext;
|
||||
let mockMessages: any[];
|
||||
|
||||
beforeEach(() => {
|
||||
mockMessages = [
|
||||
{ id: 'msg1', content: 'Hello', peer_name: 'assistant' },
|
||||
{ id: 'msg2', content: 'Hi there', peer_name: 'user' },
|
||||
{ id: 'msg3', content: 'How are you?', peer_name: 'user' },
|
||||
{ id: 'msg4', content: 'I am doing well, thank you!', peer_name: 'assistant' },
|
||||
createTestMessage('msg1', 'Hello', 'assistant'),
|
||||
createTestMessage('msg2', 'Hi there', 'user'),
|
||||
createTestMessage('msg3', 'How are you?', 'user'),
|
||||
createTestMessage('msg4', 'I am doing well, thank you!', 'assistant'),
|
||||
];
|
||||
|
||||
sessionContext = new SessionContext('test-session', mockMessages, 'This is a summary');
|
||||
sessionContext = new SessionContext('test-session', mockMessages, '');
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with all properties', () => {
|
||||
expect(sessionContext.sessionId).toBe('test-session');
|
||||
expect(sessionContext.messages).toEqual(mockMessages);
|
||||
expect(sessionContext.summary).toBe('This is a summary');
|
||||
expect(sessionContext.summary).toBe('');
|
||||
});
|
||||
|
||||
it('should initialize with empty summary when not provided', () => {
|
||||
|
|
@ -53,24 +69,24 @@ describe('SessionContext', () => {
|
|||
const openAIMessages = sessionContext.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'Hi there' },
|
||||
{ role: 'user', content: 'How are you?' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!' },
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'Hi there', name: 'user' },
|
||||
{ role: 'user', content: 'How are you?', name: 'user' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert messages to OpenAI format with Peer object', () => {
|
||||
const mockHoncho = {} as any;
|
||||
const assistantPeer = new Peer('assistant', mockHoncho);
|
||||
const mockClient = {} as any;
|
||||
const assistantPeer = new Peer('assistant', 'test-workspace', mockClient);
|
||||
|
||||
const openAIMessages = sessionContext.toOpenAI(assistantPeer);
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'Hi there' },
|
||||
{ role: 'user', content: 'How are you?' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!' },
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'Hi there', name: 'user' },
|
||||
{ role: 'user', content: 'How are you?', name: 'user' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -78,10 +94,10 @@ describe('SessionContext', () => {
|
|||
const openAIMessages = sessionContext.toOpenAI('different-assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'user', content: 'Hi there' },
|
||||
{ role: 'user', content: 'How are you?' },
|
||||
{ role: 'user', content: 'I am doing well, thank you!' },
|
||||
{ role: 'user', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'Hi there', name: 'user' },
|
||||
{ role: 'user', content: 'How are you?', name: 'user' },
|
||||
{ role: 'user', content: 'I am doing well, thank you!', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -92,37 +108,50 @@ describe('SessionContext', () => {
|
|||
expect(openAIMessages).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle messages with missing peer_name', () => {
|
||||
it('should include summary message when summary exists', () => {
|
||||
const contextWithSummary = new SessionContext('test-session', mockMessages, 'This is a summary');
|
||||
const openAIMessages = contextWithSummary.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'system', content: '<summary>This is a summary</summary>' },
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'Hi there', name: 'user' },
|
||||
{ role: 'user', content: 'How are you?', name: 'user' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle messages with missing peer_id', () => {
|
||||
const messagesWithMissingPeer = [
|
||||
{ id: 'msg1', content: 'Hello', peer_name: 'assistant' },
|
||||
{ id: 'msg2', content: 'No peer' }, // missing peer_name
|
||||
{ id: 'msg3', content: 'Another message', peer_name: null },
|
||||
createTestMessage('msg1', 'Hello', 'assistant'),
|
||||
createTestMessage('msg2', 'No peer', ''), // missing peer_id
|
||||
createTestMessage('msg3', 'Another message', ''), // null peer_id
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithMissingPeer);
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'No peer' },
|
||||
{ role: 'user', content: 'Another message' },
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'No peer', name: '' },
|
||||
{ role: 'user', content: 'Another message', name: '' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle complex message content', () => {
|
||||
const complexMessages = [
|
||||
{ id: 'msg1', content: 'Message with\nnewlines and special chars!@#$%', peer_name: 'assistant' },
|
||||
{ id: 'msg2', content: '', peer_name: 'user' }, // empty content
|
||||
{ id: 'msg3', content: ' whitespace ', peer_name: 'assistant' },
|
||||
createTestMessage('msg1', 'Message with\nnewlines and special chars!@#$%', 'assistant'),
|
||||
createTestMessage('msg2', '', 'user'), // empty content
|
||||
createTestMessage('msg3', ' whitespace ', 'assistant'),
|
||||
];
|
||||
const context = new SessionContext('test', complexMessages);
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Message with\nnewlines and special chars!@#$%' },
|
||||
{ role: 'user', content: '' },
|
||||
{ role: 'assistant', content: ' whitespace ' },
|
||||
{ role: 'assistant', content: 'Message with\nnewlines and special chars!@#$%', name: 'assistant' },
|
||||
{ role: 'user', content: '', name: 'user' },
|
||||
{ role: 'assistant', content: ' whitespace ', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -133,22 +162,22 @@ describe('SessionContext', () => {
|
|||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'Hi there' },
|
||||
{ role: 'user', content: 'How are you?' },
|
||||
{ role: 'user', content: 'user: Hi there' },
|
||||
{ role: 'user', content: 'user: How are you?' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert messages to Anthropic format with Peer object', () => {
|
||||
const mockHoncho = {} as any;
|
||||
const assistantPeer = new Peer('assistant', mockHoncho);
|
||||
const mockClient = {} as any;
|
||||
const assistantPeer = new Peer('assistant', 'test-workspace', mockClient);
|
||||
|
||||
const anthropicMessages = sessionContext.toAnthropic(assistantPeer);
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'Hi there' },
|
||||
{ role: 'user', content: 'How are you?' },
|
||||
{ role: 'user', content: 'user: Hi there' },
|
||||
{ role: 'user', content: 'user: How are you?' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!' },
|
||||
]);
|
||||
});
|
||||
|
|
@ -157,10 +186,10 @@ describe('SessionContext', () => {
|
|||
const anthropicMessages = sessionContext.toAnthropic('different-assistant');
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'user', content: 'Hi there' },
|
||||
{ role: 'user', content: 'How are you?' },
|
||||
{ role: 'user', content: 'I am doing well, thank you!' },
|
||||
{ role: 'user', content: 'assistant: Hello' },
|
||||
{ role: 'user', content: 'user: Hi there' },
|
||||
{ role: 'user', content: 'user: How are you?' },
|
||||
{ role: 'user', content: 'assistant: I am doing well, thank you!' },
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -171,11 +200,24 @@ describe('SessionContext', () => {
|
|||
expect(anthropicMessages).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle messages with missing peer_name', () => {
|
||||
it('should include summary message when summary exists', () => {
|
||||
const contextWithSummary = new SessionContext('test-session', mockMessages, 'This is a summary');
|
||||
const anthropicMessages = contextWithSummary.toAnthropic('assistant');
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'user', content: '<summary>This is a summary</summary>' },
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'user: Hi there' },
|
||||
{ role: 'user', content: 'user: How are you?' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle messages with missing peer_id', () => {
|
||||
const messagesWithMissingPeer = [
|
||||
{ id: 'msg1', content: 'Hello', peer_name: 'assistant' },
|
||||
{ id: 'msg2', content: 'No peer' }, // missing peer_name
|
||||
{ id: 'msg3', content: 'Another message', peer_name: undefined },
|
||||
createTestMessage('msg1', 'Hello', 'assistant'),
|
||||
createTestMessage('msg2', 'No peer', ''), // missing peer_id
|
||||
createTestMessage('msg3', 'Another message', ''), // undefined peer_id
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithMissingPeer);
|
||||
|
||||
|
|
@ -183,8 +225,8 @@ describe('SessionContext', () => {
|
|||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'No peer' },
|
||||
{ role: 'user', content: 'Another message' },
|
||||
{ role: 'user', content: ': No peer' },
|
||||
{ role: 'user', content: ': Another message' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -208,58 +250,56 @@ describe('SessionContext', () => {
|
|||
describe('toString', () => {
|
||||
it('should return correct string representation', () => {
|
||||
const result = sessionContext.toString();
|
||||
expect(result).toBe('SessionContext(messages=4)');
|
||||
expect(result).toBe('SessionContext(messages=4, summary=)');
|
||||
});
|
||||
|
||||
it('should handle empty messages', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
const result = emptyContext.toString();
|
||||
expect(result).toBe('SessionContext(messages=0)');
|
||||
expect(result).toBe('SessionContext(messages=0, summary=)');
|
||||
});
|
||||
|
||||
it('should handle large number of messages', () => {
|
||||
const manyMessages = Array.from({ length: 1000 }, (_, i) => ({
|
||||
id: `msg${i}`,
|
||||
content: `Message ${i}`,
|
||||
peer_name: i % 2 === 0 ? 'assistant' : 'user',
|
||||
}));
|
||||
const manyMessages = Array.from({ length: 1000 }, (_, i) =>
|
||||
createTestMessage(`msg${i}`, `Message ${i}`, i % 2 === 0 ? 'assistant' : 'user')
|
||||
);
|
||||
const context = new SessionContext('session-id', manyMessages);
|
||||
|
||||
const result = context.toString();
|
||||
expect(result).toBe('SessionContext(messages=1000)');
|
||||
expect(result).toBe('SessionContext(messages=1000, summary=)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases and error handling', () => {
|
||||
it('should handle messages with null content', () => {
|
||||
const messagesWithNullContent = [
|
||||
{ id: 'msg1', content: null, peer_name: 'assistant' },
|
||||
{ id: 'msg2', content: undefined, peer_name: 'user' },
|
||||
createTestMessage('msg1', null as any, 'assistant'),
|
||||
createTestMessage('msg2', undefined as any, 'user'),
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithNullContent);
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: null },
|
||||
{ role: 'user', content: undefined },
|
||||
{ role: 'assistant', content: null, name: 'assistant' },
|
||||
{ role: 'user', content: undefined, name: 'user' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle messages with non-string content', () => {
|
||||
const messagesWithNonStringContent = [
|
||||
{ id: 'msg1', content: 123, peer_name: 'assistant' },
|
||||
{ id: 'msg2', content: { text: 'object content' }, peer_name: 'user' },
|
||||
{ id: 'msg3', content: true, peer_name: 'assistant' },
|
||||
createTestMessage('msg1', 123 as any, 'assistant'),
|
||||
createTestMessage('msg2', { text: 'object content' } as any, 'user'),
|
||||
createTestMessage('msg3', true as any, 'assistant'),
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithNonStringContent);
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 123 },
|
||||
{ role: 'user', content: { text: 'object content' } },
|
||||
{ role: 'assistant', content: true },
|
||||
{ role: 'assistant', content: 123, name: 'assistant' },
|
||||
{ role: 'user', content: { text: 'object content' }, name: 'user' },
|
||||
{ role: 'assistant', content: true, name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -270,57 +310,54 @@ describe('SessionContext', () => {
|
|||
|
||||
expect(context.sessionId).toBe(longSessionId);
|
||||
expect(context.summary).toBe(longSummary);
|
||||
expect(context.length).toBe(4);
|
||||
expect(context.length).toBe(5); // 4 messages + 1 summary
|
||||
});
|
||||
|
||||
it('should handle messages with additional properties', () => {
|
||||
const messagesWithExtraProps = [
|
||||
{
|
||||
id: 'msg1',
|
||||
content: 'Hello',
|
||||
peer_name: 'assistant',
|
||||
createTestMessage('msg1', 'Hello', 'assistant', {
|
||||
timestamp: '2023-01-01T00:00:00Z',
|
||||
metadata: { important: true },
|
||||
extra_field: 'extra_value'
|
||||
},
|
||||
}),
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithExtraProps);
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle case-sensitive peer names', () => {
|
||||
const caseMessages = [
|
||||
{ id: 'msg1', content: 'Hello', peer_name: 'Assistant' },
|
||||
{ id: 'msg2', content: 'Hi', peer_name: 'ASSISTANT' },
|
||||
{ id: 'msg3', content: 'Hey', peer_name: 'assistant' },
|
||||
createTestMessage('msg1', 'Hello', 'Assistant'),
|
||||
createTestMessage('msg2', 'Hi', 'ASSISTANT'),
|
||||
createTestMessage('msg3', 'Hey', 'assistant'),
|
||||
];
|
||||
const context = new SessionContext('test', caseMessages);
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'user', content: 'Hello' }, // 'Assistant' != 'assistant'
|
||||
{ role: 'user', content: 'Hi' }, // 'ASSISTANT' != 'assistant'
|
||||
{ role: 'assistant', content: 'Hey' }, // exact match
|
||||
{ role: 'user', content: 'Hello', name: 'Assistant' }, // 'Assistant' != 'assistant'
|
||||
{ role: 'user', content: 'Hi', name: 'ASSISTANT' }, // 'ASSISTANT' != 'assistant'
|
||||
{ role: 'assistant', content: 'Hey', name: 'assistant' }, // exact match
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle messages without id field', () => {
|
||||
const messagesWithoutId = [
|
||||
{ content: 'Message without ID', peer_name: 'assistant' },
|
||||
{ peer_name: 'user', content: 'Another message' },
|
||||
createTestMessage('', 'Message without ID', 'assistant'),
|
||||
createTestMessage('', 'Another message', 'user'),
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithoutId);
|
||||
|
||||
expect(context.length).toBe(2);
|
||||
expect(context.toOpenAI('assistant')).toEqual([
|
||||
{ role: 'assistant', content: 'Message without ID' },
|
||||
{ role: 'user', content: 'Another message' },
|
||||
{ role: 'assistant', content: 'Message without ID', name: 'assistant' },
|
||||
{ role: 'user', content: 'Another message', name: 'user' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@
|
|||
"": {
|
||||
"name": "@honcho-ai/sdk",
|
||||
"dependencies": {
|
||||
"@honcho-ai/core": "1.2.0",
|
||||
"@honcho-ai/core": "1.3.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"zod": "4.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.1.2",
|
||||
|
|
@ -89,25 +90,25 @@
|
|||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
|
||||
|
||||
"@biomejs/biome": ["@biomejs/biome@2.1.2", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.1.2", "@biomejs/cli-darwin-x64": "2.1.2", "@biomejs/cli-linux-arm64": "2.1.2", "@biomejs/cli-linux-arm64-musl": "2.1.2", "@biomejs/cli-linux-x64": "2.1.2", "@biomejs/cli-linux-x64-musl": "2.1.2", "@biomejs/cli-win32-arm64": "2.1.2", "@biomejs/cli-win32-x64": "2.1.2" }, "bin": { "biome": "bin/biome" } }, "sha512-yq8ZZuKuBVDgAS76LWCfFKHSYIAgqkxVB3mGVVpOe2vSkUTs7xG46zXZeNPRNVjiJuw0SZ3+J2rXiYx0RUpfGg=="],
|
||||
"@biomejs/biome": ["@biomejs/biome@2.1.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.1.3", "@biomejs/cli-darwin-x64": "2.1.3", "@biomejs/cli-linux-arm64": "2.1.3", "@biomejs/cli-linux-arm64-musl": "2.1.3", "@biomejs/cli-linux-x64": "2.1.3", "@biomejs/cli-linux-x64-musl": "2.1.3", "@biomejs/cli-win32-arm64": "2.1.3", "@biomejs/cli-win32-x64": "2.1.3" }, "bin": { "biome": "bin/biome" } }, "sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w=="],
|
||||
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.1.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-leFAks64PEIjc7MY/cLjE8u5OcfBKkcDB0szxsWUB4aDfemBep1WVKt0qrEyqZBOW8LPHzrFMyDl3FhuuA0E7g=="],
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LFLkSWRoSGS1wVUD/BE6Nlt2dSn0ulH3XImzg2O/36BoToJHKXjSxzPEMAqT9QvwVtk7/9AQhZpTneERU9qaXA=="],
|
||||
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.1.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-Nmmv7wRX5Nj7lGmz0FjnWdflJg4zii8Ivruas6PBKzw5SJX/q+Zh2RfnO+bBnuKLXpj8kiI2x2X12otpH6a32A=="],
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Q/4OTw8P9No9QeowyxswcWdm0n2MsdCwWcc5NcKQQvzwPjwuPdf8dpPPf4r+x0RWKBtl1FLiAUtJvBlri6DnYw=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.1.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-NWNy2Diocav61HZiv2enTQykbPP/KrA/baS7JsLSojC7Xxh2nl9IczuvE5UID7+ksRy2e7yH7klm/WkA72G1dw=="],
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-2hS6LgylRqMFmAZCOFwYrf77QMdUwJp49oe8PX/O8+P2yKZMSpyQTf3Eo5ewnsMFUEmYbPOskafdV1ds1MZMJA=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.1.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-qgHvafhjH7Oca114FdOScmIKf1DlXT1LqbOrrbR30kQDLFPEOpBG0uzx6MhmsrmhGiCFCr2obDamu+czk+X0HQ=="],
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-KXouFSBnoxAWZYDQrnNRzZBbt5s9UJkIm40hdvSL9mBxSSoxRFQJbtg1hP3aa8A2SnXyQHxQfpiVeJlczZt76w=="],
|
||||
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.1.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Km/UYeVowygTjpX6sGBzlizjakLoMQkxWbruVZSNE6osuSI63i4uCeIL+6q2AJlD3dxoiBJX70dn1enjQnQqwA=="],
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-NxlSCBhLvQtWGagEztfAZ4WcE1AkMTntZV65ZvR+J9jp06+EtOYEBPQndA70ZGhHbEDG57bR6uNvqkd1WrEYVA=="],
|
||||
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.1.2", "", { "os": "linux", "cpu": "x64" }, "sha512-xlB3mU14ZUa3wzLtXfmk2IMOGL+S0aHFhSix/nssWS/2XlD27q+S6f0dlQ8WOCbYoXcuz8BCM7rCn2lxdTrlQA=="],
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-KaLAxnROouzIWtl6a0Y88r/4hW5oDUJTIqQorOTVQITaKQsKjZX4XCUmHIhdEk8zMnaiLZzRTAwk1yIAl+mIew=="],
|
||||
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.1.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-G8KWZli5ASOXA3yUQgx+M4pZRv3ND16h77UsdunUL17uYpcL/UC7RkWTdkfvMQvogVsAuz5JUcBDjgZHXxlKoA=="],
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-V9CUZCtWH4u0YwyCYbQ3W5F4ZGPWp2C2TYcsiWFNNyRfmOW1j/TY/jAurl33SaRjgZPO5UUhGyr9m6BN9t84NQ=="],
|
||||
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.1.2", "", { "os": "win32", "cpu": "x64" }, "sha512-9zajnk59PMpjBkty3bK2IrjUsUHvqe9HWwyAWQBjGLE7MIBjbX2vwv1XPEhmO2RRuGoTkVx3WCanHrjAytICLA=="],
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-dxy599q6lgp8ANPpR8sDMscwdp9oOumEsVXuVCVT9N2vAho8uYXlCz53JhxX6LtJOXaE73qzgkGQ7QqvFlMC0g=="],
|
||||
|
||||
"@honcho-ai/core": ["@honcho-ai/core@1.2.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-VPHCFIGfC00GeE4P83DDIT7hkuMnMVkWlMTmMd2tw4HSEUciqLBh09AX/6aMKfJAzprg1diub6pJJ6LJP6eJ+g=="],
|
||||
"@honcho-ai/core": ["@honcho-ai/core@1.3.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-yhxpZ00sQhO24PrHoTlj1cwR1q7/8FKSFZoCImqsFFmdERiXtE74YXhhfqzseyuShu1WfM7IwD8T2oCOB3hu7A=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="],
|
||||
|
||||
|
|
@ -161,7 +162,7 @@
|
|||
|
||||
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||
|
||||
"@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
|
||||
|
||||
|
|
@ -173,9 +174,9 @@
|
|||
|
||||
"@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="],
|
||||
|
||||
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
|
||||
"@types/node": ["@types/node@24.2.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw=="],
|
||||
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="],
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
||||
|
||||
"@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
|
||||
|
||||
|
|
@ -197,8 +198,6 @@
|
|||
|
||||
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="],
|
||||
|
|
@ -207,7 +206,7 @@
|
|||
|
||||
"babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
|
||||
|
||||
"babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.1.1", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-23fWKohMTvS5s0wwJKycOe0dBdCwQ6+iiLaNR9zy8P13mtFRFM9qLLX6HJX5DL2pi/FNDf3fCQHM4FIMoHH/7w=="],
|
||||
"babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="],
|
||||
|
||||
"babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
|
||||
|
||||
|
|
@ -231,7 +230,7 @@
|
|||
|
||||
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001731", "", {}, "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
|
|
@ -275,9 +274,7 @@
|
|||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.192", "", {}, "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.197", "", {}, "sha512-m1xWB3g7vJ6asIFz+2pBUbq3uGmfmln1M9SSvBe4QIFWYrRHylP73zL/3nMjDmwz8V+1xAXQDfBd6+HPW0WvDQ=="],
|
||||
|
||||
"emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="],
|
||||
|
||||
|
|
@ -311,8 +308,6 @@
|
|||
|
||||
"fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
|
||||
|
||||
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
|
@ -347,6 +342,8 @@
|
|||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
|
@ -393,8 +390,6 @@
|
|||
|
||||
"istanbul-reports": ["istanbul-reports@3.1.7", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g=="],
|
||||
|
||||
"jake": ["jake@10.9.2", "", { "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", "filelist": "^1.0.4", "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" } }, "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA=="],
|
||||
|
||||
"jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="],
|
||||
|
||||
"jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="],
|
||||
|
|
@ -489,10 +484,14 @@
|
|||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
|
@ -595,15 +594,17 @@
|
|||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"ts-jest": ["ts-jest@29.4.0", "", { "dependencies": { "bs-logger": "^0.2.6", "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.2", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/transform": "^29.0.0 || ^30.0.0", "@jest/types": "^29.0.0 || ^30.0.0", "babel-jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "optionalPeers": ["@babel/core", "@jest/transform", "@jest/types", "babel-jest", "jest-util"], "bin": { "ts-jest": "cli.js" } }, "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q=="],
|
||||
"ts-jest": ["ts-jest@29.4.1", "", { "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", "handlebars": "^4.7.8", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.2", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/transform": "^29.0.0 || ^30.0.0", "@jest/types": "^29.0.0 || ^30.0.0", "babel-jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "optionalPeers": ["@babel/core", "@jest/transform", "@jest/types", "babel-jest", "jest-util"], "bin": { "ts-jest": "cli.js" } }, "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw=="],
|
||||
|
||||
"type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
|
||||
|
||||
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
|
||||
|
||||
|
|
@ -619,6 +620,8 @@
|
|||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
|
@ -635,11 +638,13 @@
|
|||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@4.0.0", "", {}, "sha512-9diLdTPc/L7w/5jI4C3gHYNiGHDV9IZYxo1e5LSD8cabi65WVTWWb+g2BGPEpUUCOxR4D+6O5B0AzyMdUAXwrw=="],
|
||||
|
||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@honcho-ai/core/@types/node": ["@types/node@18.19.120", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WtCGHFXnVI8WHLxDAt5TbnCM4eSE+nI0QN2NJtwzcgMhht2eNz6V9evJrk+lwC8bCY8OWV5Ym8Jz7ZEyGnKnMA=="],
|
||||
"@honcho-ai/core/@types/node": ["@types/node@18.19.121", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||
|
||||
|
|
@ -649,8 +654,6 @@
|
|||
|
||||
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
||||
|
||||
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
|
@ -660,7 +663,5 @@
|
|||
"@honcho-ai/core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"filelist/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Honcho } from '../src';
|
||||
import { Honcho, Message } from '../src';
|
||||
|
||||
/**
|
||||
* Example demonstrating how to get context from a session with summary and token limits.
|
||||
|
|
@ -15,19 +15,19 @@ async function main() {
|
|||
|
||||
console.log('Creating peers...');
|
||||
const peers = [
|
||||
honcho.peer('alice'),
|
||||
honcho.peer('bob'),
|
||||
honcho.peer('charlie'),
|
||||
await honcho.peer('alice'),
|
||||
await honcho.peer('bob'),
|
||||
await honcho.peer('charlie'),
|
||||
];
|
||||
|
||||
// Create a new session
|
||||
const sessionId = `context_test_${crypto.randomUUID()}`;
|
||||
const session = honcho.session(sessionId);
|
||||
const session = await honcho.session(sessionId);
|
||||
console.log(`Created session: ${sessionId}`);
|
||||
|
||||
console.log('Generating random messages...');
|
||||
// Generate some random messages from alice, bob, and charlie and add them to the session
|
||||
const messages = [];
|
||||
const messages: Message[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const randomPeer = peers[Math.floor(Math.random() * peers.length)];
|
||||
messages.push(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Honcho } from '../src';
|
||||
import { Honcho, Message } from '../src';
|
||||
|
||||
/**
|
||||
* Example demonstrating how to get peer representations.
|
||||
|
|
@ -15,19 +15,19 @@ async function main() {
|
|||
|
||||
console.log('Creating peers...');
|
||||
const peers = [
|
||||
honcho.peer('alice'),
|
||||
honcho.peer('bob'),
|
||||
honcho.peer('charlie'),
|
||||
await honcho.peer('alice'),
|
||||
await honcho.peer('bob'),
|
||||
await honcho.peer('charlie'),
|
||||
];
|
||||
|
||||
// Create a new session
|
||||
const sessionId = `context_test_${crypto.randomUUID()}`;
|
||||
const session = honcho.session(sessionId);
|
||||
const session = await honcho.session(sessionId);
|
||||
console.log(`Created session: ${sessionId}`);
|
||||
|
||||
console.log('Generating random messages...');
|
||||
// Generate some random messages from alice, bob, and charlie and add them to the session
|
||||
const messages = [];
|
||||
const messages: Message[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const randomPeer = peers[Math.floor(Math.random() * peers.length)];
|
||||
messages.push(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Honcho } from '../src';
|
||||
import { Honcho, Message } from '../src';
|
||||
|
||||
/**
|
||||
* Example demonstrating search functionality across different scopes.
|
||||
|
|
@ -15,16 +15,16 @@ async function main() {
|
|||
|
||||
console.log('Creating peers...');
|
||||
const peers = [
|
||||
honcho.peer('alice'),
|
||||
honcho.peer('bob'),
|
||||
honcho.peer('charlie'),
|
||||
await honcho.peer('alice'),
|
||||
await honcho.peer('bob'),
|
||||
await honcho.peer('charlie'),
|
||||
];
|
||||
|
||||
const alice = peers[0];
|
||||
|
||||
// Create a new session
|
||||
const sessionId = `search_test_${crypto.randomUUID()}`;
|
||||
const session = honcho.session(sessionId);
|
||||
const session = await honcho.session(sessionId);
|
||||
console.log(`Created session: ${sessionId}`);
|
||||
|
||||
// Create a message with our special keyword
|
||||
|
|
@ -34,7 +34,7 @@ async function main() {
|
|||
|
||||
console.log('Generating random messages...');
|
||||
// Generate some random messages from alice, bob, and charlie and add them to the session
|
||||
const messages = [];
|
||||
const messages: Message[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const randomPeer = peers[Math.floor(Math.random() * peers.length)];
|
||||
messages.push(
|
||||
|
|
@ -48,24 +48,24 @@ async function main() {
|
|||
console.log('Searching the session...');
|
||||
// Search the session for the special keyword
|
||||
const sessionSearchResults = await session.search(keyword);
|
||||
console.log(`Session search returned ${sessionSearchResults.total} results:`);
|
||||
for await (const message of sessionSearchResults) {
|
||||
console.log(`Session search returned ${sessionSearchResults.length} results:`);
|
||||
for (const message of sessionSearchResults) {
|
||||
console.log(` - ${message.content} (from ${message.peer_id})`);
|
||||
}
|
||||
|
||||
console.log('Searching the workspace...');
|
||||
// Search the workspace for the special keyword
|
||||
const workspaceSearchResults = await honcho.search(keyword);
|
||||
console.log(`Workspace search returned ${workspaceSearchResults.total} results:`);
|
||||
for await (const message of workspaceSearchResults) {
|
||||
console.log(`Workspace search returned ${workspaceSearchResults.length} results:`);
|
||||
for (const message of workspaceSearchResults) {
|
||||
console.log(` - ${message.content} (from ${message.peer_id})`);
|
||||
}
|
||||
|
||||
console.log('Searching alice\'s messages...');
|
||||
// Search alice's messages for the special keyword
|
||||
const aliceSearchResults = await alice.search(keyword);
|
||||
console.log(`Alice search returned ${aliceSearchResults.total} results:`);
|
||||
for await (const message of aliceSearchResults) {
|
||||
console.log(`Alice search returned ${aliceSearchResults.length} results:`);
|
||||
for (const message of aliceSearchResults) {
|
||||
console.log(` - ${message.content} (from ${message.peer_id})`);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@honcho-ai/sdk",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"description": "Official DX Optimized TypeScript SDK for Honcho",
|
||||
"author": "Plastic Labs <hello@plasticlabs.ai>",
|
||||
"license": "Apache-2.0",
|
||||
|
|
@ -20,8 +20,9 @@
|
|||
"test:coverage": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@honcho-ai/core": "1.3.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"@honcho-ai/core": "1.2.0"
|
||||
"zod": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.1.2",
|
||||
|
|
|
|||
|
|
@ -1,94 +1,272 @@
|
|||
import HonchoCore from '@honcho-ai/core'
|
||||
import type { DefaultQuery } from '@honcho-ai/core/src/core'
|
||||
import type { Message } from '@honcho-ai/core/src/resources/workspaces/sessions/messages'
|
||||
import type {
|
||||
DeriverStatus,
|
||||
WorkspaceDeriverStatusParams,
|
||||
} from '@honcho-ai/core/src/resources/workspaces/workspaces'
|
||||
import { Page } from './pagination'
|
||||
import { Peer } from './peer'
|
||||
import { Session } from './session'
|
||||
import {
|
||||
type DeriverStatusOptions,
|
||||
DeriverStatusOptionsSchema,
|
||||
FilterSchema,
|
||||
type Filters,
|
||||
type HonchoConfig,
|
||||
HonchoConfigSchema,
|
||||
LimitSchema,
|
||||
type PeerConfig,
|
||||
PeerConfigSchema,
|
||||
PeerIdSchema,
|
||||
type PeerMetadata,
|
||||
PeerMetadataSchema,
|
||||
SearchQuerySchema,
|
||||
type SessionConfig,
|
||||
SessionConfigSchema,
|
||||
SessionIdSchema,
|
||||
type SessionMetadata,
|
||||
SessionMetadataSchema,
|
||||
type WorkspaceMetadata,
|
||||
WorkspaceMetadataSchema,
|
||||
} from './validation'
|
||||
|
||||
/**
|
||||
* Main client for the Honcho TypeScript SDK.
|
||||
* Provides access to peers, sessions, and workspace operations.
|
||||
*
|
||||
* Provides access to peers, sessions, and workspace operations with configuration
|
||||
* from environment variables or explicit parameters. This is the primary entry
|
||||
* point for interacting with the Honcho conversational memory platform.
|
||||
*
|
||||
* For advanced usage, the underlying @honcho-ai/core client can be accessed via the
|
||||
* `core` property to use functionality not exposed through this SDK.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const honcho = new Honcho({
|
||||
* apiKey: 'your-api-key',
|
||||
* workspaceId: 'your-workspace-id'
|
||||
* })
|
||||
*
|
||||
* const peer = await honcho.peer('user123')
|
||||
* const session = await honcho.session('session456')
|
||||
* ```
|
||||
*/
|
||||
export class Honcho {
|
||||
private _client: InstanceType<typeof HonchoCore>
|
||||
/**
|
||||
* Workspace ID for scoping operations.
|
||||
*/
|
||||
readonly workspaceId: string
|
||||
/**
|
||||
* Reference to the core Honcho client instance.
|
||||
*/
|
||||
private _client: HonchoCore
|
||||
|
||||
/**
|
||||
* Access the underlying @honcho-ai/core client. The @honcho-ai/core client is the raw Stainless-generated client,
|
||||
* allowing users to access functionality that is not exposed through this SDK.
|
||||
*
|
||||
* @returns The underlying HonchoCore client instance
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Honcho } from '@honcho-ai/sdk';
|
||||
*
|
||||
* const client = new Honcho();
|
||||
*
|
||||
* const workspace = await client.core.workspaces.getOrCreate({ id: "custom-workspace-id" });
|
||||
* ```
|
||||
*/
|
||||
get core(): InstanceType<typeof HonchoCore> {
|
||||
return this._client
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Honcho client.
|
||||
*
|
||||
* @param options - Configuration options for the client
|
||||
* @param options.apiKey - API key for authentication. If not provided, will attempt to
|
||||
* read from HONCHO_API_KEY environment variable
|
||||
* @param options.environment - Environment to use (local, production, or demo)
|
||||
* @param options.baseURL - Base URL for the Honcho API. If not provided, will attempt to
|
||||
* read from HONCHO_URL environment variable or default to the
|
||||
* production API URL
|
||||
* @param options.workspaceId - Workspace ID to use for operations. If not provided, will
|
||||
* attempt to read from HONCHO_WORKSPACE_ID environment variable
|
||||
* or default to "default"
|
||||
* @param options.timeout - Optional custom timeout for the HTTP client
|
||||
* @param options.maxRetries - Optional custom maximum number of retries for the HTTP client
|
||||
* @param options.defaultHeaders - Optional custom default headers for the HTTP client
|
||||
* @param options.defaultQuery - Optional custom default query parameters for the HTTP client
|
||||
*/
|
||||
constructor(options: {
|
||||
apiKey?: string
|
||||
environment?: 'local' | 'production' | 'demo'
|
||||
baseURL?: string
|
||||
workspaceId?: string
|
||||
timeout?: number
|
||||
maxRetries?: number
|
||||
defaultHeaders?: Record<string, string>
|
||||
defaultQuery?: Record<string, unknown>
|
||||
}) {
|
||||
constructor(options: HonchoConfig) {
|
||||
const validatedOptions = HonchoConfigSchema.parse(options)
|
||||
this.workspaceId =
|
||||
options.workspaceId || process.env.HONCHO_WORKSPACE_ID || 'default'
|
||||
validatedOptions.workspaceId ||
|
||||
process.env.HONCHO_WORKSPACE_ID ||
|
||||
'default'
|
||||
this._client = new HonchoCore({
|
||||
apiKey: options.apiKey || process.env.HONCHO_API_KEY,
|
||||
environment: options.environment,
|
||||
baseURL: options.baseURL || process.env.HONCHO_URL,
|
||||
timeout: options.timeout,
|
||||
maxRetries: options.maxRetries,
|
||||
defaultHeaders: options.defaultHeaders,
|
||||
defaultQuery: options.defaultQuery as any,
|
||||
}) as any
|
||||
apiKey: validatedOptions.apiKey || process.env.HONCHO_API_KEY,
|
||||
environment: validatedOptions.environment,
|
||||
baseURL: validatedOptions.baseURL || process.env.HONCHO_URL,
|
||||
timeout: validatedOptions.timeout,
|
||||
maxRetries: validatedOptions.maxRetries,
|
||||
defaultHeaders: validatedOptions.defaultHeaders,
|
||||
defaultQuery: validatedOptions.defaultQuery as DefaultQuery,
|
||||
})
|
||||
// Note: Constructor cannot be async, so we can't await here
|
||||
// The workspace will be created on first use if it doesn't exist
|
||||
// due to the upsert behavior of the API
|
||||
this._client.workspaces.getOrCreate({ id: this.workspaceId })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a peer with the given ID.
|
||||
*
|
||||
* Creates a Peer object that can be used to interact with the specified peer.
|
||||
* If metadata or config is provided, makes an API call to get/create the peer
|
||||
* immediately with those values.
|
||||
*
|
||||
* Provided metadata and configuration will overwrite existing data for this peer
|
||||
* if it already exists.
|
||||
*
|
||||
* @param id - Unique identifier for the peer within the workspace. Should be a
|
||||
* stable identifier that can be used consistently across sessions.
|
||||
* @param metadata - Optional metadata dictionary to associate with this peer.
|
||||
* If set, will get/create peer immediately with metadata.
|
||||
* @param config - Optional configuration to set for this peer.
|
||||
* If set, will get/create peer immediately with flags.
|
||||
* @returns Promise resolving to a Peer object that can be used to send messages,
|
||||
* join sessions, and query the peer's knowledge representations
|
||||
* @throws Error if the peer ID is empty or invalid
|
||||
*/
|
||||
peer(id: string, options?: { config?: Record<string, unknown> }): Peer {
|
||||
if (!id || typeof id !== 'string') {
|
||||
throw new Error('Peer ID must be a non-empty string')
|
||||
async peer(
|
||||
id: string,
|
||||
options?: {
|
||||
metadata?: PeerMetadata
|
||||
config?: PeerConfig
|
||||
}
|
||||
return new Peer(id, this, options?.config)
|
||||
): Promise<Peer> {
|
||||
const validatedId = PeerIdSchema.parse(id)
|
||||
const validatedMetadata = options?.metadata
|
||||
? PeerMetadataSchema.parse(options.metadata)
|
||||
: undefined
|
||||
const validatedConfig = options?.config
|
||||
? PeerConfigSchema.parse(options.config)
|
||||
: undefined
|
||||
const peer = new Peer(validatedId, this.workspaceId, this._client)
|
||||
|
||||
if (validatedConfig || validatedMetadata) {
|
||||
await this._client.workspaces.peers.getOrCreate(this.workspaceId, {
|
||||
id: peer.id,
|
||||
configuration: validatedConfig,
|
||||
metadata: validatedMetadata,
|
||||
})
|
||||
}
|
||||
|
||||
return peer
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all peers in the current workspace.
|
||||
*
|
||||
* Makes an API call to retrieve all peers that have been created or used
|
||||
* within the current workspace. Returns a paginated result.
|
||||
*
|
||||
* @param filters - Optional filter criteria for peers. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
* @returns Promise resolving to a Page of Peer objects representing all peers in the workspace
|
||||
*/
|
||||
async getPeers(
|
||||
filter?: { [key: string]: unknown } | null
|
||||
): Promise<Page<Peer>> {
|
||||
async getPeers(filters?: Filters): Promise<Page<Peer>> {
|
||||
const validatedFilter = filters ? FilterSchema.parse(filters) : undefined
|
||||
const peersPage = await this._client.workspaces.peers.list(
|
||||
this.workspaceId,
|
||||
{ filter }
|
||||
{ filters: validatedFilter }
|
||||
)
|
||||
return new Page(
|
||||
peersPage,
|
||||
(peer) => new Peer(peer.id, this.workspaceId, this._client)
|
||||
)
|
||||
return new Page(peersPage, (peer: any) => new Peer(peer.id, this))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a session with the given ID.
|
||||
*
|
||||
* Creates a Session object that can be used to manage conversations between
|
||||
* multiple peers. If metadata or config is provided, makes an API call to
|
||||
* get/create the session immediately with those values.
|
||||
*
|
||||
* Provided metadata and configuration will overwrite existing data for this session
|
||||
* if it already exists.
|
||||
*
|
||||
* @param id - Unique identifier for the session within the workspace. Should be a
|
||||
* stable identifier that can be used consistently to reference the
|
||||
* same conversation
|
||||
* @param metadata - Optional metadata dictionary to associate with this session.
|
||||
* If set, will get/create session immediately with metadata.
|
||||
* @param config - Optional configuration to set for this session.
|
||||
* If set, will get/create session immediately with flags.
|
||||
* @returns Promise resolving to a Session object that can be used to add peers,
|
||||
* send messages, and manage conversation context
|
||||
* @throws Error if the session ID is empty or invalid
|
||||
*/
|
||||
session(id: string, options?: { config?: Record<string, unknown> }): Session {
|
||||
if (!id || typeof id !== 'string') {
|
||||
throw new Error('Session ID must be a non-empty string')
|
||||
async session(
|
||||
id: string,
|
||||
options?: {
|
||||
metadata?: SessionMetadata
|
||||
config?: SessionConfig
|
||||
}
|
||||
return new Session(id, this, options?.config)
|
||||
): Promise<Session> {
|
||||
const validatedId = SessionIdSchema.parse(id)
|
||||
const validatedMetadata = options?.metadata
|
||||
? SessionMetadataSchema.parse(options.metadata)
|
||||
: undefined
|
||||
const validatedConfig = options?.config
|
||||
? SessionConfigSchema.parse(options.config)
|
||||
: undefined
|
||||
const session = new Session(validatedId, this.workspaceId, this._client)
|
||||
|
||||
if (validatedConfig || validatedMetadata) {
|
||||
await this._client.workspaces.sessions.getOrCreate(this.workspaceId, {
|
||||
id: session.id,
|
||||
configuration: validatedConfig,
|
||||
metadata: validatedMetadata,
|
||||
})
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sessions in the current workspace.
|
||||
*
|
||||
* Makes an API call to retrieve all sessions that have been created within
|
||||
* the current workspace.
|
||||
*
|
||||
* @param filters - Optional filter criteria for sessions. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
* @returns Promise resolving to a Page of Session objects representing all sessions
|
||||
* in the workspace. Returns an empty page if no sessions exist
|
||||
*/
|
||||
async getSessions(
|
||||
filter?: { [key: string]: unknown } | null
|
||||
): Promise<Page<Session>> {
|
||||
async getSessions(filters?: Filters): Promise<Page<Session>> {
|
||||
const validatedFilter = filters ? FilterSchema.parse(filters) : undefined
|
||||
const sessionsPage = await this._client.workspaces.sessions.list(
|
||||
this.workspaceId,
|
||||
{ filter }
|
||||
{ filters: validatedFilter }
|
||||
)
|
||||
return new Page(
|
||||
sessionsPage,
|
||||
(session: any) => new Session(session.id, this)
|
||||
(session) => new Session(session.id, this.workspaceId, this._client)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for the current workspace.
|
||||
*
|
||||
* Makes an API call to retrieve metadata associated with the current workspace.
|
||||
* Workspace metadata can include settings, configuration, or any other
|
||||
* key-value data associated with the workspace.
|
||||
*
|
||||
* @returns Promise resolving to a dictionary containing the workspace's metadata.
|
||||
* Returns an empty dictionary if no metadata is set
|
||||
*/
|
||||
async getMetadata(): Promise<Record<string, unknown>> {
|
||||
const workspace = await this._client.workspaces.getOrCreate({
|
||||
|
|
@ -99,18 +277,35 @@ export class Honcho {
|
|||
|
||||
/**
|
||||
* Set metadata for the current workspace.
|
||||
*
|
||||
* Makes an API call to update the metadata associated with the current workspace.
|
||||
* This will overwrite any existing metadata with the provided values.
|
||||
*
|
||||
* @param metadata - A dictionary of metadata to associate with the workspace.
|
||||
* Keys must be strings, values can be any JSON-serializable type
|
||||
*/
|
||||
async setMetadata(metadata: Record<string, unknown>): Promise<void> {
|
||||
await this._client.workspaces.update(this.workspaceId, { metadata })
|
||||
async setMetadata(metadata: WorkspaceMetadata): Promise<void> {
|
||||
const validatedMetadata = WorkspaceMetadataSchema.parse(metadata)
|
||||
await this._client.workspaces.update(this.workspaceId, {
|
||||
metadata: validatedMetadata,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all workspace IDs from the Honcho instance.
|
||||
*
|
||||
* Makes an API call to retrieve all workspace IDs that the authenticated
|
||||
* user has access to.
|
||||
*
|
||||
* @param filters - Optional filter criteria for workspaces. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
* @returns Promise resolving to a list of workspace ID strings. Returns an empty
|
||||
* list if no workspaces are accessible or none exist
|
||||
*/
|
||||
async getWorkspaces(
|
||||
filter?: { [key: string]: unknown } | null
|
||||
): Promise<string[]> {
|
||||
const workspacesPage = await this._client.workspaces.list({ filter })
|
||||
async getWorkspaces(filters?: Filters): Promise<string[]> {
|
||||
const validatedFilter = filters ? FilterSchema.parse(filters) : undefined
|
||||
const workspacesPage = await this._client.workspaces.list({
|
||||
filters: validatedFilter,
|
||||
})
|
||||
const ids: string[] = []
|
||||
for await (const workspace of workspacesPage) {
|
||||
ids.push(workspace.id)
|
||||
|
|
@ -123,45 +318,63 @@ export class Honcho {
|
|||
*
|
||||
* Makes an API call to search for messages in the current workspace.
|
||||
*
|
||||
* @param query The search query to use
|
||||
* @returns A Page of Message objects representing the search results.
|
||||
* Returns an empty page if no messages are found.
|
||||
* @param query - The search query to use
|
||||
* @param filters - Optional filters to scope the search. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
* @param limit - Number of results to return (1-100, default: 10).
|
||||
* @returns Promise resolving to an array of Message objects representing the search results.
|
||||
* Returns an empty array if no messages are found.
|
||||
* @throws Error if the search query is empty or invalid
|
||||
*/
|
||||
async search(query: string): Promise<Page<any>> {
|
||||
if (!query || typeof query !== 'string' || query.trim().length === 0) {
|
||||
throw new Error('Search query must be a non-empty string')
|
||||
async search(
|
||||
query: string,
|
||||
options?: {
|
||||
filters?: Filters
|
||||
limit?: number
|
||||
}
|
||||
const messagesPage = await this._client.workspaces.search(
|
||||
this.workspaceId,
|
||||
{ body: query }
|
||||
)
|
||||
return new Page(messagesPage)
|
||||
): Promise<Message[]> {
|
||||
const validatedQuery = SearchQuerySchema.parse(query)
|
||||
const validatedFilters = options?.filters
|
||||
? FilterSchema.parse(options.filters)
|
||||
: undefined
|
||||
const validatedLimit = options?.limit
|
||||
? LimitSchema.parse(options.limit)
|
||||
: undefined
|
||||
return await this._client.workspaces.search(this.workspaceId, {
|
||||
query: validatedQuery,
|
||||
filters: validatedFilters,
|
||||
limit: validatedLimit,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deriver processing status, optionally scoped to an observer, sender, and/or session.
|
||||
*
|
||||
* @param options Configuration options for the status request
|
||||
* @param options.observerId Optional observer ID to scope the status to
|
||||
* @param options.senderId Optional sender ID to scope the status to
|
||||
* @param options.sessionId Optional session ID to scope the status to
|
||||
* @returns Promise resolving to the deriver status information
|
||||
* Makes an API call to retrieve the current status of the deriver processing queue.
|
||||
* The deriver is responsible for processing messages and updating peer representations.
|
||||
*
|
||||
* @param options - Configuration options for the status request
|
||||
* @param options.observerId - Optional observer ID to scope the status to
|
||||
* @param options.senderId - Optional sender ID to scope the status to
|
||||
* @param options.sessionId - Optional session ID to scope the status to
|
||||
* @returns Promise resolving to the deriver status information including work unit counts
|
||||
*/
|
||||
async getDeriverStatus(options?: {
|
||||
observerId?: string
|
||||
senderId?: string
|
||||
sessionId?: string
|
||||
}): Promise<{
|
||||
async getDeriverStatus(options?: DeriverStatusOptions): Promise<{
|
||||
totalWorkUnits: number
|
||||
completedWorkUnits: number
|
||||
inProgressWorkUnits: number
|
||||
pendingWorkUnits: number
|
||||
sessions?: Record<string, any>
|
||||
sessions?: Record<string, DeriverStatus.Sessions>
|
||||
}> {
|
||||
const queryParams: any = {}
|
||||
if (options?.observerId) queryParams.observer_id = options.observerId
|
||||
if (options?.senderId) queryParams.sender_id = options.senderId
|
||||
if (options?.sessionId) queryParams.session_id = options.sessionId
|
||||
const validatedOptions = options
|
||||
? DeriverStatusOptionsSchema.parse(options)
|
||||
: undefined
|
||||
const queryParams: WorkspaceDeriverStatusParams = {}
|
||||
if (validatedOptions?.observerId)
|
||||
queryParams.observer_id = validatedOptions.observerId
|
||||
if (validatedOptions?.senderId)
|
||||
queryParams.sender_id = validatedOptions.senderId
|
||||
if (validatedOptions?.sessionId)
|
||||
queryParams.session_id = validatedOptions.sessionId
|
||||
|
||||
const status = await this._client.workspaces.deriverStatus(
|
||||
this.workspaceId,
|
||||
|
|
@ -184,31 +397,29 @@ export class Honcho {
|
|||
*
|
||||
* The polling estimates sleep time by assuming each work unit takes 1 second.
|
||||
*
|
||||
* @param options Configuration options for the status request
|
||||
* @param options.observerId Optional observer ID to scope the status to
|
||||
* @param options.senderId Optional sender ID to scope the status to
|
||||
* @param options.sessionId Optional session ID to scope the status to
|
||||
* @param options.timeoutMs Optional timeout in milliseconds (default: 300000 - 5 minutes)
|
||||
* @param options - Configuration options for the status request
|
||||
* @param options.observerId - Optional observer ID to scope the status to
|
||||
* @param options.senderId - Optional sender ID to scope the status to
|
||||
* @param options.sessionId - Optional session ID to scope the status to
|
||||
* @param options.timeoutMs - Optional timeout in milliseconds (default: 300000 - 5 minutes)
|
||||
* @returns Promise resolving to the final deriver status when processing is complete
|
||||
* @throws Error if timeout is exceeded before processing completes
|
||||
*/
|
||||
async pollDeriverStatus(options?: {
|
||||
observerId?: string
|
||||
senderId?: string
|
||||
sessionId?: string
|
||||
timeoutMs?: number
|
||||
}): Promise<{
|
||||
async pollDeriverStatus(options?: DeriverStatusOptions): Promise<{
|
||||
totalWorkUnits: number
|
||||
completedWorkUnits: number
|
||||
inProgressWorkUnits: number
|
||||
pendingWorkUnits: number
|
||||
sessions?: Record<string, any>
|
||||
sessions?: Record<string, DeriverStatus.Sessions>
|
||||
}> {
|
||||
const timeoutMs = options?.timeoutMs ?? 300000 // Default to 5 minutes
|
||||
const validatedOptions = options
|
||||
? DeriverStatusOptionsSchema.parse(options)
|
||||
: undefined
|
||||
const timeoutMs = validatedOptions?.timeoutMs ?? 300000 // Default to 5 minutes
|
||||
const startTime = Date.now()
|
||||
|
||||
while (true) {
|
||||
const status = await this.getDeriverStatus(options)
|
||||
const status = await this.getDeriverStatus(validatedOptions)
|
||||
if (status.pendingWorkUnits === 0 && status.inProgressWorkUnits === 0) {
|
||||
return status
|
||||
}
|
||||
|
|
@ -237,4 +448,13 @@ export class Honcho {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the Honcho client.
|
||||
*
|
||||
* @returns A string representation suitable for debugging
|
||||
*/
|
||||
toString(): string {
|
||||
return `Honcho(workspaceId='${this.workspaceId}', baseURL='${this._client.baseURL}')`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,29 @@
|
|||
// Main entry point for the Honcho TypeScript SDK
|
||||
// Exports all main classes and types
|
||||
|
||||
export { Message } from '@honcho-ai/core/src/resources/workspaces/sessions/messages'
|
||||
export { Honcho } from './client'
|
||||
export { Page } from './pagination'
|
||||
export { Peer } from './peer'
|
||||
export { Session, SessionPeerConfig } from './session'
|
||||
export { SessionContext } from './session_context'
|
||||
|
||||
// Export validation types for advanced usage
|
||||
export type {
|
||||
ChatQuery,
|
||||
ContextParams,
|
||||
DeriverStatusOptions,
|
||||
FileUpload,
|
||||
Filters,
|
||||
HonchoConfig,
|
||||
MessageAddition,
|
||||
MessageCreate,
|
||||
PeerAddition,
|
||||
PeerConfig,
|
||||
PeerMetadata,
|
||||
PeerRemoval,
|
||||
SessionConfig,
|
||||
SessionMetadata,
|
||||
WorkingRepParams,
|
||||
WorkspaceMetadata,
|
||||
} from './validation'
|
||||
|
|
|
|||
|
|
@ -1,55 +1,95 @@
|
|||
import type { Honcho } from './client'
|
||||
import type HonchoCore from '@honcho-ai/core'
|
||||
import type { Message } from '@honcho-ai/core/src/resources/workspaces/sessions/messages'
|
||||
import { Page } from './pagination'
|
||||
import { Session } from './session'
|
||||
import {
|
||||
ChatQuerySchema,
|
||||
FilterSchema,
|
||||
type Filters,
|
||||
LimitSchema,
|
||||
MessageContentSchema,
|
||||
MessageMetadataSchema,
|
||||
SearchQuerySchema,
|
||||
type MessageCreate as ValidatedMessageCreate,
|
||||
} from './validation'
|
||||
|
||||
/**
|
||||
* Represents a peer in the Honcho system.
|
||||
*
|
||||
* Peers can send messages, participate in sessions, and maintain both global
|
||||
* and local representations for contextual interactions. A peer represents
|
||||
* an entity (user, assistant, etc.) that can communicate within the system.
|
||||
*/
|
||||
export class Peer {
|
||||
/**
|
||||
* Unique identifier for this peer.
|
||||
*/
|
||||
readonly id: string
|
||||
private _honcho: Honcho
|
||||
/**
|
||||
* Workspace ID for scoping operations.
|
||||
*/
|
||||
readonly workspaceId: string
|
||||
/**
|
||||
* Reference to the parent Honcho client instance.
|
||||
*/
|
||||
private _client: HonchoCore
|
||||
|
||||
/**
|
||||
* Initialize a new Peer.
|
||||
* Initialize a new Peer. **Do not call this directly, use the client.peer() method instead.**
|
||||
*
|
||||
* @param id - Unique identifier for this peer within the workspace
|
||||
* @param workspaceId - Workspace ID for scoping operations
|
||||
* @param client - Reference to the parent Honcho client instance
|
||||
*/
|
||||
constructor(id: string, honcho: Honcho, config?: Record<string, unknown>) {
|
||||
constructor(id: string, workspaceId: string, client: HonchoCore) {
|
||||
this.id = id
|
||||
this._honcho = honcho
|
||||
|
||||
if (config) {
|
||||
this._honcho['_client'].workspaces.peers.getOrCreate(
|
||||
this._honcho.workspaceId,
|
||||
{ id: this.id, configuration: config }
|
||||
)
|
||||
}
|
||||
this.workspaceId = workspaceId
|
||||
this._client = client
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the peer's representation with a natural language question.
|
||||
*
|
||||
* Makes an API call to the Honcho dialectic endpoint to query either the peer's
|
||||
* global representation (all content associated with this peer) or their local
|
||||
* representation of another peer (what this peer knows about the target peer).
|
||||
*
|
||||
* @param query - The natural language question to ask
|
||||
* @param stream - Whether to stream the response
|
||||
* @param target - Optional target peer for local representation query. If provided,
|
||||
* queries what this peer knows about the target peer rather than
|
||||
* querying the peer's global representation
|
||||
* @param sessionId - Optional session ID to scope the query to a specific session.
|
||||
* If provided, only information from that session is considered
|
||||
* @returns Promise resolving to response string containing the answer to the query,
|
||||
* or null if no relevant information is available
|
||||
*/
|
||||
async chat(
|
||||
query: string,
|
||||
opts?: {
|
||||
options?: {
|
||||
stream?: boolean
|
||||
target?: string | Peer
|
||||
sessionId?: string
|
||||
}
|
||||
): Promise<string | null> {
|
||||
const response = await this._honcho['_client'].workspaces.peers.chat(
|
||||
this._honcho.workspaceId,
|
||||
const chatParams = ChatQuerySchema.parse({
|
||||
query,
|
||||
stream: options?.stream,
|
||||
target: options?.target,
|
||||
sessionId: options?.sessionId,
|
||||
})
|
||||
const response = await this._client.workspaces.peers.chat(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
{
|
||||
query,
|
||||
stream: opts?.stream,
|
||||
target: opts?.target
|
||||
? typeof opts.target === 'string'
|
||||
? opts.target
|
||||
: opts.target.id
|
||||
query: chatParams.query,
|
||||
stream: chatParams.stream,
|
||||
target: chatParams.target
|
||||
? typeof chatParams.target === 'string'
|
||||
? chatParams.target
|
||||
: chatParams.target.id
|
||||
: undefined,
|
||||
session_id: opts?.sessionId,
|
||||
session_id: chatParams.sessionId,
|
||||
}
|
||||
)
|
||||
if (!response.content || response.content === 'None') {
|
||||
|
|
@ -60,38 +100,68 @@ export class Peer {
|
|||
|
||||
/**
|
||||
* Get all sessions this peer is a member of.
|
||||
*
|
||||
* Makes an API call to retrieve all sessions where this peer is an active participant.
|
||||
* Sessions are created when peers are added to them or send messages to them.
|
||||
*
|
||||
* @param filters - Optional filter criteria for sessions. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
* @returns Promise resolving to a paginated list of Session objects this peer belongs to.
|
||||
* Returns an empty list if the peer is not a member of any sessions
|
||||
*/
|
||||
async getSessions(
|
||||
filter?: { [key: string]: unknown } | null
|
||||
): Promise<Page<Session>> {
|
||||
const sessionsPage = await this._honcho[
|
||||
'_client'
|
||||
].workspaces.peers.sessions.list(this._honcho.workspaceId, this.id, {
|
||||
filter,
|
||||
})
|
||||
async getSessions(filters?: Filters | null): Promise<Page<Session>> {
|
||||
const validatedFilter = filters ? FilterSchema.parse(filters) : undefined
|
||||
const sessionsPage = await this._client.workspaces.peers.sessions.list(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
{
|
||||
filters: validatedFilter,
|
||||
}
|
||||
)
|
||||
return new Page(
|
||||
sessionsPage,
|
||||
(session: any) => new Session(session.id, this._honcho)
|
||||
(session) => new Session(session.id, this.workspaceId, this._client)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message attributed to this peer.
|
||||
* Create a message object attributed to this peer.
|
||||
*
|
||||
* This is a convenience method for creating message objects with this peer's ID.
|
||||
* The created message object can then be added to sessions or used in other operations.
|
||||
*
|
||||
* @param content - The text content for the message
|
||||
* @param metadata - Optional metadata to associate with the message
|
||||
* @returns A new message object with this peer's ID and the provided content
|
||||
*/
|
||||
message(content: string, opts?: { metadata?: Record<string, unknown> }): any {
|
||||
message(
|
||||
content: string,
|
||||
options?: { metadata?: Record<string, unknown> }
|
||||
): ValidatedMessageCreate {
|
||||
const validatedContent = MessageContentSchema.parse(content)
|
||||
const validatedMetadata = options?.metadata
|
||||
? MessageMetadataSchema.parse(options.metadata)
|
||||
: undefined
|
||||
|
||||
return {
|
||||
peerId: this.id,
|
||||
content,
|
||||
metadata: opts?.metadata,
|
||||
peer_id: this.id,
|
||||
content: validatedContent,
|
||||
metadata: validatedMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current metadata for this peer.
|
||||
*
|
||||
* Makes an API call to retrieve metadata associated with this peer. Metadata
|
||||
* can include custom attributes, settings, or any other key-value data
|
||||
* associated with the peer.
|
||||
*
|
||||
* @returns Promise resolving to a dictionary containing the peer's metadata.
|
||||
* Returns an empty dictionary if no metadata is set
|
||||
*/
|
||||
async getMetadata(): Promise<Record<string, unknown>> {
|
||||
const peer = await this._honcho['_client'].workspaces.peers.getOrCreate(
|
||||
this._honcho.workspaceId,
|
||||
const peer = await this._client.workspaces.peers.getOrCreate(
|
||||
this.workspaceId,
|
||||
{ id: this.id }
|
||||
)
|
||||
return peer.metadata || {}
|
||||
|
|
@ -99,13 +169,50 @@ export class Peer {
|
|||
|
||||
/**
|
||||
* Set the metadata for this peer.
|
||||
*
|
||||
* Makes an API call to update the metadata associated with this peer.
|
||||
* This will overwrite any existing metadata with the provided values.
|
||||
*
|
||||
* @param metadata - A dictionary of metadata to associate with this peer.
|
||||
* Keys must be strings, values can be any JSON-serializable type
|
||||
*/
|
||||
async setMetadata(metadata: Record<string, unknown>): Promise<void> {
|
||||
await this._honcho['_client'].workspaces.peers.update(
|
||||
this._honcho.workspaceId,
|
||||
this.id,
|
||||
{ metadata }
|
||||
await this._client.workspaces.peers.update(this.workspaceId, this.id, {
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current workspace-level configuration for this peer.
|
||||
*
|
||||
* Makes an API call to retrieve configuration associated with this peer.
|
||||
* Configuration currently includes one optional flag, `observe_me`.
|
||||
*
|
||||
* @returns Promise resolving to a dictionary containing the peer's configuration
|
||||
*/
|
||||
async getPeerConfig(): Promise<Record<string, unknown>> {
|
||||
const peer = await this._client.workspaces.peers.getOrCreate(
|
||||
this.workspaceId,
|
||||
{ id: this.id }
|
||||
)
|
||||
return peer.configuration || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration for this peer. Currently the only supported config
|
||||
* value is the `observe_me` flag, which controls whether derivation tasks
|
||||
* should be created for this peer's global representation. Default is True.
|
||||
*
|
||||
* Makes an API call to update the configuration associated with this peer.
|
||||
* This will overwrite any existing configuration with the provided values.
|
||||
*
|
||||
* @param config - A dictionary of configuration to associate with this peer.
|
||||
* Keys must be strings, values can be any JSON-serializable type
|
||||
*/
|
||||
async setPeerConfig(config: Record<string, unknown>): Promise<void> {
|
||||
await this._client.workspaces.peers.update(this.workspaceId, this.id, {
|
||||
configuration: config,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -114,54 +221,39 @@ export class Peer {
|
|||
* Makes an API call to search endpoint.
|
||||
*
|
||||
* @param query The search query to use
|
||||
* @returns A Page of Message objects representing the search results.
|
||||
* Returns an empty page if no messages are found.
|
||||
* @param filters - Optional filters to scope the search. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
* @param limit - Optional limit on the number of results to return.
|
||||
* @returns Promise resolving to an array of Message objects representing the search results.
|
||||
* Returns an empty array if no messages are found.
|
||||
*/
|
||||
async search(query: string): Promise<Page<any>> {
|
||||
if (!query || typeof query !== 'string' || query.trim().length === 0) {
|
||||
throw new Error('Search query must be a non-empty string')
|
||||
}
|
||||
const messagesPage = await this._honcho['_client'].workspaces.peers.search(
|
||||
this._honcho.workspaceId,
|
||||
async search(
|
||||
query: string,
|
||||
options?: { filters?: Filters; limit?: number }
|
||||
): Promise<Message[]> {
|
||||
const validatedQuery = SearchQuerySchema.parse(query)
|
||||
const validatedFilters = options?.filters
|
||||
? FilterSchema.parse(options.filters)
|
||||
: undefined
|
||||
const validatedLimit = options?.limit
|
||||
? LimitSchema.parse(options.limit)
|
||||
: undefined
|
||||
return await this._client.workspaces.peers.search(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
{ query: query }
|
||||
{
|
||||
query: validatedQuery,
|
||||
filters: validatedFilters,
|
||||
limit: validatedLimit,
|
||||
}
|
||||
)
|
||||
return new Page(messagesPage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file to create messages in this peer's global representation.
|
||||
* Return a string representation of the Peer.
|
||||
*
|
||||
* Makes an API call to upload a file and convert it into messages. The file is
|
||||
* processed to extract text content, split into appropriately sized chunks,
|
||||
* and created as messages attributed to this peer.
|
||||
*
|
||||
* @param file File to upload. Should be an object with filename, content (as Buffer or Uint8Array), and content_type
|
||||
* @returns A list of Message objects representing the created messages
|
||||
*
|
||||
* @note Supported file types include PDFs, text files, and JSON documents.
|
||||
* Large files will be automatically split into multiple messages to fit
|
||||
* within message size limits.
|
||||
* @returns A string representation suitable for debugging
|
||||
*/
|
||||
async uploadFile(file: {
|
||||
filename: string
|
||||
content: Buffer | Uint8Array
|
||||
content_type: string
|
||||
}): Promise<any[]> {
|
||||
// Convert file to the format expected by the API
|
||||
const fileData = {
|
||||
filename: file.filename,
|
||||
content: file.content,
|
||||
content_type: file.content_type,
|
||||
}
|
||||
|
||||
// Call the upload endpoint
|
||||
const response = await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.peers.messages.upload(this._honcho.workspaceId, this.id, {
|
||||
file: fileData,
|
||||
})
|
||||
|
||||
return response
|
||||
toString(): string {
|
||||
return `Peer(id='${this.id}')`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +1,186 @@
|
|||
import type { Honcho } from './client'
|
||||
import type HonchoCore from '@honcho-ai/core'
|
||||
import type { Message } from '@honcho-ai/core/src/resources/workspaces/sessions/messages'
|
||||
import type { Uploadable } from '@honcho-ai/core/src/uploads'
|
||||
import { Page } from './pagination'
|
||||
import { Peer } from './peer'
|
||||
import { SessionContext } from './session_context'
|
||||
import {
|
||||
ContextParamsSchema,
|
||||
FileUploadSchema,
|
||||
FilterSchema,
|
||||
type Filters,
|
||||
LimitSchema,
|
||||
type MessageAddition,
|
||||
MessageAdditionSchema,
|
||||
type PeerAddition,
|
||||
PeerAdditionSchema,
|
||||
type PeerRemoval,
|
||||
PeerRemovalSchema,
|
||||
SearchQuerySchema,
|
||||
SessionPeerConfigSchema,
|
||||
WorkingRepParamsSchema,
|
||||
} from './validation'
|
||||
|
||||
/**
|
||||
* Configuration options for a peer within a specific session.
|
||||
*
|
||||
* Controls how peers interact and observe each other within the context
|
||||
* of a particular session, allowing for fine-grained control over
|
||||
* representation building and theory-of-mind behaviors.
|
||||
*/
|
||||
export class SessionPeerConfig {
|
||||
observe_others: boolean
|
||||
observe_me: boolean
|
||||
/**
|
||||
* Whether other peers in this session should try to form a session-level
|
||||
* theory-of-mind representation of this peer. When false, prevents other
|
||||
* peers from building local representations of this peer within this session.
|
||||
*/
|
||||
observe_me?: boolean | null
|
||||
|
||||
constructor(opts?: { observe_others?: boolean; observe_me?: boolean }) {
|
||||
this.observe_others = opts?.observe_others ?? false
|
||||
this.observe_me = opts?.observe_me ?? true
|
||||
/**
|
||||
* Whether this peer should form session-level theory-of-mind representations
|
||||
* of other peers in the session. When false, this peer will not build local
|
||||
* representations of other peers within this session.
|
||||
*/
|
||||
observe_others?: boolean
|
||||
|
||||
/**
|
||||
* Initialize SessionPeerConfig with observation settings.
|
||||
*
|
||||
* @param observe_me - Whether other peers should observe this peer in the session
|
||||
* @param observe_others - Whether this peer should observe others in the session
|
||||
*/
|
||||
constructor(observe_me?: boolean | null, observe_others?: boolean) {
|
||||
const validatedConfig = SessionPeerConfigSchema.parse({
|
||||
observe_me,
|
||||
observe_others,
|
||||
})
|
||||
this.observe_me = validatedConfig.observe_me
|
||||
this.observe_others = validatedConfig.observe_others
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a session in Honcho.
|
||||
* Represents a session in the Honcho system.
|
||||
*
|
||||
* Sessions are scoped to a set of peers and contain messages/content. They create
|
||||
* bidirectional relationships between peers and provide a context for multi-party
|
||||
* conversations and interactions. Sessions serve as containers for conversations,
|
||||
* allowing peers to communicate while maintaining both global and local
|
||||
* representations of each other.
|
||||
*
|
||||
* Key features:
|
||||
* - Multi-peer conversations with configurable observation settings
|
||||
* - Message storage and retrieval with filtering capabilities
|
||||
* - Context optimization for token-limited scenarios
|
||||
* - File upload support with automatic message creation
|
||||
* - Session-scoped peer representations and theory-of-mind modeling
|
||||
* - Search functionality across session messages
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const session = await honcho.session('conversation-123')
|
||||
*
|
||||
* // Add peers to the session
|
||||
* await session.addPeers(['user1', 'assistant1'])
|
||||
*
|
||||
* // Send messages
|
||||
* await session.addMessages([
|
||||
* { peer_id: 'user1', content: 'Hello!' },
|
||||
* { peer_id: 'assistant1', content: 'Hi there!' }
|
||||
* ])
|
||||
*
|
||||
* // Get optimized context
|
||||
* const context = await session.getContext(true, 4000)
|
||||
* ```
|
||||
*/
|
||||
export class Session {
|
||||
/**
|
||||
* Unique identifier for this session.
|
||||
*/
|
||||
readonly id: string
|
||||
private _honcho: Honcho
|
||||
/**
|
||||
* Workspace ID for scoping operations.
|
||||
*/
|
||||
readonly workspaceId: string
|
||||
/**
|
||||
* Reference to the parent Honcho client instance.
|
||||
*/
|
||||
private _client: HonchoCore
|
||||
|
||||
/**
|
||||
* Initialize a new Session.
|
||||
* Initialize a new Session. **Do not call this directly, use the client.session() method instead.**
|
||||
*
|
||||
* @param id - Unique identifier for this session within the workspace
|
||||
* @param workspaceId - Workspace ID for scoping operations
|
||||
* @param client - Reference to the parent Honcho client instance
|
||||
*/
|
||||
constructor(id: string, honcho: Honcho, config?: Record<string, unknown>) {
|
||||
constructor(id: string, workspaceId: string, client: HonchoCore) {
|
||||
this.id = id
|
||||
this._honcho = honcho
|
||||
|
||||
if (config) {
|
||||
this._honcho['_client'].workspaces.sessions.getOrCreate(
|
||||
this._honcho.workspaceId,
|
||||
{ id: this.id, configuration: config }
|
||||
)
|
||||
}
|
||||
this.workspaceId = workspaceId
|
||||
this._client = client
|
||||
}
|
||||
|
||||
/**
|
||||
* Add peers to this session.
|
||||
*
|
||||
* Makes an API call to add one or more peers to this session. Adding peers
|
||||
* creates bidirectional relationships and allows them to participate in
|
||||
* the session's conversations. Peers can be added with optional session-specific
|
||||
* configuration to control observation behaviors.
|
||||
*
|
||||
* @param peers - Peers to add to the session. Can be:
|
||||
* - string: Single peer ID
|
||||
* - Peer: Single Peer object
|
||||
* - Array<string | Peer>: List of peer IDs and/or Peer objects
|
||||
* - [string | Peer, SessionPeerConfig]: Single peer with session config
|
||||
* - Array<string | Peer | [string | Peer, SessionPeerConfig]>: Mixed list
|
||||
* of peers and peer+config combinations
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Add single peer
|
||||
* await session.addPeers('user123')
|
||||
*
|
||||
* // Add multiple peers
|
||||
* await session.addPeers(['user1', 'user2', peer3])
|
||||
*
|
||||
* // Add peer with custom config
|
||||
* await session.addPeers(['user1', new SessionPeerConfig(false, true)])
|
||||
*
|
||||
* // Add mixed peers with and without configs
|
||||
* await session.addPeers([
|
||||
* 'user1',
|
||||
* ['user2', new SessionPeerConfig(true, false)],
|
||||
* peer3
|
||||
* ])
|
||||
* ```
|
||||
*/
|
||||
async addPeers(
|
||||
peers:
|
||||
| string
|
||||
| Peer
|
||||
| Array<string | Peer>
|
||||
| [string | Peer, SessionPeerConfig]
|
||||
| Array<[string | Peer, SessionPeerConfig]>
|
||||
| Array<string | Peer | [string | Peer, SessionPeerConfig]>
|
||||
): Promise<void> {
|
||||
async addPeers(peers: PeerAddition): Promise<void> {
|
||||
const validatedPeers = PeerAdditionSchema.parse(peers)
|
||||
const peerDict: Record<string, SessionPeerConfig> = {}
|
||||
const peersArray = Array.isArray(peers) ? peers : [peers]
|
||||
const peersArray = Array.isArray(validatedPeers)
|
||||
? validatedPeers
|
||||
: [validatedPeers]
|
||||
|
||||
for (const peer of peersArray) {
|
||||
if (typeof peer === 'string') {
|
||||
peerDict[peer] = { observe_others: false, observe_me: true }
|
||||
} else if (typeof peer === 'object' && 'id' in peer) {
|
||||
peerDict[peer.id] = { observe_others: false, observe_me: true }
|
||||
// Handle string peer ID
|
||||
peerDict[peer] = {}
|
||||
} else if (Array.isArray(peer)) {
|
||||
// Handle tuple [string | Peer, SessionPeerConfig]
|
||||
const peerId = typeof peer[0] === 'string' ? peer[0] : peer[0].id
|
||||
peerDict[peerId] = peer[1]
|
||||
} else if (
|
||||
typeof peer === 'object' &&
|
||||
'id' in peer &&
|
||||
'observe_others' in peer &&
|
||||
'observe_me' in peer
|
||||
) {
|
||||
peerDict[(peer as any).id] = {
|
||||
observe_others: (peer as any).observe_others,
|
||||
observe_me: (peer as any).observe_me,
|
||||
}
|
||||
} else if (typeof peer === 'object' && 'id' in peer) {
|
||||
// Handle Peer object
|
||||
peerDict[peer.id] = {}
|
||||
} else {
|
||||
// This should never happen with proper typing, but handle gracefully
|
||||
throw new Error(`Invalid peer type: ${typeof peer}`)
|
||||
}
|
||||
}
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.peers.add(
|
||||
this._honcho.workspaceId,
|
||||
|
||||
await this._client.workspaces.sessions.peers.add(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
peerDict
|
||||
)
|
||||
|
|
@ -81,40 +188,45 @@ export class Session {
|
|||
|
||||
/**
|
||||
* Set the complete peer list for this session.
|
||||
*
|
||||
* Makes an API call to replace the current peer list with the provided peers.
|
||||
* This will remove any peers not in the new list and add any that are missing.
|
||||
* Unlike addPeers(), this method overwrites the entire peer membership.
|
||||
*
|
||||
* @param peers - Peers to set for the session. Can be:
|
||||
* - string: Single peer ID
|
||||
* - Peer: Single Peer object
|
||||
* - Array<string | Peer>: List of peer IDs and/or Peer objects
|
||||
* - [string | Peer, SessionPeerConfig]: Single peer with session config
|
||||
* - Array<string | Peer | [string | Peer, SessionPeerConfig]>: Mixed list
|
||||
* of peers and peer+config combinations
|
||||
*/
|
||||
async setPeers(
|
||||
peers:
|
||||
| string
|
||||
| Peer
|
||||
| Array<string | Peer>
|
||||
| [string | Peer, SessionPeerConfig]
|
||||
| Array<[string | Peer, SessionPeerConfig]>
|
||||
| Array<string | Peer | [string | Peer, SessionPeerConfig]>
|
||||
): Promise<void> {
|
||||
async setPeers(peers: PeerAddition): Promise<void> {
|
||||
const validatedPeers = PeerAdditionSchema.parse(peers)
|
||||
const peerDict: Record<string, SessionPeerConfig> = {}
|
||||
const peersArray = Array.isArray(peers) ? peers : [peers]
|
||||
const peersArray = Array.isArray(validatedPeers)
|
||||
? validatedPeers
|
||||
: [validatedPeers]
|
||||
|
||||
for (const peer of peersArray) {
|
||||
if (typeof peer === 'string') {
|
||||
peerDict[peer] = { observe_others: false, observe_me: true }
|
||||
} else if (typeof peer === 'object' && 'id' in peer) {
|
||||
peerDict[peer.id] = { observe_others: false, observe_me: true }
|
||||
// Handle string peer ID
|
||||
peerDict[peer] = {}
|
||||
} else if (Array.isArray(peer)) {
|
||||
// Handle tuple [string | Peer, SessionPeerConfig]
|
||||
const peerId = typeof peer[0] === 'string' ? peer[0] : peer[0].id
|
||||
peerDict[peerId] = peer[1]
|
||||
} else if (
|
||||
typeof peer === 'object' &&
|
||||
'id' in peer &&
|
||||
'observe_others' in peer &&
|
||||
'observe_me' in peer
|
||||
) {
|
||||
peerDict[(peer as any).id] = {
|
||||
observe_others: (peer as any).observe_others,
|
||||
observe_me: (peer as any).observe_me,
|
||||
}
|
||||
} else if (typeof peer === 'object' && 'id' in peer) {
|
||||
// Handle Peer object
|
||||
peerDict[peer.id] = {}
|
||||
} else {
|
||||
// This should never happen with proper typing, but handle gracefully
|
||||
throw new Error(`Invalid peer type: ${typeof peer}`)
|
||||
}
|
||||
}
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.peers.set(
|
||||
this._honcho.workspaceId,
|
||||
|
||||
await this._client.workspaces.sessions.peers.set(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
peerDict
|
||||
)
|
||||
|
|
@ -122,40 +234,65 @@ export class Session {
|
|||
|
||||
/**
|
||||
* Remove peers from this session.
|
||||
*
|
||||
* Makes an API call to remove one or more peers from this session.
|
||||
* Removed peers will no longer be able to participate in the session
|
||||
* unless added back. Their existing messages remain in the session.
|
||||
*
|
||||
* @param peers - Peers to remove from the session. Can be:
|
||||
* - string: Single peer ID
|
||||
* - Peer: Single Peer object
|
||||
* - Array<string | Peer>: List of peer IDs and/or Peer objects
|
||||
*/
|
||||
async removePeers(
|
||||
peers: string | Peer | Array<string | Peer>
|
||||
): Promise<void> {
|
||||
const peerIds = Array.isArray(peers)
|
||||
? peers.map((p) => (typeof p === 'string' ? p : p.id))
|
||||
: [typeof peers === 'string' ? peers : peers.id]
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.peers.remove(
|
||||
this._honcho.workspaceId,
|
||||
async removePeers(peers: PeerRemoval): Promise<void> {
|
||||
const validatedPeers = PeerRemovalSchema.parse(peers)
|
||||
const peerIds = Array.isArray(validatedPeers)
|
||||
? validatedPeers.map((p) => (typeof p === 'string' ? p : p.id))
|
||||
: [
|
||||
typeof validatedPeers === 'string'
|
||||
? validatedPeers
|
||||
: validatedPeers.id,
|
||||
]
|
||||
await this._client.workspaces.sessions.peers.remove(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
peerIds
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all peers in this session. Automatically converts the paginated response
|
||||
* into a list for us -- the max number of peers in a session is usually 10.
|
||||
* Get all peers in this session.
|
||||
*
|
||||
* Makes an API call to retrieve the list of peers that are currently
|
||||
* members of this session. Automatically converts the paginated response
|
||||
* into a list for convenience -- the max number of peers in a session is usually 10.
|
||||
*
|
||||
* @returns Promise resolving to a list of Peer objects that are members of this session
|
||||
*/
|
||||
async getPeers(): Promise<Peer[]> {
|
||||
const peersPage = await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.sessions.peers.list(this._honcho.workspaceId, this.id)
|
||||
return peersPage.items.map((peer: any) => new Peer(peer.id, this._honcho))
|
||||
const peersPage = await this._client.workspaces.sessions.peers.list(
|
||||
this.workspaceId,
|
||||
this.id
|
||||
)
|
||||
return peersPage.items.map(
|
||||
(peer) => new Peer(peer.id, this.workspaceId, this._client)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration for a peer in this session.
|
||||
*
|
||||
* Makes an API call to retrieve the session-specific configuration for a peer.
|
||||
* This includes observation settings that control how this peer interacts
|
||||
* with other peers within this session context.
|
||||
*
|
||||
* @param peer - The peer to get configuration for. Can be peer ID string or Peer object
|
||||
* @returns Promise resolving to SessionPeerConfig object with the peer's session settings
|
||||
*/
|
||||
async getPeerConfig(peer: string | Peer): Promise<SessionPeerConfig> {
|
||||
const peerId = typeof peer === 'string' ? peer : peer.id
|
||||
return await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.sessions.peers.getConfig(
|
||||
this._honcho.workspaceId,
|
||||
return await this._client.workspaces.sessions.peers.getConfig(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
peerId
|
||||
)
|
||||
|
|
@ -163,92 +300,164 @@ export class Session {
|
|||
|
||||
/**
|
||||
* Set the configuration for a peer in this session.
|
||||
*
|
||||
* Makes an API call to update the session-specific configuration for a peer.
|
||||
* This controls observation behaviors and theory-of-mind formation within
|
||||
* this session context.
|
||||
*
|
||||
* @param peer - The peer to configure. Can be peer ID string or Peer object
|
||||
* @param config - SessionPeerConfig object specifying the observation settings
|
||||
*/
|
||||
async setPeerConfig(
|
||||
peer: string | Peer,
|
||||
config: SessionPeerConfig
|
||||
): Promise<void> {
|
||||
const peerId = typeof peer === 'string' ? peer : peer.id
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.peers.setConfig(
|
||||
this._honcho.workspaceId,
|
||||
const validatedConfig = SessionPeerConfigSchema.parse(config)
|
||||
await this._client.workspaces.sessions.peers.setConfig(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
peerId,
|
||||
{
|
||||
observe_others: config.observe_others,
|
||||
observe_me: config.observe_me,
|
||||
observe_others: validatedConfig.observe_others,
|
||||
observe_me: validatedConfig.observe_me,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more messages to this session.
|
||||
*
|
||||
* Makes an API call to store messages in this session. Any message added
|
||||
* to a session will automatically add the creating peer to the session
|
||||
* if they are not already a member. Messages are the primary way content
|
||||
* flows through the Honcho system.
|
||||
*
|
||||
* @param messages - Messages to add to the session. Can be:
|
||||
* - MessageCreate: Single message object with peer_id and content
|
||||
* - MessageCreate[]: Array of message objects
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Add single message
|
||||
* await session.addMessages({
|
||||
* peer_id: 'user123',
|
||||
* content: 'Hello world!'
|
||||
* })
|
||||
*
|
||||
* // Add multiple messages
|
||||
* await session.addMessages([
|
||||
* { peer_id: 'user1', content: 'Hello!' },
|
||||
* { peer_id: 'assistant', content: 'Hi there!' }
|
||||
* ])
|
||||
* ```
|
||||
*/
|
||||
async addMessages(messages: any | any[]): Promise<void> {
|
||||
const msgs = Array.isArray(messages) ? messages : [messages]
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.messages.create(
|
||||
this._honcho.workspaceId,
|
||||
async addMessages(messages: MessageAddition): Promise<void> {
|
||||
const validatedMessages = MessageAdditionSchema.parse(messages)
|
||||
const messagesList = Array.isArray(validatedMessages)
|
||||
? validatedMessages
|
||||
: [validatedMessages]
|
||||
await this._client.workspaces.sessions.messages.create(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
{
|
||||
messages: msgs.map((msg) => ({
|
||||
peer_id: msg.peerId,
|
||||
content: msg.content,
|
||||
metadata: msg.metadata,
|
||||
})),
|
||||
messages: messagesList,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get messages from this session with optional filtering.
|
||||
*
|
||||
* Makes an API call to retrieve messages from this session. Results can be
|
||||
* filtered based on various criteria and are returned in a paginated format.
|
||||
* Messages are ordered by creation time (most recent first by default).
|
||||
*
|
||||
* @param filters - Optional filter criteria for messages. See [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
* @returns Promise resolving to a Page of Message objects matching the specified criteria
|
||||
*/
|
||||
async getMessages(opts?: {
|
||||
filter?: Record<string, unknown>
|
||||
}): Promise<Page<any>> {
|
||||
const messagesPage = await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.sessions.messages.list(
|
||||
this._honcho.workspaceId,
|
||||
async getMessages(filters?: Filters): Promise<Page<Message>> {
|
||||
const validatedFilter = filters ? FilterSchema.parse(filters) : undefined
|
||||
const messagesPage = await this._client.workspaces.sessions.messages.list(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
opts?.filter
|
||||
validatedFilter
|
||||
)
|
||||
return new Page(messagesPage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for this session.
|
||||
*
|
||||
* Makes an API call to retrieve the current metadata associated with this session.
|
||||
* Metadata can include custom attributes, settings, or any other key-value data
|
||||
* that provides context about the session.
|
||||
*
|
||||
* @returns Promise resolving to a dictionary containing the session's metadata.
|
||||
* Returns an empty dictionary if no metadata is set
|
||||
*/
|
||||
async getMetadata(): Promise<Record<string, unknown>> {
|
||||
const session = await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.sessions.getOrCreate(this._honcho.workspaceId, { id: this.id })
|
||||
const session = await this._client.workspaces.sessions.getOrCreate(
|
||||
this.workspaceId,
|
||||
{ id: this.id }
|
||||
)
|
||||
return session.metadata || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set metadata for this session.
|
||||
*
|
||||
* Makes an API call to update the metadata associated with this session.
|
||||
* This will overwrite any existing metadata with the provided values.
|
||||
* Metadata is useful for storing custom attributes, configuration, or
|
||||
* contextual information about the session.
|
||||
*
|
||||
* @param metadata - A dictionary of metadata to associate with this session.
|
||||
* Keys must be strings, values can be any JSON-serializable type
|
||||
*/
|
||||
async setMetadata(metadata: Record<string, unknown>): Promise<void> {
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.update(
|
||||
this._honcho.workspaceId,
|
||||
this.id,
|
||||
{ metadata }
|
||||
)
|
||||
await this._client.workspaces.sessions.update(this.workspaceId, this.id, {
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get optimized context for this session within a token limit.
|
||||
*
|
||||
* Makes an API call to retrieve a curated list of messages that provides
|
||||
* optimal context for the conversation while staying within the specified
|
||||
* token limit. Uses tiktoken for token counting, so results should be
|
||||
* compatible with OpenAI models. The context optimization balances
|
||||
* recency and relevance to provide the best conversational context.
|
||||
*
|
||||
* @param summary - Whether to include summary information in the context.
|
||||
* When true, includes session summary if available. Defaults to true
|
||||
* @param tokens - Maximum number of tokens to include in the context. If not provided,
|
||||
* uses the server's default configuration
|
||||
* @returns Promise resolving to a SessionContext object containing the optimized
|
||||
* message history and summary (if available) that maximizes conversational
|
||||
* context while respecting the token limit
|
||||
*
|
||||
* @note Token counting is performed using tiktoken. For models using different
|
||||
* tokenizers, you may need to adjust the token limit accordingly.
|
||||
*/
|
||||
async getContext(opts?: {
|
||||
async getContext(options?: {
|
||||
summary?: boolean
|
||||
tokens?: number
|
||||
}): Promise<SessionContext> {
|
||||
const context = await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.sessions.getContext(this._honcho.workspaceId, this.id, {
|
||||
tokens: opts?.tokens,
|
||||
summary: opts?.summary,
|
||||
const contextParams = ContextParamsSchema.parse({
|
||||
summary: options?.summary,
|
||||
tokens: options?.tokens,
|
||||
})
|
||||
return new SessionContext(this.id, context.messages, context.summary || '')
|
||||
const context = await this._client.workspaces.sessions.getContext(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
{
|
||||
tokens: contextParams.tokens,
|
||||
summary: contextParams.summary,
|
||||
}
|
||||
)
|
||||
return new SessionContext(this.id, context.messages, context.summary)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -257,19 +466,34 @@ export class Session {
|
|||
* Makes an API call to search for messages in this session.
|
||||
*
|
||||
* @param query The search query to use
|
||||
* @returns A Page of Message objects representing the search results.
|
||||
* Returns an empty page if no messages are found.
|
||||
* @param filters - Optional filters to scope the search: see [search filters documentation](https://docs.honcho.dev/v2/guides/using-filters).
|
||||
* @param limit Number of results to return (1-100, default: 10).
|
||||
* @returns A list of Message objects representing the search results.
|
||||
* Returns an empty list if no messages are found.
|
||||
*/
|
||||
async search(query: string): Promise<Page<any>> {
|
||||
if (!query || typeof query !== 'string' || query.trim().length === 0) {
|
||||
throw new Error('Search query must be a non-empty string')
|
||||
async search(
|
||||
query: string,
|
||||
options?: {
|
||||
filters?: Filters
|
||||
limit?: number
|
||||
}
|
||||
const messagesPage = await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.sessions.search(this._honcho.workspaceId, this.id, {
|
||||
query: query,
|
||||
})
|
||||
return new Page(messagesPage)
|
||||
): Promise<Message[]> {
|
||||
const validatedQuery = SearchQuerySchema.parse(query)
|
||||
const validatedFilters = options?.filters
|
||||
? FilterSchema.parse(options.filters)
|
||||
: undefined
|
||||
const validatedLimit = options?.limit
|
||||
? LimitSchema.parse(options.limit)
|
||||
: undefined
|
||||
return await this._client.workspaces.sessions.search(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
{
|
||||
query: validatedQuery,
|
||||
filters: validatedFilters,
|
||||
limit: validatedLimit,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -277,65 +501,94 @@ export class Session {
|
|||
*
|
||||
* Makes an API call to upload a file and convert it into messages. The file is
|
||||
* processed to extract text content, split into appropriately sized chunks,
|
||||
* and created as messages attributed to this peer.
|
||||
* and created as messages attributed to the specified peer. The peer will be
|
||||
* automatically added to the session if not already a member.
|
||||
*
|
||||
* @param file File to upload. Should be an object with filename, content (as Buffer or Uint8Array), and content_type
|
||||
* @param peerId The peer ID to attribute the messages to
|
||||
* @returns A list of Message objects representing the created messages
|
||||
* @param file - File to upload. Can be:
|
||||
* - File objects (browser File API)
|
||||
* - Buffer or Uint8Array with filename and content_type
|
||||
* - { filename: string, content: Buffer | Uint8Array, content_type: string }
|
||||
* @param peerId - The peer ID to attribute the created messages to
|
||||
* @returns Promise resolving to a list of Message objects representing the created messages
|
||||
*
|
||||
* @note Supported file types include PDFs, text files, and JSON documents.
|
||||
* Large files will be automatically split into multiple messages to fit
|
||||
* within message size limits.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Upload a file
|
||||
* const messages = await session.uploadFile(fileInput.files[0], 'user123')
|
||||
* console.log(`Created ${messages.length} messages from file`)
|
||||
* ```
|
||||
*/
|
||||
async uploadFile(
|
||||
file: {
|
||||
filename: string
|
||||
content: Buffer | Uint8Array
|
||||
content_type: string
|
||||
},
|
||||
peerId: string
|
||||
): Promise<any[]> {
|
||||
// Convert file to the format expected by the API
|
||||
const fileData = {
|
||||
filename: file.filename,
|
||||
content: file.content,
|
||||
content_type: file.content_type,
|
||||
}
|
||||
|
||||
// Call the upload endpoint
|
||||
const response = await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.sessions.messages.upload(this._honcho.workspaceId, this.id, {
|
||||
file: fileData,
|
||||
peer_id: peerId,
|
||||
})
|
||||
async uploadFile(file: Uploadable, peerId: string): Promise<Message[]> {
|
||||
const uploadParams = FileUploadSchema.parse({ file, peerId })
|
||||
const response = await this._client.workspaces.sessions.messages.upload(
|
||||
this.workspaceId,
|
||||
this.id,
|
||||
{
|
||||
file: uploadParams.file,
|
||||
peer_id: uploadParams.peerId,
|
||||
}
|
||||
)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current working representation of the peer in this session.
|
||||
* Get the current working representation of a peer in this session.
|
||||
*
|
||||
* @param peer The peer to get the working representation of.
|
||||
* @param target The target peer to get the representation of. If provided, queries what `peer` knows about the `target`.
|
||||
* @returns A dictionary containing information about the peer.
|
||||
* Makes an API call to retrieve the session-scoped representation that has been
|
||||
* built for a peer. This can be either the peer's global representation or
|
||||
* their local representation of another peer (theory-of-mind).
|
||||
*
|
||||
* @param peer - The peer to get the working representation of. Can be peer ID string or Peer object
|
||||
* @param target - Optional target peer. If provided, returns what `peer` knows about
|
||||
* `target` within this session context rather than `peer`'s global representation
|
||||
* @returns Promise resolving to a dictionary containing the peer's representation information,
|
||||
* including facts, characteristics, and contextual knowledge
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Get peer's global representation in this session
|
||||
* const globalRep = await session.workingRep('user123')
|
||||
*
|
||||
* // Get what user123 knows about assistant in this session
|
||||
* const localRep = await session.workingRep('user123', 'assistant')
|
||||
* ```
|
||||
*/
|
||||
async workingRep(
|
||||
peer: string | Peer,
|
||||
target?: string | Peer
|
||||
): Promise<Record<string, unknown>> {
|
||||
const peerId = typeof peer === 'string' ? peer : peer.id
|
||||
const targetId = target
|
||||
? typeof target === 'string'
|
||||
? target
|
||||
: target.id
|
||||
const workingRepParams = WorkingRepParamsSchema.parse({ peer, target })
|
||||
const peerId =
|
||||
typeof workingRepParams.peer === 'string'
|
||||
? workingRepParams.peer
|
||||
: workingRepParams.peer.id
|
||||
const targetId = workingRepParams.target
|
||||
? typeof workingRepParams.target === 'string'
|
||||
? workingRepParams.target
|
||||
: workingRepParams.target.id
|
||||
: undefined
|
||||
|
||||
return await (
|
||||
this._honcho['_client'] as any
|
||||
).workspaces.peers.workingRepresentation(this._honcho.workspaceId, peerId, {
|
||||
session_id: this.id,
|
||||
target: targetId,
|
||||
})
|
||||
return await this._client.workspaces.peers.workingRepresentation(
|
||||
this.workspaceId,
|
||||
peerId,
|
||||
{
|
||||
session_id: this.id,
|
||||
target: targetId,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the Session.
|
||||
*
|
||||
* @returns A string representation suitable for debugging
|
||||
*/
|
||||
toString(): string {
|
||||
return `Session(id='${this.id}')`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { Message } from '@honcho-ai/core/src/resources/workspaces/sessions/messages'
|
||||
import type { Peer } from './peer'
|
||||
|
||||
/**
|
||||
|
|
@ -16,7 +17,7 @@ export class SessionContext {
|
|||
/**
|
||||
* List of Message objects representing the conversation context.
|
||||
*/
|
||||
readonly messages: any[]
|
||||
readonly messages: Message[]
|
||||
|
||||
/**
|
||||
* Summary of the session history prior to the message cutoff.
|
||||
|
|
@ -30,7 +31,7 @@ export class SessionContext {
|
|||
* @param messages List of Message objects to include in the context
|
||||
* @param summary Summary of the session history prior to the message cutoff
|
||||
*/
|
||||
constructor(sessionId: string, messages: any[], summary: string = '') {
|
||||
constructor(sessionId: string, messages: Message[], summary: string = '') {
|
||||
this.sessionId = sessionId
|
||||
this.messages = messages
|
||||
this.summary = summary || ''
|
||||
|
|
@ -39,8 +40,8 @@ export class SessionContext {
|
|||
/**
|
||||
* Convert the context to OpenAI-compatible message format.
|
||||
*
|
||||
* Transforms the message history into the format expected by OpenAI's
|
||||
* Chat Completions API, with proper role assignments based on the
|
||||
* Transforms the message history and summary into the format expected by
|
||||
* OpenAI's Chat Completions API, with proper role assignments based on the
|
||||
* assistant's identity.
|
||||
*
|
||||
* @param assistant The assistant peer (Peer object or peer ID string) to use
|
||||
|
|
@ -49,47 +50,71 @@ export class SessionContext {
|
|||
* @returns A list of dictionaries in OpenAI format, where each dictionary contains
|
||||
* "role" and "content" keys suitable for the OpenAI API
|
||||
*/
|
||||
toOpenAI(assistant: string | Peer): Array<{ role: string; content: string }> {
|
||||
toOpenAI(
|
||||
assistant: string | Peer
|
||||
): Array<{ role: string; content: string; name?: string }> {
|
||||
const assistantId = typeof assistant === 'string' ? assistant : assistant.id
|
||||
return this.messages.map((message) => ({
|
||||
role: message.peer_name === assistantId ? 'assistant' : 'user',
|
||||
const summaryMessage = {
|
||||
role: 'system',
|
||||
content: `<summary>${this.summary}</summary>`,
|
||||
}
|
||||
const messages = this.messages.map((message) => ({
|
||||
role: message.peer_id === assistantId ? 'assistant' : 'user',
|
||||
name: message.peer_id,
|
||||
content: message.content,
|
||||
}))
|
||||
return this.summary ? [summaryMessage, ...messages] : messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the context to Anthropic-compatible message format.
|
||||
*
|
||||
* Transforms the message history into the format expected by Anthropic's
|
||||
* Claude API.
|
||||
* Claude API, with proper role assignments based on the assistant's identity.
|
||||
*
|
||||
* @param assistant The assistant peer (Peer object or peer ID string) to use
|
||||
* for determining message roles. Messages from this peer will
|
||||
* be marked as "assistant", others as "user"
|
||||
* @returns A list of dictionaries in Anthropic format, where each dictionary contains
|
||||
* "role" and "content" keys suitable for the Anthropic API
|
||||
*
|
||||
* Note:
|
||||
* Future versions may implement role alternation requirements for
|
||||
* Anthropic's API compatibility
|
||||
*/
|
||||
toAnthropic(
|
||||
assistant: string | Peer
|
||||
): Array<{ role: string; content: string }> {
|
||||
const assistantId = typeof assistant === 'string' ? assistant : assistant.id
|
||||
return this.messages.map((message) => ({
|
||||
role: message.peer_name === assistantId ? 'assistant' : 'user',
|
||||
content: message.content,
|
||||
}))
|
||||
const summaryMessage = {
|
||||
role: 'user',
|
||||
content: `<summary>${this.summary}</summary>`,
|
||||
}
|
||||
const messages = this.messages.map((message) =>
|
||||
message.peer_id === assistantId
|
||||
? {
|
||||
role: 'assistant',
|
||||
content: message.content,
|
||||
}
|
||||
: {
|
||||
role: 'user',
|
||||
content: `${message.peer_id}: ${message.content}`,
|
||||
}
|
||||
)
|
||||
return this.summary ? [summaryMessage, ...messages] : messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of messages in the context.
|
||||
*/
|
||||
get length(): number {
|
||||
return this.messages.length
|
||||
return this.messages.length + (this.summary.length > 0 ? 1 : 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the SessionContext.
|
||||
*/
|
||||
toString(): string {
|
||||
return `SessionContext(messages=${this.messages.length})`
|
||||
return `SessionContext(messages=${this.messages.length}, summary=${this.summary})`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,261 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
/**
|
||||
* Validation schemas for the Honcho TypeScript SDK.
|
||||
*
|
||||
* These schemas ensure type safety and runtime validation for all inputs
|
||||
* to the SDK, providing clear error messages when validation fails.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Schema for Honcho client configuration options.
|
||||
*/
|
||||
export const HonchoConfigSchema = z.object({
|
||||
apiKey: z.string().optional(),
|
||||
environment: z.enum(['local', 'production', 'demo']).optional(),
|
||||
baseURL: z.string().url('Base URL must be a valid URL').optional(),
|
||||
workspaceId: z
|
||||
.string()
|
||||
.min(1, 'Workspace ID must be a non-empty string')
|
||||
.optional(),
|
||||
timeout: z.number().positive('Timeout must be a positive number').optional(),
|
||||
maxRetries: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0, 'Max retries must be a non-negative integer')
|
||||
.optional(),
|
||||
defaultHeaders: z.record(z.string(), z.string()).optional(),
|
||||
defaultQuery: z.record(z.string(), z.unknown()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for peer metadata.
|
||||
*/
|
||||
export const PeerMetadataSchema = z.record(z.string(), z.unknown())
|
||||
|
||||
/**
|
||||
* Schema for peer configuration.
|
||||
*/
|
||||
export const PeerConfigSchema = z.record(z.string(), z.unknown())
|
||||
|
||||
/**
|
||||
* Schema for peer ID validation.
|
||||
*/
|
||||
export const PeerIdSchema = z
|
||||
.string()
|
||||
.min(1, 'Peer ID must be a non-empty string')
|
||||
|
||||
/**
|
||||
* Schema for session metadata.
|
||||
*/
|
||||
export const SessionMetadataSchema = z.record(z.string(), z.unknown())
|
||||
|
||||
/**
|
||||
* Schema for session configuration.
|
||||
*/
|
||||
export const SessionConfigSchema = z.record(z.string(), z.unknown())
|
||||
|
||||
/**
|
||||
* Schema for session ID validation.
|
||||
*/
|
||||
export const SessionIdSchema = z
|
||||
.string()
|
||||
.min(1, 'Session ID must be a non-empty string')
|
||||
|
||||
/**
|
||||
* Schema for session peer configuration.
|
||||
*/
|
||||
export const SessionPeerConfigSchema = z.object({
|
||||
observe_me: z.boolean().nullable().optional(),
|
||||
observe_others: z.boolean().optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for message content.
|
||||
*/
|
||||
export const MessageContentSchema = z
|
||||
.string()
|
||||
.refine(
|
||||
(content: string) => content === '' || content.trim().length > 0,
|
||||
'Message content cannot be only whitespace'
|
||||
)
|
||||
|
||||
/**
|
||||
* Schema for message metadata.
|
||||
*/
|
||||
export const MessageMetadataSchema = z
|
||||
.record(z.string(), z.unknown())
|
||||
.optional()
|
||||
|
||||
/**
|
||||
* Schema for message creation.
|
||||
*/
|
||||
export const MessageCreateSchema = z.object({
|
||||
peer_id: PeerIdSchema,
|
||||
content: MessageContentSchema,
|
||||
metadata: MessageMetadataSchema,
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for search query validation.
|
||||
*/
|
||||
export const SearchQuerySchema = z
|
||||
.string()
|
||||
.min(1, 'Search query must be a non-empty string')
|
||||
.refine(
|
||||
(query: string) => query.trim().length > 0,
|
||||
'Search query cannot be only whitespace'
|
||||
)
|
||||
|
||||
/**
|
||||
* Schema for filter objects.
|
||||
*/
|
||||
export const FilterSchema = z.record(z.string(), z.unknown()).optional()
|
||||
|
||||
/**
|
||||
* Schema for chat query parameters.
|
||||
*/
|
||||
export const ChatQuerySchema = z.object({
|
||||
query: SearchQuerySchema,
|
||||
stream: z.boolean().optional(),
|
||||
target: z.union([z.string(), z.object({ id: z.string() })]).optional(),
|
||||
sessionId: z.string().optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for context retrieval parameters.
|
||||
*/
|
||||
export const ContextParamsSchema = z.object({
|
||||
summary: z.boolean().optional(),
|
||||
tokens: z
|
||||
.number()
|
||||
.positive('Token limit must be a positive number')
|
||||
.optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for deriver status options.
|
||||
*/
|
||||
export const DeriverStatusOptionsSchema = z.object({
|
||||
observerId: z.string().optional(),
|
||||
senderId: z.string().optional(),
|
||||
sessionId: z.string().optional(),
|
||||
timeoutMs: z
|
||||
.number()
|
||||
.positive('Timeout must be a positive number')
|
||||
.optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for file upload parameters.
|
||||
* Supports File objects (browser), Buffer, Uint8Array, and custom uploadable objects.
|
||||
*/
|
||||
export const FileUploadSchema = z.object({
|
||||
file: z.union([
|
||||
// Browser File object
|
||||
z.instanceof(File),
|
||||
// Node.js Buffer
|
||||
z.instanceof(Buffer),
|
||||
// Uint8Array
|
||||
z.instanceof(Uint8Array),
|
||||
// Custom uploadable object with filename, content, and content_type
|
||||
z.object({
|
||||
filename: z.string().min(1, 'Filename must be a non-empty string'),
|
||||
content: z.union([z.instanceof(Buffer), z.instanceof(Uint8Array)]),
|
||||
content_type: z
|
||||
.string()
|
||||
.min(1, 'Content type must be a non-empty string'),
|
||||
}),
|
||||
// Fallback for any other uploadable type
|
||||
z
|
||||
.any()
|
||||
.refine(
|
||||
(val) => val !== null && val !== undefined,
|
||||
'File must not be null or undefined'
|
||||
),
|
||||
]),
|
||||
peerId: PeerIdSchema,
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for working representation parameters.
|
||||
*/
|
||||
export const WorkingRepParamsSchema = z.object({
|
||||
peer: z.union([z.string(), z.object({ id: z.string() })]),
|
||||
target: z.union([z.string(), z.object({ id: z.string() })]).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Schema for peer addition to session.
|
||||
*/
|
||||
export const PeerAdditionSchema = z.union([
|
||||
z.string(),
|
||||
z.object({ id: z.string() }),
|
||||
z.array(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.object({ id: z.string() }),
|
||||
z.tuple([
|
||||
z.union([z.string(), z.object({ id: z.string() })]),
|
||||
SessionPeerConfigSchema,
|
||||
]),
|
||||
])
|
||||
),
|
||||
z.tuple([
|
||||
z.union([z.string(), z.object({ id: z.string() })]),
|
||||
SessionPeerConfigSchema,
|
||||
]),
|
||||
])
|
||||
|
||||
/**
|
||||
* Schema for peer removal from session.
|
||||
*/
|
||||
export const PeerRemovalSchema = z.union([
|
||||
z.string(),
|
||||
z.object({ id: z.string() }),
|
||||
z.array(z.union([z.string(), z.object({ id: z.string() })])),
|
||||
])
|
||||
|
||||
/**
|
||||
* Schema for message addition to session.
|
||||
*/
|
||||
export const MessageAdditionSchema = z.union([
|
||||
MessageCreateSchema,
|
||||
z.array(MessageCreateSchema),
|
||||
])
|
||||
|
||||
/**
|
||||
* Schema for workspace metadata.
|
||||
*/
|
||||
export const WorkspaceMetadataSchema = z.record(z.string(), z.unknown())
|
||||
|
||||
/**
|
||||
* Schema for limit.
|
||||
*/
|
||||
export const LimitSchema = z
|
||||
.number()
|
||||
.int()
|
||||
.min(1, 'Limit must be a positive integer')
|
||||
.max(100, 'Limit must be less than or equal to 100')
|
||||
|
||||
/**
|
||||
* Type exports for use throughout the SDK.
|
||||
*/
|
||||
export type HonchoConfig = z.infer<typeof HonchoConfigSchema>
|
||||
export type PeerMetadata = z.infer<typeof PeerMetadataSchema>
|
||||
export type PeerConfig = z.infer<typeof PeerConfigSchema>
|
||||
export type SessionMetadata = z.infer<typeof SessionMetadataSchema>
|
||||
export type SessionConfig = z.infer<typeof SessionConfigSchema>
|
||||
export type SessionPeerConfig = z.infer<typeof SessionPeerConfigSchema>
|
||||
export type MessageCreate = z.infer<typeof MessageCreateSchema>
|
||||
export type Filters = z.infer<typeof FilterSchema>
|
||||
export type ChatQuery = z.infer<typeof ChatQuerySchema>
|
||||
export type ContextParams = z.infer<typeof ContextParamsSchema>
|
||||
export type DeriverStatusOptions = z.infer<typeof DeriverStatusOptionsSchema>
|
||||
export type FileUpload = z.infer<typeof FileUploadSchema>
|
||||
export type WorkingRepParams = z.infer<typeof WorkingRepParamsSchema>
|
||||
export type PeerAddition = z.infer<typeof PeerAdditionSchema>
|
||||
export type PeerRemoval = z.infer<typeof PeerRemovalSchema>
|
||||
export type MessageAddition = z.infer<typeof MessageAdditionSchema>
|
||||
export type WorkspaceMetadata = z.infer<typeof WorkspaceMetadataSchema>
|
||||
export type Limit = z.infer<typeof LimitSchema>
|
||||
|
|
@ -15,11 +15,11 @@
|
|||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"baseUrl": "."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,10 +193,6 @@ class DeriverSettings(HonchoSettings):
|
|||
# Thinking budget tokens are only applied when using Anthropic as provider
|
||||
THINKING_BUDGET_TOKENS: Annotated[int, Field(default=1024, gt=0, le=5000)] = 1024
|
||||
|
||||
# Default number of observations to retrieve for each reasoning level
|
||||
DEDUCTIVE_OBSERVATIONS_COUNT: Annotated[int, Field(default=6, gt=0, le=50)] = 6
|
||||
EXPLICIT_OBSERVATIONS_COUNT: Annotated[int, Field(default=10, gt=0, le=50)] = 10
|
||||
|
||||
|
||||
class DialecticSettings(HonchoSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="DIALECTIC_", extra="ignore") # pyright: ignore
|
||||
|
|
@ -244,8 +240,6 @@ class AppSettings(HonchoSettings):
|
|||
|
||||
# Application-wide settings
|
||||
LOG_LEVEL: str = "INFO"
|
||||
FASTAPI_HOST: str = "0.0.0.0"
|
||||
FASTAPI_PORT: Annotated[int, Field(default=8000, gt=0, le=65_535)] = 8000
|
||||
SESSION_PEERS_LIMIT: Annotated[int, Field(default=10, gt=0)] = 10
|
||||
MAX_FILE_SIZE: Annotated[int, Field(default=5_242_880, gt=0)] = 5_242_880 # 5MB
|
||||
GET_CONTEXT_MAX_TOKENS: Annotated[int, Field(default=100_000, gt=0, le=250_000)] = (
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from .message import (
|
|||
get_message_seq_in_session,
|
||||
get_messages,
|
||||
get_messages_id_range,
|
||||
search,
|
||||
update_message,
|
||||
)
|
||||
from .peer import (
|
||||
|
|
@ -56,7 +55,6 @@ __all__ = [
|
|||
"get_message",
|
||||
"get_message_seq_in_session",
|
||||
"update_message",
|
||||
"search",
|
||||
# Peer
|
||||
"get_or_create_peers",
|
||||
"get_peer",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from src import models, schemas
|
||||
from src.config import settings
|
||||
from src.embedding_client import embedding_client
|
||||
from src.exceptions import DisabledException, ValidationException
|
||||
from src.utils.filter import apply_filter
|
||||
|
||||
from .session import get_or_create_session
|
||||
|
|
@ -314,141 +313,3 @@ async def update_message(
|
|||
await db.commit()
|
||||
# await db.refresh(honcho_message)
|
||||
return honcho_message
|
||||
|
||||
|
||||
async def search(
|
||||
query: str,
|
||||
*,
|
||||
workspace_name: str,
|
||||
session_name: str | None = None,
|
||||
peer_name: str | None = None,
|
||||
semantic: bool | None = None,
|
||||
) -> Select[tuple[models.Message]]:
|
||||
"""
|
||||
Search across message content using a hybrid approach:
|
||||
- Uses semantic search if embed_messages is set, else fall back to full text
|
||||
- Uses PostgreSQL full text search for natural language queries
|
||||
- Falls back to exact string matching for queries with special characters
|
||||
- Optionally uses semantic search with embeddings
|
||||
|
||||
If a session or peer is provided, the search will be scoped to that
|
||||
session or peer. Otherwise, it will search across all messages in the workspace.
|
||||
|
||||
Args:
|
||||
query: Search query to match against message content
|
||||
workspace_name: Name of the workspace
|
||||
session_name: Optional name of the session
|
||||
peer_name: Optional name of the peer
|
||||
semantic: Optional boolean to configure semantic search:
|
||||
- None: try semantic search if embed_messages is set, else fall back to full text
|
||||
- True: try semantic search if embed_messages is set, else throw error
|
||||
- False: use full text search
|
||||
|
||||
Returns:
|
||||
List of messages that match the search query, ordered by relevance
|
||||
"""
|
||||
import re
|
||||
|
||||
from sqlalchemy import func, or_
|
||||
|
||||
# Base query conditions
|
||||
base_conditions = [models.Message.workspace_name == workspace_name]
|
||||
|
||||
should_use_semantic_search = False # Default to full text search
|
||||
|
||||
if semantic is None:
|
||||
# Try semantic search if embed_messages is set, else fall back to full text
|
||||
should_use_semantic_search = settings.EMBED_MESSAGES
|
||||
elif semantic is True:
|
||||
# Try semantic search if embed_messages is set, else throw error
|
||||
if settings.EMBED_MESSAGES:
|
||||
should_use_semantic_search = True
|
||||
else:
|
||||
raise DisabledException(
|
||||
"Semantic search requires EMBED_MESSAGES flag to be enabled"
|
||||
)
|
||||
|
||||
if should_use_semantic_search:
|
||||
# Generate embedding for the search query
|
||||
try:
|
||||
embedding_query = await embedding_client.embed(query)
|
||||
except ValueError as e:
|
||||
raise ValidationException(
|
||||
f"Query exceeds maximum token limit of {settings.MAX_EMBEDDING_TOKENS}."
|
||||
) from e
|
||||
|
||||
# Use cosine distance for semantic search on MessageEmbedding table
|
||||
# Join with Message table to get the actual message data
|
||||
base_query = (
|
||||
select(models.Message)
|
||||
.join(
|
||||
models.MessageEmbedding,
|
||||
models.Message.public_id == models.MessageEmbedding.message_id,
|
||||
)
|
||||
.where(models.MessageEmbedding.workspace_name == workspace_name)
|
||||
.order_by(
|
||||
models.MessageEmbedding.embedding.cosine_distance(embedding_query)
|
||||
)
|
||||
)
|
||||
|
||||
if session_name is not None:
|
||||
stmt = base_query.where(
|
||||
models.MessageEmbedding.session_name == session_name
|
||||
)
|
||||
elif peer_name is not None:
|
||||
stmt = base_query.where(models.MessageEmbedding.peer_name == peer_name)
|
||||
else:
|
||||
stmt = base_query
|
||||
|
||||
else:
|
||||
# Check if query contains special characters that FTS might not handle well
|
||||
has_special_chars = bool(
|
||||
re.search(r'[~`!@#$%^&*()_+=\[\]{};\':"\\|,.<>/?-]', query)
|
||||
)
|
||||
|
||||
if has_special_chars:
|
||||
# For queries with special characters, use exact string matching (ILIKE)
|
||||
# This ensures we can find exact matches like "~special-uuid~"
|
||||
search_condition = models.Message.content.ilike(f"%{query}%")
|
||||
|
||||
base_query = (
|
||||
select(models.Message)
|
||||
.where(*base_conditions, search_condition)
|
||||
.order_by(models.Message.created_at.desc())
|
||||
)
|
||||
else:
|
||||
# For natural language queries, use full text search with ranking
|
||||
fts_condition = func.to_tsvector("english", models.Message.content).op(
|
||||
"@@"
|
||||
)(func.plainto_tsquery("english", query))
|
||||
|
||||
# Combine FTS with ILIKE as fallback for better coverage
|
||||
combined_condition = or_(
|
||||
fts_condition, models.Message.content.ilike(f"%{query}%")
|
||||
)
|
||||
|
||||
base_query = (
|
||||
select(models.Message)
|
||||
.where(*base_conditions, combined_condition)
|
||||
.order_by(
|
||||
# Order by FTS relevance first, then by creation time
|
||||
func.coalesce(
|
||||
func.ts_rank(
|
||||
func.to_tsvector("english", models.Message.content),
|
||||
func.plainto_tsquery("english", query),
|
||||
),
|
||||
0,
|
||||
).desc(),
|
||||
models.Message.created_at.desc(),
|
||||
)
|
||||
)
|
||||
|
||||
# Add additional filters based on parameters
|
||||
if session_name is not None:
|
||||
stmt = base_query.where(models.Message.session_name == session_name)
|
||||
elif peer_name is not None:
|
||||
stmt = base_query.where(models.Message.peer_name == peer_name)
|
||||
else:
|
||||
stmt = base_query
|
||||
|
||||
return stmt
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ app = FastAPI(
|
|||
title="Honcho API",
|
||||
summary="The Identity Layer for the Agentic World",
|
||||
description="""Honcho is a platform for giving agents user-centric memory and social cognition""",
|
||||
version="2.1.2",
|
||||
version="2.2.0",
|
||||
contact={
|
||||
"name": "Plastic Labs",
|
||||
"url": "https://honcho.dev",
|
||||
|
|
|
|||
|
|
@ -154,8 +154,8 @@ async def get_messages(
|
|||
"""Get all messages for a session"""
|
||||
try:
|
||||
filters = None
|
||||
if options and hasattr(options, "filter"):
|
||||
filters = options.filter
|
||||
if options and hasattr(options, "filters"):
|
||||
filters = options.filters
|
||||
if filters == {}:
|
||||
filters = None
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from src.dependencies import db
|
|||
from src.dialectic import chat as dialectic_chat
|
||||
from src.exceptions import AuthenticationException, ResourceNotFoundException
|
||||
from src.security import JWTParams, require_auth
|
||||
from src.utils.search import search
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -42,8 +43,8 @@ async def get_peers(
|
|||
):
|
||||
"""Get All Peers for a Workspace"""
|
||||
filter_param = None
|
||||
if options and hasattr(options, "filter"):
|
||||
filter_param = options.filter
|
||||
if options and hasattr(options, "filters"):
|
||||
filter_param = options.filters
|
||||
if filter_param == {}:
|
||||
filter_param = None
|
||||
|
||||
|
|
@ -67,7 +68,7 @@ async def get_or_create_peer(
|
|||
Get a Peer by ID
|
||||
|
||||
If peer_id is provided as a query parameter, it uses that (must match JWT workspace_id).
|
||||
Otherwise, it uses the peer_id from the JWT token.
|
||||
Otherwise, it uses the peer_id from the JWT.
|
||||
"""
|
||||
# validate workspace query param
|
||||
if not jwt_params.ad and jwt_params.w is not None and jwt_params.w != workspace_id:
|
||||
|
|
@ -125,8 +126,8 @@ async def get_sessions_for_peer(
|
|||
"""Get All Sessions for a Peer"""
|
||||
filter_param = None
|
||||
|
||||
if options and hasattr(options, "filter"):
|
||||
filter_param = options.filter
|
||||
if options and hasattr(options, "filters"):
|
||||
filter_param = options.filters
|
||||
if filter_param == {}:
|
||||
filter_param = None
|
||||
|
||||
|
|
@ -239,7 +240,7 @@ async def get_working_representation(
|
|||
|
||||
@router.post(
|
||||
"/{peer_id}/search",
|
||||
response_model=Page[schemas.Message],
|
||||
response_model=list[schemas.Message],
|
||||
dependencies=[
|
||||
Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id"))
|
||||
],
|
||||
|
|
@ -247,17 +248,14 @@ async def get_working_representation(
|
|||
async def search_peer(
|
||||
workspace_id: str = Path(..., description="ID of the workspace"),
|
||||
peer_id: str = Path(..., description="ID of the peer"),
|
||||
search: schemas.MessageSearchOptions = Body(
|
||||
body: schemas.MessageSearchOptions = Body(
|
||||
..., description="Message search parameters "
|
||||
),
|
||||
db: AsyncSession = db,
|
||||
):
|
||||
"""Search a Peer"""
|
||||
stmt = await crud.search(
|
||||
search.query,
|
||||
workspace_name=workspace_id,
|
||||
peer_name=peer_id,
|
||||
semantic=search.semantic,
|
||||
)
|
||||
|
||||
return await apaginate(db, stmt)
|
||||
# take user-provided filter and add workspace_id and peer_id to it
|
||||
filters = body.filters or {}
|
||||
filters["workspace_id"] = workspace_id
|
||||
filters["peer_id"] = peer_id
|
||||
return await search(db, body.query, filters=filters, limit=body.limit)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from src.exceptions import (
|
|||
)
|
||||
from src.security import JWTParams, require_auth
|
||||
from src.utils import summarizer
|
||||
from src.utils.search import search
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ async def get_or_create_session(
|
|||
Get a specific session in a workspace.
|
||||
|
||||
If session_id is provided as a query parameter, it verifies the session is in the workspace.
|
||||
Otherwise, it uses the session_id from the JWT token for verification.
|
||||
Otherwise, it uses the session_id from the JWT for verification.
|
||||
"""
|
||||
# Verify JWT has access to the requested resource
|
||||
if not jwt_params.ad and jwt_params.w is not None and jwt_params.w != workspace_id:
|
||||
|
|
@ -85,8 +86,8 @@ async def get_sessions(
|
|||
"""Get All Sessions in a Workspace"""
|
||||
filter_param = None
|
||||
|
||||
if options and hasattr(options, "filter") and options.filter:
|
||||
filter_param = options.filter
|
||||
if options and hasattr(options, "filters") and options.filters:
|
||||
filter_param = options.filters
|
||||
if filter_param == {}: # Explicitly check for empty dict
|
||||
filter_param = None
|
||||
|
||||
|
|
@ -448,7 +449,7 @@ async def get_session_context(
|
|||
|
||||
@router.post(
|
||||
"/{session_id}/search",
|
||||
response_model=Page[schemas.Message],
|
||||
response_model=list[schemas.Message],
|
||||
dependencies=[
|
||||
Depends(require_auth(workspace_name="workspace_id", session_name="session_id"))
|
||||
],
|
||||
|
|
@ -456,19 +457,19 @@ async def get_session_context(
|
|||
async def search_session(
|
||||
workspace_id: str = Path(..., description="ID of the workspace"),
|
||||
session_id: str = Path(..., description="ID of the session"),
|
||||
search: schemas.MessageSearchOptions = Body(
|
||||
..., description="Message search parameters "
|
||||
body: schemas.MessageSearchOptions = Body(
|
||||
..., description="Message search parameters"
|
||||
),
|
||||
db: AsyncSession = db,
|
||||
):
|
||||
"""Search a Session"""
|
||||
query, semantic = search.query, search.semantic
|
||||
|
||||
stmt = await crud.search(
|
||||
query,
|
||||
workspace_name=workspace_id,
|
||||
session_name=session_id,
|
||||
semantic=semantic,
|
||||
# take user-provided filter and add workspace_id and session_id to it
|
||||
filters = body.filters or {}
|
||||
filters["workspace_id"] = workspace_id
|
||||
filters["session_id"] = session_id
|
||||
return await search(
|
||||
db,
|
||||
body.query,
|
||||
filters=filters,
|
||||
limit=body.limit,
|
||||
)
|
||||
|
||||
return await apaginate(db, stmt)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from src import crud, schemas
|
|||
from src.dependencies import db
|
||||
from src.exceptions import AuthenticationException
|
||||
from src.security import JWTParams, require_auth
|
||||
from src.utils.search import search
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ async def get_or_create_workspace(
|
|||
Get a Workspace by ID.
|
||||
|
||||
If workspace_id is provided as a query parameter, it uses that (must match JWT workspace_id).
|
||||
Otherwise, it uses the workspace_id from the JWT token.
|
||||
Otherwise, it uses the workspace_id from the JWT.
|
||||
"""
|
||||
# If workspace_id provided in query, check if it matches jwt or user is admin
|
||||
if workspace.name:
|
||||
|
|
@ -60,8 +61,8 @@ async def get_all_workspaces(
|
|||
):
|
||||
"""Get all Workspaces"""
|
||||
filter_param = None
|
||||
if options and hasattr(options, "filter"):
|
||||
filter_param = options.filter
|
||||
if options and hasattr(options, "filters"):
|
||||
filter_param = options.filters
|
||||
if filter_param == {}:
|
||||
filter_param = None
|
||||
|
||||
|
|
@ -93,18 +94,21 @@ async def update_workspace(
|
|||
|
||||
@router.post(
|
||||
"/{workspace_id}/search",
|
||||
response_model=Page[schemas.Message],
|
||||
response_model=list[schemas.Message],
|
||||
dependencies=[Depends(require_auth(workspace_name="workspace_id"))],
|
||||
)
|
||||
async def search_workspace(
|
||||
workspace_id: str = Path(..., description="ID of the workspace to search"),
|
||||
query: str = Body(..., description="Search query"),
|
||||
body: schemas.MessageSearchOptions = Body(
|
||||
..., description="Message search parameters "
|
||||
),
|
||||
db: AsyncSession = db,
|
||||
):
|
||||
"""Search a Workspace"""
|
||||
stmt = await crud.search(query, workspace_name=workspace_id)
|
||||
|
||||
return await apaginate(db, stmt)
|
||||
# take user-provided filter and add workspace_id to it
|
||||
filters = body.filters or {}
|
||||
filters["workspace_id"] = workspace_id
|
||||
return await search(db, body.query, filters=filters, limit=body.limit)
|
||||
|
||||
|
||||
@router.get(
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class WorkspaceCreate(WorkspaceBase):
|
|||
|
||||
|
||||
class WorkspaceGet(WorkspaceBase):
|
||||
filter: dict[str, Any] | None = None
|
||||
filters: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class WorkspaceUpdate(WorkspaceBase):
|
||||
|
|
@ -67,7 +67,7 @@ class PeerCreate(PeerBase):
|
|||
|
||||
|
||||
class PeerGet(PeerBase):
|
||||
filter: dict[str, Any] | None = None
|
||||
filters: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class PeerUpdate(PeerBase):
|
||||
|
|
@ -131,7 +131,7 @@ class MessageCreate(MessageBase):
|
|||
|
||||
|
||||
class MessageGet(MessageBase):
|
||||
filter: dict[str, Any] | None = None
|
||||
filters: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class MessageUpdate(MessageBase):
|
||||
|
|
@ -197,7 +197,7 @@ class SessionCreate(SessionBase):
|
|||
|
||||
|
||||
class SessionGet(SessionBase):
|
||||
filter: dict[str, Any] | None = None
|
||||
filters: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class SessionUpdate(SessionBase):
|
||||
|
|
@ -246,9 +246,14 @@ class DocumentUpdate(DocumentBase):
|
|||
|
||||
class MessageSearchOptions(BaseModel):
|
||||
query: str = Field(..., description="Search query")
|
||||
semantic: bool | None = Field(
|
||||
default=None,
|
||||
description="Whether to explicitly use semantic search to filter the results",
|
||||
filters: dict[str, Any] | None = Field(
|
||||
default=None, description="Filters to scope the search"
|
||||
)
|
||||
limit: int = Field(
|
||||
default=10,
|
||||
ge=1,
|
||||
le=100,
|
||||
description="Number of results to return",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ def create_admin_jwt() -> str:
|
|||
|
||||
|
||||
def create_jwt(params: JWTParams) -> str:
|
||||
"""Create a JWT token from the given parameters."""
|
||||
"""Create a JWT from the given parameters."""
|
||||
payload = {k: v for k, v in params.__dict__.items() if v is not None}
|
||||
if not settings.AUTH.JWT_SECRET:
|
||||
raise ValueError("AUTH_JWT_SECRET is not set, cannot create JWT.")
|
||||
|
|
@ -80,7 +80,7 @@ def create_jwt(params: JWTParams) -> str:
|
|||
|
||||
|
||||
async def verify_jwt(token: str) -> JWTParams:
|
||||
"""Verify a JWT token and return the decoded parameters."""
|
||||
"""Verify a JWT and return the decoded parameters."""
|
||||
|
||||
params = JWTParams()
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -33,29 +33,6 @@ class EmbeddingStore:
|
|||
self.workspace_name: str = workspace_name
|
||||
self.peer_name: str = peer_name
|
||||
self.collection_name: str = collection_name
|
||||
# Initialize observation counts with config defaults
|
||||
self.explicit_observations_count: int = (
|
||||
settings.DERIVER.EXPLICIT_OBSERVATIONS_COUNT
|
||||
)
|
||||
self.deductive_observations_count: int = (
|
||||
settings.DERIVER.DEDUCTIVE_OBSERVATIONS_COUNT
|
||||
)
|
||||
|
||||
def set_observation_counts(
|
||||
self,
|
||||
explicit: int | None = None,
|
||||
deductive: int | None = None,
|
||||
) -> None:
|
||||
"""Set the number of observations to retrieve for each reasoning level.
|
||||
|
||||
Args:
|
||||
explicit: Number of explicit observations to retrieve
|
||||
deductive: Number of deductive observations to retrieve
|
||||
"""
|
||||
if explicit is not None:
|
||||
self.explicit_observations_count = explicit
|
||||
if deductive is not None:
|
||||
self.deductive_observations_count = deductive
|
||||
|
||||
@conditional_observe
|
||||
async def save_unified_observations(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
"""
|
||||
Reciprocal Rank Fusion (RRF) utilities for combining search results.
|
||||
|
||||
RRF is a method to combine multiple ranked lists by computing the reciprocal
|
||||
of each item's rank in each list, then summing these reciprocal ranks.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Any, TypeVar
|
||||
|
||||
from sqlalchemy import Select, func, or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src import models
|
||||
from src.config import settings
|
||||
from src.embedding_client import embedding_client
|
||||
from src.exceptions import ValidationException
|
||||
from src.utils.filter import apply_filter
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def reciprocal_rank_fusion(*ranked_lists: list[T], k: int = 60, limit: int) -> list[T]:
|
||||
"""
|
||||
Combine multiple ranked lists using Reciprocal Rank Fusion (RRF).
|
||||
|
||||
RRF assigns a score to each item based on the formula:
|
||||
RRF_score = sum(1 / (k + rank_i)) for all lists where the item appears
|
||||
|
||||
Where:
|
||||
- k is a constant (typically 60) that controls the impact of high-ranked items
|
||||
- rank_i is the rank of the item in list i (1-indexed)
|
||||
|
||||
Args:
|
||||
*ranked_lists: Variable number of ranked lists to combine
|
||||
k: RRF constant parameter (default: 60)
|
||||
limit: Maximum number of results to return
|
||||
|
||||
Returns:
|
||||
list of items ranked by RRF score (highest score first)
|
||||
"""
|
||||
if not ranked_lists:
|
||||
return []
|
||||
|
||||
# dictionary to store RRF scores for each item
|
||||
rrf_scores: dict[T, float] = {}
|
||||
|
||||
# Process each ranked list
|
||||
for ranked_list in ranked_lists:
|
||||
for rank, item in enumerate(ranked_list, 1): # 1-indexed ranking
|
||||
if item not in rrf_scores:
|
||||
rrf_scores[item] = 0.0
|
||||
# Add reciprocal rank contribution from this list
|
||||
rrf_scores[item] += 1.0 / (k + rank)
|
||||
|
||||
# Sort items by RRF score (descending order)
|
||||
sorted_items = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
# Extract just the items (not the scores)
|
||||
result = [item for item, _ in sorted_items]
|
||||
|
||||
return result[:limit]
|
||||
|
||||
|
||||
async def _semantic_search(
|
||||
db: AsyncSession,
|
||||
query: str,
|
||||
stmt: Select[tuple[models.Message]],
|
||||
limit: int,
|
||||
) -> list[models.Message]:
|
||||
"""
|
||||
Perform semantic search using message embeddings.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
query: Search query
|
||||
stmt: Base SQL query conditions
|
||||
limit: Maximum number of results to return
|
||||
|
||||
Returns:
|
||||
list of messages ordered by semantic similarity
|
||||
"""
|
||||
try:
|
||||
embedding_query = await embedding_client.embed(query)
|
||||
except ValueError as e:
|
||||
raise ValidationException(
|
||||
f"Query exceeds maximum token limit of {settings.MAX_EMBEDDING_TOKENS}."
|
||||
) from e
|
||||
|
||||
# Use cosine distance for semantic search on MessageEmbedding table
|
||||
semantic_query = stmt.join(
|
||||
models.MessageEmbedding,
|
||||
models.Message.public_id == models.MessageEmbedding.message_id,
|
||||
).order_by(models.MessageEmbedding.embedding.cosine_distance(embedding_query))
|
||||
|
||||
semantic_query = semantic_query.limit(limit)
|
||||
|
||||
result = await db.execute(semantic_query)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def _fulltext_search(
|
||||
db: AsyncSession,
|
||||
query: str,
|
||||
stmt: Select[tuple[models.Message]],
|
||||
limit: int,
|
||||
) -> list[models.Message]:
|
||||
"""
|
||||
Perform full-text search using PostgreSQL FTS and ILIKE fallback.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
query: Search query
|
||||
stmt: Base SQL query conditions
|
||||
limit: Maximum number of results to return
|
||||
|
||||
Returns:
|
||||
list of messages ordered by text search relevance
|
||||
"""
|
||||
# Check if query contains special characters that FTS might not handle well
|
||||
has_special_chars = bool(
|
||||
re.search(r'[~`!@#$%^&*()_+=\[\]{};\':"\\|,.<>/?-]', query)
|
||||
)
|
||||
|
||||
if has_special_chars:
|
||||
# For queries with special characters, use exact string matching (ILIKE)
|
||||
search_condition = models.Message.content.ilike(f"%{query}%")
|
||||
fulltext_query = stmt.where(search_condition).order_by(
|
||||
models.Message.created_at.desc()
|
||||
)
|
||||
else:
|
||||
# For natural language queries, use full text search with ranking
|
||||
fts_condition = func.to_tsvector("english", models.Message.content).op("@@")(
|
||||
func.plainto_tsquery("english", query)
|
||||
)
|
||||
|
||||
# Combine FTS with ILIKE as fallback for better coverage
|
||||
combined_condition = or_(
|
||||
fts_condition, models.Message.content.ilike(f"%{query}%")
|
||||
)
|
||||
|
||||
fulltext_query = stmt.where(combined_condition).order_by(
|
||||
# Order by FTS relevance first, then by creation time
|
||||
func.coalesce(
|
||||
func.ts_rank(
|
||||
func.to_tsvector("english", models.Message.content),
|
||||
func.plainto_tsquery("english", query),
|
||||
),
|
||||
0,
|
||||
).desc(),
|
||||
models.Message.created_at.desc(),
|
||||
)
|
||||
|
||||
fulltext_query = fulltext_query.limit(limit)
|
||||
|
||||
result = await db.execute(fulltext_query)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def search(
|
||||
db: AsyncSession,
|
||||
query: str,
|
||||
*,
|
||||
filters: dict[str, Any] | None = None,
|
||||
limit: int = 10,
|
||||
) -> list[models.Message]:
|
||||
"""
|
||||
Search across message content using a hybrid approach with Reciprocal Rank Fusion (RRF).
|
||||
|
||||
This function combines semantic search and full-text search results using RRF when both
|
||||
are available, providing better search results than either method alone.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
query: Search query to match against message content
|
||||
filters: Optional filters to scope search
|
||||
limit: Maximum number of results to return
|
||||
|
||||
Returns:
|
||||
list of messages that match the search query, ordered by RRF relevance or individual search relevance
|
||||
|
||||
Raises:
|
||||
ValidationException: If query exceeds maximum token limit for embeddings
|
||||
"""
|
||||
# Base query conditions
|
||||
stmt = select(models.Message)
|
||||
stmt = apply_filter(stmt, models.Message, filters)
|
||||
|
||||
search_results: list[list[models.Message]] = []
|
||||
|
||||
# Perform semantic search if enabled
|
||||
if settings.EMBED_MESSAGES:
|
||||
# Get more results for fusion
|
||||
semantic_limit = limit * 2
|
||||
semantic_results = await _semantic_search(
|
||||
db=db, query=query, stmt=stmt, limit=semantic_limit
|
||||
)
|
||||
search_results.append(semantic_results)
|
||||
|
||||
# Perform full-text search
|
||||
# Get more results for fusion
|
||||
fulltext_limit = limit * 2
|
||||
fulltext_results = await _fulltext_search(
|
||||
db=db, query=query, stmt=stmt, limit=fulltext_limit
|
||||
)
|
||||
search_results.append(fulltext_results)
|
||||
|
||||
# Combine results using RRF if we have multiple search methods
|
||||
if len(search_results) > 1:
|
||||
# Use RRF to combine semantic and full-text results
|
||||
combined_results = reciprocal_rank_fusion(*search_results, limit=limit)
|
||||
elif len(search_results) == 1:
|
||||
# Single search method - apply limit directly
|
||||
combined_results = search_results[0]
|
||||
combined_results = combined_results[:limit]
|
||||
else:
|
||||
# No search results
|
||||
combined_results = []
|
||||
|
||||
return combined_results
|
||||
|
|
@ -62,10 +62,11 @@ class TestSessionCRUD:
|
|||
assert len(configurations) == 2
|
||||
|
||||
# Verify the structure of returned data
|
||||
for peer_name, peer_config, session_peer_config in configurations:
|
||||
for peer_name, peer_config, session_peer_config, is_active in configurations:
|
||||
assert isinstance(peer_name, str)
|
||||
assert isinstance(peer_config, dict) or peer_config is None
|
||||
assert isinstance(session_peer_config, dict)
|
||||
assert isinstance(is_active, bool)
|
||||
|
||||
# Check that session_peer_config matches what we set
|
||||
expected_config = peer_configs[peer_name]
|
||||
|
|
|
|||
|
|
@ -1035,9 +1035,9 @@ class TestGetEffectiveObserveMeFunction:
|
|||
sender_name = f"sender_{i}"
|
||||
|
||||
result = get_effective_observe_me(sender_name, peers_with_configuration)
|
||||
assert result == expected, (
|
||||
f"Test case {i} failed: peer_config={peer_config}, session_config={session_config}, expected={expected}, got={result}"
|
||||
)
|
||||
assert (
|
||||
result == expected
|
||||
), f"Test case {i} failed: peer_config={peer_config}, session_config={session_config}, expected={expected}, got={result}"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src import models
|
||||
from src.crud import create_messages, search
|
||||
from src.crud import create_messages
|
||||
from src.models import Peer, Workspace
|
||||
from src.schemas import MessageCreate
|
||||
from src.utils.search import search
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -243,21 +244,21 @@ async def test_semantic_search_when_embeddings_enabled(
|
|||
# Check the call count before search
|
||||
initial_call_count: int = mock_openai_embeddings["embed"].call_count
|
||||
|
||||
search_stmt = await search(
|
||||
search_results = await search(
|
||||
db=db_session,
|
||||
query=search_query,
|
||||
workspace_name=test_workspace.name,
|
||||
session_name=test_session.name,
|
||||
filters={
|
||||
"workspace_id": test_workspace.name,
|
||||
"session_id": test_session.name,
|
||||
},
|
||||
)
|
||||
|
||||
# Verify that the embed method was called during search - e.g. we used semantic search
|
||||
assert mock_openai_embeddings["embed"].call_count == initial_call_count + 1
|
||||
|
||||
search_result = await db_session.execute(search_stmt)
|
||||
found_messages = list(search_result.scalars().all())
|
||||
|
||||
# Verify that our message was found via semantic search
|
||||
assert len(found_messages) > 0
|
||||
found_message_ids = [msg.public_id for msg in found_messages]
|
||||
assert len(search_results) > 0
|
||||
found_message_ids = [msg.public_id for msg in search_results]
|
||||
assert created_message.public_id in found_message_ids
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ async def test_get_messages_with_empty_filter(
|
|||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list",
|
||||
json={"filter": {}},
|
||||
json={"filters": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -304,7 +304,7 @@ async def test_get_messages_with_null_filter(
|
|||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list",
|
||||
json={"filter": None},
|
||||
json={"filters": None},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -377,7 +377,7 @@ async def test_get_filtered_messages(
|
|||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list",
|
||||
json={"filter": {"metadata": {"key": "value2"}}},
|
||||
json={"filters": {"metadata": {"key": "value2"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -432,7 +432,7 @@ async def test_get_filtered_messages_with_complex_filter(
|
|||
# Test old-style filter (backward compatibility)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list",
|
||||
json={"filter": {"metadata": {"priority": "high", "category": "technical"}}},
|
||||
json={"filters": {"metadata": {"priority": "high", "category": "technical"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -444,7 +444,7 @@ async def test_get_filtered_messages_with_complex_filter(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"metadata": {"priority": "high"}},
|
||||
{"metadata": {"category": "technical"}},
|
||||
|
|
@ -461,7 +461,7 @@ async def test_get_filtered_messages_with_complex_filter(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"OR": [
|
||||
{"metadata": {"priority": "high"}},
|
||||
{"metadata": {"type": "question"}},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import pytest
|
||||
from typing import Any
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from nanoid import generate as generate_nanoid
|
||||
|
||||
|
|
@ -114,7 +115,7 @@ def test_get_peers(client: TestClient, sample_data: tuple[Workspace, Peer]):
|
|||
# Get peers with simple filter (backward compatibility)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"peer_key": "peer_value"}}},
|
||||
json={"filters": {"metadata": {"peer_key": "peer_value"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -125,7 +126,7 @@ def test_get_peers(client: TestClient, sample_data: tuple[Workspace, Peer]):
|
|||
# Test new filter with NOT operator
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"NOT": [{"metadata": {"peer_key": "peer_value2"}}]}},
|
||||
json={"filters": {"NOT": [{"metadata": {"peer_key": "peer_value2"}}]}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -142,7 +143,7 @@ def test_get_peers_with_empty_filter(
|
|||
test_workspace, _ = sample_data
|
||||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filter": {}}
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filters": {}}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -157,7 +158,7 @@ def test_get_peers_with_null_filter(
|
|||
test_workspace, _ = sample_data
|
||||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filter": None}
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filters": None}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -296,7 +297,7 @@ def test_get_sessions_for_peer_with_empty_filter(
|
|||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/sessions",
|
||||
json={"filter": {}},
|
||||
json={"filters": {}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -392,17 +393,13 @@ def test_search_peer(client: TestClient, sample_data: tuple[Workspace, Peer]):
|
|||
# Search with a query
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/search",
|
||||
json={"query": "search query"},
|
||||
json={"query": "search query", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should have pagination structure
|
||||
assert "items" in data
|
||||
assert "total" in data
|
||||
assert "page" in data
|
||||
assert "size" in data
|
||||
assert isinstance(data["items"], list)
|
||||
# Response should be a direct list of messages
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
def test_search_peer_empty_query(
|
||||
|
|
@ -414,14 +411,13 @@ def test_search_peer_empty_query(
|
|||
# Search with empty query
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/search",
|
||||
json={"query": ""},
|
||||
json={"query": "", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should still have proper pagination structure
|
||||
assert "items" in data
|
||||
assert isinstance(data["items"], list)
|
||||
# Response should be a direct list of messages
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
def test_search_peer_nonexistent(
|
||||
|
|
@ -433,16 +429,18 @@ def test_search_peer_nonexistent(
|
|||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/{nonexistent_peer_id}/search",
|
||||
json={"query": "test query"},
|
||||
json={"query": "test query", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["items"] == []
|
||||
data: list[dict[str, Any]] = response.json()
|
||||
assert isinstance(data, list)
|
||||
assert len(data) == 0
|
||||
|
||||
|
||||
def test_search_peer_with_semantic_search_false(
|
||||
def test_search_peer_with_messages(
|
||||
client: TestClient, sample_data: tuple[Workspace, Peer]
|
||||
):
|
||||
"""Test peer search with semantic=false"""
|
||||
"""Test peer search with actual messages"""
|
||||
test_workspace, test_peer = sample_data
|
||||
|
||||
# Add some messages to search through
|
||||
|
|
@ -456,31 +454,22 @@ def test_search_peer_with_semantic_search_false(
|
|||
},
|
||||
)
|
||||
|
||||
# Search with semantic=false
|
||||
# Search for content
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/search",
|
||||
json={"query": "search", "semantic": False},
|
||||
json={"query": "search", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should have pagination structure
|
||||
assert "items" in data
|
||||
assert "total" in data
|
||||
assert "page" in data
|
||||
assert "size" in data
|
||||
assert isinstance(data["items"], list)
|
||||
# Response should be a direct list of messages
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
def test_search_peer_with_semantic_search_true_disabled(
|
||||
client: TestClient,
|
||||
sample_data: tuple[Workspace, Peer],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
def test_search_peer_with_limit(
|
||||
client: TestClient, sample_data: tuple[Workspace, Peer]
|
||||
):
|
||||
"""Test peer search with semantic=true when EMBED_MESSAGES is disabled"""
|
||||
# Override the EMBED_MESSAGES setting to False for this test
|
||||
monkeypatch.setattr("src.config.settings.EMBED_MESSAGES", False)
|
||||
|
||||
"""Test peer search with custom limit"""
|
||||
test_workspace, test_peer = sample_data
|
||||
|
||||
# Add some messages to search through
|
||||
|
|
@ -490,22 +479,22 @@ def test_search_peer_with_semantic_search_true_disabled(
|
|||
"messages": [
|
||||
{"content": "Search this content", "peer_id": test_peer.name},
|
||||
{"content": "Another searchable message", "peer_id": test_peer.name},
|
||||
{"content": "More searchable content", "peer_id": test_peer.name},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
# Search with semantic=true (should fail if EMBED_MESSAGES is disabled)
|
||||
# Search with custom limit
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/search",
|
||||
json={"query": "search", "semantic": True},
|
||||
json={"query": "search", "limit": 2},
|
||||
)
|
||||
|
||||
assert response.status_code == 405
|
||||
|
||||
data = response.json()
|
||||
assert "Semantic search requires EMBED_MESSAGES flag to be enabled" in data.get(
|
||||
"detail", ""
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data: list[dict[str, Any]] = response.json()
|
||||
assert isinstance(data, list)
|
||||
# Should not exceed the limit
|
||||
assert len(data) <= 2
|
||||
|
||||
|
||||
def test_get_peers_with_complex_filter(
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ def test_get_peer_by_name_with_auth(
|
|||
# Use POST /list endpoint to get peers
|
||||
response = auth_client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"id": test_peer.name}},
|
||||
json={"filters": {"id": test_peer.name}},
|
||||
)
|
||||
|
||||
# Admin JWT or JWT with matching workspace should be allowed
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ def test_create_session_with_too_many_peers(
|
|||
|
||||
session_response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"id": "test_session"}},
|
||||
json={"filters": {"id": "test_session"}},
|
||||
)
|
||||
assert session_response.status_code == 200
|
||||
assert len(session_response.json()["items"]) == 0
|
||||
|
|
@ -186,7 +186,7 @@ def test_get_sessions(client: TestClient, sample_data: tuple[Workspace, Peer]):
|
|||
assert data["workspace_id"] == test_workspace.name
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"metadata": {"test_key": "test_value"}}},
|
||||
json={"filters": {"metadata": {"test_key": "test_value"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -203,7 +203,7 @@ def test_get_sessions_with_empty_filter(
|
|||
test_workspace, _ = sample_data
|
||||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list", json={"filter": {}}
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list", json={"filters": {}}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -356,7 +356,7 @@ def test_delete_session(client: TestClient, sample_data: tuple[Workspace, Peer])
|
|||
# Check that session is marked as inactive
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"is_active": False}},
|
||||
json={"filters": {"is_active": False}},
|
||||
)
|
||||
data = response.json()
|
||||
# Find our session in the inactive sessions
|
||||
|
|
@ -773,17 +773,13 @@ def test_search_session(client: TestClient, sample_data: tuple[Workspace, Peer])
|
|||
# Search with a query
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/search",
|
||||
json={"query": "search query"},
|
||||
json={"query": "search query", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should have pagination structure
|
||||
assert "items" in data
|
||||
assert "total" in data
|
||||
assert "page" in data
|
||||
assert "size" in data
|
||||
assert isinstance(data["items"], list)
|
||||
# Response should be a direct list of messages
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
def test_search_session_empty_query(
|
||||
|
|
@ -802,14 +798,13 @@ def test_search_session_empty_query(
|
|||
# Search with empty query
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/search",
|
||||
json={"query": ""},
|
||||
json={"query": "", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should still have proper pagination structure
|
||||
assert "items" in data
|
||||
assert isinstance(data["items"], list)
|
||||
# Response should be a direct list of messages
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
def test_search_session_nonexistent(
|
||||
|
|
@ -821,23 +816,25 @@ def test_search_session_nonexistent(
|
|||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{nonexistent_session_id}/search",
|
||||
json={"query": "test query"},
|
||||
json={"query": "test query", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["items"] == []
|
||||
data: list[dict[str, Any]] = response.json()
|
||||
assert isinstance(data, list)
|
||||
assert len(data) == 0
|
||||
|
||||
|
||||
def test_search_session_with_semantic_search_false(
|
||||
def test_search_session_with_messages(
|
||||
client: TestClient, sample_data: tuple[Workspace, Peer]
|
||||
):
|
||||
"""Test session search with semantic=false"""
|
||||
"""Test session search with actual messages"""
|
||||
test_workspace, test_peer = sample_data
|
||||
session_id = str(generate_nanoid())
|
||||
|
||||
# Create session
|
||||
client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions",
|
||||
json={"id": session_id, "peer_names": {test_peer.name: {}}},
|
||||
json={"id": session_id, "peers": {test_peer.name: {}}},
|
||||
)
|
||||
|
||||
# Add messages to search through
|
||||
|
|
@ -851,38 +848,29 @@ def test_search_session_with_semantic_search_false(
|
|||
},
|
||||
)
|
||||
|
||||
# Search with semantic=false
|
||||
# Search for content
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/search",
|
||||
json={"query": "search", "semantic": False},
|
||||
json={"query": "search", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should have pagination structure
|
||||
assert "items" in data
|
||||
assert "total" in data
|
||||
assert "page" in data
|
||||
assert "size" in data
|
||||
assert isinstance(data["items"], list)
|
||||
# Response should be a direct list of messages
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
def test_search_session_with_semantic_search_true_disabled(
|
||||
client: TestClient,
|
||||
sample_data: tuple[Workspace, Peer],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
def test_search_session_with_limit(
|
||||
client: TestClient, sample_data: tuple[Workspace, Peer]
|
||||
):
|
||||
"""Test session search with semantic=true when EMBED_MESSAGES is disabled"""
|
||||
# Override the EMBED_MESSAGES setting to False for this test
|
||||
monkeypatch.setattr("src.config.settings.EMBED_MESSAGES", False)
|
||||
|
||||
"""Test session search with custom limit"""
|
||||
test_workspace, test_peer = sample_data
|
||||
session_id = str(generate_nanoid())
|
||||
|
||||
# Create session
|
||||
client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions",
|
||||
json={"id": session_id, "peer_names": {test_peer.name: {}}},
|
||||
json={"id": session_id, "peers": {test_peer.name: {}}},
|
||||
)
|
||||
|
||||
# Add messages to search through
|
||||
|
|
@ -892,19 +880,19 @@ def test_search_session_with_semantic_search_true_disabled(
|
|||
"messages": [
|
||||
{"content": "Search this content", "peer_id": test_peer.name},
|
||||
{"content": "Another message to find", "peer_id": test_peer.name},
|
||||
{"content": "More searchable content", "peer_id": test_peer.name},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
# Search with semantic=true (should fail if EMBED_MESSAGES is disabled)
|
||||
# Search with custom limit
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/search",
|
||||
json={"query": "search", "semantic": True},
|
||||
json={"query": "search", "limit": 2},
|
||||
)
|
||||
|
||||
assert response.status_code == 405
|
||||
|
||||
data = response.json()
|
||||
assert "Semantic search requires EMBED_MESSAGES flag to be enabled" in data.get(
|
||||
"detail", ""
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data: list[dict[str, Any]] = response.json()
|
||||
assert isinstance(data, list)
|
||||
# Should not exceed the limit
|
||||
assert len(data) <= 2
|
||||
|
|
|
|||
|
|
@ -220,9 +220,9 @@ def test_filter_validations_api(
|
|||
# Test invalid filter type in message list (at session level)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": "not a dict"},
|
||||
json={"filters": "not a dict"},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
error = response.json()["detail"][0]
|
||||
assert error["loc"] == ["body", "filter"]
|
||||
assert error["loc"] == ["body", "filters"]
|
||||
assert error["type"] == "dict_type"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from nanoid import generate as generate_nanoid
|
||||
|
|
@ -91,7 +93,7 @@ async def test_get_all_workspaces(client: TestClient):
|
|||
|
||||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={"filter": {"metadata": {"test_key": "test_value"}}},
|
||||
json={"filters": {"metadata": {"test_key": "test_value"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -103,7 +105,7 @@ async def test_get_all_workspaces(client: TestClient):
|
|||
@pytest.mark.asyncio
|
||||
async def test_get_all_workspaces_with_empty_filter(client: TestClient):
|
||||
"""Test workspace listing with empty filter object"""
|
||||
response = client.post("/v2/workspaces/list", json={"filter": {}})
|
||||
response = client.post("/v2/workspaces/list", json={"filters": {}})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "items" in data
|
||||
|
|
@ -113,7 +115,7 @@ async def test_get_all_workspaces_with_empty_filter(client: TestClient):
|
|||
@pytest.mark.asyncio
|
||||
async def test_get_all_workspaces_with_null_filter(client: TestClient):
|
||||
"""Test workspace listing with null filter"""
|
||||
response = client.post("/v2/workspaces/list", json={"filter": None})
|
||||
response = client.post("/v2/workspaces/list", json={"filters": None})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "items" in data
|
||||
|
|
@ -222,17 +224,14 @@ def test_search_workspace(client: TestClient, sample_data: tuple[Workspace, Peer
|
|||
|
||||
# Test search with a query
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/search", json="test search query"
|
||||
f"/v2/workspaces/{test_workspace.name}/search",
|
||||
json={"query": "test search query", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should have pagination structure
|
||||
assert "items" in data
|
||||
assert "total" in data
|
||||
assert "page" in data
|
||||
assert "size" in data
|
||||
assert isinstance(data["items"], list)
|
||||
# Response should be a direct list of messages
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
def test_search_workspace_empty_query(
|
||||
|
|
@ -242,13 +241,14 @@ def test_search_workspace_empty_query(
|
|||
test_workspace, _ = sample_data
|
||||
|
||||
# Test search with empty query
|
||||
response = client.post(f"/v2/workspaces/{test_workspace.name}/search", json="")
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/search", json={"query": "", "limit": 10}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should still have proper pagination structure
|
||||
assert "items" in data
|
||||
assert isinstance(data["items"], list)
|
||||
# Response should be a direct list of messages
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
def test_search_workspace_nonexistent(client: TestClient):
|
||||
|
|
@ -256,6 +256,11 @@ def test_search_workspace_nonexistent(client: TestClient):
|
|||
nonexistent_workspace_id = str(generate_nanoid())
|
||||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{nonexistent_workspace_id}/search", json="test query"
|
||||
f"/v2/workspaces/{nonexistent_workspace_id}/search",
|
||||
json={"query": "test query", "limit": 10},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data: list[dict[str, Any]] = response.json()
|
||||
# Should return empty list for nonexistent workspace
|
||||
assert isinstance(data, list)
|
||||
assert len(data) == 0
|
||||
|
|
|
|||
|
|
@ -172,9 +172,9 @@ async def test_workspace_search(client_fixture: tuple[Honcho | AsyncHoncho, str]
|
|||
await session.add_messages([user.message(search_query)])
|
||||
|
||||
search_results = await honcho_client.search(search_query)
|
||||
results = search_results.items
|
||||
assert len(results) >= 1
|
||||
assert search_query in results[0].content
|
||||
assert isinstance(search_results, list)
|
||||
assert len(search_results) >= 1
|
||||
assert search_query in search_results[0].content
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="search-session-ws")
|
||||
|
|
@ -184,9 +184,9 @@ async def test_workspace_search(client_fixture: tuple[Honcho | AsyncHoncho, str]
|
|||
session.add_messages([user.message(search_query)])
|
||||
|
||||
search_results = honcho_client.search(search_query)
|
||||
results = list(search_results)
|
||||
assert len(results) >= 1
|
||||
assert search_query in results[0].content
|
||||
assert isinstance(search_results, list)
|
||||
assert len(search_results) >= 1
|
||||
assert search_query in search_results[0].content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -257,9 +257,9 @@ async def test_session_search(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
|||
await session.add_messages([user.message(search_query)])
|
||||
|
||||
search_results = await session.search(search_query)
|
||||
results = search_results.items
|
||||
assert len(results) >= 1
|
||||
assert search_query in results[0].content
|
||||
assert isinstance(search_results, list)
|
||||
assert len(search_results) >= 1
|
||||
assert search_query in search_results[0].content
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="search-session-s")
|
||||
|
|
@ -269,9 +269,9 @@ async def test_session_search(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
|||
session.add_messages([user.message(search_query)])
|
||||
|
||||
search_results = session.search(search_query)
|
||||
results = list(search_results)
|
||||
assert len(results) >= 1
|
||||
assert search_query in results[0].content
|
||||
assert isinstance(search_results, list)
|
||||
assert len(search_results) >= 1
|
||||
assert search_query in search_results[0].content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ async def test_logical_operators_and_filters(
|
|||
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": combined_filter},
|
||||
json={"filters": combined_filter},
|
||||
)
|
||||
assert response.status_code == 200, f"Failed testing {description}"
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ async def test_comparison_operators_filters(
|
|||
# Test the filter configuration
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": filter_config},
|
||||
json={"filters": filter_config},
|
||||
)
|
||||
assert response.status_code == 200, f"Failed testing {description}"
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ async def test_wildcard_filters(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"id": "*"},
|
||||
{"metadata": {"type": "bot"}},
|
||||
|
|
@ -277,7 +277,7 @@ async def test_wildcard_filters(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"id": {"in": ["*"]} # Wildcard in comparison should also match all
|
||||
}
|
||||
},
|
||||
|
|
@ -347,11 +347,11 @@ async def test_complex_nested_filters(
|
|||
},
|
||||
)
|
||||
|
||||
# Complex filter: (urgent OR normal priority) AND open status AND NOT assigned to charlie
|
||||
# Complex filters: (urgent OR normal priority) AND open status AND NOT assigned to charlie
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{
|
||||
"OR": [
|
||||
|
|
@ -397,7 +397,7 @@ async def test_filters_across_different_models(
|
|||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"metadata": {"environment": "production"}},
|
||||
{"metadata": {"version": {"gte": "2.0"}}},
|
||||
|
|
@ -434,7 +434,7 @@ async def test_filters_across_different_models(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"OR": [
|
||||
{"metadata": {"duration": {"gte": 45}}},
|
||||
{"metadata": {"type": "meeting"}},
|
||||
|
|
@ -460,7 +460,7 @@ async def test_filter_edge_cases(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [] # Empty AND should not crash
|
||||
}
|
||||
},
|
||||
|
|
@ -470,14 +470,14 @@ async def test_filter_edge_cases(
|
|||
# Test nested empty operators
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"OR": [{"AND": []}, {"id": test_peer.name}]}},
|
||||
json={"filters": {"OR": [{"AND": []}, {"id": test_peer.name}]}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Test filter with non-existent columns (should be ignored gracefully)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"non_existent_column": "value"}},
|
||||
json={"filters": {"non_existent_column": "value"}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
|
@ -485,7 +485,7 @@ async def test_filter_edge_cases(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"id": "*"}, # Wildcard
|
||||
{"id": test_peer.name}, # Regular value
|
||||
|
|
@ -520,7 +520,7 @@ async def test_backward_compatibility(
|
|||
# Test old-style simple equality filter
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"role": "admin"}}},
|
||||
json={"filters": {"metadata": {"role": "admin"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -530,7 +530,7 @@ async def test_backward_compatibility(
|
|||
# Test multiple field simple filter (implicit AND)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"role": "admin"}, "id": peer_name}},
|
||||
json={"filters": {"metadata": {"role": "admin"}, "id": peer_name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -579,7 +579,7 @@ async def test_range_queries_with_dates(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"metadata": {"created_date": {"gte": "2024-02-01", "lte": "2024-02-28"}}
|
||||
}
|
||||
},
|
||||
|
|
@ -595,7 +595,7 @@ async def test_range_queries_with_dates(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"metadata": {"created_date": {"gte": "2024-01-01"}}},
|
||||
{"metadata": {"priority": {"gt": 4}}},
|
||||
|
|
@ -638,7 +638,7 @@ async def test_all_workspace_columns_filtering(client: TestClient):
|
|||
# Test filtering by id (maps to name internally)
|
||||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={"filter": {"id": workspace1_name}},
|
||||
json={"filters": {"id": workspace1_name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -648,7 +648,7 @@ async def test_all_workspace_columns_filtering(client: TestClient):
|
|||
# Test filtering by id with comparison operators
|
||||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={"filter": {"id": {"in": [workspace1_name, workspace2_name]}}},
|
||||
json={"filters": {"id": {"in": [workspace1_name, workspace2_name]}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -659,7 +659,7 @@ async def test_all_workspace_columns_filtering(client: TestClient):
|
|||
# Test filtering by metadata (maps to h_metadata internally)
|
||||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={"filter": {"metadata": {"env": "dev"}}},
|
||||
json={"filters": {"metadata": {"env": "dev"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -670,7 +670,7 @@ async def test_all_workspace_columns_filtering(client: TestClient):
|
|||
# Test filtering by metadata with comparison operators
|
||||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={"filter": {"metadata": {"version": {"gte": "2.0"}}}},
|
||||
json={"filters": {"metadata": {"version": {"gte": "2.0"}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -681,7 +681,7 @@ async def test_all_workspace_columns_filtering(client: TestClient):
|
|||
# Test filtering by created_at (datetime field)
|
||||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={"filter": {"created_at": {"gte": "2020-01-01"}}},
|
||||
json={"filters": {"created_at": {"gte": "2020-01-01"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
# Should return all workspaces created after 2020
|
||||
|
|
@ -718,7 +718,7 @@ async def test_all_peer_columns_filtering(
|
|||
# Test filtering by id (maps to name internally)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"id": peer1_name}},
|
||||
json={"filters": {"id": peer1_name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -728,7 +728,7 @@ async def test_all_peer_columns_filtering(
|
|||
# Test filtering by workspace_id (maps to workspace_name internally)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"workspace_id": test_workspace.name}},
|
||||
json={"filters": {"workspace_id": test_workspace.name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -738,7 +738,7 @@ async def test_all_peer_columns_filtering(
|
|||
# Test filtering by metadata
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"role": "admin"}}},
|
||||
json={"filters": {"metadata": {"role": "admin"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -749,7 +749,7 @@ async def test_all_peer_columns_filtering(
|
|||
# Test filtering by metadata with comparison operators
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"level": {"gte": 3}}}},
|
||||
json={"filters": {"metadata": {"level": {"gte": 3}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -760,7 +760,7 @@ async def test_all_peer_columns_filtering(
|
|||
# Test filtering by created_at
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"created_at": {"gte": "2020-01-01"}}},
|
||||
json={"filters": {"created_at": {"gte": "2020-01-01"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
# Should return all peers created after 2020
|
||||
|
|
@ -799,7 +799,7 @@ async def test_all_session_columns_filtering(
|
|||
# Test filtering by id (maps to name internally)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"id": session1_id}},
|
||||
json={"filters": {"id": session1_id}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -809,7 +809,7 @@ async def test_all_session_columns_filtering(
|
|||
# Test filtering by workspace_id (maps to workspace_name internally)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"workspace_id": test_workspace.name}},
|
||||
json={"filters": {"workspace_id": test_workspace.name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -819,7 +819,7 @@ async def test_all_session_columns_filtering(
|
|||
# Test filtering by is_active (boolean field)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"is_active": True}},
|
||||
json={"filters": {"is_active": True}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -830,7 +830,7 @@ async def test_all_session_columns_filtering(
|
|||
# Test filtering by metadata
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"metadata": {"type": "chat"}}},
|
||||
json={"filters": {"metadata": {"type": "chat"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -840,7 +840,7 @@ async def test_all_session_columns_filtering(
|
|||
# Test filtering by metadata with comparison operators
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"metadata": {"priority": {"gte": 3}}}},
|
||||
json={"filters": {"metadata": {"priority": {"gte": 3}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -851,7 +851,7 @@ async def test_all_session_columns_filtering(
|
|||
# Test filtering by created_at
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"created_at": {"gte": "2020-01-01"}}},
|
||||
json={"filters": {"created_at": {"gte": "2020-01-01"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
# Should return all sessions created after 2020
|
||||
|
|
@ -900,7 +900,7 @@ async def test_all_message_columns_filtering(
|
|||
# Test filtering by session_id (maps to session_name internally)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"session_id": session_id}},
|
||||
json={"filters": {"session_id": session_id}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -909,7 +909,7 @@ async def test_all_message_columns_filtering(
|
|||
# Test filtering by peer_id (maps to peer_name internally)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"peer_id": test_peer.name}},
|
||||
json={"filters": {"peer_id": test_peer.name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -918,7 +918,7 @@ async def test_all_message_columns_filtering(
|
|||
# Test filtering by workspace_id (maps to workspace_name internally)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"workspace_id": test_workspace.name}},
|
||||
json={"filters": {"workspace_id": test_workspace.name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -927,21 +927,21 @@ async def test_all_message_columns_filtering(
|
|||
# Test filtering by content (text field) (not allowed)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"content": "Hello world message"}},
|
||||
json={"filters": {"content": "Hello world message"}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
# Test filtering by content with contains operator (not allowed)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"content": {"contains": "support"}}},
|
||||
json={"filters": {"content": {"contains": "support"}}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
# Test filtering by metadata
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"type": "greeting"}}},
|
||||
json={"filters": {"metadata": {"type": "greeting"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -950,7 +950,7 @@ async def test_all_message_columns_filtering(
|
|||
# Test filtering by metadata with comparison operators
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"priority": {"gte": 3}}}},
|
||||
json={"filters": {"metadata": {"priority": {"gte": 3}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -959,7 +959,7 @@ async def test_all_message_columns_filtering(
|
|||
# Test filtering by token_count (integer field) - this should exist after message creation
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"token_count": {"gte": 0}}},
|
||||
json={"filters": {"token_count": {"gte": 0}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -968,7 +968,7 @@ async def test_all_message_columns_filtering(
|
|||
# Test filtering by created_at
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"created_at": {"gte": "2020-01-01"}}},
|
||||
json={"filters": {"created_at": {"gte": "2020-01-01"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1025,7 +1025,7 @@ async def test_id_field_interpolation_consistency(client: TestClient):
|
|||
# Workspace: id should map to name
|
||||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={"filter": {"id": workspace_name}},
|
||||
json={"filters": {"id": workspace_name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1035,7 +1035,7 @@ async def test_id_field_interpolation_consistency(client: TestClient):
|
|||
# Peer: id should map to name
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/peers/list",
|
||||
json={"filter": {"id": peer_name}},
|
||||
json={"filters": {"id": peer_name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1045,7 +1045,7 @@ async def test_id_field_interpolation_consistency(client: TestClient):
|
|||
# Session: id should map to name
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/list",
|
||||
json={"filter": {"id": session_id}},
|
||||
json={"filters": {"id": session_id}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1055,7 +1055,7 @@ async def test_id_field_interpolation_consistency(client: TestClient):
|
|||
# Message: id is not allowed to be filtered on
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"id": message_id}},
|
||||
json={"filters": {"id": message_id}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
|
@ -1102,7 +1102,7 @@ async def test_foreign_key_field_interpolation(
|
|||
# Test workspace_id filtering for peers (maps to workspace_name)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"workspace_id": test_workspace.name}},
|
||||
json={"filters": {"workspace_id": test_workspace.name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1113,7 +1113,7 @@ async def test_foreign_key_field_interpolation(
|
|||
# Test workspace_id filtering for sessions (maps to workspace_name)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/list",
|
||||
json={"filter": {"workspace_id": test_workspace.name}},
|
||||
json={"filters": {"workspace_id": test_workspace.name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1123,7 +1123,7 @@ async def test_foreign_key_field_interpolation(
|
|||
# Test session_id filtering for messages (maps to session_name)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"session_id": session_id}},
|
||||
json={"filters": {"session_id": session_id}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1132,7 +1132,7 @@ async def test_foreign_key_field_interpolation(
|
|||
# Test peer_id filtering for messages (maps to peer_name)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"peer_id": peer_name}},
|
||||
json={"filters": {"peer_id": peer_name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1141,7 +1141,7 @@ async def test_foreign_key_field_interpolation(
|
|||
# Test workspace_id filtering for messages (maps to workspace_name)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"workspace_id": test_workspace.name}},
|
||||
json={"filters": {"workspace_id": test_workspace.name}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1203,7 +1203,7 @@ async def test_metadata_field_interpolation(client: TestClient):
|
|||
# Workspace metadata
|
||||
response = client.post(
|
||||
"/v2/workspaces/list",
|
||||
json={"filter": {"metadata": {"env": "test"}}},
|
||||
json={"filters": {"metadata": {"env": "test"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1213,7 +1213,7 @@ async def test_metadata_field_interpolation(client: TestClient):
|
|||
# Peer metadata
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/peers/list",
|
||||
json={"filter": {"metadata": {"role": "user"}}},
|
||||
json={"filters": {"metadata": {"role": "user"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1223,7 +1223,7 @@ async def test_metadata_field_interpolation(client: TestClient):
|
|||
# Session metadata
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/list",
|
||||
json={"filter": {"metadata": {"type": "chat"}}},
|
||||
json={"filters": {"metadata": {"type": "chat"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1233,7 +1233,7 @@ async def test_metadata_field_interpolation(client: TestClient):
|
|||
# Message metadata
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"sentiment": "positive"}}},
|
||||
json={"filters": {"metadata": {"sentiment": "positive"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1244,7 +1244,7 @@ async def test_metadata_field_interpolation(client: TestClient):
|
|||
# Numeric metadata comparison
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/peers/list",
|
||||
json={"filter": {"metadata": {"level": {"gte": 3}}}},
|
||||
json={"filters": {"metadata": {"level": {"gte": 3}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1254,7 +1254,7 @@ async def test_metadata_field_interpolation(client: TestClient):
|
|||
# Boolean metadata comparison
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/list",
|
||||
json={"filter": {"metadata": {"archived": {"ne": True}}}},
|
||||
json={"filters": {"metadata": {"archived": {"ne": True}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1264,7 +1264,7 @@ async def test_metadata_field_interpolation(client: TestClient):
|
|||
# Float metadata comparison
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"confidence": {"gte": 0.9}}}},
|
||||
json={"filters": {"metadata": {"confidence": {"gte": 0.9}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1288,7 +1288,7 @@ async def test_nonexistent_columns_ignored_gracefully(
|
|||
# Test filtering by non-existent columns
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"nonexistent_column": "value"}},
|
||||
json={"filters": {"nonexistent_column": "value"}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
|
@ -1296,7 +1296,7 @@ async def test_nonexistent_columns_ignored_gracefully(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"metadata": {"role": "test"}}, # Real filter
|
||||
{"fake_column": "fake_value"}, # Non-existent filter
|
||||
|
|
@ -1310,7 +1310,7 @@ async def test_nonexistent_columns_ignored_gracefully(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"OR": [
|
||||
{"nonexistent_field": "value"},
|
||||
{"metadata": {"role": "test"}},
|
||||
|
|
@ -1358,7 +1358,7 @@ async def test_not_logic_correctness(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"NOT": [
|
||||
{"metadata": {"role": "admin"}},
|
||||
{"metadata": {"department": "engineering"}},
|
||||
|
|
@ -1426,7 +1426,7 @@ async def test_jsonb_type_casting_edge_cases(
|
|||
# Test boolean comparisons - PostgreSQL stores booleans as "true"/"false" strings
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"active": {"ne": False}}}},
|
||||
json={"filters": {"metadata": {"active": {"ne": False}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1436,7 +1436,7 @@ async def test_jsonb_type_casting_edge_cases(
|
|||
# Test string vs numeric comparison
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"score": {"gt": 10}}}},
|
||||
json={"filters": {"metadata": {"score": {"gt": 10}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1447,7 +1447,7 @@ async def test_jsonb_type_casting_edge_cases(
|
|||
# Test large number handling
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"score": {"gte": 999999999}}}},
|
||||
json={"filters": {"metadata": {"score": {"gte": 999999999}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1488,7 +1488,7 @@ async def test_real_datetime_column_filtering(
|
|||
# Test ISO format
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"created_at": {"gte": one_minute_ago.isoformat()}}},
|
||||
json={"filters": {"created_at": {"gte": one_minute_ago.isoformat()}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1500,7 +1500,7 @@ async def test_real_datetime_column_filtering(
|
|||
today = datetime.now(timezone.utc).date().isoformat()
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"created_at": {"gte": today}}},
|
||||
json={"filters": {"created_at": {"gte": today}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
|
@ -1523,7 +1523,7 @@ async def test_invalid_datetime_handling(
|
|||
for malicious_dt in malicious_datetimes:
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"created_at": {"gte": malicious_dt}}},
|
||||
json={"filters": {"created_at": {"gte": malicious_dt}}},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
|
@ -1573,7 +1573,7 @@ async def test_nested_jsonb_filtering(
|
|||
# Test nested object filtering - this might not work as expected
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"user": {"profile": {"premium": True}}}}},
|
||||
json={"filters": {"metadata": {"user": {"profile": {"premium": True}}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1626,7 +1626,7 @@ async def test_multiple_operators_same_field(
|
|||
# Test range query: score >= 3 AND score <= 8
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"score": {"gte": 3, "lte": 8}}}},
|
||||
json={"filters": {"metadata": {"score": {"gte": 3, "lte": 8}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1643,7 +1643,7 @@ async def test_empty_and_null_filter_handling(
|
|||
|
||||
# Test completely empty filter
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filter": {}}
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filters": {}}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1652,14 +1652,14 @@ async def test_empty_and_null_filter_handling(
|
|||
|
||||
# Test null filter
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filter": None}
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filters": None}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Test empty comparison dict
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"role": {}}}},
|
||||
json={"filters": {"metadata": {"role": {}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
|
@ -1687,7 +1687,7 @@ async def test_unicode_and_special_characters(
|
|||
# Test unicode in metadata contains
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"description": {"icontains": "wörld"}}}},
|
||||
json={"filters": {"metadata": {"description": {"icontains": "wörld"}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1724,7 +1724,7 @@ async def test_case_sensitivity_edge_cases(
|
|||
# Test exact case matching (should be case sensitive for JSONB)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"Role": "Admin"}}},
|
||||
json={"filters": {"metadata": {"Role": "Admin"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1735,7 +1735,7 @@ async def test_case_sensitivity_edge_cases(
|
|||
# Test icontains for case insensitive search
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": {"metadata": {"Department": {"icontains": "engineering"}}}},
|
||||
json={"filters": {"metadata": {"Department": {"icontains": "engineering"}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1764,7 +1764,7 @@ async def test_malformed_filter_structures(
|
|||
for malformed_filter in malformed_filters:
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": malformed_filter},
|
||||
json={"filters": malformed_filter},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
|
@ -1813,7 +1813,7 @@ async def test_performance_with_complex_filters(
|
|||
# This should complete without timeout
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": complex_filter},
|
||||
json={"filters": complex_filter},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
# Should return results in reasonable time (this is more of a performance test)
|
||||
|
|
@ -1854,7 +1854,7 @@ async def test_filter_precedence_and_grouping(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{
|
||||
"OR": [
|
||||
|
|
@ -1928,7 +1928,7 @@ async def test_jsonb_contains_vs_equality_semantics(
|
|||
# Test JSONB contains behavior - should match both exact and superset
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"role": "admin"}}},
|
||||
json={"filters": {"metadata": {"role": "admin"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -1955,7 +1955,7 @@ async def test_wildcard_edge_cases_comprehensive(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {"metadata": {"level": {"gte": "*"}}} # Wildcard in comparison
|
||||
"filters": {"metadata": {"level": {"gte": "*"}}} # Wildcard in comparison
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
|
@ -1967,7 +1967,7 @@ async def test_wildcard_edge_cases_comprehensive(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"metadata": {"role": {"in": ["*", "admin"]}}
|
||||
} # Mixed wildcard and value
|
||||
},
|
||||
|
|
@ -1981,7 +1981,7 @@ async def test_wildcard_edge_cases_comprehensive(
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"id": "*"},
|
||||
{"metadata": {"role": "*"}},
|
||||
|
|
@ -2013,7 +2013,7 @@ async def test_error_logging_and_debugging(
|
|||
for filter_dict in problematic_filters:
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/peers/list",
|
||||
json={"filter": filter_dict},
|
||||
json={"filters": filter_dict},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
|
@ -2061,7 +2061,7 @@ async def test_boundary_conditions_numeric(
|
|||
# Test boundary conditions
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"score": {"gte": 0}}}},
|
||||
json={"filters": {"metadata": {"score": {"gte": 0}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2073,7 +2073,7 @@ async def test_boundary_conditions_numeric(
|
|||
# Test floating point precision
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list",
|
||||
json={"filter": {"metadata": {"score": {"gt": 3.14}}}},
|
||||
json={"filters": {"metadata": {"score": {"gt": 3.14}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2165,7 +2165,7 @@ async def test_float_precision_edge_cases(client: TestClient):
|
|||
# Test exact equality - this may or may not work due to floating point precision
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/ilovefloatingpoints/messages/list",
|
||||
json={"filter": {"metadata": {"value": 0.3}}},
|
||||
json={"filters": {"metadata": {"value": 0.3}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2179,7 +2179,7 @@ async def test_float_precision_edge_cases(client: TestClient):
|
|||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/ilovefloatingpoints/messages/list",
|
||||
json={
|
||||
"filter": {
|
||||
"filters": {
|
||||
"AND": [
|
||||
{"metadata": {"value": {"gte": 0.3 - epsilon}}},
|
||||
{"metadata": {"value": {"lte": 0.3 + epsilon}}},
|
||||
|
|
@ -2197,7 +2197,7 @@ async def test_float_precision_edge_cases(client: TestClient):
|
|||
# Test greater than with floating point precision
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/ilovefloatingpoints/messages/list",
|
||||
json={"filter": {"metadata": {"value": {"gt": 0.3}}}},
|
||||
json={"filters": {"metadata": {"value": {"gt": 0.3}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2208,7 +2208,7 @@ async def test_float_precision_edge_cases(client: TestClient):
|
|||
# Test very small numbers and scientific notation
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/ilovefloatingpoints/messages/list",
|
||||
json={"filter": {"metadata": {"value": {"lt": 1e-9}}}},
|
||||
json={"filters": {"metadata": {"value": {"lt": 1e-9}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2218,7 +2218,7 @@ async def test_float_precision_edge_cases(client: TestClient):
|
|||
# Test large number precision
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/ilovefloatingpoints/messages/list",
|
||||
json={"filter": {"metadata": {"value": {"gte": 999999.0}}}},
|
||||
json={"filters": {"metadata": {"value": {"gte": 999999.0}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2228,7 +2228,7 @@ async def test_float_precision_edge_cases(client: TestClient):
|
|||
# Test repeating decimal precision
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/ilovefloatingpoints/messages/list",
|
||||
json={"filter": {"metadata": {"value": {"gte": 0.333}}}},
|
||||
json={"filters": {"metadata": {"value": {"gte": 0.333}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2238,7 +2238,7 @@ async def test_float_precision_edge_cases(client: TestClient):
|
|||
# Test floating point comparison with string representation
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/ilovefloatingpoints/messages/list",
|
||||
json={"filter": {"metadata": {"calculation": "0.1 + 0.2"}}},
|
||||
json={"filters": {"metadata": {"calculation": "0.1 + 0.2"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2336,7 +2336,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test string vs numeric equality
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"priority": 5}}},
|
||||
json={"filters": {"metadata": {"priority": 5}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2348,7 +2348,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test string number comparison with numeric operator
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"priority": {"gte": 5}}}},
|
||||
json={"filters": {"metadata": {"priority": {"gte": 5}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2359,7 +2359,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test string boolean vs actual boolean
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"active": True}}},
|
||||
json={"filters": {"metadata": {"active": True}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2370,7 +2370,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test explicit string matching
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"priority": "5"}}},
|
||||
json={"filters": {"metadata": {"priority": "5"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2380,7 +2380,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test numeric comparison with string numbers
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"score": {"gt": 10}}}},
|
||||
json={"filters": {"metadata": {"score": {"gt": 10}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2390,7 +2390,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test zero comparisons (string "0" vs integer 0)
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"count": 0}}},
|
||||
json={"filters": {"metadata": {"count": 0}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2400,7 +2400,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test null vs string "null"
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"count": None}}},
|
||||
json={"filters": {"metadata": {"count": None}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2410,7 +2410,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test leading zeros handling
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"priority": "05"}}},
|
||||
json={"filters": {"metadata": {"priority": "05"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2420,7 +2420,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test case sensitivity for string booleans
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"active": "TRUE"}}},
|
||||
json={"filters": {"metadata": {"active": "TRUE"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2430,7 +2430,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test empty string vs other falsy values
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"priority": ""}}},
|
||||
json={"filters": {"metadata": {"priority": ""}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2440,7 +2440,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test special string values
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"score": "NaN"}}},
|
||||
json={"filters": {"metadata": {"score": "NaN"}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -2450,7 +2450,7 @@ async def test_mixed_type_comparisons(client: TestClient):
|
|||
# Test mixed type in operator
|
||||
response = client.post(
|
||||
f"/v2/workspaces/{workspace_name}/sessions/mixedtypes/messages/list",
|
||||
json={"filter": {"metadata": {"priority": {"in": [5, "5", 5.0]}}}},
|
||||
json={"filters": {"metadata": {"priority": {"in": [5, "5", 5.0]}}}},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
|
|||
644
uv.lock
644
uv.lock
|
|
@ -33,7 +33,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "0.60.0"
|
||||
version = "0.61.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -44,14 +44,14 @@ dependencies = [
|
|||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/03/3334921dc54ed822b3dd993ae72d823a7402588521bbba3e024b3333a1fd/anthropic-0.60.0.tar.gz", hash = "sha256:a22ba187c6f4fd5afecb2fc913b960feccf72bc0d25c1b7ce0345e87caede577", size = 425983, upload-time = "2025-07-28T19:53:47.685Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/9a/b384758ef93b8f931a523efc8782f7191b175714b3952ff11002899f638b/anthropic-0.61.0.tar.gz", hash = "sha256:af4b3b8f3bc4626cca6af2d412e301974da1747179341ad9e271bdf5cbd2f008", size = 426606, upload-time = "2025-08-05T16:29:37.958Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/bb/d84f287fb1c217b30c328af987cf8bbe3897edf0518dcc5fa39412f794ec/anthropic-0.60.0-py3-none-any.whl", hash = "sha256:65ad1f088a960217aaf82ba91ff743d6c89e9d811c6d64275b9a7c59ee9ac3c6", size = 293116, upload-time = "2025-07-28T19:53:45.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/07/c7907eee22f5c27a53118dd2576267052ae01f52811dbb06a2848012639e/anthropic-0.61.0-py3-none-any.whl", hash = "sha256:798c8e6cc61e6315143c3f5847d2f220c45f1e69f433436872a237413ca58803", size = 294935, upload-time = "2025-08-05T16:29:36.379Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
version = "4.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
|
|
@ -59,9 +59,9 @@ dependencies = [
|
|||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -93,14 +93,14 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "basedpyright"
|
||||
version = "1.31.0"
|
||||
version = "1.31.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nodejs-wheel-binaries" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/23/6dc0df43c62fdec401b1ec3aea698ba50c5abfca25259e9f0208b34d7abe/basedpyright-1.31.0.tar.gz", hash = "sha256:900a573a525a0f66f884075c2a98711bb9478e44dc60ffdf182ef681bf8e2c76", size = 22062384, upload-time = "2025-07-16T11:37:29.189Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/33/39/e2870a3739dce055a5b7822d027843c9ba9b3453dcb4b226d9b0e9d486f4/basedpyright-1.31.1.tar.gz", hash = "sha256:4e4d922a385f45dc93e50738d1131ec4533fee5d338b700ef2d28e2e0412e642", size = 22067890, upload-time = "2025-08-03T13:41:15.405Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/85/cb46707458c514ae959fe139135d8f7231d95faf1c383a56979a3436b965/basedpyright-1.31.0-py3-none-any.whl", hash = "sha256:d7460ddcd3a2332b1c3fd738735d18bf2966d49aed67237efa1f19635199d414", size = 11538999, upload-time = "2025-07-16T11:37:26.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/cc/8bca3b3a48d6a03a4b857a297fb1473ed1b9fa111be2d20c01f11112e75c/basedpyright-1.31.1-py3-none-any.whl", hash = "sha256:8b647bf07fff929892db4be83a116e6e1e59c13462ecb141214eb271f6785ee5", size = 11540576, upload-time = "2025-08-03T13:41:11.571Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -114,11 +114,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.7.14"
|
||||
version = "2025.8.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -271,87 +271,87 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.10.1"
|
||||
version = "7.10.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/87/0e/66dbd4c6a7f0758a8d18044c048779ba21fb94856e1edcf764bd5403e710/coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57", size = 819938, upload-time = "2025-07-27T14:13:39.045Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/76/17780846fc7aade1e66712e1e27dd28faa0a5d987a1f433610974959eaa8/coverage-7.10.2.tar.gz", hash = "sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055", size = 820754, upload-time = "2025-08-04T00:35:17.511Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/e7/0f4e35a15361337529df88151bddcac8e8f6d6fd01da94a4b7588901c2fe/coverage-7.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1c86eb388bbd609d15560e7cc0eb936c102b6f43f31cf3e58b4fd9afe28e1372", size = 214627, upload-time = "2025-07-27T14:11:01.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/fd/17872e762c408362072c936dbf3ca28c67c609a1f5af434b1355edcb7e12/coverage-7.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b4ba0f488c1bdb6bd9ba81da50715a372119785458831c73428a8566253b86b", size = 215015, upload-time = "2025-07-27T14:11:03.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/50/c9d445ba38ee5f685f03876c0f8223469e2e46c5d3599594dca972b470c8/coverage-7.10.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083442ecf97d434f0cb3b3e3676584443182653da08b42e965326ba12d6b5f2a", size = 241995, upload-time = "2025-07-27T14:11:05.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/83/4ae6e0f60376af33de543368394d21b9ac370dc86434039062ef171eebf8/coverage-7.10.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c1a40c486041006b135759f59189385da7c66d239bad897c994e18fd1d0c128f", size = 243253, upload-time = "2025-07-27T14:11:07.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/90/17a4d9ac7171be364ce8c0bb2b6da05e618ebfe1f11238ad4f26c99f5467/coverage-7.10.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3beb76e20b28046989300c4ea81bf690df84ee98ade4dc0bbbf774a28eb98440", size = 245110, upload-time = "2025-07-27T14:11:09.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/f7/edc3f485d536ed417f3af2b4969582bcb5fab456241721825fa09354161e/coverage-7.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc265a7945e8d08da28999ad02b544963f813a00f3ed0a7a0ce4165fd77629f8", size = 243056, upload-time = "2025-07-27T14:11:10.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/2c/c4c316a57718556b8d0cc8304437741c31b54a62934e7c8c551a7915c2f4/coverage-7.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:47c91f32ba4ac46f1e224a7ebf3f98b4b24335bad16137737fe71a5961a0665c", size = 241731, upload-time = "2025-07-27T14:11:12.145Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/93/c78e144c6f086043d0d7d9237c5b880e71ac672ed2712c6f8cca5544481f/coverage-7.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1a108dd78ed185020f66f131c60078f3fae3f61646c28c8bb4edd3fa121fc7fc", size = 242023, upload-time = "2025-07-27T14:11:13.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/e1/34e8505ca81fc144a612e1cc79fadd4a78f42e96723875f4e9f1f470437e/coverage-7.10.1-cp310-cp310-win32.whl", hash = "sha256:7092cc82382e634075cc0255b0b69cb7cada7c1f249070ace6a95cb0f13548ef", size = 217130, upload-time = "2025-07-27T14:11:15.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/2b/82adfce6edffc13d804aee414e64c0469044234af9296e75f6d13f92f6a2/coverage-7.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac0c5bba938879c2fc0bc6c1b47311b5ad1212a9dcb8b40fe2c8110239b7faed", size = 218015, upload-time = "2025-07-27T14:11:16.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/8e/ef088112bd1b26e2aa931ee186992b3e42c222c64f33e381432c8ee52aae/coverage-7.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45e2f9d5b0b5c1977cb4feb5f594be60eb121106f8900348e29331f553a726f", size = 214747, upload-time = "2025-07-27T14:11:18.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/76/a1e46f3c6e0897758eb43af88bb3c763cb005f4950769f7b553e22aa5f89/coverage-7.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a7a4d74cb0f5e3334f9aa26af7016ddb94fb4bfa11b4a573d8e98ecba8c34f1", size = 215128, upload-time = "2025-07-27T14:11:19.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/4d/903bafb371a8c887826ecc30d3977b65dfad0e1e66aa61b7e173de0828b0/coverage-7.10.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4b0aab55ad60ead26159ff12b538c85fbab731a5e3411c642b46c3525863437", size = 245140, upload-time = "2025-07-27T14:11:21.261Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/f1/1f8f09536f38394a8698dd08a0e9608a512eacee1d3b771e2d06397f77bf/coverage-7.10.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dcc93488c9ebd229be6ee1f0d9aad90da97b33ad7e2912f5495804d78a3cd6b7", size = 246977, upload-time = "2025-07-27T14:11:23.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/cc/ed6bbc5a3bdb36ae1bca900bbbfdcb23b260ef2767a7b2dab38b92f61adf/coverage-7.10.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa309df995d020f3438407081b51ff527171cca6772b33cf8f85344b8b4b8770", size = 249140, upload-time = "2025-07-27T14:11:24.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/f5/e881ade2d8e291b60fa1d93d6d736107e940144d80d21a0d4999cff3642f/coverage-7.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cfb8b9d8855c8608f9747602a48ab525b1d320ecf0113994f6df23160af68262", size = 246869, upload-time = "2025-07-27T14:11:26.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/b9/6a5665cb8996e3cd341d184bb11e2a8edf01d8dadcf44eb1e742186cf243/coverage-7.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:320d86da829b012982b414c7cdda65f5d358d63f764e0e4e54b33097646f39a3", size = 244899, upload-time = "2025-07-27T14:11:27.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/11/24156776709c4e25bf8a33d6bb2ece9a9067186ddac19990f6560a7f8130/coverage-7.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc60ddd483c556590da1d9482a4518292eec36dd0e1e8496966759a1f282bcd0", size = 245507, upload-time = "2025-07-27T14:11:29.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/db/a6f0340b7d6802a79928659c9a32bc778ea420e87a61b568d68ac36d45a8/coverage-7.10.1-cp311-cp311-win32.whl", hash = "sha256:4fcfe294f95b44e4754da5b58be750396f2b1caca8f9a0e78588e3ef85f8b8be", size = 217167, upload-time = "2025-07-27T14:11:31.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/6f/1990eb4fd05cea4cfabdf1d587a997ac5f9a8bee883443a1d519a2a848c9/coverage-7.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:efa23166da3fe2915f8ab452dde40319ac84dc357f635737174a08dbd912980c", size = 218054, upload-time = "2025-07-27T14:11:33.202Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/4d/5e061d6020251b20e9b4303bb0b7900083a1a384ec4e5db326336c1c4abd/coverage-7.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:d12b15a8c3759e2bb580ffa423ae54be4f184cf23beffcbd641f4fe6e1584293", size = 216483, upload-time = "2025-07-27T14:11:34.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/3f/b051feeb292400bd22d071fdf933b3ad389a8cef5c80c7866ed0c7414b9e/coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4", size = 214934, upload-time = "2025-07-27T14:11:36.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/e4/a61b27d5c4c2d185bdfb0bfe9d15ab4ac4f0073032665544507429ae60eb/coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e", size = 215173, upload-time = "2025-07-27T14:11:38.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/01/40a6ee05b60d02d0bc53742ad4966e39dccd450aafb48c535a64390a3552/coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4", size = 246190, upload-time = "2025-07-27T14:11:39.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/ef/a28d64d702eb583c377255047281305dc5a5cfbfb0ee36e721f78255adb6/coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a", size = 248618, upload-time = "2025-07-27T14:11:41.841Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/ad/73d018bb0c8317725370c79d69b5c6e0257df84a3b9b781bda27a438a3be/coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe", size = 250081, upload-time = "2025-07-27T14:11:43.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/dd/496adfbbb4503ebca5d5b2de8bed5ec00c0a76558ffc5b834fd404166bc9/coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386", size = 247990, upload-time = "2025-07-27T14:11:45.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/3c/a9331a7982facfac0d98a4a87b36ae666fe4257d0f00961a3a9ef73e015d/coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6", size = 246191, upload-time = "2025-07-27T14:11:47.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/0c/75345895013b83f7afe92ec595e15a9a525ede17491677ceebb2ba5c3d85/coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f", size = 247400, upload-time = "2025-07-27T14:11:48.643Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/a9/98b268cfc5619ef9df1d5d34fee408ecb1542d9fd43d467e5c2f28668cd4/coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca", size = 217338, upload-time = "2025-07-27T14:11:50.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/31/22a5440e4d1451f253c5cd69fdcead65e92ef08cd4ec237b8756dc0b20a7/coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3", size = 218125, upload-time = "2025-07-27T14:11:52.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/2b/40d9f0ce7ee839f08a43c5bfc9d05cec28aaa7c9785837247f96cbe490b9/coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4", size = 216523, upload-time = "2025-07-27T14:11:53.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/72/135ff5fef09b1ffe78dbe6fcf1e16b2e564cd35faeacf3d63d60d887f12d/coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39", size = 214960, upload-time = "2025-07-27T14:11:55.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/aa/73a5d1a6fc08ca709a8177825616aa95ee6bf34d522517c2595484a3e6c9/coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7", size = 215220, upload-time = "2025-07-27T14:11:57.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/40/3124fdd45ed3772a42fc73ca41c091699b38a2c3bd4f9cb564162378e8b6/coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892", size = 245772, upload-time = "2025-07-27T14:12:00.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/62/a77b254822efa8c12ad59e8039f2bc3df56dc162ebda55e1943e35ba31a5/coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7", size = 248116, upload-time = "2025-07-27T14:12:03.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/01/8101f062f472a3a6205b458d18ef0444a63ae5d36a8a5ed5dd0f6167f4db/coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994", size = 249554, upload-time = "2025-07-27T14:12:04.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/7b/e51bc61573e71ff7275a4f167aecbd16cb010aefdf54bcd8b0a133391263/coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0", size = 247766, upload-time = "2025-07-27T14:12:06.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/71/1c96d66a51d4204a9d6d12df53c4071d87e110941a2a1fe94693192262f5/coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7", size = 245735, upload-time = "2025-07-27T14:12:08.305Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/d5/efbc2ac4d35ae2f22ef6df2ca084c60e13bd9378be68655e3268c80349ab/coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7", size = 247118, upload-time = "2025-07-27T14:12:09.903Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/22/073848352bec28ca65f2b6816b892fcf9a31abbef07b868487ad15dd55f1/coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7", size = 217381, upload-time = "2025-07-27T14:12:11.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/df/df6a0ff33b042f000089bd11b6bb034bab073e2ab64a56e78ed882cba55d/coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e", size = 218152, upload-time = "2025-07-27T14:12:13.182Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/e3/5085ca849a40ed6b47cdb8f65471c2f754e19390b5a12fa8abd25cbfaa8f/coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4", size = 216559, upload-time = "2025-07-27T14:12:14.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/93/58714efbfdeb547909feaabe1d67b2bdd59f0597060271b9c548d5efb529/coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72", size = 215677, upload-time = "2025-07-27T14:12:16.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/0c/18eaa5897e7e8cb3f8c45e563e23e8a85686b4585e29d53cacb6bc9cb340/coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af", size = 215899, upload-time = "2025-07-27T14:12:18.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/c1/9d1affacc3c75b5a184c140377701bbf14fc94619367f07a269cd9e4fed6/coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7", size = 257140, upload-time = "2025-07-27T14:12:20.357Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/0f/339bc6b8fa968c346df346068cca1f24bdea2ddfa93bb3dc2e7749730962/coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759", size = 259005, upload-time = "2025-07-27T14:12:22.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/22/89390864b92ea7c909079939b71baba7e5b42a76bf327c1d615bd829ba57/coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324", size = 261143, upload-time = "2025-07-27T14:12:23.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/56/3d04d89017c0c41c7a71bd69b29699d919b6bbf2649b8b2091240b97dd6a/coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53", size = 258735, upload-time = "2025-07-27T14:12:25.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/40/312252c8afa5ca781063a09d931f4b9409dc91526cd0b5a2b84143ffafa2/coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f", size = 256871, upload-time = "2025-07-27T14:12:27.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/2b/564947d5dede068215aaddb9e05638aeac079685101462218229ddea9113/coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd", size = 257692, upload-time = "2025-07-27T14:12:29.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/1b/c8a867ade85cb26d802aea2209b9c2c80613b9c122baa8c8ecea6799648f/coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c", size = 218059, upload-time = "2025-07-27T14:12:31.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/fe/cd4ab40570ae83a516bf5e754ea4388aeedd48e660e40c50b7713ed4f930/coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18", size = 219150, upload-time = "2025-07-27T14:12:32.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/16/6e5ed5854be6d70d0c39e9cb9dd2449f2c8c34455534c32c1a508c7dbdb5/coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4", size = 217014, upload-time = "2025-07-27T14:12:34.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/8e/6d0bfe9c3d7121cf936c5f8b03e8c3da1484fb801703127dba20fb8bd3c7/coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c", size = 214951, upload-time = "2025-07-27T14:12:36.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/29/e3e51a8c653cf2174c60532aafeb5065cea0911403fa144c9abe39790308/coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e", size = 215229, upload-time = "2025-07-27T14:12:37.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/59/3c972080b2fa18b6c4510201f6d4dc87159d450627d062cd9ad051134062/coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b", size = 245738, upload-time = "2025-07-27T14:12:39.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/04/fc0d99d3f809452654e958e1788454f6e27b34e43f8f8598191c8ad13537/coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41", size = 248045, upload-time = "2025-07-27T14:12:41.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/2e/afcbf599e77e0dfbf4c97197747250d13d397d27e185b93987d9eaac053d/coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f", size = 249666, upload-time = "2025-07-27T14:12:43.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/ae/bc47f7f8ecb7a06cbae2bf86a6fa20f479dd902bc80f57cff7730438059d/coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1", size = 247692, upload-time = "2025-07-27T14:12:44.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/26/cbfa3092d31ccba8ba7647e4d25753263e818b4547eba446b113d7d1efdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2", size = 245536, upload-time = "2025-07-27T14:12:46.527Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/77/9c68e92500e6a1c83d024a70eadcc9a173f21aadd73c4675fe64c9c43fdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4", size = 246954, upload-time = "2025-07-27T14:12:49.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/a5/ba96671c5a669672aacd9877a5987c8551501b602827b4e84256da2a30a7/coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613", size = 217616, upload-time = "2025-07-27T14:12:51.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/3c/e1e1eb95fc1585f15a410208c4795db24a948e04d9bde818fe4eb893bc85/coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e", size = 218412, upload-time = "2025-07-27T14:12:53.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/85/7e1e5be2cb966cba95566ba702b13a572ca744fbb3779df9888213762d67/coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652", size = 216776, upload-time = "2025-07-27T14:12:55.482Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/0f/5bb8f29923141cca8560fe2217679caf4e0db643872c1945ac7d8748c2a7/coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894", size = 215698, upload-time = "2025-07-27T14:12:57.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/29/547038ffa4e8e4d9e82f7dfc6d152f75fcdc0af146913f0ba03875211f03/coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5", size = 215902, upload-time = "2025-07-27T14:12:59.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/8a/7aaa8fbfaed900147987a424e112af2e7790e1ac9cd92601e5bd4e1ba60a/coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2", size = 257230, upload-time = "2025-07-27T14:13:01.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/1d/c252b5ffac44294e23a0d79dd5acf51749b39795ccc898faeabf7bee903f/coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb", size = 259194, upload-time = "2025-07-27T14:13:03.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/ad/6c8d9f83d08f3bac2e7507534d0c48d1a4f52c18e6f94919d364edbdfa8f/coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b", size = 261316, upload-time = "2025-07-27T14:13:04.957Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/4e/f9bbf3a36c061e2e0e0f78369c006d66416561a33d2bee63345aee8ee65e/coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea", size = 258794, upload-time = "2025-07-27T14:13:06.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/82/e600bbe78eb2cb0541751d03cef9314bcd0897e8eea156219c39b685f869/coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd", size = 256869, upload-time = "2025-07-27T14:13:08.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/5d/2fc9a9236c5268f68ac011d97cd3a5ad16cc420535369bedbda659fdd9b7/coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d", size = 257765, upload-time = "2025-07-27T14:13:10.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/05/b4e00b2bd48a2dc8e1c7d2aea7455f40af2e36484ab2ef06deb85883e9fe/coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47", size = 218420, upload-time = "2025-07-27T14:13:12.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/fb/d21d05f33ea27ece327422240e69654b5932b0b29e7fbc40fbab3cf199bf/coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651", size = 219536, upload-time = "2025-07-27T14:13:14.718Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/68/7fea94b141281ed8be3d1d5c4319a97f2befc3e487ce33657fc64db2c45e/coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab", size = 217190, upload-time = "2025-07-27T14:13:16.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/64/922899cff2c0fd3496be83fa8b81230f5a8d82a2ad30f98370b133c2c83b/coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7", size = 206597, upload-time = "2025-07-27T14:13:37.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/5f/5ce748ab3f142593698aff5f8a0cf020775aa4e24b9d8748b5a56b64d3f8/coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65", size = 215003, upload-time = "2025-08-04T00:33:02.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/ed/507088561217b000109552139802fa99c33c16ad19999c687b601b3790d0/coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8", size = 215391, upload-time = "2025-08-04T00:33:05.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/1b/0f496259fe137c4c5e1e8eaff496fb95af88b71700f5e57725a4ddbe742b/coverage-7.10.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ae385e1d58fbc6a9b1c315e5510ac52281e271478b45f92ca9b5ad42cf39643f", size = 242367, upload-time = "2025-08-04T00:33:07.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/8e/5a8835fb0122a2e2a108bf3527931693c4625fdc4d953950a480b9625852/coverage-7.10.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f0cbe5f7dd19f3a32bac2251b95d51c3b89621ac88a2648096ce40f9a5aa1e7", size = 243627, upload-time = "2025-08-04T00:33:08.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/96/6a528429c2e0e8d85261764d0cd42e51a429510509bcc14676ee5d1bb212/coverage-7.10.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd17f427f041f6b116dc90b4049c6f3e1230524407d00daa2d8c7915037b5947", size = 245485, upload-time = "2025-08-04T00:33:10.29Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/82/1fba935c4d02c33275aca319deabf1f22c0f95f2c0000bf7c5f276d6f7b4/coverage-7.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f10ca4cde7b466405cce0a0e9971a13eb22e57a5ecc8b5f93a81090cc9c7eb9", size = 243429, upload-time = "2025-08-04T00:33:11.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/a8/c8dc0a57a729fc93be33ab78f187a8f52d455fa8f79bfb379fe23b45868d/coverage-7.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b990df23dd51dccce26d18fb09fd85a77ebe46368f387b0ffba7a74e470b31b", size = 242104, upload-time = "2025-08-04T00:33:13.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/6f/0b7da1682e2557caeed299a00897b42afde99a241a01eba0197eb982b90f/coverage-7.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc3902584d25c7eef57fb38f440aa849a26a3a9f761a029a72b69acfca4e31f8", size = 242397, upload-time = "2025-08-04T00:33:14.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/e4/54dc833dadccd519c04a28852f39a37e522bad35d70cfe038817cdb8f168/coverage-7.10.2-cp310-cp310-win32.whl", hash = "sha256:9dd37e9ac00d5eb72f38ed93e3cdf2280b1dbda3bb9b48c6941805f265ad8d87", size = 217502, upload-time = "2025-08-04T00:33:16.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/e7/2f78159c4c127549172f427dff15b02176329327bf6a6a1fcf1f603b5456/coverage-7.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:99d16f15cb5baf0729354c5bd3080ae53847a4072b9ba1e10957522fb290417f", size = 218388, upload-time = "2025-08-04T00:33:17.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/53/0125a6fc0af4f2687b4e08b0fb332cd0d5e60f3ca849e7456f995d022656/coverage-7.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3b210d79925a476dfc8d74c7d53224888421edebf3a611f3adae923e212b27", size = 215119, upload-time = "2025-08-04T00:33:19.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/2e/960d9871de9152dbc9ff950913c6a6e9cf2eb4cc80d5bc8f93029f9f2f9f/coverage-7.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf67d1787cd317c3f8b2e4c6ed1ae93497be7e30605a0d32237ac37a37a8a322", size = 215511, upload-time = "2025-08-04T00:33:20.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/34/68509e44995b9cad806d81b76c22bc5181f3535bca7cd9c15791bfd8951e/coverage-7.10.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:069b779d03d458602bc0e27189876e7d8bdf6b24ac0f12900de22dd2154e6ad7", size = 245513, upload-time = "2025-08-04T00:33:21.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/d4/9b12f357413248ce40804b0f58030b55a25b28a5c02db95fb0aa50c5d62c/coverage-7.10.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c2de4cb80b9990e71c62c2d3e9f3ec71b804b1f9ca4784ec7e74127e0f42468", size = 247350, upload-time = "2025-08-04T00:33:23.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/40/257945eda1f72098e4a3c350b1d68fdc5d7d032684a0aeb6c2391153ecf4/coverage-7.10.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75bf7ab2374a7eb107602f1e07310cda164016cd60968abf817b7a0b5703e288", size = 249516, upload-time = "2025-08-04T00:33:25.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/55/8987f852ece378cecbf39a367f3f7ec53351e39a9151b130af3a3045b83f/coverage-7.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3f37516458ec1550815134937f73d6d15b434059cd10f64678a2068f65c62406", size = 247241, upload-time = "2025-08-04T00:33:26.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/ae/da397de7a42a18cea6062ed9c3b72c50b39e0b9e7b2893d7172d3333a9a1/coverage-7.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:de3c6271c482c250d3303fb5c6bdb8ca025fff20a67245e1425df04dc990ece9", size = 245274, upload-time = "2025-08-04T00:33:28.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/64/7baa895eb55ec0e1ec35b988687ecd5d4475ababb0d7ae5ca3874dd90ee7/coverage-7.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98a838101321ac3089c9bb1d4bfa967e8afed58021fda72d7880dc1997f20ae1", size = 245882, upload-time = "2025-08-04T00:33:30.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/6c/1fd76a0bd09ae75220ae9775a8290416d726f0e5ba26ea72346747161240/coverage-7.10.2-cp311-cp311-win32.whl", hash = "sha256:f2a79145a531a0e42df32d37be5af069b4a914845b6f686590739b786f2f7bce", size = 217541, upload-time = "2025-08-04T00:33:31.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/2d/8c18fb7a6e74c79fd4661e82535bc8c68aee12f46c204eabf910b097ccc9/coverage-7.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e4f5f1320f8ee0d7cfa421ceb257bef9d39fd614dd3ddcfcacd284d4824ed2c2", size = 218426, upload-time = "2025-08-04T00:33:32.976Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/40/425bb35e4ff7c7af177edf5dffd4154bc2a677b27696afe6526d75c77fec/coverage-7.10.2-cp311-cp311-win_arm64.whl", hash = "sha256:d8f2d83118f25328552c728b8e91babf93217db259ca5c2cd4dd4220b8926293", size = 217116, upload-time = "2025-08-04T00:33:34.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/1e/2c752bdbbf6f1199c59b1a10557fbb6fb3dc96b3c0077b30bd41a5922c1f/coverage-7.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:890ad3a26da9ec7bf69255b9371800e2a8da9bc223ae5d86daeb940b42247c83", size = 215311, upload-time = "2025-08-04T00:33:35.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/6a/84277d73a2cafb96e24be81b7169372ba7ff28768ebbf98e55c85a491b0f/coverage-7.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38fd1ccfca7838c031d7a7874d4353e2f1b98eb5d2a80a2fe5732d542ae25e9c", size = 215550, upload-time = "2025-08-04T00:33:37.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/e7/5358b73b46ac76f56cc2de921eeabd44fabd0b7ff82ea4f6b8c159c4d5dc/coverage-7.10.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76c1ffaaf4f6f0f6e8e9ca06f24bb6454a7a5d4ced97a1bc466f0d6baf4bd518", size = 246564, upload-time = "2025-08-04T00:33:38.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/0e/b0c901dd411cb7fc0cfcb28ef0dc6f3049030f616bfe9fc4143aecd95901/coverage-7.10.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86da8a3a84b79ead5c7d0e960c34f580bc3b231bb546627773a3f53c532c2f21", size = 248993, upload-time = "2025-08-04T00:33:39.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/4e/a876db272072a9e0df93f311e187ccdd5f39a190c6d1c1f0b6e255a0d08e/coverage-7.10.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cef9731c8a39801830a604cc53c93c9e57ea8b44953d26589499eded9576e0", size = 250454, upload-time = "2025-08-04T00:33:41.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/d6/1222dc69f8dd1be208d55708a9f4a450ad582bf4fa05320617fea1eaa6d8/coverage-7.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea58b112f2966a8b91eb13f5d3b1f8bb43c180d624cd3283fb33b1cedcc2dd75", size = 248365, upload-time = "2025-08-04T00:33:42.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/e3/40fd71151064fc315c922dd9a35e15b30616f00146db1d6a0b590553a75a/coverage-7.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:20f405188d28da9522b7232e51154e1b884fc18d0b3a10f382d54784715bbe01", size = 246562, upload-time = "2025-08-04T00:33:43.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/14/8aa93ddcd6623ddaef5d8966268ac9545b145bce4fe7b1738fd1c3f0d957/coverage-7.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:64586ce42bbe0da4d9f76f97235c545d1abb9b25985a8791857690f96e23dc3b", size = 247772, upload-time = "2025-08-04T00:33:45.068Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/4e/dcb1c01490623c61e2f2ea85cb185fa6a524265bb70eeb897d3c193efeb9/coverage-7.10.2-cp312-cp312-win32.whl", hash = "sha256:bc2e69b795d97ee6d126e7e22e78a509438b46be6ff44f4dccbb5230f550d340", size = 217710, upload-time = "2025-08-04T00:33:46.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/16/e8aab4162b5f80ad2e5e1f54b1826e2053aa2f4db508b864af647f00c239/coverage-7.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:adda2268b8cf0d11f160fad3743b4dfe9813cd6ecf02c1d6397eceaa5b45b388", size = 218499, upload-time = "2025-08-04T00:33:48.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/7f/c112ec766e8f1131ce8ce26254be028772757b2d1e63e4f6a4b0ad9a526c/coverage-7.10.2-cp312-cp312-win_arm64.whl", hash = "sha256:164429decd0d6b39a0582eaa30c67bf482612c0330572343042d0ed9e7f15c20", size = 217154, upload-time = "2025-08-04T00:33:49.299Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/04/9b7a741557f93c0ed791b854d27aa8d9fe0b0ce7bb7c52ca1b0f2619cb74/coverage-7.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186", size = 215337, upload-time = "2025-08-04T00:33:50.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/a4/8d1088cd644750c94bc305d3cf56082b4cdf7fb854a25abb23359e74892f/coverage-7.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226", size = 215596, upload-time = "2025-08-04T00:33:52.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/2f/643a8d73343f70e162d8177a3972b76e306b96239026bc0c12cfde4f7c7a/coverage-7.10.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba", size = 246145, upload-time = "2025-08-04T00:33:53.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/4a/722098d1848db4072cda71b69ede1e55730d9063bf868375264d0d302bc9/coverage-7.10.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074", size = 248492, upload-time = "2025-08-04T00:33:55.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/b0/8a6d7f326f6e3e6ed398cde27f9055e860a1e858317001835c521673fb60/coverage-7.10.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57", size = 249927, upload-time = "2025-08-04T00:33:57.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/21/1aaadd3197b54d1e61794475379ecd0f68d8fc5c2ebd352964dc6f698a3d/coverage-7.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb", size = 248138, upload-time = "2025-08-04T00:33:58.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/65/be75bafb2bdd22fd8bf9bf63cd5873b91bb26ec0d68f02d4b8b09c02decb/coverage-7.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0", size = 246111, upload-time = "2025-08-04T00:33:59.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/30/a4f0c5e249c3cc60e6c6f30d8368e372f2d380eda40e0434c192ac27ccf5/coverage-7.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a", size = 247493, upload-time = "2025-08-04T00:34:01.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/99/f09b9493e44a75cf99ca834394c12f8cb70da6c1711ee296534f97b52729/coverage-7.10.2-cp313-cp313-win32.whl", hash = "sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b", size = 217756, upload-time = "2025-08-04T00:34:03.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/bb/cbcb09103be330c7d26ff0ab05c4a8861dd2e254656fdbd3eb7600af4336/coverage-7.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe", size = 218526, upload-time = "2025-08-04T00:34:04.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/8f/8bfb4e0bca52c00ab680767c0dd8cfd928a2a72d69897d9b2d5d8b5f63f5/coverage-7.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7", size = 217176, upload-time = "2025-08-04T00:34:05.973Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/25/d458ba0bf16a8204a88d74dbb7ec5520f29937ffcbbc12371f931c11efd2/coverage-7.10.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e", size = 216058, upload-time = "2025-08-04T00:34:07.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/1c/af4dfd2d7244dc7610fed6d59d57a23ea165681cd764445dc58d71ed01a6/coverage-7.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03", size = 216273, upload-time = "2025-08-04T00:34:09.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/67/ec5095d4035c6e16368226fa9cb15f77f891194c7e3725aeefd08e7a3e5a/coverage-7.10.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0", size = 257513, upload-time = "2025-08-04T00:34:10.403Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/47/be5550b57a3a8ba797de4236b0fd31031f88397b2afc84ab3c2d4cf265f6/coverage-7.10.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0", size = 259377, upload-time = "2025-08-04T00:34:12.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/50/b12a4da1382e672305c2d17cd3029dc16b8a0470de2191dbf26b91431378/coverage-7.10.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1", size = 261516, upload-time = "2025-08-04T00:34:13.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/41/4d3296dbd33dd8da178171540ca3391af7c0184c0870fd4d4574ac290290/coverage-7.10.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1", size = 259110, upload-time = "2025-08-04T00:34:15.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/f1/b409959ecbc0cec0e61e65683b22bacaa4a3b11512f834e16dd8ffbc37db/coverage-7.10.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca", size = 257248, upload-time = "2025-08-04T00:34:16.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/ab/7076dc1c240412e9267d36ec93e9e299d7659f6a5c1e958f87e998b0fb6d/coverage-7.10.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb", size = 258063, upload-time = "2025-08-04T00:34:18.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/77/f6b51a0288f8f5f7dcc7c89abdd22cf514f3bc5151284f5cd628917f8e10/coverage-7.10.2-cp313-cp313t-win32.whl", hash = "sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824", size = 218433, upload-time = "2025-08-04T00:34:19.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/6d/547a86493e25270ce8481543e77f3a0aa3aa872c1374246b7b76273d66eb/coverage-7.10.2-cp313-cp313t-win_amd64.whl", hash = "sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3", size = 219523, upload-time = "2025-08-04T00:34:21.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/d5/3c711e38eaf9ab587edc9bed232c0298aed84e751a9f54aaa556ceaf7da6/coverage-7.10.2-cp313-cp313t-win_arm64.whl", hash = "sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f", size = 217739, upload-time = "2025-08-04T00:34:22.514Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/53/83bafa669bb9d06d4c8c6a055d8d05677216f9480c4698fb183ba7ec5e47/coverage-7.10.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6", size = 215328, upload-time = "2025-08-04T00:34:23.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/6c/30827a9c5a48a813e865fbaf91e2db25cce990bd223a022650ef2293fe11/coverage-7.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b", size = 215608, upload-time = "2025-08-04T00:34:25.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/a0/c92d85948056ddc397b72a3d79d36d9579c53cb25393ed3c40db7d33b193/coverage-7.10.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be", size = 246111, upload-time = "2025-08-04T00:34:26.857Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/cf/d695cf86b2559aadd072c91720a7844be4fb82cb4a3b642a2c6ce075692d/coverage-7.10.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1", size = 248419, upload-time = "2025-08-04T00:34:28.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/0a/03206aec4a05986e039418c038470d874045f6e00426b0c3879adc1f9251/coverage-7.10.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95", size = 250038, upload-time = "2025-08-04T00:34:30.061Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/9b/b3bd6bd52118c12bc4cf319f5baba65009c9beea84e665b6b9f03fa3f180/coverage-7.10.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46", size = 248066, upload-time = "2025-08-04T00:34:31.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/cc/bfa92e261d3e055c851a073e87ba6a3bff12a1f7134233e48a8f7d855875/coverage-7.10.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303", size = 245909, upload-time = "2025-08-04T00:34:32.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/80/c8df15db4847710c72084164f615ae900af1ec380dce7f74a5678ccdf5e1/coverage-7.10.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd", size = 247329, upload-time = "2025-08-04T00:34:34.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/6f/cb66e1f7124d5dd9ced69f889f02931419cb448125e44a89a13f4e036124/coverage-7.10.2-cp314-cp314-win32.whl", hash = "sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8", size = 218007, upload-time = "2025-08-04T00:34:35.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/e1/3d4be307278ce32c1b9d95cc02ee60d54ddab784036101d053ec9e4fe7f5/coverage-7.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3", size = 218802, upload-time = "2025-08-04T00:34:37.35Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/66/1e43bbeb66c55a5a5efec70f1c153cf90cfc7f1662ab4ebe2d844de9122c/coverage-7.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc", size = 217397, upload-time = "2025-08-04T00:34:39.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/01/ae29c129217f6110dc694a217475b8aecbb1b075d8073401f868c825fa99/coverage-7.10.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b", size = 216068, upload-time = "2025-08-04T00:34:40.648Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/50/6e9221d4139f357258f36dfa1d8cac4ec56d9d5acf5fdcc909bb016954d7/coverage-7.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4", size = 216285, upload-time = "2025-08-04T00:34:42.441Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/ec/89d1d0c0ece0d296b4588e0ef4df185200456d42a47f1141335f482c2fc5/coverage-7.10.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b", size = 257603, upload-time = "2025-08-04T00:34:43.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/06/c830af66734671c778fc49d35b58339e8f0687fbd2ae285c3f96c94da092/coverage-7.10.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de", size = 259568, upload-time = "2025-08-04T00:34:45.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/57/f280dd6f1c556ecc744fbf39e835c33d3ae987d040d64d61c6f821e87829/coverage-7.10.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca", size = 261691, upload-time = "2025-08-04T00:34:47.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2b/c63a0acbd19d99ec32326164c23df3a4e18984fb86e902afdd66ff7b3d83/coverage-7.10.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8", size = 259166, upload-time = "2025-08-04T00:34:48.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/c5/cd2997dcfcbf0683634da9df52d3967bc1f1741c1475dd0e4722012ba9ef/coverage-7.10.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4", size = 257241, upload-time = "2025-08-04T00:34:51.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/26/c9e30f82fdad8d47aee90af4978b18c88fa74369ae0f0ba0dbf08cee3a80/coverage-7.10.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed", size = 258139, upload-time = "2025-08-04T00:34:52.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/99/bdb7bd00bebcd3dedfb895fa9af8e46b91422993e4a37ac634a5f1113790/coverage-7.10.2-cp314-cp314t-win32.whl", hash = "sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0", size = 218809, upload-time = "2025-08-04T00:34:54.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/5e/56a7852e38a04d1520dda4dfbfbf74a3d6dec932c20526968f7444763567/coverage-7.10.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf", size = 219926, upload-time = "2025-08-04T00:34:55.643Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/12/7fbe6b9c52bb9d627e9556f9f2edfdbe88b315e084cdecc9afead0c3b36a/coverage-7.10.2-cp314-cp314t-win_arm64.whl", hash = "sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc", size = 217925, upload-time = "2025-08-04T00:34:57.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/d8/9b768ac73a8ac2d10c080af23937212434a958c8d2a1c84e89b450237942/coverage-7.10.2-py3-none-any.whl", hash = "sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f", size = 206973, upload-time = "2025-08-04T00:35:15.918Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -361,49 +361,49 @@ toml = [
|
|||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "45.0.5"
|
||||
version = "45.0.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949, upload-time = "2025-08-05T23:59:27.93Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/8b/34394337abe4566848a2bd49b26bcd4b07fd466afd3e8cce4cb79a390869/cryptography-45.0.5-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:206210d03c1193f4e1ff681d22885181d47efa1ab3018766a7b32a7b3d6e6afd", size = 3575762, upload-time = "2025-07-02T13:05:53.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/5d/a19441c1e89afb0f173ac13178606ca6fab0d3bd3ebc29e9ed1318b507fc/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097", size = 4140906, upload-time = "2025-07-02T13:05:55.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/db/daceb259982a3c2da4e619f45b5bfdec0e922a23de213b2636e78ef0919b/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e", size = 4374411, upload-time = "2025-07-02T13:05:57.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/35/5d06ad06402fc522c8bf7eab73422d05e789b4e38fe3206a85e3d6966c11/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30", size = 4140942, upload-time = "2025-07-02T13:06:00.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/79/020a5413347e44c382ef1f7f7e7a66817cd6273e3e6b5a72d18177b08b2f/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e", size = 4374079, upload-time = "2025-07-02T13:06:02.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/c5/c0e07d84a9a2a8a0ed4f865e58f37c71af3eab7d5e094ff1b21f3f3af3bc/cryptography-45.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b9e38e0a83cd51e07f5a48ff9691cae95a79bea28fe4ded168a8e5c6c77e819d", size = 3321362, upload-time = "2025-07-02T13:06:04.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878, upload-time = "2025-07-02T13:06:06.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702, upload-time = "2025-08-05T23:58:23.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483, upload-time = "2025-08-05T23:58:27.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679, upload-time = "2025-08-05T23:58:29.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553, upload-time = "2025-08-05T23:58:30.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499, upload-time = "2025-08-05T23:58:32.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484, upload-time = "2025-08-05T23:58:33.526Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281, upload-time = "2025-08-05T23:58:35.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890, upload-time = "2025-08-05T23:58:36.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247, upload-time = "2025-08-05T23:58:38.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045, upload-time = "2025-08-05T23:58:40.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923, upload-time = "2025-08-05T23:58:41.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805, upload-time = "2025-08-05T23:58:43.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111, upload-time = "2025-08-05T23:58:45.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169, upload-time = "2025-08-05T23:58:47.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273, upload-time = "2025-08-05T23:58:48.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211, upload-time = "2025-08-05T23:58:50.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732, upload-time = "2025-08-05T23:58:52.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655, upload-time = "2025-08-05T23:58:53.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956, upload-time = "2025-08-05T23:58:55.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859, upload-time = "2025-08-05T23:58:56.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254, upload-time = "2025-08-05T23:58:58.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815, upload-time = "2025-08-05T23:59:00.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147, upload-time = "2025-08-05T23:59:01.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459, upload-time = "2025-08-05T23:59:03.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/d2/4482d97c948c029be08cb29854a91bd2ae8da7eb9c4152461f1244dcea70/cryptography-45.0.6-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012", size = 3576812, upload-time = "2025-08-05T23:59:04.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/24/55fc238fcaa122855442604b8badb2d442367dfbd5a7ca4bb0bd346e263a/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d", size = 4141694, upload-time = "2025-08-05T23:59:06.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/7e/3ea4fa6fbe51baf3903806a0241c666b04c73d2358a3ecce09ebee8b9622/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d", size = 4375010, upload-time = "2025-08-05T23:59:08.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/42/ec5a892d82d2a2c29f80fc19ced4ba669bca29f032faf6989609cff1f8dc/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da", size = 4141377, upload-time = "2025-08-05T23:59:09.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/d7/246c4c973a22b9c2931999da953a2c19cae7c66b9154c2d62ffed811225e/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db", size = 4374609, upload-time = "2025-08-05T23:59:11.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/6d/c49ccf243f0a1b0781c2a8de8123ee552f0c8a417c6367a24d2ecb7c11b3/cryptography-45.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18", size = 3322156, upload-time = "2025-08-05T23:59:13.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/69/c252de4ec047ba2f567ecb53149410219577d408c2aea9c989acae7eafce/cryptography-45.0.6-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983", size = 3584669, upload-time = "2025-08-05T23:59:15.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/fe/deea71e9f310a31fe0a6bfee670955152128d309ea2d1c79e2a5ae0f0401/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427", size = 4153022, upload-time = "2025-08-05T23:59:16.954Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/45/a77452f5e49cb580feedba6606d66ae7b82c128947aa754533b3d1bd44b0/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b", size = 4386802, upload-time = "2025-08-05T23:59:18.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/b9/a2f747d2acd5e3075fdf5c145c7c3568895daaa38b3b0c960ef830db6cdc/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c", size = 4152706, upload-time = "2025-08-05T23:59:20.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/ec/381b3e8d0685a3f3f304a382aa3dfce36af2d76467da0fd4bb21ddccc7b2/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385", size = 4386740, upload-time = "2025-08-05T23:59:21.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/76/cf8d69da8d0b5ecb0db406f24a63a3f69ba5e791a11b782aeeefef27ccbb/cryptography-45.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043", size = 3331874, upload-time = "2025-08-05T23:59:23.017Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -591,7 +591,7 @@ grpc = [
|
|||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.177.0"
|
||||
version = "2.178.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-api-core" },
|
||||
|
|
@ -600,9 +600,9 @@ dependencies = [
|
|||
{ name = "httplib2" },
|
||||
{ name = "uritemplate" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/75/a89cad519fa8910132e3b08571d0e682ae1163643da6f963f1930f3dc788/google_api_python_client-2.177.0.tar.gz", hash = "sha256:9ffd2b57d68f5afa7e6ac64e2c440534eaa056cbb394812a62ff94723c31b50e", size = 13184405, upload-time = "2025-07-23T16:22:46.321Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/99/98/916385a87d145a27661b630c480fadf9db32bb1ad9fb1b13e8dbcbe2af70/google_api_python_client-2.178.0.tar.gz", hash = "sha256:99cba921eb471bb5973b780c653ac54d96eef8a42f1b7375b7ab98f257a4414c", size = 13282628, upload-time = "2025-08-06T14:04:51.062Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/47/f5/121248e18ca605a11720c81ae1b52a5a8cb690af9f01887c56de23cd9a5a/google_api_python_client-2.177.0-py3-none-any.whl", hash = "sha256:f2f50f11105ab883eb9b6cf38ec54ea5fd4b429249f76444bec90deba5be79b3", size = 13709470, upload-time = "2025-07-23T16:22:44.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/34/8ae31410a2d3f28b16b7135931133caf759d3aa0653f8397e344acec5a88/google_api_python_client-2.178.0-py3-none-any.whl", hash = "sha256:f420adcd050150ff1baefa817e96e1ffa16872744f53471cd34096612e580c34", size = 13809959, upload-time = "2025-08-06T14:04:47.94Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -634,7 +634,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "google-genai"
|
||||
version = "1.27.0"
|
||||
version = "1.28.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -646,9 +646,9 @@ dependencies = [
|
|||
{ name = "typing-extensions" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/37/6c0ececc3a7a629029b5beed2ceb9f28f73292236eb96272355636769b0d/google_genai-1.27.0.tar.gz", hash = "sha256:15a13ffe7b3938da50b9ab77204664d82122617256f55b5ce403d593848ef635", size = 220099, upload-time = "2025-07-23T22:00:46.145Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/f1/039bb08df4670e204c55b5da0b2fa5228dff3346bda01389a86b300f6f58/google_genai-1.28.0.tar.gz", hash = "sha256:e93053c02e616842679ba5ecce5b99db8c0ca6310623c55ff6245b5b1d293138", size = 221029, upload-time = "2025-07-30T21:39:57.002Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/12/279afe7357af73f9737a3412b6f0bc1482075b896340eb46a2f9cb0fd791/google_genai-1.27.0-py3-none-any.whl", hash = "sha256:afd6b4efaf8ec1d20a6e6657d768b68d998d60007c6e220e9024e23c913c1833", size = 218489, upload-time = "2025-07-23T22:00:44.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/ea/b704df3b348d3ae3572b0db5b52438fa426900b0830cff664107abfdba69/google_genai-1.28.0-py3-none-any.whl", hash = "sha256:7fd506799005cc87d3c5704a2eb5a2cb020d45b4d216a802e606700308f7f2f3", size = 219384, upload-time = "2025-07-30T21:39:55.652Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -734,7 +734,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "groq"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -744,9 +744,9 @@ dependencies = [
|
|||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/b1/72ca20dc9b977b7f604648e8944c77b267bddeb90d8e16bda0cf0e397844/groq-0.30.0.tar.gz", hash = "sha256:919466e48fcbebef08fed3f71debb0f96b0ea8d2ec77842c384aa843019f6e2c", size = 134928, upload-time = "2025-07-11T20:28:36.583Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/a2/77fd1460e7d55859219223719aa44ae8902a3a1ad333cd5faf330eb0b894/groq-0.31.0.tar.gz", hash = "sha256:182252e9bf0d696df607c137cbafa851d2c84aaf94bcfe9165c0bc231043490c", size = 136237, upload-time = "2025-08-05T23:14:01.183Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/b8/5b90edf9fbd795597220e3d1b5534d845e69a73ffe1fdeb967443ed2a6cf/groq-0.30.0-py3-none-any.whl", hash = "sha256:6d9609a7778ba56432f45c1bac21b005f02c6c0aca9c1c094e65536f162c1e83", size = 131056, upload-time = "2025-07-11T20:28:35.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/f8/14672d69a91495f43462c5490067eeafc30346e81bda1a62848e897f9bc3/groq-0.31.0-py3-none-any.whl", hash = "sha256:5e3c7ec9728b7cccf913da982a9b5ebb46dc18a070b35e12a3d6a1e12d6b0f7f", size = 131365, upload-time = "2025-08-05T23:13:59.768Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -822,7 +822,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "honcho"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "alembic" },
|
||||
|
|
@ -891,8 +891,8 @@ requires-dist = [
|
|||
dev = [
|
||||
{ name = "basedpyright", specifier = ">=1.29.4" },
|
||||
{ name = "coverage", specifier = ">=7.6.0" },
|
||||
{ name = "honcho-ai", specifier = ">=1.2.1" },
|
||||
{ name = "honcho-core", specifier = ">=1.2.0" },
|
||||
{ name = "honcho-ai", specifier = "==1.2.2" },
|
||||
{ name = "honcho-core", specifier = "==1.3.0" },
|
||||
{ name = "interrogate", specifier = ">=1.7.0" },
|
||||
{ name = "pre-commit", specifier = ">=4.2.0" },
|
||||
{ name = "py-spy", specifier = ">=0.3.14" },
|
||||
|
|
@ -919,7 +919,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "honcho-core"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -929,9 +929,9 @@ dependencies = [
|
|||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/60/e870902c5d247b5a0fb38401d6f3730e11eaf038f5a15c68afc4465e332f/honcho_core-1.2.0.tar.gz", hash = "sha256:1f16fd9ecd236bfc4c30ecc33354baf4bdd9a4206e84f92ec785ecd61b25d193", size = 122450, upload-time = "2025-07-16T19:59:06.362Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/49/1810de2351be1fdff330ffadcc5eef709889b2f698b0340966cae2c1eef9/honcho_core-1.3.0.tar.gz", hash = "sha256:276a73e8d523f7d22f06746922fda97e4a11fbbef4a51a0b43573bc817089cec", size = 123333, upload-time = "2025-08-06T16:42:10.941Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/99/f435c093ea2067d7da50545cfbe2037e28370541ba161f5c8df6e33030b0/honcho_core-1.2.0-py3-none-any.whl", hash = "sha256:d9260e1a2a1254c26aeec464f8bca7ebb6c2fa9b5ae568a9344b5458467d78ab", size = 110721, upload-time = "2025-07-16T19:59:05.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/3f/3beb750ed65bb1b604c585ae1c51a54f8b34ea3002f4525f93fe86549531/honcho_core-1.3.0-py3-none-any.whl", hash = "sha256:c7c0bf77b61162e6c65a580327fc054785aef794923abc35acd3dbd8287245db", size = 112895, upload-time = "2025-08-06T16:42:08.967Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1250,7 +1250,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "mirascope"
|
||||
version = "1.25.4"
|
||||
version = "1.25.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "docstring-parser" },
|
||||
|
|
@ -1258,9 +1258,9 @@ dependencies = [
|
|||
{ name = "pydantic" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/09/87a2227998cdcbe189a6444463d4e25edff210237caab05d60e7004ccaf7/mirascope-1.25.4.tar.gz", hash = "sha256:6316e988aa7286772488346dfd109b9a23796df162d426d676f74c29cd8653e5", size = 627260, upload-time = "2025-07-07T20:47:12.223Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/bf/7b2441d8740fe82fdd201bcdefacff300ac9f61245169d74d2ebfa607a25/mirascope-1.25.5.tar.gz", hash = "sha256:bcc2734e4f83d6a8d66c7fb1660a12285418ea8f4d52e8f341b8e47ee54c6667", size = 627330, upload-time = "2025-08-05T22:06:41.809Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/97/8d19eec6156dd02d2af384263426aa9f7820827af4830b6f8177e37f0af8/mirascope-1.25.4-py3-none-any.whl", hash = "sha256:ad724539f0307a8ea98425fd285969b608b62aee824967ed2a134147cb43bbf4", size = 373214, upload-time = "2025-07-07T20:47:10.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/2a/b5d57fcdaa7253cbfb6a2b3100d1e08a2133cf9f44964f8774541d09c36e/mirascope-1.25.5-py3-none-any.whl", hash = "sha256:b7f383ff6f4eeecd80f172fae51718103a473f8213859249014a65d30959226a", size = 373303, upload-time = "2025-08-05T22:06:40.077Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -1299,18 +1299,16 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "nodejs-wheel-binaries"
|
||||
version = "22.17.1"
|
||||
version = "22.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/27/97bf86b124b3e385fa39c22480d7fad934c28e6591c869ee8879260329f5/nodejs_wheel_binaries-22.17.1.tar.gz", hash = "sha256:0a8bf2a9d319988b8fa8b8b7bb5a7d986527672e6d6352735714f768af9828d0", size = 8065, upload-time = "2025-07-27T16:02:29.207Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/04/814f76f2e5f27eae05aaa6bb8840f01f378e9a441c5ccdbe7999efa7acae/nodejs_wheel_binaries-22.17.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:1f4d208c0c087a2909b6e9e6e0735da083dc997aa74e9b302703b0798b2faa4c", size = 51003618, upload-time = "2025-07-27T16:01:57.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/db/d0edcebaa420b1079e180959bc841dce638ccc3e45a76b41c4ea7318aa0d/nodejs_wheel_binaries-22.17.1-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:457ada98c6e3e03c7fd3f7d6a55572b70af7155c8dd908246373c63697226db6", size = 51936048, upload-time = "2025-07-27T16:02:01.357Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/e2/4dc2362718e880341db1105a56d7a58477c53edd36e8aac83a197ba7152d/nodejs_wheel_binaries-22.17.1-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79a87aeb2f1dfc3d36cd595921f7671a7342467f8b224b56e9049771e5ec20b", size = 57928386, upload-time = "2025-07-27T16:02:04.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/c2/9ef78ae76cfd4540c0b552f499d3f73b35bd9391ecf13bf558e8ea22cd00/nodejs_wheel_binaries-22.17.1-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05c4eafec348e439d069cd5a114f893c5f7ea898752e34a8aa43aacd39fcf9a3", size = 58461623, upload-time = "2025-07-27T16:02:08.595Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/65/7f2adb75571981615a22b4bc3852de1a8cc1cf416de12191665ce5e76119/nodejs_wheel_binaries-22.17.1-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fc9690ed95b186ef4bb8dd316e83878d016a0b6072454f8f68fa843c1016f85b", size = 59781116, upload-time = "2025-07-27T16:02:12.181Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/a9/d3c28d3626bb8639067d479bc9e30127673ce4bc477a2db2e8a4355cafa9/nodejs_wheel_binaries-22.17.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8200379c4c5ec957230285d2e7844f94de87ec0e8316b72b7a5f923cf19e88f2", size = 60832038, upload-time = "2025-07-27T16:02:16.516Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/aa/189ebcd6ad819dcacba9e117ae13f4a6d30397d12e862c6db261b63b348a/nodejs_wheel_binaries-22.17.1-py2.py3-none-win_amd64.whl", hash = "sha256:dadc1cf0d5dfacb4dbf2f339d8c070c58e48b37328a44f845de1d27fbbf2381f", size = 40120435, upload-time = "2025-07-27T16:02:23.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/9e/6761d5af600a431862d7f8236e0a3ce326d40d3b33358a6e074d97bb53c6/nodejs_wheel_binaries-22.17.1-py2.py3-none-win_arm64.whl", hash = "sha256:fde8e767272620c58cb882df104462d8f62859223dbf9ab50678d9f0c09dbbf5", size = 38864712, upload-time = "2025-07-27T16:02:25.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/6d/773e09de4a052cc75c129c3766a3cf77c36bff8504a38693b735f4a1eb55/nodejs_wheel_binaries-22.18.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b04495857755c5d5658f7ac969d84f25898fe0b0c1bdc41172e5e0ac6105ca", size = 50873051, upload-time = "2025-08-01T11:10:29.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/fc/3d6fd4ad5d26c9acd46052190d6a8895dc5050297b03d9cce03def53df0d/nodejs_wheel_binaries-22.18.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:bd4d016257d4dfe604ed526c19bd4695fdc4f4cc32e8afc4738111447aa96d03", size = 51814481, upload-time = "2025-08-01T11:10:33.086Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/f9/7be44809a861605f844077f9e731a117b669d5ca6846a7820e7dd82c9fad/nodejs_wheel_binaries-22.18.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b125f94f3f5e8ab9560d3bd637497f02e45470aeea74cf6fe60afe751cfa5f", size = 57804907, upload-time = "2025-08-01T11:10:36.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/67/563e74a0dff653ec7ddee63dc49b3f37a20df39f23675cfc801d7e8e4bb7/nodejs_wheel_binaries-22.18.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bbb81b6e67c15f04e2a9c6c220d7615fb46ae8f1ad388df0d66abac6bed5f8", size = 58335587, upload-time = "2025-08-01T11:10:40.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/b1/ec45fefef60223dd40e7953e2ff087964e200d6ec2d04eae0171d6428679/nodejs_wheel_binaries-22.18.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5d3ea8b7f957ae16b73241451f6ce831d6478156f363cce75c7ea71cbe6c6f7", size = 59662356, upload-time = "2025-08-01T11:10:44.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/ed/6de2c73499eebf49d0d20e0704f64566029a3441c48cd4f655d49befd28b/nodejs_wheel_binaries-22.18.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bcda35b07677039670102a6f9b78c2313fd526111d407cb7ffc2a4c243a48ef9", size = 60706806, upload-time = "2025-08-01T11:10:48.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/f5/487434b1792c4f28c63876e4a896f2b6e953e2dc1f0b3940e912bd087755/nodejs_wheel_binaries-22.18.0-py2.py3-none-win_amd64.whl", hash = "sha256:0f55e72733f1df2f542dce07f35145ac2e125408b5e2051cac08e5320e41b4d1", size = 39998139, upload-time = "2025-08-01T11:10:52.676Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1465,7 +1463,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.97.1"
|
||||
version = "1.99.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -1477,9 +1475,9 @@ dependencies = [
|
|||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/57/1c471f6b3efb879d26686d31582997615e969f3bb4458111c9705e56332e/openai-1.97.1.tar.gz", hash = "sha256:a744b27ae624e3d4135225da9b1c89c107a2a7e5bc4c93e5b7b5214772ce7a4e", size = 494267, upload-time = "2025-07-22T13:10:12.607Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/03/30/f0fb7907a77e733bb801c7bdcde903500b31215141cdb261f04421e6fbec/openai-1.99.1.tar.gz", hash = "sha256:2c9d8e498c298f51bb94bcac724257a3a6cac6139ccdfc1186c6708f7a93120f", size = 497075, upload-time = "2025-08-05T19:42:36.131Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/35/412a0e9c3f0d37c94ed764b8ac7adae2d834dbd20e69f6aca582118e0f55/openai-1.97.1-py3-none-any.whl", hash = "sha256:4e96bbdf672ec3d44968c9ea39d2c375891db1acc1794668d8149d5fa6000606", size = 764380, upload-time = "2025-07-22T13:10:10.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/15/9c85154ffd283abfc43309ff3aaa63c3fd02f7767ee684e73670f6c5ade2/openai-1.99.1-py3-none-any.whl", hash = "sha256:8eeccc69e0ece1357b51ca0d9fb21324afee09b20c3e5b547d02445ca18a4e03", size = 767827, upload-time = "2025-08-05T19:42:34.192Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1730,17 +1728,17 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "py-spy"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/cd/9dacc04604dc4398ce5bed77ed59918ad0940f15165954d4aaa651cc640c/py_spy-0.4.0.tar.gz", hash = "sha256:806602ce7972782cc9c1e383f339bfc27bfb822d42485e6a3e0530ae5040e1f0", size = 253236, upload-time = "2024-11-01T19:08:51.487Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/e2/ff811a367028b87e86714945bb9ecb5c1cc69114a8039a67b3a862cef921/py_spy-0.4.1.tar.gz", hash = "sha256:e53aa53daa2e47c2eef97dd2455b47bb3a7e7f962796a86cc3e7dbde8e6f4db4", size = 244726, upload-time = "2025-07-31T19:33:25.172Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/7e/02ca3ee68507db47afce769504060d71b4dc1455f0f9faa8d32fc7762221/py_spy-0.4.0-py2.py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f2cf3f7130e7d780471faa5957441d3b4e0ec39a79b2c00f4c33d494f7728428", size = 3617847, upload-time = "2024-11-01T19:08:37.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/7c/d9e26cc4c8e91f96a3a65de04d2e2e4131fbcaf6830d10917d4fab9d6788/py_spy-0.4.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:47cdda4c34d9b6cb01f3aaeceb2e88faf57da880207fe72ff6ff97e9bb6cc8a9", size = 1761955, upload-time = "2024-11-01T19:08:39.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/e4/8fbfd219b7f282b80e6b2e74c9197850d2c51db8555705567bb65507b060/py_spy-0.4.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eee3d0bde85ca5cf4f01f012d461180ca76c24835a96f7b5c4ded64eb6a008ab", size = 2059471, upload-time = "2024-11-01T19:08:41.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/1d/79a94a5ace810c13b730ce96765ca465c171b4952034f1be7402d8accbc1/py_spy-0.4.0-py2.py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5f06ffce4c9c98b7fc9f5e67e5e7db591173f1351837633f3f23d9378b1d18a", size = 2067486, upload-time = "2024-11-01T19:08:43.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/90/fbbb038f826a83ed15ebc4ae606815d6cad6c5c6399c86c7ab96f6c60817/py_spy-0.4.0-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87573e64dbfdfc89ba2e0f5e2f525aa84e0299c7eb6454b47ea335fde583a7a0", size = 2141433, upload-time = "2024-11-01T19:08:45.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/c1/5e012669ebb687e546dc99fcfc4861ebfcf3a337b7a41af945df23140bb5/py_spy-0.4.0-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8bf2f3702cef367a489faa45177b41a6c31b2a3e5bd78c978d44e29340152f5a", size = 2732951, upload-time = "2024-11-01T19:08:48.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/8b/dd8490660019a6b0be28d9ffd2bf1db967604b19f3f2719c0e283a16ac7f/py_spy-0.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:77d8f637ade38367d944874776f45b703b7ac5938b1f7be8891f3a5876ddbb96", size = 1810770, upload-time = "2024-11-01T19:08:50.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/e3/3a32500d845bdd94f6a2b4ed6244982f42ec2bc64602ea8fcfe900678ae7/py_spy-0.4.1-py2.py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:809094208c6256c8f4ccadd31e9a513fe2429253f48e20066879239ba12cd8cc", size = 3682508, upload-time = "2025-07-31T19:33:13.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/bf/e4d280e9e0bec71d39fc646654097027d4bbe8e04af18fb68e49afcff404/py_spy-0.4.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:1fb8bf71ab8df95a95cc387deed6552934c50feef2cf6456bc06692a5508fd0c", size = 1796395, upload-time = "2025-07-31T19:33:15.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/79/9ed50bb0a9de63ed023aa2db8b6265b04a7760d98c61eb54def6a5fddb68/py_spy-0.4.1-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee776b9d512a011d1ad3907ed53ae32ce2f3d9ff3e1782236554e22103b5c084", size = 2034938, upload-time = "2025-07-31T19:33:17.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/a5/36862e3eea59f729dfb70ee6f9e14b051d8ddce1aa7e70e0b81d9fe18536/py_spy-0.4.1-py2.py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:532d3525538254d1859b49de1fbe9744df6b8865657c9f0e444bf36ce3f19226", size = 2658968, upload-time = "2025-07-31T19:33:18.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/f8/9ea0b586b065a623f591e5e7961282ec944b5fbbdca33186c7c0296645b3/py_spy-0.4.1-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4972c21890b6814017e39ac233c22572c4a61fd874524ebc5ccab0f2237aee0a", size = 2147541, upload-time = "2025-07-31T19:33:20.565Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/fb/bc7f639aed026bca6e7beb1e33f6951e16b7d315594e7635a4f7d21d63f4/py_spy-0.4.1-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6a80ec05eb8a6883863a367c6a4d4f2d57de68466f7956b6367d4edd5c61bb29", size = 2763338, upload-time = "2025-07-31T19:33:22.202Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/da/fcc9a9fcd4ca946ff402cff20348e838b051d69f50f5d1f5dca4cd3c5eb8/py_spy-0.4.1-py2.py3-none-win_amd64.whl", hash = "sha256:d92e522bd40e9bf7d87c204033ce5bb5c828fca45fa28d970f58d71128069fdc", size = 1818784, upload-time = "2025-07-31T19:33:23.802Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2050,71 +2048,81 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.11.6"
|
||||
version = "2025.7.34"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/de/e13fa6dc61d78b30ba47481f99933a3b49a57779d625c392d8036770a60d/regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a", size = 400714, upload-time = "2025-07-31T00:21:16.262Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674, upload-time = "2024-11-06T20:08:57.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684, upload-time = "2024-11-06T20:08:59.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589, upload-time = "2024-11-06T20:09:01.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511, upload-time = "2024-11-06T20:09:04.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149, upload-time = "2024-11-06T20:09:06.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707, upload-time = "2024-11-06T20:09:07.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702, upload-time = "2024-11-06T20:09:10.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976, upload-time = "2024-11-06T20:09:11.566Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397, upload-time = "2024-11-06T20:09:13.119Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726, upload-time = "2024-11-06T20:09:14.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098, upload-time = "2024-11-06T20:09:16.504Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325, upload-time = "2024-11-06T20:09:18.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277, upload-time = "2024-11-06T20:09:21.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197, upload-time = "2024-11-06T20:09:24.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714, upload-time = "2024-11-06T20:09:26.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042, upload-time = "2024-11-06T20:09:28.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload-time = "2024-11-06T20:09:31.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload-time = "2024-11-06T20:09:32.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload-time = "2024-11-06T20:09:35.504Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload-time = "2024-11-06T20:09:37.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload-time = "2024-11-06T20:09:40.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload-time = "2024-11-06T20:09:43.059Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload-time = "2024-11-06T20:09:48.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload-time = "2024-11-06T20:09:49.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload-time = "2024-11-06T20:09:51.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload-time = "2024-11-06T20:09:53.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload-time = "2024-11-06T20:09:56.222Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload-time = "2024-11-06T20:09:58.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload-time = "2024-11-06T20:10:00.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload-time = "2024-11-06T20:10:03.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload-time = "2024-11-06T20:10:05.179Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781, upload-time = "2024-11-06T20:10:07.07Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455, upload-time = "2024-11-06T20:10:09.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759, upload-time = "2024-11-06T20:10:11.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976, upload-time = "2024-11-06T20:10:13.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077, upload-time = "2024-11-06T20:10:15.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160, upload-time = "2024-11-06T20:10:19.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896, upload-time = "2024-11-06T20:10:21.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997, upload-time = "2024-11-06T20:10:24.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725, upload-time = "2024-11-06T20:10:28.067Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481, upload-time = "2024-11-06T20:10:31.612Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896, upload-time = "2024-11-06T20:10:34.054Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138, upload-time = "2024-11-06T20:10:36.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692, upload-time = "2024-11-06T20:10:38.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135, upload-time = "2024-11-06T20:10:40.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567, upload-time = "2024-11-06T20:10:43.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/d2/0a44a9d92370e5e105f16669acf801b215107efea9dea4317fe96e9aad67/regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6", size = 484591, upload-time = "2025-07-31T00:18:46.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/b1/00c4f83aa902f1048495de9f2f33638ce970ce1cf9447b477d272a0e22bb/regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83", size = 289293, upload-time = "2025-07-31T00:18:53.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/b0/5bc5c8ddc418e8be5530b43ae1f7c9303f43aeff5f40185c4287cf6732f2/regex-2025.7.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95b4639c77d414efa93c8de14ce3f7965a94d007e068a94f9d4997bb9bd9c81f", size = 285932, upload-time = "2025-07-31T00:18:54.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/c7/a1a28d050b23665a5e1eeb4d7f13b83ea86f0bc018da7b8f89f86ff7f094/regex-2025.7.34-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7de1ceed5a5f84f342ba4a9f4ae589524adf9744b2ee61b5da884b5b659834", size = 780361, upload-time = "2025-07-31T00:18:56.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/0d/82e7afe7b2c9fe3d488a6ab6145d1d97e55f822dfb9b4569aba2497e3d09/regex-2025.7.34-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02e5860a250cd350c4933cf376c3bc9cb28948e2c96a8bc042aee7b985cfa26f", size = 849176, upload-time = "2025-07-31T00:18:57.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/16/3036e16903d8194f1490af457a7e33b06d9e9edd9576b1fe6c7ac660e9ed/regex-2025.7.34-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a5966220b9a1a88691282b7e4350e9599cf65780ca60d914a798cb791aa1177", size = 897222, upload-time = "2025-07-31T00:18:58.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/c2/010e089ae00d31418e7d2c6601760eea1957cde12be719730c7133b8c165/regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48fb045bbd4aab2418dc1ba2088a5e32de4bfe64e1457b948bb328a8dc2f1c2e", size = 789831, upload-time = "2025-07-31T00:19:00.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/86/b312b7bf5c46d21dbd9a3fdc4a80fde56ea93c9c0b89cf401879635e094d/regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20ff8433fa45e131f7316594efe24d4679c5449c0ca69d91c2f9d21846fdf064", size = 780665, upload-time = "2025-07-31T00:19:01.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/e5/674b82bfff112c820b09e3c86a423d4a568143ede7f8440fdcbce259e895/regex-2025.7.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c436fd1e95c04c19039668cfb548450a37c13f051e8659f40aed426e36b3765f", size = 773511, upload-time = "2025-07-31T00:19:03.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/18/39e7c578eb6cf1454db2b64e4733d7e4f179714867a75d84492ec44fa9b2/regex-2025.7.34-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b85241d3cfb9f8a13cefdfbd58a2843f208f2ed2c88181bf84e22e0c7fc066d", size = 843990, upload-time = "2025-07-31T00:19:05.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/d9/522a6715aefe2f463dc60c68924abeeb8ab6893f01adf5720359d94ede8c/regex-2025.7.34-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:075641c94126b064c65ab86e7e71fc3d63e7ff1bea1fb794f0773c97cdad3a03", size = 834676, upload-time = "2025-07-31T00:19:07.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/53/c4d5284cb40543566542e24f1badc9f72af68d01db21e89e36e02292eee0/regex-2025.7.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70645cad3407d103d1dbcb4841839d2946f7d36cf38acbd40120fee1682151e5", size = 778420, upload-time = "2025-07-31T00:19:08.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/4a/b779a7707d4a44a7e6ee9d0d98e40b2a4de74d622966080e9c95e25e2d24/regex-2025.7.34-cp310-cp310-win32.whl", hash = "sha256:3b836eb4a95526b263c2a3359308600bd95ce7848ebd3c29af0c37c4f9627cd3", size = 263999, upload-time = "2025-07-31T00:19:10.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/6e/33c7583f5427aa039c28bff7f4103c2de5b6aa5b9edc330c61ec576b1960/regex-2025.7.34-cp310-cp310-win_amd64.whl", hash = "sha256:cbfaa401d77334613cf434f723c7e8ba585df162be76474bccc53ae4e5520b3a", size = 276023, upload-time = "2025-07-31T00:19:11.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/fc/00b32e0ac14213d76d806d952826402b49fd06d42bfabacdf5d5d016bc47/regex-2025.7.34-cp310-cp310-win_arm64.whl", hash = "sha256:bca11d3c38a47c621769433c47f364b44e8043e0de8e482c5968b20ab90a3986", size = 268357, upload-time = "2025-07-31T00:19:12.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/85/f497b91577169472f7c1dc262a5ecc65e39e146fc3a52c571e5daaae4b7d/regex-2025.7.34-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da304313761b8500b8e175eb2040c4394a875837d5635f6256d6fa0377ad32c8", size = 484594, upload-time = "2025-07-31T00:19:13.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/c5/ad2a5c11ce9e6257fcbfd6cd965d07502f6054aaa19d50a3d7fd991ec5d1/regex-2025.7.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:35e43ebf5b18cd751ea81455b19acfdec402e82fe0dc6143edfae4c5c4b3909a", size = 289294, upload-time = "2025-07-31T00:19:15.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/01/83ffd9641fcf5e018f9b51aa922c3e538ac9439424fda3df540b643ecf4f/regex-2025.7.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96bbae4c616726f4661fe7bcad5952e10d25d3c51ddc388189d8864fbc1b3c68", size = 285933, upload-time = "2025-07-31T00:19:16.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/20/5edab2e5766f0259bc1da7381b07ce6eb4401b17b2254d02f492cd8a81a8/regex-2025.7.34-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9feab78a1ffa4f2b1e27b1bcdaad36f48c2fed4870264ce32f52a393db093c78", size = 792335, upload-time = "2025-07-31T00:19:18.561Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/bd/744d3ed8777dce8487b2606b94925e207e7c5931d5870f47f5b643a4580a/regex-2025.7.34-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f14b36e6d4d07f1a5060f28ef3b3561c5d95eb0651741474ce4c0a4c56ba8719", size = 858605, upload-time = "2025-07-31T00:19:20.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/3d/93754176289718d7578c31d151047e7b8acc7a8c20e7706716f23c49e45e/regex-2025.7.34-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85c3a958ef8b3d5079c763477e1f09e89d13ad22198a37e9d7b26b4b17438b33", size = 905780, upload-time = "2025-07-31T00:19:21.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/2e/c689f274a92deffa03999a430505ff2aeace408fd681a90eafa92fdd6930/regex-2025.7.34-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37555e4ae0b93358fa7c2d240a4291d4a4227cc7c607d8f85596cdb08ec0a083", size = 798868, upload-time = "2025-07-31T00:19:23.222Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/9e/39673688805d139b33b4a24851a71b9978d61915c4d72b5ffda324d0668a/regex-2025.7.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee38926f31f1aa61b0232a3a11b83461f7807661c062df9eb88769d86e6195c3", size = 781784, upload-time = "2025-07-31T00:19:24.59Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/bd/4c1cab12cfabe14beaa076523056b8ab0c882a8feaf0a6f48b0a75dab9ed/regex-2025.7.34-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a664291c31cae9c4a30589bd8bc2ebb56ef880c9c6264cb7643633831e606a4d", size = 852837, upload-time = "2025-07-31T00:19:25.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/21/663d983cbb3bba537fc213a579abbd0f263fb28271c514123f3c547ab917/regex-2025.7.34-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f3e5c1e0925e77ec46ddc736b756a6da50d4df4ee3f69536ffb2373460e2dafd", size = 844240, upload-time = "2025-07-31T00:19:27.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/2d/9beeeb913bc5d32faa913cf8c47e968da936af61ec20af5d269d0f84a100/regex-2025.7.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d428fc7731dcbb4e2ffe43aeb8f90775ad155e7db4347a639768bc6cd2df881a", size = 787139, upload-time = "2025-07-31T00:19:29.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/f5/9b9384415fdc533551be2ba805dd8c4621873e5df69c958f403bfd3b2b6e/regex-2025.7.34-cp311-cp311-win32.whl", hash = "sha256:e154a7ee7fa18333ad90b20e16ef84daaeac61877c8ef942ec8dfa50dc38b7a1", size = 264019, upload-time = "2025-07-31T00:19:31.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/9d/e069ed94debcf4cc9626d652a48040b079ce34c7e4fb174f16874958d485/regex-2025.7.34-cp311-cp311-win_amd64.whl", hash = "sha256:24257953d5c1d6d3c129ab03414c07fc1a47833c9165d49b954190b2b7f21a1a", size = 276047, upload-time = "2025-07-31T00:19:32.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/cf/3bafbe9d1fd1db77355e7fbbbf0d0cfb34501a8b8e334deca14f94c7b315/regex-2025.7.34-cp311-cp311-win_arm64.whl", hash = "sha256:3157aa512b9e606586900888cd469a444f9b898ecb7f8931996cb715f77477f0", size = 268362, upload-time = "2025-07-31T00:19:34.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/f0/31d62596c75a33f979317658e8d261574785c6cd8672c06741ce2e2e2070/regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50", size = 485492, upload-time = "2025-07-31T00:19:35.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/16/b818d223f1c9758c3434be89aa1a01aae798e0e0df36c1f143d1963dd1ee/regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f", size = 290000, upload-time = "2025-07-31T00:19:37.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/70/69506d53397b4bd6954061bae75677ad34deb7f6ca3ba199660d6f728ff5/regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130", size = 286072, upload-time = "2025-07-31T00:19:38.612Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/73/536a216d5f66084fb577bb0543b5cb7de3272eb70a157f0c3a542f1c2551/regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46", size = 797341, upload-time = "2025-07-31T00:19:40.119Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/af/733f8168449e56e8f404bb807ea7189f59507cbea1b67a7bbcd92f8bf844/regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4", size = 862556, upload-time = "2025-07-31T00:19:41.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/dd/59c464d58c06c4f7d87de4ab1f590e430821345a40c5d345d449a636d15f/regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0", size = 910762, upload-time = "2025-07-31T00:19:43Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a8/b05ccf33ceca0815a1e253693b2c86544932ebcc0049c16b0fbdf18b688b/regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b", size = 801892, upload-time = "2025-07-31T00:19:44.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/9a/b993cb2e634cc22810afd1652dba0cae156c40d4864285ff486c73cd1996/regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01", size = 786551, upload-time = "2025-07-31T00:19:46.127Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/79/7849d67910a0de4e26834b5bb816e028e35473f3d7ae563552ea04f58ca2/regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77", size = 856457, upload-time = "2025-07-31T00:19:47.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/c6/de516bc082524b27e45cb4f54e28bd800c01efb26d15646a65b87b13a91e/regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da", size = 848902, upload-time = "2025-07-31T00:19:49.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/22/519ff8ba15f732db099b126f039586bd372da6cd4efb810d5d66a5daeda1/regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282", size = 788038, upload-time = "2025-07-31T00:19:50.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/7d/aabb467d8f57d8149895d133c88eb809a1a6a0fe262c1d508eb9dfabb6f9/regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588", size = 264417, upload-time = "2025-07-31T00:19:52.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/39/bd922b55a4fc5ad5c13753274e5b536f5b06ec8eb9747675668491c7ab7a/regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62", size = 275387, upload-time = "2025-07-31T00:19:53.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/3c/c61d2fdcecb754a40475a3d1ef9a000911d3e3fc75c096acf44b0dfb786a/regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176", size = 268482, upload-time = "2025-07-31T00:19:55.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/16/b709b2119975035169a25aa8e4940ca177b1a2e25e14f8d996d09130368e/regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5", size = 485334, upload-time = "2025-07-31T00:19:56.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/a6/c09136046be0595f0331bc58a0e5f89c2d324cf734e0b0ec53cf4b12a636/regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd", size = 289942, upload-time = "2025-07-31T00:19:57.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/91/08fc0fd0f40bdfb0e0df4134ee37cfb16e66a1044ac56d36911fd01c69d2/regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b", size = 285991, upload-time = "2025-07-31T00:19:59.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/2f/99dc8f6f756606f0c214d14c7b6c17270b6bbe26d5c1f05cde9dbb1c551f/regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad", size = 797415, upload-time = "2025-07-31T00:20:01.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/cf/2fcdca1110495458ba4e95c52ce73b361cf1cafd8a53b5c31542cde9a15b/regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59", size = 862487, upload-time = "2025-07-31T00:20:03.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/38/899105dd27fed394e3fae45607c1983e138273ec167e47882fc401f112b9/regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415", size = 910717, upload-time = "2025-07-31T00:20:04.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/f6/4716198dbd0bcc9c45625ac4c81a435d1c4d8ad662e8576dac06bab35b17/regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f", size = 801943, upload-time = "2025-07-31T00:20:07.1Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/5d/cff8896d27e4e3dd11dd72ac78797c7987eb50fe4debc2c0f2f1682eb06d/regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1", size = 786664, upload-time = "2025-07-31T00:20:08.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/29/758bf83cf7b4c34f07ac3423ea03cee3eb3176941641e4ccc05620f6c0b8/regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c", size = 856457, upload-time = "2025-07-31T00:20:10.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/30/c19d212b619963c5b460bfed0ea69a092c6a43cba52a973d46c27b3e2975/regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a", size = 849008, upload-time = "2025-07-31T00:20:11.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/b8/3c35da3b12c87e3cc00010ef6c3a4ae787cff0bc381aa3d251def219969a/regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0", size = 788101, upload-time = "2025-07-31T00:20:13.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/80/2f46677c0b3c2b723b2c358d19f9346e714113865da0f5f736ca1a883bde/regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1", size = 264401, upload-time = "2025-07-31T00:20:15.233Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/fa/917d64dd074682606a003cba33585c28138c77d848ef72fc77cbb1183849/regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997", size = 275368, upload-time = "2025-07-31T00:20:16.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/cd/f94383666704170a2154a5df7b16be28f0c27a266bffcd843e58bc84120f/regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f", size = 268482, upload-time = "2025-07-31T00:20:18.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/23/6376f3a23cf2f3c00514b1cdd8c990afb4dfbac3cb4a68b633c6b7e2e307/regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a", size = 485385, upload-time = "2025-07-31T00:20:19.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/5b/6d4d3a0b4d312adbfd6d5694c8dddcf1396708976dd87e4d00af439d962b/regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435", size = 289788, upload-time = "2025-07-31T00:20:21.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/71/5862ac9913746e5054d01cb9fb8125b3d0802c0706ef547cae1e7f4428fa/regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac", size = 286136, upload-time = "2025-07-31T00:20:26.146Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/df/5b505dc447eb71278eba10d5ec940769ca89c1af70f0468bfbcb98035dc2/regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72", size = 797753, upload-time = "2025-07-31T00:20:27.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/38/3e3dc953d13998fa047e9a2414b556201dbd7147034fbac129392363253b/regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e", size = 863263, upload-time = "2025-07-31T00:20:29.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/e5/3ff66b29dde12f5b874dda2d9dec7245c2051f2528d8c2a797901497f140/regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751", size = 910103, upload-time = "2025-07-31T00:20:31.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/fe/14176f2182125977fba3711adea73f472a11f3f9288c1317c59cd16ad5e6/regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4", size = 801709, upload-time = "2025-07-31T00:20:33.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/0d/80d4e66ed24f1ba876a9e8e31b709f9fd22d5c266bf5f3ab3c1afe683d7d/regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98", size = 786726, upload-time = "2025-07-31T00:20:35.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/75/c3ebb30e04a56c046f5c85179dc173818551037daae2c0c940c7b19152cb/regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7", size = 857306, upload-time = "2025-07-31T00:20:37.12Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/b2/a4dc5d8b14f90924f27f0ac4c4c4f5e195b723be98adecc884f6716614b6/regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47", size = 848494, upload-time = "2025-07-31T00:20:38.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/21/9ac6e07a4c5e8646a90b56b61f7e9dac11ae0747c857f91d3d2bc7c241d9/regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e", size = 787850, upload-time = "2025-07-31T00:20:40.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/6c/d51204e28e7bc54f9a03bb799b04730d7e54ff2718862b8d4e09e7110a6a/regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb", size = 269730, upload-time = "2025-07-31T00:20:42.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/52/a7e92d02fa1fdef59d113098cb9f02c5d03289a0e9f9e5d4d6acccd10677/regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae", size = 278640, upload-time = "2025-07-31T00:20:44.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/78/a815529b559b1771080faa90c3ab401730661f99d495ab0071649f139ebd/regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64", size = 271757, upload-time = "2025-07-31T00:20:46.355Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2267,40 +2275,40 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.5"
|
||||
version = "0.12.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/30/cd/01015eb5034605fd98d829c5839ec2c6b4582b479707f7c1c2af861e8258/ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e", size = 5170722, upload-time = "2025-07-24T13:26:37.456Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814, upload-time = "2025-07-29T22:32:35.877Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/de/ad2f68f0798ff15dd8c0bcc2889558970d9a685b3249565a937cd820ad34/ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92", size = 11819133, upload-time = "2025-07-24T13:25:56.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/fc/c6b65cd0e7fbe60f17e7ad619dca796aa49fbca34bb9bea5f8faf1ec2643/ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a", size = 12501114, upload-time = "2025-07-24T13:25:59.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/de/c6bec1dce5ead9f9e6a946ea15e8d698c35f19edc508289d70a577921b30/ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf", size = 11716873, upload-time = "2025-07-24T13:26:01.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/16/cf372d2ebe91e4eb5b82a2275c3acfa879e0566a7ac94d331ea37b765ac8/ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73", size = 11958829, upload-time = "2025-07-24T13:26:03.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/bf/cd07e8f6a3a6ec746c62556b4c4b79eeb9b0328b362bb8431b7b8afd3856/ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac", size = 11626619, upload-time = "2025-07-24T13:26:06.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/c9/c2ccb3b8cbb5661ffda6925f81a13edbb786e623876141b04919d1128370/ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08", size = 13221894, upload-time = "2025-07-24T13:26:08.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/58/68a5be2c8e5590ecdad922b2bcd5583af19ba648f7648f95c51c3c1eca81/ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4", size = 14163909, upload-time = "2025-07-24T13:26:10.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/d1/ef6b19622009ba8386fdb792c0743f709cf917b0b2f1400589cbe4739a33/ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b", size = 13583652, upload-time = "2025-07-24T13:26:13.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/e3/1c98c566fe6809a0c83751d825a03727f242cdbe0d142c9e292725585521/ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a", size = 12700451, upload-time = "2025-07-24T13:26:15.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ff/96058f6506aac0fbc0d0fc0d60b0d0bd746240a0594657a2d94ad28033ba/ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a", size = 12937465, upload-time = "2025-07-24T13:26:17.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/d3/68bc5e7ab96c94b3589d1789f2dd6dd4b27b263310019529ac9be1e8f31b/ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5", size = 11771136, upload-time = "2025-07-24T13:26:20.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/75/7356af30a14584981cabfefcf6106dea98cec9a7af4acb5daaf4b114845f/ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293", size = 11601644, upload-time = "2025-07-24T13:26:22.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/67/91c71d27205871737cae11025ee2b098f512104e26ffd8656fd93d0ada0a/ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb", size = 12478068, upload-time = "2025-07-24T13:26:26.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/04/b6b00383cf2f48e8e78e14eb258942fdf2a9bf0287fbf5cdd398b749193a/ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb", size = 12991537, upload-time = "2025-07-24T13:26:28.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/b9/053d6445dc7544fb6594785056d8ece61daae7214859ada4a152ad56b6e0/ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9", size = 11751575, upload-time = "2025-07-24T13:26:30.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/0f/ab16e8259493137598b9149734fec2e06fdeda9837e6f634f5c4e35916da/ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5", size = 12882273, upload-time = "2025-07-24T13:26:32.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564, upload-time = "2025-07-24T13:26:34.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189, upload-time = "2025-07-29T22:31:41.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389, upload-time = "2025-07-29T22:31:54.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384, upload-time = "2025-07-29T22:31:59.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759, upload-time = "2025-07-29T22:32:01.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028, upload-time = "2025-07-29T22:32:04.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209, upload-time = "2025-07-29T22:32:06.952Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353, upload-time = "2025-07-29T22:32:10.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555, upload-time = "2025-07-29T22:32:12.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556, upload-time = "2025-07-29T22:32:15.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784, upload-time = "2025-07-29T22:32:17.69Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356, upload-time = "2025-07-29T22:32:20.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124, upload-time = "2025-07-29T22:32:22.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945, upload-time = "2025-07-29T22:32:24.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677, upload-time = "2025-07-29T22:32:27.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687, upload-time = "2025-07-29T22:32:29.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365, upload-time = "2025-07-29T22:32:31.517Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.33.2"
|
||||
version = "2.34.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/82/dfe4a91fd38e048fbb55ca6c072710408e8802015aa27cde18e8684bb1e9/sentry_sdk-2.33.2.tar.gz", hash = "sha256:e85002234b7b8efac9b74c2d91dbd4f8f3970dc28da8798e39530e65cb740f94", size = 335804, upload-time = "2025-07-22T10:41:18.578Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/38/10d6bfe23df1bfc65ac2262ed10b45823f47f810b0057d3feeea1ca5c7ed/sentry_sdk-2.34.1.tar.gz", hash = "sha256:69274eb8c5c38562a544c3e9f68b5be0a43be4b697f5fd385bf98e4fbe672687", size = 336969, upload-time = "2025-07-30T11:13:37.93Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/dc/4d825d5eb6e924dfcc6a91c8185578a7b0a5c41fd2416a6f49c8226d6ef9/sentry_sdk-2.33.2-py2.py3-none-any.whl", hash = "sha256:8d57a3b4861b243aa9d558fda75509ad487db14f488cbdb6c78c614979d77632", size = 356692, upload-time = "2025-07-22T10:41:16.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/3e/bb34de65a5787f76848a533afbb6610e01fbcdd59e76d8679c254e02255c/sentry_sdk-2.34.1-py2.py3-none-any.whl", hash = "sha256:b7a072e1cdc5abc48101d5146e1ae680fa81fe886d8d95aaa25a0b450c818d32", size = 357743, upload-time = "2025-07-30T11:13:36.145Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -2334,47 +2342,47 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.41"
|
||||
version = "2.0.42"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/03/a0af991e3a43174d6b83fca4fb399745abceddd1171bdabae48ce877ff47/sqlalchemy-2.0.42.tar.gz", hash = "sha256:160bedd8a5c28765bd5be4dec2d881e109e33b34922e50a3b881a7681773ac5f", size = 9749972, upload-time = "2025-07-29T12:48:09.323Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/12/d7c445b1940276a828efce7331cb0cb09d6e5f049651db22f4ebb0922b77/sqlalchemy-2.0.41-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1f09b6821406ea1f94053f346f28f8215e293344209129a9c0fcc3578598d7b", size = 2117967, upload-time = "2025-05-14T17:48:15.841Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/b8/cb90f23157e28946b27eb01ef401af80a1fab7553762e87df51507eaed61/sqlalchemy-2.0.41-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1936af879e3db023601196a1684d28e12f19ccf93af01bf3280a3262c4b6b4e5", size = 2107583, upload-time = "2025-05-14T17:48:18.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/c2/eef84283a1c8164a207d898e063edf193d36a24fb6a5bb3ce0634b92a1e8/sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2ac41acfc8d965fb0c464eb8f44995770239668956dc4cdf502d1b1ffe0d747", size = 3186025, upload-time = "2025-05-14T17:51:51.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/72/49d52bd3c5e63a1d458fd6d289a1523a8015adedbddf2c07408ff556e772/sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c24e0c0fde47a9723c81d5806569cddef103aebbf79dbc9fcbb617153dea30", size = 3186259, upload-time = "2025-05-14T17:55:22.526Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/9e/e3ffc37d29a3679a50b6bbbba94b115f90e565a2b4545abb17924b94c52d/sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23a8825495d8b195c4aa9ff1c430c28f2c821e8c5e2d98089228af887e5d7e29", size = 3126803, upload-time = "2025-05-14T17:51:53.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/76/56b21e363f6039978ae0b72690237b38383e4657281285a09456f313dd77/sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:60c578c45c949f909a4026b7807044e7e564adf793537fc762b2489d522f3d11", size = 3148566, upload-time = "2025-05-14T17:55:24.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/92/11b8e1b69bf191bc69e300a99badbbb5f2f1102f2b08b39d9eee2e21f565/sqlalchemy-2.0.41-cp310-cp310-win32.whl", hash = "sha256:118c16cd3f1b00c76d69343e38602006c9cfb9998fa4f798606d28d63f23beda", size = 2086696, upload-time = "2025-05-14T17:55:59.136Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/88/2d706c9cc4502654860f4576cd54f7db70487b66c3b619ba98e0be1a4642/sqlalchemy-2.0.41-cp310-cp310-win_amd64.whl", hash = "sha256:7492967c3386df69f80cf67efd665c0f667cee67032090fe01d7d74b0e19bb08", size = 2110200, upload-time = "2025-05-14T17:56:00.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/4e/b00e3ffae32b74b5180e15d2ab4040531ee1bef4c19755fe7926622dc958/sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f", size = 2121232, upload-time = "2025-05-14T17:48:20.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/30/6547ebb10875302074a37e1970a5dce7985240665778cfdee2323709f749/sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560", size = 2110897, upload-time = "2025-05-14T17:48:21.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/21/59df2b41b0f6c62da55cd64798232d7349a9378befa7f1bb18cf1dfd510a/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f", size = 3273313, upload-time = "2025-05-14T17:51:56.205Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/e4/b9a7a0e5c6f79d49bcd6efb6e90d7536dc604dab64582a9dec220dab54b6/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6", size = 3273807, upload-time = "2025-05-14T17:55:26.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/d8/79f2427251b44ddee18676c04eab038d043cff0e764d2d8bb08261d6135d/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04", size = 3209632, upload-time = "2025-05-14T17:51:59.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/16/730a82dda30765f63e0454918c982fb7193f6b398b31d63c7c3bd3652ae5/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582", size = 3233642, upload-time = "2025-05-14T17:55:29.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/61/c0d4607f7799efa8b8ea3c49b4621e861c8f5c41fd4b5b636c534fcb7d73/sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8", size = 2086475, upload-time = "2025-05-14T17:56:02.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/8e/8344f8ae1cb6a479d0741c02cd4f666925b2bf02e2468ddaf5ce44111f30/sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504", size = 2110903, upload-time = "2025-05-14T17:56:03.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/2a/f1f4e068b371154740dd10fb81afb5240d5af4aa0087b88d8b308b5429c2/sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9", size = 2119645, upload-time = "2025-05-14T17:55:24.854Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/e8/c664a7e73d36fbfc4730f8cf2bf930444ea87270f2825efbe17bf808b998/sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1", size = 2107399, upload-time = "2025-05-14T17:55:28.097Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/78/8a9cf6c5e7135540cb682128d091d6afa1b9e48bd049b0d691bf54114f70/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70", size = 3293269, upload-time = "2025-05-14T17:50:38.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/35/f74add3978c20de6323fb11cb5162702670cc7a9420033befb43d8d5b7a4/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e", size = 3303364, upload-time = "2025-05-14T17:51:49.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/d4/c990f37f52c3f7748ebe98883e2a0f7d038108c2c5a82468d1ff3eec50b7/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078", size = 3229072, upload-time = "2025-05-14T17:50:39.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/69/cab11fecc7eb64bc561011be2bd03d065b762d87add52a4ca0aca2e12904/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae", size = 3268074, upload-time = "2025-05-14T17:51:51.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/ca/0c19ec16858585d37767b167fc9602593f98998a68a798450558239fb04a/sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6", size = 2084514, upload-time = "2025-05-14T17:55:49.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/23/4c2833d78ff3010a4e17f984c734f52b531a8c9060a50429c9d4b0211be6/sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0", size = 2111557, upload-time = "2025-05-14T17:55:51.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/12/33ff43214c2c6cc87499b402fe419869d2980a08101c991daae31345e901/sqlalchemy-2.0.42-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:172b244753e034d91a826f80a9a70f4cbac690641207f2217f8404c261473efe", size = 2130469, upload-time = "2025-07-29T13:25:15.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/c4/4d2f2c21ddde9a2c7f7b258b202d6af0bac9fc5abfca5de367461c86d766/sqlalchemy-2.0.42-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be28f88abd74af8519a4542185ee80ca914933ca65cdfa99504d82af0e4210df", size = 2120393, upload-time = "2025-07-29T13:25:16.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/0d/5ff2f2dfbac10e4a9ade1942f8985ffc4bd8f157926b1f8aed553dfe3b88/sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b344859d282fde388047f1710860bb23f4098f705491e06b8ab52a48aafea9", size = 3206173, upload-time = "2025-07-29T13:29:00.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/59/71493fe74bd76a773ae8fa0c50bfc2ccac1cbf7cfa4f9843ad92897e6dcf/sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97978d223b11f1d161390a96f28c49a13ce48fdd2fed7683167c39bdb1b8aa09", size = 3206910, upload-time = "2025-07-29T13:24:50.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/51/01b1d85bbb492a36b25df54a070a0f887052e9b190dff71263a09f48576b/sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e35b9b000c59fcac2867ab3a79fc368a6caca8706741beab3b799d47005b3407", size = 3145479, upload-time = "2025-07-29T13:29:02.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/78/10834f010e2a3df689f6d1888ea6ea0074ff10184e6a550b8ed7f9189a89/sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bc7347ad7a7b1c78b94177f2d57263113bb950e62c59b96ed839b131ea4234e1", size = 3169605, upload-time = "2025-07-29T13:24:52.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/75/e6fdd66d237582c8488dd1dfa90899f6502822fbd866363ab70e8ac4a2ce/sqlalchemy-2.0.42-cp310-cp310-win32.whl", hash = "sha256:739e58879b20a179156b63aa21f05ccacfd3e28e08e9c2b630ff55cd7177c4f1", size = 2098759, upload-time = "2025-07-29T13:23:55.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/a8/366db192641c2c2d1ea8977e7c77b65a0d16a7858907bb76ea68b9dd37af/sqlalchemy-2.0.42-cp310-cp310-win_amd64.whl", hash = "sha256:1aef304ada61b81f1955196f584b9e72b798ed525a7c0b46e09e98397393297b", size = 2122423, upload-time = "2025-07-29T13:23:56.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/3c/7bfd65f3c2046e2fb4475b21fa0b9d7995f8c08bfa0948df7a4d2d0de869/sqlalchemy-2.0.42-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c34100c0b7ea31fbc113c124bcf93a53094f8951c7bf39c45f39d327bad6d1e7", size = 2133779, upload-time = "2025-07-29T13:25:18.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/17/19be542fe9dd64a766090e90e789e86bdaa608affda6b3c1e118a25a2509/sqlalchemy-2.0.42-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad59dbe4d1252448c19d171dfba14c74e7950b46dc49d015722a4a06bfdab2b0", size = 2123843, upload-time = "2025-07-29T13:25:19.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/fc/83e45fc25f0acf1c26962ebff45b4c77e5570abb7c1a425a54b00bcfa9c7/sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9187498c2149919753a7fd51766ea9c8eecdec7da47c1b955fa8090bc642eaa", size = 3294824, upload-time = "2025-07-29T13:29:03.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/81/421efc09837104cd1a267d68b470e5b7b6792c2963b8096ca1e060ba0975/sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f092cf83ebcafba23a247f5e03f99f5436e3ef026d01c8213b5eca48ad6efa9", size = 3294662, upload-time = "2025-07-29T13:24:53.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/ba/55406e09d32ed5e5f9e8aaec5ef70c4f20b4ae25b9fa9784f4afaa28e7c3/sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc6afee7e66fdba4f5a68610b487c1f754fccdc53894a9567785932dbb6a265e", size = 3229413, upload-time = "2025-07-29T13:29:05.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/c4/df596777fce27bde2d1a4a2f5a7ddea997c0c6d4b5246aafba966b421cc0/sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:260ca1d2e5910f1f1ad3fe0113f8fab28657cee2542cb48c2f342ed90046e8ec", size = 3255563, upload-time = "2025-07-29T13:24:55.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/ed/b9c4a939b314400f43f972c9eb0091da59d8466ef9c51d0fd5b449edc495/sqlalchemy-2.0.42-cp311-cp311-win32.whl", hash = "sha256:2eb539fd83185a85e5fcd6b19214e1c734ab0351d81505b0f987705ba0a1e231", size = 2098513, upload-time = "2025-07-29T13:23:58.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/72/55b0c34e39feb81991aa3c974d85074c356239ac1170dfb81a474b4c23b3/sqlalchemy-2.0.42-cp311-cp311-win_amd64.whl", hash = "sha256:9193fa484bf00dcc1804aecbb4f528f1123c04bad6a08d7710c909750fa76aeb", size = 2123380, upload-time = "2025-07-29T13:24:00.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/66/ac31a9821fc70a7376321fb2c70fdd7eadbc06dadf66ee216a22a41d6058/sqlalchemy-2.0.42-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:09637a0872689d3eb71c41e249c6f422e3e18bbd05b4cd258193cfc7a9a50da2", size = 2132203, upload-time = "2025-07-29T13:29:19.291Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/ba/fd943172e017f955d7a8b3a94695265b7114efe4854feaa01f057e8f5293/sqlalchemy-2.0.42-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3cb3ec67cc08bea54e06b569398ae21623534a7b1b23c258883a7c696ae10df", size = 2120373, upload-time = "2025-07-29T13:29:21.049Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/a2/b5f7d233d063ffadf7e9fff3898b42657ba154a5bec95a96f44cba7f818b/sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87e6a5ef6f9d8daeb2ce5918bf5fddecc11cae6a7d7a671fcc4616c47635e01", size = 3317685, upload-time = "2025-07-29T13:26:40.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/00/fcd8daab13a9119d41f3e485a101c29f5d2085bda459154ba354c616bf4e/sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b718011a9d66c0d2f78e1997755cd965f3414563b31867475e9bc6efdc2281d", size = 3326967, upload-time = "2025-07-29T13:22:31.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/85/e622a273d648d39d6771157961956991a6d760e323e273d15e9704c30ccc/sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:16d9b544873fe6486dddbb859501a07d89f77c61d29060bb87d0faf7519b6a4d", size = 3255331, upload-time = "2025-07-29T13:26:42.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/a0/2c2338b592c7b0a61feffd005378c084b4c01fabaf1ed5f655ab7bd446f0/sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21bfdf57abf72fa89b97dd74d3187caa3172a78c125f2144764a73970810c4ee", size = 3291791, upload-time = "2025-07-29T13:22:32.454Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/19/b8a2907972a78285fdce4c880ecaab3c5067eb726882ca6347f7a4bf64f6/sqlalchemy-2.0.42-cp312-cp312-win32.whl", hash = "sha256:78b46555b730a24901ceb4cb901c6b45c9407f8875209ed3c5d6bcd0390a6ed1", size = 2096180, upload-time = "2025-07-29T13:16:08.952Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/1f/67a78f3dfd08a2ed1c7be820fe7775944f5126080b5027cc859084f8e223/sqlalchemy-2.0.42-cp312-cp312-win_amd64.whl", hash = "sha256:4c94447a016f36c4da80072e6c6964713b0af3c8019e9c4daadf21f61b81ab53", size = 2123533, upload-time = "2025-07-29T13:16:11.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/7e/25d8c28b86730c9fb0e09156f601d7a96d1c634043bf8ba36513eb78887b/sqlalchemy-2.0.42-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941804f55c7d507334da38133268e3f6e5b0340d584ba0f277dd884197f4ae8c", size = 2127905, upload-time = "2025-07-29T13:29:22.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/a1/9d8c93434d1d983880d976400fcb7895a79576bd94dca61c3b7b90b1ed0d/sqlalchemy-2.0.42-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d3d06a968a760ce2aa6a5889fefcbdd53ca935735e0768e1db046ec08cbf01", size = 2115726, upload-time = "2025-07-29T13:29:23.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/cc/d33646fcc24c87cc4e30a03556b611a4e7bcfa69a4c935bffb923e3c89f4/sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf10396a8a700a0f38ccd220d940be529c8f64435c5d5b29375acab9267a6c9", size = 3246007, upload-time = "2025-07-29T13:26:44.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/08/4e6c533d4c7f5e7c4cbb6fe8a2c4e813202a40f05700d4009a44ec6e236d/sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cae6c2b05326d7c2c7c0519f323f90e0fb9e8afa783c6a05bb9ee92a90d0f04", size = 3250919, upload-time = "2025-07-29T13:22:33.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/82/f680e9a636d217aece1b9a8030d18ad2b59b5e216e0c94e03ad86b344af3/sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f50f7b20677b23cfb35b6afcd8372b2feb348a38e3033f6447ee0704540be894", size = 3180546, upload-time = "2025-07-29T13:26:45.648Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/a2/8c8f6325f153894afa3775584c429cc936353fb1db26eddb60a549d0ff4b/sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d88a1c0d66d24e229e3938e1ef16ebdbd2bf4ced93af6eff55225f7465cf350", size = 3216683, upload-time = "2025-07-29T13:22:34.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/44/3a451d7fa4482a8ffdf364e803ddc2cfcafc1c4635fb366f169ecc2c3b11/sqlalchemy-2.0.42-cp313-cp313-win32.whl", hash = "sha256:45c842c94c9ad546c72225a0c0d1ae8ef3f7c212484be3d429715a062970e87f", size = 2093990, upload-time = "2025-07-29T13:16:13.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/9e/9bce34f67aea0251c8ac104f7bdb2229d58fb2e86a4ad8807999c4bee34b/sqlalchemy-2.0.42-cp313-cp313-win_amd64.whl", hash = "sha256:eb9905f7f1e49fd57a7ed6269bc567fcbbdac9feadff20ad6bd7707266a91577", size = 2120473, upload-time = "2025-07-29T13:16:14.502Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/55/ba2546ab09a6adebc521bf3974440dc1d8c06ed342cceb30ed62a8858835/sqlalchemy-2.0.42-py3-none-any.whl", hash = "sha256:defcdff7e661f0043daa381832af65d616e060ddb54d3fe4476f51df7eaa1835", size = 1922072, upload-time = "2025-07-29T13:09:17.061Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2422,38 +2430,38 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "tiktoken"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "regex" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991, upload-time = "2025-02-14T06:03:01.003Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/6a/e0f66d5cf612979fec2c18f9069962c73ed51949403967ba0de9ea6859e5/tiktoken-0.10.0.tar.gz", hash = "sha256:7cd88c11699b18081822e6ae1beee55e8f20ea361d73c507d33f5a89a1898f1c", size = 36451, upload-time = "2025-08-05T17:58:07.619Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/f3/50ec5709fad61641e4411eb1b9ac55b99801d71f1993c29853f256c726c9/tiktoken-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:586c16358138b96ea804c034b8acf3f5d3f0258bd2bc3b0227af4af5d622e382", size = 1065770, upload-time = "2025-02-14T06:02:01.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/f8/5a9560a422cf1755b6e0a9a436e14090eeb878d8ec0f80e0cd3d45b78bf4/tiktoken-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9c59ccc528c6c5dd51820b3474402f69d9a9e1d656226848ad68a8d5b2e5108", size = 1009314, upload-time = "2025-02-14T06:02:02.869Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/20/3ed4cfff8f809cb902900ae686069e029db74567ee10d017cb254df1d598/tiktoken-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0968d5beeafbca2a72c595e8385a1a1f8af58feaebb02b227229b69ca5357fd", size = 1143140, upload-time = "2025-02-14T06:02:04.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/95/cc2c6d79df8f113bdc6c99cdec985a878768120d87d839a34da4bd3ff90a/tiktoken-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a5fb085a6a3b7350b8fc838baf493317ca0e17bd95e8642f95fc69ecfed1de", size = 1197860, upload-time = "2025-02-14T06:02:06.268Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/6c/9c1a4cc51573e8867c9381db1814223c09ebb4716779c7f845d48688b9c8/tiktoken-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15a2752dea63d93b0332fb0ddb05dd909371ededa145fe6a3242f46724fa7990", size = 1259661, upload-time = "2025-02-14T06:02:08.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/4c/22eb8e9856a2b1808d0a002d171e534eac03f96dbe1161978d7389a59498/tiktoken-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:26113fec3bd7a352e4b33dbaf1bd8948de2507e30bd95a44e2b1156647bc01b4", size = 894026, upload-time = "2025-02-14T06:02:12.841Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987, upload-time = "2025-02-14T06:02:14.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155, upload-time = "2025-02-14T06:02:15.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898, upload-time = "2025-02-14T06:02:16.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535, upload-time = "2025-02-14T06:02:18.595Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548, upload-time = "2025-02-14T06:02:20.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895, upload-time = "2025-02-14T06:02:22.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/e5/21ff33ecfa2101c1bb0f9b6df750553bd873b7fb532ce2cb276ff40b197f/tiktoken-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e88f121c1c22b726649ce67c089b90ddda8b9662545a8aeb03cfef15967ddd03", size = 1065073, upload-time = "2025-02-14T06:02:24.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/03/a95e7b4863ee9ceec1c55983e4cc9558bcfd8f4f80e19c4f8a99642f697d/tiktoken-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6600660f2f72369acb13a57fb3e212434ed38b045fd8cc6cdd74947b4b5d210", size = 1008075, upload-time = "2025-02-14T06:02:26.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/10/1305bb02a561595088235a513ec73e50b32e74364fef4de519da69bc8010/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e811743b5dfa74f4b227927ed86cbc57cad4df859cb3b643be797914e41794", size = 1140754, upload-time = "2025-02-14T06:02:28.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/40/da42522018ca496432ffd02793c3a72a739ac04c3794a4914570c9bb2925/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99376e1370d59bcf6935c933cb9ba64adc29033b7e73f5f7569f3aad86552b22", size = 1196678, upload-time = "2025-02-14T06:02:29.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/41/1e59dddaae270ba20187ceb8aa52c75b24ffc09f547233991d5fd822838b/tiktoken-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:badb947c32739fb6ddde173e14885fb3de4d32ab9d8c591cbd013c22b4c31dd2", size = 1259283, upload-time = "2025-02-14T06:02:33.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/64/b16003419a1d7728d0d8c0d56a4c24325e7b10a21a9dd1fc0f7115c02f0a/tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16", size = 894897, upload-time = "2025-02-14T06:02:36.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/11/09d936d37f49f4f494ffe660af44acd2d99eb2429d60a57c71318af214e0/tiktoken-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b0e8e05a26eda1249e824156d537015480af7ae222ccb798e5234ae0285dbdb", size = 1064919, upload-time = "2025-02-14T06:02:37.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/0e/f38ba35713edb8d4197ae602e80837d574244ced7fb1b6070b31c29816e0/tiktoken-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27d457f096f87685195eea0165a1807fae87b97b2161fe8c9b1df5bd74ca6f63", size = 1007877, upload-time = "2025-02-14T06:02:39.516Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/82/9197f77421e2a01373e27a79dd36efdd99e6b4115746ecc553318ecafbf0/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf8ded49cddf825390e36dd1ad35cd49589e8161fdcb52aa25f0583e90a3e01", size = 1140095, upload-time = "2025-02-14T06:02:41.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/bb/4513da71cac187383541facd0291c4572b03ec23c561de5811781bbd988f/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc156cb314119a8bb9748257a2eaebd5cc0753b6cb491d26694ed42fc7cb3139", size = 1195649, upload-time = "2025-02-14T06:02:43Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/5c/74e4c137530dd8504e97e3a41729b1103a4ac29036cbfd3250b11fd29451/tiktoken-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cd69372e8c9dd761f0ab873112aba55a0e3e506332dd9f7522ca466e817b1b7a", size = 1258465, upload-time = "2025-02-14T06:02:45.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/a8/8f499c179ec900783ffe133e9aab10044481679bb9aad78436d239eee716/tiktoken-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ea0edb6f83dc56d794723286215918c1cde03712cbbafa0348b33448faf5b95", size = 894669, upload-time = "2025-02-14T06:02:47.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/69/68ac82e68cd03df75b4fbe280331ad5f1bab879464d679c5b37fdafcb4e6/tiktoken-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1db7a65b196d757d18ef53193957d44549b88f373d4b87db532f04d18193b847", size = 1047220, upload-time = "2025-08-05T17:57:27.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/1a/6396b6987d6dd1508fddc645fd41ae1b505c4afd2c5ed310f28fb7fbc2b6/tiktoken-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f55701461267d025597ebb2290d612fe9c5c5fbb625ebf7495c9f0f8e4c30f01", size = 989183, upload-time = "2025-08-05T17:57:29.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/75/1ddd7ee07332966a5686c5dab91bb862d41edc7b144b405104c6e5993f41/tiktoken-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83693279af9e8deac0363cbf21dc3d666807f22dcc1091f51e69e6fe6433f71", size = 1120360, upload-time = "2025-08-05T17:57:32.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/14/6083daba660141b580b7da6ee2b3ed7084c73822ad52739071164ea26786/tiktoken-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f973bdeb68d645f73dcce60c7795cb5c6f8b7f3dcf92c40c39ad4aee398c075", size = 1175483, upload-time = "2025-08-05T17:57:33.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/19/9a16880dbf592007eec7a7010f42225253eb669c1787d2f6b1bd5a84e25e/tiktoken-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d5cab2a8e8974d6c9744264e94886a58b9087a39737c53fd65dbe2fe8522e719", size = 1237876, upload-time = "2025-08-05T17:57:35.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/2a/d80d05a53a21f4da7b38c975f220639e2829bd598b17028ccaca64db679a/tiktoken-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:7a49d2aedf911b68f23c9c84abce8eaf569cb02613f307cee4fb1bebd8c55ae9", size = 876340, upload-time = "2025-08-05T17:57:36.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/ba/d392a2ffa94dbbcf1cd187b70df18f61a96f76d1de9d5d2bc8414b402d05/tiktoken-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:29f55c6b7c9a6a7ea953691c3962ee8fe4ee2f0ceb2a3fded3925acfc45e4b0a", size = 1047150, upload-time = "2025-08-05T17:57:37.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/e0/86b36df9ab5f014ef976f75b000b9daf06c6edff229186e05e56b94f4cdf/tiktoken-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f641d0735059b48252c4409d6546b6a62edeb4c4e48306499db11bbe403b872f", size = 988709, upload-time = "2025-08-05T17:57:38.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/43/24813c6e7db16314229b5cd13bc23719a58401242dd42be2bac3b36516d4/tiktoken-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad76e03676b36c2a5e5304b428cff48eb86a033af55212d41a6ac6faec25b2ad", size = 1120621, upload-time = "2025-08-05T17:57:40.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/94/fd4156ccc20d4c7986352353385d768307e4c6d8e2502c3d6ee061847474/tiktoken-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6420ecbd4dc4db3b82ca3421f50d1207d750de1a2856e6ca0544294fe58d2853", size = 1175233, upload-time = "2025-08-05T17:57:41.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/db/32f4b4f1c13f84118924258b7a92c2a20a1d72f974abcdd73c3cd6308983/tiktoken-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d2e032cd44700a9ed511c50b9f302bb9e9e77e2ebf8e2d9b8ec12ce64ebf00c6", size = 1237489, upload-time = "2025-08-05T17:57:42.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/a4/dc6558ce48a6c339a30a9e532bb5767e0ff511f8f948bca1340f57aa2fea/tiktoken-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:eb520960fa1788caf033489e3b63bb675c0f321db40569b2d3ca24d7fab5ca72", size = 876253, upload-time = "2025-08-05T17:57:43.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/8a/2dad6ea056f88f12602bdbdba19ed1d29a574bb8dbe034af0842eb2ee29f/tiktoken-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c27021a605b2778af07f84a33fe7c74b5c960a8cfac5d2a7a1075ed592cbe4", size = 1046690, upload-time = "2025-08-05T17:57:45.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/88/b0ec9383e7126f7cf033971dac81c4dea0954607db8c2fbfe109061bf730/tiktoken-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:45d654f049b4f7ed617ad0c66462c15cc41e5db120f37f122d5b57ffa2aec062", size = 988269, upload-time = "2025-08-05T17:57:46.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/83/ed1900dd68890c891f55ded1f760f7878c5e16c8c68e576145105d66aa63/tiktoken-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34f73d93641b9cc73e19b18dbf9ce1baf086f1c4e18cfa36b952956843bca23c", size = 1118515, upload-time = "2025-08-05T17:57:48.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/a8/84ad6c23cfd51d2c39ba1bfbc70b6208c6be3e5cd15132a9d11cc93bfdd5/tiktoken-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fbfc5dda624c0f85c56bbf8f2ffb42964c798fc6fc1c321a452bf31d4ba21f5", size = 1174193, upload-time = "2025-08-05T17:57:49.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/b5/7be4c78a10566ae068d88788a6fdf3a8b1162e7f78997999c46c919cdb69/tiktoken-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:853671af10636b581d632fe7b64b755301c73bc1f2ce837a8cdd9a44ab51f846", size = 1235834, upload-time = "2025-08-05T17:57:50.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/da/a0e90c85f36bd21ef4dbe881f9e259403c6fd6287841ff3691c1d2a5baab/tiktoken-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:56c4e7544be63c78265c82857b9f2a4e2190ae29541fe3467708fc7aabcb1522", size = 875661, upload-time = "2025-08-05T17:57:52.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/44/03c259d8df262181f0efab0159373435284f1074adf966418663879023e1/tiktoken-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:10e779827ddb0490a415d09d95f8e7a982fd8e14f88c4c2348358f722af3c415", size = 1046536, upload-time = "2025-08-05T17:57:53.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/16/98ee535d30f7abf528ac25e78d67171cc2dfc1366d762cf79eb0dcc9054d/tiktoken-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:783892cdbec0d33f0f51373d8969e059ab1244af531a52338f702131a032a116", size = 987947, upload-time = "2025-08-05T17:57:54.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/bd/c3847b55e4fd6c6ab89ab75d258c725c67890734a4e58e28315ae2208ee1/tiktoken-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43f38d62b6a62a7c82be3770fcdc24cdf5bff3bdf718909d038b804ac774a025", size = 1118091, upload-time = "2025-08-05T17:57:55.383Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/07/9a2c6e1d1ef138f79a6c943bf1c7c7e7cc159e2b7fbbe6ce5d176f963232/tiktoken-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72d0b30e08f1e856fa76ee423a3d3f0b1bc984cb6e86a21dbd7c5eee8f67f8f5", size = 1174129, upload-time = "2025-08-05T17:57:56.739Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/4f/ae9e36e43c0abbeb631b848697b22662cca5dbbe90a1b692b5c0ceda0fac/tiktoken-0.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa840eba09a0c1db0b436baf6cfe1ab7906f33fb663cf96f772a818bad5856a6", size = 1236097, upload-time = "2025-08-05T17:57:58.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/74/c6f69e46f98e42d6727853e4017d6287b57075905ff715fae0f35fa53173/tiktoken-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:642b8d9f266004629ea2457aa29a9eed4f1a031fd804f8c489edbf32df7026c3", size = 876020, upload-time = "2025-08-05T17:57:59.52Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2629,16 +2637,16 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.32.0"
|
||||
version = "20.33.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/96/0834f30fa08dca3738614e6a9d42752b6420ee94e58971d702118f7cfd30/virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0", size = 6076970, upload-time = "2025-07-21T04:09:50.985Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/60/4f20960df6c7b363a18a55ab034c8f2bcd5d9770d1f94f9370ec104c1855/virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8", size = 6082160, upload-time = "2025-08-05T16:10:55.605Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761, upload-time = "2025-07-21T04:09:48.059Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/ff/ded57ac5ff40a09e6e198550bab075d780941e0b0f83cbeabd087c59383a/virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67", size = 6060362, upload-time = "2025-08-05T16:10:52.81Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in New Issue