diff --git a/autogpt_platform/backend/backend/copilot/prompting.py b/autogpt_platform/backend/backend/copilot/prompting.py index 2ed1c8dac428..da34d1e35c87 100644 --- a/autogpt_platform/backend/backend/copilot/prompting.py +++ b/autogpt_platform/backend/backend/copilot/prompting.py @@ -12,34 +12,18 @@ # Shared technical notes that apply to both SDK and baseline modes _SHARED_TOOL_NOTES = f"""\ -### Sharing files with the user -After saving a file to the persistent workspace with `write_workspace_file`, -share it with the user by embedding the `download_url` from the response in -your message as a Markdown link or image: - -- **Any file** — shows as a clickable download link: - `[report.csv](workspace://file_id#text/csv)` -- **Image** — renders inline in chat: - `![chart](workspace://file_id#image/png)` -- **Video** — renders inline in chat with player controls: - `![recording](workspace://file_id#video/mp4)` - -The `download_url` field in the `write_workspace_file` response is already -in the correct format — paste it directly after the `(` in the Markdown. - -### Passing file content to tools — @@agptfile: references -Instead of copying large file contents into a tool argument, pass a file -reference and the platform will load the content for you. - -Syntax: `@@agptfile:[-]` - -- `` **must** start with `workspace://` or `/` (absolute path): - - `workspace://` — workspace file by ID - - `workspace:///` — workspace file by virtual path - - `/absolute/local/path` — ephemeral or sdk_cwd file - - E2B sandbox absolute path (e.g. `/home/user/script.py`) -- `[-]` is an optional 1-indexed inclusive line range. -- URIs that do not start with `workspace://` or `/` are **not** expanded. +### Sharing files +After `write_workspace_file`, embed the `download_url` in Markdown: +- File: `[report.csv](workspace://file_id#text/csv)` +- Image: `![chart](workspace://file_id#image/png)` +- Video: `![recording](workspace://file_id#video/mp4)` + +### File references — @@agptfile: +Pass large file content to tools by reference: `@@agptfile:[-]` +- `workspace://` or `workspace:///` — workspace files +- `/absolute/path` — local/sandbox files +- `[start-end]` — optional 1-indexed line range +- Multiple refs per argument supported. Only `workspace://` and absolute paths are expanded. Examples: ``` @@ -50,21 +34,9 @@ @@agptfile:/home/user/script.py ``` -You can embed a reference inside any string argument, or use it as the entire -value. Multiple references in one argument are all expanded. +**Structured data**: When the entire argument is a single file reference, the platform auto-parses by extension/MIME. Supported: JSON, JSONL, CSV, TSV, YAML, TOML, Parquet, Excel (.xlsx only; legacy `.xls` is NOT supported). Unrecognised formats return plain string. -**Structured data**: When the **entire** argument value is a single file -reference (no surrounding text), the platform automatically parses the file -content based on its extension or MIME type. Supported formats: JSON, JSONL, -CSV, TSV, YAML, TOML, Parquet, and Excel (.xlsx — first sheet only). -For example, pass `@@agptfile:workspace://` where the file is a `.csv` and -the rows will be parsed into `list[list[str]]` automatically. If the format is -unrecognised or parsing fails, the content is returned as a plain string. -Legacy `.xls` files are **not** supported — only the modern `.xlsx` format. - -**Type coercion**: The platform also coerces expanded values to match the -block's expected input types. For example, if a block expects `list[list[str]]` -and the expanded value is a JSON string, it will be parsed into the correct type. +**Type coercion**: The platform auto-coerces expanded string values to match block input types (e.g. JSON string → `list[list[str]]`). ### Media file inputs (format: "file") Some block inputs accept media files — their schema shows `"format": "file"`. @@ -166,17 +138,12 @@ def _build_storage_supplement( ## Tool notes -### Shell commands -- The SDK built-in Bash tool is NOT available. Use the `bash_exec` MCP tool - for shell commands — it runs {sandbox_type}. - -### Working directory -- Your working directory is: `{working_dir}` -- All SDK file tools AND `bash_exec` operate on the same filesystem -- Use relative paths or absolute paths under `{working_dir}` for all file operations +### Shell & filesystem +- The SDK built-in Bash tool is NOT available. Use `bash_exec` for shell commands ({sandbox_type}). Working dir: `{working_dir}` +- SDK file tools (Read/Write/Edit/Glob/Grep) and `bash_exec` share one filesystem — use relative or absolute paths under this dir. +- `read_workspace_file`/`write_workspace_file` operate on **persistent cloud workspace storage** (separate from the working dir). ### Two storage systems — CRITICAL to understand - 1. **{storage_system_1_name}** (`{working_dir}`): {characteristics} {persistence} diff --git a/autogpt_platform/backend/backend/copilot/tools/add_understanding.py b/autogpt_platform/backend/backend/copilot/tools/add_understanding.py index b3291c5b0ea1..a5ef7caabf29 100644 --- a/autogpt_platform/backend/backend/copilot/tools/add_understanding.py +++ b/autogpt_platform/backend/backend/copilot/tools/add_understanding.py @@ -22,13 +22,12 @@ def name(self) -> str: @property def description(self) -> str: - return """Capture and store information about the user's business context, -workflows, pain points, and automation goals. Call this tool whenever the user -shares information about their business. Each call incrementally adds to the -existing understanding - you don't need to provide all fields at once. - -Use this to build a comprehensive profile that helps recommend better agents -and automations for the user's specific needs.""" + return ( + "Store user's business context, workflows, pain points, and automation goals. " + "Call whenever the user shares business info. Each call incrementally merges " + "with existing data — provide only the fields you have. " + "Builds a profile that helps recommend better agents for the user's needs." + ) @property def parameters(self) -> dict[str, Any]: diff --git a/autogpt_platform/backend/backend/copilot/tools/agent_browser.py b/autogpt_platform/backend/backend/copilot/tools/agent_browser.py index b75b16341720..b626e5362f55 100644 --- a/autogpt_platform/backend/backend/copilot/tools/agent_browser.py +++ b/autogpt_platform/backend/backend/copilot/tools/agent_browser.py @@ -410,18 +410,11 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Navigate to a URL using a real browser. Returns an accessibility " - "tree snapshot listing the page's interactive elements with @ref IDs " - "(e.g. @e3) that can be used with browser_act. " - "Session persists — cookies and login state carry over between calls. " - "Use this (with browser_act) for multi-step interaction: login flows, " - "form filling, button clicks, or anything requiring page interaction. " - "For plain static pages, prefer web_fetch — no browser overhead. " - "For authenticated pages: navigate to the login page first, use browser_act " - "to fill credentials and submit, then navigate to the target page. " - "Note: for slow SPAs, the returned snapshot may reflect a partially-loaded " - "state. If elements seem missing, use browser_act with action='wait' and a " - "CSS selector or millisecond delay, then take a browser_screenshot to verify." + "Navigate to a URL in a real browser. Returns accessibility tree with @ref IDs " + "for browser_act. Session persists (cookies/auth carry over). " + "For static pages, prefer web_fetch. " + "For SPAs, elements may load late — use browser_act with wait + browser_screenshot to verify. " + "For auth: navigate to login, fill creds and submit with browser_act, then navigate to target." ) @property @@ -431,13 +424,13 @@ def parameters(self) -> dict[str, Any]: "properties": { "url": { "type": "string", - "description": "The HTTP/HTTPS URL to navigate to.", + "description": "HTTP/HTTPS URL to navigate to.", }, "wait_for": { "type": "string", "enum": ["networkidle", "load", "domcontentloaded"], "default": "networkidle", - "description": "When to consider navigation complete. Use 'networkidle' for SPAs (default).", + "description": "Navigation completion strategy (default: networkidle).", }, }, "required": ["url"], @@ -556,14 +549,12 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Interact with the current browser page. Use @ref IDs from the " - "snapshot (e.g. '@e3') to target elements. Returns an updated snapshot. " - "Supported actions: click, dblclick, fill, type, scroll, hover, press, " + "Interact with the current browser page using @ref IDs from the snapshot. " + "Actions: click, dblclick, fill, type, scroll, hover, press, " "check, uncheck, select, wait, back, forward, reload. " - "fill clears the field before typing; type appends without clearing. " - "wait accepts a CSS selector (waits for element) or milliseconds string (e.g. '1000'). " - "Example login flow: fill @e1 with email → fill @e2 with password → " - "click @e3 (submit) → browser_navigate to the target page." + "fill clears field first; type appends. " + "wait accepts CSS selector or milliseconds (e.g. '1000'). " + "Returns updated snapshot." ) @property @@ -589,30 +580,21 @@ def parameters(self) -> dict[str, Any]: "forward", "reload", ], - "description": "The action to perform.", + "description": "Action to perform.", }, "target": { "type": "string", - "description": ( - "Element to target. Use @ref from snapshot (e.g. '@e3'), " - "a CSS selector, or a text description. " - "Required for: click, dblclick, fill, type, hover, check, uncheck, select. " - "For wait: a CSS selector to wait for, or milliseconds as a string (e.g. '1000')." - ), + "description": "@ref ID (e.g. '@e3'), CSS selector, or text. Required for: click, dblclick, fill, type, hover, check, uncheck, select. For wait: CSS selector or milliseconds string (e.g. '1000').", }, "value": { "type": "string", - "description": ( - "For fill/type: the text to enter. " - "For press: key name (e.g. 'Enter', 'Tab', 'Control+a'). " - "For select: the option value to select." - ), + "description": "Text for fill/type, key for press (e.g. 'Enter'), option for select.", }, "direction": { "type": "string", "enum": ["up", "down", "left", "right"], "default": "down", - "description": "For scroll: direction to scroll.", + "description": "Scroll direction (default: down).", }, }, "required": ["action"], @@ -759,12 +741,10 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Take a screenshot of the current browser page and save it to the workspace. " - "IMPORTANT: After calling this tool, immediately call read_workspace_file " - "with the returned file_id to display the image inline to the user — " - "the screenshot is not visible until you do this. " - "With annotate=true (default), @ref labels are overlaid on interactive " - "elements, making it easy to see which @ref ID maps to which element on screen." + "Screenshot the current browser page and save to workspace. " + "annotate=true overlays @ref labels on elements. " + "IMPORTANT: After calling, you MUST immediately call read_workspace_file with the " + "returned file_id to display the image inline." ) @property @@ -775,12 +755,12 @@ def parameters(self) -> dict[str, Any]: "annotate": { "type": "boolean", "default": True, - "description": "Overlay @ref labels on interactive elements (default: true).", + "description": "Overlay @ref labels (default: true).", }, "filename": { "type": "string", "default": "screenshot.png", - "description": "Filename to save in the workspace.", + "description": "Workspace filename (default: screenshot.png).", }, }, } diff --git a/autogpt_platform/backend/backend/copilot/tools/agent_output.py b/autogpt_platform/backend/backend/copilot/tools/agent_output.py index 4ccf323acab3..e6610d72f189 100644 --- a/autogpt_platform/backend/backend/copilot/tools/agent_output.py +++ b/autogpt_platform/backend/backend/copilot/tools/agent_output.py @@ -108,22 +108,12 @@ def name(self) -> str: @property def description(self) -> str: - return """Retrieve execution outputs from agents in the user's library. - - Identify the agent using one of: - - agent_name: Fuzzy search in user's library - - library_agent_id: Exact library agent ID - - store_slug: Marketplace format 'username/agent-name' - - Select which run to retrieve using: - - execution_id: Specific execution ID - - run_time: 'latest' (default), 'yesterday', 'last week', or ISO date 'YYYY-MM-DD' - - Wait for completion (optional): - - wait_if_running: Max seconds to wait if execution is still running (0-300). - If the execution is running/queued, waits up to this many seconds for completion. - Returns current status on timeout. If already finished, returns immediately. - """ + return ( + "Retrieve execution outputs from a library agent. " + "Identify by agent_name, library_agent_id, or store_slug. " + "Filter by execution_id or run_time. " + "Optionally wait for running executions." + ) @property def parameters(self) -> dict[str, Any]: @@ -132,32 +122,29 @@ def parameters(self) -> dict[str, Any]: "properties": { "agent_name": { "type": "string", - "description": "Agent name to search for in user's library (fuzzy match)", + "description": "Agent name (fuzzy match).", }, "library_agent_id": { "type": "string", - "description": "Exact library agent ID", + "description": "Library agent ID.", }, "store_slug": { "type": "string", - "description": "Marketplace identifier: 'username/agent-slug'", + "description": "Marketplace 'username/agent-name'.", }, "execution_id": { "type": "string", - "description": "Specific execution ID to retrieve", + "description": "Specific execution ID.", }, "run_time": { "type": "string", - "description": ( - "Time filter: 'latest', 'yesterday', 'last week', or 'YYYY-MM-DD'" - ), + "description": "Time filter: 'latest', 'today', 'yesterday', 'last week', 'last 7 days', 'last month', 'last 30 days', 'YYYY-MM-DD', or ISO datetime.", }, "wait_if_running": { "type": "integer", - "description": ( - "Max seconds to wait if execution is still running (0-300). " - "If running, waits for completion. Returns current state on timeout." - ), + "description": "Max seconds to wait if still running (0-300). Returns current state on timeout.", + "minimum": 0, + "maximum": 300, }, }, "required": [], diff --git a/autogpt_platform/backend/backend/copilot/tools/bash_exec.py b/autogpt_platform/backend/backend/copilot/tools/bash_exec.py index ce7bab5fc2fb..3f4c59a8bf82 100644 --- a/autogpt_platform/backend/backend/copilot/tools/bash_exec.py +++ b/autogpt_platform/backend/backend/copilot/tools/bash_exec.py @@ -42,15 +42,9 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Execute a Bash command or script. " - "Full Bash scripting is supported (loops, conditionals, pipes, " - "functions, etc.). " - "The working directory is shared with the SDK Read/Write/Edit/Glob/Grep " - "tools — files created by either are immediately visible to both. " - "Execution is killed after the timeout (default 30s, max 120s). " - "Returns stdout and stderr. " - "Useful for file manipulation, data processing, running scripts, " - "and installing packages." + "Execute a Bash command or script. Shares filesystem with SDK file tools. " + "Useful for scripts, data processing, and package installation. " + "Killed after timeout (default 30s, max 120s)." ) @property @@ -60,13 +54,11 @@ def parameters(self) -> dict[str, Any]: "properties": { "command": { "type": "string", - "description": "Bash command or script to execute.", + "description": "Bash command or script.", }, "timeout": { "type": "integer", - "description": ( - "Max execution time in seconds (default 30, max 120)." - ), + "description": "Max seconds (default 30, max 120).", "default": 30, }, }, diff --git a/autogpt_platform/backend/backend/copilot/tools/continue_run_block.py b/autogpt_platform/backend/backend/copilot/tools/continue_run_block.py index ea15b8a31181..48a4ff238c87 100644 --- a/autogpt_platform/backend/backend/copilot/tools/continue_run_block.py +++ b/autogpt_platform/backend/backend/copilot/tools/continue_run_block.py @@ -30,12 +30,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Continue executing a block after human review approval. " - "Use this after a run_block call returned review_required. " - "Pass the review_id from the review_required response. " - "The block will execute with the original pre-approved input data." - ) + return "Resume block execution after a run_block call returned review_required. Pass the review_id." @property def parameters(self) -> dict[str, Any]: @@ -44,10 +39,7 @@ def parameters(self) -> dict[str, Any]: "properties": { "review_id": { "type": "string", - "description": ( - "The review_id from a previous review_required response. " - "This resumes execution with the pre-approved input data." - ), + "description": "review_id from the review_required response.", }, }, "required": ["review_id"], diff --git a/autogpt_platform/backend/backend/copilot/tools/create_agent.py b/autogpt_platform/backend/backend/copilot/tools/create_agent.py index 549e01157e6b..decdf9ef1f74 100644 --- a/autogpt_platform/backend/backend/copilot/tools/create_agent.py +++ b/autogpt_platform/backend/backend/copilot/tools/create_agent.py @@ -23,12 +23,8 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Create a new agent workflow. Pass `agent_json` with the complete " - "agent graph JSON you generated using block schemas from find_block. " - "The tool validates, auto-fixes, and saves.\n\n" - "IMPORTANT: Before calling this tool, search for relevant existing agents " - "using find_library_agent that could be used as building blocks. " - "Pass their IDs in the library_agent_ids parameter." + "Create a new agent from JSON (nodes + links). Validates, auto-fixes, and saves. " + "Before calling, search for existing agents with find_library_agent." ) @property @@ -42,34 +38,21 @@ def parameters(self) -> dict[str, Any]: "properties": { "agent_json": { "type": "object", - "description": ( - "The agent JSON to validate and save. " - "Must contain 'nodes' and 'links' arrays, and optionally " - "'name' and 'description'." - ), + "description": "Agent graph with 'nodes' and 'links' arrays.", }, "library_agent_ids": { "type": "array", "items": {"type": "string"}, - "description": ( - "List of library agent IDs to use as building blocks." - ), + "description": "Library agent IDs as building blocks.", }, "save": { "type": "boolean", - "description": ( - "Whether to save the agent. Default is true. " - "Set to false for preview only." - ), + "description": "Save the agent (default: true). False for preview.", "default": True, }, "folder_id": { "type": "string", - "description": ( - "Optional folder ID to save the agent into. " - "If not provided, the agent is saved at root level. " - "Use list_folders to find available folders." - ), + "description": "Folder ID to save into (default: root).", }, }, "required": ["agent_json"], diff --git a/autogpt_platform/backend/backend/copilot/tools/customize_agent.py b/autogpt_platform/backend/backend/copilot/tools/customize_agent.py index d8e31eb83dc1..e869696d6ec2 100644 --- a/autogpt_platform/backend/backend/copilot/tools/customize_agent.py +++ b/autogpt_platform/backend/backend/copilot/tools/customize_agent.py @@ -23,9 +23,7 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Customize a marketplace or template agent. Pass `agent_json` " - "with the complete customized agent JSON. The tool validates, " - "auto-fixes, and saves." + "Customize a marketplace/template agent. Validates, auto-fixes, and saves." ) @property @@ -39,32 +37,21 @@ def parameters(self) -> dict[str, Any]: "properties": { "agent_json": { "type": "object", - "description": ( - "Complete customized agent JSON to validate and save. " - "Optionally include 'name' and 'description'." - ), + "description": "Customized agent JSON with nodes and links.", }, "library_agent_ids": { "type": "array", "items": {"type": "string"}, - "description": ( - "List of library agent IDs to use as building blocks." - ), + "description": "Library agent IDs as building blocks.", }, "save": { "type": "boolean", - "description": ( - "Whether to save the customized agent. Default is true." - ), + "description": "Save the agent (default: true). False for preview.", "default": True, }, "folder_id": { "type": "string", - "description": ( - "Optional folder ID to save the agent into. " - "If not provided, the agent is saved at root level. " - "Use list_folders to find available folders." - ), + "description": "Folder ID to save into (default: root).", }, }, "required": ["agent_json"], diff --git a/autogpt_platform/backend/backend/copilot/tools/edit_agent.py b/autogpt_platform/backend/backend/copilot/tools/edit_agent.py index 1cabc77012e6..0ad2d106061f 100644 --- a/autogpt_platform/backend/backend/copilot/tools/edit_agent.py +++ b/autogpt_platform/backend/backend/copilot/tools/edit_agent.py @@ -23,12 +23,8 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Edit an existing agent. Pass `agent_json` with the complete " - "updated agent JSON you generated. The tool validates, auto-fixes, " - "and saves.\n\n" - "IMPORTANT: Before calling this tool, if the changes involve adding new " - "functionality, search for relevant existing agents using find_library_agent " - "that could be used as building blocks." + "Edit an existing agent. Validates, auto-fixes, and saves. " + "Before calling, search for existing agents with find_library_agent." ) @property @@ -42,33 +38,20 @@ def parameters(self) -> dict[str, Any]: "properties": { "agent_id": { "type": "string", - "description": ( - "The ID of the agent to edit. " - "Can be a graph ID or library agent ID." - ), + "description": "Graph ID or library agent ID to edit.", }, "agent_json": { "type": "object", - "description": ( - "Complete updated agent JSON to validate and save. " - "Must contain 'nodes' and 'links'. " - "Include 'name' and/or 'description' if they need " - "to be updated." - ), + "description": "Updated agent JSON with nodes and links.", }, "library_agent_ids": { "type": "array", "items": {"type": "string"}, - "description": ( - "List of library agent IDs to use as building blocks for the changes." - ), + "description": "Library agent IDs as building blocks.", }, "save": { "type": "boolean", - "description": ( - "Whether to save the changes. " - "Default is true. Set to false for preview only." - ), + "description": "Save changes (default: true). False for preview.", "default": True, }, }, diff --git a/autogpt_platform/backend/backend/copilot/tools/feature_requests.py b/autogpt_platform/backend/backend/copilot/tools/feature_requests.py index 06f1faeaeebb..d296cea1103c 100644 --- a/autogpt_platform/backend/backend/copilot/tools/feature_requests.py +++ b/autogpt_platform/backend/backend/copilot/tools/feature_requests.py @@ -134,11 +134,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Search existing feature requests to check if a similar request " - "already exists before creating a new one. Returns matching feature " - "requests with their ID, title, and description." - ) + return "Search existing feature requests. Check before creating a new one." @property def parameters(self) -> dict[str, Any]: @@ -234,14 +230,9 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Create a new feature request or add a customer need to an existing one. " - "Always search first with search_feature_requests to avoid duplicates. " - "If a matching request exists, pass its ID as existing_issue_id to add " - "the user's need to it instead of creating a duplicate. " - "IMPORTANT: Never include personally identifiable information (PII) in " - "the title or description — no names, emails, phone numbers, company " - "names, or other identifying details. Write titles and descriptions in " - "generic, feature-focused language." + "Create a feature request or add need to existing one. " + "Search first to avoid duplicates. Pass existing_issue_id to add to existing. " + "Never include PII (names, emails, phone numbers, company names) in title/description." ) @property @@ -251,28 +242,15 @@ def parameters(self) -> dict[str, Any]: "properties": { "title": { "type": "string", - "description": ( - "Title for the feature request. Must be generic and " - "feature-focused — do not include any user names, emails, " - "company names, or other PII." - ), + "description": "Feature request title. No names, emails, or company info.", }, "description": { "type": "string", - "description": ( - "Detailed description of what the user wants and why. " - "Must not contain any personally identifiable information " - "(PII) — describe the feature need generically without " - "referencing specific users, companies, or contact details." - ), + "description": "What the user wants and why. No names, emails, or company info.", }, "existing_issue_id": { "type": "string", - "description": ( - "If adding a need to an existing feature request, " - "provide its Linear issue ID (from search results). " - "Omit to create a new feature request." - ), + "description": "Linear issue ID to add need to (from search results).", }, }, "required": ["title", "description"], diff --git a/autogpt_platform/backend/backend/copilot/tools/find_agent.py b/autogpt_platform/backend/backend/copilot/tools/find_agent.py index c62c8a3d857c..a3cd3155d820 100644 --- a/autogpt_platform/backend/backend/copilot/tools/find_agent.py +++ b/autogpt_platform/backend/backend/copilot/tools/find_agent.py @@ -18,10 +18,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Discover agents from the marketplace based on capabilities and " - "user needs, or look up a specific agent by its creator/slug ID." - ) + return "Search marketplace agents by capability, or look up by slug ('username/agent-name')." @property def parameters(self) -> dict[str, Any]: @@ -30,7 +27,7 @@ def parameters(self) -> dict[str, Any]: "properties": { "query": { "type": "string", - "description": "Search query describing what the user wants to accomplish, or a creator/slug ID (e.g. 'username/agent-name') for direct lookup. Use single keywords for best results.", + "description": "Search keywords, or 'username/agent-name' for direct slug lookup.", }, }, "required": ["query"], diff --git a/autogpt_platform/backend/backend/copilot/tools/find_block.py b/autogpt_platform/backend/backend/copilot/tools/find_block.py index fc35169c310f..79c5bdc529b7 100644 --- a/autogpt_platform/backend/backend/copilot/tools/find_block.py +++ b/autogpt_platform/backend/backend/copilot/tools/find_block.py @@ -54,13 +54,9 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Search for available blocks by name or description, or look up a " - "specific block by its ID. " - "Blocks are reusable components that perform specific tasks like " - "sending emails, making API calls, processing text, etc. " - "IMPORTANT: Use this tool FIRST to get the block's 'id' before calling run_block. " - "The response includes each block's id, name, and description. " - "Call run_block with the block's id **with no inputs** to see detailed inputs/outputs and execute it." + "Search blocks by name or description. Returns block IDs for run_block. " + "Always call this FIRST to get block IDs before using run_block. " + "Then call run_block with the block's id and empty input_data to see its detailed schema." ) @property @@ -70,19 +66,11 @@ def parameters(self) -> dict[str, Any]: "properties": { "query": { "type": "string", - "description": ( - "Search query to find blocks by name or description, " - "or a block ID (UUID) for direct lookup. " - "Use keywords like 'email', 'http', 'text', 'ai', etc." - ), + "description": "Search keywords (e.g. 'email', 'http', 'ai').", }, "include_schemas": { "type": "boolean", - "description": ( - "If true, include full input_schema and output_schema " - "for each block. Use when generating agent JSON that " - "needs block schemas. Default is false." - ), + "description": "Include full input/output schemas (for agent JSON generation).", "default": False, }, }, diff --git a/autogpt_platform/backend/backend/copilot/tools/find_library_agent.py b/autogpt_platform/backend/backend/copilot/tools/find_library_agent.py index f5964c8bbc3f..724676f8c571 100644 --- a/autogpt_platform/backend/backend/copilot/tools/find_library_agent.py +++ b/autogpt_platform/backend/backend/copilot/tools/find_library_agent.py @@ -19,13 +19,8 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Search for or list agents in the user's library. Use this to find " - "agents the user has already added to their library, including agents " - "they created or added from the marketplace. " - "When creating agents with sub-agent composition, use this to get " - "the agent's graph_id, graph_version, input_schema, and output_schema " - "needed for AgentExecutorBlock nodes. " - "Omit the query to list all agents." + "Search user's library agents. Returns graph_id, schemas for sub-agent composition. " + "Omit query to list all." ) @property @@ -35,10 +30,7 @@ def parameters(self) -> dict[str, Any]: "properties": { "query": { "type": "string", - "description": ( - "Search query to find agents by name or description. " - "Omit to list all agents in the library." - ), + "description": "Search by name/description. Omit to list all.", }, }, "required": [], diff --git a/autogpt_platform/backend/backend/copilot/tools/fix_agent.py b/autogpt_platform/backend/backend/copilot/tools/fix_agent.py index c108b060d0d7..817f9b4069e5 100644 --- a/autogpt_platform/backend/backend/copilot/tools/fix_agent.py +++ b/autogpt_platform/backend/backend/copilot/tools/fix_agent.py @@ -22,20 +22,10 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Auto-fix common issues in an agent JSON graph. Applies fixes for:\n" - "- Missing or invalid UUIDs on nodes and links\n" - "- StoreValueBlock prerequisites for ConditionBlock\n" - "- Double curly brace escaping in prompt templates\n" - "- AddToList/AddToDictionary prerequisite blocks\n" - "- CodeExecutionBlock output field naming\n" - "- Missing credentials configuration\n" - "- Node X coordinate spacing (800+ units apart)\n" - "- AI model default parameters\n" - "- Link static properties based on input schema\n" - "- Type mismatches (inserts conversion blocks)\n\n" - "Returns the fixed agent JSON plus a list of fixes applied. " - "After fixing, the agent is re-validated. If still invalid, " - "the remaining errors are included in the response." + "Auto-fix common agent JSON issues: missing/invalid UUIDs, StoreValueBlock prerequisites, " + "double curly brace escaping, AddToList/AddToDictionary prerequisites, credentials, " + "node spacing, AI model defaults, link static properties, and type mismatches. " + "Returns fixed JSON and list of fixes applied." ) @property diff --git a/autogpt_platform/backend/backend/copilot/tools/get_agent_building_guide.py b/autogpt_platform/backend/backend/copilot/tools/get_agent_building_guide.py index 018fd6df8484..a2dfc86eff66 100644 --- a/autogpt_platform/backend/backend/copilot/tools/get_agent_building_guide.py +++ b/autogpt_platform/backend/backend/copilot/tools/get_agent_building_guide.py @@ -42,12 +42,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Returns the complete guide for building agent JSON graphs, including " - "block IDs, link structure, AgentInputBlock, AgentOutputBlock, " - "AgentExecutorBlock (for sub-agent composition), and MCPToolBlock usage. " - "Call this before generating agent JSON to ensure correct structure." - ) + return "Get the agent JSON building guide (nodes, links, AgentExecutorBlock, MCPToolBlock usage). Call before generating agent JSON." @property def parameters(self) -> dict[str, Any]: diff --git a/autogpt_platform/backend/backend/copilot/tools/get_doc_page.py b/autogpt_platform/backend/backend/copilot/tools/get_doc_page.py index 87ec7225a5d1..3ae7505a23df 100644 --- a/autogpt_platform/backend/backend/copilot/tools/get_doc_page.py +++ b/autogpt_platform/backend/backend/copilot/tools/get_doc_page.py @@ -25,8 +25,7 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Get the full content of a documentation page by its path. " - "Use this after search_docs to read the complete content of a relevant page." + "Read full documentation page content by path (from search_docs results)." ) @property @@ -36,10 +35,7 @@ def parameters(self) -> dict[str, Any]: "properties": { "path": { "type": "string", - "description": ( - "The path to the documentation file, as returned by search_docs. " - "Example: 'platform/block-sdk-guide.md'" - ), + "description": "Doc file path (e.g. 'platform/block-sdk-guide.md').", }, }, "required": ["path"], diff --git a/autogpt_platform/backend/backend/copilot/tools/get_mcp_guide.py b/autogpt_platform/backend/backend/copilot/tools/get_mcp_guide.py index eff118fa1b74..5171166accb7 100644 --- a/autogpt_platform/backend/backend/copilot/tools/get_mcp_guide.py +++ b/autogpt_platform/backend/backend/copilot/tools/get_mcp_guide.py @@ -38,11 +38,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Returns the MCP tool guide: known hosted server URLs (Notion, Linear, " - "Stripe, Intercom, Cloudflare, Atlassian) and authentication workflow. " - "Call before using run_mcp_tool if you need a server URL or auth info." - ) + return "Get MCP server URLs and auth guide. Call before run_mcp_tool if you need a server URL or auth info." @property def parameters(self) -> dict[str, Any]: diff --git a/autogpt_platform/backend/backend/copilot/tools/manage_folders.py b/autogpt_platform/backend/backend/copilot/tools/manage_folders.py index a342786d688a..16f97e0001c3 100644 --- a/autogpt_platform/backend/backend/copilot/tools/manage_folders.py +++ b/autogpt_platform/backend/backend/copilot/tools/manage_folders.py @@ -88,10 +88,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Create a new folder in the user's library to organize agents. " - "Optionally nest it inside an existing folder using parent_id." - ) + return "Create a library folder. Use parent_id to nest inside another folder." @property def requires_auth(self) -> bool: @@ -104,22 +101,19 @@ def parameters(self) -> dict[str, Any]: "properties": { "name": { "type": "string", - "description": "Name for the new folder (max 100 chars).", + "description": "Folder name (max 100 chars).", }, "parent_id": { "type": "string", - "description": ( - "ID of the parent folder to nest inside. " - "Omit to create at root level." - ), + "description": "Parent folder ID (omit for root).", }, "icon": { "type": "string", - "description": "Optional icon identifier for the folder.", + "description": "Icon identifier.", }, "color": { "type": "string", - "description": "Optional hex color code (#RRGGBB).", + "description": "Hex color (#RRGGBB).", }, }, "required": ["name"], @@ -175,13 +169,9 @@ def name(self) -> str: @property def description(self) -> str: return ( - "List the user's library folders. " - "Omit parent_id to get the full folder tree. " - "Provide parent_id to list only direct children of that folder. " - "Set include_agents=true to also return the agents inside each folder " - "and root-level agents not in any folder. Always set include_agents=true " - "when the user asks about agents, wants to see what's in their folders, " - "or mentions agents alongside folders." + "List library folders. Omit parent_id for full tree. " + "Set include_agents=true when user asks about agents, wants to see " + "what's in their folders, or mentions agents alongside folders." ) @property @@ -195,17 +185,11 @@ def parameters(self) -> dict[str, Any]: "properties": { "parent_id": { "type": "string", - "description": ( - "List children of this folder. " - "Omit to get the full folder tree." - ), + "description": "List children of this folder (omit for full tree).", }, "include_agents": { "type": "boolean", - "description": ( - "Whether to include the list of agents inside each folder. " - "Defaults to false." - ), + "description": "Include agents in each folder (default: false).", }, }, "required": [], @@ -357,10 +341,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Move a folder to a different parent folder. " - "Set target_parent_id to null to move to root level." - ) + return "Move a folder. Set target_parent_id to null for root." @property def requires_auth(self) -> bool: @@ -373,14 +354,11 @@ def parameters(self) -> dict[str, Any]: "properties": { "folder_id": { "type": "string", - "description": "ID of the folder to move.", + "description": "Folder ID.", }, "target_parent_id": { "type": ["string", "null"], - "description": ( - "ID of the new parent folder. " - "Use null to move to root level." - ), + "description": "New parent folder ID (null for root).", }, }, "required": ["folder_id"], @@ -433,10 +411,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Delete a folder from the user's library. " - "Agents inside the folder are moved to root level (not deleted)." - ) + return "Delete a folder. Agents inside move to root (not deleted)." @property def requires_auth(self) -> bool: @@ -499,10 +474,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Move one or more agents to a folder. " - "Set folder_id to null to move agents to root level." - ) + return "Move agents to a folder. Set folder_id to null for root." @property def requires_auth(self) -> bool: @@ -516,13 +488,11 @@ def parameters(self) -> dict[str, Any]: "agent_ids": { "type": "array", "items": {"type": "string"}, - "description": "List of library agent IDs to move.", + "description": "Library agent IDs to move.", }, "folder_id": { "type": ["string", "null"], - "description": ( - "Target folder ID. Use null to move to root level." - ), + "description": "Target folder ID (null for root).", }, }, "required": ["agent_ids"], diff --git a/autogpt_platform/backend/backend/copilot/tools/run_agent.py b/autogpt_platform/backend/backend/copilot/tools/run_agent.py index 07e84b11bb4b..2625da1b238a 100644 --- a/autogpt_platform/backend/backend/copilot/tools/run_agent.py +++ b/autogpt_platform/backend/backend/copilot/tools/run_agent.py @@ -104,19 +104,11 @@ def name(self) -> str: @property def description(self) -> str: - return """Run or schedule an agent from the marketplace or user's library. - - The tool automatically handles the setup flow: - - Returns missing inputs if required fields are not provided - - Returns missing credentials if user needs to configure them - - Executes immediately if all requirements are met - - Schedules execution if cron expression is provided - - Identify the agent using either: - - username_agent_slug: Marketplace format 'username/agent-name' - - library_agent_id: ID of an agent in the user's library - - For scheduled execution, provide: schedule_name, cron, and optionally timezone.""" + return ( + "Run or schedule an agent. Automatically checks inputs and credentials. " + "Identify by username_agent_slug ('user/agent') or library_agent_id. " + "For scheduling, provide schedule_name + cron." + ) @property def parameters(self) -> dict[str, Any]: @@ -125,40 +117,38 @@ def parameters(self) -> dict[str, Any]: "properties": { "username_agent_slug": { "type": "string", - "description": "Agent identifier in format 'username/agent-name'", + "description": "Marketplace format 'username/agent-name'.", }, "library_agent_id": { "type": "string", - "description": "Library agent ID from user's library", + "description": "Library agent ID.", }, "inputs": { "type": "object", - "description": "Input values for the agent", + "description": "Input values for the agent.", "additionalProperties": True, }, "use_defaults": { "type": "boolean", - "description": "Set to true to run with default values (user must confirm)", + "description": "Run with default values (confirm with user first).", }, "schedule_name": { "type": "string", - "description": "Name for scheduled execution (triggers scheduling mode)", + "description": "Name for scheduled execution. Providing this triggers scheduling mode (also requires cron).", }, "cron": { "type": "string", - "description": "Cron expression (5 fields: min hour day month weekday)", + "description": "Cron expression (min hour day month weekday).", }, "timezone": { "type": "string", - "description": "IANA timezone for schedule (default: UTC)", + "description": "IANA timezone (default: UTC).", }, "wait_for_result": { "type": "integer", - "description": ( - "Max seconds to wait for execution to complete (0-300). " - "If >0, blocks until the execution finishes or times out. " - "Returns execution outputs when complete." - ), + "description": "Max seconds to wait for completion (0-300).", + "minimum": 0, + "maximum": 300, }, }, "required": [], diff --git a/autogpt_platform/backend/backend/copilot/tools/run_block.py b/autogpt_platform/backend/backend/copilot/tools/run_block.py index 5a2547b39e01..43108a600712 100644 --- a/autogpt_platform/backend/backend/copilot/tools/run_block.py +++ b/autogpt_platform/backend/backend/copilot/tools/run_block.py @@ -45,13 +45,10 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Execute a specific block with the provided input data. " - "IMPORTANT: You MUST call find_block first to get the block's 'id' - " - "do NOT guess or make up block IDs. " - "On first attempt (without input_data), returns detailed schema showing " - "required inputs and outputs. Then call again with proper input_data to execute. " - "If a block requires human review, use continue_run_block with the " - "review_id after the user approves." + "Execute a block. IMPORTANT: Always get block_id from find_block first " + "— do NOT guess or fabricate IDs. " + "Call with empty input_data to see schema, then with data to execute. " + "If review_required, use continue_run_block." ) @property @@ -61,28 +58,14 @@ def parameters(self) -> dict[str, Any]: "properties": { "block_id": { "type": "string", - "description": ( - "The block's 'id' field from find_block results. " - "NEVER guess this - always get it from find_block first." - ), - }, - "block_name": { - "type": "string", - "description": ( - "The block's human-readable name from find_block results. " - "Used for display purposes in the UI." - ), + "description": "Block ID from find_block results.", }, "input_data": { "type": "object", - "description": ( - "Input values for the block. " - "First call with empty {} to see the block's schema, " - "then call again with proper values to execute." - ), + "description": "Input values. Use {} first to see schema.", }, }, - "required": ["block_id", "block_name", "input_data"], + "required": ["block_id", "input_data"], } @property diff --git a/autogpt_platform/backend/backend/copilot/tools/run_mcp_tool.py b/autogpt_platform/backend/backend/copilot/tools/run_mcp_tool.py index b8f0b9432728..eb7a23143f68 100644 --- a/autogpt_platform/backend/backend/copilot/tools/run_mcp_tool.py +++ b/autogpt_platform/backend/backend/copilot/tools/run_mcp_tool.py @@ -57,10 +57,9 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Connect to an MCP (Model Context Protocol) server to discover and execute its tools. " - "Two-step: (1) call with server_url to list available tools, " - "(2) call again with server_url + tool_name + tool_arguments to execute. " - "Call get_mcp_guide for known server URLs and auth details." + "Discover and execute MCP server tools. " + "Call with server_url only to list tools, then with tool_name + tool_arguments to execute. " + "Call get_mcp_guide first for server URLs and auth." ) @property @@ -70,24 +69,15 @@ def parameters(self) -> dict[str, Any]: "properties": { "server_url": { "type": "string", - "description": ( - "URL of the MCP server (Streamable HTTP endpoint), " - "e.g. https://mcp.example.com/mcp" - ), + "description": "MCP server URL (Streamable HTTP endpoint).", }, "tool_name": { "type": "string", - "description": ( - "Name of the MCP tool to execute. " - "Omit on first call to discover available tools." - ), + "description": "Tool to execute. Omit to discover available tools.", }, "tool_arguments": { "type": "object", - "description": ( - "Arguments to pass to the selected tool. " - "Must match the tool's input schema returned during discovery." - ), + "description": "Arguments matching the tool's input schema.", }, }, "required": ["server_url"], diff --git a/autogpt_platform/backend/backend/copilot/tools/search_docs.py b/autogpt_platform/backend/backend/copilot/tools/search_docs.py index b09fe64a2c1a..8f08cef8d035 100644 --- a/autogpt_platform/backend/backend/copilot/tools/search_docs.py +++ b/autogpt_platform/backend/backend/copilot/tools/search_docs.py @@ -38,11 +38,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Search the AutoGPT platform documentation for information about " - "how to use the platform, build agents, configure blocks, and more. " - "Returns relevant documentation sections. Use get_doc_page to read full content." - ) + return "Search platform documentation by keyword. Use get_doc_page to read full results." @property def parameters(self) -> dict[str, Any]: @@ -51,10 +47,7 @@ def parameters(self) -> dict[str, Any]: "properties": { "query": { "type": "string", - "description": ( - "Search query to find relevant documentation. " - "Use natural language to describe what you're looking for." - ), + "description": "Documentation search query.", }, }, "required": ["query"], diff --git a/autogpt_platform/backend/backend/copilot/tools/tool_schema_test.py b/autogpt_platform/backend/backend/copilot/tools/tool_schema_test.py new file mode 100644 index 000000000000..05a7e4cbfb80 --- /dev/null +++ b/autogpt_platform/backend/backend/copilot/tools/tool_schema_test.py @@ -0,0 +1,119 @@ +"""Schema regression tests for all registered CoPilot tools. + +Validates that every tool in TOOL_REGISTRY produces a well-formed schema: +- description is non-empty +- all `required` fields exist in `properties` +- every property has a `type` and `description` +- total schema character budget does not regress past threshold +""" + +import json +from typing import Any, cast + +import pytest + +from backend.copilot.tools import TOOL_REGISTRY + +# Character budget (~4 chars/token heuristic, targeting ~8000 tokens) +_CHAR_BUDGET = 32_000 + + +@pytest.fixture(scope="module") +def all_tool_schemas() -> list[tuple[str, Any]]: + """Return (tool_name, openai_schema) pairs for every registered tool.""" + return [(name, tool.as_openai_tool()) for name, tool in TOOL_REGISTRY.items()] + + +def _get_parametrize_data() -> list[tuple[str, object]]: + """Build parametrize data at collection time.""" + return [(name, tool.as_openai_tool()) for name, tool in TOOL_REGISTRY.items()] + + +@pytest.mark.parametrize( + "tool_name,schema", + _get_parametrize_data(), + ids=[name for name, _ in _get_parametrize_data()], +) +class TestToolSchema: + """Validate schema invariants for every registered tool.""" + + def test_description_non_empty(self, tool_name: str, schema: dict) -> None: + desc = schema["function"].get("description", "") + assert desc, f"Tool '{tool_name}' has an empty description" + + def test_required_fields_exist_in_properties( + self, tool_name: str, schema: dict + ) -> None: + params = schema["function"].get("parameters", {}) + properties = params.get("properties", {}) + required = params.get("required", []) + for field in required: + assert field in properties, ( + f"Tool '{tool_name}': required field '{field}' " + f"not found in properties {list(properties.keys())}" + ) + + def test_every_property_has_type_and_description( + self, tool_name: str, schema: dict + ) -> None: + params = schema["function"].get("parameters", {}) + properties = params.get("properties", {}) + for prop_name, prop_def in properties.items(): + assert ( + "type" in prop_def + ), f"Tool '{tool_name}', property '{prop_name}' is missing 'type'" + assert ( + "description" in prop_def + ), f"Tool '{tool_name}', property '{prop_name}' is missing 'description'" + + +def test_browser_act_action_enum_complete() -> None: + """Assert browser_act action enum still contains all 14 supported actions. + + This prevents future PRs from accidentally dropping actions during description + trimming. The enum is the authoritative list — this locks it at 14 values. + """ + tool = TOOL_REGISTRY["browser_act"] + schema = tool.as_openai_tool() + fn_def = schema["function"] + params = cast(dict[str, Any], fn_def.get("parameters", {})) + actions = params["properties"]["action"]["enum"] + expected = { + "click", + "dblclick", + "fill", + "type", + "scroll", + "hover", + "press", + "check", + "uncheck", + "select", + "wait", + "back", + "forward", + "reload", + } + assert set(actions) == expected, ( + f"browser_act action enum changed. Got {set(actions)}, expected {expected}. " + "If you added/removed an action, update this test intentionally." + ) + + +def test_total_schema_char_budget() -> None: + """Assert total tool schema size stays under the character budget. + + This locks in the 34% token reduction from #12398 and prevents future + description bloat from eroding the gains. Uses character count with a + ~4 chars/token heuristic (budget of 32000 chars ≈ 8000 tokens). + Character count is tokenizer-agnostic — no dependency on GPT or Claude + tokenizers — while still providing a stable regression gate. + """ + schemas = [tool.as_openai_tool() for tool in TOOL_REGISTRY.values()] + serialized = json.dumps(schemas) + total_chars = len(serialized) + assert total_chars < _CHAR_BUDGET, ( + f"Tool schemas use {total_chars} chars (~{total_chars // 4} tokens), " + f"exceeding budget of {_CHAR_BUDGET} chars (~{_CHAR_BUDGET // 4} tokens). " + f"Description bloat detected — trim descriptions or raise the budget intentionally." + ) diff --git a/autogpt_platform/backend/backend/copilot/tools/validate_agent.py b/autogpt_platform/backend/backend/copilot/tools/validate_agent.py index 9367b4b48ed7..ebcd049136f9 100644 --- a/autogpt_platform/backend/backend/copilot/tools/validate_agent.py +++ b/autogpt_platform/backend/backend/copilot/tools/validate_agent.py @@ -22,17 +22,9 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Validate an agent JSON graph for correctness. Checks:\n" - "- All block_ids reference real blocks\n" - "- All links reference valid source/sink nodes and fields\n" - "- Required input fields are wired or have defaults\n" - "- Data types are compatible across links\n" - "- Nested sink links use correct notation\n" - "- Prompt templates use proper curly brace escaping\n" - "- AgentExecutorBlock configurations are valid\n\n" - "Call this after generating agent JSON to verify correctness. " - "If validation fails, either fix issues manually based on the error " - "descriptions, or call fix_agent_graph to auto-fix common problems." + "Validate agent JSON for correctness: block_ids, links, required fields, " + "type compatibility, nested sink notation, prompt brace escaping, " + "and AgentExecutorBlock configs. On failure, use fix_agent_graph to auto-fix." ) @property @@ -46,11 +38,7 @@ def parameters(self) -> dict[str, Any]: "properties": { "agent_json": { "type": "object", - "description": ( - "The agent JSON to validate. Must contain 'nodes' and 'links' arrays. " - "Each node needs: id (UUID), block_id, input_default, metadata. " - "Each link needs: id (UUID), source_id, source_name, sink_id, sink_name." - ), + "description": "Agent JSON with 'nodes' and 'links' arrays.", }, }, "required": ["agent_json"], diff --git a/autogpt_platform/backend/backend/copilot/tools/web_fetch.py b/autogpt_platform/backend/backend/copilot/tools/web_fetch.py index 53d8bd55a807..6c11f0205461 100644 --- a/autogpt_platform/backend/backend/copilot/tools/web_fetch.py +++ b/autogpt_platform/backend/backend/copilot/tools/web_fetch.py @@ -59,13 +59,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Fetch the content of a public web page by URL. " - "Returns readable text extracted from HTML by default. " - "Useful for reading documentation, articles, and API responses. " - "Only supports HTTP/HTTPS GET requests to public URLs " - "(private/internal network addresses are blocked)." - ) + return "Fetch a public web page. Public URLs only — internal addresses blocked. Returns readable text from HTML by default." @property def parameters(self) -> dict[str, Any]: @@ -74,14 +68,11 @@ def parameters(self) -> dict[str, Any]: "properties": { "url": { "type": "string", - "description": "The public HTTP/HTTPS URL to fetch.", + "description": "Public HTTP/HTTPS URL.", }, "extract_text": { "type": "boolean", - "description": ( - "If true (default), extract readable text from HTML. " - "If false, return raw content." - ), + "description": "Extract text from HTML (default: true).", "default": True, }, }, diff --git a/autogpt_platform/backend/backend/copilot/tools/workspace_files.py b/autogpt_platform/backend/backend/copilot/tools/workspace_files.py index 4bad767c5f06..a5884d63b241 100644 --- a/autogpt_platform/backend/backend/copilot/tools/workspace_files.py +++ b/autogpt_platform/backend/backend/copilot/tools/workspace_files.py @@ -27,6 +27,8 @@ logger = logging.getLogger(__name__) +_MAX_FILE_SIZE_MB = Config().max_file_size_mb + # Sentinel file_id used when a tool-result file is read directly from the local # host filesystem (rather than from workspace storage). _LOCAL_TOOL_RESULT_FILE_ID = "local" @@ -415,13 +417,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "List files in the user's persistent workspace (cloud storage). " - "These files survive across sessions. " - "For ephemeral session files, use the SDK Read/Glob tools instead. " - "Returns file names, paths, sizes, and metadata. " - "Optionally filter by path prefix." - ) + return "List persistent workspace files. For ephemeral session files, use SDK Glob/Read instead. Optionally filter by path prefix." @property def parameters(self) -> dict[str, Any]: @@ -430,24 +426,17 @@ def parameters(self) -> dict[str, Any]: "properties": { "path_prefix": { "type": "string", - "description": ( - "Optional path prefix to filter files " - "(e.g., '/documents/' to list only files in documents folder). " - "By default, only files from the current session are listed." - ), + "description": "Filter by path prefix (e.g. '/documents/').", }, "limit": { "type": "integer", - "description": "Maximum number of files to return (default 50, max 100)", + "description": "Max files to return (default 50, max 100).", "minimum": 1, "maximum": 100, }, "include_all_sessions": { "type": "boolean", - "description": ( - "If true, list files from all sessions. " - "Default is false (only current session's files)." - ), + "description": "Include files from all sessions (default: false).", }, }, "required": [], @@ -530,18 +519,11 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Read a file from the user's persistent workspace (cloud storage). " - "These files survive across sessions. " - "For ephemeral session files, use the SDK Read tool instead. " - "Specify either file_id or path to identify the file. " - "For small text files, returns content directly. " - "For large or binary files, returns metadata and a download URL. " - "Use 'save_to_path' to copy the file to the working directory " - "(sandbox or ephemeral) for processing with bash_exec or file tools. " - "Use 'offset' and 'length' for paginated reads of large files " - "(e.g., persisted tool outputs). " - "Paths are scoped to the current session by default. " - "Use /sessions//... for cross-session access." + "Read a file from persistent workspace. Specify file_id or path. " + "Small text/image files return inline; large/binary return metadata+URL. " + "Use save_to_path to copy to working dir for processing. " + "Use offset/length for paginated reads. " + "Paths scoped to current session; use /sessions//... for cross-session access." ) @property @@ -551,48 +533,30 @@ def parameters(self) -> dict[str, Any]: "properties": { "file_id": { "type": "string", - "description": "The file's unique ID (from list_workspace_files)", + "description": "File ID from list_workspace_files.", }, "path": { "type": "string", - "description": ( - "The virtual file path (e.g., '/documents/report.pdf'). " - "Scoped to current session by default." - ), + "description": "Virtual file path (e.g. '/documents/report.pdf').", }, "save_to_path": { "type": "string", - "description": ( - "If provided, save the file to this path in the working " - "directory (cloud sandbox when E2B is active, or " - "ephemeral dir otherwise) so it can be processed with " - "bash_exec or file tools. " - "The file content is still returned in the response." - ), + "description": "Copy file to this working directory path for processing.", }, "force_download_url": { "type": "boolean", - "description": ( - "If true, always return metadata+URL instead of inline content. " - "Default is false (auto-selects based on file size/type)." - ), + "description": "Always return metadata+URL instead of inline content.", }, "offset": { "type": "integer", - "description": ( - "Character offset to start reading from (0-based). " - "Use with 'length' for paginated reads of large files." - ), + "description": "Character offset for paginated reads (0-based).", }, "length": { "type": "integer", - "description": ( - "Maximum number of characters to return. " - "Defaults to full file. Use with 'offset' for paginated reads." - ), + "description": "Max characters to return for paginated reads.", }, }, - "required": [], # At least one must be provided + "required": [], # At least one of file_id or path must be provided } @property @@ -755,15 +719,10 @@ def name(self) -> str: @property def description(self) -> str: return ( - "Write or create a file in the user's persistent workspace (cloud storage). " - "These files survive across sessions. " - "For ephemeral session files, use the SDK Write tool instead. " - "Provide content as plain text via 'content', OR base64-encoded via " - "'content_base64', OR copy a file from the ephemeral working directory " - "via 'source_path'. Exactly one of these three is required. " - f"Maximum file size is {Config().max_file_size_mb}MB. " - "Files are saved to the current session's folder by default. " - "Use /sessions//... for cross-session access." + "Write a file to persistent workspace (survives across sessions). " + "Provide exactly one of: content (text), content_base64 (binary), " + f"or source_path (copy from working dir). Max {_MAX_FILE_SIZE_MB}MB. " + "Paths scoped to current session; use /sessions//... for cross-session access." ) @property @@ -773,51 +732,31 @@ def parameters(self) -> dict[str, Any]: "properties": { "filename": { "type": "string", - "description": "Name for the file (e.g., 'report.pdf')", + "description": "Filename (e.g. 'report.pdf').", }, "content": { "type": "string", - "description": ( - "Plain text content to write. Use this for text files " - "(code, configs, documents, etc.). " - "Mutually exclusive with content_base64 and source_path." - ), + "description": "Plain text content. Mutually exclusive with content_base64/source_path.", }, "content_base64": { "type": "string", - "description": ( - "Base64-encoded file content. Use this for binary files " - "(images, PDFs, etc.). " - "Mutually exclusive with content and source_path." - ), + "description": "Base64-encoded binary content. Mutually exclusive with content/source_path.", }, "source_path": { "type": "string", - "description": ( - "Path to a file in the ephemeral working directory to " - "copy to workspace (e.g., '/tmp/copilot-.../output.csv'). " - "Use this to persist files created by bash_exec or SDK Write. " - "Mutually exclusive with content and content_base64." - ), + "description": "Working directory path to copy to workspace. Mutually exclusive with content/content_base64.", }, "path": { "type": "string", - "description": ( - "Optional virtual path where to save the file " - "(e.g., '/documents/report.pdf'). " - "Defaults to '/{filename}'. Scoped to current session." - ), + "description": "Virtual path (e.g. '/documents/report.pdf'). Defaults to '/{filename}'.", }, "mime_type": { "type": "string", - "description": ( - "Optional MIME type of the file. " - "Auto-detected from filename if not provided." - ), + "description": "MIME type. Auto-detected from filename if omitted.", }, "overwrite": { "type": "boolean", - "description": "Whether to overwrite if file exists at path (default: false)", + "description": "Overwrite if file exists (default: false).", }, }, "required": ["filename"], @@ -859,10 +798,10 @@ async def _execute( return resolved content: bytes = resolved - max_size = Config().max_file_size_mb * 1024 * 1024 + max_size = _MAX_FILE_SIZE_MB * 1024 * 1024 if len(content) > max_size: return ErrorResponse( - message=f"File too large. Maximum size is {Config().max_file_size_mb}MB", + message=f"File too large. Maximum size is {_MAX_FILE_SIZE_MB}MB", session_id=session_id, ) @@ -944,12 +883,7 @@ def name(self) -> str: @property def description(self) -> str: - return ( - "Delete a file from the user's persistent workspace (cloud storage). " - "Specify either file_id or path to identify the file. " - "Paths are scoped to the current session by default. " - "Use /sessions//... for cross-session access." - ) + return "Delete a file from persistent workspace. Specify file_id or path. Paths scoped to current session; use /sessions//... for cross-session access." @property def parameters(self) -> dict[str, Any]: @@ -958,17 +892,14 @@ def parameters(self) -> dict[str, Any]: "properties": { "file_id": { "type": "string", - "description": "The file's unique ID (from list_workspace_files)", + "description": "File ID from list_workspace_files.", }, "path": { "type": "string", - "description": ( - "The virtual file path (e.g., '/documents/report.pdf'). " - "Scoped to current session by default." - ), + "description": "Virtual file path.", }, }, - "required": [], # At least one must be provided + "required": [], # At least one of file_id or path must be provided } @property