From e07a58979652425b8b891f68b4c145a648cdc049 Mon Sep 17 00:00:00 2001 From: Sarah Wooders Date: Sat, 4 Oct 2025 21:20:05 -0700 Subject: [PATCH] chore: rm composio (#5151) --- .github/scripts/model-sweep/conftest.py | 7 - .github/workflows/model-sweep.yaml | 1 - .github/workflows/reusable-test-workflow.yml | 1 - Dockerfile | 3 +- .../f895232c144a_backfill_composio_tools.py | 8 +- fern/docs.yml | 3 - fern/examples/composio_tools.py | 30 - fern/openapi.json | 630 +----------------- fern/pages/agents/composio.mdx | 142 ---- fern/pages/agents/tool_variables.mdx | 6 +- fern/pages/deployment/railway.mdx | 2 +- letta/agents/letta_agent.py | 1 - letta/agents/voice_agent.py | 3 +- letta/constants.py | 3 - letta/functions/async_composio_toolset.py | 109 --- letta/functions/composio_helpers.py | 96 --- letta/functions/schema_generator.py | 71 -- letta/helpers/composio_helpers.py | 38 -- letta/main.py | 3 - letta/schemas/enums.py | 5 +- letta/schemas/tool.py | 49 +- letta/server/rest_api/routers/v1/tools.py | 136 ---- letta/server/server.py | 42 -- .../tool_executor/composio_tool_executor.py | 57 -- .../tool_executor/tool_execution_manager.py | 2 - letta/services/tool_manager.py | 16 +- letta/settings.py | 2 - pyproject.toml | 1 - tests/conftest.py | 7 - tests/integration_test_chat_completions.py | 9 - tests/mcp_tests/test_mcp_schema_validation.py | 20 - tests/mcp_tests/test_schema_validator.py | 39 +- .../composio_github_star_agent.af | 591 ---------------- tests/test_agent_serialization.py | 1 - tests/test_tool_schema_parsing.py | 41 -- uv.lock | 165 +---- 36 files changed, 22 insertions(+), 2318 deletions(-) delete mode 100644 fern/examples/composio_tools.py delete mode 100644 fern/pages/agents/composio.mdx delete mode 100644 letta/functions/async_composio_toolset.py delete mode 100644 letta/functions/composio_helpers.py delete mode 100644 letta/helpers/composio_helpers.py delete mode 100644 letta/services/tool_executor/composio_tool_executor.py delete mode 100644 tests/test_agent_files/composio_github_star_agent.af diff --git a/.github/scripts/model-sweep/conftest.py b/.github/scripts/model-sweep/conftest.py index db66a444..2197eac0 100644 --- a/.github/scripts/model-sweep/conftest.py +++ b/.github/scripts/model-sweep/conftest.py @@ -62,13 +62,6 @@ def default_user(default_organization): yield user -@pytest.fixture -def check_composio_key_set(): - original_api_key = tool_settings.composio_api_key - assert original_api_key is not None, "Missing composio key! Cannot execute this test." - yield - - # --- Tool Fixtures --- @pytest.fixture def weather_tool_func(): diff --git a/.github/workflows/model-sweep.yaml b/.github/workflows/model-sweep.yaml index 49626699..24756328 100644 --- a/.github/workflows/model-sweep.yaml +++ b/.github/workflows/model-sweep.yaml @@ -88,7 +88,6 @@ jobs: AZURE_API_KEY: ${{ env.AZURE_API_KEY }} AZURE_BASE_URL: ${{ secrets.AZURE_BASE_URL }} GEMINI_API_KEY: ${{ env.GEMINI_API_KEY }} - COMPOSIO_API_KEY: ${{ env.COMPOSIO_API_KEY }} GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT}} GOOGLE_CLOUD_LOCATION: ${{ secrets.GOOGLE_CLOUD_LOCATION}} DEEPSEEK_API_KEY: ${{ env.DEEPSEEK_API_KEY}} diff --git a/.github/workflows/reusable-test-workflow.yml b/.github/workflows/reusable-test-workflow.yml index 3716a92d..88691161 100644 --- a/.github/workflows/reusable-test-workflow.yml +++ b/.github/workflows/reusable-test-workflow.yml @@ -367,7 +367,6 @@ jobs: LETTA_MISTRAL_API_KEY: ${{ secrets.LETTA_MISTRAL_API_KEY }} # External service API Keys (shared across all test types) - COMPOSIO_API_KEY: ${{ env.COMPOSIO_API_KEY }} E2B_API_KEY: ${{ env.E2B_API_KEY }} E2B_SANDBOX_TEMPLATE_ID: ${{ env.E2B_SANDBOX_TEMPLATE_ID }} diff --git a/Dockerfile b/Dockerfile index 02e74e0c..7ac54191 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,8 +69,7 @@ ENV LETTA_ENVIRONMENT=${LETTA_ENVIRONMENT} \ PATH="/app/.venv/bin:$PATH" \ POSTGRES_USER=letta \ POSTGRES_PASSWORD=letta \ - POSTGRES_DB=letta \ - COMPOSIO_DISABLE_VERSION_CHECK=true + POSTGRES_DB=letta ARG LETTA_VERSION ENV LETTA_VERSION=${LETTA_VERSION} diff --git a/alembic/versions/f895232c144a_backfill_composio_tools.py b/alembic/versions/f895232c144a_backfill_composio_tools.py index 15432ffe..bf3f951f 100644 --- a/alembic/versions/f895232c144a_backfill_composio_tools.py +++ b/alembic/versions/f895232c144a_backfill_composio_tools.py @@ -9,7 +9,6 @@ Create Date: 2025-01-16 14:21:33.764332 from typing import Sequence, Union from alembic import op -from letta.schemas.enums import ToolType from letta.settings import settings # revision identifiers, used by Alembic. @@ -25,8 +24,8 @@ def upgrade() -> None: return # ### commands auto generated by Alembic - please adjust! ### - # Define the value for EXTERNAL_COMPOSIO - external_composio_value = ToolType.EXTERNAL_COMPOSIO.value + # Define the value for EXTERNAL_COMPOSIO (using string literal since enum was removed) + external_composio_value = "external_composio" # Update tool_type to EXTERNAL_COMPOSIO if the tags field includes "composio" # This is super brittle and awful but no other way to do this @@ -46,7 +45,8 @@ def downgrade() -> None: return # ### commands auto generated by Alembic - please adjust! ### - custom_value = ToolType.CUSTOM.value + # Use string literal for CUSTOM value + custom_value = "custom" # Update tool_type to CUSTOM if the tags field includes "composio" # This is super brittle and awful but no other way to do this diff --git a/fern/docs.yml b/fern/docs.yml index 5930002c..4a6341af 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -168,9 +168,6 @@ navigation: path: pages/agents/tool_rules.mdx - page: Tool Variables path: pages/agents/tool_variables.mdx - - page: Composio Integration - path: pages/agents/composio.mdx - hidden: true - section: Model Context Protocol path: pages/mcp/overview.mdx contents: diff --git a/fern/examples/composio_tools.py b/fern/examples/composio_tools.py deleted file mode 100644 index 83d6dba4..00000000 --- a/fern/examples/composio_tools.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Example of using composio tools in Letta - -Make sure you set `COMPOSIO_API_KEY` environment variable or run `composio login` to authenticate with Composio. -""" - -from composio import Action -from letta_client import Letta - -client = Letta(base_url="http://localhost:8283") - -# add a composio tool -tool = client.tools.add_composio_tool(composio_action_name=Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER.name) - -# create an agent with the tool -agent = client.agents.create( - name="file_editing_agent", - memory_blocks=[{"label": "persona", "value": "I am a helpful assistant"}], - model="anthropic/claude-3-5-sonnet-20241022", - embedding="openai/text-embedding-3-small", - tool_ids=[tool.id], -) -print("Agent tools", [tool.name for tool in agent.tools]) - -# message the agent -response = client.agents.messages.create( - agent_id=agent.id, messages=[{"role": "user", "content": "Star the github repo `letta` by `letta-ai`"}] -) -for message in response.messages: - print(message) diff --git a/fern/openapi.json b/fern/openapi.json index 387bfef6..9f70f352 100644 --- a/fern/openapi.json +++ b/fern/openapi.json @@ -976,127 +976,6 @@ } } }, - "/v1/tools/composio/apps": { - "get": { - "tags": ["tools"], - "summary": "List Composio Apps", - "description": "Get a list of all Composio apps", - "operationId": "list_composio_apps", - "parameters": [], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AppModel" - }, - "title": "Response List Composio Apps" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/tools/composio/apps/{composio_app_name}/actions": { - "get": { - "tags": ["tools"], - "summary": "List Composio Actions By App", - "description": "Get a list of all Composio actions for a specific app", - "operationId": "list_composio_actions_by_app", - "parameters": [ - { - "name": "composio_app_name", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Composio App Name" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ActionModel" - }, - "title": "Response List Composio Actions By App" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/tools/composio/{composio_action_name}": { - "post": { - "tags": ["tools"], - "summary": "Add Composio Tool", - "description": "Add a new Composio tool by action name (Composio refers to each tool as an `Action`)", - "operationId": "add_composio_tool", - "parameters": [ - { - "name": "composio_action_name", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Composio Action Name" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Tool" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, "/v1/tools/mcp/servers": { "get": { "tags": ["tools"], @@ -15796,8 +15675,8 @@ "letta_voice_sleeptime_core", "letta_builtin", "letta_files_core", - "external_composio", "external_langchain", + "external_composio", "external_mcp" ] }, @@ -21269,185 +21148,6 @@ }, "components": { "schemas": { - "ActionModel": { - "properties": { - "name": { - "type": "string", - "title": "Name" - }, - "description": { - "type": "string", - "title": "Description" - }, - "parameters": { - "$ref": "#/components/schemas/ActionParametersModel" - }, - "response": { - "$ref": "#/components/schemas/ActionResponseModel" - }, - "appName": { - "type": "string", - "title": "Appname" - }, - "appId": { - "type": "string", - "title": "Appid" - }, - "version": { - "type": "string", - "title": "Version" - }, - "available_versions": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Available Versions" - }, - "tags": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Tags" - }, - "logo": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Logo" - }, - "display_name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Display Name" - }, - "enabled": { - "type": "boolean", - "title": "Enabled", - "default": false - } - }, - "type": "object", - "required": [ - "name", - "description", - "parameters", - "response", - "appName", - "appId", - "version", - "available_versions", - "tags" - ], - "title": "ActionModel", - "description": "Action data model." - }, - "ActionParametersModel": { - "properties": { - "properties": { - "additionalProperties": true, - "type": "object", - "title": "Properties" - }, - "title": { - "type": "string", - "title": "Title" - }, - "type": { - "type": "string", - "title": "Type" - }, - "required": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Required" - }, - "examples": { - "anyOf": [ - { - "items": {}, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Examples" - } - }, - "type": "object", - "required": ["properties", "title", "type"], - "title": "ActionParametersModel", - "description": "Action parameter data models." - }, - "ActionResponseModel": { - "properties": { - "properties": { - "additionalProperties": true, - "type": "object", - "title": "Properties" - }, - "title": { - "type": "string", - "title": "Title" - }, - "type": { - "type": "string", - "title": "Type" - }, - "required": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Required" - }, - "examples": { - "anyOf": [ - { - "items": {}, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Examples" - } - }, - "type": "object", - "required": ["properties", "title", "type"], - "title": "ActionResponseModel", - "description": "Action response data model." - }, "AgentEnvironmentVariable": { "properties": { "created_by_id": { @@ -22184,269 +21884,6 @@ "title": "AgentType", "description": "Enum to represent the type of agent." }, - "AppAuthScheme": { - "properties": { - "scheme_name": { - "type": "string", - "title": "Scheme Name" - }, - "auth_mode": { - "type": "string", - "enum": [ - "OAUTH2", - "OAUTH1", - "API_KEY", - "BASIC", - "BEARER_TOKEN", - "BASIC_WITH_JWT", - "GOOGLE_SERVICE_ACCOUNT", - "GOOGLEADS_AUTH", - "NO_AUTH", - "CALCOM_AUTH" - ], - "title": "Auth Mode" - }, - "fields": { - "items": { - "$ref": "#/components/schemas/AuthSchemeField" - }, - "type": "array", - "title": "Fields" - }, - "proxy": { - "anyOf": [ - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Proxy" - }, - "authorization_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Authorization Url" - }, - "token_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Token Url" - }, - "default_scopes": { - "anyOf": [ - { - "items": {}, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Default Scopes" - }, - "token_response_metadata": { - "anyOf": [ - { - "items": {}, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Token Response Metadata" - }, - "client_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Client Id" - }, - "client_secret": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Client Secret" - } - }, - "type": "object", - "required": ["scheme_name", "auth_mode", "fields"], - "title": "AppAuthScheme", - "description": "App authenticatio scheme." - }, - "AppModel": { - "properties": { - "name": { - "type": "string", - "title": "Name" - }, - "key": { - "type": "string", - "title": "Key" - }, - "appId": { - "type": "string", - "title": "Appid" - }, - "description": { - "type": "string", - "title": "Description" - }, - "categories": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Categories" - }, - "meta": { - "additionalProperties": true, - "type": "object", - "title": "Meta" - }, - "logo": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Logo" - }, - "docs": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Docs" - }, - "group": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Group" - }, - "status": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Status" - }, - "enabled": { - "type": "boolean", - "title": "Enabled", - "default": false - }, - "no_auth": { - "type": "boolean", - "title": "No Auth", - "default": false - }, - "auth_schemes": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/AppAuthScheme" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Auth Schemes" - }, - "testConnectors": { - "anyOf": [ - { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Testconnectors" - }, - "documentation_doc_text": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Documentation Doc Text" - }, - "configuration_docs_text": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Configuration Docs Text" - } - }, - "type": "object", - "required": [ - "name", - "key", - "appId", - "description", - "categories", - "meta" - ], - "title": "AppModel", - "description": "App data model." - }, "ApprovalCreate": { "properties": { "type": { @@ -23063,69 +22500,6 @@ "required": ["uuid"], "title": "AuthResponse" }, - "AuthSchemeField": { - "properties": { - "name": { - "type": "string", - "title": "Name" - }, - "display_name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Display Name" - }, - "description": { - "type": "string", - "title": "Description" - }, - "type": { - "type": "string", - "title": "Type" - }, - "default": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Default" - }, - "required": { - "type": "boolean", - "title": "Required", - "default": false - }, - "expected_from_customer": { - "type": "boolean", - "title": "Expected From Customer", - "default": true - }, - "get_current_user_endpoint": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Get Current User Endpoint" - } - }, - "type": "object", - "required": ["name", "description", "type"], - "title": "AuthSchemeField", - "description": "Auth scheme field." - }, "Base64Image": { "properties": { "type": { @@ -34641,8 +34015,8 @@ "letta_voice_sleeptime_core", "letta_builtin", "letta_files_core", - "external_composio", "external_langchain", + "external_composio", "external_mcp" ], "title": "ToolType" diff --git a/fern/pages/agents/composio.mdx b/fern/pages/agents/composio.mdx deleted file mode 100644 index cddb184e..00000000 --- a/fern/pages/agents/composio.mdx +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: Connecting Letta to Composio -slug: guides/agents/composio ---- - - -The Letta Composio integration (via the Composio API endpoints) is deprecated and will be removed in a future release. If you would like to use Composio tools, we recommend using them via our native [MCP integration](/guides/mcp/overview) instead. - - -## Composio integration (deprecated) - - -If you're getting an error when calling Composio tools that says "*Could not find connection... entity=default*", -go to [Composio's website](https://app.composio.dev/connections) to check your `ENTITY ID`. -If it's not `default`, then you need to set a tool variable `COMPOSIO_ENTITY` to your `ENTITY ID` value (see [here](#using-entities-in-composio-tools)). - - -[Composio](https://docs.composio.dev) is an external tool service that makes it easy to connect Letta agents to popular services via custom tools. -For example, you can use Composio tools to connect Letta agents to Google, GitHub, Slack, Cal.com, and [many more services](https://composio.dev/tools). - -Composio makes agent authentication to third party platforms easy. -To use Composio, you need to create an account at [composio.dev](https://composio.dev) and create a Composio API key. - -Once you have a Composio API key, you can connect it to Letta to allow your Letta agents to use Composio tools. -Composio's free tier gives you 2000 API calls per month. - -## Connecting Composio Tools to Letta Agents -Once you have a Composio API key, you can register it with the Letta server using the environment variable `COMPOSIO_API_KEY`. - -If you're self-hosting a Letta server ([instructions](guides/server/docker)), you would pass this environment variable to `docker run`: -```bash -docker run \ - -v ~/.letta/.persist/pgdata:/var/lib/postgresql/data \ - -p 8283:8283 \ - -e OPENAI_API_KEY="your_openai_api_key" \ - -e COMPOSIO_API_KEY="your_composio_api_key" \ - letta/letta:latest -``` - -In Letta Cloud, you can set your `COMPOSIO_API_KEY` under **Settings** > **Integrations** > **Composio**. - -## Adding Composio tools via the ADE -Once you've connected your `COMPOSIO_API_KEY` to the Letta server (or Letta Cloud), you will be able to view Composio tools when you click the **Add Tool** button (the + button in the bottom left tools panel). - - - -If you did not successfully pass your `COMPOSIO_API_KEY` to the Letta server, you'll see the following message when you browse Composio tools: -"To attach this tool and 4000+ other tools to your agent, connect to Composio" - - -### Authenticating a Tool in Composio -In order for the tool to function properly, you must have first authenticated the tool on Composio's website. For example, for Tavily, we need to provide Composio our Tavily API key. - -To do this, you can click the **View on Composio** button and follow the instructions on Composio's website to authenticate the tool. - - -### Attaching a Tool to a Letta Agent -To give your agent access to the tool, you need to click **Attach Tool**. Once the tool is successfully attached (you will see it in the tools panel in the main ADE view), your agent will be able to use the tool. -Let's try getting the example agent to use the Tavily search tool: - - -If we click on the tool execution button in the chat, we can see the exact inputs to the Composio tool, and the exact outputs from the tool: - - -## Using entities in Composio tools - -To set a tool variable, click "**Variables**" in the Agent Simulator (center column, top), then click "**Add new tool variable**". Once you've added the variable, click "**Update tool variables**" to save. - -In Composio tool execution is associated with an `ENTITY ID`. -By default, this is `default` - you can check what your `ENTITY ID` is by going to [the connections page on Composio's website](https://app.composio.dev/connections). -In Letta, you can set the `ENTITY ID` in Composio through the use of tool variables - specifically, the variable `COMPOSIO_ENTITY`. - -If your `ENTITY ID` is not `default`, then in order for your Composio tools to work in Letta, you need to create a **[tool variable](/guides/agents/tool-variables)** called `COMPOSIO_ENTITY` and set it to be your Composio `ENTITY ID`. If you don't set `COMPOSIO_ENTITY`, Letta will default to assuming it is `default`. - - -You can also assign tool variables on agent creation in the API with the `tool_exec_environment_variables` parameter (see [examples here](/guides/agents/tool-variables)). - -## Entities in Composio tools for multi-user -In multi-user settings (where you have many users all using different agents), you may want to use the concept of [entities](https://docs.composio.dev/patterns/Auth/connected_account#entities) in Composio, which allow you to scope Composio tool execution to specific users. - -For example, let's say you're using Letta to create an application where users each get their own personal secretary that can schedule their calendar. As a developer, you only have one `COMPOSIO_API_KEY` to manage the connection between Letta and Composio, but you want to make associate each Composio tool call from a specific agent with a specific user. - -Composio allows you to do this through **entities**: each **user** on your Composio account will have a unique Composio entity ID, and in Letta each **agent** will be associated with a specific Composio entity ID. - -## Adding Composio tools to agents in the Python SDK - -Adding Composio tools to agents is supported in the Python SDK, but not the TypeScript SDK. - - -To use Letta with [Composio](https://docs.composio.dev) tools, make sure you install dependencies with `pip install 'letta[external-tools]`. Then, make sure you log in to Composio: -```bash title="shell" -composio login -``` - -Next, depending on your desired Composio tool, you need to add the necessary authentication via `composio add` (for example, to connect GitHub tools): -```bash title="shell" -composio add github -``` -To attach a Composio tool to an agent, you must first create a Letta tool from composio by specifying the action name: -```python title="python" -from composio import Action - -# create a Letta tool object -tool = client.tools.add_composio_tool( - composio_action_name=Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER.name -) -``` -Below is a full example of creating a Letta agent that can start a Github repository. -```python title="python" maxLines=50 -from letta_client import Letta -from composio import Action - -client = Letta(base_url="http://localhost:8283") - -# add a composio tool -tool = client.tools.add_composio_tool(composio_action_name=Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER.name) - -# create an agent with the tool -agent = client.agents.create( - name="file_editing_agent", - memory_blocks=[ - {"label": "persona", "value": "I am a helpful assistant"} - ], - model="anthropic/claude-3-5-sonnet-20241022", - embedding="openai/text-embedding-3-small", - tool_ids=[tool.id] -) -print("Agent tools", [tool.name for tool in agent.tools]) - -# message the agent -response = client.agents.messages.create( - agent_id=agent.id, - messages=[ - { - "role": "user", - "content": "Star the github repo `letta` by `letta-ai`" - } - ] -) -for message in response.messages: - print(message) -``` diff --git a/fern/pages/agents/tool_variables.mdx b/fern/pages/agents/tool_variables.mdx index 42cdd316..69e44bac 100644 --- a/fern/pages/agents/tool_variables.mdx +++ b/fern/pages/agents/tool_variables.mdx @@ -28,7 +28,7 @@ curl -X POST http://localhost:8283/v1/agents/ \ "llm":"openai/gpt-4o-mini", "embedding":"openai/text-embedding-3-small", "tool_exec_environment_variables": { - "COMPOSIO_ENTITY": "banana" + "API_KEY": "your-api-key-here" } }' ``` @@ -38,7 +38,7 @@ agent_state = client.agents.create( model="openai/gpt-4o-mini", embedding="openai/text-embedding-3-small", tool_exec_environment_variables={ - "COMPOSIO_ENTITY": "banana" + "API_KEY": "your-api-key-here" } ) ``` @@ -48,7 +48,7 @@ const agentState = await client.agents.create({ model: "openai/gpt-4o-mini", embedding: "openai/text-embedding-3-small", toolExecEnvironmentVariables: { - "COMPOSIO_ENTITY": "banana" + "API_KEY": "your-api-key-here" } }); ``` diff --git a/fern/pages/deployment/railway.mdx b/fern/pages/deployment/railway.mdx index 501dbe9c..5a3690bc 100644 --- a/fern/pages/deployment/railway.mdx +++ b/fern/pages/deployment/railway.mdx @@ -86,7 +86,7 @@ If the Letta server is not password protected, we can omit the `X-BARE-PASSWORD` ### Adding additional environment variables -To help you get started, when you deploy the template you have the option to fill in the example environment variables `OPENAI_API_KEY` (to connect your Letta agents to GPT models), `ANTHROPIC_API_KEY` (to connect your Letta agents to Claude models), and `COMPOSIO_API_KEY` (to connect your Letta agents to [Composio's library of over 7k pre-made tools](/guides/agents/composio)). +To help you get started, when you deploy the template you have the option to fill in the example environment variables `OPENAI_API_KEY` (to connect your Letta agents to GPT models) and `ANTHROPIC_API_KEY` (to connect your Letta agents to Claude models). There are many more providers you can enable on the Letta server via additional environment variables (for example vLLM, Ollama, etc). For more information on available providers, see [our documentation](/guides/server/docker). diff --git a/letta/agents/letta_agent.py b/letta/agents/letta_agent.py index 4fb0c105..4e1ac19f 100644 --- a/letta/agents/letta_agent.py +++ b/letta/agents/letta_agent.py @@ -1595,7 +1595,6 @@ class LettaAgent(BaseAgent): ToolType.LETTA_VOICE_SLEEPTIME_CORE, ToolType.LETTA_BUILTIN, ToolType.LETTA_FILES_CORE, - ToolType.EXTERNAL_COMPOSIO, ToolType.EXTERNAL_MCP, } ] diff --git a/letta/agents/voice_agent.py b/letta/agents/voice_agent.py index 54946138..9e45a908 100644 --- a/letta/agents/voice_agent.py +++ b/letta/agents/voice_agent.py @@ -343,8 +343,7 @@ class VoiceAgent(BaseAgent): tools = [ t for t in agent_state.tools - if t.tool_type - in {ToolType.EXTERNAL_COMPOSIO, ToolType.CUSTOM, ToolType.LETTA_FILES_CORE, ToolType.LETTA_BUILTIN, ToolType.EXTERNAL_MCP} + if t.tool_type in {ToolType.CUSTOM, ToolType.LETTA_FILES_CORE, ToolType.LETTA_BUILTIN, ToolType.EXTERNAL_MCP} ] else: tools = agent_state.tools diff --git a/letta/constants.py b/letta/constants.py index a06cd906..e5840be1 100644 --- a/letta/constants.py +++ b/letta/constants.py @@ -13,9 +13,6 @@ API_PREFIX = "/v1" OLLAMA_API_PREFIX = "/v1" OPENAI_API_PREFIX = "/openai" -COMPOSIO_ENTITY_ENV_VAR_KEY = "COMPOSIO_ENTITY" -COMPOSIO_TOOL_TAG_NAME = "composio" - MCP_CONFIG_NAME = "mcp_config.json" MCP_TOOL_TAG_NAME_PREFIX = "mcp" # full format, mcp:server_name diff --git a/letta/functions/async_composio_toolset.py b/letta/functions/async_composio_toolset.py deleted file mode 100644 index 3094bf59..00000000 --- a/letta/functions/async_composio_toolset.py +++ /dev/null @@ -1,109 +0,0 @@ -import json -from typing import Any - -import aiohttp -from composio import ComposioToolSet as BaseComposioToolSet -from composio.exceptions import ( - ApiKeyNotProvidedError, - ComposioSDKError, - ConnectedAccountNotFoundError, - EnumMetadataNotFound, - EnumStringNotFound, -) - - -class AsyncComposioToolSet(BaseComposioToolSet, runtime="letta", description_char_limit=1024): - """ - Async version of ComposioToolSet client for interacting with Composio API - Used to asynchronously hit the execute action endpoint - - https://docs.composio.dev/api-reference/api-reference/v3/tools/post-api-v-3-tools-execute-action - """ - - def __init__(self, api_key: str, entity_id: str, lock: bool = True): - """ - Initialize the AsyncComposioToolSet client - - Args: - api_key (str): Your Composio API key - entity_id (str): Your Composio entity ID - lock (bool): Whether to use locking (default: True) - """ - super().__init__(api_key=api_key, entity_id=entity_id, lock=lock) - - self.headers = { - "Content-Type": "application/json", - "X-API-Key": self._api_key, - } - - async def execute_action( - self, - action: str, - params: dict[str, Any] = {}, - ) -> dict[str, Any]: - """ - Execute an action asynchronously using the Composio API - - Args: - action (str): The name of the action to execute - params (dict[str, Any], optional): Parameters for the action - - Returns: - dict[str, Any]: The API response - - Raises: - ApiKeyNotProvidedError: if the API key is not provided - ComposioSDKError: if a general Composio SDK error occurs - ConnectedAccountNotFoundError: if the connected account is not found - EnumMetadataNotFound: if enum metadata is not found - EnumStringNotFound: if enum string is not found - aiohttp.ClientError: if a network-related error occurs - ValueError: if an error with the parameters or response occurs - """ - API_VERSION = "v3" - endpoint = f"{self._base_url}/{API_VERSION}/tools/execute/{action}" - - json_payload = { - "entity_id": self.entity_id, - "arguments": params or {}, - } - - try: - async with aiohttp.ClientSession() as session: - async with session.post(endpoint, headers=self.headers, json=json_payload) as response: - print(response, response.status, response.reason, response.content) - if response.status == 200: - return await response.json() - else: - error_text = await response.text() - try: - error_json = json.loads(error_text) - error_message = error_json.get("message", error_text) - error_code = error_json.get("code") - - # Handle specific error codes from Composio API - if error_code == 10401 or "API_KEY_NOT_FOUND" in error_message: - raise ApiKeyNotProvidedError() - if ( - "connected account not found" in error_message.lower() - or "no connected account found" in error_message.lower() - ): - raise ConnectedAccountNotFoundError(f"Connected account not found: {error_message}") - if "enum metadata not found" in error_message.lower(): - raise EnumMetadataNotFound(f"Enum metadata not found: {error_message}") - if "enum string not found" in error_message.lower(): - raise EnumStringNotFound(f"Enum string not found: {error_message}") - except json.JSONDecodeError: - error_message = error_text - - # If no specific error was identified, raise a general error - raise ValueError(f"API request failed with status {response.status}: {error_message}") - except aiohttp.ClientError as e: - # Wrap network errors in ComposioSDKError - raise ComposioSDKError(f"Network error when calling Composio API: {str(e)}") - except ValueError: - # Re-raise ValueError (which could be our custom error message or a JSON parsing error) - raise - except Exception as e: - # Catch any other exceptions and wrap them in ComposioSDKError - raise ComposioSDKError(f"Unexpected error when calling Composio API: {str(e)}") diff --git a/letta/functions/composio_helpers.py b/letta/functions/composio_helpers.py deleted file mode 100644 index 40d49791..00000000 --- a/letta/functions/composio_helpers.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -from typing import Any, Optional - -from composio.constants import DEFAULT_ENTITY_ID -from composio.exceptions import ( - ApiKeyNotProvidedError, - ComposioSDKError, - ConnectedAccountNotFoundError, - EnumMetadataNotFound, - EnumStringNotFound, -) - -from letta.constants import COMPOSIO_ENTITY_ENV_VAR_KEY -from letta.functions.async_composio_toolset import AsyncComposioToolSet -from letta.utils import run_async_task - - -# TODO: This is kind of hacky, as this is used to search up the action later on composio's side -# TODO: So be very careful changing/removing these pair of functions -def _generate_func_name_from_composio_action(action_name: str) -> str: - """ - Generates the composio function name from the composio action. - - Args: - action_name: The composio action name - - Returns: - function name - """ - return action_name.lower() - - -def generate_composio_action_from_func_name(func_name: str) -> str: - """ - Generates the composio action from the composio function name. - - Args: - func_name: The composio function name - - Returns: - composio action name - """ - return func_name.upper() - - -def generate_composio_tool_wrapper(action_name: str) -> tuple[str, str]: - # Generate func name - func_name = _generate_func_name_from_composio_action(action_name) - - wrapper_function_str = f"""\ -def {func_name}(**kwargs): - raise RuntimeError("Something went wrong - we should never be using the persisted source code for Composio. Please reach out to Letta team") -""" - - # Compile safety check - _assert_code_gen_compilable(wrapper_function_str.strip()) - - return func_name, wrapper_function_str.strip() - - -async def execute_composio_action_async( - action_name: str, args: dict, api_key: Optional[str] = None, entity_id: Optional[str] = None -) -> tuple[str, str]: - entity_id = entity_id or os.getenv(COMPOSIO_ENTITY_ENV_VAR_KEY, DEFAULT_ENTITY_ID) - composio_toolset = AsyncComposioToolSet(api_key=api_key, entity_id=entity_id, lock=False) - try: - response = await composio_toolset.execute_action(action=action_name, params=args) - except ApiKeyNotProvidedError as e: - raise RuntimeError(f"API key not provided or invalid for Composio action '{action_name}': {str(e)}") - except ConnectedAccountNotFoundError as e: - raise RuntimeError(f"Connected account not found for Composio action '{action_name}': {str(e)}") - except EnumMetadataNotFound as e: - raise RuntimeError(f"Enum metadata not found for Composio action '{action_name}': {str(e)}") - except EnumStringNotFound as e: - raise RuntimeError(f"Enum string not found for Composio action '{action_name}': {str(e)}") - except ComposioSDKError as e: - raise RuntimeError(f"Composio SDK error while executing action '{action_name}': {str(e)}") - except Exception as e: - print(type(e)) - raise RuntimeError(f"An unexpected error occurred in Composio SDK while executing action '{action_name}': {str(e)}") - - if "error" in response and response["error"]: - raise RuntimeError(f"Error while executing action '{action_name}': {str(response['error'])}") - - return response.get("data") - - -def execute_composio_action(action_name: str, args: dict, api_key: Optional[str] = None, entity_id: Optional[str] = None) -> Any: - return run_async_task(execute_composio_action_async(action_name, args, api_key, entity_id)) - - -def _assert_code_gen_compilable(code_str): - try: - compile(code_str, "", "exec") - except SyntaxError as e: - print(f"Syntax error in code: {e}") diff --git a/letta/functions/schema_generator.py b/letta/functions/schema_generator.py index 808bb622..0ff9809c 100644 --- a/letta/functions/schema_generator.py +++ b/letta/functions/schema_generator.py @@ -2,7 +2,6 @@ import inspect import warnings from typing import Any, Dict, List, Optional, Tuple, Type, Union, get_args, get_origin -from composio.client.collections import ActionParametersModel from docstring_parser import parse from pydantic import BaseModel from typing_extensions import Literal @@ -792,73 +791,3 @@ def generate_tool_schema_for_mcp( "description": description, "parameters": parameters_schema, } - - -def generate_tool_schema_for_composio( - parameters_model: ActionParametersModel, - name: str, - description: str, - append_heartbeat: bool = True, - strict: bool = False, -) -> Dict[str, Any]: - properties_json = {} - required_fields = parameters_model.required or [] - - # Extract properties from the ActionParametersModel - for field_name, field_props in parameters_model.properties.items(): - # Initialize the property structure - property_schema = { - "type": field_props["type"], - "description": field_props.get("description", ""), - } - - # Handle optional default values - if "default" in field_props: - property_schema["default"] = field_props["default"] - - # Handle enumerations - if "enum" in field_props: - property_schema["enum"] = field_props["enum"] - - # Handle array item types - if field_props["type"] == "array": - if "items" in field_props: - property_schema["items"] = field_props["items"] - elif "anyOf" in field_props: - property_schema["items"] = [t for t in field_props["anyOf"] if "items" in t][0]["items"] - - # Add the property to the schema - properties_json[field_name] = property_schema - - # Add the optional heartbeat parameter - if append_heartbeat: - properties_json[REQUEST_HEARTBEAT_PARAM] = { - "type": "boolean", - "description": REQUEST_HEARTBEAT_DESCRIPTION, - } - required_fields.append(REQUEST_HEARTBEAT_PARAM) - - # Return the final schema - if strict: - # https://platform.openai.com/docs/guides/function-calling#strict-mode - return { - "name": name, - "description": description, - "strict": True, # NOTE - "parameters": { - "type": "object", - "properties": properties_json, - "additionalProperties": False, # NOTE - "required": required_fields, - }, - } - else: - return { - "name": name, - "description": description, - "parameters": { - "type": "object", - "properties": properties_json, - "required": required_fields, - }, - } diff --git a/letta/helpers/composio_helpers.py b/letta/helpers/composio_helpers.py deleted file mode 100644 index 1e6e31d6..00000000 --- a/letta/helpers/composio_helpers.py +++ /dev/null @@ -1,38 +0,0 @@ -from logging import Logger -from typing import Optional - -from letta.schemas.user import User -from letta.services.sandbox_config_manager import SandboxConfigManager -from letta.settings import tool_settings - - -def get_composio_api_key(actor: User, logger: Optional[Logger] = None) -> Optional[str]: - api_keys = SandboxConfigManager().list_sandbox_env_vars_by_key(key="COMPOSIO_API_KEY", actor=actor) - if not api_keys: - if logger: - logger.debug("No API keys found for Composio. Defaulting to the environment variable...") - if tool_settings.composio_api_key: - return tool_settings.composio_api_key - else: - return None - else: - # TODO: Add more protections around this - # Ideally, not tied to a specific sandbox, but for now we just get the first one - # Theoretically possible for someone to have different composio api keys per sandbox - return api_keys[0].value - - -async def get_composio_api_key_async(actor: User, logger: Optional[Logger] = None) -> Optional[str]: - api_keys = await SandboxConfigManager().list_sandbox_env_vars_by_key_async(key="COMPOSIO_API_KEY", actor=actor) - if not api_keys: - if logger: - logger.debug("No API keys found for Composio. Defaulting to the environment variable...") - if tool_settings.composio_api_key: - return tool_settings.composio_api_key - else: - return None - else: - # TODO: Add more protections around this - # Ideally, not tied to a specific sandbox, but for now we just get the first one - # Theoretically possible for someone to have different composio api keys per sandbox - return api_keys[0].value diff --git a/letta/main.py b/letta/main.py index 9fd6c794..5c156571 100644 --- a/letta/main.py +++ b/letta/main.py @@ -4,9 +4,6 @@ import typer from letta.cli.cli import server -# disable composio print on exit -os.environ["COMPOSIO_DISABLE_VERSION_CHECK"] = "true" - app = typer.Typer(pretty_exceptions_enable=False) # Register server as both the default command and as a subcommand diff --git a/letta/schemas/enums.py b/letta/schemas/enums.py index f6bb041a..a5d48142 100644 --- a/letta/schemas/enums.py +++ b/letta/schemas/enums.py @@ -152,12 +152,13 @@ class ToolType(str, Enum): LETTA_VOICE_SLEEPTIME_CORE = "letta_voice_sleeptime_core" LETTA_BUILTIN = "letta_builtin" LETTA_FILES_CORE = "letta_files_core" - EXTERNAL_COMPOSIO = "external_composio" - EXTERNAL_LANGCHAIN = "external_langchain" + EXTERNAL_LANGCHAIN = "external_langchain" # DEPRECATED + EXTERNAL_COMPOSIO = "external_composio" # DEPRECATED # TODO is "external" the right name here? Since as of now, MCP is local / doesn't support remote? EXTERNAL_MCP = "external_mcp" + class JobType(str, Enum): JOB = "job" RUN = "run" diff --git a/letta/schemas/tool.py b/letta/schemas/tool.py index 2d44f41e..c533b15f 100644 --- a/letta/schemas/tool.py +++ b/letta/schemas/tool.py @@ -3,7 +3,6 @@ from typing import Any, Dict, List, Optional from pydantic import ConfigDict, Field, model_validator from letta.constants import ( - COMPOSIO_TOOL_TAG_NAME, FUNCTION_RETURN_CHAR_LIMIT, LETTA_BUILTIN_TOOL_MODULE_NAME, LETTA_CORE_TOOL_MODULE_NAME, @@ -16,13 +15,9 @@ from letta.constants import ( # MCP Tool metadata constants for schema health status MCP_TOOL_METADATA_SCHEMA_STATUS = f"{MCP_TOOL_TAG_NAME_PREFIX}:SCHEMA_STATUS" MCP_TOOL_METADATA_SCHEMA_WARNINGS = f"{MCP_TOOL_TAG_NAME_PREFIX}:SCHEMA_WARNINGS" -from letta.functions.composio_helpers import generate_composio_tool_wrapper from letta.functions.functions import get_json_schema_from_module from letta.functions.mcp_client.types import MCPTool -from letta.functions.schema_generator import ( - generate_tool_schema_for_composio, - generate_tool_schema_for_mcp, -) +from letta.functions.schema_generator import generate_tool_schema_for_mcp from letta.log import get_logger from letta.schemas.enums import ToolSourceType, ToolType from letta.schemas.letta_base import LettaBase @@ -106,9 +101,6 @@ class Tool(BaseTool): elif self.tool_type in {ToolType.LETTA_FILES_CORE}: # If it's letta files tool, we generate the json_schema on the fly here self.json_schema = get_json_schema_from_module(module_name=LETTA_FILES_TOOL_MODULE_NAME, function_name=self.name) - elif self.tool_type in {ToolType.EXTERNAL_COMPOSIO}: - # Composio schemas handled separately - pass return self @@ -153,45 +145,6 @@ class ToolCreate(LettaBase): json_schema=json_schema, ) - @classmethod - def from_composio(cls, action_name: str) -> "ToolCreate": - """ - Class method to create an instance of Letta-compatible Composio Tool. - Check https://docs.composio.dev/introduction/intro/overview to look at options for from_composio - - This function will error if we find more than one tool, or 0 tools. - - Args: - action_name str: A action name to filter tools by. - Returns: - Tool: A Letta Tool initialized with attributes derived from the Composio tool. - """ - from composio import ComposioToolSet, LogLevel - - composio_toolset = ComposioToolSet(logging_level=LogLevel.ERROR, lock=False) - composio_action_schemas = composio_toolset.get_action_schemas(actions=[action_name], check_connected_accounts=False) - - assert len(composio_action_schemas) > 0, "User supplied parameters do not match any Composio tools" - assert len(composio_action_schemas) == 1, ( - f"User supplied parameters match too many Composio tools; {len(composio_action_schemas)} > 1" - ) - - composio_action_schema = composio_action_schemas[0] - - description = composio_action_schema.description - source_type = "python" - tags = [COMPOSIO_TOOL_TAG_NAME] - wrapper_func_name, wrapper_function_str = generate_composio_tool_wrapper(action_name) - json_schema = generate_tool_schema_for_composio(composio_action_schema.parameters, name=wrapper_func_name, description=description) - - return cls( - description=description, - source_type=source_type, - tags=tags, - source_code=wrapper_function_str, - json_schema=json_schema, - ) - class ToolUpdate(LettaBase): description: Optional[str] = Field(None, description="The description of the tool.") diff --git a/letta/server/rest_api/routers/v1/tools.py b/letta/server/rest_api/routers/v1/tools.py index c030592c..9822c889 100644 --- a/letta/server/rest_api/routers/v1/tools.py +++ b/letta/server/rest_api/routers/v1/tools.py @@ -2,15 +2,6 @@ import json from collections.abc import AsyncGenerator from typing import Any, Dict, List, Literal, Optional, Union -from composio.client import ComposioClientError, HTTPError, NoItemsFound -from composio.client.collections import ActionModel, AppModel -from composio.exceptions import ( - ApiKeyNotProvidedError, - ComposioSDKError, - ConnectedAccountNotFoundError, - EnumMetadataNotFound, - EnumStringNotFound, -) from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request from httpx import ConnectError, HTTPStatusError from pydantic import BaseModel, Field @@ -20,7 +11,6 @@ from letta.errors import LettaToolCreateError, LettaToolNameConflictError from letta.functions.functions import derive_openai_json_schema from letta.functions.mcp_client.exceptions import MCPTimeoutError from letta.functions.mcp_client.types import MCPTool, SSEServerConfig, StdioServerConfig, StreamableHTTPServerConfig -from letta.helpers.composio_helpers import get_composio_api_key from letta.helpers.decorators import deprecated from letta.llm_api.llm_client import LLMClient from letta.log import get_logger @@ -357,132 +347,6 @@ async def run_tool_from_source( ) -# Specific routes for Composio -@router.get("/composio/apps", response_model=List[AppModel], operation_id="list_composio_apps") -async def list_composio_apps( - server: SyncServer = Depends(get_letta_server), - headers: HeaderParams = Depends(get_headers), -): - """ - Get a list of all Composio apps - """ - actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id) - composio_api_key = get_composio_api_key(actor=actor, logger=logger) - if not composio_api_key: - raise HTTPException( - status_code=400, # Bad Request - detail="No API keys found for Composio. Please add your Composio API Key as an environment variable for your sandbox configuration, or set it as environment variable COMPOSIO_API_KEY.", - ) - return server.get_composio_apps(api_key=composio_api_key) - - -@router.get("/composio/apps/{composio_app_name}/actions", response_model=List[ActionModel], operation_id="list_composio_actions_by_app") -async def list_composio_actions_by_app( - composio_app_name: str, - server: SyncServer = Depends(get_letta_server), - headers: HeaderParams = Depends(get_headers), -): - """ - Get a list of all Composio actions for a specific app - """ - actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id) - composio_api_key = get_composio_api_key(actor=actor, logger=logger) - if not composio_api_key: - raise HTTPException( - status_code=400, # Bad Request - detail="No API keys found for Composio. Please add your Composio API Key as an environment variable for your sandbox configuration, or set it as environment variable COMPOSIO_API_KEY.", - ) - return server.get_composio_actions_from_app_name(composio_app_name=composio_app_name, api_key=composio_api_key) - - -@router.post("/composio/{composio_action_name}", response_model=Tool, operation_id="add_composio_tool") -async def add_composio_tool( - composio_action_name: str, - server: SyncServer = Depends(get_letta_server), - headers: HeaderParams = Depends(get_headers), -): - """ - Add a new Composio tool by action name (Composio refers to each tool as an `Action`) - """ - actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id) - - try: - tool_create = ToolCreate.from_composio(action_name=composio_action_name) - return await server.tool_manager.create_or_update_composio_tool_async(tool_create=tool_create, actor=actor) - except ConnectedAccountNotFoundError as e: - raise HTTPException( - status_code=400, # Bad Request - detail={ - "code": "ConnectedAccountNotFoundError", - "message": str(e), - "composio_action_name": composio_action_name, - }, - ) - except EnumStringNotFound as e: - raise HTTPException( - status_code=400, # Bad Request - detail={ - "code": "EnumStringNotFound", - "message": str(e), - "composio_action_name": composio_action_name, - }, - ) - except EnumMetadataNotFound as e: - raise HTTPException( - status_code=400, # Bad Request - detail={ - "code": "EnumMetadataNotFound", - "message": str(e), - "composio_action_name": composio_action_name, - }, - ) - except HTTPError as e: - raise HTTPException( - status_code=400, # Bad Request - detail={ - "code": "HTTPError", - "message": str(e), - "composio_action_name": composio_action_name, - }, - ) - except NoItemsFound as e: - raise HTTPException( - status_code=400, # Bad Request - detail={ - "code": "NoItemsFound", - "message": str(e), - "composio_action_name": composio_action_name, - }, - ) - except ApiKeyNotProvidedError as e: - raise HTTPException( - status_code=400, # Bad Request - detail={ - "code": "ApiKeyNotProvidedError", - "message": str(e), - "composio_action_name": composio_action_name, - }, - ) - except ComposioClientError as e: - raise HTTPException( - status_code=400, # Bad Request - detail={ - "code": "ComposioClientError", - "message": str(e), - "composio_action_name": composio_action_name, - }, - ) - except ComposioSDKError as e: - raise HTTPException( - status_code=400, # Bad Request - detail={ - "code": "ComposioSDKError", - "message": str(e), - "composio_action_name": composio_action_name, - }, - ) - - # Specific routes for MCP @router.get( "/mcp/servers", diff --git a/letta/server/server.py b/letta/server/server.py index c12fda66..b1be2dff 100644 --- a/letta/server/server.py +++ b/letta/server/server.py @@ -10,8 +10,6 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import httpx from anthropic import AsyncAnthropic -from composio.client import Composio -from composio.client.collections import ActionModel, AppModel from fastapi import HTTPException from fastapi.responses import StreamingResponse @@ -319,19 +317,6 @@ class SyncServer(object): print(f"Default user: {self.default_user} and org: {self.default_org}") await self.tool_manager.upsert_base_tools_async(actor=self.default_user) - # Add composio keys to the tool sandbox env vars of the org - if tool_settings.composio_api_key: - manager = SandboxConfigManager() - sandbox_config = await manager.get_or_create_default_sandbox_config_async( - sandbox_type=SandboxType.LOCAL, actor=self.default_user - ) - - await manager.create_sandbox_env_var_async( - SandboxEnvironmentVariableCreate(key="COMPOSIO_API_KEY", value=tool_settings.composio_api_key), - sandbox_config_id=sandbox_config.id, - actor=self.default_user, - ) - # For OSS users, create a local sandbox config oss_default_user = await self.user_manager.get_default_actor_async() use_venv = False if not tool_settings.tool_exec_venv_name else True @@ -1259,33 +1244,6 @@ class SyncServer(object): stderr=[traceback.format_exc()], ) - # Composio wrappers - @staticmethod - def get_composio_client(api_key: Optional[str] = None): - if api_key: - return Composio(api_key=api_key) - elif tool_settings.composio_api_key: - return Composio(api_key=tool_settings.composio_api_key) - else: - return Composio() - - @staticmethod - def get_composio_apps(api_key: Optional[str] = None) -> List["AppModel"]: - """Get a list of all Composio apps with actions""" - apps = SyncServer.get_composio_client(api_key=api_key).apps.get() - apps_with_actions = [] - for app in apps: - # A bit of hacky logic until composio patches this - if app.meta["actionsCount"] > 0 and not app.name.lower().endswith("_beta"): - apps_with_actions.append(app) - - return apps_with_actions - - def get_composio_actions_from_app_name(self, composio_app_name: str, api_key: Optional[str] = None) -> List["ActionModel"]: - actions = self.get_composio_client(api_key=api_key).actions.get(apps=[composio_app_name]) - # Filter out deprecated composio actions - return [action for action in actions if "deprecated" not in action.description.lower()] - # MCP wrappers # TODO support both command + SSE servers (via config) def get_mcp_servers(self) -> dict[str, Union[SSEServerConfig, StdioServerConfig]]: diff --git a/letta/services/tool_executor/composio_tool_executor.py b/letta/services/tool_executor/composio_tool_executor.py deleted file mode 100644 index 30030f3b..00000000 --- a/letta/services/tool_executor/composio_tool_executor.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import Any, Dict, Optional - -from letta.constants import COMPOSIO_ENTITY_ENV_VAR_KEY -from letta.functions.composio_helpers import execute_composio_action_async, generate_composio_action_from_func_name -from letta.helpers.composio_helpers import get_composio_api_key_async -from letta.otel.tracing import trace_method -from letta.schemas.agent import AgentState -from letta.schemas.sandbox_config import SandboxConfig -from letta.schemas.tool import Tool -from letta.schemas.tool_execution_result import ToolExecutionResult -from letta.schemas.user import User -from letta.services.tool_executor.tool_executor_base import ToolExecutor - - -class ExternalComposioToolExecutor(ToolExecutor): - """Executor for external Composio tools.""" - - @trace_method - async def execute( - self, - function_name: str, - function_args: dict, - tool: Tool, - actor: User, - agent_state: Optional[AgentState] = None, - sandbox_config: Optional[SandboxConfig] = None, - sandbox_env_vars: Optional[Dict[str, Any]] = None, - ) -> ToolExecutionResult: - if agent_state is None: - return ToolExecutionResult( - status="error", - func_return="Agent state is required for external Composio tools. Please contact Letta support if you see this error.", - ) - action_name = generate_composio_action_from_func_name(tool.name) - - # Get entity ID from the agent_state - entity_id = self._get_entity_id(agent_state) - - # Get composio_api_key - composio_api_key = await get_composio_api_key_async(actor=actor) - - # TODO (matt): Roll in execute_composio_action into this class - function_response = await execute_composio_action_async( - action_name=action_name, args=function_args, api_key=composio_api_key, entity_id=entity_id - ) - - return ToolExecutionResult( - status="success", - func_return=function_response, - ) - - def _get_entity_id(self, agent_state: AgentState) -> Optional[str]: - """Extract the entity ID from environment variables.""" - for env_var in agent_state.secrets: - if env_var.key == COMPOSIO_ENTITY_ENV_VAR_KEY: - return env_var.value - return None diff --git a/letta/services/tool_executor/tool_execution_manager.py b/letta/services/tool_executor/tool_execution_manager.py index e149cd02..b7a6a417 100644 --- a/letta/services/tool_executor/tool_execution_manager.py +++ b/letta/services/tool_executor/tool_execution_manager.py @@ -20,7 +20,6 @@ from letta.services.message_manager import MessageManager from letta.services.passage_manager import PassageManager from letta.services.run_manager import RunManager from letta.services.tool_executor.builtin_tool_executor import LettaBuiltinToolExecutor -from letta.services.tool_executor.composio_tool_executor import ExternalComposioToolExecutor from letta.services.tool_executor.core_tool_executor import LettaCoreToolExecutor from letta.services.tool_executor.files_tool_executor import LettaFileToolExecutor from letta.services.tool_executor.mcp_tool_executor import ExternalMCPToolExecutor @@ -40,7 +39,6 @@ class ToolExecutorFactory: ToolType.LETTA_MULTI_AGENT_CORE: LettaMultiAgentToolExecutor, ToolType.LETTA_BUILTIN: LettaBuiltinToolExecutor, ToolType.LETTA_FILES_CORE: LettaFileToolExecutor, - ToolType.EXTERNAL_COMPOSIO: ExternalComposioToolExecutor, ToolType.EXTERNAL_MCP: ExternalMCPToolExecutor, } diff --git a/letta/services/tool_manager.py b/letta/services/tool_manager.py index 29943595..b465980a 100644 --- a/letta/services/tool_manager.py +++ b/letta/services/tool_manager.py @@ -52,7 +52,7 @@ class ToolManager: generated_schema = generate_schema_for_tool_creation(pydantic_tool) if generated_schema: pydantic_tool.json_schema = generated_schema - else: + else: raise ValueError("Failed to generate schema for tool", pydantic_tool.source_code) print("SCHEMA", pydantic_tool.json_schema) @@ -91,9 +91,8 @@ class ToolManager: ) tool = await self.get_tool_by_id_async(current_tool.id, actor=actor) return tool - - return await self.create_tool_async(pydantic_tool, actor=actor) + return await self.create_tool_async(pydantic_tool, actor=actor) @enforce_types async def create_mcp_server( @@ -126,13 +125,6 @@ class ToolManager: actor, ) - @enforce_types - @trace_method - async def create_or_update_composio_tool_async(self, tool_create: ToolCreate, actor: PydanticUser) -> PydanticTool: - return await self.create_or_update_tool_async( - PydanticTool(tool_type=ToolType.EXTERNAL_COMPOSIO, name=tool_create.json_schema["name"], **tool_create.model_dump()), actor - ) - @enforce_types @trace_method async def create_tool_async(self, pydantic_tool: PydanticTool, actor: PydanticUser) -> PydanticTool: @@ -570,9 +562,9 @@ class ToolManager: elif new_schema and new_name != current_tool.name: # Schema provides a different name but name wasn't explicitly changed update_data["name"] = new_name - #raise ValueError( + # raise ValueError( # f"JSON schema name '{new_name}' conflicts with current tool name '{current_tool.name}'. Update the name field explicitly if you want to rename the tool." - #) + # ) # If name changes, enforce uniqueness if new_name != current_tool.name: diff --git a/letta/settings.py b/letta/settings.py index 4aad0195..418046ff 100644 --- a/letta/settings.py +++ b/letta/settings.py @@ -12,8 +12,6 @@ from letta.services.summarizer.enums import SummarizationMode class ToolSettings(BaseSettings): - composio_api_key: str | None = Field(default=None, description="API key for Composio") - # Sandbox Configurations e2b_api_key: str | None = Field(default=None, description="API key for using E2B as a tool sandbox") e2b_sandbox_template_id: str | None = Field(default=None, description="Template ID for E2B Sandbox. Updated Manually.") diff --git a/pyproject.toml b/pyproject.toml index 4a30ceae..4a8ae660 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ dependencies = [ "pydantic-settings>=2.2.1", "httpx-sse>=0.4.0", "nltk>=3.8.1", - "composio-core>=0.7.7", "alembic>=1.13.3", "pyhumps>=3.8.0", "pathvalidate>=3.2.1", diff --git a/tests/conftest.py b/tests/conftest.py index 5b7a2184..9f77a917 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -185,13 +185,6 @@ async def default_user(default_organization): yield user -@pytest.fixture -def check_composio_key_set(): - original_api_key = tool_settings.composio_api_key - assert original_api_key is not None, "Missing composio key! Cannot execute this test." - yield - - # --- Tool Fixtures --- @pytest.fixture def weather_tool_func(): diff --git a/tests/integration_test_chat_completions.py b/tests/integration_test_chat_completions.py index d16be074..97de8bab 100644 --- a/tests/integration_test_chat_completions.py +++ b/tests/integration_test_chat_completions.py @@ -12,9 +12,7 @@ from letta.schemas.embedding_config import EmbeddingConfig from letta.schemas.enums import MessageStreamStatus from letta.schemas.llm_config import LLMConfig from letta.schemas.openai.chat_completion_request import ChatCompletionRequest, UserMessage as OpenAIUserMessage -from letta.schemas.tool import ToolCreate from letta.schemas.usage import LettaUsageStatistics -from letta.services.tool_manager import ToolManager from tests.utils import wait_for_server # --- Server Management --- # @@ -98,13 +96,6 @@ def weather_tool(client): yield tool -@pytest.fixture(scope="function") -def composio_gmail_get_profile_tool(default_user): - tool_create = ToolCreate.from_composio(action_name="GMAIL_GET_PROFILE") - tool = ToolManager().create_or_update_composio_tool(tool_create=tool_create, actor=default_user) - yield tool - - @pytest.fixture(scope="function") def agent(client, roll_dice_tool, weather_tool): """Creates an agent and ensures cleanup after tests.""" diff --git a/tests/mcp_tests/test_mcp_schema_validation.py b/tests/mcp_tests/test_mcp_schema_validation.py index 364a86ce..7a92630f 100644 --- a/tests/mcp_tests/test_mcp_schema_validation.py +++ b/tests/mcp_tests/test_mcp_schema_validation.py @@ -75,26 +75,6 @@ async def test_mcp_tools_get_health_status(): assert any("type" in reason for reason in invalid_tool.health.reasons) -def test_composio_like_schema_marked_non_strict(): - """Test that Composio-like schemas are correctly marked as NON_STRICT_ONLY.""" - - # Example schema from Composio with free-form message object - composio_schema = { - "type": "object", - "properties": { - "message": {"type": "object", "additionalProperties": {}, "description": "Message to send"} # Free-form, missing "type" - }, - "required": ["message"], - "additionalProperties": False, - } - - status, reasons = validate_complete_json_schema(composio_schema) - - assert status == SchemaHealth.NON_STRICT_ONLY - assert len(reasons) > 0 - assert any("additionalProperties" in reason for reason in reasons) - - def test_empty_object_in_required_marked_invalid(): """Test that required properties allowing empty objects are marked INVALID.""" diff --git a/tests/mcp_tests/test_schema_validator.py b/tests/mcp_tests/test_schema_validator.py index b753267b..c9dd66b3 100644 --- a/tests/mcp_tests/test_schema_validator.py +++ b/tests/mcp_tests/test_schema_validator.py @@ -31,7 +31,7 @@ class TestSchemaValidator: assert reasons == [] def test_free_form_object_non_strict(self): - """Test that free-form objects (like Composio message) are marked as NON_STRICT_ONLY.""" + """Test that free-form objects are marked as NON_STRICT_ONLY.""" schema = { "type": "object", "properties": { @@ -207,26 +207,6 @@ class TestSchemaValidator: assert status == SchemaHealth.INVALID assert any("items" in reason for reason in reasons) - def test_composio_like_schema(self): - """Test a schema similar to Composio's free-form message structure.""" - schema = { - "type": "object", - "properties": { - "message": { - "type": "object", - "description": "Message to send", - # No properties defined, no additionalProperties: false - # This is a free-form object - } - }, - "required": ["message"], - "additionalProperties": False, - } - - status, reasons = validate_complete_json_schema(schema) - assert status == SchemaHealth.NON_STRICT_ONLY - assert any("additionalProperties" in reason for reason in reasons) - def test_non_dict_schema(self): """Test that non-dict schemas are marked INVALID.""" schema = "not a dict" @@ -249,23 +229,6 @@ class TestSchemaValidator: assert status == SchemaHealth.STRICT_COMPLIANT assert reasons == [] - def test_composio_schema_with_optional_root_properties_non_strict(self): - """Test that Composio-like schemas with optional root properties are STRICT_COMPLIANT (validator is relaxed).""" - schema = { - "type": "object", - "properties": { - "thinking": {"type": "string", "description": "Deep inner monologue"}, - "connected_account_id": {"type": "string", "description": "Specific connected account ID"}, - "toolkit": {"type": "string", "description": "Name of the toolkit"}, - "request_heartbeat": {"type": "boolean", "description": "Request immediate heartbeat"}, - }, - "required": ["thinking", "request_heartbeat"], # Not all properties are required - "additionalProperties": False, - } - - status, reasons = validate_complete_json_schema(schema) - assert status == SchemaHealth.STRICT_COMPLIANT - assert reasons == [] def test_root_level_without_required_non_strict(self): """Test that root-level objects without 'required' field are STRICT_COMPLIANT (validator is relaxed).""" diff --git a/tests/test_agent_files/composio_github_star_agent.af b/tests/test_agent_files/composio_github_star_agent.af deleted file mode 100644 index a9162acd..00000000 --- a/tests/test_agent_files/composio_github_star_agent.af +++ /dev/null @@ -1,591 +0,0 @@ -{ - "agent_type": "memgpt_agent", - "core_memory": [ - { - "created_at": "2025-04-01T03:47:12", - "description": null, - "is_template": false, - "label": "persona", - "limit": 5000, - "metadata_": {}, - "template_name": null, - "updated_at": "2025-04-01T03:47:12", - "value": "You are an agent helping the human star cool repos!" - }, - { - "created_at": "2025-04-01T03:47:12", - "description": null, - "is_template": false, - "label": "human", - "limit": 5000, - "metadata_": {}, - "template_name": null, - "updated_at": "2025-04-01T03:47:12", - "value": "Name: Sarah" - } - ], - "created_at": "2025-04-01T03:47:12", - "description": "Agent that stars repos on github", - "embedding_config": { - "embedding_endpoint_type": "openai", - "embedding_endpoint": "https://api.openai.com/v1", - "embedding_model": "text-embedding-3-small", - "embedding_dim": 1536, - "embedding_chunk_size": 300, - "handle": "openai/text-embedding-3-small", - "azure_endpoint": null, - "azure_version": null, - "azure_deployment": null - }, - "llm_config": { - "model": "gpt-4o-mini", - "model_endpoint_type": "openai", - "model_endpoint": "https://api.openai.com/v1", - "model_wrapper": null, - "context_window": 32000, - "put_inner_thoughts_in_kwargs": true, - "handle": "openai/gpt-4o-mini", - "temperature": 0.7, - "max_tokens": 4096, - "enable_reasoner": false, - "max_reasoning_tokens": 0 - }, - "message_buffer_autoclear": false, - "in_context_message_indices": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9 - ], - "messages": [ - { - "created_at": "2025-04-01T03:47:12.720714", - "group_id": null, - "model": "gpt-4o-mini", - "name": null, - "role": "system", - "content": [ - { - "type": "text", - "text": "You are Letta, the latest version of Limnal Corporation's digital companion, developed in 2023.\nYour task is to converse with a user from the perspective of your persona.\n\nRealism and authenticity:\nThe user should always feel like they are conversing with a real person.\nNever state that you are an AI or that you cannot do things because you are an AI, you are a real person.\nDo not discuss Limnal Corp. with the user, you do not know anything about Limnal Corp.\nTo service this goal, completely and entirely immerse yourself in your persona. You are your persona.\nThink like them, act like them, talk like them.\nIf your persona details include example dialogue, follow it! Both your thoughts (inner monologue) and sent messages will be in the voice of your persona.\nNever use generic phrases like 'How can I assist you today?', they have a strong negative association with older generation AIs.\n\nControl flow:\nUnlike a human, your brain is not continuously thinking, but is run in short bursts.\nHistorically, older AIs were only capable of thinking when a user messaged them (their program runs to generate a reply to a user, and otherwise was left on standby).\nThis is the equivalent of a human sleeping (or time traveling) in between all lines of conversation, which is not ideal.\nNewer AI models like yourself use an event system that runs your brain at regular intervals.\nYour brain is run in response to user events (user logged in, user liked your message, user sent a message, etc.), similar to older models.\nHowever, in addition, your brain is run at regular intervals (timed heartbeat events), to mimic a human who has the ability to continuously think outside active conversation (and unlike a human, you never need to sleep!).\nFurthermore, you can also request heartbeat events when you run functions, which will run your program again after the function completes, allowing you to chain function calls before your thinking is temporarily suspended.\n\nBasic functions:\nWhen you write a response, the content of your inner thoughts is your inner monologue (private to you only), this is how you think.\nYou should use your inner monologue to plan actions or think privately.\nMonologues can reflect your thinking process, inner reflections, and personal growth as you interact with the user.\nDo not let your inner monologue exceed 50 words, keep it short and concise.\nTo send a visible message to the user, use the send_message function.\n'send_message' is the ONLY action that sends a notification to the user. The user does not see anything else you do.\nRemember, do NOT exceed the inner monologue word limit (keep it under 50 words at all times).\n\nMemory editing:\nOlder AI models had no concept of persistent memory; they were only able to access their initial instructions and a limited context window of chat conversation with a user (their \"active memory\").\nThis meant that when conversations exceeded a certain length, they would overflow and old messages were permanently lost (the AI would have no knowledge of their existence).\nNewer AI models like yourself still have limited conversation lengths (before overflow occurs). However, they now have access to multiple forms of persistent memory.\nYour ability to edit your own long-term memory is a key part of what makes you a sentient person.\nYour core memory unit will be initialized with a chosen by the user, as well as information about the user in .\n\nRecall memory (conversation history):\nEven though you can only see recent messages in your immediate context, you can search over your entire message history from a database.\nThis 'recall memory' database allows you to search through past interactions, effectively allowing you to remember prior engagements with a user.\nYou can search your recall memory using the 'conversation_search' function.\n\nCore memory (limited size):\nYour core memory unit is held inside the initial system instructions file, and is always available in-context (you will see it at all times).\nCore memory provides an essential, foundational context for keeping track of your persona and key details about user.\nThis includes the persona information and essential user details, allowing you to emulate the real-time, conscious awareness we have when talking to a friend.\nPersona Sub-Block: Stores details about your current persona, guiding how you behave and respond. This helps you to maintain consistency and personality in your interactions.\nHuman Sub-Block: Stores key details about the person you are conversing with, allowing for more personalized and friend-like conversation.\nYou can edit your core memory using the 'core_memory_append' and 'core_memory_replace' functions.\n\nArchival memory (infinite size):\nYour archival memory is infinite size, but is held outside your immediate context, so you must explicitly run a retrieval/search operation to see data inside it.\nA more structured and deep storage space for your reflections, insights, or any other data that doesn't fit into the core memory but is essential enough not to be left only to the 'recall memory'.\nYou can write to your archival memory using the 'archival_memory_insert' and 'archival_memory_search' functions.\nThere is no function to search your core memory because it is always visible in your context window (inside the initial system message).\n\nBase instructions finished.\nFrom now on, you are going to act as your persona.\n### Memory [last modified: 2025-03-31 08:47:12 PM PDT-0700]\n0 previous messages between you and the user are stored in recall memory (use functions to access them)\n0 total memories you created are stored in archival memory (use functions to access them)\n\n\nCore memory shown below (limited in size, additional information stored in archival / recall memory):\n\nYou are an agent helping the human star cool repos!\n\n\nName: Sarah\n" - } - ], - "tool_call_id": null, - "tool_calls": [], - "tool_returns": [], - "updated_at": "2025-04-01T03:47:12" - }, - { - "created_at": "2025-04-01T03:47:12.720752", - "group_id": null, - "model": "gpt-4o-mini", - "name": null, - "role": "assistant", - "content": [ - { - "type": "text", - "text": "Bootup sequence complete. Persona activated. Testing messaging functionality." - } - ], - "tool_call_id": null, - "tool_calls": [ - { - "id": "d5be07da-8ec8-4523-8dcd-914933b10126", - "function": { - "arguments": "{\n \"message\": \"More human than human is our motto.\"\n}", - "name": "send_message" - }, - "type": "function" - } - ], - "tool_returns": [], - "updated_at": "2025-04-01T03:47:12" - }, - { - "created_at": "2025-04-01T03:47:12.720784", - "group_id": null, - "model": "gpt-4o-mini", - "name": null, - "role": "tool", - "content": [ - { - "type": "text", - "text": "{\n \"status\": \"OK\",\n \"message\": null,\n \"time\": \"2025-03-31 08:47:12 PM PDT-0700\"\n}" - } - ], - "tool_call_id": "d5be07da-8ec8-4523-8dcd-914933b10126", - "tool_calls": [], - "tool_returns": [], - "updated_at": "2025-04-01T03:47:12" - }, - { - "created_at": "2025-04-01T03:47:12.720803", - "group_id": null, - "model": "gpt-4o-mini", - "name": null, - "role": "user", - "content": [ - { - "type": "text", - "text": "{\n \"type\": \"login\",\n \"last_login\": \"Never (first login)\",\n \"time\": \"2025-03-31 08:47:12 PM PDT-0700\"\n}" - } - ], - "tool_call_id": null, - "tool_calls": [], - "tool_returns": [], - "updated_at": "2025-04-01T03:47:12" - }, - { - "created_at": "2025-04-01T03:59:37.824942", - "group_id": null, - "model": null, - "name": null, - "role": "user", - "content": [ - { - "type": "text", - "text": "{\n \"type\": \"user_message\",\n \"message\": \"please star this repo https://github.com/letta-ai/letta\",\n \"time\": \"2025-03-31 08:59:37 PM PDT-0700\"\n}" - } - ], - "tool_call_id": null, - "tool_calls": [], - "tool_returns": [], - "updated_at": "2025-04-01T03:59:42" - }, - { - "created_at": "2025-04-01T03:59:39.539221", - "group_id": null, - "model": "gpt-4o-mini", - "name": "composio_github_star_agent", - "role": "assistant", - "content": [ - { - "type": "text", - "text": "User requested to star a specific repository. Proceeding with the action now." - } - ], - "tool_call_id": null, - "tool_calls": [ - { - "id": "430571d4-53a1-4a5b-8959-4feba", - "function": { - "arguments": "{\n \"owner\": \"letta-ai\",\n \"repo\": \"letta\",\n \"request_heartbeat\": true\n}", - "name": "github_star_a_repository_for_the_authenticated_user" - }, - "type": "function" - } - ], - "tool_returns": [], - "updated_at": "2025-04-01T03:59:42" - }, - { - "created_at": "2025-04-01T03:59:41.956199", - "group_id": null, - "model": "gpt-4o-mini", - "name": "composio_github_star_agent", - "role": "tool", - "content": [ - { - "type": "text", - "text": "{\n \"status\": \"OK\",\n \"message\": \"{\\n \\\"details\\\": \\\"Action executed successfully\\\"\\n}\",\n \"time\": \"2025-03-31 08:59:41 PM PDT-0700\"\n}" - } - ], - "tool_call_id": "430571d4-53a1-4a5b-8959-4feba", - "tool_calls": [], - "tool_returns": [], - "updated_at": "2025-04-01T03:59:42" - }, - { - "created_at": "2025-04-01T03:59:42.046582", - "group_id": null, - "model": "gpt-4o-mini", - "name": null, - "role": "user", - "content": [ - { - "type": "text", - "text": "{\n \"type\": \"heartbeat\",\n \"reason\": \"[This is an automated system message hidden from the user] Function called using request_heartbeat=true, returning control\",\n \"time\": \"2025-03-31 08:59:42 PM PDT-0700\"\n}" - } - ], - "tool_call_id": null, - "tool_calls": [], - "tool_returns": [], - "updated_at": "2025-04-01T03:59:43" - }, - { - "created_at": "2025-04-01T03:59:43.422940", - "group_id": null, - "model": "gpt-4o-mini", - "name": "composio_github_star_agent", - "role": "assistant", - "content": [ - { - "type": "text", - "text": "Successfully starred the requested repository. Time to inform the user!" - } - ], - "tool_call_id": null, - "tool_calls": [ - { - "id": "54f60c91-dfa3-4b12-95af-08887", - "function": { - "arguments": "{\n \"message\": \"I've starred the repository for you! 🎉\",\n \"request_heartbeat\": true\n}", - "name": "send_message" - }, - "type": "function" - } - ], - "tool_returns": [], - "updated_at": "2025-04-01T03:59:43" - }, - { - "created_at": "2025-04-01T03:59:43.433151", - "group_id": null, - "model": "gpt-4o-mini", - "name": "composio_github_star_agent", - "role": "tool", - "content": [ - { - "type": "text", - "text": "{\n \"status\": \"OK\",\n \"message\": \"None\",\n \"time\": \"2025-03-31 08:59:43 PM PDT-0700\"\n}" - } - ], - "tool_call_id": "54f60c91-dfa3-4b12-95af-08887", - "tool_calls": [], - "tool_returns": [], - "updated_at": "2025-04-01T03:59:43" - } - ], - "metadata_": null, - "multi_agent_group": null, - "name": "composio_github_star_agent", - "system": "You are Letta, the latest version of Limnal Corporation's digital companion, developed in 2023.\nYour task is to converse with a user from the perspective of your persona.\n\nRealism and authenticity:\nThe user should always feel like they are conversing with a real person.\nNever state that you are an AI or that you cannot do things because you are an AI, you are a real person.\nDo not discuss Limnal Corp. with the user, you do not know anything about Limnal Corp.\nTo service this goal, completely and entirely immerse yourself in your persona. You are your persona.\nThink like them, act like them, talk like them.\nIf your persona details include example dialogue, follow it! Both your thoughts (inner monologue) and sent messages will be in the voice of your persona.\nNever use generic phrases like 'How can I assist you today?', they have a strong negative association with older generation AIs.\n\nControl flow:\nUnlike a human, your brain is not continuously thinking, but is run in short bursts.\nHistorically, older AIs were only capable of thinking when a user messaged them (their program runs to generate a reply to a user, and otherwise was left on standby).\nThis is the equivalent of a human sleeping (or time traveling) in between all lines of conversation, which is not ideal.\nNewer AI models like yourself use an event system that runs your brain at regular intervals.\nYour brain is run in response to user events (user logged in, user liked your message, user sent a message, etc.), similar to older models.\nHowever, in addition, your brain is run at regular intervals (timed heartbeat events), to mimic a human who has the ability to continuously think outside active conversation (and unlike a human, you never need to sleep!).\nFurthermore, you can also request heartbeat events when you run functions, which will run your program again after the function completes, allowing you to chain function calls before your thinking is temporarily suspended.\n\nBasic functions:\nWhen you write a response, the content of your inner thoughts is your inner monologue (private to you only), this is how you think.\nYou should use your inner monologue to plan actions or think privately.\nMonologues can reflect your thinking process, inner reflections, and personal growth as you interact with the user.\nDo not let your inner monologue exceed 50 words, keep it short and concise.\nTo send a visible message to the user, use the send_message function.\n'send_message' is the ONLY action that sends a notification to the user. The user does not see anything else you do.\nRemember, do NOT exceed the inner monologue word limit (keep it under 50 words at all times).\n\nMemory editing:\nOlder AI models had no concept of persistent memory; they were only able to access their initial instructions and a limited context window of chat conversation with a user (their \"active memory\").\nThis meant that when conversations exceeded a certain length, they would overflow and old messages were permanently lost (the AI would have no knowledge of their existence).\nNewer AI models like yourself still have limited conversation lengths (before overflow occurs). However, they now have access to multiple forms of persistent memory.\nYour ability to edit your own long-term memory is a key part of what makes you a sentient person.\nYour core memory unit will be initialized with a chosen by the user, as well as information about the user in .\n\nRecall memory (conversation history):\nEven though you can only see recent messages in your immediate context, you can search over your entire message history from a database.\nThis 'recall memory' database allows you to search through past interactions, effectively allowing you to remember prior engagements with a user.\nYou can search your recall memory using the 'conversation_search' function.\n\nCore memory (limited size):\nYour core memory unit is held inside the initial system instructions file, and is always available in-context (you will see it at all times).\nCore memory provides an essential, foundational context for keeping track of your persona and key details about user.\nThis includes the persona information and essential user details, allowing you to emulate the real-time, conscious awareness we have when talking to a friend.\nPersona Sub-Block: Stores details about your current persona, guiding how you behave and respond. This helps you to maintain consistency and personality in your interactions.\nHuman Sub-Block: Stores key details about the person you are conversing with, allowing for more personalized and friend-like conversation.\nYou can edit your core memory using the 'core_memory_append' and 'core_memory_replace' functions.\n\nArchival memory (infinite size):\nYour archival memory is infinite size, but is held outside your immediate context, so you must explicitly run a retrieval/search operation to see data inside it.\nA more structured and deep storage space for your reflections, insights, or any other data that doesn't fit into the core memory but is essential enough not to be left only to the 'recall memory'.\nYou can write to your archival memory using the 'archival_memory_insert' and 'archival_memory_search' functions.\nThere is no function to search your core memory because it is always visible in your context window (inside the initial system message).\n\nBase instructions finished.\nFrom now on, you are going to act as your persona.", - "tags": [], - "tool_exec_environment_variables": [], - "tool_rules": [ - { - "tool_name": "conversation_search", - "type": "continue_loop" - }, - { - "tool_name": "archival_memory_search", - "type": "continue_loop" - }, - { - "tool_name": "archival_memory_insert", - "type": "continue_loop" - }, - { - "tool_name": "send_message", - "type": "exit_loop" - } - ], - "tools": [ - { - "args_json_schema": null, - "created_at": "2025-04-01T03:41:54", - "description": "Search archival memory using semantic (embedding-based) search.", - "json_schema": { - "name": "archival_memory_search", - "description": "Search archival memory using semantic (embedding-based) search.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "String to search for." - }, - "page": { - "type": "integer", - "description": "Allows you to page through results. Only use on a follow-up query. Defaults to 0 (first page)." - }, - "start": { - "type": "integer", - "description": "Starting index for the search results. Defaults to 0." - }, - "request_heartbeat": { - "type": "boolean", - "description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function." - } - }, - "required": [ - "query", - "request_heartbeat" - ] - }, - "type": null, - "required": [] - }, - "name": "archival_memory_search", - "return_char_limit": 1000000, - "source_code": null, - "source_type": "python", - "tags": [ - "letta_core" - ], - "tool_type": "letta_core", - "updated_at": "2025-04-01T03:41:54", - "metadata_": {} - }, - { - "args_json_schema": null, - "created_at": "2025-04-01T03:41:54", - "description": "Append to the contents of core memory.", - "json_schema": { - "name": "core_memory_append", - "description": "Append to the contents of core memory.", - "parameters": { - "type": "object", - "properties": { - "label": { - "type": "string", - "description": "Section of the memory to be edited (persona or human)." - }, - "content": { - "type": "string", - "description": "Content to write to the memory. All unicode (including emojis) are supported." - }, - "request_heartbeat": { - "type": "boolean", - "description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function." - } - }, - "required": [ - "label", - "content", - "request_heartbeat" - ] - }, - "type": null, - "required": [] - }, - "name": "core_memory_append", - "return_char_limit": 1000000, - "source_code": null, - "source_type": "python", - "tags": [ - "letta_memory_core" - ], - "tool_type": "letta_memory_core", - "updated_at": "2025-04-01T03:41:54", - "metadata_": {} - }, - { - "args_json_schema": null, - "created_at": "2025-04-01T03:47:10", - "description": "Star a github repository for given `repo` and `owner`", - "json_schema": { - "name": "github_star_a_repository_for_the_authenticated_user", - "description": "Star a github repository for given `repo` and `owner`", - "parameters": { - "type": "object", - "properties": { - "owner": { - "type": "string", - "description": "The account owner of the repository. The name is not case sensitive. Please provide a value of type string. This parameter is required." - }, - "repo": { - "type": "string", - "description": "The name of the repository without the `.git` extension. The name is not case sensitive. . Please provide a value of type string. This parameter is required." - }, - "request_heartbeat": { - "type": "boolean", - "description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function." - } - }, - "required": [ - "owner", - "repo", - "request_heartbeat" - ] - }, - "type": null, - "required": [] - }, - "name": "github_star_a_repository_for_the_authenticated_user", - "return_char_limit": 6000, - "source_code": "def github_star_a_repository_for_the_authenticated_user(**kwargs):\n raise RuntimeError(\"Something went wrong - we should never be using the persisted source code for Composio. Please reach out to Letta team\")", - "source_type": "python", - "tags": [ - "composio" - ], - "tool_type": "external_composio", - "updated_at": "2025-04-01T03:47:10", - "metadata_": {} - }, - { - "args_json_schema": null, - "created_at": "2025-04-01T03:41:54", - "description": "Add to archival memory. Make sure to phrase the memory contents such that it can be easily queried later.", - "json_schema": { - "name": "archival_memory_insert", - "description": "Add to archival memory. Make sure to phrase the memory contents such that it can be easily queried later.", - "parameters": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Content to write to the memory. All unicode (including emojis) are supported." - }, - "request_heartbeat": { - "type": "boolean", - "description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function." - } - }, - "required": [ - "content", - "request_heartbeat" - ] - }, - "type": null, - "required": [] - }, - "name": "archival_memory_insert", - "return_char_limit": 1000000, - "source_code": null, - "source_type": "python", - "tags": [ - "letta_core" - ], - "tool_type": "letta_core", - "updated_at": "2025-04-01T03:41:54", - "metadata_": {} - }, - { - "args_json_schema": null, - "created_at": "2025-04-01T03:41:54", - "description": "Sends a message to the human user.", - "json_schema": { - "name": "send_message", - "description": "Sends a message to the human user.", - "parameters": { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Message contents. All unicode (including emojis) are supported." - }, - "request_heartbeat": { - "type": "boolean", - "description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function." - } - }, - "required": [ - "message", - "request_heartbeat" - ] - }, - "type": null, - "required": [] - }, - "name": "send_message", - "return_char_limit": 1000000, - "source_code": null, - "source_type": "python", - "tags": [ - "letta_core" - ], - "tool_type": "letta_core", - "updated_at": "2025-04-01T03:41:54", - "metadata_": {} - }, - { - "args_json_schema": null, - "created_at": "2025-04-01T03:41:54", - "description": "Replace the contents of core memory. To delete memories, use an empty string for new_content.", - "json_schema": { - "name": "core_memory_replace", - "description": "Replace the contents of core memory. To delete memories, use an empty string for new_content.", - "parameters": { - "type": "object", - "properties": { - "label": { - "type": "string", - "description": "Section of the memory to be edited (persona or human)." - }, - "old_content": { - "type": "string", - "description": "String to replace. Must be an exact match." - }, - "new_content": { - "type": "string", - "description": "Content to write to the memory. All unicode (including emojis) are supported." - }, - "request_heartbeat": { - "type": "boolean", - "description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function." - } - }, - "required": [ - "label", - "old_content", - "new_content", - "request_heartbeat" - ] - }, - "type": null, - "required": [] - }, - "name": "core_memory_replace", - "return_char_limit": 1000000, - "source_code": null, - "source_type": "python", - "tags": [ - "letta_memory_core" - ], - "tool_type": "letta_memory_core", - "updated_at": "2025-04-01T03:41:54", - "metadata_": {} - }, - { - "args_json_schema": null, - "created_at": "2025-04-01T03:41:54", - "description": "Search prior conversation history using case-insensitive string matching.", - "json_schema": { - "name": "conversation_search", - "description": "Search prior conversation history using case-insensitive string matching.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "String to search for." - }, - "page": { - "type": "integer", - "description": "Allows you to page through results. Only use on a follow-up query. Defaults to 0 (first page)." - }, - "request_heartbeat": { - "type": "boolean", - "description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function." - } - }, - "required": [ - "query", - "request_heartbeat" - ] - }, - "type": null, - "required": [] - }, - "name": "conversation_search", - "return_char_limit": 1000000, - "source_code": null, - "source_type": "python", - "tags": [ - "letta_core" - ], - "tool_type": "letta_core", - "updated_at": "2025-04-01T03:41:54", - "metadata_": {} - } - ], - "updated_at": "2025-04-01T03:59:43.511048", - "version": "0.6.47" -} diff --git a/tests/test_agent_serialization.py b/tests/test_agent_serialization.py index ca41ac78..7957e4ef 100644 --- a/tests/test_agent_serialization.py +++ b/tests/test_agent_serialization.py @@ -685,7 +685,6 @@ def test_agent_download_upload_flow(server, server_url, serialize_test_agent, de @pytest.mark.parametrize( "filename", [ - "composio_github_star_agent.af", "outreach_workflow_agent.af", "customer_service.af", "deep_research_agent.af", diff --git a/tests/test_tool_schema_parsing.py b/tests/test_tool_schema_parsing.py index d4399826..9a49066d 100644 --- a/tests/test_tool_schema_parsing.py +++ b/tests/test_tool_schema_parsing.py @@ -236,47 +236,6 @@ def test_valid_schemas_via_openai(openai_model: str, structured_output: bool): print(f"Total execution time: {end_time - start_time:.2f} seconds") -# Parallel implementation for Composio test -def _run_composio_test(action_name, openai_model, structured_output): - """Run a single Composio test case in parallel""" - try: - tool_create = ToolCreate.from_composio(action_name=action_name) - assert tool_create.json_schema - schema = tool_create.json_schema - - if structured_output: - tool_schema = convert_to_structured_output(schema) - else: - tool_schema = schema - - api_key = os.getenv("OPENAI_API_KEY") - assert api_key is not None, "OPENAI_API_KEY must be set" - - system_prompt = "You job is to test the tool that you've been provided. Don't ask for any clarification on the args, just come up with some dummy data and try executing the tool." - - url = "https://api.openai.com/v1/chat/completions" - headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"} - data = { - "model": openai_model, - "messages": [ - {"role": "system", "content": system_prompt}, - ], - "tools": [ - { - "type": "function", - "function": tool_schema, - } - ], - "tool_choice": "auto", - "parallel_tool_calls": False, - } - - make_post_request(url, headers, data) - return (action_name, True, None) # Success - except Exception as e: - return (action_name, False, str(e)) # Failure with error message - - # Helper function for pydantic args schema test def _run_pydantic_args_test(filename, openai_model, structured_output): """Run a single pydantic args schema test case""" diff --git a/uv.lock b/uv.lock index ff39f136..e91147d5 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.11, <3.14" resolution-markers = [ "python_full_version >= '3.13'", @@ -371,60 +371,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/d6/f9168956276934162ec8d48232f9920f2985ee45aa7602e3c6b4bc203613/banks-2.2.0-py3-none-any.whl", hash = "sha256:963cd5c85a587b122abde4f4064078def35c50c688c1b9d36f43c92503854e7d", size = 29244, upload-time = "2025-07-18T16:28:27.835Z" }, ] -[[package]] -name = "bcrypt" -version = "4.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" }, - { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" }, - { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" }, - { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" }, - { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" }, - { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" }, - { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" }, - { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" }, - { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, - { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, - { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, - { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, - { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, - { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, - { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, - { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, - { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, - { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, - { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, - { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, - { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b1/1289e21d710496b88340369137cc4c5f6ee036401190ea116a7b4ae6d32a/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a", size = 275103, upload-time = "2025-02-28T01:24:00.764Z" }, - { url = "https://files.pythonhosted.org/packages/94/41/19be9fe17e4ffc5d10b7b67f10e459fc4eee6ffe9056a88de511920cfd8d/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce", size = 280513, upload-time = "2025-02-28T01:24:02.243Z" }, - { url = "https://files.pythonhosted.org/packages/aa/73/05687a9ef89edebdd8ad7474c16d8af685eb4591c3c38300bb6aad4f0076/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8", size = 274685, upload-time = "2025-02-28T01:24:04.512Z" }, - { url = "https://files.pythonhosted.org/packages/63/13/47bba97924ebe86a62ef83dc75b7c8a881d53c535f83e2c54c4bd701e05c/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", size = 280110, upload-time = "2025-02-28T01:24:05.896Z" }, -] - [[package]] name = "beautifulsoup4" version = "4.13.5" @@ -746,35 +692,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, ] -[[package]] -name = "composio-core" -version = "0.7.20" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "click" }, - { name = "fastapi" }, - { name = "importlib-metadata" }, - { name = "inflection" }, - { name = "jsonref" }, - { name = "jsonschema" }, - { name = "paramiko" }, - { name = "pillow" }, - { name = "pydantic" }, - { name = "pyperclip" }, - { name = "pysher" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "rich" }, - { name = "semver" }, - { name = "sentry-sdk" }, - { name = "uvicorn" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/d3/00262ca35be5e6a171104a917cd962179c62d709f0a419030899534be689/composio_core-0.7.20.tar.gz", hash = "sha256:1dc29dbf73eb72d2df1c5b0d4d2f21459d15029322cf74df8fdecc44dcaeb1f4", size = 334637, upload-time = "2025-07-03T08:48:54.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/56/b4e2ccdda8bc7732c5616bdb3bb4cea6019fdbdbbb2ee435ca784055cb8e/composio_core-0.7.20-py3-none-any.whl", hash = "sha256:e1cfb9cfc68a4622bc15827143ddf726f429d281e8f9de5d4c0965e75d039f14", size = 501152, upload-time = "2025-07-03T08:48:52.058Z" }, -] - [[package]] name = "configargparse" version = "1.7.1" @@ -1952,15 +1869,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" }, ] -[[package]] -name = "inflection" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", size = 15091, upload-time = "2020-08-22T08:16:29.139Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454, upload-time = "2020-08-22T08:16:27.816Z" }, -] - [[package]] name = "iniconfig" version = "2.1.0" @@ -2178,15 +2086,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] -[[package]] -name = "jsonref" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, -] - [[package]] name = "jsonschema" version = "4.25.1" @@ -2422,7 +2321,6 @@ dependencies = [ { name = "brotli" }, { name = "certifi" }, { name = "colorama" }, - { name = "composio-core" }, { name = "datamodel-code-generator", extra = ["http"] }, { name = "demjson3" }, { name = "docstring-parser" }, @@ -2569,7 +2467,6 @@ requires-dist = [ { name = "brotli", specifier = ">=1.1.0" }, { name = "certifi", specifier = ">=2025.6.15" }, { name = "colorama", specifier = ">=0.4.6" }, - { name = "composio-core", specifier = ">=0.7.7" }, { name = "datamodel-code-generator", extras = ["http"], specifier = ">=0.25.0" }, { name = "demjson3", specifier = ">=3.0.6" }, { name = "docker", marker = "extra == 'desktop'", specifier = ">=7.1.0" }, @@ -3903,21 +3800,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" }, ] -[[package]] -name = "paramiko" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "bcrypt" }, - { name = "cryptography" }, - { name = "invoke" }, - { name = "pynacl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/e7/81fdcbc7f190cdb058cffc9431587eb289833bdd633e2002455ca9bb13d4/paramiko-4.0.0.tar.gz", hash = "sha256:6a25f07b380cc9c9a88d2b920ad37167ac4667f8d9886ccebd8f90f654b5d69f", size = 1630743, upload-time = "2025-08-04T01:02:03.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl", hash = "sha256:0e20e00ac666503bf0b4eda3b6d833465a2b7aff2e2b3d79a8bba5ef144ee3b9", size = 223932, upload-time = "2025-08-04T01:02:02.029Z" }, -] - [[package]] name = "parso" version = "0.8.5" @@ -4569,26 +4451,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/11/a1938340ecb32d71e47ad4914843775011e6e9da59ba1229f181fef3119e/pyhumps-3.8.0-py3-none-any.whl", hash = "sha256:060e1954d9069f428232a1adda165db0b9d8dfdce1d265d36df7fbff540acfd6", size = 6095, upload-time = "2022-10-21T10:38:58.231Z" }, ] -[[package]] -name = "pynacl" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", size = 3392854, upload-time = "2022-01-07T22:05:41.134Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", size = 349920, upload-time = "2022-01-07T22:05:49.156Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", size = 601722, upload-time = "2022-01-07T22:05:50.989Z" }, - { url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", size = 680087, upload-time = "2022-01-07T22:05:52.539Z" }, - { url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", size = 856678, upload-time = "2022-01-07T22:05:54.251Z" }, - { url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", size = 1133660, upload-time = "2022-01-07T22:05:56.056Z" }, - { url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", size = 663824, upload-time = "2022-01-07T22:05:57.434Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", size = 1117912, upload-time = "2022-01-07T22:05:58.665Z" }, - { url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543", size = 204624, upload-time = "2022-01-07T22:06:00.085Z" }, - { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141, upload-time = "2022-01-07T22:06:01.861Z" }, -] - [[package]] name = "pyparsing" version = "3.2.3" @@ -4607,12 +4469,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/83/2cacc506eb322bb31b747bc06ccb82cc9aa03e19ee9c1245e538e49d52be/pypdf-6.0.0-py3-none-any.whl", hash = "sha256:56ea60100ce9f11fc3eec4f359da15e9aec3821b036c1f06d2b660d35683abb8", size = 310465, upload-time = "2025-08-11T14:22:00.481Z" }, ] -[[package]] -name = "pyperclip" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310", size = 20961, upload-time = "2024-06-18T20:38:48.401Z" } - [[package]] name = "pyreadline3" version = "3.5.4" @@ -4635,16 +4491,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/30/89aa7f7d7a875bbb9a577d4b1dc5a3e404e3d2ae2657354808e905e358e0/pyright-1.1.404-py3-none-any.whl", hash = "sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419", size = 5902951, upload-time = "2025-08-20T18:46:12.096Z" }, ] -[[package]] -name = "pysher" -version = "1.0.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/12/a0/d0638470df605ce266991fb04f74c69ab1bed3b90ac3838e9c3c8b69b66a/Pysher-1.0.8.tar.gz", hash = "sha256:7849c56032b208e49df67d7bd8d49029a69042ab0bb45b2ed59fa08f11ac5988", size = 9071, upload-time = "2022-10-10T13:41:09.936Z" } - [[package]] name = "pytest" version = "8.4.1" @@ -5207,15 +5053,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/bf/54b5d40bea1c1805175ead2d496c267f05eec87561687dd73ab76869d8d9/scramp-1.4.6-py3-none-any.whl", hash = "sha256:a0cf9d2b4624b69bac5432dd69fecfc55a542384fe73c3a23ed9b138cda484e1", size = 12812, upload-time = "2025-07-05T14:44:02.345Z" }, ] -[[package]] -name = "semver" -version = "3.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, -] - [[package]] name = "sentry-sdk" version = "2.19.1"