From cb3b6104bdcd42b513434516ac61cf9e4b49ec49 Mon Sep 17 00:00:00 2001 From: doria <93405247+dr-frmr@users.noreply.github.com> Date: Thu, 26 Jun 2025 17:07:23 -0400 Subject: [PATCH] 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 --- pyproject.toml | 6 +- sdks/python/.gitignore | 13 + sdks/python/README.md | 167 + sdks/python/examples/async_example.py | 129 + sdks/python/examples/chat.py | 41 + sdks/python/examples/example.py | 118 + sdks/python/examples/get_context.py | 31 + sdks/python/examples/get_representation.py | 37 + .../examples/pydantic_validation_example.py | 127 + sdks/python/examples/search.py | 51 + sdks/python/pyproject.toml | 56 + sdks/python/src/honcho/__init__.py | 60 + .../src/honcho/async_client/__init__.py | 18 + sdks/python/src/honcho/async_client/client.py | 330 ++ .../src/honcho/async_client/pagination.py | 91 + sdks/python/src/honcho/async_client/peer.py | 331 ++ .../python/src/honcho/async_client/session.py | 513 ++ sdks/python/src/honcho/client.py | 304 + sdks/python/src/honcho/pagination.py | 82 + sdks/python/src/honcho/peer.py | 311 + sdks/python/src/honcho/py.typed | 1 + sdks/python/src/honcho/session.py | 490 ++ sdks/python/src/honcho/session_context.py | 142 + sdks/python/uv.lock | 509 ++ sdks/typescript/.gitignore | 2 + sdks/typescript/README.md | 40 + sdks/typescript/__tests__/README.md | 182 + .../__tests__/__mocks__/@honcho-ai/core.ts | 45 + sdks/typescript/__tests__/client.test.ts | 385 ++ sdks/typescript/__tests__/integration.test.ts | 418 ++ sdks/typescript/__tests__/pagination.test.ts | 453 ++ sdks/typescript/__tests__/peer.test.ts | 537 ++ sdks/typescript/__tests__/session.test.ts | 692 +++ .../__tests__/session_context.test.ts | 327 ++ sdks/typescript/__tests__/setup.ts | 19 + sdks/typescript/bun.lock | 788 +++ sdks/typescript/examples/example.ts | 109 + sdks/typescript/examples/get_context.ts | 52 + .../typescript/examples/get_representation.ts | 59 + sdks/typescript/examples/search.ts | 82 + sdks/typescript/jest.config.js | 20 + sdks/typescript/package-lock.json | 5156 +++++++++++++++++ sdks/typescript/package.json | 30 + sdks/typescript/src/client.ts | 119 + sdks/typescript/src/index.ts | 8 + sdks/typescript/src/pagination.ts | 77 + sdks/typescript/src/peer.ts | 152 + sdks/typescript/src/session.ts | 245 + sdks/typescript/src/session_context.ts | 93 + sdks/typescript/tsconfig.json | 25 + src/crud.py | 62 +- src/routers/sessions.py | 2 +- tests/sdk/__init__.py | 0 tests/sdk/conftest.py | 72 + tests/sdk/sdk_integration_test.py | 106 + tests/sdk/test_client.py | 186 + tests/sdk/test_peer.py | 197 + tests/sdk/test_session.py | 299 + uv.lock | 25 +- 59 files changed, 14993 insertions(+), 29 deletions(-) create mode 100644 sdks/python/.gitignore create mode 100644 sdks/python/README.md create mode 100644 sdks/python/examples/async_example.py create mode 100644 sdks/python/examples/chat.py create mode 100644 sdks/python/examples/example.py create mode 100644 sdks/python/examples/get_context.py create mode 100644 sdks/python/examples/get_representation.py create mode 100644 sdks/python/examples/pydantic_validation_example.py create mode 100644 sdks/python/examples/search.py create mode 100644 sdks/python/pyproject.toml create mode 100644 sdks/python/src/honcho/__init__.py create mode 100644 sdks/python/src/honcho/async_client/__init__.py create mode 100644 sdks/python/src/honcho/async_client/client.py create mode 100644 sdks/python/src/honcho/async_client/pagination.py create mode 100644 sdks/python/src/honcho/async_client/peer.py create mode 100644 sdks/python/src/honcho/async_client/session.py create mode 100644 sdks/python/src/honcho/client.py create mode 100644 sdks/python/src/honcho/pagination.py create mode 100644 sdks/python/src/honcho/peer.py create mode 100644 sdks/python/src/honcho/py.typed create mode 100644 sdks/python/src/honcho/session.py create mode 100644 sdks/python/src/honcho/session_context.py create mode 100644 sdks/python/uv.lock create mode 100644 sdks/typescript/.gitignore create mode 100644 sdks/typescript/README.md create mode 100644 sdks/typescript/__tests__/README.md create mode 100644 sdks/typescript/__tests__/__mocks__/@honcho-ai/core.ts create mode 100644 sdks/typescript/__tests__/client.test.ts create mode 100644 sdks/typescript/__tests__/integration.test.ts create mode 100644 sdks/typescript/__tests__/pagination.test.ts create mode 100644 sdks/typescript/__tests__/peer.test.ts create mode 100644 sdks/typescript/__tests__/session.test.ts create mode 100644 sdks/typescript/__tests__/session_context.test.ts create mode 100644 sdks/typescript/__tests__/setup.ts create mode 100644 sdks/typescript/bun.lock create mode 100644 sdks/typescript/examples/example.ts create mode 100644 sdks/typescript/examples/get_context.ts create mode 100644 sdks/typescript/examples/get_representation.ts create mode 100644 sdks/typescript/examples/search.ts create mode 100644 sdks/typescript/jest.config.js create mode 100644 sdks/typescript/package-lock.json create mode 100644 sdks/typescript/package.json create mode 100644 sdks/typescript/src/client.ts create mode 100644 sdks/typescript/src/index.ts create mode 100644 sdks/typescript/src/pagination.ts create mode 100644 sdks/typescript/src/peer.ts create mode 100644 sdks/typescript/src/session.ts create mode 100644 sdks/typescript/src/session_context.ts create mode 100644 sdks/typescript/tsconfig.json create mode 100644 tests/sdk/__init__.py create mode 100644 tests/sdk/conftest.py create mode 100644 tests/sdk/sdk_integration_test.py create mode 100644 tests/sdk/test_client.py create mode 100644 tests/sdk/test_peer.py create mode 100644 tests/sdk/test_session.py diff --git a/pyproject.toml b/pyproject.toml index 32e989ce..ba743c82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 \ No newline at end of file +# reportUnknownMemberType = false +reportImportCycles = false \ No newline at end of file diff --git a/sdks/python/.gitignore b/sdks/python/.gitignore new file mode 100644 index 00000000..91abc4fb --- /dev/null +++ b/sdks/python/.gitignore @@ -0,0 +1,13 @@ +.vscode +.DS_Store + +__pycache__ +.mypy_cache +.ruff_cache +*.egg-info + +dist + +.venv + +.env diff --git a/sdks/python/README.md b/sdks/python/README.md new file mode 100644 index 00000000..c8375527 --- /dev/null +++ b/sdks/python/README.md @@ -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) diff --git a/sdks/python/examples/async_example.py b/sdks/python/examples/async_example.py new file mode 100644 index 00000000..c8c211b1 --- /dev/null +++ b/sdks/python/examples/async_example.py @@ -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()) diff --git a/sdks/python/examples/chat.py b/sdks/python/examples/chat.py new file mode 100644 index 00000000..7b92f135 --- /dev/null +++ b/sdks/python/examples/chat.py @@ -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) diff --git a/sdks/python/examples/example.py b/sdks/python/examples/example.py new file mode 100644 index 00000000..5ef81ab0 --- /dev/null +++ b/sdks/python/examples/example.py @@ -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!") diff --git a/sdks/python/examples/get_context.py b/sdks/python/examples/get_context.py new file mode 100644 index 00000000..300fb3b9 --- /dev/null +++ b/sdks/python/examples/get_context.py @@ -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) diff --git a/sdks/python/examples/get_representation.py b/sdks/python/examples/get_representation.py new file mode 100644 index 00000000..018e5b7b --- /dev/null +++ b/sdks/python/examples/get_representation.py @@ -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) diff --git a/sdks/python/examples/pydantic_validation_example.py b/sdks/python/examples/pydantic_validation_example.py new file mode 100644 index 00000000..0fe24c4b --- /dev/null +++ b/sdks/python/examples/pydantic_validation_example.py @@ -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() diff --git a/sdks/python/examples/search.py b/sdks/python/examples/search.py new file mode 100644 index 00000000..247eaebe --- /dev/null +++ b/sdks/python/examples/search.py @@ -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]) diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml new file mode 100644 index 00000000..e6c9a9c4 --- /dev/null +++ b/sdks/python/pyproject.toml @@ -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"] \ No newline at end of file diff --git a/sdks/python/src/honcho/__init__.py b/sdks/python/src/honcho/__init__.py new file mode 100644 index 00000000..9b9ba4be --- /dev/null +++ b/sdks/python/src/honcho/__init__.py @@ -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", +] diff --git a/sdks/python/src/honcho/async_client/__init__.py b/sdks/python/src/honcho/async_client/__init__.py new file mode 100644 index 00000000..c02a9b72 --- /dev/null +++ b/sdks/python/src/honcho/async_client/__init__.py @@ -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", +] diff --git a/sdks/python/src/honcho/async_client/client.py b/sdks/python/src/honcho/async_client/client.py new file mode 100644 index 00000000..9e3aedd8 --- /dev/null +++ b/sdks/python/src/honcho/async_client/client.py @@ -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})" diff --git a/sdks/python/src/honcho/async_client/pagination.py b/sdks/python/src/honcho/async_client/pagination.py new file mode 100644 index 00000000..a8232095 --- /dev/null +++ b/sdks/python/src/honcho/async_client/pagination.py @@ -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 diff --git a/sdks/python/src/honcho/async_client/peer.py b/sdks/python/src/honcho/async_client/peer.py new file mode 100644 index 00000000..c874c712 --- /dev/null +++ b/sdks/python/src/honcho/async_client/peer.py @@ -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 diff --git a/sdks/python/src/honcho/async_client/session.py b/sdks/python/src/honcho/async_client/session.py new file mode 100644 index 00000000..c497e477 --- /dev/null +++ b/sdks/python/src/honcho/async_client/session.py @@ -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 diff --git a/sdks/python/src/honcho/client.py b/sdks/python/src/honcho/client.py new file mode 100644 index 00000000..504e3347 --- /dev/null +++ b/sdks/python/src/honcho/client.py @@ -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})" diff --git a/sdks/python/src/honcho/pagination.py b/sdks/python/src/honcho/pagination.py new file mode 100644 index 00000000..b22270db --- /dev/null +++ b/sdks/python/src/honcho/pagination.py @@ -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 diff --git a/sdks/python/src/honcho/peer.py b/sdks/python/src/honcho/peer.py new file mode 100644 index 00000000..f21ab1d4 --- /dev/null +++ b/sdks/python/src/honcho/peer.py @@ -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 diff --git a/sdks/python/src/honcho/py.typed b/sdks/python/src/honcho/py.typed new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/sdks/python/src/honcho/py.typed @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sdks/python/src/honcho/session.py b/sdks/python/src/honcho/session.py new file mode 100644 index 00000000..93d05239 --- /dev/null +++ b/sdks/python/src/honcho/session.py @@ -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 diff --git a/sdks/python/src/honcho/session_context.py b/sdks/python/src/honcho/session_context.py new file mode 100644 index 00000000..c5d7aed3 --- /dev/null +++ b/sdks/python/src/honcho/session_context.py @@ -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)})" diff --git a/sdks/python/uv.lock b/sdks/python/uv.lock new file mode 100644 index 00000000..2e8ed310 --- /dev/null +++ b/sdks/python/uv.lock @@ -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" }, +] diff --git a/sdks/typescript/.gitignore b/sdks/typescript/.gitignore new file mode 100644 index 00000000..76add878 --- /dev/null +++ b/sdks/typescript/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/sdks/typescript/README.md b/sdks/typescript/README.md new file mode 100644 index 00000000..bfd3ca58 --- /dev/null +++ b/sdks/typescript/README.md @@ -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. + diff --git a/sdks/typescript/__tests__/README.md b/sdks/typescript/__tests__/README.md new file mode 100644 index 00000000..c72ee898 --- /dev/null +++ b/sdks/typescript/__tests__/README.md @@ -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. \ No newline at end of file diff --git a/sdks/typescript/__tests__/__mocks__/@honcho-ai/core.ts b/sdks/typescript/__tests__/__mocks__/@honcho-ai/core.ts new file mode 100644 index 00000000..76962510 --- /dev/null +++ b/sdks/typescript/__tests__/__mocks__/@honcho-ai/core.ts @@ -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 + } +} \ No newline at end of file diff --git a/sdks/typescript/__tests__/client.test.ts b/sdks/typescript/__tests__/client.test.ts new file mode 100644 index 00000000..3ec56b62 --- /dev/null +++ b/sdks/typescript/__tests__/client.test.ts @@ -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'); + }); + }); +}); \ No newline at end of file diff --git a/sdks/typescript/__tests__/integration.test.ts b/sdks/typescript/__tests__/integration.test.ts new file mode 100644 index 00000000..3ba31562 --- /dev/null +++ b/sdks/typescript/__tests__/integration.test.ts @@ -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'); + } + }); + }); +}); \ No newline at end of file diff --git a/sdks/typescript/__tests__/pagination.test.ts b/sdks/typescript/__tests__/pagination.test.ts new file mode 100644 index 00000000..d347ac22 --- /dev/null +++ b/sdks/typescript/__tests__/pagination.test.ts @@ -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 }); + }); + }); +}); \ No newline at end of file diff --git a/sdks/typescript/__tests__/peer.test.ts b/sdks/typescript/__tests__/peer.test.ts new file mode 100644 index 00000000..9c695b1b --- /dev/null +++ b/sdks/typescript/__tests__/peer.test.ts @@ -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'); + }); + }); +}); \ No newline at end of file diff --git a/sdks/typescript/__tests__/session.test.ts b/sdks/typescript/__tests__/session.test.ts new file mode 100644 index 00000000..3b949168 --- /dev/null +++ b/sdks/typescript/__tests__/session.test.ts @@ -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'); + }); + }); +}); \ No newline at end of file diff --git a/sdks/typescript/__tests__/session_context.test.ts b/sdks/typescript/__tests__/session_context.test.ts new file mode 100644 index 00000000..459691e1 --- /dev/null +++ b/sdks/typescript/__tests__/session_context.test.ts @@ -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' }, + ]); + }); + }); +}); \ No newline at end of file diff --git a/sdks/typescript/__tests__/setup.ts b/sdks/typescript/__tests__/setup.ts new file mode 100644 index 00000000..069c490a --- /dev/null +++ b/sdks/typescript/__tests__/setup.ts @@ -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'; \ No newline at end of file diff --git a/sdks/typescript/bun.lock b/sdks/typescript/bun.lock new file mode 100644 index 00000000..60eca065 --- /dev/null +++ b/sdks/typescript/bun.lock @@ -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=="], + } +} diff --git a/sdks/typescript/examples/example.ts b/sdks/typescript/examples/example.ts new file mode 100644 index 00000000..54beb06e --- /dev/null +++ b/sdks/typescript/examples/example.ts @@ -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); +}); \ No newline at end of file diff --git a/sdks/typescript/examples/get_context.ts b/sdks/typescript/examples/get_context.ts new file mode 100644 index 00000000..41a8c060 --- /dev/null +++ b/sdks/typescript/examples/get_context.ts @@ -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); +}); \ No newline at end of file diff --git a/sdks/typescript/examples/get_representation.ts b/sdks/typescript/examples/get_representation.ts new file mode 100644 index 00000000..ce1c61d9 --- /dev/null +++ b/sdks/typescript/examples/get_representation.ts @@ -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); +}); \ No newline at end of file diff --git a/sdks/typescript/examples/search.ts b/sdks/typescript/examples/search.ts new file mode 100644 index 00000000..b801b068 --- /dev/null +++ b/sdks/typescript/examples/search.ts @@ -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); +}); \ No newline at end of file diff --git a/sdks/typescript/jest.config.js b/sdks/typescript/jest.config.js new file mode 100644 index 00000000..71ea0f49 --- /dev/null +++ b/sdks/typescript/jest.config.js @@ -0,0 +1,20 @@ +export default { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src', '/__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: ['/__tests__/setup.ts'], + moduleNameMapper: { + '^@honcho-ai/core$': '/__tests__/__mocks__/@honcho-ai/core.ts', + }, + testTimeout: 10000, +}; \ No newline at end of file diff --git a/sdks/typescript/package-lock.json b/sdks/typescript/package-lock.json new file mode 100644 index 00000000..5d584396 --- /dev/null +++ b/sdks/typescript/package-lock.json @@ -0,0 +1,5156 @@ +{ + "name": "@honcho-ai/sdk", + "version": "1.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@honcho-ai/sdk", + "version": "1.1.0", + "license": "Apache-2.0", + "dependencies": { + "@honcho-ai/core": "^1.1.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" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@honcho-ai/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@honcho-ai/core/-/core-1.1.0.tgz", + "integrity": "sha512-81tcO8qW7gFddMjpXiJdGZCN9LD6NE6ujE1jmKNbVkFINb3sou421/68Ivtmf5oCMqw2E3bbisIdWOYhGofIVw==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@honcho-ai/core/node_modules/@types/node": { + "version": "18.19.112", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.112.tgz", + "integrity": "sha512-i+Vukt9POdS/MBI7YrrkkI5fMfwFtOjphSmt4WXYLfwqsfr6z/HdCx7LqT9M7JktGob8WNgj8nFB4TbGNE4Cog==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@honcho-ai/core/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", + "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.176", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.176.tgz", + "integrity": "sha512-2nDK9orkm7M9ZZkjO3PjbEd3VUulQLyg5T9O3enJdFvUg46Hzd4DUvTvAuEgbdHYXyFsiG4A5sO9IzToMH1cDg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "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" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-jest": { + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", + "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", + "dev": true, + "license": "MIT", + "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" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "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" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json new file mode 100644 index 00000000..754320ec --- /dev/null +++ b/sdks/typescript/package.json @@ -0,0 +1,30 @@ +{ + "name": "@honcho-ai/sdk", + "version": "1.1.0", + "description": "Official DX Optimized TypeScript SDK for Honcho", + "author": "Plastic Labs ", + "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" + } +} diff --git a/sdks/typescript/src/client.ts b/sdks/typescript/src/client.ts new file mode 100644 index 00000000..560b81e2 --- /dev/null +++ b/sdks/typescript/src/client.ts @@ -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; + 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; + defaultQuery?: Record; + }) { + 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 }): 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> { + 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 }): 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> { + 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> { + const workspace = await this._client.workspaces.getOrCreate({ id: this.workspaceId }); + return workspace.metadata || {}; + } + + /** + * Set metadata for the current workspace. + */ + async setMetadata(metadata: Record): Promise { + await this._client.workspaces.update(this.workspaceId, { metadata }); + } + + /** + * Get all workspace IDs from the Honcho instance. + */ + async getWorkspaces(): Promise { + 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> { + 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); + } +} \ No newline at end of file diff --git a/sdks/typescript/src/index.ts b/sdks/typescript/src/index.ts new file mode 100644 index 00000000..633b05c0 --- /dev/null +++ b/sdks/typescript/src/index.ts @@ -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'; \ No newline at end of file diff --git a/sdks/typescript/src/pagination.ts b/sdks/typescript/src/pagination.ts new file mode 100644 index 00000000..9166ee6b --- /dev/null +++ b/sdks/typescript/src/pagination.ts @@ -0,0 +1,77 @@ +/** + * Generic paginated result wrapper for Honcho SDK. + * Provides async iteration and transformation capabilities. + */ +export class Page implements AsyncIterable { + 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 { + // 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 { + 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 { + 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 | null> { + const nextPage = await this._originalPage.nextPage(); + if (!nextPage) return null; + return new Page(nextPage, this._transformFunc); + } +} \ No newline at end of file diff --git a/sdks/typescript/src/peer.ts b/sdks/typescript/src/peer.ts new file mode 100644 index 00000000..cbec6617 --- /dev/null +++ b/sdks/typescript/src/peer.ts @@ -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) { + 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 { + 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> { + 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 { + 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 }): Promise> { + 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 }): any { + return { + peerId: this.id, + content, + metadata: opts?.metadata, + }; + } + + /** + * Get the current metadata for this peer. + */ + async getMetadata(): Promise> { + 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): Promise { + 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> { + 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); + } +} \ No newline at end of file diff --git a/sdks/typescript/src/session.ts b/sdks/typescript/src/session.ts new file mode 100644 index 00000000..44288197 --- /dev/null +++ b/sdks/typescript/src/session.ts @@ -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) { + 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, SessionPeerConfig] | Array<[string | Peer, SessionPeerConfig]> | Array): Promise { + const peerDict: Record = {}; + 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, SessionPeerConfig] | Array<[string | Peer, SessionPeerConfig]> | Array): Promise { + const peerDict: Record = {}; + 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): Promise { + 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 { + 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 { + 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 { + 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 { + 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 }): Promise> { + 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> { + 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): Promise { + 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 { + 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> { + 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> { + 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 + } + ); + } +} \ No newline at end of file diff --git a/sdks/typescript/src/session_context.ts b/sdks/typescript/src/session_context.ts new file mode 100644 index 00000000..e6ef8e37 --- /dev/null +++ b/sdks/typescript/src/session_context.ts @@ -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})`; + } +} \ No newline at end of file diff --git a/sdks/typescript/tsconfig.json b/sdks/typescript/tsconfig.json new file mode 100644 index 00000000..f4c23d99 --- /dev/null +++ b/sdks/typescript/tsconfig.json @@ -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": ".", +} \ No newline at end of file diff --git a/src/crud.py b/src/crud.py index 93914efc..26a5bfbc 100644 --- a/src/crud.py +++ b/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() diff --git a/src/routers/sessions.py b/src/routers/sessions.py index ff103445..22fcc118 100644 --- a/src/routers/sessions.py +++ b/src/routers/sessions.py @@ -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( diff --git a/tests/sdk/__init__.py b/tests/sdk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/sdk/conftest.py b/tests/sdk/conftest.py new file mode 100644 index 00000000..20ed7aba --- /dev/null +++ b/tests/sdk/conftest.py @@ -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" diff --git a/tests/sdk/sdk_integration_test.py b/tests/sdk/sdk_integration_test.py new file mode 100644 index 00000000..7a839ffe --- /dev/null +++ b/tests/sdk/sdk_integration_test.py @@ -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?") diff --git a/tests/sdk/test_client.py b/tests/sdk/test_client.py new file mode 100644 index 00000000..b2935087 --- /dev/null +++ b/tests/sdk/test_client.py @@ -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 diff --git a/tests/sdk/test_peer.py b/tests/sdk/test_peer.py new file mode 100644 index 00000000..e3d72b6a --- /dev/null +++ b/tests/sdk/test_peer.py @@ -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 diff --git a/tests/sdk/test_session.py b/tests/sdk/test_session.py new file mode 100644 index 00000000..c4dea44e --- /dev/null +++ b/tests/sdk/test_session.py @@ -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) diff --git a/uv.lock b/uv.lock index 711ab68e..c2d45af1 100644 --- a/uv.lock +++ b/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]]