diff --git a/fern/pages/src/tool-calling/executing-tools.mdx b/fern/pages/src/tool-calling/executing-tools.mdx index 53777dcb1d..f6f98a6fb6 100644 --- a/fern/pages/src/tool-calling/executing-tools.mdx +++ b/fern/pages/src/tool-calling/executing-tools.mdx @@ -231,4 +231,114 @@ response = composio.tools.proxy( ``` -If you're interested in extending toolkits and creating custom tools, see [Custom tools](/docs/custom-tools). \ No newline at end of file +If you're interested in extending toolkits and creating custom tools, see [Custom tools](/docs/custom-tools). + +## Automatic File Handling + +Composio SDK includes automatic file handling for tools that work with files. When enabled (default), the SDK automatically handles file uploads and downloads during tool execution. + +### File Upload + +When a tool accepts file inputs (marked with `file_uploadable: true`), you can pass local file paths or URLs. Here's an example using Google Drive upload: + + +```typescript TypeScript maxLines=60 wordWrap +import { Composio } from '@composio/core'; +import path from 'path'; + +const composio = new Composio({ + apiKey: process.env.COMPOSIO_API_KEY +}); + +// Upload a local file to Google Drive +const result = await composio.tools.execute('GOOGLEDRIVE_UPLOAD_FILE', { + userId: 'default', + arguments: { + file_to_upload: path.join(__dirname, 'document.pdf') // Local file path + } +}); + +console.log(result.data); // Contains Google Drive file details +``` + +```python Python maxLines=60 wordWrap +import os + +from composio import Composio + +composio = Composio() + +# Upload a local file to Google Drive +result = composio.tools.execute( + "GOOGLEDRIVE_UPLOAD_FILE", + user_id="default", + arguments={ + "file_to_upload": os.path.join(os.getcwd(), "document.pdf") # Local file path + } +) + +print(result.data) # Contains Google Drive file details +``` + + +The SDK automatically: +1. Reads the file content +2. Uploads it to secure storage +3. Passes the file metadata to the tool + +### File Download + +When a tool returns file outputs, the SDK automatically: +1. Downloads the file to a local temporary directory +2. Provides the local file path in the response + + +```typescript TypeScript maxLines=60 wordWrap +// Download a file from Google Drive +const result = await composio.tools.execute('GOOGLEDRIVE_DOWNLOAD_FILE', { + userId: 'default', + arguments: { + file_id: 'your_file_id' + } +}); + +// Result includes local file path +console.log(result.data.file.uri); // "/path/to/downloaded/file.pdf" +``` + +```python Python maxLines=60 wordWrap +# Download a file from Google Drive +result = composio.tools.execute( + "GOOGLEDRIVE_DOWNLOAD_FILE", + user_id="default", + arguments={ + "file_id": "your_file_id" + } +) + +# Result includes local file path +print(result.data["file"]) # "/path/to/downloaded/file.pdf" +``` + + +### Disabling Auto File Handling + +You can disable automatic file handling when initializing the Typescript SDK: + + +```typescript TypeScript maxLines=60 wordWrap +const composio = new Composio({ + apiKey: process.env.COMPOSIO_API_KEY, + autoUploadDownloadFiles: false +}); + +// Now you need to handle files manually using composio.files API +const fileData = await composio.files.upload({ + filePath: path.join(__dirname, 'document.pdf'), + toolSlug: 'GOOGLEDRIVE_UPLOAD_FILE', + toolkitSlug: 'googledrive' +}); +``` + + +For more details on file handling, see [Auto Upload and Download Files](/docs/advanced/auto-upload-download). \ No newline at end of file diff --git a/fern/pages/src/tool-calling/fetching-tools.mdx b/fern/pages/src/tool-calling/fetching-tools.mdx index 2ea95becfc..e04ec5825a 100644 --- a/fern/pages/src/tool-calling/fetching-tools.mdx +++ b/fern/pages/src/tool-calling/fetching-tools.mdx @@ -22,6 +22,7 @@ When you fetch tools from a toolkit, the most important tools are returned first Composio determines the importance of a tool based on the usage and relevance of the tool. + ```typescript TypeScript maxLines=60 wordWrap const tools = await composio.tools.get( @@ -62,6 +63,37 @@ tools = composio.tools.get( ``` +**Filtering by scopes** + +When working with OAuth-based toolkits, you can filter tools based on their required scopes. This is useful when you want to: +- Get tools that match specific permission levels +- Ensure tools align with available user permissions +- Filter tools based on their required OAuth scopes + + +Scope filtering can only be used with a single toolkit at a time. + + + +```typescript TypeScript maxLines=60 wordWrap +// Get GitHub tools that require specific scopes +const tools = await composio.tools.get(userId, { + toolkits: ["GITHUB"], + scopes: ["repo"], // Only get tools requiring these scopes + limit: 10 +}); +``` +```python Python maxLines=60 wordWrap +# Get GitHub tools that require specific scopes +tools = composio.tools.get( + user_id, + toolkits=["GITHUB"], + scopes=["repo"], # Only get tools requiring these scopes + limit=10 +) +``` + + ## Filtering by tool You may specify the list of tools to fetch by directly providing the tool names. Browse the list of tools [here](/tools) to view and inspect the tools for each toolkit. @@ -118,11 +150,17 @@ You may also filter tools by searching for them. This is a good way to find tool This step runs a semantic search on the tool names and descriptions and returns the most relevant tools. - ```typescript TypeScript maxLines=60 wordWrap const tools = await composio.tools.get(userId, { search: "hubspot organize contacts", }); + +// Search within a specific toolkit +const tools = await composio.tools.get(userId, { + search: "repository issues", + toolkits: ["GITHUB"], // Optional: limit search to specific toolkit + limit: 5 // Optional: limit number of results +}); ``` ```python Python maxLines=60 wordWrap @@ -130,5 +168,39 @@ tools = composio.tools.get( user_id, search="hubspot organize contacts", ) + +# Search within a specific toolkit +tools = composio.tools.get( + user_id, + search="repository issues", + toolkits=["GITHUB"], # Optional: limit search to specific toolkit + limit=5 # Optional: limit number of results +) ``` - \ No newline at end of file + + +## Filter Combinations + +When fetching tools, you must use one of these filter combinations: + +1. **Tools Only**: Fetch specific tools by their slugs + ```typescript + { tools: ["TOOL_1", "TOOL_2"] } + ``` + +2. **Toolkits Only**: Fetch tools from specific toolkits + ```typescript + { toolkits: ["TOOLKIT_1", "TOOLKIT_2"], limit?: number } + ``` + +3. **Single Toolkit with Scopes**: Fetch tools requiring specific OAuth scopes + ```typescript + { toolkits: ["GITHUB"], scopes: ["read:repo"], limit?: number } + ``` + +4. **Search**: Search across all tools or within specific toolkits + ```typescript + { search: "query", toolkits?: string[], limit?: number } + ``` + +These combinations are mutually exclusive - you can't mix `tools` with `search` or use `scopes` with multiple toolkits. \ No newline at end of file diff --git a/python/composio/core/models/connected_accounts.py b/python/composio/core/models/connected_accounts.py index 228245901f..8407834701 100644 --- a/python/composio/core/models/connected_accounts.py +++ b/python/composio/core/models/connected_accounts.py @@ -227,25 +227,8 @@ def calcom_auth( ), } - def snowflake( - self, options: connected_account_create_params.ConnectionStateUnionMember9Val - ) -> connected_account_create_params.ConnectionState: - """ - Create a new connected account using Snowflake. - """ - return { - "auth_scheme": "SNOWFLAKE", - "val": t.cast( - connected_account_create_params.ConnectionStateUnionMember9Val, - { - **options, - "status": "ACTIVE", - }, - ), - } - def billcom_auth( - self, options: connected_account_create_params.ConnectionStateUnionMember10Val + self, options: connected_account_create_params.ConnectionStateUnionMember9Val ) -> connected_account_create_params.ConnectionState: """ Create a new connected account using Bill.com auth. @@ -253,7 +236,7 @@ def billcom_auth( return { "auth_scheme": "BILLCOM_AUTH", "val": t.cast( - connected_account_create_params.ConnectionStateUnionMember10Val, + connected_account_create_params.ConnectionStateUnionMember9Val, { **options, "status": "ACTIVE", @@ -262,7 +245,7 @@ def billcom_auth( } def basic_with_jwt( - self, options: connected_account_create_params.ConnectionStateUnionMember11Val + self, options: connected_account_create_params.ConnectionStateUnionMember10Val ) -> connected_account_create_params.ConnectionState: """ Create a new connected account using basic auth with JWT. @@ -270,7 +253,7 @@ def basic_with_jwt( return { "auth_scheme": "BASIC_WITH_JWT", "val": t.cast( - connected_account_create_params.ConnectionStateUnionMember11Val, + connected_account_create_params.ConnectionStateUnionMember10Val, { **options, "status": "ACTIVE", diff --git a/python/composio/core/models/custom_tools.py b/python/composio/core/models/custom_tools.py index ca6df6d228..bfdad0de36 100644 --- a/python/composio/core/models/custom_tools.py +++ b/python/composio/core/models/custom_tools.py @@ -88,6 +88,7 @@ def __parse_info(self) -> Tool: output_parameters={}, available_versions=[], version="1.0.0", + scopes=[], slug=self.slug, toolkit=tool_list_response.ItemToolkit( logo="", diff --git a/python/composio/core/models/tools.py b/python/composio/core/models/tools.py index 540e277fba..47cfdbff91 100644 --- a/python/composio/core/models/tools.py +++ b/python/composio/core/models/tools.py @@ -93,6 +93,8 @@ def get_raw_composio_tools( tools: t.Optional[list[str]] = None, search: t.Optional[str] = None, toolkits: t.Optional[list[str]] = None, + scopes: t.Optional[t.List[str]] = None, + limit: t.Optional[int] = None, ) -> list[Tool]: """ Get a list of tool schemas based on the provided filters. @@ -116,6 +118,8 @@ def get_raw_composio_tools( ",".join(toolkits) if toolkits else self._client.not_given ), search=search if search else self._client.not_given, + scopes=scopes, + limit=str(limit) if limit is not None else self._client.not_given, ).items ) return tools_list @@ -126,13 +130,17 @@ def _get( tools: t.Optional[list[str]] = None, search: t.Optional[str] = None, toolkits: t.Optional[list[str]] = None, + scopes: t.Optional[t.List[str]] = None, modifiers: t.Optional[Modifiers] = None, + limit: t.Optional[int] = None, ): """Get a list of tools based on the provided filters.""" tools_list = self.get_raw_composio_tools( tools=tools, search=search, toolkits=toolkits, + scopes=scopes, + limit=limit, ) if modifiers is not None: tools_list = [ @@ -197,6 +205,8 @@ def get( user_id: str, *, toolkits: list[str], + scopes: t.Optional[t.List[str]] = None, + limit: t.Optional[int] = None, modifiers: t.Optional[Modifiers] = None, ): """Get tools by toolkit slugs (Only important tools are returned)""" @@ -218,6 +228,7 @@ def get( *, toolkits: list[str], search: t.Optional[str] = None, + limit: t.Optional[int] = None, modifiers: t.Optional[Modifiers] = None, ): """Get tool by search term and/or toolkit slugs and search term""" @@ -230,7 +241,9 @@ def get( tools: t.Optional[list[str]] = None, search: t.Optional[str] = None, toolkits: t.Optional[list[str]] = None, + scopes: t.Optional[t.List[str]] = None, modifiers: t.Optional[Modifiers] = None, + limit: t.Optional[int] = None, ): """Get a tool or list of tools based on the provided arguments.""" if slug is not None: @@ -240,7 +253,9 @@ def get( tools=tools, search=search, toolkits=toolkits, + scopes=scopes, modifiers=modifiers, + limit=limit, ) def _wrap_execute_tool( diff --git a/python/composio/core/models/triggers.py b/python/composio/core/models/triggers.py index 6c8ff78c01..2c856e8eab 100644 --- a/python/composio/core/models/triggers.py +++ b/python/composio/core/models/triggers.py @@ -732,7 +732,7 @@ def create( ) def _get_connected_account_for_user(self, trigger: str, user_id: str) -> str: - toolkit = self.get_type(slug=trigger).toolkit.name + toolkit = self.get_type(slug=trigger).toolkit.slug connected_accounts = self._client.connected_accounts.list( toolkit_slugs=[toolkit] ) diff --git a/python/pyproject.toml b/python/pyproject.toml index 56182d9060..8fb56cb7b4 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.10,<4" dependencies = [ "pysher>=1.0.8", "pydantic>=2.6.4", - "composio-client==1.3.0", + "composio-client==1.4.0", "typing-extensions>=4.0.0", "openai", ] diff --git a/ts/docs/api/tools.md b/ts/docs/api/tools.md index 6086c5a639..eee673e484 100644 --- a/ts/docs/api/tools.md +++ b/ts/docs/api/tools.md @@ -178,13 +178,15 @@ type ToolsOnlyParams = { toolkits?: never; // Cannot be used with tools limit?: never; search?: never; -}; + scopes?: never; +} type ToolkitsOnlyParams = { tools?: never; // Cannot be used with toolkits toolkits: string[]; // List of toolkit slugs to filter by limit?: number; // Limit the number of results - search?: never; // Cannot be used with important flag, + search?: never; // Cannot be used with important flag + scopes?: string[]; // Optional list of required OAuth scopes }; type ToolkitSearchOnlyParams = { @@ -192,6 +194,7 @@ type ToolkitSearchOnlyParams = { toolkits?: string[]; // Optional list of toolkit slugs to filter by limit?: number; // Limit the number of results search: string; // Search term + scopes?: string[]; // Optional list of required OAuth scopes }; type ToolListParams = ToolsOnlyParams | ToolkitsOnlyParams | ToolkitSearchOnlyParams; @@ -202,6 +205,29 @@ Note: The parameters are organized into three mutually exclusive combinations: 1. Using `tools` array to fetch specific tools by their slugs 2. Using `toolkits` with optional `important` flag to fetch tools from specific toolkits 3. Using `search` with optional `toolkits` to search for tools by name/description +4. Using `scopes` can only be done with a single `toolkits` slug + +You can also filter tools by their scopes: + +```typescript +// Get tools with specific scopes +const scopedTools = await composio.tools.get('default', { + toolkits: ['github'], + scopes: ['read:repo', 'write:repo'], // Only get tools requiring these scopes +}); + +// Search tools with specific scopes +const searchedScopedTools = await composio.tools.get('default', { + search: 'repository', + scopes: ['read:repo'], // Only get tools requiring read:repo scope + limit: 10, +}); +``` + +The `scopes` parameter allows you to: +- Filter tools based on their required OAuth scopes +- Get tools that match specific permission levels +- Ensure tools align with available user permissions Examples: diff --git a/uv.lock b/uv.lock index 1a1fb55df6..24e60a287e 100644 --- a/uv.lock +++ b/uv.lock @@ -467,7 +467,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "composio-client", specifier = "==1.3.0" }, + { name = "composio-client", specifier = "==1.4.0" }, { name = "openai" }, { name = "pydantic", specifier = ">=2.6.4" }, { name = "pysher", specifier = ">=1.0.8" }, @@ -504,7 +504,7 @@ requires-dist = [ [[package]] name = "composio-client" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -514,9 +514,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/4f/410ae47457c65923a378da46caabf7187dcf301838e1fedc70b21cc63180/composio_client-1.3.0.tar.gz", hash = "sha256:5006ae7988201c32d4595500077b8405b7c6ea5b795c28ca5973df568b1c767e", size = 167940 } +sdist = { url = "https://files.pythonhosted.org/packages/35/35/c6bb5ce5f6b602efad86e4777a16ded43c51d9cb7f653fc93ab03e26741b/composio_client-1.4.0.tar.gz", hash = "sha256:c5524a02d16dabcdb71ae77e5fc00016dd4a447e36c9cb9ef7ff3ae8e87686d6", size = 167300 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/12/aa419b5d0bdbdc68413d730f8c722187d97bd0cf534ccf5280e1194f37aa/composio_client-1.3.0-py3-none-any.whl", hash = "sha256:ff01ce07fa1a89f2bd5448858e101f1c75b4142d27f618961437f6a60a8c16e5", size = 210297 }, + { url = "https://files.pythonhosted.org/packages/65/01/83484fbce94e5d67c13920911dc8c7a41413ef0d7baded21ec40185000aa/composio_client-1.4.0-py3-none-any.whl", hash = "sha256:81e8132b35cc9a675f4196b9126f02721d03f56125bcad9a74b720a01bfd40d0", size = 207495 }, ] [[package]]