feat: add new ergo sdks to monorepo (#142)
* feat: add new ergo sdks to monorepo * fix: resolve some pyright linter errors * fix: python sdk (mostly) typesafe now * feat: add tests for python sdk, mostly good pyright * fix: update sdks/core to 1.1.0 * uv sync * fix: body->query * chore: pyright wrangling and coderabbit nits * fix: ts lib search working * chore: coderabbit nits
This commit is contained in:
parent
4d5aa4cdb2
commit
cb3b6104bd
|
|
@ -29,6 +29,7 @@ dependencies = [
|
|||
]
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"honcho-core>=1.1.0",
|
||||
"pytest>=8.2.2",
|
||||
"sqlalchemy-utils>=0.41.2",
|
||||
"pytest-asyncio>=0.23.7",
|
||||
|
|
@ -70,7 +71,7 @@ asyncio_default_fixture_loop_scope = "session"
|
|||
# than mypy and with a good extension for VSCode/Cursor.
|
||||
# https://marketplace.visualstudio.com/items?itemName=detachhead.basedpyright
|
||||
# https://docs.basedpyright.com/latest/configuration/config-files/#sample-pyprojecttoml-file
|
||||
include = ["src", "tests"]
|
||||
include = ["src", "tests", "sdks/python/src"]
|
||||
exclude = ["tests/**/disabled*.py"]
|
||||
# By default BasedPyright is very strict, so you almost certainly want to disable
|
||||
# some of the rules.
|
||||
|
|
@ -96,4 +97,5 @@ reportImplicitOverride = false
|
|||
# reportUnknownVariableType = false
|
||||
# reportUnknownArgumentType = false
|
||||
# reportUnknownParameterType = false
|
||||
# reportUnknownMemberType = false
|
||||
# reportUnknownMemberType = false
|
||||
reportImportCycles = false
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
.vscode
|
||||
.DS_Store
|
||||
|
||||
__pycache__
|
||||
.mypy_cache
|
||||
.ruff_cache
|
||||
*.egg-info
|
||||
|
||||
dist
|
||||
|
||||
.venv
|
||||
|
||||
.env
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
# Honcho Python SDK
|
||||
|
||||
The official Python library for the [Honcho](https://github.com/plastic-labs/honcho) conversational memory platform. Honcho provides tools for managing peers, sessions, and conversation context across multi-party interactions, enabling advanced conversational AI applications with persistent memory and theory-of-mind capabilities.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install honcho-ai
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from honcho import Honcho
|
||||
|
||||
# Initialize client
|
||||
client = Honcho(api_key="your-api-key")
|
||||
|
||||
# Create peers (participants in conversations)
|
||||
alice = client.peer("alice")
|
||||
bob = client.peer("bob")
|
||||
|
||||
# Create a session for group conversations
|
||||
session = client.session("conversation-1")
|
||||
|
||||
# Add messages to the session
|
||||
session.add_messages([
|
||||
alice.message("Hello, Bob!"),
|
||||
bob.message("Hi Alice, how are you?")
|
||||
])
|
||||
|
||||
# Query conversation context
|
||||
response = alice.chat("What did Bob say to me?")
|
||||
print(response)
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Peers
|
||||
|
||||
Peers represent participants in conversations.
|
||||
|
||||
```python
|
||||
# Create peers
|
||||
assistant = client.peer("assistant")
|
||||
user = client.peer("user-123")
|
||||
|
||||
# Chat with global context
|
||||
response = user.chat("What did I talk about yesterday?")
|
||||
|
||||
# Chat with perspective of another peer
|
||||
response = user.chat("Does the assistant know my preferences?", target=assistant)
|
||||
```
|
||||
|
||||
### Sessions
|
||||
|
||||
Sessions group related conversations and messages:
|
||||
|
||||
```python
|
||||
# Create a session
|
||||
session = client.session("project-discussion")
|
||||
|
||||
# Add peers to session
|
||||
session.add_peers([alice, bob])
|
||||
|
||||
# Add messages
|
||||
session.add_messages([
|
||||
alice.message("Let's discuss the project timeline"),
|
||||
bob.message("I think we need two more weeks")
|
||||
])
|
||||
|
||||
# Get conversation context
|
||||
context = session.get_context()
|
||||
```
|
||||
|
||||
### Messages and Context
|
||||
|
||||
Retrieve and use conversation history:
|
||||
|
||||
```python
|
||||
# Get messages from a session
|
||||
messages = session.get_messages()
|
||||
|
||||
# Convert to OpenAI format for further prompting
|
||||
openai_messages = context.to_openai(assistant="assistant")
|
||||
|
||||
# Convert to Anthropic format for further prompting
|
||||
anthropic_messages = context.to_anthropic(assistant="assistant")
|
||||
```
|
||||
|
||||
### Async Support
|
||||
|
||||
```python
|
||||
from honcho import AsyncHoncho
|
||||
|
||||
async def main():
|
||||
client = AsyncHoncho(api_key="your-api-key")
|
||||
|
||||
peer = client.peer("user")
|
||||
response = await peer.chat("Hello!")
|
||||
print(response)
|
||||
```
|
||||
|
||||
### Metadata Management
|
||||
|
||||
```python
|
||||
# Set peer metadata
|
||||
user.set_metadata({"location": "San Francisco", "preferences": {"theme": "dark"}})
|
||||
|
||||
# Query using metadata context
|
||||
response = user.chat("What's the weather like where I am?")
|
||||
|
||||
# Session metadata
|
||||
session.set_metadata({"topic": "project-planning", "priority": "high"})
|
||||
```
|
||||
|
||||
### Multi-Perspective Queries
|
||||
|
||||
```python
|
||||
# Alice's view of what Bob knows
|
||||
response = alice.chat("Does Bob remember our discussion about the budget?", target=bob)
|
||||
|
||||
# Session-specific perspective
|
||||
response = alice.chat("What does Bob think about this project?",
|
||||
target=bob,
|
||||
session_id=session.id)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
export HONCHO_API_KEY="your-api-key"
|
||||
export HONCHO_BASE_URL="https://api.honcho.dev" # Optional
|
||||
export HONCHO_WORKSPACE_ID="your-workspace" # Optional
|
||||
```
|
||||
|
||||
### Client Options
|
||||
|
||||
```python
|
||||
client = Honcho(
|
||||
api_key="your-api-key",
|
||||
environment="production", # or "local", "demo"
|
||||
workspace_id="custom-workspace",
|
||||
base_url="https://api.honcho.dev"
|
||||
)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Check out the `examples/` directory for complete usage examples:
|
||||
|
||||
- `example.py` - Comprehensive feature demonstration
|
||||
- `chat.py` - Basic multi-peer chat
|
||||
- `async_example.py` - Async/await usage
|
||||
- `search.py` - Context search and retrieval
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 - see [LICENSE](../../LICENSE) for details.
|
||||
|
||||
## Support
|
||||
|
||||
- [Documentation](https://docs.honcho.dev)
|
||||
- [GitHub Issues](https://github.com/plastic-labs/honcho-sdks/issues)
|
||||
- [Discord Community](https://discord.gg/honcho)
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
import asyncio
|
||||
import logging
|
||||
|
||||
from honcho import AsyncHoncho
|
||||
from honcho.async_client.session import SessionPeerConfig
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
async def main():
|
||||
# HONCHO_API_KEY is an environment variable
|
||||
# HONCHO_URL is an *optional* environment variable
|
||||
# HONCHO_WORKSPACE_ID is an *optional* environment variable
|
||||
# Using local server for this example
|
||||
honcho = AsyncHoncho(environment="local", workspace_id="test")
|
||||
|
||||
_workspaces = await honcho.get_workspaces()
|
||||
|
||||
# these don't make any API calls, just produce a AsyncPeer object in SDK
|
||||
# in practice, these would be UUIDs, as peer IDs are unique within their workspace
|
||||
assistant = await honcho.peer(id="bob")
|
||||
alice = await honcho.peer(id="alice")
|
||||
|
||||
# empty since peers are not created until they are used
|
||||
_peers = await honcho.get_peers()
|
||||
|
||||
# workspace-level metadata
|
||||
_m = await honcho.get_metadata()
|
||||
await honcho.set_metadata({"test": "test"})
|
||||
|
||||
# calling the dialectic chat endpoint makes an API call.
|
||||
# when this call occurs, the "alice" peer will be get_or_create'd
|
||||
# response will be None because we haven't talked yet!
|
||||
_response = await alice.chat("what did alice have for breakfast today?")
|
||||
|
||||
# sessions are scoped to a set of peers and contain messages/content
|
||||
# this is not an API call, like peers this is created lazily
|
||||
my_session = await honcho.session(id="session_1")
|
||||
|
||||
# API call
|
||||
await my_session.add_peers(
|
||||
[alice, (assistant, SessionPeerConfig(observe_others=False, observe_me=False))]
|
||||
)
|
||||
|
||||
# adding/removing peers from sessions creates a bidirectional relationship,
|
||||
# so no need for operations like `alice.join(my_session)`.
|
||||
|
||||
# this will return a list of sessions [my_session]
|
||||
# this is also an API call
|
||||
_sessions = await alice.get_sessions()
|
||||
|
||||
# API call to create 1 or more messages (overload, can be Message or list[Message]
|
||||
await my_session.add_messages(
|
||||
[
|
||||
# creates a Message object with peer_id="alice", etc etc
|
||||
assistant.message("what did you have for breakfast today, alice?"),
|
||||
alice.message("i had oatmeal."),
|
||||
]
|
||||
)
|
||||
|
||||
m = await my_session.get_metadata()
|
||||
m["test"] = "test2"
|
||||
await my_session.set_metadata(m)
|
||||
|
||||
# peers have one "omnipresent" global representation, comprised of all
|
||||
# the content associated with that peer in this honcho instance.
|
||||
|
||||
# they also have a potentially infinite number of "local" representations,
|
||||
# each one from the perspective of *another* peer in the honcho instance.
|
||||
|
||||
# this is a query to alice's global representation--no scope
|
||||
_response = await alice.chat("what did the user have for breakfast today?")
|
||||
|
||||
# this is a query to alice's local representation *of the assistant*
|
||||
_response = await alice.chat(
|
||||
"does alice know what bob had for breakfast?", target=assistant
|
||||
)
|
||||
|
||||
# this is a query to the assistant's local representation *of alice* in this session
|
||||
_response = await assistant.chat(
|
||||
"does the assistant know what alice had for breakfast?",
|
||||
target=alice,
|
||||
session_id=my_session.id,
|
||||
)
|
||||
|
||||
# API call to store non-message content under a peer + optional session
|
||||
await alice.add_messages(
|
||||
"this might be a document about alice, say, a journal entry."
|
||||
)
|
||||
|
||||
# This does make an API call because we set a configuration for this new peer
|
||||
charlie = await honcho.peer(id="charlie", config={"observe_me": False})
|
||||
|
||||
await my_session.add_messages(charlie.message("hello world!"))
|
||||
|
||||
# session now has 3 members: alice, bob, and charlie. a message automatically adds a peer to a session.
|
||||
|
||||
# peers, sessions, and messages all have metadata which can be modified and used in queries.
|
||||
|
||||
# API call to get metadata?
|
||||
charlie_metadata = await charlie.get_metadata()
|
||||
|
||||
charlie_metadata["location"] = "the moon"
|
||||
|
||||
# API call to store metadata?
|
||||
await charlie.set_metadata(charlie_metadata)
|
||||
|
||||
# response will tell you that charlie is on the moon
|
||||
_response = await charlie.chat("where is the user?")
|
||||
|
||||
# you can get the messages from a session, either fully or partially.
|
||||
# (API call)
|
||||
_messages = await my_session.get_messages()
|
||||
|
||||
context = await my_session.get_context()
|
||||
|
||||
_messages = context.to_openai(assistant=assistant.id)
|
||||
|
||||
_messages = context.to_anthropic(assistant=assistant.id)
|
||||
|
||||
await my_session.add_messages(
|
||||
assistant.message("This is a test message using the property syntax")
|
||||
)
|
||||
|
||||
print("Async sample code executed successfully!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import random
|
||||
import uuid
|
||||
|
||||
from honcho import Honcho
|
||||
|
||||
# Create a Honcho client with the default workspace
|
||||
honcho = Honcho(environment="local")
|
||||
|
||||
peers = [
|
||||
honcho.peer("alice"),
|
||||
honcho.peer("bob"),
|
||||
honcho.peer("charlie"),
|
||||
]
|
||||
|
||||
# Create a new session
|
||||
session = honcho.session("chat_test_" + str(uuid.uuid4()))
|
||||
|
||||
# Generate some random messages from alice, bob, and charlie and add them to the session
|
||||
messages = []
|
||||
for i in range(10):
|
||||
random_peer = random.choice(peers)
|
||||
messages.append(
|
||||
random_peer.message(f"Hello from {random_peer}! This is message {i}.")
|
||||
)
|
||||
|
||||
session.add_messages(messages)
|
||||
|
||||
# Chat with alice
|
||||
alice = peers[0]
|
||||
response = alice.chat("what did alice have for breakfast today?")
|
||||
print("response returned:", response)
|
||||
|
||||
# Chat with alice in the session
|
||||
response = alice.chat("what did alice have for breakfast today?", session_id=session.id)
|
||||
print("response returned:", response)
|
||||
|
||||
# Chat with alice in the session with a target
|
||||
# This means you are querying alice's theory-of-mind representation of bob
|
||||
bob = peers[1]
|
||||
response = alice.chat("what did bob have for breakfast today?", target=bob)
|
||||
print("response returned:", response)
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
import logging
|
||||
|
||||
from honcho import Honcho
|
||||
from honcho.session import SessionPeerConfig
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
# HONCHO_API_KEY is an environment variable
|
||||
# HONCHO_URL is an *optional* environment variable
|
||||
# HONCHO_WORKSPACE_ID is an *optional* environment variable
|
||||
# Using local server for this example
|
||||
honcho = Honcho(environment="local", workspace_id="test")
|
||||
|
||||
workspaces = honcho.get_workspaces()
|
||||
|
||||
# these don't make any API calls, just produce a Peer object in SDK
|
||||
# in practice, these would be UUIDs, as peer IDs are unique within their workspace
|
||||
assistant = honcho.peer(id="bob")
|
||||
alice = honcho.peer(id="alice")
|
||||
|
||||
# empty since peers are not created until they are used
|
||||
peers = honcho.get_peers()
|
||||
|
||||
# workspace-level metadata
|
||||
_m = honcho.get_metadata()
|
||||
honcho.set_metadata({"test": "test"})
|
||||
|
||||
# calling the dialectic chat endpoint makes an API call.
|
||||
# when this call occurs, the "alice" peer will be get_or_create'd
|
||||
# response will be None because we haven't talked yet!
|
||||
response = alice.chat("what did alice have for breakfast today?")
|
||||
|
||||
# sessions are scoped to a set of peers and contain messages/content
|
||||
# this is not an API call, like peers this is created lazily
|
||||
my_session = honcho.session(id="session_1")
|
||||
|
||||
# API call
|
||||
my_session.add_peers(
|
||||
[alice, (assistant, SessionPeerConfig(observe_others=False, observe_me=False))]
|
||||
)
|
||||
|
||||
# adding/removing peers from sessions creates a bidirectional relationship,
|
||||
# so no need for operations like `alice.join(my_session)`.
|
||||
|
||||
# this will return a list of sessions [my_session]
|
||||
# this is also an API call
|
||||
_sessions = alice.get_sessions()
|
||||
|
||||
# API call to create 1 or more messages (overload, can be Message or list[Message]
|
||||
my_session.add_messages(
|
||||
[
|
||||
# creates a Message object with peer_id="alice", etc etc
|
||||
assistant.message("what did you have for breakfast today, alice?"),
|
||||
alice.message("i had oatmeal."),
|
||||
]
|
||||
)
|
||||
|
||||
m = my_session.get_metadata()
|
||||
m["test"] = "test2"
|
||||
my_session.set_metadata(m)
|
||||
|
||||
# peers have one "omnipresent" global representation, comprised of all
|
||||
# the content associated with that peer in this honcho instance.
|
||||
|
||||
# they also have a potentially infinite number of "local" representations,
|
||||
# each one from the perspective of *another* peer in the honcho instance.
|
||||
|
||||
# this is a query to alice's global representation--no scope
|
||||
response = alice.chat("what did the user have for breakfast today?")
|
||||
|
||||
# this is a query to alice's local representation *of the assistant*
|
||||
response = alice.chat("does alice know what bob had for breakfast?", target=assistant)
|
||||
|
||||
# this is a query to the assistant's local representation *of alice* in this session
|
||||
response = assistant.chat(
|
||||
"does the assistant know what alice had for breakfast?",
|
||||
target=alice,
|
||||
session_id=my_session.id,
|
||||
)
|
||||
|
||||
# API call to store non-message content under a peer + optional session
|
||||
alice.add_messages("this might be a document about alice, say, a journal entry.")
|
||||
|
||||
charlie = honcho.peer(id="charlie")
|
||||
|
||||
my_session.add_messages(charlie.message("hello world!"))
|
||||
|
||||
# session now has 3 members: alice, bob, and charlie. a message automatically adds a peer to a session.
|
||||
|
||||
# peers, sessions, and messages all have metadata which can be modified and used in queries.
|
||||
|
||||
# API call to get metadata?
|
||||
charlie_metadata = charlie.get_metadata()
|
||||
|
||||
charlie_metadata["location"] = "the moon"
|
||||
|
||||
# API call to store metadata?
|
||||
charlie.set_metadata(charlie_metadata)
|
||||
|
||||
# response will tell you that charlie is on the moon
|
||||
response = charlie.chat("where is the user?")
|
||||
|
||||
# you can get the messages from a session, either fully or partially.
|
||||
# (API call)
|
||||
messages = my_session.get_messages()
|
||||
|
||||
context = my_session.get_context()
|
||||
|
||||
messages = context.to_openai(assistant=assistant.id)
|
||||
|
||||
messages = context.to_anthropic(assistant=assistant.id)
|
||||
|
||||
my_session.add_messages(
|
||||
assistant.message("This is a test message using the property syntax")
|
||||
)
|
||||
|
||||
print("Sample code executed successfully!")
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import random
|
||||
import uuid
|
||||
|
||||
from honcho import Honcho
|
||||
|
||||
# Create a Honcho client with the default workspace
|
||||
honcho = Honcho(environment="local")
|
||||
|
||||
peers = [
|
||||
honcho.peer("alice"),
|
||||
honcho.peer("bob"),
|
||||
honcho.peer("charlie"),
|
||||
]
|
||||
|
||||
# Create a new session
|
||||
session = honcho.session("context_test_" + str(uuid.uuid4()))
|
||||
|
||||
# Generate some random messages from alice, bob, and charlie and add them to the session
|
||||
messages = []
|
||||
for i in range(10):
|
||||
random_peer = random.choice(peers)
|
||||
messages.append(
|
||||
random_peer.message(f"Hello from {random_peer}! This is message {i}.")
|
||||
)
|
||||
|
||||
session.add_messages(messages)
|
||||
|
||||
# Get some context of the session
|
||||
# Set the token limit super low so we only get a few of the tiny messages created
|
||||
context = session.get_context(summary=True, tokens=50)
|
||||
print("context returned:", context)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import random
|
||||
import uuid
|
||||
|
||||
from honcho import Honcho
|
||||
|
||||
# Create a Honcho client with the default workspace
|
||||
honcho = Honcho(environment="local")
|
||||
|
||||
peers = [
|
||||
honcho.peer("alice"),
|
||||
honcho.peer("bob"),
|
||||
honcho.peer("charlie"),
|
||||
]
|
||||
|
||||
# Create a new session
|
||||
session = honcho.session("context_test_" + str(uuid.uuid4()))
|
||||
|
||||
# Generate some random messages from alice, bob, and charlie and add them to the session
|
||||
messages = []
|
||||
for i in range(10):
|
||||
random_peer = random.choice(peers)
|
||||
messages.append(
|
||||
random_peer.message(f"Hello from {random_peer}! This is message {i}.")
|
||||
)
|
||||
|
||||
session.add_messages(messages)
|
||||
|
||||
alice = peers[0]
|
||||
bob = peers[1]
|
||||
|
||||
# Get alice's working representation in the session
|
||||
representation = session.working_rep(alice)
|
||||
print("working representation returned:", representation)
|
||||
|
||||
# Get alice's working representation *of bob* in the session
|
||||
representation = session.working_rep(alice, target=bob)
|
||||
print("working representation returned:", representation)
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
"""
|
||||
Example demonstrating Pydantic validation in the Honcho SDK.
|
||||
|
||||
This example shows how the SDK now uses Pydantic to validate inputs at runtime,
|
||||
providing better error messages and type safety.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from honcho import Honcho
|
||||
from pydantic import ValidationError
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def demonstrate_validation():
|
||||
"""Demonstrate various validation scenarios with the Pydantic-enhanced SDK."""
|
||||
|
||||
print("=== Pydantic Validation Examples ===\n")
|
||||
|
||||
# Example 1: Valid initialization
|
||||
print("1. Valid initialization:")
|
||||
try:
|
||||
honcho = Honcho(
|
||||
environment="local",
|
||||
workspace_id="test_workspace",
|
||||
timeout=30.0,
|
||||
max_retries=3,
|
||||
)
|
||||
print(f"✅ Successfully created client: {honcho}")
|
||||
except ValidationError as e:
|
||||
print(f"❌ Validation error: {e}")
|
||||
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
|
||||
# Example 2: Invalid timeout (negative)
|
||||
print("2. Invalid timeout (negative value):")
|
||||
try:
|
||||
honcho = Honcho(environment="local", timeout=-5.0)
|
||||
print("✅ This shouldn't happen!")
|
||||
except ValidationError as e:
|
||||
print(f"❌ Validation caught invalid timeout: {e.errors()[0]['msg']}")
|
||||
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
|
||||
# Example 3: Invalid max_retries (negative)
|
||||
print("3. Invalid max_retries (negative value):")
|
||||
try:
|
||||
honcho = Honcho(environment="local", max_retries=-1)
|
||||
print("✅ This shouldn't happen!")
|
||||
except ValidationError as e:
|
||||
print(f"❌ Validation caught invalid max_retries: {e.errors()[0]['msg']}")
|
||||
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
|
||||
# Example 4: Invalid peer ID (empty)
|
||||
print("4. Invalid peer ID (empty string):")
|
||||
try:
|
||||
honcho = Honcho(environment="local", workspace_id="test")
|
||||
peer = honcho.peer("")
|
||||
print("✅ This shouldn't happen!")
|
||||
except ValidationError as e:
|
||||
print(f"❌ Validation caught empty peer ID: {e.errors()[0]['msg']}")
|
||||
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
|
||||
# Example 5: Invalid session ID (empty)
|
||||
print("5. Invalid session ID (empty string):")
|
||||
try:
|
||||
honcho = Honcho(environment="local", workspace_id="test")
|
||||
session = honcho.session("")
|
||||
print("✅ This shouldn't happen!")
|
||||
except ValidationError as e:
|
||||
print(f"❌ Validation caught empty session ID: {e.errors()[0]['msg']}")
|
||||
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
|
||||
# Example 6: Valid peer and session creation
|
||||
print("6. Valid peer and session creation:")
|
||||
try:
|
||||
honcho = Honcho(environment="local", workspace_id="test")
|
||||
peer = honcho.peer("alice")
|
||||
session = honcho.session("conversation_1")
|
||||
print(f"✅ Created peer: {peer}")
|
||||
print(f"✅ Created session: {session}")
|
||||
except ValidationError as e:
|
||||
print(f"❌ Validation error: {e}")
|
||||
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
|
||||
# Example 7: Invalid message content (empty)
|
||||
print("7. Invalid message content (empty string):")
|
||||
try:
|
||||
honcho = Honcho(environment="local", workspace_id="test")
|
||||
peer = honcho.peer("alice")
|
||||
message = peer.message("")
|
||||
print("✅ This shouldn't happen!")
|
||||
except ValidationError as e:
|
||||
print(f"❌ Validation caught empty message content: {e.errors()[0]['msg']}")
|
||||
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
|
||||
# Example 9: Valid operations
|
||||
print("9. Valid operations (no API calls made):")
|
||||
try:
|
||||
honcho = Honcho(environment="local", workspace_id="test")
|
||||
peer = honcho.peer("alice")
|
||||
session = honcho.session("conversation_1")
|
||||
|
||||
# Create a valid message
|
||||
message = peer.message("Hello, world!", metadata={"type": "greeting"})
|
||||
print(
|
||||
f"✅ Created message: peer_id={message['peer_id']}, content='{message['content']}'"
|
||||
)
|
||||
|
||||
# Valid peer operations (validation passes, but no API calls made)
|
||||
print("✅ All validations passed for standard operations")
|
||||
|
||||
except ValidationError as e:
|
||||
print(f"❌ Validation error: {e}")
|
||||
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
print("🎉 Pydantic validation examples completed!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demonstrate_validation()
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import random
|
||||
import uuid
|
||||
|
||||
from honcho import Honcho
|
||||
|
||||
# Create a Honcho client with the default workspace
|
||||
honcho = Honcho(environment="local")
|
||||
|
||||
peers = [
|
||||
honcho.peer("alice"),
|
||||
honcho.peer("bob"),
|
||||
honcho.peer("charlie"),
|
||||
]
|
||||
|
||||
# Create a new session
|
||||
session = honcho.session("search_test_" + str(uuid.uuid4()))
|
||||
|
||||
# Create a message with our special keyword
|
||||
keyword = f"~special-{str(uuid.uuid4())}~"
|
||||
session.add_messages(peers[0].message(f"I am a {keyword} message"))
|
||||
|
||||
# Generate some random messages from alice, bob, and charlie and add them to the session
|
||||
messages = []
|
||||
for i in range(10):
|
||||
random_peer = random.choice(peers)
|
||||
messages.append(
|
||||
random_peer.message(f"Hello from {random_peer}! This is message {i}.")
|
||||
)
|
||||
|
||||
session.add_messages(messages)
|
||||
|
||||
# Search the session for the special keyword
|
||||
search_results = session.search(keyword)
|
||||
print("searching the session")
|
||||
print("search results returned:", [message for message in search_results])
|
||||
|
||||
# Search the workspace for the special keyword
|
||||
search_results = honcho.search(keyword)
|
||||
print("searching the workspace")
|
||||
print("search results returned:", [message for message in search_results])
|
||||
|
||||
alice = peers[0]
|
||||
|
||||
# Add a different message to alice's global representation
|
||||
different_keyword = f"~different-{str(uuid.uuid4())}~"
|
||||
alice.add_messages(alice.message(f"I am a {different_keyword} message"))
|
||||
|
||||
# Search alice's global representation for the different message
|
||||
search_results = alice.search(different_keyword)
|
||||
print("searching alice's global representation")
|
||||
print("search results returned:", [message for message in search_results])
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
[project]
|
||||
name = "honcho-ai"
|
||||
version = "1.1.0"
|
||||
description = "Official DX Optimized Python SDK for Honcho"
|
||||
dynamic = ["readme"]
|
||||
license = "Apache-2.0"
|
||||
authors = [
|
||||
{ name = "Plastic Labs", email = "hello@plasticlabs.ai" },
|
||||
]
|
||||
dependencies = [
|
||||
"honcho-core>=1.1.0",
|
||||
"httpx>=0.28.0, <1",
|
||||
"pydantic>=2.0.0, <3",
|
||||
]
|
||||
requires-python = ">= 3.8"
|
||||
classifiers = [
|
||||
"Typing :: Typed",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Operating System :: OS Independent",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/plastic-labs/honcho-sdks"
|
||||
Repository = "https://github.com/plastic-labs/honcho-sdks"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff>=0.11.13",
|
||||
]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
readme = {file = ["README.md"], content-type = "text/markdown"}
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "testpypi"
|
||||
url = "https://test.pypi.org/simple/"
|
||||
publish-url = "https://test.pypi.org/legacy/"
|
||||
explicit = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
include = ["honcho", "honcho.async_client"]
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
Honcho Python SDK
|
||||
|
||||
A Python client library for the Honcho conversational memory platform.
|
||||
Provides tools for managing peers, sessions, and conversation context
|
||||
across multi-party interactions.
|
||||
|
||||
Usage:
|
||||
from honcho import Honcho
|
||||
|
||||
# Initialize client
|
||||
client = Honcho(api_key="your-api-key")
|
||||
|
||||
# Create peers
|
||||
alice = client.peer("alice")
|
||||
bob = client.peer("bob")
|
||||
|
||||
# Create a session
|
||||
session = client.session("conversation-1")
|
||||
|
||||
# Add peers to session
|
||||
session.add_peers([alice, bob])
|
||||
|
||||
# Add messages
|
||||
session.add_messages([
|
||||
alice.message("Hello, Bob!"),
|
||||
bob.message("Hi Alice, how are you?")
|
||||
])
|
||||
|
||||
# Query conversation context
|
||||
response = alice.chat("What did Bob say to me?")
|
||||
"""
|
||||
|
||||
from .async_client import (
|
||||
AsyncHoncho,
|
||||
AsyncPage,
|
||||
AsyncPeer,
|
||||
AsyncSession,
|
||||
)
|
||||
from .client import Honcho
|
||||
from .pagination import SyncPage
|
||||
from .peer import Peer
|
||||
from .session import Session
|
||||
from .session_context import SessionContext
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "Plastic Labs"
|
||||
__email__ = "hello@plasticlabs.ai"
|
||||
|
||||
__all__ = [
|
||||
"AsyncHoncho",
|
||||
"AsyncPeer",
|
||||
"AsyncSession",
|
||||
"AsyncPage",
|
||||
"Honcho",
|
||||
"Peer",
|
||||
"Session",
|
||||
"SessionContext",
|
||||
"SyncPage",
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Async client module for the Honcho Python SDK.
|
||||
|
||||
Provides async versions of all client classes for asynchronous operations
|
||||
with the Honcho conversational memory platform.
|
||||
"""
|
||||
|
||||
from .client import AsyncHoncho
|
||||
from .pagination import AsyncPage
|
||||
from .peer import AsyncPeer
|
||||
from .session import AsyncSession
|
||||
|
||||
__all__ = [
|
||||
"AsyncHoncho",
|
||||
"AsyncPeer",
|
||||
"AsyncSession",
|
||||
"AsyncPage",
|
||||
]
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
import logging
|
||||
import os
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, Literal
|
||||
|
||||
import httpx
|
||||
from honcho_core import AsyncHoncho as AsyncHonchoCore
|
||||
from honcho_core import Honcho as HonchoCore
|
||||
from honcho_core.types.workspaces.sessions.message import Message
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, validate_call
|
||||
|
||||
from .pagination import AsyncPage
|
||||
from .peer import AsyncPeer
|
||||
from .session import AsyncSession
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AsyncHoncho(BaseModel):
|
||||
"""
|
||||
Main async client for the Honcho SDK.
|
||||
|
||||
Provides async 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 asynchronously.
|
||||
|
||||
Attributes:
|
||||
api_key: API key for authentication
|
||||
base_url: Base URL for the Honcho API
|
||||
workspace_id: Workspace ID for scoping operations
|
||||
"""
|
||||
|
||||
workspace_id: str = Field(
|
||||
...,
|
||||
min_length=1,
|
||||
description="Workspace ID for scoping operations",
|
||||
)
|
||||
_client: AsyncHonchoCore = PrivateAttr()
|
||||
|
||||
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str | None = None,
|
||||
environment: Literal["local", "production", "demo"] | None = None,
|
||||
base_url: str | None = Field(None, description="Base URL for the Honcho API"),
|
||||
workspace_id: str | None = Field(
|
||||
None, min_length=1, description="Workspace ID for scoping operations"
|
||||
),
|
||||
timeout: float | None = Field(None, gt=0, description="Timeout in seconds"),
|
||||
max_retries: int | None = Field(
|
||||
None, ge=0, description="Maximum number of retries"
|
||||
),
|
||||
default_headers: Mapping[str, str] | None = None,
|
||||
default_query: Mapping[str, object] | None = None,
|
||||
async_http_client: httpx.AsyncClient | None = Field(
|
||||
None, description="Custom HTTP client"
|
||||
),
|
||||
http_client: httpx.Client | None = Field(
|
||||
None, description="Custom HTTP client"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the AsyncHoncho client.
|
||||
|
||||
Args:
|
||||
api_key:
|
||||
API key for authentication. If not provided, will attempt to
|
||||
read from HONCHO_API_KEY environment variable
|
||||
environment:
|
||||
Environment to use (local or production)
|
||||
base_url:
|
||||
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
|
||||
workspace_id:
|
||||
Workspace ID to use for operations. If not provided, will
|
||||
attempt to read from HONCHO_WORKSPACE_ID environment variable
|
||||
or default to "default"
|
||||
timeout:
|
||||
Optional custom timeout for the HTTP client.
|
||||
max_retries:
|
||||
Optional custom maximum number of retries for the HTTP client.
|
||||
default_headers:
|
||||
Optional custom default headers for the HTTP client.
|
||||
default_query:
|
||||
Optional custom default query parameters for the HTTP client.
|
||||
http_client:
|
||||
Optional custom httpx client.
|
||||
"""
|
||||
# Resolve workspace_id before calling super().__init__
|
||||
resolved_workspace_id = workspace_id or os.getenv(
|
||||
"HONCHO_WORKSPACE_ID", "default"
|
||||
)
|
||||
|
||||
super().__init__(workspace_id=resolved_workspace_id)
|
||||
|
||||
# Build client kwargs, excluding None values that AsyncHonchoCore doesn't handle well
|
||||
client_kwargs: dict[str, Any] = {}
|
||||
|
||||
if api_key is not None:
|
||||
client_kwargs["api_key"] = api_key
|
||||
if environment is not None:
|
||||
client_kwargs["environment"] = environment
|
||||
if base_url is not None:
|
||||
client_kwargs["base_url"] = base_url
|
||||
if timeout is not None:
|
||||
client_kwargs["timeout"] = timeout
|
||||
if max_retries is not None:
|
||||
client_kwargs["max_retries"] = max_retries
|
||||
if default_headers is not None:
|
||||
client_kwargs["default_headers"] = default_headers
|
||||
if default_query is not None:
|
||||
client_kwargs["default_query"] = default_query
|
||||
|
||||
sync_client_kwargs = client_kwargs.copy()
|
||||
async_client_kwargs = client_kwargs.copy()
|
||||
|
||||
if http_client is not None:
|
||||
sync_client_kwargs["http_client"] = http_client
|
||||
if async_http_client is not None:
|
||||
async_client_kwargs["http_client"] = async_http_client
|
||||
|
||||
self._client = AsyncHonchoCore(**async_client_kwargs)
|
||||
|
||||
# Get or create the workspace using synchronous client
|
||||
sync_client = HonchoCore(**sync_client_kwargs)
|
||||
sync_client.workspaces.get_or_create(id=self.workspace_id)
|
||||
|
||||
@validate_call
|
||||
async def peer(
|
||||
self,
|
||||
id: str = Field(
|
||||
..., min_length=1, description="Unique identifier for the peer"
|
||||
),
|
||||
*,
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this peer. If set, will get/create peer immediately with flags.",
|
||||
),
|
||||
) -> AsyncPeer:
|
||||
"""
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Returns:
|
||||
An AsyncPeer object that can be used to send messages, join sessions, and
|
||||
query the peer's knowledge representations
|
||||
|
||||
Raises:
|
||||
ValidationError: If the peer ID is empty or invalid
|
||||
"""
|
||||
if config:
|
||||
return await AsyncPeer.create(
|
||||
id, self.workspace_id, self._client, config=config
|
||||
)
|
||||
return AsyncPeer(id, self.workspace_id, self._client)
|
||||
|
||||
async def get_peers(self) -> AsyncPage[AsyncPeer]:
|
||||
"""
|
||||
Get all peers in the current workspace.
|
||||
|
||||
Makes an async API call to retrieve all peers that have been created or used
|
||||
within the current workspace. Returns a paginated result that transforms
|
||||
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
|
||||
"""
|
||||
peers_page = await self._client.workspaces.peers.list(
|
||||
workspace_id=self.workspace_id
|
||||
)
|
||||
return AsyncPage(
|
||||
peers_page, lambda peer: AsyncPeer(peer.id, self.workspace_id, self._client)
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def session(
|
||||
self,
|
||||
id: str = Field(
|
||||
..., min_length=1, description="Unique identifier for the session"
|
||||
),
|
||||
*,
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this session. If set, will get/create session immediately with flags.",
|
||||
),
|
||||
) -> AsyncSession:
|
||||
"""
|
||||
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.
|
||||
|
||||
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.
|
||||
Returns:
|
||||
An AsyncSession object that can be used to add peers, send messages, and
|
||||
manage conversation context
|
||||
|
||||
Raises:
|
||||
ValidationError: If the session ID is empty or invalid
|
||||
"""
|
||||
if config:
|
||||
return await AsyncSession.create(
|
||||
id, self.workspace_id, self._client, config=config
|
||||
)
|
||||
return AsyncSession(id, self.workspace_id, self._client)
|
||||
|
||||
async def get_sessions(self) -> AsyncPage[AsyncSession]:
|
||||
"""
|
||||
Get all sessions in the current workspace.
|
||||
|
||||
Makes an async API call to retrieve all sessions that have been created within
|
||||
the current workspace.
|
||||
|
||||
Returns:
|
||||
An AsyncPage of AsyncSession objects representing all sessions in the workspace.
|
||||
Returns an empty page if no sessions exist
|
||||
"""
|
||||
sessions_page = await self._client.workspaces.sessions.list(
|
||||
workspace_id=self.workspace_id
|
||||
)
|
||||
return AsyncPage(
|
||||
sessions_page,
|
||||
lambda session: AsyncSession(session.id, self.workspace_id, self._client),
|
||||
)
|
||||
|
||||
async def get_metadata(self) -> dict[str, object]:
|
||||
"""
|
||||
Get metadata for the current workspace.
|
||||
|
||||
Makes an async 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:
|
||||
A dictionary containing the workspace's metadata. Returns an empty
|
||||
dictionary if no metadata is set
|
||||
"""
|
||||
workspace = await self._client.workspaces.get_or_create(id=self.workspace_id)
|
||||
return workspace.metadata or {}
|
||||
|
||||
@validate_call
|
||||
async def set_metadata(
|
||||
self,
|
||||
metadata: dict[str, object] = Field(..., description="Metadata dictionary"),
|
||||
) -> None:
|
||||
"""
|
||||
Set metadata for the current workspace.
|
||||
|
||||
Makes an async API call to update the metadata associated with the current workspace.
|
||||
This will overwrite any existing metadata with the provided values.
|
||||
|
||||
Args:
|
||||
metadata: A dictionary of metadata to associate with the workspace.
|
||||
Keys must be strings, values can be any JSON-serializable type
|
||||
"""
|
||||
await self._client.workspaces.update(self.workspace_id, metadata=metadata)
|
||||
|
||||
async def get_workspaces(self) -> list[str]:
|
||||
"""
|
||||
Get all workspace IDs from the Honcho instance.
|
||||
|
||||
Makes an async API call to retrieve all workspace IDs that the authenticated
|
||||
user has access to.
|
||||
|
||||
Returns:
|
||||
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()
|
||||
workspace_ids: list[str] = []
|
||||
async for workspace in workspaces_page:
|
||||
workspace_ids.append(workspace.id)
|
||||
return workspace_ids
|
||||
|
||||
@validate_call
|
||||
async def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> AsyncPage[Message]:
|
||||
"""
|
||||
Search for messages in the current workspace.
|
||||
|
||||
Makes an async API call to search for messages in the current workspace.
|
||||
|
||||
Args:
|
||||
query: The search query to use
|
||||
|
||||
Returns:
|
||||
An AsyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
"""
|
||||
messages_page = await self._client.workspaces.search(
|
||||
self.workspace_id, body=query
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return a string representation of the AsyncHoncho client.
|
||||
|
||||
Returns:
|
||||
A string representation suitable for debugging
|
||||
"""
|
||||
return f"AsyncHoncho(workspace_id='{self.workspace_id}', base_url='{self._client.base_url}')"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Return a human-readable string representation of the AsyncHoncho client.
|
||||
|
||||
Returns:
|
||||
A string showing the workspace ID
|
||||
"""
|
||||
return f"AsyncHoncho Client (workspace: {self.workspace_id})"
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
from collections.abc import AsyncIterator, Callable
|
||||
from typing import TypeVar
|
||||
|
||||
from honcho_core.pagination import AsyncPage as AsyncPageCore
|
||||
from pydantic import Field, validate_call
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
|
||||
class AsyncPage(AsyncPageCore[U]):
|
||||
"""
|
||||
Async paginated result wrapper that transforms objects from type T to type U.
|
||||
|
||||
Provides async iteration and transformation capabilities while preserving
|
||||
pagination functionality from the underlying core AsyncPage.
|
||||
"""
|
||||
|
||||
@validate_call
|
||||
def __init__(
|
||||
self,
|
||||
original_page: AsyncPageCore[T] = Field(
|
||||
..., description="The original AsyncPage to wrap"
|
||||
),
|
||||
transform_func: Callable[[T], U] | None = Field(
|
||||
None,
|
||||
description="Optional function to transform objects from type T to type U",
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the transformed async page.
|
||||
|
||||
Args:
|
||||
original_page: The original AsyncPage to wrap
|
||||
transform_func: Optional function to transform objects from type T to type U.
|
||||
If None, objects are passed through unchanged.
|
||||
"""
|
||||
super().__init__(items=original_page.items) # pyright: ignore
|
||||
self._original_page = original_page # pyright: ignore
|
||||
self._transform_func = transform_func # pyright: ignore
|
||||
|
||||
@property
|
||||
def items(self) -> list[U]: # pyright: ignore
|
||||
"""Get all optionally transformed items as a list."""
|
||||
if self._transform_func is not None:
|
||||
return [self._transform_func(item) for item in self._original_page.items]
|
||||
return self._original_page.items # pyright: ignore
|
||||
|
||||
async def __aiter__(self) -> AsyncIterator[U]:
|
||||
"""Async iterate over optionally transformed objects."""
|
||||
async for item in self._original_page:
|
||||
if self._transform_func is not None:
|
||||
yield self._transform_func(item)
|
||||
else:
|
||||
yield item # type: ignore # pyright: ignore
|
||||
|
||||
async def __agetitem__(self, index: int) -> U:
|
||||
"""Get an optionally transformed object by index."""
|
||||
item = await self._original_page.__agetitem__(index) # type: ignore # pyright: ignore
|
||||
if self._transform_func is not None:
|
||||
return self._transform_func(item) # pyright: ignore
|
||||
return item # type: ignore # pyright: ignore
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Get the length of the page."""
|
||||
return len(self._original_page) # type: ignore # pyright: ignore
|
||||
|
||||
@property
|
||||
async def data(self) -> list[U]:
|
||||
"""Get all optionally transformed data as a list."""
|
||||
data = await self._original_page.data # type: ignore # pyright: ignore
|
||||
if self._transform_func is not None:
|
||||
return [self._transform_func(item) for item in data] # pyright: ignore
|
||||
return data # type: ignore # pyright: ignore
|
||||
|
||||
@property
|
||||
def object(self) -> str:
|
||||
"""Get the object type."""
|
||||
return self._original_page.object # type: ignore # pyright: ignore
|
||||
|
||||
@property
|
||||
def has_next_page(self) -> bool: # pyright: ignore
|
||||
"""Check if there's a next page."""
|
||||
return self._original_page.has_next_page # type: ignore # pyright: ignore
|
||||
|
||||
async def next_page(self) -> "AsyncPage[U] | None":
|
||||
"""Get the next page with optional transformation applied."""
|
||||
next_page = await self._original_page.next_page() # type: ignore # pyright: ignore
|
||||
if next_page is None:
|
||||
return None
|
||||
return AsyncPage(next_page, self._transform_func) # pyright: ignore
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from honcho_core import AsyncHoncho as AsyncHonchoCore
|
||||
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
|
||||
|
||||
from .pagination import AsyncPage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .session import AsyncSession
|
||||
|
||||
|
||||
class AsyncPeer(BaseModel):
|
||||
"""
|
||||
Represents a peer in the Honcho system with async operations.
|
||||
|
||||
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.
|
||||
|
||||
Attributes:
|
||||
id: Unique identifier for this peer
|
||||
_client: Reference to the parent AsyncHoncho client instance
|
||||
"""
|
||||
|
||||
id: str = Field(..., min_length=1, description="Unique identifier for this peer")
|
||||
workspace_id: str = Field(
|
||||
..., min_length=1, description="Workspace ID for scoping operations"
|
||||
)
|
||||
_client: AsyncHonchoCore = PrivateAttr()
|
||||
|
||||
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
|
||||
def __init__(
|
||||
self,
|
||||
peer_id: str = Field(
|
||||
...,
|
||||
min_length=1,
|
||||
description="Unique identifier for this peer within the workspace",
|
||||
),
|
||||
workspace_id: str = Field(
|
||||
..., min_length=1, description="Workspace ID for scoping operations"
|
||||
),
|
||||
client: AsyncHonchoCore = Field(
|
||||
..., description="Reference to the parent AsyncHoncho client instance"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize a new AsyncPeer.
|
||||
|
||||
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
|
||||
"""
|
||||
super().__init__(id=peer_id, workspace_id=workspace_id)
|
||||
self._client = client
|
||||
|
||||
@classmethod
|
||||
async def create(
|
||||
cls,
|
||||
peer_id: str,
|
||||
workspace_id: str,
|
||||
client: AsyncHonchoCore,
|
||||
*,
|
||||
config: dict[str, object] | None = None,
|
||||
) -> AsyncPeer:
|
||||
"""
|
||||
Create a new AsyncPeer with optional configuration.
|
||||
|
||||
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
|
||||
config: Optional configuration to set for this peer.
|
||||
If set, will get/create peer immediately with flags.
|
||||
|
||||
Returns:
|
||||
A new AsyncPeer instance
|
||||
"""
|
||||
peer = cls(peer_id, workspace_id, client)
|
||||
|
||||
if config:
|
||||
await client.workspaces.peers.get_or_create(
|
||||
workspace_id=workspace_id,
|
||||
id=peer_id,
|
||||
configuration=config,
|
||||
)
|
||||
|
||||
return peer
|
||||
|
||||
async def chat(
|
||||
self,
|
||||
queries: str | list[str],
|
||||
*,
|
||||
stream: bool = False,
|
||||
target: str | AsyncPeer | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> str | None:
|
||||
"""
|
||||
Query the peer's representation with a natural language question.
|
||||
|
||||
Makes an async 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).
|
||||
|
||||
Args:
|
||||
queries: The natural language question(s) to ask. Can be a single string or a list of strings.
|
||||
stream: Whether to stream the response
|
||||
target: Optional target peer for local representation queries. If provided,
|
||||
queries what this peer knows about the target peer rather than
|
||||
querying the peer's global representation
|
||||
session_id: Optional session ID to scope the query to a specific session.
|
||||
If provided, only information from that session is considered
|
||||
|
||||
Returns:
|
||||
Response string containing the answer to the query, or None if no
|
||||
relevant information is available
|
||||
"""
|
||||
response = await self._client.workspaces.peers.chat(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
queries=queries,
|
||||
stream=stream,
|
||||
target=str(target.id) if isinstance(target, AsyncPeer) else target,
|
||||
session_id=session_id,
|
||||
)
|
||||
# "If the context provided doesn't help address the query, write absolutely NOTHING but "None""
|
||||
if response.content in ("", None, "None"):
|
||||
return None
|
||||
return response.content
|
||||
|
||||
async def get_sessions(self) -> AsyncPage[AsyncSession]:
|
||||
"""
|
||||
Get all sessions this peer is a member of.
|
||||
|
||||
Makes an async 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.
|
||||
|
||||
Returns:
|
||||
An async paginated list of AsyncSession objects this peer belongs to. Returns an empty
|
||||
list if the peer is not a member of any sessions
|
||||
"""
|
||||
from .session import AsyncSession
|
||||
|
||||
sessions_page = await self._client.workspaces.peers.sessions.list(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
)
|
||||
return AsyncPage(
|
||||
sessions_page,
|
||||
lambda session: AsyncSession(session.id, self.workspace_id, self._client),
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def add_messages(
|
||||
self,
|
||||
content: str | MessageCreateParam | list[MessageCreateParam] = Field(
|
||||
..., description="Content to add to the peer's representation"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Add messages or content to this peer's global representation.
|
||||
|
||||
Makes an async API call to store content associated with this peer. This content
|
||||
becomes part of the peer's global knowledge base and can be retrieved
|
||||
through chat queries. Content can be provided as raw strings, Message objects,
|
||||
or lists of Message objects.
|
||||
|
||||
Args:
|
||||
content: Content to add to the peer's representation. Can be:
|
||||
- str: Raw text content that will be converted to a Message
|
||||
- Message: A single Message object to add
|
||||
- List[Message]: Multiple Message objects to add in batch
|
||||
"""
|
||||
messages: list[MessageCreateParam]
|
||||
if isinstance(content, str):
|
||||
messages = [
|
||||
MessageCreateParam(peer_id=self.id, content=content, metadata=None)
|
||||
]
|
||||
elif isinstance(content, list):
|
||||
messages = content
|
||||
else:
|
||||
messages = [content]
|
||||
|
||||
await self._client.workspaces.peers.messages.create(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def get_messages(
|
||||
self,
|
||||
*,
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Dictionary of filter criteria"
|
||||
),
|
||||
) -> AsyncPage[Message]:
|
||||
"""
|
||||
Get messages saved to this peer outside of a session with optional filtering.
|
||||
|
||||
Makes an API call to retrieve messages saved to this peer outside of a session.
|
||||
Results can be filtered based on various criteria.
|
||||
|
||||
Args:
|
||||
filters: Dictionary of filter criteria. Supported filters include:
|
||||
- peer_id: Filter messages by the peer who created them
|
||||
- metadata: Filter messages by metadata key-value pairs
|
||||
- timestamp_start: Filter messages after a specific timestamp
|
||||
- timestamp_end: Filter messages before a specific timestamp
|
||||
|
||||
Returns:
|
||||
An AsyncPage of Message objects matching the specified criteria, ordered by
|
||||
creation time (most recent first)
|
||||
"""
|
||||
messages_page = await self._client.workspaces.peers.messages.list(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
filter=filters,
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
@validate_call
|
||||
def message(
|
||||
self,
|
||||
content: str = Field(
|
||||
..., min_length=1, description="The text content for the message"
|
||||
),
|
||||
*,
|
||||
metadata: dict[str, object] | None = Field(
|
||||
None, description="Optional metadata dictionary"
|
||||
),
|
||||
) -> MessageCreateParam:
|
||||
"""
|
||||
Create a MessageCreateParam object attributed to this peer.
|
||||
|
||||
This is a convenience method for creating MessageCreateParam objects with this peer's ID.
|
||||
The created MessageCreateParam can then be added to sessions or used in other operations.
|
||||
|
||||
Args:
|
||||
content: The text content for the message
|
||||
metadata: Optional metadata dictionary to associate with the message
|
||||
|
||||
Returns:
|
||||
A new MessageCreateParam object with this peer's ID and the provided content
|
||||
"""
|
||||
return MessageCreateParam(peer_id=self.id, content=content, metadata=metadata)
|
||||
|
||||
async def get_metadata(self) -> dict[str, object]:
|
||||
"""
|
||||
Get the current metadata for this peer.
|
||||
|
||||
Makes an async 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:
|
||||
A dictionary containing the peer's metadata. Returns an empty dictionary
|
||||
if no metadata is set
|
||||
"""
|
||||
peer = await self._client.workspaces.peers.get_or_create(
|
||||
workspace_id=self.workspace_id,
|
||||
id=self.id,
|
||||
)
|
||||
return peer.metadata or {}
|
||||
|
||||
@validate_call
|
||||
async def set_metadata(
|
||||
self,
|
||||
metadata: dict[str, object] = Field(
|
||||
..., description="Metadata dictionary to associate with this peer"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Set the metadata for this peer.
|
||||
|
||||
Makes an async API call to update the metadata associated with this peer.
|
||||
This will overwrite any existing metadata with the provided values.
|
||||
|
||||
Args:
|
||||
metadata: A dictionary of metadata 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,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> AsyncPage[Message]:
|
||||
"""
|
||||
Search for messages in this peer's global representation.
|
||||
|
||||
Makes an async API call to search for messages in this peer's global representation.
|
||||
|
||||
Args:
|
||||
query: The search query to use
|
||||
|
||||
Returns:
|
||||
An AsyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
"""
|
||||
messages_page = await self._client.workspaces.peers.search(
|
||||
self.id, workspace_id=self.workspace_id, query=query
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return a string representation of the AsyncPeer.
|
||||
|
||||
Returns:
|
||||
A string representation suitable for debugging
|
||||
"""
|
||||
return f"AsyncPeer(id='{self.id}')"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Return a human-readable string representation of the AsyncPeer.
|
||||
|
||||
Returns:
|
||||
The peer's ID
|
||||
"""
|
||||
return self.id
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
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
|
||||
|
||||
from ..session_context import SessionContext
|
||||
from .pagination import AsyncPage
|
||||
|
||||
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,
|
||||
description="Whether this peer should form a session-level theory-of-mind representation of other peers in the session",
|
||||
)
|
||||
observe_me: bool | None = Field(
|
||||
None,
|
||||
description="Whether other peers in this session should try to form a session-level theory-of-mind representation of this peer",
|
||||
)
|
||||
|
||||
|
||||
class AsyncSession(BaseModel):
|
||||
"""
|
||||
Represents a session in Honcho with async operations.
|
||||
|
||||
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.
|
||||
|
||||
Attributes:
|
||||
id: Unique identifier for this session
|
||||
_client: Reference to the parent AsyncHoncho client instance
|
||||
anonymous: Whether this is an anonymous session
|
||||
summarize: Whether automatic summarization is enabled
|
||||
"""
|
||||
|
||||
id: str = Field(..., min_length=1, description="Unique identifier for this session")
|
||||
workspace_id: str = Field(
|
||||
..., min_length=1, description="Workspace ID for scoping operations"
|
||||
)
|
||||
_client: AsyncHonchoCore = PrivateAttr()
|
||||
|
||||
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
|
||||
def __init__(
|
||||
self,
|
||||
session_id: str = Field(
|
||||
..., min_length=1, description="Unique identifier for this session"
|
||||
),
|
||||
workspace_id: str = Field(
|
||||
..., min_length=1, description="Workspace ID for scoping operations"
|
||||
),
|
||||
client: AsyncHonchoCore = Field(
|
||||
..., description="Reference to the parent AsyncHoncho client instance"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize a new AsyncSession.
|
||||
|
||||
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
|
||||
"""
|
||||
super().__init__(
|
||||
id=session_id,
|
||||
workspace_id=workspace_id,
|
||||
)
|
||||
self._client = client
|
||||
|
||||
@classmethod
|
||||
async def create(
|
||||
cls,
|
||||
session_id: str,
|
||||
workspace_id: str,
|
||||
client: AsyncHonchoCore,
|
||||
*,
|
||||
config: dict[str, object] | None = None,
|
||||
) -> AsyncSession:
|
||||
"""
|
||||
Create a new AsyncSession with optional configuration.
|
||||
|
||||
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.
|
||||
|
||||
Returns:
|
||||
A new AsyncSession instance
|
||||
"""
|
||||
session = cls(session_id, workspace_id, client)
|
||||
|
||||
if config:
|
||||
await client.workspaces.sessions.get_or_create(
|
||||
workspace_id=workspace_id,
|
||||
id=session_id,
|
||||
configuration=config,
|
||||
)
|
||||
|
||||
return session
|
||||
|
||||
async def add_peers(
|
||||
self,
|
||||
peers: str
|
||||
| AsyncPeer
|
||||
| tuple[str, SessionPeerConfig]
|
||||
| tuple[AsyncPeer, SessionPeerConfig]
|
||||
| list[AsyncPeer | str]
|
||||
| list[tuple[AsyncPeer | str, SessionPeerConfig]]
|
||||
| list[AsyncPeer | str | tuple[AsyncPeer | str, SessionPeerConfig]] = Field(
|
||||
..., description="Peers to add to the session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Add peers to this session.
|
||||
|
||||
Makes an async 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.
|
||||
|
||||
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
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
||||
peer_dict: dict[str, Any] = {}
|
||||
for peer in peers:
|
||||
if isinstance(peer, tuple):
|
||||
# Handle tuple[str/AsyncPeer, SessionPeerConfig]
|
||||
peer_id = peer[0] if isinstance(peer[0], str) else peer[0].id
|
||||
peer_config = peer[1]
|
||||
peer_dict[peer_id] = peer_config.model_dump(exclude_none=True)
|
||||
else:
|
||||
# Handle direct str or AsyncPeer
|
||||
peer_id = peer if isinstance(peer, str) else peer.id
|
||||
peer_dict[peer_id] = {}
|
||||
|
||||
await self._client.workspaces.sessions.peers.add(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
body=peer_dict,
|
||||
)
|
||||
|
||||
async def set_peers(
|
||||
self,
|
||||
peers: str
|
||||
| AsyncPeer
|
||||
| tuple[str, SessionPeerConfig]
|
||||
| tuple[AsyncPeer, SessionPeerConfig]
|
||||
| list[AsyncPeer | str]
|
||||
| list[tuple[AsyncPeer | str, SessionPeerConfig]]
|
||||
| list[AsyncPeer | str | tuple[AsyncPeer | str, SessionPeerConfig]] = Field(
|
||||
..., description="Peers to set for the session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
||||
peer_dict: dict[str, Any] = {}
|
||||
for peer in peers:
|
||||
if isinstance(peer, tuple):
|
||||
# Handle tuple[str/AsyncPeer, SessionPeerConfig]
|
||||
peer_id = peer[0] if isinstance(peer[0], str) else peer[0].id
|
||||
peer_config = peer[1]
|
||||
peer_dict[peer_id] = peer_config.model_dump(exclude_none=True)
|
||||
else:
|
||||
# Handle direct str or AsyncPeer
|
||||
peer_id = peer if isinstance(peer, str) else peer.id
|
||||
peer_dict[peer_id] = {}
|
||||
|
||||
await self._client.workspaces.sessions.peers.set(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
body=peer_dict,
|
||||
)
|
||||
|
||||
async def remove_peers(
|
||||
self,
|
||||
peers: str | AsyncPeer | list[AsyncPeer | str] = Field(
|
||||
..., description="Peers to remove from the session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Remove peers from this session.
|
||||
|
||||
Makes an async 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.
|
||||
|
||||
Args:
|
||||
peers: Peers to remove from the session. Can be:
|
||||
- str: Single peer ID
|
||||
- AsyncPeer: Single AsyncPeer object
|
||||
- List[Union[AsyncPeer, str]]: List of AsyncPeer objects and/or peer IDs
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
||||
peer_ids = [peer if isinstance(peer, str) else peer.id for peer in peers]
|
||||
|
||||
await self._client.workspaces.sessions.peers.remove(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
body=peer_ids,
|
||||
)
|
||||
|
||||
async def get_peers(self) -> list[AsyncPeer]:
|
||||
"""
|
||||
Get all peers in this session.
|
||||
|
||||
Makes an async API call to retrieve the list of peer IDs that are currently
|
||||
members of this session. Automatically converts the paginated response
|
||||
into a list for us -- the max number of peers in a session is usually 10.
|
||||
|
||||
Returns:
|
||||
A list of AsyncPeer objects that are members of this session
|
||||
"""
|
||||
from .peer import AsyncPeer
|
||||
|
||||
peers_page = await self._client.workspaces.sessions.peers.list(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
)
|
||||
return [
|
||||
AsyncPeer(peer.id, self.workspace_id, self._client)
|
||||
for peer in peers_page.items
|
||||
]
|
||||
|
||||
async def get_peer_config(self, peer: str | AsyncPeer) -> SessionPeerConfig:
|
||||
"""
|
||||
Get the configuration for a peer in this session.
|
||||
"""
|
||||
from .peer import AsyncPeer
|
||||
|
||||
peer_get_config_response = (
|
||||
await self._client.workspaces.sessions.peers.get_config(
|
||||
peer_id=str(peer.id) if isinstance(peer, AsyncPeer) else peer,
|
||||
workspace_id=self.workspace_id,
|
||||
session_id=self.id,
|
||||
)
|
||||
)
|
||||
return SessionPeerConfig(
|
||||
observe_others=peer_get_config_response.observe_others,
|
||||
observe_me=peer_get_config_response.observe_me,
|
||||
)
|
||||
|
||||
async def set_peer_config(
|
||||
self, peer: str | AsyncPeer, config: SessionPeerConfig
|
||||
) -> None:
|
||||
"""
|
||||
Set the configuration for a peer in this session.
|
||||
"""
|
||||
from .peer import AsyncPeer
|
||||
|
||||
await self._client.workspaces.sessions.peers.set_config(
|
||||
peer_id=str(peer.id) if isinstance(peer, AsyncPeer) else peer,
|
||||
workspace_id=self.workspace_id,
|
||||
session_id=self.id,
|
||||
observe_others=NOT_GIVEN
|
||||
if config.observe_others is None
|
||||
else config.observe_others,
|
||||
observe_me=NOT_GIVEN if config.observe_me is None else config.observe_me,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def add_messages(
|
||||
self,
|
||||
messages: MessageCreateParam | list[MessageCreateParam] = Field(
|
||||
..., description="Messages to add to the session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
messages: Messages to add to the session. Can be:
|
||||
- MessageCreateParam: Single MessageCreateParam object
|
||||
- List[MessageCreateParam]: List of MessageCreateParam objects
|
||||
"""
|
||||
if not isinstance(messages, list):
|
||||
messages = [messages]
|
||||
|
||||
await self._client.workspaces.sessions.messages.create(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
messages=[MessageCreateParam(**message) for message in messages],
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def get_messages(
|
||||
self,
|
||||
*,
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Dictionary of filter criteria"
|
||||
),
|
||||
) -> AsyncPage[Message]:
|
||||
"""
|
||||
Get messages from this session with optional filtering.
|
||||
|
||||
Makes an async API call to retrieve messages from this session. Results can be
|
||||
filtered based on various criteria.
|
||||
|
||||
Args:
|
||||
filters: Dictionary of filter criteria. Supported filters include:
|
||||
- peer_id: Filter messages by the peer who created them
|
||||
- metadata: Filter messages by metadata key-value pairs
|
||||
- timestamp_start: Filter messages after a specific timestamp
|
||||
- timestamp_end: Filter messages before a specific timestamp
|
||||
|
||||
Returns:
|
||||
An async paginated list of Message objects matching the specified criteria, ordered by
|
||||
creation time (most recent first)
|
||||
"""
|
||||
messages_page = await self._client.workspaces.sessions.messages.list(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
filter=filters,
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
async def get_metadata(self) -> dict[str, object]:
|
||||
"""
|
||||
Get metadata for this session.
|
||||
|
||||
Makes an async API call to retrieve the current metadata associated with this session.
|
||||
Metadata can include custom attributes, settings, or any other key-value data.
|
||||
|
||||
Returns:
|
||||
A dictionary containing the session's metadata. Returns an empty dictionary
|
||||
if no metadata is set
|
||||
"""
|
||||
session = await self._client.workspaces.sessions.get_or_create(
|
||||
workspace_id=self.workspace_id,
|
||||
id=self.id,
|
||||
)
|
||||
return session.metadata or {}
|
||||
|
||||
@validate_call
|
||||
async def set_metadata(
|
||||
self,
|
||||
metadata: dict[str, object] = Field(
|
||||
..., description="Metadata dictionary to associate with this session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Set metadata for this session.
|
||||
|
||||
Makes an async API call to update the metadata associated with this session.
|
||||
This will overwrite any existing metadata with the provided values.
|
||||
|
||||
Args:
|
||||
metadata: A dictionary of metadata to associate with this session.
|
||||
Keys must be strings, values can be any JSON-serializable type
|
||||
"""
|
||||
await self._client.workspaces.sessions.update(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def get_context(
|
||||
self,
|
||||
*,
|
||||
summary: bool = True,
|
||||
tokens: int | None = Field(
|
||||
None, gt=0, description="Maximum number of tokens to include in the context"
|
||||
),
|
||||
) -> SessionContext:
|
||||
"""
|
||||
Get optimized context for this session within a token limit.
|
||||
|
||||
Makes an async 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.
|
||||
|
||||
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.
|
||||
|
||||
Returns:
|
||||
A SessionContext object containing the optimized message history
|
||||
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,
|
||||
summary=summary,
|
||||
)
|
||||
|
||||
return SessionContext(
|
||||
session_id=self.id, messages=context.messages, summary=context.summary
|
||||
)
|
||||
|
||||
@validate_call
|
||||
async def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> AsyncPage[Message]:
|
||||
"""
|
||||
Search for messages in this session.
|
||||
|
||||
Makes an async API call to search for messages in this session.
|
||||
|
||||
Args:
|
||||
query: The search query to use
|
||||
|
||||
Returns:
|
||||
An AsyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
"""
|
||||
messages_page = await self._client.workspaces.sessions.search(
|
||||
self.id, workspace_id=self.workspace_id, query=query
|
||||
)
|
||||
return AsyncPage(messages_page)
|
||||
|
||||
async def working_rep(
|
||||
self,
|
||||
peer: str | AsyncPeer,
|
||||
*,
|
||||
target: str | AsyncPeer | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""
|
||||
Get the current working representation of the peer in this session.
|
||||
|
||||
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`.
|
||||
|
||||
Returns:
|
||||
A dictionary containing information about the peer.
|
||||
"""
|
||||
from .peer import AsyncPeer
|
||||
|
||||
return await self._client.workspaces.peers.working_representation(
|
||||
str(peer.id) if isinstance(peer, AsyncPeer) else peer,
|
||||
workspace_id=self.workspace_id,
|
||||
session_id=self.id,
|
||||
target=str(target.id) if isinstance(target, AsyncPeer) else target,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return a string representation of the AsyncSession.
|
||||
|
||||
Returns:
|
||||
A string representation suitable for debugging
|
||||
"""
|
||||
return f"AsyncSession(id='{self.id}')"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Return a human-readable string representation of the AsyncSession.
|
||||
|
||||
Returns:
|
||||
The session's ID
|
||||
"""
|
||||
return self.id
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
import logging
|
||||
import os
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, Literal
|
||||
|
||||
import httpx
|
||||
from honcho_core import Honcho as HonchoCore
|
||||
from honcho_core.types.workspaces.sessions.message import Message
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, validate_call
|
||||
|
||||
from .pagination import SyncPage
|
||||
from .peer import Peer
|
||||
from .session import Session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Honcho(BaseModel):
|
||||
"""
|
||||
Main client for the Honcho SDK.
|
||||
|
||||
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.
|
||||
|
||||
Attributes:
|
||||
api_key: API key for authentication
|
||||
base_url: Base URL for the Honcho API
|
||||
workspace_id: Workspace ID for scoping operations
|
||||
"""
|
||||
|
||||
workspace_id: str = Field(
|
||||
...,
|
||||
min_length=1,
|
||||
description="Workspace ID for scoping operations",
|
||||
)
|
||||
_client: HonchoCore = PrivateAttr()
|
||||
|
||||
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str | None = None,
|
||||
environment: Literal["local", "production", "demo"] | None = None,
|
||||
base_url: str | None = Field(None, description="Base URL for the Honcho API"),
|
||||
workspace_id: str | None = Field(
|
||||
None, min_length=1, description="Workspace ID for scoping operations"
|
||||
),
|
||||
timeout: float | None = Field(None, gt=0, description="Timeout in seconds"),
|
||||
max_retries: int | None = Field(
|
||||
None, ge=0, description="Maximum number of retries"
|
||||
),
|
||||
default_headers: Mapping[str, str] | None = None,
|
||||
default_query: Mapping[str, object] | None = None,
|
||||
http_client: httpx.Client | None = Field(
|
||||
None, description="Custom HTTP client"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the Honcho client.
|
||||
|
||||
Args:
|
||||
api_key:
|
||||
API key for authentication. If not provided, will attempt to
|
||||
read from HONCHO_API_KEY environment variable
|
||||
environment:
|
||||
Environment to use (local or production)
|
||||
base_url:
|
||||
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
|
||||
workspace_id:
|
||||
Workspace ID to use for operations. If not provided, will
|
||||
attempt to read from HONCHO_WORKSPACE_ID environment variable
|
||||
or default to "default"
|
||||
timeout:
|
||||
Optional custom timeout for the HTTP client.
|
||||
max_retries:
|
||||
Optional custom maximum number of retries for the HTTP client.
|
||||
default_headers:
|
||||
Optional custom default headers for the HTTP client.
|
||||
default_query:
|
||||
Optional custom default query parameters for the HTTP client.
|
||||
http_client:
|
||||
Optional custom httpx client.
|
||||
"""
|
||||
# Resolve workspace_id before calling super().__init__
|
||||
resolved_workspace_id = workspace_id or os.getenv(
|
||||
"HONCHO_WORKSPACE_ID", "default"
|
||||
)
|
||||
|
||||
super().__init__(workspace_id=resolved_workspace_id)
|
||||
|
||||
# Build client kwargs, excluding None values that HonchoCore doesn't handle well
|
||||
client_kwargs: dict[str, Any] = {}
|
||||
|
||||
if api_key is not None:
|
||||
client_kwargs["api_key"] = api_key
|
||||
if environment is not None:
|
||||
client_kwargs["environment"] = environment
|
||||
if base_url is not None:
|
||||
client_kwargs["base_url"] = base_url
|
||||
if timeout is not None:
|
||||
client_kwargs["timeout"] = timeout
|
||||
if max_retries is not None:
|
||||
client_kwargs["max_retries"] = max_retries
|
||||
if default_headers is not None:
|
||||
client_kwargs["default_headers"] = default_headers
|
||||
if default_query is not None:
|
||||
client_kwargs["default_query"] = default_query
|
||||
if http_client is not None:
|
||||
client_kwargs["http_client"] = http_client
|
||||
|
||||
self._client = HonchoCore(**client_kwargs)
|
||||
|
||||
# Get or create the workspace
|
||||
self._client.workspaces.get_or_create(id=self.workspace_id)
|
||||
|
||||
@validate_call
|
||||
def peer(
|
||||
self,
|
||||
id: str = Field(
|
||||
..., min_length=1, description="Unique identifier for the peer"
|
||||
),
|
||||
*,
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this peer. If set, will get/create peer immediately with flags.",
|
||||
),
|
||||
) -> Peer:
|
||||
"""
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Returns:
|
||||
A Peer object that can be used to send messages, join sessions, and
|
||||
query the peer's knowledge representations
|
||||
|
||||
Raises:
|
||||
ValidationError: If the peer ID is empty or invalid
|
||||
"""
|
||||
return Peer(id, self.workspace_id, self._client, config=config)
|
||||
|
||||
def get_peers(self) -> SyncPage[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 that transforms
|
||||
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
|
||||
"""
|
||||
peers_page = self._client.workspaces.peers.list(workspace_id=self.workspace_id)
|
||||
return SyncPage(
|
||||
peers_page, lambda peer: Peer(peer.id, self.workspace_id, self._client)
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def session(
|
||||
self,
|
||||
id: str = Field(
|
||||
..., min_length=1, description="Unique identifier for the session"
|
||||
),
|
||||
*,
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this session. If set, will get/create session immediately with flags.",
|
||||
),
|
||||
) -> Session:
|
||||
"""
|
||||
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.
|
||||
|
||||
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.
|
||||
Returns:
|
||||
A Session object that can be used to add peers, send messages, and
|
||||
manage conversation context
|
||||
|
||||
Raises:
|
||||
ValidationError: If the session ID is empty or invalid
|
||||
"""
|
||||
return Session(id, self.workspace_id, self._client, config=config)
|
||||
|
||||
def get_sessions(self) -> SyncPage[Session]:
|
||||
"""
|
||||
Get all sessions in the current workspace.
|
||||
|
||||
Makes an API call to retrieve all sessions that have been created within
|
||||
the current workspace.
|
||||
|
||||
Returns:
|
||||
A SyncPage of Session objects representing all sessions in the workspace.
|
||||
Returns an empty page if no sessions exist
|
||||
"""
|
||||
sessions_page = self._client.workspaces.sessions.list(
|
||||
workspace_id=self.workspace_id
|
||||
)
|
||||
return SyncPage(
|
||||
sessions_page,
|
||||
lambda session: Session(session.id, self.workspace_id, self._client),
|
||||
)
|
||||
|
||||
def get_metadata(self) -> dict[str, object]:
|
||||
"""
|
||||
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:
|
||||
A dictionary containing the workspace's metadata. Returns an empty
|
||||
dictionary if no metadata is set
|
||||
"""
|
||||
workspace = self._client.workspaces.get_or_create(id=self.workspace_id)
|
||||
return workspace.metadata or {}
|
||||
|
||||
@validate_call
|
||||
def set_metadata(
|
||||
self,
|
||||
metadata: dict[str, object] = Field(..., description="Metadata dictionary"),
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
metadata: A dictionary of metadata to associate with the workspace.
|
||||
Keys must be strings, values can be any JSON-serializable type
|
||||
"""
|
||||
self._client.workspaces.update(self.workspace_id, metadata=metadata)
|
||||
|
||||
def get_workspaces(self) -> list[str]:
|
||||
"""
|
||||
Get all workspace IDs from the Honcho instance.
|
||||
|
||||
Makes an API call to retrieve all workspace IDs that the authenticated
|
||||
user has access to.
|
||||
|
||||
Returns:
|
||||
A list of workspace ID strings. Returns an empty list if no workspaces
|
||||
are accessible or none exist
|
||||
"""
|
||||
workspaces = self._client.workspaces.list()
|
||||
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]:
|
||||
"""
|
||||
Search for messages in the current workspace.
|
||||
|
||||
Makes an API call to search for messages in the current workspace.
|
||||
|
||||
Args:
|
||||
query: The search query to use
|
||||
|
||||
Returns:
|
||||
A SyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
"""
|
||||
messages_page = self._client.workspaces.search(self.workspace_id, body=query)
|
||||
return SyncPage(messages_page)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return a string representation of the Honcho client.
|
||||
|
||||
Returns:
|
||||
A string representation suitable for debugging
|
||||
"""
|
||||
return f"Honcho(workspace_id='{self.workspace_id}', base_url='{self._client.base_url}')"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Return a human-readable string representation of the Honcho client.
|
||||
|
||||
Returns:
|
||||
A string showing the workspace ID
|
||||
"""
|
||||
return f"Honcho Client (workspace: {self.workspace_id})"
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
from collections.abc import Callable, Iterator
|
||||
from typing import Optional, TypeVar # pyright: ignore
|
||||
|
||||
from honcho_core.pagination import SyncPage as SyncPageCore
|
||||
from pydantic import Field, validate_call
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
|
||||
class SyncPage(SyncPageCore[U]):
|
||||
"""
|
||||
Paginated result wrapper that transforms objects from type T to type U.
|
||||
|
||||
Provides iteration and transformation capabilities while preserving
|
||||
pagination functionality from the underlying core SyncPage.
|
||||
"""
|
||||
|
||||
@validate_call
|
||||
def __init__( # pyright: ignore
|
||||
self,
|
||||
original_page: SyncPageCore[T] = Field(
|
||||
..., description="The original SyncPage to wrap"
|
||||
),
|
||||
transform_func: Callable[[T], U] | None = Field(
|
||||
None,
|
||||
description="Optional function to transform objects from type T to type U",
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the transformed page.
|
||||
|
||||
Args:
|
||||
original_page: The original SyncPage to wrap
|
||||
transform_func: Optional function to transform objects from type T to type U.
|
||||
If None, objects are passed through unchanged.
|
||||
"""
|
||||
self._original_page = original_page # pyright: ignore
|
||||
self._transform_func = transform_func # pyright: ignore
|
||||
|
||||
def __iter__(self) -> Iterator[U]:
|
||||
"""Iterate over optionally transformed objects."""
|
||||
for item in self._original_page:
|
||||
if self._transform_func is not None:
|
||||
yield self._transform_func(item)
|
||||
else:
|
||||
yield item # pyright: ignore
|
||||
|
||||
def __getitem__(self, index: int) -> U:
|
||||
"""Get an optionally transformed object by index."""
|
||||
item = self._original_page[index] # pyright: ignore
|
||||
if self._transform_func is not None:
|
||||
return self._transform_func(item) # pyright: ignore
|
||||
return item # pyright: ignore
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Get the length of the page."""
|
||||
return len(self._original_page) # pyright: ignore
|
||||
|
||||
@property
|
||||
def data(self) -> list[U]:
|
||||
"""Get all optionally transformed data as a list."""
|
||||
if self._transform_func is not None:
|
||||
return [self._transform_func(item) for item in self._original_page.data] # pyright: ignore
|
||||
return self._original_page.data # pyright: ignore
|
||||
|
||||
@property
|
||||
def object(self) -> str:
|
||||
"""Get the object type."""
|
||||
return self._original_page.object # pyright: ignore
|
||||
|
||||
@property
|
||||
def has_next_page(self) -> bool: # pyright: ignore
|
||||
"""Check if there's a next page."""
|
||||
return self._original_page.has_next_page # pyright: ignore
|
||||
|
||||
def next_page(self) -> Optional["SyncPage[U]"]: # pyright: ignore
|
||||
"""Get the next page with optional transformation applied."""
|
||||
next_page = self._original_page.next_page() # pyright: ignore
|
||||
if next_page is None:
|
||||
return None
|
||||
return SyncPage(next_page, self._transform_func) # pyright: ignore
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from honcho_core import Honcho as HonchoCore
|
||||
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
|
||||
|
||||
from .pagination import SyncPage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .session import Session
|
||||
|
||||
|
||||
class Peer(BaseModel):
|
||||
"""
|
||||
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.
|
||||
|
||||
Attributes:
|
||||
id: Unique identifier for this peer
|
||||
_client: Reference to the parent Honcho client instance
|
||||
"""
|
||||
|
||||
id: str = Field(..., min_length=1, description="Unique identifier for this peer")
|
||||
workspace_id: str = Field(
|
||||
..., min_length=1, description="Workspace ID for scoping operations"
|
||||
)
|
||||
_client: HonchoCore = PrivateAttr()
|
||||
|
||||
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
|
||||
def __init__(
|
||||
self,
|
||||
peer_id: str = Field(
|
||||
...,
|
||||
min_length=1,
|
||||
description="Unique identifier for this peer within the workspace",
|
||||
),
|
||||
workspace_id: str = Field(
|
||||
..., min_length=1, description="Workspace ID for scoping operations"
|
||||
),
|
||||
client: HonchoCore = Field(
|
||||
..., description="Reference to the parent Honcho client instance"
|
||||
),
|
||||
*,
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this peer. If set, will get/create peer immediately with flags.",
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize a new Peer.
|
||||
|
||||
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
|
||||
config: Optional configuration to set for this peer.
|
||||
If set, will get/create peer immediately with flags.
|
||||
"""
|
||||
super().__init__(id=peer_id, workspace_id=workspace_id)
|
||||
self._client = client
|
||||
|
||||
if config:
|
||||
self._client.workspaces.peers.get_or_create(
|
||||
workspace_id=workspace_id,
|
||||
id=peer_id,
|
||||
configuration=config,
|
||||
)
|
||||
|
||||
def chat(
|
||||
self,
|
||||
queries: str | list[str],
|
||||
*,
|
||||
stream: bool = False,
|
||||
target: str | Peer | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> str | None:
|
||||
"""
|
||||
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).
|
||||
|
||||
Args:
|
||||
queries: The natural language question(s) to ask. Can be a single string or a list of strings.
|
||||
stream: Whether to stream the response
|
||||
target: Optional target peer for local representation queries. If provided,
|
||||
queries what this peer knows about the target peer rather than
|
||||
querying the peer's global representation
|
||||
session_id: Optional session ID to scope the query to a specific session.
|
||||
If provided, only information from that session is considered
|
||||
|
||||
Returns:
|
||||
Response string containing the answer to the query, or None if no
|
||||
relevant information is available
|
||||
"""
|
||||
response = self._client.workspaces.peers.chat(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
queries=queries,
|
||||
stream=stream,
|
||||
target=str(target.id) if isinstance(target, Peer) else target,
|
||||
session_id=session_id,
|
||||
)
|
||||
if response.content in ("", None, "None"):
|
||||
return None
|
||||
return response.content
|
||||
|
||||
def get_sessions(self) -> SyncPage[Session]:
|
||||
"""
|
||||
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.
|
||||
|
||||
Returns:
|
||||
A paginated list of Session objects this peer belongs to. Returns an empty
|
||||
list if the peer is not a member of any sessions
|
||||
"""
|
||||
from .session import Session
|
||||
|
||||
sessions_page = self._client.workspaces.peers.sessions.list(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
)
|
||||
return SyncPage(
|
||||
sessions_page,
|
||||
lambda session: Session(session.id, self.workspace_id, self._client),
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def add_messages(
|
||||
self,
|
||||
content: str | MessageCreateParam | list[MessageCreateParam] = Field(
|
||||
..., description="Content to add to the peer's representation"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Add messages or content to this peer's global representation.
|
||||
|
||||
Makes an API call to store content associated with this peer. This content
|
||||
becomes part of the peer's global knowledge base and can be retrieved
|
||||
through chat queries. Content can be provided as raw strings, Message objects,
|
||||
or lists of Message objects.
|
||||
|
||||
Args:
|
||||
content: Content to add to the peer's representation. Can be:
|
||||
- str: Raw text content that will be converted to a Message
|
||||
- Message: A single Message object to add
|
||||
- List[Message]: Multiple Message objects to add in batch
|
||||
"""
|
||||
messages: list[MessageCreateParam]
|
||||
if isinstance(content, str):
|
||||
messages = [
|
||||
MessageCreateParam(peer_id=self.id, content=content, metadata=None)
|
||||
]
|
||||
elif isinstance(content, list):
|
||||
messages = content
|
||||
else:
|
||||
messages = [content]
|
||||
|
||||
self._client.workspaces.peers.messages.create(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def get_messages(
|
||||
self,
|
||||
*,
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Dictionary of filter criteria"
|
||||
),
|
||||
) -> SyncPage[Message]:
|
||||
"""
|
||||
Get messages saved to this peer outside of a session with optional filtering.
|
||||
|
||||
Makes an API call to retrieve messages saved to this peer outside of a session.
|
||||
Results can be filtered based on various criteria.
|
||||
|
||||
Args:
|
||||
filters: Dictionary of filter criteria. Supported filters include:
|
||||
- peer_id: Filter messages by the peer who created them
|
||||
- metadata: Filter messages by metadata key-value pairs
|
||||
- timestamp_start: Filter messages after a specific timestamp
|
||||
- timestamp_end: Filter messages before a specific timestamp
|
||||
|
||||
Returns:
|
||||
A SyncPage of Message objects matching the specified criteria, ordered by
|
||||
creation time (most recent first)
|
||||
"""
|
||||
messages_page = self._client.workspaces.peers.messages.list(
|
||||
peer_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
filter=filters,
|
||||
)
|
||||
return SyncPage(messages_page)
|
||||
|
||||
@validate_call
|
||||
def message(
|
||||
self,
|
||||
content: str = Field(
|
||||
..., min_length=1, description="The text content for the message"
|
||||
),
|
||||
*,
|
||||
metadata: dict[str, object] | None = Field(
|
||||
None, description="Optional metadata dictionary"
|
||||
),
|
||||
) -> MessageCreateParam:
|
||||
"""
|
||||
Create a MessageCreateParam object attributed to this peer.
|
||||
|
||||
This is a convenience method for creating MessageCreateParam objects with this peer's ID.
|
||||
The created MessageCreateParam can then be added to sessions or used in other operations.
|
||||
|
||||
Args:
|
||||
content: The text content for the message
|
||||
metadata: Optional metadata dictionary to associate with the message
|
||||
|
||||
Returns:
|
||||
A new MessageCreateParam object with this peer's ID and the provided content
|
||||
"""
|
||||
return MessageCreateParam(peer_id=self.id, content=content, metadata=metadata)
|
||||
|
||||
def get_metadata(self) -> dict[str, object]:
|
||||
"""
|
||||
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:
|
||||
A dictionary containing the peer's metadata. Returns an empty dictionary
|
||||
if no metadata is set
|
||||
"""
|
||||
peer = self._client.workspaces.peers.get_or_create(
|
||||
workspace_id=self.workspace_id,
|
||||
id=self.id,
|
||||
)
|
||||
return peer.metadata or {}
|
||||
|
||||
@validate_call
|
||||
def set_metadata(
|
||||
self,
|
||||
metadata: dict[str, object] = Field(
|
||||
..., description="Metadata dictionary to associate with this peer"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
metadata: A dictionary of metadata 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,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> SyncPage[Message]:
|
||||
"""
|
||||
Search for messages in this peer's global representation.
|
||||
|
||||
Makes an API call to search for messages in this peer's global representation.
|
||||
|
||||
Args:
|
||||
query: The search query to use
|
||||
|
||||
Returns:
|
||||
A SyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
"""
|
||||
messages_page = self._client.workspaces.peers.search(
|
||||
self.id, workspace_id=self.workspace_id, query=query
|
||||
)
|
||||
return SyncPage(messages_page)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return a string representation of the Peer.
|
||||
|
||||
Returns:
|
||||
A string representation suitable for debugging
|
||||
"""
|
||||
return f"Peer(id='{self.id}')"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Return a human-readable string representation of the Peer.
|
||||
|
||||
Returns:
|
||||
The peer's ID
|
||||
"""
|
||||
return self.id
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1,490 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
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
|
||||
|
||||
from .pagination import SyncPage
|
||||
from .session_context import SessionContext
|
||||
|
||||
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,
|
||||
description="Whether this peer should form a session-level theory-of-mind representation of other peers in the session",
|
||||
)
|
||||
observe_me: bool | None = Field(
|
||||
None,
|
||||
description="Whether other peers in this session should try to form a session-level theory-of-mind representation of this peer",
|
||||
)
|
||||
|
||||
|
||||
class Session(BaseModel):
|
||||
"""
|
||||
Represents a session in Honcho.
|
||||
|
||||
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.
|
||||
|
||||
Attributes:
|
||||
id: Unique identifier for this session
|
||||
_honcho: Reference to the parent Honcho client instance
|
||||
anonymous: Whether this is an anonymous session
|
||||
summarize: Whether automatic summarization is enabled
|
||||
"""
|
||||
|
||||
id: str = Field(..., min_length=1, description="Unique identifier for this session")
|
||||
workspace_id: str = Field(
|
||||
..., min_length=1, description="Workspace ID for scoping operations"
|
||||
)
|
||||
_client: HonchoCore = PrivateAttr()
|
||||
|
||||
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
|
||||
def __init__(
|
||||
self,
|
||||
session_id: str = Field(
|
||||
..., min_length=1, description="Unique identifier for this session"
|
||||
),
|
||||
workspace_id: str = Field(
|
||||
..., min_length=1, description="Workspace ID for scoping operations"
|
||||
),
|
||||
client: HonchoCore = Field(
|
||||
..., description="Reference to the parent Honcho client instance"
|
||||
),
|
||||
*,
|
||||
config: dict[str, object] | None = Field(
|
||||
None,
|
||||
description="Optional configuration to set for this session. If set, will get/create session immediately with flags.",
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize a new Session.
|
||||
|
||||
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.
|
||||
"""
|
||||
super().__init__(
|
||||
id=session_id,
|
||||
workspace_id=workspace_id,
|
||||
)
|
||||
self._client = client
|
||||
|
||||
if config:
|
||||
self._client.workspaces.sessions.get_or_create(
|
||||
workspace_id=workspace_id,
|
||||
id=session_id,
|
||||
configuration=config,
|
||||
)
|
||||
|
||||
def add_peers(
|
||||
self,
|
||||
peers: str
|
||||
| Peer
|
||||
| tuple[str, SessionPeerConfig]
|
||||
| tuple[Peer, SessionPeerConfig]
|
||||
| list[Peer | str]
|
||||
| list[tuple[Peer | str, SessionPeerConfig]]
|
||||
| list[Peer | str | tuple[Peer | str, SessionPeerConfig]] = Field(
|
||||
..., description="Peers to add to the session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Add peers to this session.
|
||||
|
||||
Makes an async 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.
|
||||
|
||||
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
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
||||
peer_dict: dict[str, Any] = {}
|
||||
for peer in peers:
|
||||
if isinstance(peer, tuple):
|
||||
# Handle tuple[str/Peer, SessionPeerConfig]
|
||||
peer_id = peer[0] if isinstance(peer[0], str) else peer[0].id
|
||||
peer_config = peer[1]
|
||||
peer_dict[peer_id] = peer_config.model_dump(exclude_none=True)
|
||||
else:
|
||||
# Handle direct str or Peer
|
||||
peer_id = peer if isinstance(peer, str) else peer.id
|
||||
peer_dict[peer_id] = {}
|
||||
|
||||
self._client.workspaces.sessions.peers.add(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
body=peer_dict,
|
||||
)
|
||||
|
||||
def set_peers(
|
||||
self,
|
||||
peers: str
|
||||
| Peer
|
||||
| tuple[str, SessionPeerConfig]
|
||||
| tuple[Peer, SessionPeerConfig]
|
||||
| list[Peer | str]
|
||||
| list[tuple[Peer | str, SessionPeerConfig]]
|
||||
| list[Peer | str | tuple[Peer | str, SessionPeerConfig]] = Field(
|
||||
..., description="Peers to set for the session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
||||
peer_dict: dict[str, Any] = {}
|
||||
for peer in peers:
|
||||
if isinstance(peer, tuple):
|
||||
# Handle tuple[str/Peer, SessionPeerConfig]
|
||||
peer_id = peer[0] if isinstance(peer[0], str) else peer[0].id
|
||||
peer_config = peer[1]
|
||||
peer_dict[peer_id] = peer_config.model_dump(exclude_none=True)
|
||||
else:
|
||||
# Handle direct str or Peer
|
||||
peer_id = peer if isinstance(peer, str) else peer.id
|
||||
peer_dict[peer_id] = {}
|
||||
|
||||
self._client.workspaces.sessions.peers.set(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
body=peer_dict,
|
||||
)
|
||||
|
||||
def remove_peers(
|
||||
self,
|
||||
peers: str | Peer | list[Peer | str] = Field(
|
||||
..., description="Peers to remove from the session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
peers: Peers to remove from the session. Can be:
|
||||
- str: Single peer ID
|
||||
- Peer: Single Peer object
|
||||
- List[Union[Peer, str]]: List of Peer objects and/or peer IDs
|
||||
"""
|
||||
if not isinstance(peers, list):
|
||||
peers = [peers]
|
||||
|
||||
peer_ids = [peer if isinstance(peer, str) else peer.id for peer in peers]
|
||||
|
||||
self._client.workspaces.sessions.peers.remove(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
body=peer_ids,
|
||||
)
|
||||
|
||||
def get_peers(self) -> list[Peer]:
|
||||
"""
|
||||
Get all peers in this session.
|
||||
|
||||
Makes an API call to retrieve the list of peer IDs that are currently
|
||||
members of this session. Automatically converts the paginated response
|
||||
into a list for us -- the max number of peers in a session is usually 10.
|
||||
|
||||
Returns:
|
||||
A list of Peer objects that are members of this session
|
||||
"""
|
||||
from .peer import Peer
|
||||
|
||||
peers_page = self._client.workspaces.sessions.peers.list(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
)
|
||||
return [
|
||||
Peer(peer.id, self.workspace_id, self._client) for peer in peers_page.items
|
||||
]
|
||||
|
||||
def get_peer_config(self, peer: str | Peer) -> SessionPeerConfig:
|
||||
"""
|
||||
Get the configuration for a peer in this session.
|
||||
"""
|
||||
from .peer import Peer
|
||||
|
||||
peer_get_config_response = self._client.workspaces.sessions.peers.get_config(
|
||||
peer_id=str(peer.id) if isinstance(peer, Peer) else peer,
|
||||
workspace_id=self.workspace_id,
|
||||
session_id=self.id,
|
||||
)
|
||||
return SessionPeerConfig(
|
||||
observe_others=peer_get_config_response.observe_others,
|
||||
observe_me=peer_get_config_response.observe_me,
|
||||
)
|
||||
|
||||
def set_peer_config(self, peer: str | Peer, config: SessionPeerConfig) -> None:
|
||||
"""
|
||||
Set the configuration for a peer in this session.
|
||||
"""
|
||||
from .peer import Peer
|
||||
|
||||
self._client.workspaces.sessions.peers.set_config(
|
||||
peer_id=str(peer.id) if isinstance(peer, Peer) else peer,
|
||||
workspace_id=self.workspace_id,
|
||||
session_id=self.id,
|
||||
observe_others=NOT_GIVEN
|
||||
if config.observe_others is None
|
||||
else config.observe_others,
|
||||
observe_me=NOT_GIVEN if config.observe_me is None else config.observe_me,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def add_messages(
|
||||
self,
|
||||
messages: MessageCreateParam | list[MessageCreateParam] = Field(
|
||||
..., description="Messages to add to the session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
messages: Messages to add to the session. Can be:
|
||||
- MessageCreateParam: Single MessageCreateParam object
|
||||
- List[MessageCreateParam]: List of MessageCreateParam objects
|
||||
"""
|
||||
if not isinstance(messages, list):
|
||||
messages = [messages]
|
||||
|
||||
self._client.workspaces.sessions.messages.create(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
messages=[MessageCreateParam(**message) for message in messages],
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def get_messages(
|
||||
self,
|
||||
*,
|
||||
filters: dict[str, object] | None = Field(
|
||||
None, description="Dictionary of filter criteria"
|
||||
),
|
||||
) -> SyncPage[Message]:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
filters: Dictionary of filter criteria. Supported filters include:
|
||||
- peer_id: Filter messages by the peer who created them
|
||||
- metadata: Filter messages by metadata key-value pairs
|
||||
- timestamp_start: Filter messages after a specific timestamp
|
||||
- timestamp_end: Filter messages before a specific timestamp
|
||||
|
||||
Returns:
|
||||
A list of Message objects matching the specified criteria, ordered by
|
||||
creation time (most recent first)
|
||||
"""
|
||||
messages_page = self._client.workspaces.sessions.messages.list(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
filter=filters,
|
||||
)
|
||||
return SyncPage(messages_page)
|
||||
|
||||
def get_metadata(self) -> dict[str, object]:
|
||||
"""
|
||||
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.
|
||||
|
||||
Returns:
|
||||
A dictionary containing the session's metadata. Returns an empty dictionary
|
||||
if no metadata is set
|
||||
"""
|
||||
return (
|
||||
self._client.workspaces.sessions.get_or_create(
|
||||
workspace_id=self.workspace_id,
|
||||
id=self.id,
|
||||
).metadata
|
||||
or {}
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def set_metadata(
|
||||
self,
|
||||
metadata: dict[str, object] = Field(
|
||||
..., description="Metadata dictionary to associate with this session"
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
metadata: A dictionary of metadata to associate with this session.
|
||||
Keys must be strings, values can be any JSON-serializable type
|
||||
"""
|
||||
self._client.workspaces.sessions.update(
|
||||
session_id=self.id,
|
||||
workspace_id=self.workspace_id,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def get_context(
|
||||
self,
|
||||
*,
|
||||
summary: bool = True,
|
||||
tokens: int | None = Field(
|
||||
None, gt=0, description="Maximum number of tokens to include in the context"
|
||||
),
|
||||
) -> SessionContext:
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Returns:
|
||||
A SessionContext object containing the optimized message history
|
||||
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,
|
||||
summary=summary,
|
||||
)
|
||||
|
||||
return SessionContext(
|
||||
session_id=self.id, messages=context.messages, summary=context.summary
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def search(
|
||||
self,
|
||||
query: str = Field(..., min_length=1, description="The search query to use"),
|
||||
) -> SyncPage[Message]:
|
||||
"""
|
||||
Search for messages in this session.
|
||||
|
||||
Makes an API call to search for messages in this session.
|
||||
|
||||
Args:
|
||||
query: The search query to use
|
||||
|
||||
Returns:
|
||||
A SyncPage of Message objects representing the search results.
|
||||
Returns an empty page if no messages are found.
|
||||
"""
|
||||
messages_page = self._client.workspaces.sessions.search(
|
||||
self.id, workspace_id=self.workspace_id, query=query
|
||||
)
|
||||
return SyncPage(messages_page)
|
||||
|
||||
def working_rep(
|
||||
self,
|
||||
peer: str | Peer,
|
||||
*,
|
||||
target: str | Peer | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""
|
||||
Get the current working representation of the peer in this session.
|
||||
|
||||
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`.
|
||||
|
||||
Returns:
|
||||
A dictionary containing information about the peer.
|
||||
"""
|
||||
from .peer import Peer
|
||||
|
||||
return self._client.workspaces.peers.working_representation(
|
||||
str(peer.id) if isinstance(peer, Peer) else peer,
|
||||
workspace_id=self.workspace_id,
|
||||
session_id=self.id,
|
||||
target=str(target.id) if isinstance(target, Peer) else target,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return a string representation of the Session.
|
||||
|
||||
Returns:
|
||||
A string representation suitable for debugging
|
||||
"""
|
||||
return f"Session(id='{self.id}')"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Return a human-readable string representation of the Session.
|
||||
|
||||
Returns:
|
||||
The session's ID
|
||||
"""
|
||||
return self.id
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
from honcho_core.types.workspaces.sessions.message import Message
|
||||
from pydantic import BaseModel, Field, validate_call
|
||||
|
||||
from .peer import Peer
|
||||
|
||||
|
||||
class SessionContext(BaseModel):
|
||||
"""
|
||||
Represents the context of a session containing a curated list of messages.
|
||||
|
||||
The SessionContext provides methods to convert message history into formats
|
||||
compatible with different LLM providers while staying within token limits
|
||||
and providing optimal conversation context.
|
||||
|
||||
Attributes:
|
||||
messages: List of Message objects representing the conversation context
|
||||
"""
|
||||
|
||||
session_id: str = Field(
|
||||
..., description="ID of the session this context belongs to"
|
||||
)
|
||||
messages: list[Message] = Field(
|
||||
..., description="List of Message objects to include in the context"
|
||||
)
|
||||
summary: str = Field(
|
||||
..., description="Summary of the session history prior to the message cutoff"
|
||||
)
|
||||
|
||||
@validate_call
|
||||
def __init__(
|
||||
self,
|
||||
session_id: str = Field(
|
||||
..., description="ID of the session this context belongs to"
|
||||
),
|
||||
messages: list[Message] = Field(
|
||||
..., description="List of Message objects to include in the context"
|
||||
),
|
||||
summary: str = Field(
|
||||
...,
|
||||
description="Summary of the session history prior to the message cutoff",
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Initialize a new SessionContext.
|
||||
|
||||
Args:
|
||||
messages: List of Message objects to include in the context
|
||||
"""
|
||||
super().__init__(
|
||||
session_id=session_id,
|
||||
messages=messages,
|
||||
summary=summary,
|
||||
)
|
||||
|
||||
def to_openai(
|
||||
self,
|
||||
*,
|
||||
assistant: str | Peer,
|
||||
) -> list[dict[str, object]]:
|
||||
"""
|
||||
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
|
||||
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"
|
||||
|
||||
Returns:
|
||||
A list of dictionaries in OpenAI format, where each dictionary contains
|
||||
"role" and "content" keys suitable for the OpenAI API
|
||||
|
||||
Raises:
|
||||
ValidationError: If assistant parameter is invalid
|
||||
"""
|
||||
assistant_id = assistant.id if isinstance(assistant, Peer) else assistant
|
||||
return [
|
||||
{
|
||||
"role": "assistant" if message.peer_id == assistant_id else "user",
|
||||
"content": message.content,
|
||||
}
|
||||
for message in self.messages
|
||||
]
|
||||
|
||||
def to_anthropic(
|
||||
self,
|
||||
*,
|
||||
assistant: str | Peer,
|
||||
) -> list[dict[str, object]]:
|
||||
"""
|
||||
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.
|
||||
|
||||
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"
|
||||
|
||||
Returns:
|
||||
A list of dictionaries in Anthropic format, where each dictionary contains
|
||||
"role" and "content" keys suitable for the Anthropic API
|
||||
|
||||
Raises:
|
||||
ValidationError: If assistant parameter is invalid
|
||||
|
||||
Note:
|
||||
Future versions may implement role alternation requirements for
|
||||
Anthropic's API compatibility
|
||||
"""
|
||||
assistant_id = assistant.id if isinstance(assistant, Peer) else assistant
|
||||
return [
|
||||
{
|
||||
"role": "assistant" if message.peer_id == assistant_id else "user",
|
||||
"content": message.content,
|
||||
}
|
||||
for message in self.messages
|
||||
]
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Return the number of messages in the context.
|
||||
|
||||
Returns:
|
||||
The number of messages in this context
|
||||
"""
|
||||
return len(self.messages)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return a string representation of the SessionContext.
|
||||
|
||||
Returns:
|
||||
A string representation suitable for debugging
|
||||
"""
|
||||
return f"SessionContext(messages={len(self.messages)})"
|
||||
|
|
@ -0,0 +1,509 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.8"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.9'",
|
||||
"python_full_version < '3.9'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.9'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.9'" },
|
||||
{ name = "idna", marker = "python_full_version < '3.9'" },
|
||||
{ name = "sniffio", marker = "python_full_version < '3.9'" },
|
||||
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/f9/9a7ce600ebe7804daf90d4d48b1c0510a4561ddce43a596be46676f82343/anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b", size = 171293, upload-time = "2024-10-13T22:18:03.307Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/b4/f7e396030e3b11394436358ca258a81d6010106582422f23443c16ca1873/anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f", size = 89766, upload-time = "2024-10-13T22:18:01.524Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.9'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
|
||||
{ name = "idna", marker = "python_full_version >= '3.9'" },
|
||||
{ name = "sniffio", 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' and 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" }
|
||||
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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.4.26"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distro"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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' and python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "honcho-ai"
|
||||
version = "1.0.2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "honcho-core" },
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic", version = "2.10.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "pydantic", version = "2.11.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "honcho-core", specifier = ">=1.0.1" },
|
||||
{ name = "httpx", specifier = ">=0.28.0,<1" },
|
||||
{ name = "pydantic", specifier = ">=2.0.0,<3" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "ruff", specifier = ">=0.11.13" }]
|
||||
|
||||
[[package]]
|
||||
name = "honcho-core"
|
||||
version = "1.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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'" },
|
||||
{ name = "distro" },
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic", version = "2.10.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "pydantic", version = "2.11.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
||||
{ name = "sniffio" },
|
||||
{ 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/cc/73/59cfd4769eb041e6bf47cfdfec609b7c913e7c6844c9732ad2c53c92bd6d/honcho_core-1.0.1.tar.gz", hash = "sha256:90c1a6ca79c0686453a764eee47ae8e74ca277849dbbe5bd7de9bbd0505ea6ad", size = 119013, upload-time = "2025-06-19T16:29:09.276Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/b1/0b7809b2635c60c16d0d53cedaaf8102b19ff9a5ef91578d8a713c970057/honcho_core-1.0.1-py3-none-any.whl", hash = "sha256:5f549dfadcb07a366c18d1feef6d4020e7b97ff9f3d98f454cb591b117e662c8", size = 110594, upload-time = "2025-06-19T16:29:08.153Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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'" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.9'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "annotated-types", marker = "python_full_version < '3.9'" },
|
||||
{ name = "pydantic-core", version = "2.27.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.9'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "annotated-types", marker = "python_full_version >= '3.9'" },
|
||||
{ name = "pydantic-core", version = "2.33.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'" },
|
||||
{ name = "typing-inspection", marker = "python_full_version >= '3.9'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.9'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938, upload-time = "2024-12-18T11:27:14.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684, upload-time = "2024-12-18T11:27:16.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169, upload-time = "2024-12-18T11:27:22.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227, upload-time = "2024-12-18T11:27:25.097Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695, upload-time = "2024-12-18T11:27:28.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662, upload-time = "2024-12-18T11:27:30.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370, upload-time = "2024-12-18T11:27:33.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813, upload-time = "2024-12-18T11:27:37.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287, upload-time = "2024-12-18T11:27:40.566Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414, upload-time = "2024-12-18T11:27:43.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301, upload-time = "2024-12-18T11:27:47.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685, upload-time = "2024-12-18T11:27:50.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876, upload-time = "2024-12-18T11:27:53.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421, upload-time = "2024-12-18T11:27:55.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998, upload-time = "2024-12-18T11:27:57.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167, upload-time = "2024-12-18T11:27:59.146Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071, upload-time = "2024-12-18T11:28:02.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244, upload-time = "2024-12-18T11:28:04.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470, upload-time = "2024-12-18T11:28:07.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291, upload-time = "2024-12-18T11:28:10.297Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613, upload-time = "2024-12-18T11:28:13.362Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355, upload-time = "2024-12-18T11:28:16.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661, upload-time = "2024-12-18T11:28:18.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261, upload-time = "2024-12-18T11:28:21.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361, upload-time = "2024-12-18T11:28:23.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484, upload-time = "2024-12-18T11:28:25.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102, upload-time = "2024-12-18T11:28:28.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/53/13e9917fc69c0a4aea06fd63ed6a8d6cda9cf140ca9584d49c1650b0ef5e/pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506", size = 1899595, upload-time = "2024-12-18T11:29:40.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/20/26c549249769ed84877f862f7bb93f89a6ee08b4bee1ed8781616b7fbb5e/pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320", size = 1775010, upload-time = "2024-12-18T11:29:44.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/eb/8234e05452d92d2b102ffa1b56d801c3567e628fdc63f02080fdfc68fd5e/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145", size = 1830727, upload-time = "2024-12-18T11:29:46.904Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/df/59f915c8b929d5f61e5a46accf748a87110ba145156f9326d1a7d28912b2/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1", size = 1868393, upload-time = "2024-12-18T11:29:49.098Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/52/81cf4071dca654d485c277c581db368b0c95b2b883f4d7b736ab54f72ddf/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228", size = 2040300, upload-time = "2024-12-18T11:29:51.43Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/00/05197ce1614f5c08d7a06e1d39d5d8e704dc81971b2719af134b844e2eaf/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046", size = 2738785, upload-time = "2024-12-18T11:29:55.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/a3/5f19bc495793546825ab160e530330c2afcee2281c02b5ffafd0b32ac05e/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5", size = 1996493, upload-time = "2024-12-18T11:29:57.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/e8/e0102c2ec153dc3eed88aea03990e1b06cfbca532916b8a48173245afe60/pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a", size = 1998544, upload-time = "2024-12-18T11:30:00.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/a3/4be70845b555bd80aaee9f9812a7cf3df81550bce6dadb3cfee9c5d8421d/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d", size = 2007449, upload-time = "2024-12-18T11:30:02.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/9f/b779ed2480ba355c054e6d7ea77792467631d674b13d8257085a4bc7dcda/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9", size = 2129460, upload-time = "2024-12-18T11:30:06.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f0/a6ab0681f6e95260c7fbf552874af7302f2ea37b459f9b7f00698f875492/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da", size = 2159609, upload-time = "2024-12-18T11:30:09.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/2b/e1059506795104349712fbca647b18b3f4a7fd541c099e6259717441e1e0/pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b", size = 1819886, upload-time = "2024-12-18T11:30:11.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/6d/df49c17f024dfc58db0bacc7b03610058018dd2ea2eaf748ccbada4c3d06/pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad", size = 1980773, upload-time = "2024-12-18T11:30:14.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/97/3aef1ddb65c5ccd6eda9050036c956ff6ecbfe66cb7eb40f280f121a5bb0/pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993", size = 1896475, upload-time = "2024-12-18T11:30:18.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/d3/5668da70e373c9904ed2f372cb52c0b996426f302e0dee2e65634c92007d/pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308", size = 1772279, upload-time = "2024-12-18T11:30:20.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/9e/e44b8cb0edf04a2f0a1f6425a65ee089c1d6f9c4c2dcab0209127b6fdfc2/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4", size = 1829112, upload-time = "2024-12-18T11:30:23.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/90/1160d7ac700102effe11616e8119e268770f2a2aa5afb935f3ee6832987d/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf", size = 1866780, upload-time = "2024-12-18T11:30:25.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/33/13983426df09a36d22c15980008f8d9c77674fc319351813b5a2739b70f3/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76", size = 2037943, upload-time = "2024-12-18T11:30:28.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/d7/ced164e376f6747e9158c89988c293cd524ab8d215ae4e185e9929655d5c/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118", size = 2740492, upload-time = "2024-12-18T11:30:30.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/1f/3dc6e769d5b7461040778816aab2b00422427bcaa4b56cc89e9c653b2605/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630", size = 1995714, upload-time = "2024-12-18T11:30:34.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/d7/a0bd09bc39283530b3f7c27033a814ef254ba3bd0b5cfd040b7abf1fe5da/pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54", size = 1997163, upload-time = "2024-12-18T11:30:37.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/bb/2db4ad1762e1c5699d9b857eeb41959191980de6feb054e70f93085e1bcd/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f", size = 2005217, upload-time = "2024-12-18T11:30:40.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/5f/23a5a3e7b8403f8dd8fc8a6f8b49f6b55c7d715b77dcf1f8ae919eeb5628/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362", size = 2127899, upload-time = "2024-12-18T11:30:42.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/ae/aa38bb8dd3d89c2f1d8362dd890ee8f3b967330821d03bbe08fa01ce3766/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96", size = 2155726, upload-time = "2024-12-18T11:30:45.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/61/4f784608cc9e98f70839187117ce840480f768fed5d386f924074bf6213c/pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e", size = 1817219, upload-time = "2024-12-18T11:30:47.718Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/82/bb16a68e4a1a858bb3768c2c8f1ff8d8978014e16598f001ea29a25bf1d1/pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67", size = 1985382, upload-time = "2024-12-18T11:30:51.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159, upload-time = "2024-12-18T11:30:54.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331, upload-time = "2024-12-18T11:30:58.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467, upload-time = "2024-12-18T11:31:00.6Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797, upload-time = "2024-12-18T11:31:07.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839, upload-time = "2024-12-18T11:31:09.775Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861, upload-time = "2024-12-18T11:31:13.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582, upload-time = "2024-12-18T11:31:17.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985, upload-time = "2024-12-18T11:31:19.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715, upload-time = "2024-12-18T11:31:22.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/0e/dcaea00c9dbd0348b723cae82b0e0c122e0fa2b43fa933e1622fd237a3ee/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656", size = 1891733, upload-time = "2024-12-18T11:31:26.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/d3/e797bba8860ce650272bda6383a9d8cad1d1c9a75a640c9d0e848076f85e/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278", size = 1768375, upload-time = "2024-12-18T11:31:29.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/f7/f847b15fb14978ca2b30262548f5fc4872b2724e90f116393eb69008299d/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb", size = 1822307, upload-time = "2024-12-18T11:31:33.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/63/ed80ec8255b587b2f108e514dc03eed1546cd00f0af281e699797f373f38/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd", size = 1979971, upload-time = "2024-12-18T11:31:35.755Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/6d/6d18308a45454a0de0e975d70171cadaf454bc7a0bf86b9c7688e313f0bb/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc", size = 1987616, upload-time = "2024-12-18T11:31:38.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/8a/05f8780f2c1081b800a7ca54c1971e291c2d07d1a50fb23c7e4aef4ed403/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b", size = 1998943, upload-time = "2024-12-18T11:31:41.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/3e/fe5b6613d9e4c0038434396b46c5303f5ade871166900b357ada4766c5b7/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b", size = 2116654, upload-time = "2024-12-18T11:31:44.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/ad/28869f58938fad8cc84739c4e592989730bfb69b7c90a8fff138dff18e1e/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2", size = 2152292, upload-time = "2024-12-18T11:31:48.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/0c/c5c5cd3689c32ed1fe8c5d234b079c12c281c051759770c05b8bed6412b5/pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35", size = 2004961, upload-time = "2024-12-18T11:31:52.446Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.33.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.9'",
|
||||
]
|
||||
dependencies = [
|
||||
{ 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/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/ea/bbe9095cdd771987d13c82d104a9c8559ae9aec1e29f139e286fd2e9256e/pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", size = 2028677, upload-time = "2025-04-23T18:32:27.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/1d/4ac5ed228078737d457a609013e8f7edc64adc37b91d619ea965758369e5/pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", size = 1864735, upload-time = "2025-04-23T18:32:29.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/9a/2e70d6388d7cda488ae38f57bc2f7b03ee442fbcf0d75d848304ac7e405b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", size = 1898467, upload-time = "2025-04-23T18:32:31.119Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/2e/1568934feb43370c1ffb78a77f0baaa5a8b6897513e7a91051af707ffdc4/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", size = 1983041, upload-time = "2025-04-23T18:32:33.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/1a/1a1118f38ab64eac2f6269eb8c120ab915be30e387bb561e3af904b12499/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", size = 2136503, upload-time = "2025-04-23T18:32:35.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/da/44754d1d7ae0f22d6d3ce6c6b1486fc07ac2c524ed8f6eca636e2e1ee49b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", size = 2736079, upload-time = "2025-04-23T18:32:37.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/98/f43cd89172220ec5aa86654967b22d862146bc4d736b1350b4c41e7c9c03/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", size = 2006508, upload-time = "2025-04-23T18:32:39.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/cc/f77e8e242171d2158309f830f7d5d07e0531b756106f36bc18712dc439df/pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", size = 2113693, upload-time = "2025-04-23T18:32:41.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/7a/7be6a7bd43e0a47c147ba7fbf124fe8aaf1200bc587da925509641113b2d/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", size = 2074224, upload-time = "2025-04-23T18:32:44.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/07/31cf8fadffbb03be1cb520850e00a8490c0927ec456e8293cafda0726184/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", size = 2245403, upload-time = "2025-04-23T18:32:45.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/8d/bbaf4c6721b668d44f01861f297eb01c9b35f612f6b8e14173cb204e6240/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", size = 2242331, upload-time = "2025-04-23T18:32:47.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/93/3cc157026bca8f5006250e74515119fcaa6d6858aceee8f67ab6dc548c16/pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", size = 1910571, upload-time = "2025-04-23T18:32:49.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/90/7edc3b2a0d9f0dda8806c04e511a67b0b7a41d2187e2003673a996fb4310/pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", size = 1956504, upload-time = "2025-04-23T18:32:51.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/98/dbf3fdfabaf81cda5622154fda78ea9965ac467e3239078e0dcd6df159e7/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", size = 2024034, upload-time = "2025-04-23T18:33:32.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/99/7810aa9256e7f2ccd492590f86b79d370df1e9292f1f80b000b6a75bd2fb/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", size = 1858578, upload-time = "2025-04-23T18:33:34.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/60/bc06fa9027c7006cc6dd21e48dbf39076dc39d9abbaf718a1604973a9670/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", size = 1892858, upload-time = "2025-04-23T18:33:36.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/40/9d03997d9518816c68b4dfccb88969756b9146031b61cd37f781c74c9b6a/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", size = 2068498, upload-time = "2025-04-23T18:33:38.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/62/d490198d05d2d86672dc269f52579cad7261ced64c2df213d5c16e0aecb1/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", size = 2108428, upload-time = "2025-04-23T18:33:41.18Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/ec/4cd215534fd10b8549015f12ea650a1a973da20ce46430b68fc3185573e8/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", size = 2069854, upload-time = "2025-04-23T18:33:43.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/1a/abbd63d47e1d9b0d632fee6bb15785d0889c8a6e0a6c3b5a8e28ac1ec5d2/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", size = 2237859, upload-time = "2025-04-23T18:33:45.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/1c/fa883643429908b1c90598fd2642af8839efd1d835b65af1f75fba4d94fe/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", size = 2239059, upload-time = "2025-04-23T18:33:47.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661, upload-time = "2025-04-23T18:33:49.995Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/da/9c6f995903b4d9474b39da91d2d626659af3ff1eeb43e9ae7c119349dba6/ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514", size = 4282054, upload-time = "2025-06-05T21:00:15.721Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/ce/a11d381192966e0b4290842cc8d4fac7dc9214ddf627c11c1afff87da29b/ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46", size = 10292516, upload-time = "2025-06-05T20:59:32.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/db/87c3b59b0d4e753e40b6a3b4a2642dfd1dcaefbff121ddc64d6c8b47ba00/ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48", size = 11106083, upload-time = "2025-06-05T20:59:37.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/79/d8cec175856ff810a19825d09ce700265f905c643c69f45d2b737e4a470a/ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b", size = 10436024, upload-time = "2025-06-05T20:59:39.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/5b/f6d94f2980fa1ee854b41568368a2e1252681b9238ab2895e133d303538f/ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a", size = 10646324, upload-time = "2025-06-05T20:59:42.185Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/9c/b4c2acf24ea4426016d511dfdc787f4ce1ceb835f3c5fbdbcb32b1c63bda/ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc", size = 10174416, upload-time = "2025-06-05T20:59:44.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/10/e2e62f77c65ede8cd032c2ca39c41f48feabedb6e282bfd6073d81bb671d/ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629", size = 11724197, upload-time = "2025-06-05T20:59:46.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/f0/466fe8469b85c561e081d798c45f8a1d21e0b4a5ef795a1d7f1a9a9ec182/ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933", size = 12511615, upload-time = "2025-06-05T20:59:49.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/0e/cefe778b46dbd0cbcb03a839946c8f80a06f7968eb298aa4d1a4293f3448/ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165", size = 12117080, upload-time = "2025-06-05T20:59:51.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/2c/caaeda564cbe103bed145ea557cb86795b18651b0f6b3ff6a10e84e5a33f/ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71", size = 11326315, upload-time = "2025-06-05T20:59:54.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/f0/782e7d681d660eda8c536962920c41309e6dd4ebcea9a2714ed5127d44bd/ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9", size = 11555640, upload-time = "2025-06-05T20:59:56.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/d4/3d580c616316c7f07fb3c99dbecfe01fbaea7b6fd9a82b801e72e5de742a/ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc", size = 10507364, upload-time = "2025-06-05T20:59:59.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/dc/195e6f17d7b3ea6b12dc4f3e9de575db7983db187c378d44606e5d503319/ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7", size = 10141462, upload-time = "2025-06-05T21:00:01.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/8e/39a094af6967faa57ecdeacb91bedfb232474ff8c3d20f16a5514e6b3534/ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432", size = 11121028, upload-time = "2025-06-05T21:00:04.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/c0/b0b508193b0e8a1654ec683ebab18d309861f8bd64e3a2f9648b80d392cb/ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492", size = 11602992, upload-time = "2025-06-05T21:00:06.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/91/263e33ab93ab09ca06ce4f8f8547a858cc198072f873ebc9be7466790bae/ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250", size = 10474944, upload-time = "2025-06-05T21:00:08.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/f4/7c27734ac2073aae8efb0119cae6931b6fb48017adf048fdf85c19337afc/ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3", size = 11548669, upload-time = "2025-06-05T21:00:11.147Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b", size = 10683928, upload-time = "2025-06-05T21:00:13.758Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.13.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.9'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.9'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ 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/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
|
||||
]
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
dist
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Honcho TypeScript SDK
|
||||
|
||||
A high-level, ergonomic TypeScript SDK for the Honcho conversational memory platform. This library wraps [honcho-node-core](../honcho-node-core) to provide a user-friendly, Pythonic API for managing peers, sessions, and conversational context.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install @honcho-ai/sdk
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { Honcho } from "@honcho-ai/sdk";
|
||||
|
||||
const honcho = new Honcho({
|
||||
apiKey: process.env.HONCHO_API_KEY,
|
||||
baseUrl: "http://localhost:8000",
|
||||
workspaceId: "test",
|
||||
});
|
||||
|
||||
const assistant = honcho.peer("bob");
|
||||
const alice = honcho.peer("alice");
|
||||
|
||||
await honcho.getPeers();
|
||||
|
||||
const session = honcho.session("session_1");
|
||||
await session.addPeers([alice, assistant]);
|
||||
|
||||
await session.addMessages([
|
||||
assistant.message("What did you have for breakfast today, alice?"),
|
||||
alice.message("I had oatmeal."),
|
||||
]);
|
||||
|
||||
const response = await alice.chat("what did alice have for breakfast today?");
|
||||
console.log(response);
|
||||
```
|
||||
|
||||
See `examples/` for more.
|
||||
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
# Honcho TypeScript SDK Test Suite
|
||||
|
||||
This directory contains an exhaustive and idiomatic test suite for the Honcho TypeScript SDK that covers every available endpoint in multiple ways.
|
||||
|
||||
## Test Structure
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Each class has its own dedicated test file with comprehensive coverage:
|
||||
|
||||
- **`client.test.ts`** - Tests for the main `Honcho` client class
|
||||
- **`peer.test.ts`** - Tests for the `Peer` class
|
||||
- **`session.test.ts`** - Tests for the `Session` class
|
||||
- **`session_context.test.ts`** - Tests for the `SessionContext` class
|
||||
- **`pagination.test.ts`** - Tests for the `Page` class
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- **`integration.test.ts`** - End-to-end workflow tests that demonstrate real-world usage patterns
|
||||
|
||||
### Test Configuration
|
||||
|
||||
- **`setup.ts`** - Global test setup and configuration
|
||||
- **`jest.config.js`** - Jest configuration
|
||||
- **`__mocks__/@honcho-ai/core.ts`** - Mock implementation of the core API client
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### Honcho Client (`client.test.ts`)
|
||||
- ✅ Constructor with all option variations
|
||||
- ✅ Environment variable fallbacks
|
||||
- ✅ Peer creation and validation
|
||||
- ✅ Session creation and validation
|
||||
- ✅ Workspace metadata operations
|
||||
- ✅ Workspace listing
|
||||
- ✅ Search functionality
|
||||
- ✅ Error handling for all methods
|
||||
- ✅ Edge cases and input validation
|
||||
|
||||
### Peer Class (`peer.test.ts`)
|
||||
- ✅ Chat functionality with all option combinations
|
||||
- ✅ Session management
|
||||
- ✅ Message operations (add, get, create)
|
||||
- ✅ Metadata operations
|
||||
- ✅ Search within peer scope
|
||||
- ✅ Different input types and formats
|
||||
- ✅ Null/empty response handling
|
||||
- ✅ Error scenarios
|
||||
|
||||
### Session Class (`session.test.ts`)
|
||||
- ✅ Peer management (add, set, remove, list)
|
||||
- ✅ Message operations with filtering
|
||||
- ✅ Metadata operations
|
||||
- ✅ Context retrieval with options
|
||||
- ✅ Search within session scope
|
||||
- ✅ Working representation queries
|
||||
- ✅ Mixed input types (strings vs objects)
|
||||
- ✅ Constructor options
|
||||
- ✅ Error handling
|
||||
|
||||
### SessionContext Class (`session_context.test.ts`)
|
||||
- ✅ Constructor variations
|
||||
- ✅ OpenAI format conversion
|
||||
- ✅ Anthropic format conversion
|
||||
- ✅ Length and toString methods
|
||||
- ✅ Empty/null message handling
|
||||
- ✅ Complex message content
|
||||
- ✅ Case sensitivity
|
||||
- ✅ Missing field handling
|
||||
- ✅ Edge cases and malformed data
|
||||
|
||||
### Page Class (`pagination.test.ts`)
|
||||
- ✅ Async iteration
|
||||
- ✅ Transform functions
|
||||
- ✅ Data retrieval methods
|
||||
- ✅ Pagination navigation
|
||||
- ✅ Different page formats
|
||||
- ✅ Error handling in transforms
|
||||
- ✅ Large dataset handling
|
||||
- ✅ Circular references
|
||||
- ✅ Complex nested structures
|
||||
|
||||
### Integration Tests (`integration.test.ts`)
|
||||
- ✅ Complete chat session workflow
|
||||
- ✅ Workspace and peer management
|
||||
- ✅ Multi-scope search functionality
|
||||
- ✅ Error scenario handling
|
||||
- ✅ Pagination workflows
|
||||
- ✅ Working representation queries
|
||||
- ✅ Type safety verification
|
||||
- ✅ Empty/null response handling
|
||||
|
||||
## Test Patterns
|
||||
|
||||
### Comprehensive Mocking
|
||||
All tests use comprehensive mocks of the underlying `@honcho-ai/core` API client to ensure:
|
||||
- Tests run independently of external services
|
||||
- Predictable and controllable test scenarios
|
||||
- Fast test execution
|
||||
- Ability to test error conditions
|
||||
|
||||
### Edge Case Coverage
|
||||
Each test suite includes extensive edge case testing:
|
||||
- Empty/null inputs and responses
|
||||
- Invalid input types
|
||||
- API error conditions
|
||||
- Boundary conditions
|
||||
- Malformed data handling
|
||||
|
||||
### Multiple Input Types
|
||||
Tests verify that methods handle various input types correctly:
|
||||
- String vs object parameters
|
||||
- Single items vs arrays
|
||||
- Optional vs required parameters
|
||||
- Different data structures
|
||||
|
||||
### Error Scenarios
|
||||
Comprehensive error testing including:
|
||||
- API failures
|
||||
- Invalid inputs
|
||||
- Network errors
|
||||
- Timeout scenarios
|
||||
- Validation errors
|
||||
|
||||
### Async Operations
|
||||
Proper testing of all asynchronous operations:
|
||||
- Promise resolution/rejection
|
||||
- Async iteration
|
||||
- Concurrent operations
|
||||
- Error propagation
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Run tests with coverage
|
||||
npm run test:coverage
|
||||
|
||||
# Run tests in watch mode
|
||||
npm run test:watch
|
||||
|
||||
# Run specific test file
|
||||
npm test client.test.ts
|
||||
|
||||
# Run integration tests only
|
||||
npm test integration.test.ts
|
||||
```
|
||||
|
||||
## Coverage Goals
|
||||
|
||||
This test suite aims for:
|
||||
- **100% function coverage** - Every function is called
|
||||
- **100% branch coverage** - Every code path is tested
|
||||
- **100% statement coverage** - Every line is executed
|
||||
- **Comprehensive edge case coverage** - Every failure mode is tested
|
||||
|
||||
## Test Philosophy
|
||||
|
||||
1. **Exhaustive Testing**: Every public method and property is tested
|
||||
2. **Multiple Scenarios**: Each method is tested with various inputs and conditions
|
||||
3. **Real-world Usage**: Integration tests mirror actual usage patterns
|
||||
4. **Error Resilience**: Extensive error condition testing
|
||||
5. **Type Safety**: TypeScript types are verified throughout
|
||||
6. **Performance Awareness**: Tests include large dataset scenarios
|
||||
7. **Maintainability**: Clear, well-documented test cases
|
||||
|
||||
## Mock Strategy
|
||||
|
||||
The test suite uses a sophisticated mocking strategy:
|
||||
|
||||
1. **Core API Mocking**: The `@honcho-ai/core` module is completely mocked
|
||||
2. **Flexible Responses**: Mock responses can be configured per test
|
||||
3. **Error Simulation**: Easy simulation of API errors and edge cases
|
||||
4. **Isolation**: Each test runs in complete isolation
|
||||
5. **Deterministic**: Tests produce consistent, reproducible results
|
||||
|
||||
This ensures that the SDK layer is thoroughly tested while remaining independent of the underlying API implementation.
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Mock implementation of @honcho-ai/core for testing
|
||||
export default class MockHonchoCore {
|
||||
public workspaces = {
|
||||
peers: {
|
||||
list: jest.fn(),
|
||||
chat: jest.fn(),
|
||||
sessions: {
|
||||
list: jest.fn(),
|
||||
},
|
||||
messages: {
|
||||
create: jest.fn(),
|
||||
list: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
search: jest.fn(),
|
||||
workingRepresentation: jest.fn(),
|
||||
},
|
||||
sessions: {
|
||||
list: jest.fn(),
|
||||
peers: {
|
||||
add: jest.fn(),
|
||||
set: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
list: jest.fn(),
|
||||
},
|
||||
messages: {
|
||||
create: jest.fn(),
|
||||
list: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
getContext: jest.fn(),
|
||||
search: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn().mockResolvedValue({ id: 'test-workspace', metadata: {} }),
|
||||
update: jest.fn(),
|
||||
list: jest.fn(),
|
||||
search: jest.fn(),
|
||||
};
|
||||
|
||||
constructor(options?: any) {
|
||||
// Mock constructor
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,385 @@
|
|||
import { Honcho } from '../src/client';
|
||||
import { Peer } from '../src/peer';
|
||||
import { Session } from '../src/session';
|
||||
import { Page } from '../src/pagination';
|
||||
|
||||
// Mock the @honcho-ai/core module
|
||||
jest.mock('@honcho-ai/core', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
workspaces: {
|
||||
peers: {
|
||||
list: jest.fn(),
|
||||
},
|
||||
sessions: {
|
||||
list: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
list: jest.fn(),
|
||||
search: jest.fn(),
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Honcho Client', () => {
|
||||
let honcho: Honcho;
|
||||
let mockClient: any;
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear all mocks before each test
|
||||
jest.clearAllMocks();
|
||||
|
||||
honcho = new Honcho({
|
||||
workspaceId: 'test-workspace',
|
||||
apiKey: 'test-key',
|
||||
environment: 'local',
|
||||
});
|
||||
|
||||
mockClient = (honcho as any)._client;
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with provided options', () => {
|
||||
const client = new Honcho({
|
||||
workspaceId: 'custom-workspace',
|
||||
apiKey: 'custom-key',
|
||||
environment: 'production',
|
||||
baseURL: 'https://custom-url.com',
|
||||
timeout: 5000,
|
||||
maxRetries: 3,
|
||||
});
|
||||
|
||||
expect(client.workspaceId).toBe('custom-workspace');
|
||||
});
|
||||
|
||||
it('should use environment variables as fallbacks', () => {
|
||||
process.env.HONCHO_WORKSPACE_ID = 'env-workspace';
|
||||
process.env.HONCHO_API_KEY = 'env-key';
|
||||
process.env.HONCHO_URL = 'https://env-url.com';
|
||||
|
||||
const client = new Honcho({});
|
||||
|
||||
expect(client.workspaceId).toBe('env-workspace');
|
||||
|
||||
// Clean up environment variables
|
||||
delete process.env.HONCHO_WORKSPACE_ID;
|
||||
delete process.env.HONCHO_API_KEY;
|
||||
delete process.env.HONCHO_URL;
|
||||
});
|
||||
|
||||
it('should use default workspace ID when none provided', () => {
|
||||
const client = new Honcho({});
|
||||
expect(client.workspaceId).toBe('default');
|
||||
});
|
||||
|
||||
it('should handle all constructor options', () => {
|
||||
const client = new Honcho({
|
||||
workspaceId: 'test',
|
||||
apiKey: 'key',
|
||||
environment: 'local',
|
||||
baseURL: 'https://example.com',
|
||||
timeout: 10000,
|
||||
maxRetries: 5,
|
||||
defaultHeaders: { 'X-Custom': 'header' },
|
||||
defaultQuery: { param: 'value' },
|
||||
});
|
||||
|
||||
expect(client.workspaceId).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('peer', () => {
|
||||
it('should create a new Peer instance', () => {
|
||||
const peer = 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 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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPeers', () => {
|
||||
it('should return a Page of Peer instances', async () => {
|
||||
const mockPeersData = {
|
||||
items: [
|
||||
{ id: 'peer1', metadata: {} },
|
||||
{ id: 'peer2', metadata: {} },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.peers.list.mockResolvedValue(mockPeersData);
|
||||
|
||||
const peersPage = await honcho.getPeers();
|
||||
|
||||
expect(peersPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.peers.list).toHaveBeenCalledWith('test-workspace');
|
||||
});
|
||||
|
||||
it('should handle empty peers list', async () => {
|
||||
const mockPeersData = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.peers.list.mockResolvedValue(mockPeersData);
|
||||
|
||||
const peersPage = await honcho.getPeers();
|
||||
|
||||
expect(peersPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.peers.list).toHaveBeenCalledWith('test-workspace');
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.list.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
await expect(honcho.getPeers()).rejects.toThrow('API Error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('session', () => {
|
||||
it('should create a new Session instance', () => {
|
||||
const session = 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 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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSessions', () => {
|
||||
it('should return a Page of Session instances', async () => {
|
||||
const mockSessionsData = {
|
||||
items: [
|
||||
{ id: 'session1', metadata: {} },
|
||||
{ id: 'session2', metadata: {} },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.sessions.list.mockResolvedValue(mockSessionsData);
|
||||
|
||||
const sessionsPage = await honcho.getSessions();
|
||||
|
||||
expect(sessionsPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.sessions.list).toHaveBeenCalledWith('test-workspace');
|
||||
});
|
||||
|
||||
it('should handle empty sessions list', async () => {
|
||||
const mockSessionsData = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.sessions.list.mockResolvedValue(mockSessionsData);
|
||||
|
||||
const sessionsPage = await honcho.getSessions();
|
||||
|
||||
expect(sessionsPage).toBeInstanceOf(Page);
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.list.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
await expect(honcho.getSessions()).rejects.toThrow('API Error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMetadata', () => {
|
||||
it('should return workspace metadata', async () => {
|
||||
const mockWorkspace = {
|
||||
id: 'test-workspace',
|
||||
metadata: { key: 'value', setting: 'config' },
|
||||
};
|
||||
mockClient.workspaces.getOrCreate.mockResolvedValue(mockWorkspace);
|
||||
|
||||
const metadata = await honcho.getMetadata();
|
||||
|
||||
expect(metadata).toEqual({ key: 'value', setting: 'config' });
|
||||
expect(mockClient.workspaces.getOrCreate).toHaveBeenCalledWith({ id: 'test-workspace' });
|
||||
});
|
||||
|
||||
it('should return empty object when no metadata exists', async () => {
|
||||
const mockWorkspace = {
|
||||
id: 'test-workspace',
|
||||
metadata: null,
|
||||
};
|
||||
mockClient.workspaces.getOrCreate.mockResolvedValue(mockWorkspace);
|
||||
|
||||
const metadata = await honcho.getMetadata();
|
||||
|
||||
expect(metadata).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.getOrCreate.mockRejectedValue(new Error('Workspace not found'));
|
||||
|
||||
await expect(honcho.getMetadata()).rejects.toThrow('Workspace not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMetadata', () => {
|
||||
it('should update workspace metadata', async () => {
|
||||
const metadata = { newKey: 'newValue', updated: true };
|
||||
mockClient.workspaces.update.mockResolvedValue({});
|
||||
|
||||
await honcho.setMetadata(metadata);
|
||||
|
||||
expect(mockClient.workspaces.update).toHaveBeenCalledWith('test-workspace', { metadata });
|
||||
});
|
||||
|
||||
it('should handle empty metadata object', async () => {
|
||||
mockClient.workspaces.update.mockResolvedValue({});
|
||||
|
||||
await honcho.setMetadata({});
|
||||
|
||||
expect(mockClient.workspaces.update).toHaveBeenCalledWith('test-workspace', { metadata: {} });
|
||||
});
|
||||
|
||||
it('should handle complex metadata objects', async () => {
|
||||
const complexMetadata = {
|
||||
nested: { object: { with: 'values' } },
|
||||
array: [1, 2, 3],
|
||||
boolean: true,
|
||||
number: 42,
|
||||
string: 'test',
|
||||
};
|
||||
mockClient.workspaces.update.mockResolvedValue({});
|
||||
|
||||
await honcho.setMetadata(complexMetadata);
|
||||
|
||||
expect(mockClient.workspaces.update).toHaveBeenCalledWith('test-workspace', { metadata: complexMetadata });
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.update.mockRejectedValue(new Error('Update failed'));
|
||||
|
||||
await expect(honcho.setMetadata({ key: 'value' })).rejects.toThrow('Update failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWorkspaces', () => {
|
||||
it('should return array of workspace IDs', async () => {
|
||||
const mockWorkspacesPage = {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield { id: 'workspace1' };
|
||||
yield { id: 'workspace2' };
|
||||
yield { id: 'workspace3' };
|
||||
},
|
||||
};
|
||||
mockClient.workspaces.list.mockResolvedValue(mockWorkspacesPage);
|
||||
|
||||
const workspaces = await honcho.getWorkspaces();
|
||||
|
||||
expect(workspaces).toEqual(['workspace1', 'workspace2', 'workspace3']);
|
||||
expect(mockClient.workspaces.list).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle empty workspaces list', async () => {
|
||||
const mockWorkspacesPage = {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
// Empty iterator
|
||||
},
|
||||
};
|
||||
mockClient.workspaces.list.mockResolvedValue(mockWorkspacesPage);
|
||||
|
||||
const workspaces = await honcho.getWorkspaces();
|
||||
|
||||
expect(workspaces).toEqual([]);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
mockClient.workspaces.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await honcho.search('hello');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.search).toHaveBeenCalledWith('test-workspace', 'hello');
|
||||
});
|
||||
|
||||
it('should handle empty search results', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await honcho.search('nonexistent');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
it('should handle complex search queries', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
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', complexQuery);
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.search.mockRejectedValue(new Error('Search failed'));
|
||||
|
||||
await expect(honcho.search('test')).rejects.toThrow('Search failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,418 @@
|
|||
import { Honcho } from '../src/client';
|
||||
import { Peer } from '../src/peer';
|
||||
import { Session } from '../src/session';
|
||||
import { SessionContext } from '../src/session_context';
|
||||
import { Page } from '../src/pagination';
|
||||
|
||||
// Mock the @honcho-ai/core module
|
||||
let mockWorkspacesApi: any;
|
||||
|
||||
jest.mock('@honcho-ai/core', () => {
|
||||
return jest.fn().mockImplementation(() => mockWorkspacesApi);
|
||||
});
|
||||
|
||||
describe('Honcho SDK Integration Tests', () => {
|
||||
let honcho: Honcho;
|
||||
|
||||
beforeEach(() => {
|
||||
mockWorkspacesApi = {
|
||||
workspaces: {
|
||||
peers: {
|
||||
list: jest.fn(),
|
||||
chat: jest.fn(),
|
||||
sessions: { list: jest.fn() },
|
||||
messages: { create: jest.fn(), list: jest.fn() },
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
search: jest.fn(),
|
||||
workingRepresentation: jest.fn(),
|
||||
},
|
||||
sessions: {
|
||||
list: jest.fn(),
|
||||
peers: { add: jest.fn(), set: jest.fn(), remove: jest.fn(), list: jest.fn() },
|
||||
messages: { create: jest.fn(), list: jest.fn() },
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
getContext: jest.fn(),
|
||||
search: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
list: jest.fn(),
|
||||
search: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
jest.clearAllMocks();
|
||||
|
||||
honcho = new Honcho({
|
||||
workspaceId: 'integration-test-workspace',
|
||||
apiKey: 'test-api-key',
|
||||
environment: 'local',
|
||||
});
|
||||
});
|
||||
|
||||
describe('Complete Workflow Integration', () => {
|
||||
it('should handle complete chat session workflow', async () => {
|
||||
// Setup mock responses
|
||||
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' },
|
||||
];
|
||||
const mockContextData = { messages: mockMessages, summary: 'Friendly greeting' };
|
||||
|
||||
mockWorkspacesApi.workspaces.peers.getOrCreate.mockResolvedValue(mockPeerData);
|
||||
mockWorkspacesApi.workspaces.sessions.getOrCreate.mockResolvedValue(mockSessionData);
|
||||
mockWorkspacesApi.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
mockWorkspacesApi.workspaces.sessions.messages.create.mockResolvedValue({});
|
||||
mockWorkspacesApi.workspaces.sessions.getContext.mockResolvedValue(mockContextData);
|
||||
mockWorkspacesApi.workspaces.peers.chat.mockResolvedValue({ content: 'AI response' });
|
||||
|
||||
// Step 1: Create peers
|
||||
const user = honcho.peer('user');
|
||||
const assistant = honcho.peer('assistant');
|
||||
|
||||
expect(user).toBeInstanceOf(Peer);
|
||||
expect(assistant).toBeInstanceOf(Peer);
|
||||
expect(user.id).toBe('user');
|
||||
expect(assistant.id).toBe('assistant');
|
||||
|
||||
// Step 2: Create session
|
||||
const session = honcho.session('chat-session');
|
||||
expect(session).toBeInstanceOf(Session);
|
||||
expect(session.id).toBe('chat-session');
|
||||
|
||||
// Step 3: Add peers to session
|
||||
await session.addPeers([user, assistant]);
|
||||
expect(mockWorkspacesApi.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'chat-session',
|
||||
{
|
||||
'user': { observe_me: true, observe_others: false },
|
||||
'assistant': { observe_me: true, observe_others: false }
|
||||
}
|
||||
);
|
||||
|
||||
// Step 4: Add messages to session
|
||||
const userMessage = user.message('Hello');
|
||||
const assistantMessage = assistant.message('Hi there!');
|
||||
|
||||
await session.addMessages([userMessage, assistantMessage]);
|
||||
expect(mockWorkspacesApi.workspaces.sessions.messages.create).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'chat-session',
|
||||
{
|
||||
messages: [
|
||||
{ peer_id: 'user', content: 'Hello', metadata: undefined },
|
||||
{ peer_id: 'assistant', content: 'Hi there!', metadata: undefined },
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
// Step 5: Get session context
|
||||
const context = await session.getContext();
|
||||
expect(context).toBeInstanceOf(SessionContext);
|
||||
expect(context.sessionId).toBe('chat-session');
|
||||
expect(context.messages).toEqual(mockMessages);
|
||||
expect(context.summary).toBe('Friendly greeting');
|
||||
|
||||
// Step 6: Convert context to different formats
|
||||
const openAIFormat = context.toOpenAI('assistant');
|
||||
const anthropicFormat = context.toAnthropic('assistant');
|
||||
|
||||
expect(openAIFormat).toEqual([
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'assistant', content: 'Hi there!' },
|
||||
]);
|
||||
|
||||
expect(anthropicFormat).toEqual([
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'assistant', content: 'Hi there!' },
|
||||
]);
|
||||
|
||||
// Step 7: Query assistant
|
||||
const response = await assistant.chat('How are you?');
|
||||
expect(response).toBe('AI response');
|
||||
expect(mockWorkspacesApi.workspaces.peers.chat).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'assistant',
|
||||
{ queries: 'How are you?', stream: undefined, target: undefined, session_id: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle workspace and peer management workflow', async () => {
|
||||
// Setup mock responses
|
||||
const mockWorkspaceMetadata = { name: 'Test Workspace', version: '1.0' };
|
||||
const mockPeersList = {
|
||||
items: [
|
||||
{ id: 'peer1', metadata: { role: 'user' } },
|
||||
{ id: 'peer2', metadata: { role: 'assistant' } },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
|
||||
mockWorkspacesApi.workspaces.getOrCreate.mockResolvedValue({
|
||||
id: 'integration-test-workspace',
|
||||
metadata: mockWorkspaceMetadata,
|
||||
});
|
||||
mockWorkspacesApi.workspaces.update.mockResolvedValue({});
|
||||
mockWorkspacesApi.workspaces.peers.list.mockResolvedValue(mockPeersList);
|
||||
|
||||
// Step 1: Get workspace metadata
|
||||
const metadata = await honcho.getMetadata();
|
||||
expect(metadata).toEqual(mockWorkspaceMetadata);
|
||||
|
||||
// Step 2: Update workspace metadata
|
||||
const newMetadata = { ...mockWorkspaceMetadata, updated: true };
|
||||
await honcho.setMetadata(newMetadata);
|
||||
expect(mockWorkspacesApi.workspaces.update).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
{ metadata: newMetadata }
|
||||
);
|
||||
|
||||
// Step 3: Get all peers
|
||||
const peersPage = await honcho.getPeers();
|
||||
expect(peersPage).toBeInstanceOf(Page);
|
||||
|
||||
// Step 4: Iterate through peers
|
||||
const peersList: Peer[] = [];
|
||||
for await (const peer of peersPage) {
|
||||
peersList.push(peer);
|
||||
}
|
||||
|
||||
expect(peersList).toHaveLength(2);
|
||||
expect(peersList[0]).toBeInstanceOf(Peer);
|
||||
expect(peersList[1]).toBeInstanceOf(Peer);
|
||||
expect(peersList[0].id).toBe('peer1');
|
||||
expect(peersList[1].id).toBe('peer2');
|
||||
});
|
||||
|
||||
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 mockPeerSearchResults = {
|
||||
items: [
|
||||
{ id: 'msg2', content: 'peer message', peer_id: 'peer1' },
|
||||
],
|
||||
total: 1,
|
||||
size: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
|
||||
const mockSessionSearchResults = {
|
||||
items: [
|
||||
{ id: 'msg3', content: 'session message', peer_id: 'peer1' },
|
||||
],
|
||||
total: 1,
|
||||
size: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
|
||||
mockWorkspacesApi.workspaces.search.mockResolvedValue(mockWorkspaceSearchResults);
|
||||
mockWorkspacesApi.workspaces.peers.search.mockResolvedValue(mockPeerSearchResults);
|
||||
mockWorkspacesApi.workspaces.sessions.search.mockResolvedValue(mockSessionSearchResults);
|
||||
|
||||
// Step 1: Search workspace
|
||||
const workspaceResults = await honcho.search('test query');
|
||||
expect(workspaceResults).toBeInstanceOf(Page);
|
||||
expect(mockWorkspacesApi.workspaces.search).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'test query'
|
||||
);
|
||||
|
||||
// Step 2: Search peer
|
||||
const peer = honcho.peer('test-peer');
|
||||
const peerResults = await peer.search('peer query');
|
||||
expect(peerResults).toBeInstanceOf(Page);
|
||||
expect(mockWorkspacesApi.workspaces.peers.search).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'test-peer',
|
||||
{ query: 'peer query' }
|
||||
);
|
||||
|
||||
// Step 3: Search session
|
||||
const session = honcho.session('test-session');
|
||||
const sessionResults = await session.search('session query');
|
||||
expect(sessionResults).toBeInstanceOf(Page);
|
||||
expect(mockWorkspacesApi.workspaces.sessions.search).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'test-session',
|
||||
'session query'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle error scenarios gracefully', async () => {
|
||||
// Setup error scenarios
|
||||
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');
|
||||
|
||||
// Test error handling in chat
|
||||
await expect(assistant.chat('Hello')).rejects.toThrow('Chat API failed');
|
||||
|
||||
// Test error handling in context
|
||||
await expect(session.getContext()).rejects.toThrow('Context API failed');
|
||||
});
|
||||
|
||||
it('should handle pagination correctly', async () => {
|
||||
// Setup paginated response
|
||||
const firstPageData = {
|
||||
items: [
|
||||
{ id: 'peer1', metadata: {} },
|
||||
{ id: 'peer2', metadata: {} },
|
||||
],
|
||||
total: 4,
|
||||
size: 2,
|
||||
hasNextPage: true,
|
||||
nextPage: jest.fn(),
|
||||
};
|
||||
|
||||
const secondPageData = {
|
||||
items: [
|
||||
{ id: 'peer3', metadata: {} },
|
||||
{ id: 'peer4', metadata: {} },
|
||||
],
|
||||
total: 4,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
nextPage: jest.fn().mockResolvedValue(null),
|
||||
};
|
||||
|
||||
firstPageData.nextPage.mockResolvedValue(secondPageData);
|
||||
mockWorkspacesApi.workspaces.peers.list.mockResolvedValue(firstPageData);
|
||||
|
||||
// Step 1: Get first page
|
||||
const firstPage = await honcho.getPeers();
|
||||
expect(firstPage.total).toBe(4);
|
||||
expect(firstPage.size).toBe(2);
|
||||
expect(firstPage.hasNextPage).toBe(true);
|
||||
|
||||
// Step 2: Get data from first page
|
||||
const firstPageData_ = await firstPage.data();
|
||||
expect(firstPageData_).toHaveLength(2);
|
||||
|
||||
// Step 3: Get next page
|
||||
const secondPage = await firstPage.nextPage();
|
||||
expect(secondPage).not.toBeNull();
|
||||
expect(secondPage!.hasNextPage).toBe(false);
|
||||
|
||||
// Step 4: Get data from second page
|
||||
const secondPageData_ = await secondPage!.data();
|
||||
expect(secondPageData_).toHaveLength(2);
|
||||
|
||||
// Step 5: Verify no more pages
|
||||
const thirdPage = await secondPage!.nextPage();
|
||||
expect(thirdPage).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle working representation queries', async () => {
|
||||
const mockWorkingRep = {
|
||||
peer_id: 'alice',
|
||||
knowledge: 'Alice likes coffee and works as a developer',
|
||||
relationships: ['bob', 'charlie'],
|
||||
context: 'session-specific context',
|
||||
};
|
||||
|
||||
mockWorkspacesApi.workspaces.peers.workingRepresentation.mockResolvedValue(mockWorkingRep);
|
||||
|
||||
const session = honcho.session('working-rep-session');
|
||||
const alice = honcho.peer('alice');
|
||||
const bob = honcho.peer('bob');
|
||||
|
||||
// Test working representation without target
|
||||
const globalRep = await session.workingRep('alice');
|
||||
expect(globalRep).toEqual(mockWorkingRep);
|
||||
expect(mockWorkspacesApi.workspaces.peers.workingRepresentation).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'alice',
|
||||
{ session_id: 'working-rep-session', target: undefined }
|
||||
);
|
||||
|
||||
// Test working representation with target
|
||||
await session.workingRep(alice, bob);
|
||||
expect(mockWorkspacesApi.workspaces.peers.workingRepresentation).toHaveBeenCalledWith(
|
||||
'integration-test-workspace',
|
||||
'alice',
|
||||
{ session_id: 'working-rep-session', target: 'bob' }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases and Error Handling Integration', () => {
|
||||
it('should handle empty and null responses gracefully', async () => {
|
||||
// Setup empty/null responses
|
||||
mockWorkspacesApi.workspaces.peers.chat.mockResolvedValue({ content: null });
|
||||
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');
|
||||
|
||||
// Test null chat response
|
||||
const chatResult = await peer.chat('Hello');
|
||||
expect(chatResult).toBeNull();
|
||||
|
||||
// Test empty peers list
|
||||
const peersPage = await honcho.getPeers();
|
||||
const peersList = await peersPage.data();
|
||||
expect(peersList).toEqual([]);
|
||||
|
||||
// Test empty context
|
||||
const context = await session.getContext();
|
||||
expect(context.messages).toEqual([]);
|
||||
expect(context.length).toBe(0);
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
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.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' }],
|
||||
summary: 'Test summary',
|
||||
});
|
||||
|
||||
const context: SessionContext = await session.getContext();
|
||||
expect(typeof context.sessionId).toBe('string');
|
||||
expect(Array.isArray(context.messages)).toBe(true);
|
||||
expect(typeof context.summary).toBe('string');
|
||||
expect(typeof context.length).toBe('number');
|
||||
expect(typeof context.toString()).toBe('string');
|
||||
|
||||
const openAI = context.toOpenAI(peer);
|
||||
const anthropic = context.toAnthropic('assistant');
|
||||
|
||||
expect(Array.isArray(openAI)).toBe(true);
|
||||
expect(Array.isArray(anthropic)).toBe(true);
|
||||
|
||||
if (openAI.length > 0) {
|
||||
expect(typeof openAI[0].role).toBe('string');
|
||||
expect(typeof openAI[0].content).toBe('string');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,453 @@
|
|||
import { Page } from '../src/pagination';
|
||||
|
||||
describe('Page', () => {
|
||||
let mockOriginalPage: any;
|
||||
let mockItems: any[];
|
||||
|
||||
beforeEach(() => {
|
||||
mockItems = [
|
||||
{ id: 'item1', name: 'Item 1' },
|
||||
{ id: 'item2', name: 'Item 2' },
|
||||
{ id: 'item3', name: 'Item 3' },
|
||||
];
|
||||
|
||||
mockOriginalPage = {
|
||||
items: mockItems,
|
||||
data: mockItems,
|
||||
size: 3,
|
||||
total: 10,
|
||||
hasNextPage: true,
|
||||
get: jest.fn(),
|
||||
nextPage: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with original page', () => {
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
expect(page['_originalPage']).toBe(mockOriginalPage);
|
||||
expect(page['_transformFunc']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should initialize with transform function', () => {
|
||||
const transformFunc = (item: any) => ({ ...item, transformed: true });
|
||||
const page = new Page(mockOriginalPage, transformFunc);
|
||||
|
||||
expect(page['_originalPage']).toBe(mockOriginalPage);
|
||||
expect(page['_transformFunc']).toBe(transformFunc);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Symbol.asyncIterator', () => {
|
||||
it('should iterate through items without transform', async () => {
|
||||
const page = new Page(mockOriginalPage);
|
||||
const items: any[] = [];
|
||||
|
||||
for await (const item of page) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
expect(items).toEqual(mockItems);
|
||||
});
|
||||
|
||||
it('should iterate through items with transform', async () => {
|
||||
const transformFunc = (item: any) => ({ ...item, transformed: true });
|
||||
const page = new Page(mockOriginalPage, transformFunc);
|
||||
const items: any[] = [];
|
||||
|
||||
for await (const item of page) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
expect(items).toEqual([
|
||||
{ id: 'item1', name: 'Item 1', transformed: true },
|
||||
{ id: 'item2', name: 'Item 2', transformed: true },
|
||||
{ id: 'item3', name: 'Item 3', transformed: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle empty items array', async () => {
|
||||
const emptyPage = { items: [], data: [], size: 0, total: 0, hasNextPage: false };
|
||||
const page = new Page(emptyPage);
|
||||
const items: any[] = [];
|
||||
|
||||
for await (const item of page) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
expect(items).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle page with data field instead of items', async () => {
|
||||
const pageWithData = {
|
||||
data: mockItems,
|
||||
size: 3,
|
||||
total: 10,
|
||||
hasNextPage: true,
|
||||
};
|
||||
const page = new Page(pageWithData);
|
||||
const items: any[] = [];
|
||||
|
||||
for await (const item of page) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
expect(items).toEqual(mockItems);
|
||||
});
|
||||
|
||||
it('should handle page with neither items nor data', async () => {
|
||||
const pageWithoutItems = {
|
||||
size: 0,
|
||||
total: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const page = new Page(pageWithoutItems);
|
||||
const items: any[] = [];
|
||||
|
||||
for await (const item of page) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
expect(items).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('should get item by index without transform', async () => {
|
||||
mockOriginalPage.get.mockResolvedValue(mockItems[1]);
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
const item = await page.get(1);
|
||||
|
||||
expect(item).toEqual(mockItems[1]);
|
||||
expect(mockOriginalPage.get).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should get item by index with transform', async () => {
|
||||
const transformFunc = (item: any) => ({ ...item, transformed: true });
|
||||
mockOriginalPage.get.mockResolvedValue(mockItems[1]);
|
||||
const page = new Page(mockOriginalPage, transformFunc);
|
||||
|
||||
const item = await page.get(1);
|
||||
|
||||
expect(item).toEqual({ ...mockItems[1], transformed: true });
|
||||
expect(mockOriginalPage.get).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should handle out of bounds index', async () => {
|
||||
mockOriginalPage.get.mockResolvedValue(undefined);
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
const item = await page.get(999);
|
||||
|
||||
expect(item).toBeUndefined();
|
||||
expect(mockOriginalPage.get).toHaveBeenCalledWith(999);
|
||||
});
|
||||
|
||||
it('should handle negative index', async () => {
|
||||
mockOriginalPage.get.mockResolvedValue(undefined);
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
const item = await page.get(-1);
|
||||
|
||||
expect(item).toBeUndefined();
|
||||
expect(mockOriginalPage.get).toHaveBeenCalledWith(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('size getter', () => {
|
||||
it('should return size from original page', () => {
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
expect(page.size).toBe(3);
|
||||
});
|
||||
|
||||
it('should handle missing size', () => {
|
||||
const pageWithoutSize = { items: mockItems };
|
||||
const page = new Page(pageWithoutSize);
|
||||
|
||||
expect(page.size).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('total getter', () => {
|
||||
it('should return total from original page', () => {
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
expect(page.total).toBe(10);
|
||||
});
|
||||
|
||||
it('should handle missing total', () => {
|
||||
const pageWithoutTotal = { items: mockItems };
|
||||
const page = new Page(pageWithoutTotal);
|
||||
|
||||
expect(page.total).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('data', () => {
|
||||
it('should return data array without transform', async () => {
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
const data = await page.data();
|
||||
|
||||
expect(data).toEqual(mockItems);
|
||||
});
|
||||
|
||||
it('should return data array with transform', async () => {
|
||||
const transformFunc = (item: any) => ({ ...item, transformed: true });
|
||||
const page = new Page(mockOriginalPage, transformFunc);
|
||||
|
||||
const data = await page.data();
|
||||
|
||||
expect(data).toEqual([
|
||||
{ id: 'item1', name: 'Item 1', transformed: true },
|
||||
{ id: 'item2', name: 'Item 2', transformed: true },
|
||||
{ id: 'item3', name: 'Item 3', transformed: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle data as function', async () => {
|
||||
const mockDataFunction = jest.fn().mockResolvedValue(mockItems);
|
||||
const pageWithDataFunction = {
|
||||
data: mockDataFunction,
|
||||
size: 3,
|
||||
total: 10,
|
||||
hasNextPage: true,
|
||||
};
|
||||
const page = new Page(pageWithDataFunction);
|
||||
|
||||
const data = await page.data();
|
||||
|
||||
expect(data).toEqual(mockItems);
|
||||
expect(mockDataFunction).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle items as function', async () => {
|
||||
const mockItemsFunction = jest.fn().mockResolvedValue(mockItems);
|
||||
const pageWithItemsFunction = {
|
||||
items: mockItemsFunction,
|
||||
size: 3,
|
||||
total: 10,
|
||||
hasNextPage: true,
|
||||
};
|
||||
const page = new Page(pageWithItemsFunction);
|
||||
|
||||
const data = await page.data();
|
||||
|
||||
expect(data).toEqual(mockItems);
|
||||
expect(mockItemsFunction).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle empty data', async () => {
|
||||
const emptyPage = { items: [], size: 0, total: 0, hasNextPage: false };
|
||||
const page = new Page(emptyPage);
|
||||
|
||||
const data = await page.data();
|
||||
|
||||
expect(data).toEqual([]);
|
||||
});
|
||||
|
||||
it('should prioritize items over data field', async () => {
|
||||
const pageWithBoth = {
|
||||
items: [{ id: 'from-items' }],
|
||||
data: [{ id: 'from-data' }],
|
||||
size: 1,
|
||||
total: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const page = new Page(pageWithBoth);
|
||||
|
||||
const data = await page.data();
|
||||
|
||||
expect(data).toEqual([{ id: 'from-items' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasNextPage getter', () => {
|
||||
it('should return hasNextPage from original page', () => {
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
expect(page.hasNextPage).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle missing hasNextPage', () => {
|
||||
const pageWithoutHasNextPage = { items: mockItems };
|
||||
const page = new Page(pageWithoutHasNextPage);
|
||||
|
||||
expect(page.hasNextPage).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle false hasNextPage', () => {
|
||||
const lastPage = { ...mockOriginalPage, hasNextPage: false };
|
||||
const page = new Page(lastPage);
|
||||
|
||||
expect(page.hasNextPage).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nextPage', () => {
|
||||
it('should return next page with same transform function', async () => {
|
||||
const nextPageData = {
|
||||
items: [{ id: 'item4', name: 'Item 4' }],
|
||||
size: 1,
|
||||
total: 10,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const transformFunc = (item: any) => ({ ...item, transformed: true });
|
||||
mockOriginalPage.nextPage.mockResolvedValue(nextPageData);
|
||||
const page = new Page(mockOriginalPage, transformFunc);
|
||||
|
||||
const nextPage = await page.nextPage();
|
||||
|
||||
expect(nextPage).toBeInstanceOf(Page);
|
||||
expect(nextPage!['_transformFunc']).toBe(transformFunc);
|
||||
expect(mockOriginalPage.nextPage).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return null when no next page', async () => {
|
||||
mockOriginalPage.nextPage.mockResolvedValue(null);
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
const nextPage = await page.nextPage();
|
||||
|
||||
expect(nextPage).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle next page returning undefined', async () => {
|
||||
mockOriginalPage.nextPage.mockResolvedValue(undefined);
|
||||
const page = new Page(mockOriginalPage);
|
||||
|
||||
const nextPage = await page.nextPage();
|
||||
|
||||
expect(nextPage).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle error from original page nextPage', async () => {
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases and error handling', () => {
|
||||
it('should handle null original page', () => {
|
||||
const page = new Page(null);
|
||||
|
||||
expect(page['_originalPage']).toBeNull();
|
||||
expect(page.size).toBeUndefined();
|
||||
expect(page.total).toBeUndefined();
|
||||
expect(page.hasNextPage).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle transform function that throws error', async () => {
|
||||
const errorTransform = () => {
|
||||
throw new Error('Transform error');
|
||||
};
|
||||
const page = new Page(mockOriginalPage, errorTransform);
|
||||
|
||||
await expect(async () => {
|
||||
for await (const item of page) {
|
||||
// This should throw
|
||||
}
|
||||
}).rejects.toThrow('Transform error');
|
||||
});
|
||||
|
||||
it('should handle transform function returning null', async () => {
|
||||
const nullTransform = () => null;
|
||||
const page = new Page(mockOriginalPage, nullTransform);
|
||||
const items: any[] = [];
|
||||
|
||||
for await (const item of page) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
expect(items).toEqual([null, null, null]);
|
||||
});
|
||||
|
||||
it('should handle very large datasets', async () => {
|
||||
const largeItems = Array.from({ length: 10000 }, (_, i) => ({ id: i }));
|
||||
const largePage = {
|
||||
items: largeItems,
|
||||
size: 10000,
|
||||
total: 10000,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const page = new Page(largePage);
|
||||
let count = 0;
|
||||
|
||||
for await (const item of page) {
|
||||
count++;
|
||||
if (count > 10) break; // Don't actually iterate through all 10k items
|
||||
}
|
||||
|
||||
expect(count).toBe(11);
|
||||
});
|
||||
|
||||
it('should handle circular reference in items', async () => {
|
||||
const circularItem: any = { id: 'circular' };
|
||||
circularItem.self = circularItem;
|
||||
const pageWithCircular = {
|
||||
items: [circularItem],
|
||||
size: 1,
|
||||
total: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const page = new Page(pageWithCircular);
|
||||
const items: any[] = [];
|
||||
|
||||
for await (const item of page) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].id).toBe('circular');
|
||||
expect(items[0].self).toBe(items[0]);
|
||||
});
|
||||
|
||||
it('should handle items with complex nested structures', async () => {
|
||||
const complexItems = [
|
||||
{
|
||||
id: 'complex1',
|
||||
nested: {
|
||||
deep: {
|
||||
value: 'deeply nested',
|
||||
array: [1, 2, { nested: 'array object' }],
|
||||
},
|
||||
},
|
||||
metadata: new Map([['key', 'value']]),
|
||||
},
|
||||
];
|
||||
const complexPage = {
|
||||
items: complexItems,
|
||||
size: 1,
|
||||
total: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
const page = new Page(complexPage);
|
||||
const items: any[] = [];
|
||||
|
||||
for await (const item of page) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
expect(items).toEqual(complexItems);
|
||||
expect(items[0].nested.deep.array[2].nested).toBe('array object');
|
||||
});
|
||||
|
||||
it('should handle async transform function', async () => {
|
||||
const asyncTransform = async (item: any) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
return { ...item, asyncTransformed: true };
|
||||
};
|
||||
mockOriginalPage.get.mockResolvedValue(mockItems[0]);
|
||||
const page = new Page(mockOriginalPage, asyncTransform);
|
||||
|
||||
const item = await page.get(0);
|
||||
|
||||
expect(item).toEqual({ ...mockItems[0], asyncTransformed: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,537 @@
|
|||
import { Peer } from '../src/peer';
|
||||
import { Session } from '../src/session';
|
||||
import { Page } from '../src/pagination';
|
||||
import { Honcho } from '../src/client';
|
||||
|
||||
// Mock the @honcho-ai/core module
|
||||
jest.mock('@honcho-ai/core', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
workspaces: {
|
||||
peers: {
|
||||
chat: jest.fn(),
|
||||
sessions: {
|
||||
list: jest.fn(),
|
||||
},
|
||||
messages: {
|
||||
create: jest.fn(),
|
||||
list: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
search: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Peer', () => {
|
||||
let honcho: Honcho;
|
||||
let peer: Peer;
|
||||
let mockClient: any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
honcho = new Honcho({
|
||||
workspaceId: 'test-workspace',
|
||||
apiKey: 'test-key',
|
||||
environment: 'local',
|
||||
});
|
||||
|
||||
peer = new Peer('test-peer', honcho);
|
||||
mockClient = (honcho as any)._client;
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with correct properties', () => {
|
||||
const newPeer = new Peer('peer-id', honcho);
|
||||
|
||||
expect(newPeer.id).toBe('peer-id');
|
||||
expect(newPeer['_honcho']).toBe(honcho);
|
||||
});
|
||||
});
|
||||
|
||||
describe('chat', () => {
|
||||
it('should query peer representation and return response', async () => {
|
||||
const mockResponse = { content: 'Hello, I am a peer response' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await peer.chat('Hello');
|
||||
|
||||
expect(result).toBe('Hello, I am a peer response');
|
||||
expect(mockClient.workspaces.peers.chat).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ queries: 'Hello', stream: undefined, target: undefined, session_id: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should return null for None content', async () => {
|
||||
const mockResponse = { content: 'None' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await peer.chat('Hello');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for empty content', async () => {
|
||||
const mockResponse = { content: null };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await peer.chat('Hello');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle chat with streaming option', async () => {
|
||||
const mockResponse = { content: 'Streamed response' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
await peer.chat('Hello', { stream: true });
|
||||
|
||||
expect(mockClient.workspaces.peers.chat).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ queries: 'Hello', stream: true, target: undefined, session_id: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle chat with target peer', async () => {
|
||||
const targetPeer = new Peer('target-peer', honcho);
|
||||
const mockResponse = { content: 'Targeted response' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
await peer.chat('Hello', { target: targetPeer });
|
||||
|
||||
expect(mockClient.workspaces.peers.chat).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ queries: 'Hello', stream: undefined, target: 'target-peer', session_id: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle chat with target as string', async () => {
|
||||
const mockResponse = { content: 'Targeted response' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
await peer.chat('Hello', { target: 'string-target' });
|
||||
|
||||
expect(mockClient.workspaces.peers.chat).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ queries: 'Hello', stream: undefined, target: 'string-target', session_id: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle chat with session ID', async () => {
|
||||
const mockResponse = { content: 'Session-specific response' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
await peer.chat('Hello', { sessionId: 'session-123' });
|
||||
|
||||
expect(mockClient.workspaces.peers.chat).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ queries: 'Hello', stream: undefined, target: undefined, session_id: 'session-123' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle all options together', async () => {
|
||||
const targetPeer = new Peer('target-peer', honcho);
|
||||
const mockResponse = { content: 'Full options response' };
|
||||
mockClient.workspaces.peers.chat.mockResolvedValue(mockResponse);
|
||||
|
||||
await peer.chat('Hello', {
|
||||
stream: true,
|
||||
target: targetPeer,
|
||||
sessionId: 'session-456'
|
||||
});
|
||||
|
||||
expect(mockClient.workspaces.peers.chat).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ queries: 'Hello', stream: true, target: 'target-peer', session_id: 'session-456' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.chat.mockRejectedValue(new Error('Chat failed'));
|
||||
|
||||
await expect(peer.chat('Hello')).rejects.toThrow('Chat failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSessions', () => {
|
||||
it('should return Page of Session instances', async () => {
|
||||
const mockSessionsData = {
|
||||
items: [
|
||||
{ id: 'session1', metadata: {} },
|
||||
{ id: 'session2', metadata: {} },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.peers.sessions.list.mockResolvedValue(mockSessionsData);
|
||||
|
||||
const sessionsPage = await peer.getSessions();
|
||||
|
||||
expect(sessionsPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.peers.sessions.list).toHaveBeenCalledWith(
|
||||
'test-peer',
|
||||
'test-workspace'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty sessions list', async () => {
|
||||
const mockSessionsData = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.peers.sessions.list.mockResolvedValue(mockSessionsData);
|
||||
|
||||
const sessionsPage = await peer.getSessions();
|
||||
|
||||
expect(sessionsPage).toBeInstanceOf(Page);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addMessages', () => {
|
||||
it('should add a single string message', async () => {
|
||||
mockClient.workspaces.peers.messages.create.mockResolvedValue({});
|
||||
|
||||
await peer.addMessages('Hello world');
|
||||
|
||||
expect(mockClient.workspaces.peers.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ messages: [{ peer_id: 'test-peer', content: 'Hello world', metadata: undefined }] }
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a single message object', async () => {
|
||||
const message = {
|
||||
peerId: 'test-peer',
|
||||
content: 'Test message',
|
||||
metadata: { type: 'test' },
|
||||
};
|
||||
mockClient.workspaces.peers.messages.create.mockResolvedValue({});
|
||||
|
||||
await peer.addMessages(message);
|
||||
|
||||
expect(mockClient.workspaces.peers.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ messages: [{ peer_id: 'test-peer', content: 'Test message', metadata: { type: 'test' } }] }
|
||||
);
|
||||
});
|
||||
|
||||
it('should add message object without specified peerId', async () => {
|
||||
const message = {
|
||||
content: 'Test message without peer ID',
|
||||
metadata: { type: 'test' },
|
||||
};
|
||||
mockClient.workspaces.peers.messages.create.mockResolvedValue({});
|
||||
|
||||
await peer.addMessages(message);
|
||||
|
||||
expect(mockClient.workspaces.peers.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ messages: [{ peer_id: 'test-peer', content: 'Test message without peer ID', metadata: { type: 'test' } }] }
|
||||
);
|
||||
});
|
||||
|
||||
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 } },
|
||||
];
|
||||
mockClient.workspaces.peers.messages.create.mockResolvedValue({});
|
||||
|
||||
await peer.addMessages(messages);
|
||||
|
||||
expect(mockClient.workspaces.peers.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{
|
||||
messages: [
|
||||
{ peer_id: 'peer1', content: 'Message 1', metadata: { order: 1 } },
|
||||
{ peer_id: 'peer2', content: 'Message 2', metadata: { order: 2 } },
|
||||
]
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty array', async () => {
|
||||
mockClient.workspaces.peers.messages.create.mockResolvedValue({});
|
||||
|
||||
await peer.addMessages([]);
|
||||
|
||||
expect(mockClient.workspaces.peers.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ messages: [] }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.messages.create.mockRejectedValue(new Error('Failed to add messages'));
|
||||
|
||||
await expect(peer.addMessages('test')).rejects.toThrow('Failed to add messages');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessages', () => {
|
||||
it('should get messages without options', async () => {
|
||||
const mockMessagesData = {
|
||||
items: [
|
||||
{ id: 'msg1', content: 'Message 1', peer_id: 'test-peer' },
|
||||
{ id: 'msg2', content: 'Message 2', peer_id: 'test-peer' },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.peers.messages.list.mockResolvedValue(mockMessagesData);
|
||||
|
||||
const messagesPage = await peer.getMessages();
|
||||
|
||||
expect(messagesPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.peers.messages.list).toHaveBeenCalledWith(
|
||||
'test-peer',
|
||||
'test-workspace',
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('should get messages with filter options', async () => {
|
||||
const mockMessagesData = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.peers.messages.list.mockResolvedValue(mockMessagesData);
|
||||
|
||||
const options = {
|
||||
filter: { type: 'important', date: '2023-01-01' }
|
||||
};
|
||||
await peer.getMessages(options);
|
||||
|
||||
expect(mockClient.workspaces.peers.messages.list).toHaveBeenCalledWith(
|
||||
'test-peer',
|
||||
'test-workspace',
|
||||
{ type: 'important', date: '2023-01-01' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.messages.list.mockRejectedValue(new Error('Failed to get messages'));
|
||||
|
||||
await expect(peer.getMessages()).rejects.toThrow('Failed to get messages');
|
||||
});
|
||||
});
|
||||
|
||||
describe('message', () => {
|
||||
it('should create message object without metadata', () => {
|
||||
const message = peer.message('Test content');
|
||||
|
||||
expect(message).toEqual({
|
||||
peerId: 'test-peer',
|
||||
content: 'Test content',
|
||||
metadata: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create message object with metadata', () => {
|
||||
const metadata = { importance: 'high', category: 'greeting' };
|
||||
const message = peer.message('Hello there', { metadata });
|
||||
|
||||
expect(message).toEqual({
|
||||
peerId: 'test-peer',
|
||||
content: 'Hello there',
|
||||
metadata: { importance: 'high', category: 'greeting' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty content', () => {
|
||||
const message = peer.message('');
|
||||
|
||||
expect(message).toEqual({
|
||||
peerId: 'test-peer',
|
||||
content: '',
|
||||
metadata: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMetadata', () => {
|
||||
it('should return peer metadata', async () => {
|
||||
const mockPeer = {
|
||||
id: 'test-peer',
|
||||
metadata: { name: 'Test Peer', role: 'assistant' },
|
||||
};
|
||||
mockClient.workspaces.peers.getOrCreate.mockResolvedValue(mockPeer);
|
||||
|
||||
const metadata = await peer.getMetadata();
|
||||
|
||||
expect(metadata).toEqual({ name: 'Test Peer', role: 'assistant' });
|
||||
expect(mockClient.workspaces.peers.getOrCreate).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
{ id: 'test-peer' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty object when no metadata exists', async () => {
|
||||
const mockPeer = {
|
||||
id: 'test-peer',
|
||||
metadata: null,
|
||||
};
|
||||
mockClient.workspaces.peers.getOrCreate.mockResolvedValue(mockPeer);
|
||||
|
||||
const metadata = await peer.getMetadata();
|
||||
|
||||
expect(metadata).toEqual({});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMetadata', () => {
|
||||
it('should update peer metadata', async () => {
|
||||
const metadata = { name: 'Updated Peer', status: 'active' };
|
||||
mockClient.workspaces.peers.update.mockResolvedValue({});
|
||||
|
||||
await peer.setMetadata(metadata);
|
||||
|
||||
expect(mockClient.workspaces.peers.update).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ metadata }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty metadata', async () => {
|
||||
mockClient.workspaces.peers.update.mockResolvedValue({});
|
||||
|
||||
await peer.setMetadata({});
|
||||
|
||||
expect(mockClient.workspaces.peers.update).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ metadata: {} }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle complex metadata objects', async () => {
|
||||
const complexMetadata = {
|
||||
profile: { name: 'Complex Peer', age: 25 },
|
||||
settings: { theme: 'dark', notifications: true },
|
||||
tags: ['ai', 'assistant', 'helpful'],
|
||||
};
|
||||
mockClient.workspaces.peers.update.mockResolvedValue({});
|
||||
|
||||
await peer.setMetadata(complexMetadata);
|
||||
|
||||
expect(mockClient.workspaces.peers.update).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ metadata: complexMetadata }
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
mockClient.workspaces.peers.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await peer.search('hello');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.peers.search).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ body: 'hello' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty search results', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.peers.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await peer.search('nonexistent');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
it('should handle complex search queries', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.peers.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const complexQuery = 'complex query with "quotes" and special characters!@#$%';
|
||||
await peer.search(complexQuery);
|
||||
|
||||
expect(mockClient.workspaces.peers.search).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-peer',
|
||||
{ body: complexQuery }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.peers.search.mockRejectedValue(new Error('Search failed'));
|
||||
|
||||
await expect(peer.search('test')).rejects.toThrow('Search failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,692 @@
|
|||
import { Session } from '../src/session';
|
||||
import { Peer } from '../src/peer';
|
||||
import { Page } from '../src/pagination';
|
||||
import { SessionContext } from '../src/session_context';
|
||||
import { Honcho } from '../src/client';
|
||||
|
||||
// Mock the @honcho-ai/core module
|
||||
jest.mock('@honcho-ai/core', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
workspaces: {
|
||||
sessions: {
|
||||
peers: {
|
||||
add: jest.fn(),
|
||||
set: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
list: jest.fn(),
|
||||
},
|
||||
messages: {
|
||||
create: jest.fn(),
|
||||
list: jest.fn(),
|
||||
},
|
||||
getOrCreate: jest.fn(),
|
||||
update: jest.fn(),
|
||||
getContext: jest.fn(),
|
||||
search: jest.fn(),
|
||||
},
|
||||
peers: {
|
||||
workingRepresentation: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Session', () => {
|
||||
let honcho: Honcho;
|
||||
let session: Session;
|
||||
let mockClient: any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
honcho = new Honcho({
|
||||
workspaceId: 'test-workspace',
|
||||
apiKey: 'test-key',
|
||||
environment: 'local',
|
||||
});
|
||||
|
||||
session = new Session('test-session', honcho);
|
||||
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
|
||||
});
|
||||
|
||||
expect(newSession.id).toBe('session-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addPeers', () => {
|
||||
it('should add single peer by string ID', async () => {
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
await session.addPeers('peer1');
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ 'peer1': { observe_me: true, observe_others: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should add single peer by Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
await session.addPeers(peer);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ 'peer1': { observe_me: true, observe_others: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should add array of peer strings', async () => {
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
await session.addPeers(['peer1', 'peer2', 'peer3']);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'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 }
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should add array of Peer objects', async () => {
|
||||
const peers = [
|
||||
new Peer('peer1', honcho),
|
||||
new Peer('peer2', honcho),
|
||||
new Peer('peer3', honcho),
|
||||
];
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
await session.addPeers(peers);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'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 }
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should add mixed array of strings and Peer objects', async () => {
|
||||
const peers = [
|
||||
'string-peer',
|
||||
new Peer('object-peer', honcho),
|
||||
];
|
||||
mockClient.workspaces.sessions.peers.add.mockResolvedValue({});
|
||||
|
||||
await session.addPeers(peers);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.add).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
'string-peer': { observe_me: true, observe_others: false },
|
||||
'object-peer': { observe_me: true, observe_others: false }
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPeers', () => {
|
||||
it('should set single peer by string ID', async () => {
|
||||
mockClient.workspaces.sessions.peers.set.mockResolvedValue({});
|
||||
|
||||
await session.setPeers('peer1');
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.set).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ 'peer1': { observe_me: true, observe_others: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should set single peer by Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
mockClient.workspaces.sessions.peers.set.mockResolvedValue({});
|
||||
|
||||
await session.setPeers(peer);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.set).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ 'peer1': { observe_me: true, observe_others: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should set array of peers', async () => {
|
||||
const peers = ['peer1', new Peer('peer2', honcho)];
|
||||
mockClient.workspaces.sessions.peers.set.mockResolvedValue({});
|
||||
|
||||
await session.setPeers(peers);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.set).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
'peer1': { observe_me: true, observe_others: false },
|
||||
'peer2': { observe_me: true, observe_others: false }
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removePeers', () => {
|
||||
it('should remove single peer by string ID', async () => {
|
||||
mockClient.workspaces.sessions.peers.remove.mockResolvedValue({});
|
||||
|
||||
await session.removePeers('peer1');
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.remove).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
['peer1']
|
||||
);
|
||||
});
|
||||
|
||||
it('should remove single peer by Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
mockClient.workspaces.sessions.peers.remove.mockResolvedValue({});
|
||||
|
||||
await session.removePeers(peer);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.remove).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
['peer1']
|
||||
);
|
||||
});
|
||||
|
||||
it('should remove array of peers', async () => {
|
||||
const peers = ['peer1', new Peer('peer2', honcho)];
|
||||
mockClient.workspaces.sessions.peers.remove.mockResolvedValue({});
|
||||
|
||||
await session.removePeers(peers);
|
||||
|
||||
expect(mockClient.workspaces.sessions.peers.remove).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
['peer1', 'peer2']
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPeers', () => {
|
||||
it('should return Page of Peer instances', async () => {
|
||||
const mockPeersData = {
|
||||
items: [
|
||||
{ id: 'peer1', metadata: {} },
|
||||
{ id: 'peer2', metadata: {} },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.sessions.peers.list.mockResolvedValue(mockPeersData);
|
||||
|
||||
const peers = await session.getPeers();
|
||||
|
||||
expect(peers).toBeInstanceOf(Array);
|
||||
expect(mockClient.workspaces.sessions.peers.list).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty peers list', async () => {
|
||||
const mockPeersData = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.sessions.peers.list.mockResolvedValue(mockPeersData);
|
||||
|
||||
const peers = await session.getPeers();
|
||||
|
||||
expect(peers).toBeInstanceOf(Array);
|
||||
expect(peers.length).toBe(0);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addMessages', () => {
|
||||
it('should add single message', async () => {
|
||||
const message = {
|
||||
peerId: 'peer1',
|
||||
content: 'Hello world',
|
||||
metadata: { type: 'greeting' },
|
||||
};
|
||||
mockClient.workspaces.sessions.messages.create.mockResolvedValue({});
|
||||
|
||||
await session.addMessages(message);
|
||||
|
||||
expect(mockClient.workspaces.sessions.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
messages: [{
|
||||
peer_id: 'peer1',
|
||||
content: 'Hello world',
|
||||
metadata: { type: 'greeting' }
|
||||
}]
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
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 } },
|
||||
];
|
||||
mockClient.workspaces.sessions.messages.create.mockResolvedValue({});
|
||||
|
||||
await session.addMessages(messages);
|
||||
|
||||
expect(mockClient.workspaces.sessions.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{
|
||||
messages: [
|
||||
{ peer_id: 'peer1', content: 'Message 1', metadata: { order: 1 } },
|
||||
{ peer_id: 'peer2', content: 'Message 2', metadata: { order: 2 } },
|
||||
]
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle messages without metadata', async () => {
|
||||
const message = {
|
||||
peerId: 'peer1',
|
||||
content: 'Simple message',
|
||||
};
|
||||
mockClient.workspaces.sessions.messages.create.mockResolvedValue({});
|
||||
|
||||
await session.addMessages(message);
|
||||
|
||||
expect(mockClient.workspaces.sessions.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ messages: [{ peer_id: 'peer1', content: 'Simple message', metadata: undefined }] }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty array', async () => {
|
||||
mockClient.workspaces.sessions.messages.create.mockResolvedValue({});
|
||||
|
||||
await session.addMessages([]);
|
||||
|
||||
expect(mockClient.workspaces.sessions.messages.create).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ messages: [] }
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessages', () => {
|
||||
it('should get messages without options', async () => {
|
||||
const mockMessagesData = {
|
||||
items: [
|
||||
{ id: 'msg1', content: 'Message 1', peer_id: 'peer1' },
|
||||
{ id: 'msg2', content: 'Message 2', peer_id: 'peer2' },
|
||||
],
|
||||
total: 2,
|
||||
size: 2,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.sessions.messages.list.mockResolvedValue(mockMessagesData);
|
||||
|
||||
const messagesPage = await session.getMessages();
|
||||
|
||||
expect(messagesPage).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.sessions.messages.list).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('should get messages with filter options', async () => {
|
||||
const mockMessagesData = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.sessions.messages.list.mockResolvedValue(mockMessagesData);
|
||||
|
||||
const options = {
|
||||
filter: { peer_id: 'peer1', type: 'important' }
|
||||
};
|
||||
await session.getMessages(options);
|
||||
|
||||
expect(mockClient.workspaces.sessions.messages.list).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ peer_id: 'peer1', type: '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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMetadata', () => {
|
||||
it('should return session metadata', async () => {
|
||||
const mockSession = {
|
||||
id: 'test-session',
|
||||
metadata: { name: 'Test Session', active: true },
|
||||
};
|
||||
mockClient.workspaces.sessions.getOrCreate.mockResolvedValue(mockSession);
|
||||
|
||||
const metadata = await session.getMetadata();
|
||||
|
||||
expect(metadata).toEqual({ name: 'Test Session', active: true });
|
||||
expect(mockClient.workspaces.sessions.getOrCreate).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
{ id: 'test-session' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty object when no metadata exists', async () => {
|
||||
const mockSession = {
|
||||
id: 'test-session',
|
||||
metadata: null,
|
||||
};
|
||||
mockClient.workspaces.sessions.getOrCreate.mockResolvedValue(mockSession);
|
||||
|
||||
const metadata = await session.getMetadata();
|
||||
|
||||
expect(metadata).toEqual({});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMetadata', () => {
|
||||
it('should update session metadata', async () => {
|
||||
const metadata = { name: 'Updated Session', status: 'active' };
|
||||
mockClient.workspaces.sessions.update.mockResolvedValue({});
|
||||
|
||||
await session.setMetadata(metadata);
|
||||
|
||||
expect(mockClient.workspaces.sessions.update).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ metadata }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty metadata', async () => {
|
||||
mockClient.workspaces.sessions.update.mockResolvedValue({});
|
||||
|
||||
await session.setMetadata({});
|
||||
|
||||
expect(mockClient.workspaces.sessions.update).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ metadata: {} }
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getContext', () => {
|
||||
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' },
|
||||
],
|
||||
summary: 'Conversation summary',
|
||||
};
|
||||
mockClient.workspaces.sessions.getContext.mockResolvedValue(mockContext);
|
||||
|
||||
const context = await session.getContext();
|
||||
|
||||
expect(context).toBeInstanceOf(SessionContext);
|
||||
expect(context.sessionId).toBe('test-session');
|
||||
expect(context.messages).toEqual(mockContext.messages);
|
||||
expect(context.summary).toBe('Conversation summary');
|
||||
expect(mockClient.workspaces.sessions.getContext).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ tokens: undefined, summary: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should get session context with options', async () => {
|
||||
const mockContext = {
|
||||
messages: [{ id: 'msg1', content: 'Hello', peer_name: 'peer1' }],
|
||||
summary: 'Brief summary',
|
||||
};
|
||||
mockClient.workspaces.sessions.getContext.mockResolvedValue(mockContext);
|
||||
|
||||
const options = { summary: true, tokens: 1000 };
|
||||
const context = await session.getContext(options);
|
||||
|
||||
expect(context).toBeInstanceOf(SessionContext);
|
||||
expect(mockClient.workspaces.sessions.getContext).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
{ tokens: 1000, summary: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle context without summary', async () => {
|
||||
const mockContext = {
|
||||
messages: [{ id: 'msg1', content: 'Hello', peer_name: 'peer1' }],
|
||||
};
|
||||
mockClient.workspaces.sessions.getContext.mockResolvedValue(mockContext);
|
||||
|
||||
const context = await session.getContext();
|
||||
|
||||
expect(context.summary).toBe('');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
mockClient.workspaces.sessions.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await session.search('hello');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
expect(mockClient.workspaces.sessions.search).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'test-session',
|
||||
'hello'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty search results', async () => {
|
||||
const mockSearchResults = {
|
||||
items: [],
|
||||
total: 0,
|
||||
size: 0,
|
||||
hasNextPage: false,
|
||||
};
|
||||
mockClient.workspaces.sessions.search.mockResolvedValue(mockSearchResults);
|
||||
|
||||
const results = await session.search('nonexistent');
|
||||
|
||||
expect(results).toBeInstanceOf(Page);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockClient.workspaces.sessions.search.mockRejectedValue(new Error('Search failed'));
|
||||
|
||||
await expect(session.search('test')).rejects.toThrow('Search failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('workingRep', () => {
|
||||
it('should get working representation with peer string', async () => {
|
||||
const mockRepresentation = {
|
||||
peer_id: 'peer1',
|
||||
knowledge: 'Some knowledge about the peer',
|
||||
relationships: ['peer2', 'peer3'],
|
||||
};
|
||||
mockClient.workspaces.peers.workingRepresentation.mockResolvedValue(mockRepresentation);
|
||||
|
||||
const result = await session.workingRep('peer1');
|
||||
|
||||
expect(result).toEqual(mockRepresentation);
|
||||
expect(mockClient.workspaces.peers.workingRepresentation).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'peer1',
|
||||
{ session_id: 'test-session', target: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should get working representation with Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
const mockRepresentation = {
|
||||
peer_id: 'peer1',
|
||||
knowledge: 'Some knowledge',
|
||||
};
|
||||
mockClient.workspaces.peers.workingRepresentation.mockResolvedValue(mockRepresentation);
|
||||
|
||||
const result = await session.workingRep(peer);
|
||||
|
||||
expect(result).toEqual(mockRepresentation);
|
||||
expect(mockClient.workspaces.peers.workingRepresentation).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'peer1',
|
||||
{ session_id: 'test-session', target: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should get working representation with target peer string', async () => {
|
||||
const mockRepresentation = {
|
||||
peer_id: 'peer1',
|
||||
target_knowledge: 'What peer1 knows about target',
|
||||
};
|
||||
mockClient.workspaces.peers.workingRepresentation.mockResolvedValue(mockRepresentation);
|
||||
|
||||
const result = await session.workingRep('peer1', 'target-peer');
|
||||
|
||||
expect(result).toEqual(mockRepresentation);
|
||||
expect(mockClient.workspaces.peers.workingRepresentation).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'peer1',
|
||||
{ session_id: 'test-session', target: 'target-peer' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should get working representation with target Peer object', async () => {
|
||||
const peer = new Peer('peer1', honcho);
|
||||
const target = new Peer('target-peer', honcho);
|
||||
const mockRepresentation = {
|
||||
peer_id: 'peer1',
|
||||
target_knowledge: 'What peer1 knows about target',
|
||||
};
|
||||
mockClient.workspaces.peers.workingRepresentation.mockResolvedValue(mockRepresentation);
|
||||
|
||||
const result = await session.workingRep(peer, target);
|
||||
|
||||
expect(result).toEqual(mockRepresentation);
|
||||
expect(mockClient.workspaces.peers.workingRepresentation).toHaveBeenCalledWith(
|
||||
'test-workspace',
|
||||
'peer1',
|
||||
{ session_id: 'test-session', target: 'target-peer' }
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
import { SessionContext } from '../src/session_context';
|
||||
import { Peer } from '../src/peer';
|
||||
|
||||
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' },
|
||||
];
|
||||
|
||||
sessionContext = new SessionContext('test-session', mockMessages, 'This is a summary');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
it('should initialize with empty summary when not provided', () => {
|
||||
const context = new SessionContext('session-id', mockMessages);
|
||||
|
||||
expect(context.sessionId).toBe('session-id');
|
||||
expect(context.messages).toEqual(mockMessages);
|
||||
expect(context.summary).toBe('');
|
||||
});
|
||||
|
||||
it('should handle empty messages array', () => {
|
||||
const context = new SessionContext('session-id', [], 'No messages');
|
||||
|
||||
expect(context.sessionId).toBe('session-id');
|
||||
expect(context.messages).toEqual([]);
|
||||
expect(context.summary).toBe('No messages');
|
||||
});
|
||||
|
||||
it('should handle null/undefined summary', () => {
|
||||
const context1 = new SessionContext('session-id', mockMessages, undefined as any);
|
||||
const context2 = new SessionContext('session-id', mockMessages, null as any);
|
||||
|
||||
expect(context1.summary).toBe('');
|
||||
expect(context2.summary).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toOpenAI', () => {
|
||||
it('should convert messages to OpenAI format with string assistant', () => {
|
||||
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!' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert messages to OpenAI format with Peer object', () => {
|
||||
const mockHoncho = {} as any;
|
||||
const assistantPeer = new Peer('assistant', mockHoncho);
|
||||
|
||||
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!' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle messages where assistant is different peer', () => {
|
||||
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!' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle empty messages array', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
const openAIMessages = emptyContext.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle messages with missing peer_name', () => {
|
||||
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 },
|
||||
];
|
||||
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' },
|
||||
]);
|
||||
});
|
||||
|
||||
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' },
|
||||
];
|
||||
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 ' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toAnthropic', () => {
|
||||
it('should convert messages to Anthropic format with string assistant', () => {
|
||||
const anthropicMessages = sessionContext.toAnthropic('assistant');
|
||||
|
||||
expect(anthropicMessages).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!' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert messages to Anthropic format with Peer object', () => {
|
||||
const mockHoncho = {} as any;
|
||||
const assistantPeer = new Peer('assistant', mockHoncho);
|
||||
|
||||
const anthropicMessages = sessionContext.toAnthropic(assistantPeer);
|
||||
|
||||
expect(anthropicMessages).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!' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle messages where assistant is different peer', () => {
|
||||
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!' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle empty messages array', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
const anthropicMessages = emptyContext.toAnthropic('assistant');
|
||||
|
||||
expect(anthropicMessages).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle messages with missing peer_name', () => {
|
||||
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 },
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithMissingPeer);
|
||||
|
||||
const anthropicMessages = context.toAnthropic('assistant');
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'No peer' },
|
||||
{ role: 'user', content: 'Another message' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('length getter', () => {
|
||||
it('should return correct message count', () => {
|
||||
expect(sessionContext.length).toBe(4);
|
||||
});
|
||||
|
||||
it('should return zero for empty messages', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
expect(emptyContext.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should return correct count for single message', () => {
|
||||
const singleMessageContext = new SessionContext('session-id', [mockMessages[0]]);
|
||||
expect(singleMessageContext.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toString', () => {
|
||||
it('should return correct string representation', () => {
|
||||
const result = sessionContext.toString();
|
||||
expect(result).toBe('SessionContext(messages=4)');
|
||||
});
|
||||
|
||||
it('should handle empty messages', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
const result = emptyContext.toString();
|
||||
expect(result).toBe('SessionContext(messages=0)');
|
||||
});
|
||||
|
||||
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 context = new SessionContext('session-id', manyMessages);
|
||||
|
||||
const result = context.toString();
|
||||
expect(result).toBe('SessionContext(messages=1000)');
|
||||
});
|
||||
});
|
||||
|
||||
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' },
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithNullContent);
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: null },
|
||||
{ role: 'user', content: undefined },
|
||||
]);
|
||||
});
|
||||
|
||||
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' },
|
||||
];
|
||||
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 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle very long session IDs and summaries', () => {
|
||||
const longSessionId = 'x'.repeat(1000);
|
||||
const longSummary = 'Very long summary that goes on and on...'.repeat(100);
|
||||
const context = new SessionContext(longSessionId, mockMessages, longSummary);
|
||||
|
||||
expect(context.sessionId).toBe(longSessionId);
|
||||
expect(context.summary).toBe(longSummary);
|
||||
expect(context.length).toBe(4);
|
||||
});
|
||||
|
||||
it('should handle messages with additional properties', () => {
|
||||
const messagesWithExtraProps = [
|
||||
{
|
||||
id: 'msg1',
|
||||
content: 'Hello',
|
||||
peer_name: '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' },
|
||||
]);
|
||||
});
|
||||
|
||||
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' },
|
||||
];
|
||||
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
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle messages without id field', () => {
|
||||
const messagesWithoutId = [
|
||||
{ content: 'Message without ID', peer_name: 'assistant' },
|
||||
{ peer_name: 'user', content: 'Another message' },
|
||||
];
|
||||
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' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Global test setup
|
||||
import 'jest';
|
||||
|
||||
// Suppress console warnings during tests unless explicitly testing them
|
||||
const originalConsoleWarn = console.warn;
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
beforeAll(() => {
|
||||
console.warn = jest.fn();
|
||||
console.error = jest.fn();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
console.warn = originalConsoleWarn;
|
||||
console.error = originalConsoleError;
|
||||
});
|
||||
|
||||
// Mock environment variables for tests
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
|
@ -0,0 +1,788 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "honcho-ai",
|
||||
"dependencies": {
|
||||
"@honcho-ai/core": "^1.0.0",
|
||||
"@types/node": "^24.0.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"eslint": "^8.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.27.5", "", {}, "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.27.4", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.4", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.4", "@babel/types": "^7.27.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.27.5", "", { "dependencies": { "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="],
|
||||
|
||||
"@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="],
|
||||
|
||||
"@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="],
|
||||
|
||||
"@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="],
|
||||
|
||||
"@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="],
|
||||
|
||||
"@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="],
|
||||
|
||||
"@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="],
|
||||
|
||||
"@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="],
|
||||
|
||||
"@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="],
|
||||
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="],
|
||||
|
||||
"@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="],
|
||||
|
||||
"@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="],
|
||||
|
||||
"@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="],
|
||||
|
||||
"@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="],
|
||||
|
||||
"@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="],
|
||||
|
||||
"@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.27.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="],
|
||||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
|
||||
|
||||
"@honcho-ai/core": ["@honcho-ai/core@1.0.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-WwpKTxMhkBEpiQ2UYlM9HH+MCAkWA8o+5w/J/bYKAo9traZF4UKsO2VouWIXiLsyIvLfqzLGgGrALoH2P4h9Uw=="],
|
||||
|
||||
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
|
||||
|
||||
"@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="],
|
||||
|
||||
"@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="],
|
||||
|
||||
"@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="],
|
||||
|
||||
"@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="],
|
||||
|
||||
"@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="],
|
||||
|
||||
"@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="],
|
||||
|
||||
"@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="],
|
||||
|
||||
"@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="],
|
||||
|
||||
"@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
|
||||
|
||||
"@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="],
|
||||
|
||||
"@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="],
|
||||
|
||||
"@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="],
|
||||
|
||||
"@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="],
|
||||
|
||||
"@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
|
||||
|
||||
"@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
|
||||
|
||||
"@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||
|
||||
"@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/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
|
||||
|
||||
"@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
|
||||
|
||||
"@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
|
||||
|
||||
"@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
|
||||
|
||||
"@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.0.3", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg=="],
|
||||
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="],
|
||||
|
||||
"@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
|
||||
|
||||
"@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="],
|
||||
|
||||
"@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="],
|
||||
|
||||
"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.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" } }, "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.25.0", "", { "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA=="],
|
||||
|
||||
"bs-logger": ["bs-logger@0.2.6", "", { "dependencies": { "fast-json-stable-stringify": "2.x" } }, "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog=="],
|
||||
|
||||
"bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001723", "", {}, "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
|
||||
|
||||
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
|
||||
|
||||
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="],
|
||||
|
||||
"collect-v8-coverage": ["collect-v8-coverage@1.0.2", "", {}, "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"dedent": ["dedent@1.6.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="],
|
||||
|
||||
"diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="],
|
||||
|
||||
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
|
||||
|
||||
"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.170", "", {}, "sha512-GP+M7aeluQo9uAyiTCxgIj/j+PrWhMlY7LFVj8prlsPljd0Fdg9AprlfUi+OCSFWy9Y5/2D/Jrj9HS8Z4rpKWA=="],
|
||||
|
||||
"emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
|
||||
|
||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||
|
||||
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
|
||||
|
||||
"exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="],
|
||||
|
||||
"expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
|
||||
|
||||
"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@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
|
||||
|
||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||
|
||||
"form-data": ["form-data@4.0.3", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA=="],
|
||||
|
||||
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
|
||||
|
||||
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
|
||||
|
||||
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
|
||||
|
||||
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
|
||||
|
||||
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
|
||||
|
||||
"istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="],
|
||||
|
||||
"istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
|
||||
|
||||
"istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="],
|
||||
|
||||
"jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="],
|
||||
|
||||
"jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="],
|
||||
|
||||
"jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
|
||||
|
||||
"jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="],
|
||||
|
||||
"jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="],
|
||||
|
||||
"jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
|
||||
|
||||
"jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
|
||||
|
||||
"jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="],
|
||||
|
||||
"jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="],
|
||||
|
||||
"jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="],
|
||||
|
||||
"jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
|
||||
|
||||
"jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
|
||||
|
||||
"jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="],
|
||||
|
||||
"jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="],
|
||||
|
||||
"jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="],
|
||||
|
||||
"jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="],
|
||||
|
||||
"jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="],
|
||||
|
||||
"jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="],
|
||||
|
||||
"jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="],
|
||||
|
||||
"jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
|
||||
|
||||
"jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="],
|
||||
|
||||
"jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="],
|
||||
|
||||
"jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||
|
||||
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
|
||||
|
||||
"make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
|
||||
|
||||
"makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
|
||||
|
||||
"pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
|
||||
|
||||
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
|
||||
"resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
|
||||
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||
|
||||
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
||||
|
||||
"string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="],
|
||||
|
||||
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
|
||||
|
||||
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
|
||||
|
||||
"tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
|
||||
|
||||
"walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"@honcho-ai/core/@types/node": ["@types/node@18.19.112", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-i+Vukt9POdS/MBI7YrrkkI5fMfwFtOjphSmt4WXYLfwqsfr6z/HdCx7LqT9M7JktGob8WNgj8nFB4TbGNE4Cog=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
|
||||
|
||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
||||
|
||||
"babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
|
||||
|
||||
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
|
||||
|
||||
"stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"@honcho-ai/core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
import { Honcho, SessionPeerConfig } from '../src';
|
||||
|
||||
/**
|
||||
* Example usage of the Honcho TypeScript SDK.
|
||||
*
|
||||
* This demonstrates how to manage peers, sessions, and messages
|
||||
* using the high-level SDK API.
|
||||
*/
|
||||
async function main() {
|
||||
console.log('Initializing Honcho client...');
|
||||
const honcho = new Honcho({
|
||||
environment: 'local',
|
||||
workspaceId: 'test',
|
||||
});
|
||||
|
||||
console.log('Creating peers...');
|
||||
const assistant = honcho.peer('bob');
|
||||
const alice = honcho.peer('alice');
|
||||
|
||||
console.log('Fetching all peers in workspace...');
|
||||
const peers = await honcho.getPeers();
|
||||
for await (const peer of peers) {
|
||||
console.log('Peer:', peer.id);
|
||||
}
|
||||
|
||||
console.log('Fetching workspace metadata...');
|
||||
const m = await honcho.getMetadata();
|
||||
console.log('Current metadata:', m);
|
||||
await honcho.setMetadata({ test: 'test' });
|
||||
console.log('Set workspace metadata.');
|
||||
|
||||
console.log('Testing chat endpoint (should be null)...');
|
||||
const response = await alice.chat('what did alice have for breakfast today?');
|
||||
console.log('Chat response:', response);
|
||||
|
||||
console.log('Creating session...');
|
||||
const mySession = honcho.session('session_1');
|
||||
|
||||
console.log('Adding peers to session...');
|
||||
await mySession.addPeers([alice, [assistant, new SessionPeerConfig({ observe_me: false })]]);
|
||||
console.log('Peers added to session.');
|
||||
|
||||
console.log('Fetching sessions for alice...');
|
||||
const _sessions = await alice.getSessions();
|
||||
for await (const session of _sessions) {
|
||||
console.log('Session:', session.id);
|
||||
}
|
||||
|
||||
console.log('Adding messages to session...');
|
||||
await mySession.addMessages([
|
||||
assistant.message('what did you have for breakfast today, alice?'),
|
||||
alice.message('i had oatmeal.'),
|
||||
]);
|
||||
console.log('Messages added.');
|
||||
|
||||
const sessionMetadata = await mySession.getMetadata();
|
||||
console.log('Session metadata:', sessionMetadata);
|
||||
await mySession.setMetadata({ ...sessionMetadata, test: 'test2' });
|
||||
console.log('Session metadata updated.');
|
||||
|
||||
console.log('Querying alice global representation...');
|
||||
await alice.chat('what did the user have for breakfast today?');
|
||||
|
||||
console.log('Querying alice local representation of assistant...');
|
||||
await alice.chat('does alice know what bob had for breakfast?', { target: assistant });
|
||||
|
||||
console.log('Querying assistant local representation of alice in session...');
|
||||
await assistant.chat('does the assistant know what alice had for breakfast?', {
|
||||
target: alice,
|
||||
sessionId: mySession.id,
|
||||
});
|
||||
|
||||
console.log('Adding non-message content to alice...');
|
||||
await alice.addMessages('this might be a document about alice, say, a journal entry.');
|
||||
|
||||
console.log('Creating charlie peer and adding message...');
|
||||
const charlie = honcho.peer('charlie');
|
||||
await mySession.addMessages(charlie.message('hello world!'));
|
||||
|
||||
console.log('Fetching and updating charlie metadata...');
|
||||
const charlieMetadata = await charlie.getMetadata();
|
||||
await charlie.setMetadata({ ...charlieMetadata, location: 'the moon' });
|
||||
console.log('Charlie metadata updated.');
|
||||
|
||||
console.log('Querying charlie for location...');
|
||||
await charlie.chat('where is the user?');
|
||||
|
||||
console.log('Fetching all messages from session...');
|
||||
const messages = await mySession.getMessages();
|
||||
console.log('Messages:', messages.total);
|
||||
|
||||
console.log('Fetching session context...');
|
||||
const context = await mySession.getContext();
|
||||
const openaiMessages = context.toOpenAI(alice.id);
|
||||
const anthropicMessages = context.toAnthropic(alice.id);
|
||||
console.log('OpenAI context:', openaiMessages);
|
||||
console.log('Anthropic context:', anthropicMessages);
|
||||
|
||||
console.log('Adding test message using property syntax...');
|
||||
await mySession.addMessages(
|
||||
assistant.message('This is a test message using the property syntax')
|
||||
);
|
||||
|
||||
console.log('Sample code executed successfully!');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Error running example:', err);
|
||||
});
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { Honcho } from '../src';
|
||||
|
||||
/**
|
||||
* Example demonstrating how to get context from a session with summary and token limits.
|
||||
*
|
||||
* This creates a session with random messages and retrieves context
|
||||
* with a low token limit to demonstrate the summarization feature.
|
||||
*/
|
||||
async function main() {
|
||||
console.log('Creating Honcho client...');
|
||||
// Create a Honcho client with the default workspace
|
||||
const honcho = new Honcho({
|
||||
environment: 'local'
|
||||
});
|
||||
|
||||
console.log('Creating peers...');
|
||||
const peers = [
|
||||
honcho.peer('alice'),
|
||||
honcho.peer('bob'),
|
||||
honcho.peer('charlie'),
|
||||
];
|
||||
|
||||
// Create a new session
|
||||
const sessionId = `context_test_${crypto.randomUUID()}`;
|
||||
const session = 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 = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const randomPeer = peers[Math.floor(Math.random() * peers.length)];
|
||||
messages.push(
|
||||
randomPeer.message(`Hello from ${randomPeer.id}! This is message ${i}.`)
|
||||
);
|
||||
}
|
||||
|
||||
await session.addMessages(messages);
|
||||
console.log('Added 10 random messages to session.');
|
||||
|
||||
console.log('Getting context with summary and low token limit...');
|
||||
// Get some context of the session
|
||||
// Set the token limit super low so we only get a few of the tiny messages created
|
||||
const context = await session.getContext({ summary: true, tokens: 50 });
|
||||
console.log('Context returned:', context);
|
||||
|
||||
console.log('Example completed successfully!');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Error running get_context example:', err);
|
||||
});
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { Honcho } from '../src';
|
||||
|
||||
/**
|
||||
* Example demonstrating how to get peer representations.
|
||||
*
|
||||
* This creates a session with random messages and retrieves both
|
||||
* global and local representations for a peer.
|
||||
*/
|
||||
async function main() {
|
||||
console.log('Creating Honcho client...');
|
||||
// Create a Honcho client with the default workspace
|
||||
const honcho = new Honcho({
|
||||
environment: 'local'
|
||||
});
|
||||
|
||||
console.log('Creating peers...');
|
||||
const peers = [
|
||||
honcho.peer('alice'),
|
||||
honcho.peer('bob'),
|
||||
honcho.peer('charlie'),
|
||||
];
|
||||
|
||||
// Create a new session
|
||||
const sessionId = `context_test_${crypto.randomUUID()}`;
|
||||
const session = 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 = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const randomPeer = peers[Math.floor(Math.random() * peers.length)];
|
||||
messages.push(
|
||||
randomPeer.message(`Hello from ${randomPeer.id}! This is message ${i}.`)
|
||||
);
|
||||
}
|
||||
|
||||
await session.addMessages(messages);
|
||||
console.log('Added 10 random messages to session.');
|
||||
|
||||
const alice = peers[0];
|
||||
const bob = peers[1];
|
||||
|
||||
console.log('Getting alice\'s working representation in session...');
|
||||
// Get alice's working representation in the session
|
||||
const workingRepresentation = await session.workingRep(alice);
|
||||
console.log('Working representation returned:', workingRepresentation);
|
||||
|
||||
console.log('Getting alice\'s working representation *of bob* in session...');
|
||||
// Get alice's working representation *of bob* in the session
|
||||
const workingRepresentationOfBob = await session.workingRep(alice, bob);
|
||||
console.log('Working representation returned:', workingRepresentationOfBob);
|
||||
|
||||
console.log('Example completed successfully!');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Error running get_representation example:', err);
|
||||
});
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { Honcho } from '../src';
|
||||
|
||||
/**
|
||||
* Example demonstrating search functionality across different scopes.
|
||||
*
|
||||
* This creates sessions with special keywords and demonstrates
|
||||
* searching at session, workspace, and peer levels.
|
||||
*/
|
||||
async function main() {
|
||||
console.log('Creating Honcho client...');
|
||||
// Create a Honcho client with the default workspace
|
||||
const honcho = new Honcho({
|
||||
environment: 'local'
|
||||
});
|
||||
|
||||
console.log('Creating peers...');
|
||||
const peers = [
|
||||
honcho.peer('alice'),
|
||||
honcho.peer('bob'),
|
||||
honcho.peer('charlie'),
|
||||
];
|
||||
|
||||
// Create a new session
|
||||
const sessionId = `search_test_${crypto.randomUUID()}`;
|
||||
const session = honcho.session(sessionId);
|
||||
console.log(`Created session: ${sessionId}`);
|
||||
|
||||
// Create a message with our special keyword
|
||||
const keyword = `~special-${crypto.randomUUID()}~`;
|
||||
console.log(`Using keyword: ${keyword}`);
|
||||
await session.addMessages(peers[0].message(`I am a ${keyword} message`));
|
||||
|
||||
console.log('Generating random messages...');
|
||||
// Generate some random messages from alice, bob, and charlie and add them to the session
|
||||
const messages = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const randomPeer = peers[Math.floor(Math.random() * peers.length)];
|
||||
messages.push(
|
||||
randomPeer.message(`Hello from ${randomPeer.id}! This is message ${i}.`)
|
||||
);
|
||||
}
|
||||
|
||||
await session.addMessages(messages);
|
||||
console.log('Added random messages to session.');
|
||||
|
||||
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(` - ${message.content} (from ${message.peer_id})`);
|
||||
}
|
||||
|
||||
const alice = peers[0];
|
||||
|
||||
// Add a different message to alice's global representation
|
||||
const differentKeyword = `~different-${crypto.randomUUID()}~`;
|
||||
console.log(`Using different keyword: ${differentKeyword}`);
|
||||
await alice.addMessages(alice.message(`I am a ${differentKeyword} message`));
|
||||
|
||||
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(` - ${message.content} (from ${message.peer_id})`);
|
||||
}
|
||||
|
||||
console.log('Searching alice\'s global representation...');
|
||||
// Search alice's global representation for the different message
|
||||
const aliceSearchResults = await alice.search(differentKeyword);
|
||||
console.log(`Alice search returned ${aliceSearchResults.total} results:`);
|
||||
for await (const message of aliceSearchResults) {
|
||||
console.log(` - ${message.content} (from ${message.peer_id})`);
|
||||
}
|
||||
|
||||
console.log('Example completed successfully!');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Error running search example:', err);
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
export default {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src', '<rootDir>/__tests__'],
|
||||
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(spec|test).ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
},
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.ts',
|
||||
'!src/**/*.d.ts',
|
||||
],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
setupFilesAfterEnv: ['<rootDir>/__tests__/setup.ts'],
|
||||
moduleNameMapper: {
|
||||
'^@honcho-ai/core$': '<rootDir>/__tests__/__mocks__/@honcho-ai/core.ts',
|
||||
},
|
||||
testTimeout: 10000,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "@honcho-ai/sdk",
|
||||
"version": "1.1.0",
|
||||
"description": "Official DX Optimized TypeScript SDK for Honcho",
|
||||
"author": "Plastic Labs <hello@plasticlabs.ai>",
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.json",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^24.0.1",
|
||||
"@honcho-ai/core": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"eslint": "^8.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
import HonchoCore from '@honcho-ai/core';
|
||||
import { Page } from './pagination';
|
||||
import { Peer } from './peer';
|
||||
import { Session } from './session';
|
||||
|
||||
/**
|
||||
* Main client for the Honcho TypeScript SDK.
|
||||
* Provides access to peers, sessions, and workspace operations.
|
||||
*/
|
||||
export class Honcho {
|
||||
private _client: InstanceType<typeof HonchoCore>;
|
||||
readonly workspaceId: string;
|
||||
|
||||
/**
|
||||
* Initialize the Honcho 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>;
|
||||
}) {
|
||||
this.workspaceId = options.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;
|
||||
this._client.workspaces.getOrCreate({ id: this.workspaceId })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a peer with the given ID.
|
||||
*/
|
||||
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');
|
||||
}
|
||||
return new Peer(id, this, options?.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all peers in the current workspace.
|
||||
*/
|
||||
async getPeers(): Promise<Page<Peer>> {
|
||||
const peersPage = await this._client.workspaces.peers.list(this.workspaceId);
|
||||
return new Page(peersPage, (peer: any) => new Peer(peer.id, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a session with the given ID.
|
||||
*/
|
||||
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');
|
||||
}
|
||||
return new Session(id, this, options?.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sessions in the current workspace.
|
||||
*/
|
||||
async getSessions(): Promise<Page<Session>> {
|
||||
const sessionsPage = await this._client.workspaces.sessions.list(this.workspaceId);
|
||||
return new Page(sessionsPage, (session: any) => new Session(session.id, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for the current workspace.
|
||||
*/
|
||||
async getMetadata(): Promise<Record<string, unknown>> {
|
||||
const workspace = await this._client.workspaces.getOrCreate({ id: this.workspaceId });
|
||||
return workspace.metadata || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set metadata for the current workspace.
|
||||
*/
|
||||
async setMetadata(metadata: Record<string, unknown>): Promise<void> {
|
||||
await this._client.workspaces.update(this.workspaceId, { metadata });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all workspace IDs from the Honcho instance.
|
||||
*/
|
||||
async getWorkspaces(): Promise<string[]> {
|
||||
const workspacesPage = await this._client.workspaces.list();
|
||||
const ids: string[] = [];
|
||||
for await (const workspace of workspacesPage) {
|
||||
ids.push(workspace.id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for messages in the current workspace.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
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._client.workspaces.search(this.workspaceId, { body: query });
|
||||
return new Page(messagesPage);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// Main entry point for the Honcho TypeScript SDK
|
||||
// Exports all main classes and types
|
||||
|
||||
export { Honcho } from './client';
|
||||
export { Peer } from './peer';
|
||||
export { Session, SessionPeerConfig } from './session';
|
||||
export { SessionContext } from './session_context';
|
||||
export { Page } from './pagination';
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Generic paginated result wrapper for Honcho SDK.
|
||||
* Provides async iteration and transformation capabilities.
|
||||
*/
|
||||
export class Page<T> implements AsyncIterable<T> {
|
||||
private _originalPage: any;
|
||||
private _transformFunc?: (item: any) => T;
|
||||
|
||||
/**
|
||||
* Initialize a new Page.
|
||||
*/
|
||||
constructor(originalPage: any, transformFunc?: (item: any) => T) {
|
||||
this._originalPage = originalPage;
|
||||
this._transformFunc = transformFunc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async iterator for the page's items.
|
||||
*/
|
||||
async *[Symbol.asyncIterator](): AsyncIterator<T> {
|
||||
// Handle different page structure formats
|
||||
const items = this._originalPage.items || this._originalPage.data || [];
|
||||
for (const item of items) {
|
||||
yield this._transformFunc ? this._transformFunc(item) : item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item by index.
|
||||
*/
|
||||
async get(index: number): Promise<T> {
|
||||
if (!this._originalPage?.get || typeof this._originalPage.get !== 'function') {
|
||||
throw new Error('Original page does not support indexed access');
|
||||
}
|
||||
const item = await this._originalPage.get(index);
|
||||
return this._transformFunc ? this._transformFunc(item) : item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the page.
|
||||
*/
|
||||
get size(): number {
|
||||
return this._originalPage?.size ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of items.
|
||||
*/
|
||||
get total(): number {
|
||||
return this._originalPage?.total ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data as a list.
|
||||
*/
|
||||
async data(): Promise<T[]> {
|
||||
const items = this._originalPage.items || this._originalPage.data || [];
|
||||
const data = typeof items === 'function' ? await items() : items;
|
||||
return this._transformFunc ? data.map(this._transformFunc) : data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's a next page.
|
||||
*/
|
||||
get hasNextPage(): boolean {
|
||||
return this._originalPage?.hasNextPage ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next page.
|
||||
*/
|
||||
async nextPage(): Promise<Page<T> | null> {
|
||||
const nextPage = await this._originalPage.nextPage();
|
||||
if (!nextPage) return null;
|
||||
return new Page(nextPage, this._transformFunc);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
import { Session } from './session';
|
||||
import { Page } from './pagination';
|
||||
import type { Honcho } from './client';
|
||||
|
||||
/**
|
||||
* Represents a peer in the Honcho system.
|
||||
*/
|
||||
export class Peer {
|
||||
/**
|
||||
* Unique identifier for this peer.
|
||||
*/
|
||||
readonly id: string;
|
||||
private _honcho: Honcho;
|
||||
|
||||
/**
|
||||
* Initialize a new Peer.
|
||||
*/
|
||||
constructor(id: string, honcho: Honcho, config?: Record<string, unknown>) {
|
||||
this.id = id;
|
||||
this._honcho = honcho;
|
||||
|
||||
if (config) {
|
||||
this._honcho['_client'].workspaces.peers.getOrCreate(
|
||||
this._honcho.workspaceId,
|
||||
{ id: this.id, configuration: config }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the peer's representation with a natural language question.
|
||||
*/
|
||||
async chat(queries: string | string[], opts?: {
|
||||
stream?: boolean;
|
||||
target?: string | Peer;
|
||||
sessionId?: string;
|
||||
}): Promise<string | null> {
|
||||
const response = await this._honcho['_client'].workspaces.peers.chat(
|
||||
this._honcho.workspaceId,
|
||||
this.id,
|
||||
{ queries, stream: opts?.stream, target: opts?.target ? (typeof opts.target === 'string' ? opts.target : opts.target.id) : undefined, session_id: opts?.sessionId },
|
||||
);
|
||||
if (!response.content || response.content === 'None') {
|
||||
return null;
|
||||
}
|
||||
return response.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sessions this peer is a member of.
|
||||
*/
|
||||
async getSessions(): Promise<Page<Session>> {
|
||||
const sessionsPage = await this._honcho['_client'].workspaces.peers.sessions.list(
|
||||
this.id,
|
||||
this._honcho.workspaceId,
|
||||
);
|
||||
return new Page(sessionsPage, (session: any) => new Session(session.id, this._honcho));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add messages or content to this peer's global representation.
|
||||
*/
|
||||
async addMessages(content: string | any | any[]): Promise<void> {
|
||||
let messages: any[];
|
||||
if (typeof content === 'string') {
|
||||
messages = [{ peer_id: this.id, content, metadata: undefined }];
|
||||
} else if (Array.isArray(content)) {
|
||||
messages = content.map((msg) => ({
|
||||
peer_id: msg.peerId || this.id,
|
||||
content: msg.content,
|
||||
metadata: msg.metadata,
|
||||
}));
|
||||
} else {
|
||||
messages = [{
|
||||
peer_id: content.peerId || this.id,
|
||||
content: content.content,
|
||||
metadata: content.metadata,
|
||||
}];
|
||||
}
|
||||
await this._honcho['_client'].workspaces.peers.messages.create(
|
||||
this._honcho.workspaceId,
|
||||
this.id,
|
||||
{ messages }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get messages saved to this peer outside of a session with optional filtering.
|
||||
*/
|
||||
async getMessages(opts?: { filter?: Record<string, unknown> }): Promise<Page<any>> {
|
||||
const messagesPage = await this._honcho['_client'].workspaces.peers.messages.list(
|
||||
this.id,
|
||||
this._honcho.workspaceId,
|
||||
opts?.filter,
|
||||
);
|
||||
return new Page(messagesPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message attributed to this peer.
|
||||
*/
|
||||
message(content: string, opts?: { metadata?: Record<string, unknown> }): any {
|
||||
return {
|
||||
peerId: this.id,
|
||||
content,
|
||||
metadata: opts?.metadata,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current metadata for this peer.
|
||||
*/
|
||||
async getMetadata(): Promise<Record<string, unknown>> {
|
||||
const peer = await this._honcho['_client'].workspaces.peers.getOrCreate(
|
||||
this._honcho.workspaceId,
|
||||
{ id: this.id }
|
||||
);
|
||||
return peer.metadata || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the metadata for this peer.
|
||||
*/
|
||||
async setMetadata(metadata: Record<string, unknown>): Promise<void> {
|
||||
await this._honcho['_client'].workspaces.peers.update(
|
||||
this._honcho.workspaceId,
|
||||
this.id,
|
||||
{ metadata },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for messages in this peer's global representation.
|
||||
*
|
||||
* Makes an API call to search for messages in this peer's global representation.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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,
|
||||
this.id,
|
||||
{ query: query }
|
||||
);
|
||||
return new Page(messagesPage);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
import { Peer } from './peer';
|
||||
import { Page } from './pagination';
|
||||
import { SessionContext } from './session_context';
|
||||
import type { Honcho } from './client';
|
||||
|
||||
|
||||
export class SessionPeerConfig {
|
||||
observe_others: boolean;
|
||||
observe_me: boolean;
|
||||
|
||||
constructor(opts?: { observe_others?: boolean; observe_me?: boolean }) {
|
||||
this.observe_others = opts?.observe_others ?? false;
|
||||
this.observe_me = opts?.observe_me ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a session in Honcho.
|
||||
*/
|
||||
export class Session {
|
||||
/**
|
||||
* Unique identifier for this session.
|
||||
*/
|
||||
readonly id: string;
|
||||
private _honcho: Honcho;
|
||||
|
||||
/**
|
||||
* Initialize a new Session.
|
||||
*/
|
||||
constructor(id: string, honcho: Honcho, config?: Record<string, unknown>) {
|
||||
this.id = id;
|
||||
this._honcho = honcho;
|
||||
|
||||
if (config) {
|
||||
this._honcho['_client'].workspaces.sessions.getOrCreate(
|
||||
this._honcho.workspaceId,
|
||||
{ id: this.id, configuration: config }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add peers to this session.
|
||||
*/
|
||||
async addPeers(peers: string | Peer | Array<string | Peer> | [string | Peer, SessionPeerConfig] | Array<[string | Peer, SessionPeerConfig]> | Array<string | Peer | [string | Peer, SessionPeerConfig]>): Promise<void> {
|
||||
const peerDict: Record<string, SessionPeerConfig> = {};
|
||||
if (!Array.isArray(peers)) {
|
||||
peers = [peers];
|
||||
}
|
||||
for (const peer of peers) {
|
||||
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 };
|
||||
} else if (Array.isArray(peer)) {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.peers.add(
|
||||
this._honcho.workspaceId,
|
||||
this.id,
|
||||
peerDict
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the complete peer list for this session.
|
||||
*/
|
||||
async setPeers(peers: string | Peer | Array<string | Peer> | [string | Peer, SessionPeerConfig] | Array<[string | Peer, SessionPeerConfig]> | Array<string | Peer | [string | Peer, SessionPeerConfig]>): Promise<void> {
|
||||
const peerDict: Record<string, SessionPeerConfig> = {};
|
||||
if (!Array.isArray(peers)) {
|
||||
peers = [peers];
|
||||
}
|
||||
for (const peer of peers) {
|
||||
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 };
|
||||
} else if (Array.isArray(peer)) {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.peers.set(
|
||||
this._honcho.workspaceId,
|
||||
this.id,
|
||||
peerDict
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove peers from this session.
|
||||
*/
|
||||
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, 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.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration for a peer in this session.
|
||||
*/
|
||||
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,
|
||||
this.id,
|
||||
peerId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration for a peer in this session.
|
||||
*/
|
||||
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,
|
||||
this.id,
|
||||
peerId,
|
||||
{
|
||||
observe_others: config.observe_others,
|
||||
observe_me: config.observe_me
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more messages to this session.
|
||||
*/
|
||||
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,
|
||||
this.id,
|
||||
{
|
||||
messages: msgs.map((msg) => ({
|
||||
peer_id: msg.peerId,
|
||||
content: msg.content,
|
||||
metadata: msg.metadata,
|
||||
}))
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get messages from this session with optional filtering.
|
||||
*/
|
||||
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, this.id, opts?.filter);
|
||||
return new Page(messagesPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for this session.
|
||||
*/
|
||||
async getMetadata(): Promise<Record<string, unknown>> {
|
||||
const session = await (this._honcho['_client'] as any).workspaces.sessions.getOrCreate(
|
||||
this._honcho.workspaceId,
|
||||
{ id: this.id }
|
||||
);
|
||||
return session.metadata || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set metadata for this session.
|
||||
*/
|
||||
async setMetadata(metadata: Record<string, unknown>): Promise<void> {
|
||||
await (this._honcho['_client'] as any).workspaces.sessions.update(this._honcho.workspaceId, this.id, { metadata });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get optimized context for this session within a token limit.
|
||||
*/
|
||||
async getContext(opts?: { 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
|
||||
}
|
||||
);
|
||||
return new SessionContext(this.id, context.messages, context.summary || '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for messages in this 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.
|
||||
*/
|
||||
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'] as any).workspaces.sessions.search(
|
||||
this._honcho.workspaceId,
|
||||
this.id,
|
||||
{ query: query }
|
||||
);
|
||||
return new Page(messagesPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current working representation of the 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.
|
||||
*/
|
||||
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) : undefined;
|
||||
|
||||
return await (this._honcho['_client'] as any).workspaces.peers.workingRepresentation(
|
||||
this._honcho.workspaceId,
|
||||
peerId,
|
||||
{
|
||||
session_id: this.id,
|
||||
target: targetId
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
import { Peer } from './peer';
|
||||
|
||||
/**
|
||||
* Represents the context of a session containing a curated list of messages.
|
||||
*
|
||||
* The SessionContext provides methods to convert message history into formats
|
||||
* compatible with different LLM providers while staying within token limits
|
||||
* and providing optimal conversation context.
|
||||
*/
|
||||
export class SessionContext {
|
||||
/**
|
||||
* ID of the session this context belongs to.
|
||||
*/
|
||||
readonly sessionId: string;
|
||||
|
||||
/**
|
||||
* List of Message objects representing the conversation context.
|
||||
*/
|
||||
readonly messages: any[];
|
||||
|
||||
/**
|
||||
* Summary of the session history prior to the message cutoff.
|
||||
*/
|
||||
readonly summary: string;
|
||||
|
||||
/**
|
||||
* Initialize a new SessionContext.
|
||||
*
|
||||
* @param sessionId ID of the session this context belongs to
|
||||
* @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 = '') {
|
||||
this.sessionId = sessionId;
|
||||
this.messages = messages;
|
||||
this.summary = summary || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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 OpenAI format, where each dictionary contains
|
||||
* "role" and "content" keys suitable for the OpenAI API
|
||||
*/
|
||||
toOpenAI(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,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the context to Anthropic-compatible message format.
|
||||
*
|
||||
* Transforms the message history into the format expected by Anthropic's
|
||||
* Claude API.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of messages in the context.
|
||||
*/
|
||||
get length(): number {
|
||||
return this.messages.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the SessionContext.
|
||||
*/
|
||||
toString(): string {
|
||||
return `SessionContext(messages=${this.messages.length})`;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"lib": [
|
||||
"es6",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
}
|
||||
62
src/crud.py
62
src/crud.py
|
|
@ -372,8 +372,13 @@ async def get_or_create_session(
|
|||
f"Cannot create session {session.name} with {len(session.peer_names)} peers. Maximum allowed is {settings.SESSION_PEERS_LIMIT} peers per session."
|
||||
)
|
||||
|
||||
# Create honcho session
|
||||
# Get or create workspace to ensure it exists
|
||||
await get_or_create_workspace(
|
||||
db,
|
||||
schemas.WorkspaceCreate(name=workspace_name),
|
||||
)
|
||||
|
||||
# Create honcho session
|
||||
honcho_session = models.Session(
|
||||
workspace_name=workspace_name,
|
||||
name=session.name,
|
||||
|
|
@ -931,42 +936,53 @@ async def set_peer_config(
|
|||
db: AsyncSession,
|
||||
workspace_name: str,
|
||||
session_name: str,
|
||||
peer_id: str,
|
||||
peer_name: str,
|
||||
config: schemas.SessionPeerConfig,
|
||||
) -> None:
|
||||
"""
|
||||
Set the configuration for a peer in a session.
|
||||
Set the configuration for a specific peer in a session.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
workspace_name: Name of the workspace
|
||||
session_name: Name of the session
|
||||
peer_id: Name of the peer
|
||||
config: Configuration for the peer
|
||||
|
||||
Returns:
|
||||
True if the peer config was set successfully
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundException: If the session or peer does not exist
|
||||
peer_name: Name of the peer
|
||||
config: The peer configuration to set
|
||||
"""
|
||||
# Get row from session_peer table
|
||||
stmt = select(models.SessionPeer).where(
|
||||
models.SessionPeer.workspace_name == workspace_name,
|
||||
models.SessionPeer.session_name == session_name,
|
||||
models.SessionPeer.peer_name == peer_id,
|
||||
# First, get the session and peer to ensure they exist
|
||||
await get_session(db, session_name, workspace_name)
|
||||
await get_peer(db, workspace_name, schemas.PeerCreate(name=peer_name))
|
||||
|
||||
# Check if a SessionPeer entry already exists
|
||||
stmt = (
|
||||
select(models.SessionPeer)
|
||||
.where(models.SessionPeer.session_name == session_name)
|
||||
.where(models.SessionPeer.peer_name == peer_name)
|
||||
.where(models.SessionPeer.workspace_name == workspace_name)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
session_peer = result.scalar_one_or_none()
|
||||
|
||||
if session_peer is None:
|
||||
raise ResourceNotFoundException(
|
||||
f"Session peer {peer_id} not found in session {session_name} in workspace {workspace_name}"
|
||||
)
|
||||
update_data = config.model_dump(exclude_none=True)
|
||||
|
||||
# Update peer config
|
||||
session_peer.configuration["observe_others"] = config.observe_others
|
||||
session_peer.configuration["observe_me"] = config.observe_me
|
||||
if session_peer:
|
||||
# Update existing configuration
|
||||
if session_peer.configuration:
|
||||
# Create a new dictionary and update it to ensure SQLAlchemy tracks the change
|
||||
new_config = session_peer.configuration.copy()
|
||||
new_config.update(update_data)
|
||||
session_peer.configuration = new_config
|
||||
else:
|
||||
session_peer.configuration = update_data
|
||||
else:
|
||||
# Create a new SessionPeer entry
|
||||
session_peer = models.SessionPeer(
|
||||
session_name=session_name,
|
||||
peer_name=peer_name,
|
||||
workspace_name=workspace_name,
|
||||
configuration=update_data,
|
||||
)
|
||||
db.add(session_peer)
|
||||
|
||||
await db.commit()
|
||||
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ async def set_peer_config(
|
|||
db,
|
||||
workspace_name=workspace_id,
|
||||
session_name=session_id,
|
||||
peer_id=peer_id,
|
||||
peer_name=peer_id,
|
||||
config=config,
|
||||
)
|
||||
logger.info(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
# Add the SDK src to the path to allow imports
|
||||
sdk_src_path = Path(__file__).parent.parent.parent / "sdks" / "python" / "src"
|
||||
sys.path.insert(0, str(sdk_src_path))
|
||||
|
||||
# This is a bit of a hack to make the main conftest discoverable
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from sdks.python.src.honcho.async_client.client import AsyncHoncho # noqa: E402
|
||||
from sdks.python.src.honcho.client import Honcho # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def honcho_sync_test_client(client: TestClient) -> Honcho:
|
||||
"""
|
||||
Returns a Honcho SDK client configured to talk to the test API.
|
||||
"""
|
||||
http_client = httpx.Client(
|
||||
transport=client._transport, # pyright: ignore
|
||||
base_url=str(client.base_url),
|
||||
headers=client.headers,
|
||||
)
|
||||
|
||||
honcho_client = Honcho(
|
||||
workspace_id="sdk-test-workspace-sync", http_client=http_client
|
||||
)
|
||||
return honcho_client
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def honcho_async_test_client(
|
||||
client: TestClient,
|
||||
) -> AsyncHoncho:
|
||||
"""
|
||||
Returns an async Honcho SDK client configured to talk to the test API.
|
||||
"""
|
||||
async_http_client = httpx.AsyncClient(
|
||||
transport=httpx.ASGITransport(app=client.app),
|
||||
base_url=str(client.base_url),
|
||||
headers=client.headers,
|
||||
)
|
||||
|
||||
http_client = httpx.Client(
|
||||
transport=client._transport, # pyright: ignore
|
||||
base_url=str(client.base_url),
|
||||
headers=client.headers,
|
||||
)
|
||||
|
||||
honcho_client = AsyncHoncho(
|
||||
workspace_id="sdk-test-workspace-async",
|
||||
async_http_client=async_http_client,
|
||||
http_client=http_client,
|
||||
)
|
||||
return honcho_client
|
||||
|
||||
|
||||
@pytest.fixture(params=["sync", "async"])
|
||||
def client_fixture(
|
||||
request: pytest.FixtureRequest,
|
||||
honcho_sync_test_client: Honcho,
|
||||
honcho_async_test_client: AsyncHoncho,
|
||||
) -> tuple[Honcho | AsyncHoncho, str]:
|
||||
if request.param == "sync":
|
||||
return honcho_sync_test_client, "sync"
|
||||
return honcho_async_test_client, "async"
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
import sys
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
# Add the SDK src to the path to allow imports
|
||||
sdk_src_path = Path(__file__).parent.parent.parent / "sdks" / "python" / "src"
|
||||
sys.path.insert(0, str(sdk_src_path))
|
||||
|
||||
from sdks.python.src.honcho.client import Honcho # noqa: E402
|
||||
from sdks.python.src.honcho.peer import Peer # noqa: E402
|
||||
from sdks.python.src.honcho.session import Session, SessionPeerConfig # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def honcho_test_client(client: TestClient) -> Generator[Honcho, None, None]:
|
||||
"""
|
||||
Returns a Honcho SDK client configured to talk to the test API.
|
||||
"""
|
||||
http_client = httpx.Client(
|
||||
transport=client._transport, # pyright: ignore
|
||||
base_url=str(client.base_url),
|
||||
headers=client.headers,
|
||||
)
|
||||
|
||||
honcho_client = Honcho(workspace_id="sdk-test-workspace", http_client=http_client)
|
||||
yield honcho_client
|
||||
|
||||
|
||||
def test_peer_operations(honcho_test_client: Honcho):
|
||||
"""
|
||||
Tests creation and metadata operations for peers.
|
||||
"""
|
||||
peers_page = honcho_test_client.get_peers()
|
||||
assert len(list(peers_page)) == 0
|
||||
|
||||
peer = honcho_test_client.peer(id="test-peer-1")
|
||||
assert isinstance(peer, Peer)
|
||||
|
||||
# Peer is created on first use
|
||||
metadata = peer.get_metadata()
|
||||
assert metadata == {}
|
||||
|
||||
peers_page = honcho_test_client.get_peers()
|
||||
assert len(list(peers_page)) == 1
|
||||
|
||||
peer.set_metadata({"foo": "bar"})
|
||||
metadata = peer.get_metadata()
|
||||
assert metadata == {"foo": "bar"}
|
||||
|
||||
|
||||
def test_session_operations(honcho_test_client: Honcho):
|
||||
"""
|
||||
Tests creation, peer management, and metadata for sessions.
|
||||
"""
|
||||
sessions_page = honcho_test_client.get_sessions()
|
||||
assert len(list(sessions_page)) == 0
|
||||
|
||||
session = honcho_test_client.session(id="test-session-1")
|
||||
assert isinstance(session, Session)
|
||||
|
||||
# Session is created on first use
|
||||
metadata = session.get_metadata()
|
||||
assert metadata == {}
|
||||
|
||||
sessions_page = honcho_test_client.get_sessions()
|
||||
assert len(list(sessions_page)) == 1
|
||||
|
||||
session.set_metadata({"bar": "baz"})
|
||||
metadata = session.get_metadata()
|
||||
assert metadata == {"bar": "baz"}
|
||||
|
||||
assistant = honcho_test_client.peer(id="assistant")
|
||||
user = honcho_test_client.peer(id="user")
|
||||
|
||||
session.add_peers(
|
||||
[assistant, (user, SessionPeerConfig(observe_others=False, observe_me=False))]
|
||||
)
|
||||
|
||||
session_peers = session.get_peers()
|
||||
assert len(session_peers) == 2
|
||||
|
||||
|
||||
def test_message_and_chat_operations(honcho_test_client: Honcho):
|
||||
"""
|
||||
Tests adding messages to a session and using the chat functionality.
|
||||
"""
|
||||
session = honcho_test_client.session(id="test-chat-session")
|
||||
assistant = honcho_test_client.peer(id="chat-assistant")
|
||||
user = honcho_test_client.peer(id="chat-user")
|
||||
|
||||
session.add_messages(
|
||||
[
|
||||
user.message("What is the capital of France?"),
|
||||
assistant.message("The capital of France is Paris."),
|
||||
]
|
||||
)
|
||||
|
||||
messages = session.get_messages()
|
||||
assert len(list(messages)) == 2
|
||||
|
||||
# This is a mock response from the agent
|
||||
_response = user.chat("What did I ask about?")
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from sdks.python.src.honcho.async_client.client import AsyncHoncho
|
||||
from sdks.python.src.honcho.async_client.pagination import AsyncPage
|
||||
from sdks.python.src.honcho.async_client.peer import AsyncPeer
|
||||
from sdks.python.src.honcho.async_client.session import AsyncSession
|
||||
from sdks.python.src.honcho.client import Honcho
|
||||
from sdks.python.src.honcho.pagination import SyncPage
|
||||
from sdks.python.src.honcho.peer import Peer
|
||||
from sdks.python.src.honcho.session import Session
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_init(
|
||||
client_fixture: tuple[Honcho | AsyncHoncho, str], client: TestClient
|
||||
):
|
||||
"""
|
||||
Tests that the Honcho SDK clients can be initialized and that they create a workspace.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
assert honcho_client.workspace_id == "sdk-test-workspace-async"
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
assert honcho_client.workspace_id == "sdk-test-workspace-sync"
|
||||
|
||||
# Check all pages to find the workspace
|
||||
found_workspace = False
|
||||
page = 1
|
||||
|
||||
while not found_workspace:
|
||||
res = client.post("/v2/workspaces/list", json={}, params={"page": page})
|
||||
assert res.status_code == 200
|
||||
|
||||
data = res.json()
|
||||
workspaces = data["items"]
|
||||
workspace_ids = [w["id"] for w in workspaces]
|
||||
|
||||
if honcho_client.workspace_id in workspace_ids:
|
||||
found_workspace = True
|
||||
break
|
||||
|
||||
# Check if there are more pages
|
||||
if page >= data.get("pages", 1) or len(workspaces) == 0:
|
||||
break
|
||||
|
||||
page += 1
|
||||
|
||||
assert found_workspace, (
|
||||
f"Workspace {honcho_client.workspace_id} not found in any page of results"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_workspace_metadata(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests getting and setting metadata on a workspace.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
metadata = await honcho_client.get_metadata()
|
||||
assert metadata == {}
|
||||
await honcho_client.set_metadata({"foo": "bar"})
|
||||
metadata = await honcho_client.get_metadata()
|
||||
assert metadata == {"foo": "bar"}
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
metadata = honcho_client.get_metadata()
|
||||
assert metadata == {}
|
||||
honcho_client.set_metadata({"foo": "bar"})
|
||||
metadata = honcho_client.get_metadata()
|
||||
assert metadata == {"foo": "bar"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_workspaces(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests listing available workspaces.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
workspaces = await honcho_client.get_workspaces()
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
workspaces = honcho_client.get_workspaces()
|
||||
|
||||
assert isinstance(workspaces, list)
|
||||
assert honcho_client.workspace_id in workspaces
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_list_peers_and_sessions(
|
||||
client_fixture: tuple[Honcho | AsyncHoncho, str],
|
||||
):
|
||||
"""
|
||||
Tests listing peers and sessions at the client level.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
peers_page = await honcho_client.get_peers()
|
||||
assert isinstance(peers_page, AsyncPage)
|
||||
assert len(peers_page.items) == 0
|
||||
|
||||
sessions_page = await honcho_client.get_sessions()
|
||||
assert isinstance(sessions_page, AsyncPage)
|
||||
assert len(sessions_page.items) == 0
|
||||
|
||||
peer = await honcho_client.peer(id="test-peer-client")
|
||||
assert isinstance(peer, AsyncPeer)
|
||||
await peer.get_metadata() # Creates the peer
|
||||
|
||||
peers_page = await honcho_client.get_peers()
|
||||
assert len(peers_page.items) == 1
|
||||
|
||||
session = await honcho_client.session(id="test-session-client")
|
||||
assert isinstance(session, AsyncSession)
|
||||
await session.get_metadata() # Creates the session
|
||||
|
||||
sessions_page = await honcho_client.get_sessions()
|
||||
assert len(sessions_page.items) == 1
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
peers_page = honcho_client.get_peers()
|
||||
assert isinstance(peers_page, SyncPage)
|
||||
assert len(list(peers_page)) == 0
|
||||
|
||||
sessions_page = honcho_client.get_sessions()
|
||||
assert isinstance(sessions_page, SyncPage)
|
||||
assert len(list(sessions_page)) == 0
|
||||
|
||||
peer = honcho_client.peer(id="test-peer-client")
|
||||
assert isinstance(peer, Peer)
|
||||
peer.get_metadata()
|
||||
|
||||
peers_page = honcho_client.get_peers()
|
||||
assert len(list(peers_page)) == 1
|
||||
|
||||
session = honcho_client.session(id="test-session-client")
|
||||
assert isinstance(session, Session)
|
||||
session.get_metadata()
|
||||
|
||||
sessions_page = honcho_client.get_sessions()
|
||||
assert len(list(sessions_page)) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_workspace_search(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests searching for messages within a workspace.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
search_query = "a unique message for workspace search"
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
session = await honcho_client.session(id="search-session-ws")
|
||||
assert isinstance(session, AsyncSession)
|
||||
user = await honcho_client.peer(id="search-user-ws")
|
||||
assert isinstance(user, AsyncPeer)
|
||||
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
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="search-session-ws")
|
||||
assert isinstance(session, Session)
|
||||
user = honcho_client.peer(id="search-user-ws")
|
||||
assert isinstance(user, Peer)
|
||||
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
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
import pytest
|
||||
|
||||
from sdks.python.src.honcho.async_client.client import AsyncHoncho
|
||||
from sdks.python.src.honcho.async_client.peer import AsyncPeer
|
||||
from sdks.python.src.honcho.client import Honcho
|
||||
from sdks.python.src.honcho.peer import Peer
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_peer_metadata(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests creation and metadata operations for peers.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
peer = await honcho_client.peer(id="test-peer-meta")
|
||||
assert isinstance(peer, AsyncPeer)
|
||||
|
||||
metadata = await peer.get_metadata()
|
||||
assert metadata == {}
|
||||
|
||||
await peer.set_metadata({"foo": "bar"})
|
||||
metadata = await peer.get_metadata()
|
||||
assert metadata == {"foo": "bar"}
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
peer = honcho_client.peer(id="test-peer-meta")
|
||||
assert isinstance(peer, Peer)
|
||||
|
||||
metadata = peer.get_metadata()
|
||||
assert metadata == {}
|
||||
|
||||
peer.set_metadata({"foo": "bar"})
|
||||
metadata = peer.get_metadata()
|
||||
assert metadata == {"foo": "bar"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_peer_add_and_get_messages(
|
||||
client_fixture: tuple[Honcho | AsyncHoncho, str],
|
||||
):
|
||||
"""
|
||||
Tests adding and getting messages from a peer's global representation.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
peer = await honcho_client.peer(id="test-peer-gms")
|
||||
assert isinstance(peer, AsyncPeer)
|
||||
|
||||
await peer.add_messages("a simple string message")
|
||||
message_obj = peer.message("a message object")
|
||||
await peer.add_messages(message_obj)
|
||||
await peer.add_messages([peer.message("a message in a list")])
|
||||
|
||||
messages_page = await peer.get_messages()
|
||||
messages = messages_page.items
|
||||
assert len(messages) == 3
|
||||
contents = {m.content for m in messages}
|
||||
assert "a simple string message" in contents
|
||||
assert "a message object" in contents
|
||||
assert "a message in a list" in contents
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
peer = honcho_client.peer(id="test-peer-gms")
|
||||
assert isinstance(peer, Peer)
|
||||
|
||||
peer.add_messages("a simple string message")
|
||||
message_obj = peer.message("a message object")
|
||||
peer.add_messages(message_obj)
|
||||
peer.add_messages([peer.message("a message in a list")])
|
||||
|
||||
messages_page = peer.get_messages()
|
||||
messages = list(messages_page)
|
||||
assert len(messages) == 3
|
||||
contents = {m.content for m in messages}
|
||||
assert "a simple string message" in contents
|
||||
assert "a message object" in contents
|
||||
assert "a message in a list" in contents
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_peer_chat(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests the chat functionality of a peer, including target and session scoping.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
question = "What is my name?"
|
||||
answer = "Your name is test-peer-chat"
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
peer = await honcho_client.peer(id="test-peer-chat")
|
||||
assert isinstance(peer, AsyncPeer)
|
||||
await peer.add_messages(answer)
|
||||
|
||||
_response = await peer.chat(question)
|
||||
|
||||
# Test target
|
||||
target_peer = await honcho_client.peer(id="target-peer-chat")
|
||||
_response = await peer.chat(
|
||||
"Does the assistant know my name?", target=target_peer
|
||||
)
|
||||
|
||||
# Test session_id
|
||||
session = await honcho_client.session(id="chat-session-scope")
|
||||
await session.add_messages(peer.message(answer))
|
||||
_response = await peer.chat(question, session_id=session.id)
|
||||
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
peer = honcho_client.peer(id="test-peer-chat")
|
||||
assert isinstance(peer, Peer)
|
||||
peer.add_messages(answer)
|
||||
|
||||
_response = peer.chat(question)
|
||||
|
||||
# Test target
|
||||
target_peer = honcho_client.peer(id="target-peer-chat")
|
||||
_response = peer.chat("Does the assistant know my name?", target=target_peer)
|
||||
|
||||
# Test session_id
|
||||
session = honcho_client.session(id="chat-session-scope")
|
||||
session.add_messages(peer.message(answer))
|
||||
_response = peer.chat(question, session_id=session.id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_peer_get_sessions(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests retrieving the sessions a peer is a member of.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
peer = await honcho_client.peer(id="test-peer-sessions")
|
||||
session1 = await honcho_client.session(id="s1")
|
||||
session2 = await honcho_client.session(id="s2")
|
||||
|
||||
await session1.add_peers(peer)
|
||||
await session2.add_peers(peer)
|
||||
|
||||
sessions_page = await peer.get_sessions()
|
||||
sessions = sessions_page.items
|
||||
assert len(sessions) == 2
|
||||
session_ids = {s.id for s in sessions}
|
||||
assert "s1" in session_ids
|
||||
assert "s2" in session_ids
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
peer = honcho_client.peer(id="test-peer-sessions")
|
||||
session1 = honcho_client.session(id="s1")
|
||||
session2 = honcho_client.session(id="s2")
|
||||
|
||||
session1.add_peers(peer)
|
||||
session2.add_peers(peer)
|
||||
|
||||
sessions_page = peer.get_sessions()
|
||||
sessions = list(sessions_page)
|
||||
assert len(sessions) == 2
|
||||
session_ids = {s.id for s in sessions}
|
||||
assert "s1" in session_ids
|
||||
assert "s2" in session_ids
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_peer_search(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests searching for messages in a peer's global representation.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
search_query = "a unique message for peer search"
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
peer = await honcho_client.peer(id="search-peer")
|
||||
assert isinstance(peer, AsyncPeer)
|
||||
await peer.add_messages(search_query)
|
||||
|
||||
search_results = await peer.search(search_query)
|
||||
results = search_results.items
|
||||
assert len(results) >= 1
|
||||
assert search_query in results[0].content
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
peer = honcho_client.peer(id="search-peer")
|
||||
assert isinstance(peer, Peer)
|
||||
peer.add_messages(search_query)
|
||||
|
||||
search_results = peer.search(search_query)
|
||||
results = list(search_results)
|
||||
assert len(results) >= 1
|
||||
assert search_query in results[0].content
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
import pytest
|
||||
|
||||
from sdks.python.src.honcho.async_client.client import AsyncHoncho
|
||||
from sdks.python.src.honcho.async_client.peer import AsyncPeer
|
||||
from sdks.python.src.honcho.async_client.session import (
|
||||
AsyncSession,
|
||||
)
|
||||
from sdks.python.src.honcho.async_client.session import (
|
||||
SessionPeerConfig as AsyncSessionPeerConfig,
|
||||
)
|
||||
from sdks.python.src.honcho.client import Honcho
|
||||
from sdks.python.src.honcho.peer import Peer
|
||||
from sdks.python.src.honcho.session import Session, SessionPeerConfig
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_metadata(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests creation and metadata operations for sessions.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
session = await honcho_client.session(id="test-session-meta")
|
||||
assert isinstance(session, AsyncSession)
|
||||
|
||||
metadata = await session.get_metadata()
|
||||
assert metadata == {}
|
||||
|
||||
await session.set_metadata({"foo": "bar"})
|
||||
metadata = await session.get_metadata()
|
||||
assert metadata == {"foo": "bar"}
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="test-session-meta")
|
||||
assert isinstance(session, Session)
|
||||
|
||||
metadata = session.get_metadata()
|
||||
assert metadata == {}
|
||||
|
||||
session.set_metadata({"foo": "bar"})
|
||||
metadata = session.get_metadata()
|
||||
assert metadata == {"foo": "bar"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_peer_management(
|
||||
client_fixture: tuple[Honcho | AsyncHoncho, str],
|
||||
):
|
||||
"""
|
||||
Tests adding, setting, getting, and removing peers from a session.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
session = await honcho_client.session(id="test-session-peers")
|
||||
assert isinstance(session, AsyncSession)
|
||||
peer1 = await honcho_client.peer(id="p1")
|
||||
assert isinstance(peer1, AsyncPeer)
|
||||
peer2 = await honcho_client.peer(id="p2")
|
||||
assert isinstance(peer2, AsyncPeer)
|
||||
peer3 = await honcho_client.peer(id="p3")
|
||||
assert isinstance(peer3, AsyncPeer)
|
||||
|
||||
await session.add_peers([peer1, peer2])
|
||||
peers = await session.get_peers()
|
||||
assert len(peers) == 2
|
||||
peer_ids = {p.id for p in peers}
|
||||
assert "p1" in peer_ids and "p2" in peer_ids
|
||||
|
||||
await session.set_peers([peer2, peer3])
|
||||
peers = await session.get_peers()
|
||||
assert len(peers) == 2
|
||||
peer_ids = {p.id for p in peers}
|
||||
assert "p2" in peer_ids and "p3" in peer_ids
|
||||
|
||||
await session.remove_peers([peer2])
|
||||
peers = await session.get_peers()
|
||||
assert len(peers) == 1
|
||||
assert peers[0].id == "p3"
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="test-session-peers")
|
||||
assert isinstance(session, Session)
|
||||
peer1 = honcho_client.peer(id="p1")
|
||||
assert isinstance(peer1, Peer)
|
||||
peer2 = honcho_client.peer(id="p2")
|
||||
assert isinstance(peer2, Peer)
|
||||
peer3 = honcho_client.peer(id="p3")
|
||||
assert isinstance(peer3, Peer)
|
||||
|
||||
session.add_peers([peer1, peer2])
|
||||
peers = session.get_peers()
|
||||
assert len(peers) == 2
|
||||
peer_ids = {p.id for p in peers}
|
||||
assert "p1" in peer_ids and "p2" in peer_ids
|
||||
|
||||
session.set_peers([peer2, peer3])
|
||||
peers = session.get_peers()
|
||||
assert len(peers) == 2
|
||||
peer_ids = {p.id for p in peers}
|
||||
assert "p2" in peer_ids and "p3" in peer_ids
|
||||
|
||||
session.remove_peers([peer2])
|
||||
peers = session.get_peers()
|
||||
assert len(peers) == 1
|
||||
assert peers[0].id == "p3"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_peer_config(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests getting and setting peer configurations in a session.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
config = AsyncSessionPeerConfig(observe_others=False, observe_me=False)
|
||||
session = await honcho_client.session(id="test-session-config")
|
||||
assert isinstance(session, AsyncSession)
|
||||
peer = await honcho_client.peer(id="p-config")
|
||||
assert isinstance(peer, AsyncPeer)
|
||||
await session.add_peers([(peer, config)])
|
||||
|
||||
retrieved_config = await session.get_peer_config(peer)
|
||||
assert retrieved_config.observe_me is False
|
||||
assert retrieved_config.observe_others is False
|
||||
|
||||
await session.set_peer_config(
|
||||
peer, AsyncSessionPeerConfig(observe_others=True, observe_me=True)
|
||||
)
|
||||
retrieved_config = await session.get_peer_config(peer)
|
||||
assert retrieved_config.observe_me is True
|
||||
assert retrieved_config.observe_others is True
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
config = SessionPeerConfig(observe_others=False, observe_me=False)
|
||||
session = honcho_client.session(id="test-session-config")
|
||||
assert isinstance(session, Session)
|
||||
peer = honcho_client.peer(id="p-config")
|
||||
assert isinstance(peer, Peer)
|
||||
session.add_peers([(peer, config)])
|
||||
|
||||
retrieved_config = session.get_peer_config(peer)
|
||||
assert retrieved_config.observe_me is False
|
||||
assert retrieved_config.observe_others is False
|
||||
|
||||
session.set_peer_config(
|
||||
peer, SessionPeerConfig(observe_others=True, observe_me=True)
|
||||
)
|
||||
retrieved_config = session.get_peer_config(peer)
|
||||
assert retrieved_config.observe_me
|
||||
assert retrieved_config.observe_others
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_messages(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests adding and getting messages from a session.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
session = await honcho_client.session(id="test-session-msg")
|
||||
assert isinstance(session, AsyncSession)
|
||||
user = await honcho_client.peer(id="user-msg")
|
||||
assert isinstance(user, AsyncPeer)
|
||||
assistant = await honcho_client.peer(id="assistant-msg")
|
||||
assert isinstance(assistant, AsyncPeer)
|
||||
|
||||
await session.add_messages(
|
||||
[
|
||||
user.message("Hello assistant"),
|
||||
assistant.message("Hello user"),
|
||||
]
|
||||
)
|
||||
messages_page = await session.get_messages()
|
||||
messages = messages_page.items
|
||||
assert len(messages) == 2
|
||||
|
||||
messages_page = await session.get_messages(filters={"peer_id": user.id})
|
||||
messages = messages_page.items
|
||||
assert len(messages) == 1
|
||||
assert messages[0].content == "Hello assistant"
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="test-session-msg")
|
||||
assert isinstance(session, Session)
|
||||
user = honcho_client.peer(id="user-msg")
|
||||
assert isinstance(user, Peer)
|
||||
assistant = honcho_client.peer(id="assistant-msg")
|
||||
assert isinstance(assistant, Peer)
|
||||
|
||||
session.add_messages(
|
||||
[
|
||||
user.message("Hello assistant"),
|
||||
assistant.message("Hello user"),
|
||||
]
|
||||
)
|
||||
messages_page = session.get_messages()
|
||||
messages = list(messages_page)
|
||||
assert len(messages) == 2
|
||||
|
||||
messages_page = session.get_messages(filters={"peer_id": user.id})
|
||||
messages = list(messages_page)
|
||||
assert len(messages) == 1
|
||||
assert messages[0].content == "Hello assistant"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_get_context(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests getting the context of a session.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
session = await honcho_client.session(id="test-session-ctx")
|
||||
assert isinstance(session, AsyncSession)
|
||||
user = await honcho_client.peer(id="user-ctx")
|
||||
assert isinstance(user, AsyncPeer)
|
||||
await session.add_messages([user.message("This is a context test.")])
|
||||
context = await session.get_context()
|
||||
assert len(context.messages) == 1
|
||||
assert "context test" in context.messages[0].content
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="test-session-ctx")
|
||||
assert isinstance(session, Session)
|
||||
user = honcho_client.peer(id="user-ctx")
|
||||
assert isinstance(user, Peer)
|
||||
session.add_messages([user.message("This is a context test.")])
|
||||
context = session.get_context()
|
||||
assert len(context.messages) == 1
|
||||
assert "context test" in context.messages[0].content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_search(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests searching for messages in a session.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
search_query = "a unique message for session search"
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
session = await honcho_client.session(id="search-session-s")
|
||||
assert isinstance(session, AsyncSession)
|
||||
user = await honcho_client.peer(id="search-user-s")
|
||||
assert isinstance(user, AsyncPeer)
|
||||
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
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="search-session-s")
|
||||
assert isinstance(session, Session)
|
||||
user = honcho_client.peer(id="search-user-s")
|
||||
assert isinstance(user, Peer)
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_working_rep(client_fixture: tuple[Honcho | AsyncHoncho, str]):
|
||||
"""
|
||||
Tests getting the working representation of a peer in a session.
|
||||
"""
|
||||
honcho_client, client_type = client_fixture
|
||||
|
||||
if client_type == "async":
|
||||
assert isinstance(honcho_client, AsyncHoncho)
|
||||
session = await honcho_client.session(id="test-session-wr")
|
||||
assert isinstance(session, AsyncSession)
|
||||
peer = await honcho_client.peer(id="peer-wr")
|
||||
assert isinstance(peer, AsyncPeer)
|
||||
await session.add_messages([peer.message("test message for working rep")])
|
||||
_working_rep = await session.working_rep(peer)
|
||||
else:
|
||||
assert isinstance(honcho_client, Honcho)
|
||||
session = honcho_client.session(id="test-session-wr")
|
||||
assert isinstance(session, Session)
|
||||
peer = honcho_client.peer(id="peer-wr")
|
||||
assert isinstance(peer, Peer)
|
||||
session.add_messages([peer.message("test message for working rep")])
|
||||
_working_rep = session.working_rep(peer)
|
||||
25
uv.lock
25
uv.lock
|
|
@ -505,6 +505,7 @@ dependencies = [
|
|||
dev = [
|
||||
{ name = "basedpyright" },
|
||||
{ name = "coverage" },
|
||||
{ name = "honcho-core" },
|
||||
{ name = "interrogate" },
|
||||
{ name = "py-spy" },
|
||||
{ name = "pytest" },
|
||||
|
|
@ -539,6 +540,7 @@ requires-dist = [
|
|||
dev = [
|
||||
{ name = "basedpyright", specifier = ">=1.29.4" },
|
||||
{ name = "coverage", specifier = ">=7.6.0" },
|
||||
{ name = "honcho-core", specifier = ">=1.1.0" },
|
||||
{ name = "interrogate", specifier = ">=1.7.0" },
|
||||
{ name = "py-spy", specifier = ">=0.3.14" },
|
||||
{ name = "pytest", specifier = ">=8.2.2" },
|
||||
|
|
@ -547,6 +549,23 @@ dev = [
|
|||
{ name = "sqlalchemy-utils", specifier = ">=0.41.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "honcho-core"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "distro" },
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/2f/27739b0d8950da05743cba5085be66f1184195c07ba9b852988d45ba252d/honcho_core-1.1.0.tar.gz", hash = "sha256:d31dc932573b771056952d234cf0c615a4ca591a11eb29c543e9a2277fcc0926", size = 121388, upload-time = "2025-06-26T19:31:00.814Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/14/c2bc7dce35a76a7d89166c55f35f2be21fdb3a94ee3f6502aa83f399bb1a/honcho_core-1.1.0-py3-none-any.whl", hash = "sha256:5716aa572cf33416d1c2c475dbd714879e9d351f2e2b1c6c37751b2c91adc6ce", size = 112730, upload-time = "2025-06-26T19:30:59.455Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
|
|
@ -1024,7 +1043,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.92.0"
|
||||
version = "1.92.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -1036,9 +1055,9 @@ dependencies = [
|
|||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/3f/75ad8dfe1ee4e6daacebc5a8145878c00b22c4b89e3446228d3ffccbf726/openai-1.92.0.tar.gz", hash = "sha256:acaf4ee5fca8611a09035e37ceb69a352b4a84e25b103f0fc1ccc696bf9a16f0", size = 485264, upload-time = "2025-06-26T16:57:18.996Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/44/9dba6f521591c49d3ba70b6800b32cb3f8feec89648b27fcce766046c57c/openai-1.92.2.tar.gz", hash = "sha256:b571a79fc7e165e7d00e6963a8a95eb5f42b60ac89fd316f1dc0a2dac5c6fae1", size = 485428, upload-time = "2025-06-26T19:38:01.228Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/66/8f85019b7943e4976d931fdb0e2b973090b4dac999c3e90844d2336f1184/openai-1.92.0-py3-none-any.whl", hash = "sha256:99210715bad1a4de5a387993aca7e4e8e5a742f1e185eefe2f805001cc2c21f3", size = 753397, upload-time = "2025-06-26T16:57:17.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/e4/e558d3049feedab15fabf3cb704837b21c8194c133b9fb9fe56b8b82ca30/openai-1.92.2-py3-none-any.whl", hash = "sha256:abb64bee7f2571709edf9a856f598ffe871730129a7d807a8a4d8d2958f5c842", size = 753297, upload-time = "2025-06-26T19:37:59.35Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in New Issue