chore: rm composio (#5151)

This commit is contained in:
Sarah Wooders
2025-10-04 21:20:05 -07:00
committed by Caren Thomas
parent 07a687880f
commit e07a589796
36 changed files with 22 additions and 2318 deletions

View File

@@ -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():

View File

@@ -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}}

View File

@@ -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 }}

View File

@@ -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}

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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"

View File

@@ -1,142 +0,0 @@
---
title: Connecting Letta to Composio
slug: guides/agents/composio
---
<Warning>
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.
</Warning>
## Composio integration (deprecated)
<Tip>
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)).
</Tip>
[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).
<img src="../../images/tavily.png" />
<Warning>
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"
</Warning>
### 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.
<img src="../../images/tavily_connect.png" />
### 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:
<img src="../../images/tavily_call.png" />
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:
<img src="../../images/tavily_call_expanded.png" />
## Using entities in Composio tools
<Tip>
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.
</Tip>
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`.
<img src="../../images/tool_variables.png" />
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
<Note>
Adding Composio tools to agents is supported in the Python SDK, but not the TypeScript SDK.
</Note>
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)
```

View File

@@ -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"
}
});
```

View File

@@ -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).

View File

@@ -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,
}
]

View File

@@ -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

View File

@@ -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

View File

@@ -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)}")

View File

@@ -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, "<string>", "exec")
except SyntaxError as e:
print(f"Syntax error in code: {e}")

View File

@@ -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,
},
}

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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.")

View File

@@ -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",

View File

@@ -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]]:

View File

@@ -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

View File

@@ -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,
}

View File

@@ -94,7 +94,6 @@ class ToolManager:
return await self.create_tool_async(pydantic_tool, actor=actor)
@enforce_types
async def create_mcp_server(
self, server_config: Union[StdioServerConfig, SSEServerConfig], actor: PydanticUser
@@ -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:

View File

@@ -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.")

View File

@@ -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",

View File

@@ -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():

View File

@@ -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."""

View File

@@ -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."""

View File

@@ -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)."""

File diff suppressed because one or more lines are too long

View File

@@ -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",

View File

@@ -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"""

165
uv.lock generated
View File

@@ -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"