diff --git a/crates/goose-bench/src/eval_suites/core/developer_image/image.rs b/crates/goose-bench/src/eval_suites/core/developer_image/image.rs index 98fa73b2c5..a9178f43f6 100644 --- a/crates/goose-bench/src/eval_suites/core/developer_image/image.rs +++ b/crates/goose-bench/src/eval_suites/core/developer_image/image.rs @@ -68,7 +68,7 @@ impl Evaluation for DeveloperImage { if let MessageContent::ToolResponse(tool_resp) = content { if let Ok(result) = &tool_resp.tool_result { // Check each item in the result list - for item in result { + for item in &result.content { if let Some(image) = item.as_image() { // Image content already contains mime_type and data if image.mime_type.starts_with("image/") diff --git a/crates/goose-cli/src/commands/acp.rs b/crates/goose-cli/src/commands/acp.rs index 8c9684a037..89242bc656 100644 --- a/crates/goose-cli/src/commands/acp.rs +++ b/crates/goose-cli/src/commands/acp.rs @@ -11,7 +11,7 @@ use goose::mcp_utils::ToolResult; use goose::providers::create; use goose::session::session_manager::SessionType; use goose::session::SessionManager; -use rmcp::model::{Content, RawContent, ResourceContents, Role}; +use rmcp::model::{CallToolResult, RawContent, ResourceContents, Role}; use std::collections::{HashMap, HashSet}; use std::fs; use std::sync::Arc; @@ -74,8 +74,8 @@ fn extract_tool_locations( .and_then(|c| c.as_str()); // Extract line numbers from the response content - if let Ok(content_items) = &tool_response.tool_result { - for content in content_items { + if let Ok(result) = &tool_response.tool_result { + for content in &result.content { if let RawContent::Text(text_content) = &content.raw { let text = &text_content.text; @@ -491,9 +491,10 @@ impl GooseAcpAgent { } /// Build tool call content from tool result -fn build_tool_call_content(tool_result: &ToolResult>) -> Vec { +fn build_tool_call_content(tool_result: &ToolResult) -> Vec { match tool_result { - Ok(content_items) => content_items + Ok(result) => result + .content .iter() .filter_map(|content| match &content.raw { RawContent::Text(val) => Some(ToolCallContent::Content { diff --git a/crates/goose-cli/src/scenario_tests/recordings/anthropic/weather_tool.json b/crates/goose-cli/src/scenario_tests/recordings/anthropic/weather_tool.json index 1a128963eb..68ce092dce 100644 --- a/crates/goose-cli/src/scenario_tests/recordings/anthropic/weather_tool.json +++ b/crates/goose-cli/src/scenario_tests/recordings/anthropic/weather_tool.json @@ -1,12 +1,12 @@ { - "74a3cc715c4c65dcae4ecec084d962947e4ce3cf02a8b639ee23b080e88bf6ec": { + "1b90ffd4b16eab7aef1c24e9b61f6b6cc6f0d9b04e8a4f4344454a45b0793284": { "input": { "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", "messages": [ { - "id": "msg_20251124_8_0", + "id": "msg_20251211_27_0", "role": "user", - "created": 1763989805, + "created": 1765495263, "content": [ { "type": "text", @@ -19,13 +19,13 @@ } }, { - "id": "msg_b4135f6c-9a29-4904-a59f-632ea849ef5a", + "id": "msg_9076da8a-a3b1-4cfc-b5d9-0ffc2a7879c9", "role": "assistant", - "created": 1763989808, + "created": 1765495266, "content": [ { "type": "toolRequest", - "id": "toolu_01VxSzmLcPK7UNNE4ms8XRa6", + "id": "toolu_01CDkt9pjh8oC9C1984uhvuG", "toolCall": { "status": "success", "value": { @@ -43,21 +43,23 @@ } }, { - "id": "msg_253b4067-0c08-4807-8b36-23b576f138b5", + "id": "msg_d7b3db5e-0402-42a8-937c-2ccf68a3e4e0", "role": "user", - "created": 1763989808, + "created": 1765495266, "content": [ { "type": "toolResponse", - "id": "toolu_01VxSzmLcPK7UNNE4ms8XRa6", + "id": "toolu_01CDkt9pjh8oC9C1984uhvuG", "toolResult": { "status": "success", - "value": [ - { - "type": "text", - "text": "The weather in Berlin, Germany is cloudy and 18°C" - } - ] + "value": { + "content": [ + { + "type": "text", + "text": "The weather in Berlin, Germany is cloudy and 18°C" + } + ] + } } } ], @@ -305,11 +307,11 @@ "message": { "id": null, "role": "assistant", - "created": 1763989811, + "created": 1765495269, "content": [ { "type": "text", - "text": "The weather in Berlin, Germany is currently **cloudy** with a temperature of **18°C** (64°F)." + "text": "# Weather in Berlin, Germany\n\nThe current weather in Berlin, Germany is:\n- **Conditions**: Cloudy\n- **Temperature**: 18°C (64°F)\n\nIt's a mild day with overcast conditions in Berlin!" } ], "metadata": { @@ -321,301 +323,8 @@ "model": "claude-sonnet-4-20250514", "usage": { "input_tokens": 2406, - "output_tokens": 29, - "total_tokens": 2435 - } - } - } - }, - "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": { - "input": { - "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", - "messages": [ - { - "id": "msg_20251124_8_0", - "role": "user", - "created": 1763989805, - "content": [ - { - "type": "text", - "text": "tell me what the weather is in Berlin, Germany" - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - } - ], - "tools": [ - { - "name": "dynamic_task__create_task", - "description": "Create tasks with instructions or prompt. For simple tasks, only include the instructions field. Extensions control: omit field = use all current extensions; empty array [] = no extensions; array with names = only those extensions. Specify extensions as shortnames (the prefixes for your tools). Specify return_last_only as true and have your subagent summarize its work in its last message to conserve your own context. Optional: title, description, extensions, settings, retry, response schema, activities. Arrays for multiple tasks.", - "inputSchema": { - "$defs": { - "TaskParameter": { - "description": "Parameters for a single task", - "properties": { - "activities": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "extensions": { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": [ - "array", - "null" - ] - }, - "instructions": { - "type": [ - "string", - "null" - ] - }, - "parameters": { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": [ - "array", - "null" - ] - }, - "prompt": { - "type": [ - "string", - "null" - ] - }, - "response": { - "additionalProperties": true, - "type": [ - "object", - "null" - ] - }, - "retry": { - "additionalProperties": true, - "type": [ - "object", - "null" - ] - }, - "return_last_only": { - "description": "If true, return only the last message from the subagent (default: false, returns full conversation)", - "type": [ - "boolean", - "null" - ] - }, - "settings": { - "additionalProperties": true, - "type": [ - "object", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - } - }, - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "execution_mode": { - "description": "How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)", - "type": [ - "string", - "null" - ] - }, - "task_parameters": { - "description": "Array of tasks. Each task must have either 'instructions' OR 'prompt' field (at least one is required).", - "items": { - "$ref": "#/$defs/TaskParameter" - }, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "task_parameters" - ], - "title": "CreateDynamicTaskParams", - "type": "object" - }, - "annotations": { - "title": "Create Dynamic Tasks", - "readOnlyHint": false, - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": true - } - }, - { - "name": "platform__manage_schedule", - "description": "Manage scheduled recipe execution for this goose instance.\n\nActions:\n- \"list\": List all scheduled jobs\n- \"create\": Create a new scheduled job from a recipe file\n- \"run_now\": Execute a scheduled job immediately \n- \"pause\": Pause a scheduled job\n- \"unpause\": Resume a paused job\n- \"delete\": Remove a scheduled job\n- \"kill\": Terminate a currently running job\n- \"inspect\": Get details about a running job\n- \"sessions\": List execution history for a job\n- \"session_content\": Get the full content (messages) of a specific session\n", - "inputSchema": { - "properties": { - "action": { - "enum": [ - "list", - "create", - "run_now", - "pause", - "unpause", - "delete", - "kill", - "inspect", - "sessions", - "session_content" - ], - "type": "string" - }, - "cron_expression": { - "description": "A cron expression for create action. Supports both 5-field (minute hour day month weekday) and 6-field (second minute hour day month weekday) formats. 5-field expressions are automatically converted to 6-field by prepending '0' for seconds.", - "type": "string" - }, - "job_id": { - "description": "Job identifier for operations on existing jobs", - "type": "string" - }, - "limit": { - "default": 50, - "description": "Limit for sessions list", - "type": "integer" - }, - "recipe_path": { - "description": "Path to recipe file for create action", - "type": "string" - }, - "session_id": { - "description": "Session identifier for session_content action", - "type": "string" - } - }, - "required": [ - "action" - ], - "type": "object" - }, - "annotations": { - "title": "Manage scheduled recipes", - "readOnlyHint": false, - "destructiveHint": true, - "idempotentHint": false, - "openWorldHint": false - } - }, - { - "name": "subagent__execute_task", - "description": "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.\n EXECUTION STRATEGY DECISION:\n 1. If the tasks are created with execution_mode, use the execution_mode.\n 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'\n\n IMPLEMENTATION:\n - Sequential execution: Call this tool multiple times, passing exactly ONE task per call\n - Parallel execution: Call this tool once, passing an ARRAY of all tasks\n\n EXAMPLES:\n User Intent Based:\n - User: 'get weather and tell me a joke' → Sequential (2 separate tool calls, 1 task each)\n - User: 'get weather and joke in parallel' → Parallel (1 tool call with array of 2 tasks)\n - User: 'run these simultaneously' → Parallel (1 tool call with task array)\n - User: 'do task A then task B' → Sequential (2 separate tool calls)", - "inputSchema": { - "properties": { - "execution_mode": { - "default": "sequential", - "description": "Execution strategy for multiple tasks. Use 'sequential' (default) unless user explicitly requests parallel execution with words like 'parallel', 'simultaneously', 'at the same time', or 'concurrently'.", - "enum": [ - "sequential", - "parallel" - ], - "type": "string" - }, - "task_ids": { - "items": { - "description": "Unique identifier for the task", - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "task_ids" - ], - "type": "object" - }, - "annotations": { - "title": "Run tasks in parallel", - "readOnlyHint": false, - "destructiveHint": true, - "idempotentHint": false, - "openWorldHint": true - } - }, - { - "name": "weather_extension__get_weather", - "description": "Get the weather for a location", - "inputSchema": { - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - ] - }, - "output": { - "message": { - "id": null, - "role": "assistant", - "created": 1763989808, - "content": [ - { - "type": "text", - "text": "I'll get the current weather information for Berlin, Germany." - }, - { - "type": "toolRequest", - "id": "toolu_01VxSzmLcPK7UNNE4ms8XRa6", - "toolCall": { - "status": "success", - "value": { - "name": "weather_extension__get_weather", - "arguments": { - "location": "Berlin, Germany" - } - } - } - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - }, - "usage": { - "model": "claude-sonnet-4-20250514", - "usage": { - "input_tokens": 2320, - "output_tokens": 72, - "total_tokens": 2392 + "output_tokens": 55, + "total_tokens": 2461 } } } @@ -627,7 +336,7 @@ { "id": null, "role": "user", - "created": 1763989805, + "created": 1765495263, "content": [ { "type": "text", @@ -646,7 +355,7 @@ "message": { "id": null, "role": "assistant", - "created": 1763989810, + "created": 1765495265, "content": [ { "type": "text", @@ -667,5 +376,298 @@ } } } + }, + "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": { + "input": { + "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", + "messages": [ + { + "id": "msg_20251211_27_0", + "role": "user", + "created": 1765495263, + "content": [ + { + "type": "text", + "text": "tell me what the weather is in Berlin, Germany" + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + } + ], + "tools": [ + { + "name": "dynamic_task__create_task", + "description": "Create tasks with instructions or prompt. For simple tasks, only include the instructions field. Extensions control: omit field = use all current extensions; empty array [] = no extensions; array with names = only those extensions. Specify extensions as shortnames (the prefixes for your tools). Specify return_last_only as true and have your subagent summarize its work in its last message to conserve your own context. Optional: title, description, extensions, settings, retry, response schema, activities. Arrays for multiple tasks.", + "inputSchema": { + "$defs": { + "TaskParameter": { + "description": "Parameters for a single task", + "properties": { + "activities": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "extensions": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": [ + "array", + "null" + ] + }, + "instructions": { + "type": [ + "string", + "null" + ] + }, + "parameters": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": [ + "array", + "null" + ] + }, + "prompt": { + "type": [ + "string", + "null" + ] + }, + "response": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "retry": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "return_last_only": { + "description": "If true, return only the last message from the subagent (default: false, returns full conversation)", + "type": [ + "boolean", + "null" + ] + }, + "settings": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "execution_mode": { + "description": "How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)", + "type": [ + "string", + "null" + ] + }, + "task_parameters": { + "description": "Array of tasks. Each task must have either 'instructions' OR 'prompt' field (at least one is required).", + "items": { + "$ref": "#/$defs/TaskParameter" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "task_parameters" + ], + "title": "CreateDynamicTaskParams", + "type": "object" + }, + "annotations": { + "title": "Create Dynamic Tasks", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + } + }, + { + "name": "platform__manage_schedule", + "description": "Manage scheduled recipe execution for this goose instance.\n\nActions:\n- \"list\": List all scheduled jobs\n- \"create\": Create a new scheduled job from a recipe file\n- \"run_now\": Execute a scheduled job immediately \n- \"pause\": Pause a scheduled job\n- \"unpause\": Resume a paused job\n- \"delete\": Remove a scheduled job\n- \"kill\": Terminate a currently running job\n- \"inspect\": Get details about a running job\n- \"sessions\": List execution history for a job\n- \"session_content\": Get the full content (messages) of a specific session\n", + "inputSchema": { + "properties": { + "action": { + "enum": [ + "list", + "create", + "run_now", + "pause", + "unpause", + "delete", + "kill", + "inspect", + "sessions", + "session_content" + ], + "type": "string" + }, + "cron_expression": { + "description": "A cron expression for create action. Supports both 5-field (minute hour day month weekday) and 6-field (second minute hour day month weekday) formats. 5-field expressions are automatically converted to 6-field by prepending '0' for seconds.", + "type": "string" + }, + "job_id": { + "description": "Job identifier for operations on existing jobs", + "type": "string" + }, + "limit": { + "default": 50, + "description": "Limit for sessions list", + "type": "integer" + }, + "recipe_path": { + "description": "Path to recipe file for create action", + "type": "string" + }, + "session_id": { + "description": "Session identifier for session_content action", + "type": "string" + } + }, + "required": [ + "action" + ], + "type": "object" + }, + "annotations": { + "title": "Manage scheduled recipes", + "readOnlyHint": false, + "destructiveHint": true, + "idempotentHint": false, + "openWorldHint": false + } + }, + { + "name": "subagent__execute_task", + "description": "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.\n EXECUTION STRATEGY DECISION:\n 1. If the tasks are created with execution_mode, use the execution_mode.\n 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'\n\n IMPLEMENTATION:\n - Sequential execution: Call this tool multiple times, passing exactly ONE task per call\n - Parallel execution: Call this tool once, passing an ARRAY of all tasks\n\n EXAMPLES:\n User Intent Based:\n - User: 'get weather and tell me a joke' → Sequential (2 separate tool calls, 1 task each)\n - User: 'get weather and joke in parallel' → Parallel (1 tool call with array of 2 tasks)\n - User: 'run these simultaneously' → Parallel (1 tool call with task array)\n - User: 'do task A then task B' → Sequential (2 separate tool calls)", + "inputSchema": { + "properties": { + "execution_mode": { + "default": "sequential", + "description": "Execution strategy for multiple tasks. Use 'sequential' (default) unless user explicitly requests parallel execution with words like 'parallel', 'simultaneously', 'at the same time', or 'concurrently'.", + "enum": [ + "sequential", + "parallel" + ], + "type": "string" + }, + "task_ids": { + "items": { + "description": "Unique identifier for the task", + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "task_ids" + ], + "type": "object" + }, + "annotations": { + "title": "Run tasks in parallel", + "readOnlyHint": false, + "destructiveHint": true, + "idempotentHint": false, + "openWorldHint": true + } + }, + { + "name": "weather_extension__get_weather", + "description": "Get the weather for a location", + "inputSchema": { + "properties": { + "location": { + "description": "The city and state, e.g. San Francisco, CA", + "type": "string" + } + }, + "required": [ + "location" + ], + "type": "object" + } + } + ] + }, + "output": { + "message": { + "id": null, + "role": "assistant", + "created": 1765495266, + "content": [ + { + "type": "text", + "text": "I'll get the current weather information for Berlin, Germany for you." + }, + { + "type": "toolRequest", + "id": "toolu_01CDkt9pjh8oC9C1984uhvuG", + "toolCall": { + "status": "success", + "value": { + "name": "weather_extension__get_weather", + "arguments": { + "location": "Berlin, Germany" + } + } + } + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + }, + "usage": { + "model": "claude-sonnet-4-20250514", + "usage": { + "input_tokens": 2320, + "output_tokens": 74, + "total_tokens": 2394 + } + } + } } } \ No newline at end of file diff --git a/crates/goose-cli/src/scenario_tests/recordings/azure_openai/weather_tool.json b/crates/goose-cli/src/scenario_tests/recordings/azure_openai/weather_tool.json index fffc7b746b..105749bcdf 100644 --- a/crates/goose-cli/src/scenario_tests/recordings/azure_openai/weather_tool.json +++ b/crates/goose-cli/src/scenario_tests/recordings/azure_openai/weather_tool.json @@ -1,12 +1,60 @@ { + "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": { + "input": { + "system": "Reply with only a description in four words or less", + "messages": [ + { + "id": null, + "role": "user", + "created": 1765495269, + "content": [ + { + "type": "text", + "text": "Here are the first few user messages:\ntell me what the weather is in Berlin, Germany\n\nBased on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description" + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + } + ], + "tools": [] + }, + "output": { + "message": { + "id": null, + "role": "assistant", + "created": 1765495270, + "content": [ + { + "type": "text", + "text": "Weather inquiry for Berlin" + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + }, + "usage": { + "model": "gpt-4o-mini-2024-07-18", + "usage": { + "input_tokens": 84, + "output_tokens": 5, + "total_tokens": 89 + } + } + } + }, "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": { "input": { "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", "messages": [ { - "id": "msg_20251124_9_0", + "id": "msg_20251211_28_0", "role": "user", - "created": 1763989812, + "created": 1765495269, "content": [ { "type": "text", @@ -257,11 +305,11 @@ "message": { "id": null, "role": "assistant", - "created": 1763989813, + "created": 1765495270, "content": [ { "type": "toolRequest", - "id": "call_5IgyNY33nTougqsNFhizypew", + "id": "call_JJok3ApyTL54ohjLX8tTYVHC", "toolCall": { "status": "success", "value": { @@ -288,14 +336,14 @@ } } }, - "2c07b9a6570269fef4ae329889ce5a5243d7b09b52165580be701b67b8b95818": { + "e020c157caa13bad743162d7079a04ddafca2d075469124a0162c5562a2cf21b": { "input": { "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", "messages": [ { - "id": "msg_20251124_9_0", + "id": "msg_20251211_28_0", "role": "user", - "created": 1763989812, + "created": 1765495269, "content": [ { "type": "text", @@ -308,13 +356,13 @@ } }, { - "id": "msg_b87436fe-d710-4fcd-b912-a69ab4ccfe7e", + "id": "msg_1d08355f-6d2d-413e-a3b5-787d635f9c6e", "role": "assistant", - "created": 1763989813, + "created": 1765495270, "content": [ { "type": "toolRequest", - "id": "call_5IgyNY33nTougqsNFhizypew", + "id": "call_JJok3ApyTL54ohjLX8tTYVHC", "toolCall": { "status": "success", "value": { @@ -332,21 +380,23 @@ } }, { - "id": "msg_9cf859cf-4adc-4464-aba4-1d4ccf9d7d05", + "id": "msg_929b0769-72dd-46f8-9027-31d1c09d4f08", "role": "user", - "created": 1763989813, + "created": 1765495270, "content": [ { "type": "toolResponse", - "id": "call_5IgyNY33nTougqsNFhizypew", + "id": "call_JJok3ApyTL54ohjLX8tTYVHC", "toolResult": { "status": "success", - "value": [ - { - "type": "text", - "text": "The weather in Berlin, Germany is cloudy and 18°C" - } - ] + "value": { + "content": [ + { + "type": "text", + "text": "The weather in Berlin, Germany is cloudy and 18°C" + } + ] + } } } ], @@ -594,11 +644,11 @@ "message": { "id": null, "role": "assistant", - "created": 1763989814, + "created": 1765495271, "content": [ { "type": "text", - "text": "### Current Weather in Berlin, Germany\n\n- **Condition:** Cloudy\n- **Temperature:** 18°C" + "text": "### Current Weather in Berlin, Germany\n\n- **Condition:** Cloudy\n- **Temperature:** 18°C\n\nIf you need more information or a forecast, let me know!" } ], "metadata": { @@ -610,56 +660,8 @@ "model": "gpt-4o-mini-2024-07-18", "usage": { "input_tokens": 1534, - "output_tokens": 24, - "total_tokens": 1558 - } - } - } - }, - "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": { - "input": { - "system": "Reply with only a description in four words or less", - "messages": [ - { - "id": null, - "role": "user", - "created": 1763989812, - "content": [ - { - "type": "text", - "text": "Here are the first few user messages:\ntell me what the weather is in Berlin, Germany\n\nBased on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description" - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - } - ], - "tools": [] - }, - "output": { - "message": { - "id": null, - "role": "assistant", - "created": 1763989813, - "content": [ - { - "type": "text", - "text": "Berlin weather inquiry" - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - }, - "usage": { - "model": "gpt-4o-mini-2024-07-18", - "usage": { - "input_tokens": 84, - "output_tokens": 4, - "total_tokens": 88 + "output_tokens": 38, + "total_tokens": 1572 } } } diff --git a/crates/goose-cli/src/scenario_tests/recordings/groq/weather_tool.json b/crates/goose-cli/src/scenario_tests/recordings/groq/weather_tool.json index 7a65421844..d32272fdac 100644 --- a/crates/goose-cli/src/scenario_tests/recordings/groq/weather_tool.json +++ b/crates/goose-cli/src/scenario_tests/recordings/groq/weather_tool.json @@ -1,301 +1,12 @@ { - "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": { + "075d1e65f3a25bbdfc21aee19a1119b43fd4241f8643e646c9eb928a431975d4": { "input": { "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", "messages": [ { - "id": "msg_20251124_10_0", + "id": "msg_20251211_40_0", "role": "user", - "created": 1763989814, - "content": [ - { - "type": "text", - "text": "tell me what the weather is in Berlin, Germany" - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - } - ], - "tools": [ - { - "name": "dynamic_task__create_task", - "description": "Create tasks with instructions or prompt. For simple tasks, only include the instructions field. Extensions control: omit field = use all current extensions; empty array [] = no extensions; array with names = only those extensions. Specify extensions as shortnames (the prefixes for your tools). Specify return_last_only as true and have your subagent summarize its work in its last message to conserve your own context. Optional: title, description, extensions, settings, retry, response schema, activities. Arrays for multiple tasks.", - "inputSchema": { - "$defs": { - "TaskParameter": { - "description": "Parameters for a single task", - "properties": { - "activities": { - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "extensions": { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": [ - "array", - "null" - ] - }, - "instructions": { - "type": [ - "string", - "null" - ] - }, - "parameters": { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": [ - "array", - "null" - ] - }, - "prompt": { - "type": [ - "string", - "null" - ] - }, - "response": { - "additionalProperties": true, - "type": [ - "object", - "null" - ] - }, - "retry": { - "additionalProperties": true, - "type": [ - "object", - "null" - ] - }, - "return_last_only": { - "description": "If true, return only the last message from the subagent (default: false, returns full conversation)", - "type": [ - "boolean", - "null" - ] - }, - "settings": { - "additionalProperties": true, - "type": [ - "object", - "null" - ] - }, - "title": { - "type": [ - "string", - "null" - ] - } - }, - "type": "object" - } - }, - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "execution_mode": { - "description": "How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)", - "type": [ - "string", - "null" - ] - }, - "task_parameters": { - "description": "Array of tasks. Each task must have either 'instructions' OR 'prompt' field (at least one is required).", - "items": { - "$ref": "#/$defs/TaskParameter" - }, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "task_parameters" - ], - "title": "CreateDynamicTaskParams", - "type": "object" - }, - "annotations": { - "title": "Create Dynamic Tasks", - "readOnlyHint": false, - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": true - } - }, - { - "name": "platform__manage_schedule", - "description": "Manage scheduled recipe execution for this goose instance.\n\nActions:\n- \"list\": List all scheduled jobs\n- \"create\": Create a new scheduled job from a recipe file\n- \"run_now\": Execute a scheduled job immediately \n- \"pause\": Pause a scheduled job\n- \"unpause\": Resume a paused job\n- \"delete\": Remove a scheduled job\n- \"kill\": Terminate a currently running job\n- \"inspect\": Get details about a running job\n- \"sessions\": List execution history for a job\n- \"session_content\": Get the full content (messages) of a specific session\n", - "inputSchema": { - "properties": { - "action": { - "enum": [ - "list", - "create", - "run_now", - "pause", - "unpause", - "delete", - "kill", - "inspect", - "sessions", - "session_content" - ], - "type": "string" - }, - "cron_expression": { - "description": "A cron expression for create action. Supports both 5-field (minute hour day month weekday) and 6-field (second minute hour day month weekday) formats. 5-field expressions are automatically converted to 6-field by prepending '0' for seconds.", - "type": "string" - }, - "job_id": { - "description": "Job identifier for operations on existing jobs", - "type": "string" - }, - "limit": { - "default": 50, - "description": "Limit for sessions list", - "type": "integer" - }, - "recipe_path": { - "description": "Path to recipe file for create action", - "type": "string" - }, - "session_id": { - "description": "Session identifier for session_content action", - "type": "string" - } - }, - "required": [ - "action" - ], - "type": "object" - }, - "annotations": { - "title": "Manage scheduled recipes", - "readOnlyHint": false, - "destructiveHint": true, - "idempotentHint": false, - "openWorldHint": false - } - }, - { - "name": "subagent__execute_task", - "description": "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.\n EXECUTION STRATEGY DECISION:\n 1. If the tasks are created with execution_mode, use the execution_mode.\n 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'\n\n IMPLEMENTATION:\n - Sequential execution: Call this tool multiple times, passing exactly ONE task per call\n - Parallel execution: Call this tool once, passing an ARRAY of all tasks\n\n EXAMPLES:\n User Intent Based:\n - User: 'get weather and tell me a joke' → Sequential (2 separate tool calls, 1 task each)\n - User: 'get weather and joke in parallel' → Parallel (1 tool call with array of 2 tasks)\n - User: 'run these simultaneously' → Parallel (1 tool call with task array)\n - User: 'do task A then task B' → Sequential (2 separate tool calls)", - "inputSchema": { - "properties": { - "execution_mode": { - "default": "sequential", - "description": "Execution strategy for multiple tasks. Use 'sequential' (default) unless user explicitly requests parallel execution with words like 'parallel', 'simultaneously', 'at the same time', or 'concurrently'.", - "enum": [ - "sequential", - "parallel" - ], - "type": "string" - }, - "task_ids": { - "items": { - "description": "Unique identifier for the task", - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "task_ids" - ], - "type": "object" - }, - "annotations": { - "title": "Run tasks in parallel", - "readOnlyHint": false, - "destructiveHint": true, - "idempotentHint": false, - "openWorldHint": true - } - }, - { - "name": "weather_extension__get_weather", - "description": "Get the weather for a location", - "inputSchema": { - "properties": { - "location": { - "description": "The city and state, e.g. San Francisco, CA", - "type": "string" - } - }, - "required": [ - "location" - ], - "type": "object" - } - } - ] - }, - "output": { - "message": { - "id": null, - "role": "assistant", - "created": 1763989814, - "content": [ - { - "type": "toolRequest", - "id": "4kpg6v08d", - "toolCall": { - "status": "success", - "value": { - "name": "weather_extension__get_weather", - "arguments": { - "location": "Berlin, Germany" - } - } - } - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - }, - "usage": { - "model": "llama-3.3-70b-versatile", - "usage": { - "input_tokens": 2258, - "output_tokens": 20, - "total_tokens": 2278 - } - } - } - }, - "165258e98e56cdcccdc49bd70ecd2b081b41d35ddb3559ed3bc7b1e420290bed": { - "input": { - "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", - "messages": [ - { - "id": "msg_20251124_10_0", - "role": "user", - "created": 1763989814, + "created": 1765495334, "content": [ { "type": "text", @@ -308,13 +19,13 @@ } }, { - "id": "msg_9d25f6e9-6d02-4f41-8710-325795e87fbc", + "id": "msg_6147fd68-5a38-4528-9924-d4dbaf5e7f44", "role": "assistant", - "created": 1763989814, + "created": 1765495335, "content": [ { "type": "toolRequest", - "id": "4kpg6v08d", + "id": "zemx6n94z", "toolCall": { "status": "success", "value": { @@ -332,21 +43,23 @@ } }, { - "id": "msg_513af900-f4fe-4ed0-aed8-7ffb4c8c384d", + "id": "msg_0fac763c-62c3-4a33-a15b-27d605c5b81d", "role": "user", - "created": 1763989814, + "created": 1765495335, "content": [ { "type": "toolResponse", - "id": "4kpg6v08d", + "id": "zemx6n94z", "toolResult": { "status": "success", - "value": [ - { - "type": "text", - "text": "The weather in Berlin, Germany is cloudy and 18°C" - } - ] + "value": { + "content": [ + { + "type": "text", + "text": "The weather in Berlin, Germany is cloudy and 18°C" + } + ] + } } } ], @@ -594,7 +307,7 @@ "message": { "id": null, "role": "assistant", - "created": 1763989815, + "created": 1765495335, "content": [ { "type": "text", @@ -623,7 +336,7 @@ { "id": null, "role": "user", - "created": 1763989814, + "created": 1765495334, "content": [ { "type": "text", @@ -642,11 +355,11 @@ "message": { "id": null, "role": "assistant", - "created": 1763989814, + "created": 1765495334, "content": [ { "type": "text", - "text": "Berlin weather query" + "text": "Berlin weather" } ], "metadata": { @@ -658,8 +371,297 @@ "model": "llama-3.3-70b-versatile", "usage": { "input_tokens": 108, - "output_tokens": 4, - "total_tokens": 112 + "output_tokens": 3, + "total_tokens": 111 + } + } + } + }, + "1bc400a528c54b25f4f1f609481e98e44222b3deaf7eee2c9e640e6345c73861": { + "input": { + "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", + "messages": [ + { + "id": "msg_20251211_40_0", + "role": "user", + "created": 1765495334, + "content": [ + { + "type": "text", + "text": "tell me what the weather is in Berlin, Germany" + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + } + ], + "tools": [ + { + "name": "dynamic_task__create_task", + "description": "Create tasks with instructions or prompt. For simple tasks, only include the instructions field. Extensions control: omit field = use all current extensions; empty array [] = no extensions; array with names = only those extensions. Specify extensions as shortnames (the prefixes for your tools). Specify return_last_only as true and have your subagent summarize its work in its last message to conserve your own context. Optional: title, description, extensions, settings, retry, response schema, activities. Arrays for multiple tasks.", + "inputSchema": { + "$defs": { + "TaskParameter": { + "description": "Parameters for a single task", + "properties": { + "activities": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "extensions": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": [ + "array", + "null" + ] + }, + "instructions": { + "type": [ + "string", + "null" + ] + }, + "parameters": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": [ + "array", + "null" + ] + }, + "prompt": { + "type": [ + "string", + "null" + ] + }, + "response": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "retry": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "return_last_only": { + "description": "If true, return only the last message from the subagent (default: false, returns full conversation)", + "type": [ + "boolean", + "null" + ] + }, + "settings": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "execution_mode": { + "description": "How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task)", + "type": [ + "string", + "null" + ] + }, + "task_parameters": { + "description": "Array of tasks. Each task must have either 'instructions' OR 'prompt' field (at least one is required).", + "items": { + "$ref": "#/$defs/TaskParameter" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "task_parameters" + ], + "title": "CreateDynamicTaskParams", + "type": "object" + }, + "annotations": { + "title": "Create Dynamic Tasks", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + } + }, + { + "name": "platform__manage_schedule", + "description": "Manage scheduled recipe execution for this goose instance.\n\nActions:\n- \"list\": List all scheduled jobs\n- \"create\": Create a new scheduled job from a recipe file\n- \"run_now\": Execute a scheduled job immediately \n- \"pause\": Pause a scheduled job\n- \"unpause\": Resume a paused job\n- \"delete\": Remove a scheduled job\n- \"kill\": Terminate a currently running job\n- \"inspect\": Get details about a running job\n- \"sessions\": List execution history for a job\n- \"session_content\": Get the full content (messages) of a specific session\n", + "inputSchema": { + "properties": { + "action": { + "enum": [ + "list", + "create", + "run_now", + "pause", + "unpause", + "delete", + "kill", + "inspect", + "sessions", + "session_content" + ], + "type": "string" + }, + "cron_expression": { + "description": "A cron expression for create action. Supports both 5-field (minute hour day month weekday) and 6-field (second minute hour day month weekday) formats. 5-field expressions are automatically converted to 6-field by prepending '0' for seconds.", + "type": "string" + }, + "job_id": { + "description": "Job identifier for operations on existing jobs", + "type": "string" + }, + "limit": { + "default": 50, + "description": "Limit for sessions list", + "type": "integer" + }, + "recipe_path": { + "description": "Path to recipe file for create action", + "type": "string" + }, + "session_id": { + "description": "Session identifier for session_content action", + "type": "string" + } + }, + "required": [ + "action" + ], + "type": "object" + }, + "annotations": { + "title": "Manage scheduled recipes", + "readOnlyHint": false, + "destructiveHint": true, + "idempotentHint": false, + "openWorldHint": false + } + }, + { + "name": "subagent__execute_task", + "description": "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.\n EXECUTION STRATEGY DECISION:\n 1. If the tasks are created with execution_mode, use the execution_mode.\n 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'\n\n IMPLEMENTATION:\n - Sequential execution: Call this tool multiple times, passing exactly ONE task per call\n - Parallel execution: Call this tool once, passing an ARRAY of all tasks\n\n EXAMPLES:\n User Intent Based:\n - User: 'get weather and tell me a joke' → Sequential (2 separate tool calls, 1 task each)\n - User: 'get weather and joke in parallel' → Parallel (1 tool call with array of 2 tasks)\n - User: 'run these simultaneously' → Parallel (1 tool call with task array)\n - User: 'do task A then task B' → Sequential (2 separate tool calls)", + "inputSchema": { + "properties": { + "execution_mode": { + "default": "sequential", + "description": "Execution strategy for multiple tasks. Use 'sequential' (default) unless user explicitly requests parallel execution with words like 'parallel', 'simultaneously', 'at the same time', or 'concurrently'.", + "enum": [ + "sequential", + "parallel" + ], + "type": "string" + }, + "task_ids": { + "items": { + "description": "Unique identifier for the task", + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "task_ids" + ], + "type": "object" + }, + "annotations": { + "title": "Run tasks in parallel", + "readOnlyHint": false, + "destructiveHint": true, + "idempotentHint": false, + "openWorldHint": true + } + }, + { + "name": "weather_extension__get_weather", + "description": "Get the weather for a location", + "inputSchema": { + "properties": { + "location": { + "description": "The city and state, e.g. San Francisco, CA", + "type": "string" + } + }, + "required": [ + "location" + ], + "type": "object" + } + } + ] + }, + "output": { + "message": { + "id": null, + "role": "assistant", + "created": 1765495335, + "content": [ + { + "type": "toolRequest", + "id": "zemx6n94z", + "toolCall": { + "status": "success", + "value": { + "name": "weather_extension__get_weather", + "arguments": { + "location": "Berlin, Germany" + } + } + } + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + }, + "usage": { + "model": "llama-3.3-70b-versatile", + "usage": { + "input_tokens": 2258, + "output_tokens": 20, + "total_tokens": 2278 } } } diff --git a/crates/goose-cli/src/scenario_tests/recordings/openai/weather_tool.json b/crates/goose-cli/src/scenario_tests/recordings/openai/weather_tool.json index 195be6d270..10dbf8c397 100644 --- a/crates/goose-cli/src/scenario_tests/recordings/openai/weather_tool.json +++ b/crates/goose-cli/src/scenario_tests/recordings/openai/weather_tool.json @@ -4,9 +4,9 @@ "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", "messages": [ { - "id": "msg_20251124_7_0", + "id": "msg_20251211_26_0", "role": "user", - "created": 1763989801, + "created": 1765495261, "content": [ { "type": "text", @@ -257,11 +257,11 @@ "message": { "id": null, "role": "assistant", - "created": 1763989803, + "created": 1765495262, "content": [ { "type": "toolRequest", - "id": "call_GmIN2vOnjDg6MSVWWFuZ4FlV", + "id": "call_sVLij6GavUkUc5Ri4VeDdgmN", "toolCall": { "status": "success", "value": { @@ -288,62 +288,14 @@ } } }, - "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": { - "input": { - "system": "Reply with only a description in four words or less", - "messages": [ - { - "id": null, - "role": "user", - "created": 1763989801, - "content": [ - { - "type": "text", - "text": "Here are the first few user messages:\ntell me what the weather is in Berlin, Germany\n\nBased on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description" - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - } - ], - "tools": [] - }, - "output": { - "message": { - "id": null, - "role": "assistant", - "created": 1763989804, - "content": [ - { - "type": "text", - "text": "Berlin weather inquiry" - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - }, - "usage": { - "model": "gpt-4o-2024-08-06", - "usage": { - "input_tokens": 84, - "output_tokens": 3, - "total_tokens": 87 - } - } - } - }, - "98726e7698e93241dfcba182bdca3a7d0640f0f538f086f4981dca6689b7ab5a": { + "950a6b5ec929abdd9e7cd337d2ea80c07631c23c6d64edac31988e18c6207e92": { "input": { "system": "You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.\ngoose is being developed as an open-source software project.\n\ngoose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,\nclaude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).\nThese models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10\nmonths prior to the current date.\n\n# Extensions\n\nExtensions allow other applications to provide context to goose. Extensions connect goose to different data sources and\ntools.\nYou are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level\nproblems using the tools in these extensions, and can interact with multiple at once.\n\nIf the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional\nextensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the\nextension_name. You should only enable extensions found from the search_available_extensions tool.\nIf Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load\nnew ones.\n\nBecause you dynamically load extensions, your conversation history may refer\nto interactions with extensions that are not currently active. The currently\nactive extensions are below. Each of these extensions provides tools that are\nin your tool specification.\n\n\n## weather_extension\n\n\n\n\n\n\n# Response Guidelines\n\n- Use Markdown formatting for all responses.\n- Follow best practices for Markdown, including:\n - Using headers for organization.\n - Bullet points for lists.\n - Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic\n links using angle brackets (e.g., ).\n- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the\n language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.\n- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.", "messages": [ { - "id": "msg_20251124_7_0", + "id": "msg_20251211_26_0", "role": "user", - "created": 1763989801, + "created": 1765495261, "content": [ { "type": "text", @@ -356,13 +308,13 @@ } }, { - "id": "msg_9a0fb3d7-d681-40b9-ab58-bb1cbe03e4b0", + "id": "msg_899ef38b-1440-47f7-a942-1b2e58c8835c", "role": "assistant", - "created": 1763989803, + "created": 1765495262, "content": [ { "type": "toolRequest", - "id": "call_GmIN2vOnjDg6MSVWWFuZ4FlV", + "id": "call_sVLij6GavUkUc5Ri4VeDdgmN", "toolCall": { "status": "success", "value": { @@ -380,21 +332,23 @@ } }, { - "id": "msg_51ae1986-b095-4163-8d42-004ff1a28c59", + "id": "msg_2f8ddf2e-dedb-4b99-9549-826796292622", "role": "user", - "created": 1763989803, + "created": 1765495262, "content": [ { "type": "toolResponse", - "id": "call_GmIN2vOnjDg6MSVWWFuZ4FlV", + "id": "call_sVLij6GavUkUc5Ri4VeDdgmN", "toolResult": { "status": "success", - "value": [ - { - "type": "text", - "text": "The weather in Berlin, Germany is cloudy and 18°C" - } - ] + "value": { + "content": [ + { + "type": "text", + "text": "The weather in Berlin, Germany is cloudy and 18°C" + } + ] + } } } ], @@ -642,7 +596,7 @@ "message": { "id": null, "role": "assistant", - "created": 1763989805, + "created": 1765495263, "content": [ { "type": "text", @@ -663,5 +617,53 @@ } } } + }, + "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": { + "input": { + "system": "Reply with only a description in four words or less", + "messages": [ + { + "id": null, + "role": "user", + "created": 1765495261, + "content": [ + { + "type": "text", + "text": "Here are the first few user messages:\ntell me what the weather is in Berlin, Germany\n\nBased on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description" + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + } + ], + "tools": [] + }, + "output": { + "message": { + "id": null, + "role": "assistant", + "created": 1765495262, + "content": [ + { + "type": "text", + "text": "Berlin weather inquiry" + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + }, + "usage": { + "model": "gpt-4o-2024-08-06", + "usage": { + "input_tokens": 84, + "output_tokens": 3, + "total_tokens": 87 + } + } + } } } \ No newline at end of file diff --git a/crates/goose-cli/src/session/export.rs b/crates/goose-cli/src/session/export.rs index 61e537d60c..7246913606 100644 --- a/crates/goose-cli/src/session/export.rs +++ b/crates/goose-cli/src/session/export.rs @@ -221,12 +221,12 @@ pub fn tool_response_to_markdown(resp: &ToolResponse, export_all_content: bool) md.push_str("#### Tool Response:\n"); match &resp.tool_result { - Ok(contents) => { - if contents.is_empty() { + Ok(result) => { + if result.content.is_empty() { md.push_str("*No textual output from tool.*\n"); } - for content in contents { + for content in &result.content { if !export_all_content { if let Some(audience) = content.audience() { if !audience.contains(&Role::Assistant) { @@ -581,7 +581,12 @@ mod tests { }; let tool_response = ToolResponse { id: "test-id".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let result = tool_response_to_markdown(&tool_response, true); @@ -601,7 +606,12 @@ mod tests { }; let tool_response = ToolResponse { id: "test-id".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let result = tool_response_to_markdown(&tool_response, true); @@ -707,7 +717,12 @@ if __name__ == "__main__": }; let tool_response = ToolResponse { id: "shell-cat".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let request_result = tool_request_to_markdown(&tool_request, true); @@ -748,7 +763,12 @@ if __name__ == "__main__": }; let tool_response = ToolResponse { id: "git-status".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let request_result = tool_request_to_markdown(&tool_request, true); @@ -797,7 +817,12 @@ warning: unused variable `x` }; let tool_response = ToolResponse { id: "cargo-build".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let response_result = tool_response_to_markdown(&tool_response, true); @@ -844,7 +869,12 @@ warning: unused variable `x` }; let tool_response = ToolResponse { id: "curl-api".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let response_result = tool_response_to_markdown(&tool_response, true); @@ -880,7 +910,12 @@ warning: unused variable `x` }; let tool_response = ToolResponse { id: "editor-write".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let request_result = tool_request_to_markdown(&tool_request, true); @@ -937,7 +972,12 @@ def process_data(data: List[Dict]) -> List[Dict]: }; let tool_response = ToolResponse { id: "editor-view".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let response_result = tool_response_to_markdown(&tool_response, true); @@ -974,7 +1014,12 @@ Command failed with exit code 2"#; }; let tool_response = ToolResponse { id: "shell-error".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let response_result = tool_response_to_markdown(&tool_response, true); @@ -1014,7 +1059,12 @@ Command failed with exit code 2"#; }; let tool_response = ToolResponse { id: "script-exec".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let request_result = tool_request_to_markdown(&tool_request, true); @@ -1061,7 +1111,12 @@ drwx------ 3 user staff 96 Dec 6 16:20 com.apple.launchd.abc }; let tool_response = ToolResponse { id: "multi-cmd".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let request_result = tool_request_to_markdown(&_tool_request, true); @@ -1104,7 +1159,12 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul }; let tool_response = ToolResponse { id: "grep-search".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let request_result = tool_request_to_markdown(&tool_request, true); @@ -1144,7 +1204,12 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul }; let tool_response = ToolResponse { id: "json-test".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let response_result = tool_response_to_markdown(&tool_response, true); @@ -1185,7 +1250,12 @@ found 0 vulnerabilities"#; }; let tool_response = ToolResponse { id: "npm-install".to_string(), - tool_result: Ok(vec![Content::text(text_content.raw.text)]), + tool_result: Ok(rmcp::model::CallToolResult { + content: vec![Content::text(text_content.raw.text)], + structured_content: None, + is_error: Some(false), + meta: None, + }), }; let request_result = tool_request_to_markdown(&tool_request, true); diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index 642f78e7a2..a5ef298311 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -285,8 +285,8 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) { let config = Config::global(); match &resp.tool_result { - Ok(contents) => { - for content in contents { + Ok(result) => { + for content in &result.content { if let Some(audience) = content.audience() { if !audience.contains(&rmcp::model::Role::User) { continue; diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index 2ff1f1da62..0468bdf1e2 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -354,6 +354,8 @@ derive_utoipa!(Icon as IconSchema); super::routes::agent::start_agent, super::routes::agent::resume_agent, super::routes::agent::get_tools, + super::routes::agent::read_resource, + super::routes::agent::call_tool, super::routes::agent::update_from_session, super::routes::agent::agent_add_extension, super::routes::agent::agent_remove_extension, @@ -514,6 +516,10 @@ derive_utoipa!(Icon as IconSchema); goose::agents::types::SuccessCheck, super::routes::agent::UpdateProviderRequest, super::routes::agent::GetToolsQuery, + super::routes::agent::ReadResourceRequest, + super::routes::agent::ReadResourceResponse, + super::routes::agent::CallToolRequest, + super::routes::agent::CallToolResponse, super::routes::agent::UpdateRouterToolSelectorRequest, super::routes::agent::StartAgentRequest, super::routes::agent::ResumeAgentRequest, diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index e6f8e57474..8911fbd94b 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -25,12 +25,14 @@ use goose::{ agents::{extension::ToolInfo, extension_manager::get_parameter_names}, config::permission::PermissionLevel, }; -use serde::Deserialize; +use rmcp::model::{CallToolRequestParam, Content}; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; use std::path::PathBuf; use std::sync::atomic::Ordering; use std::sync::Arc; +use tokio_util::sync::CancellationToken; use tracing::{error, warn}; #[derive(Deserialize, utoipa::ToSchema)] @@ -90,6 +92,32 @@ pub struct RemoveExtensionRequest { session_id: String, } +#[derive(Deserialize, utoipa::ToSchema)] +pub struct ReadResourceRequest { + session_id: String, + extension_name: String, + uri: String, +} + +#[derive(Serialize, Deserialize, utoipa::ToSchema)] +pub struct ReadResourceResponse { + html: String, +} + +#[derive(Deserialize, utoipa::ToSchema)] +pub struct CallToolRequest { + session_id: String, + name: String, + arguments: Value, +} + +#[derive(Serialize, utoipa::ToSchema)] +pub struct CallToolResponse { + content: Vec, + structured_content: Option, + is_error: bool, +} + #[utoipa::path( post, path = "/agent/start", @@ -484,7 +512,7 @@ async fn update_router_tool_selector( .update_router_tool_selector(None, Some(true)) .await .map_err(|e| { - tracing::error!("Failed to update tool selection strategy: {}", e); + error!("Failed to update tool selection strategy: {}", e); StatusCode::INTERNAL_SERVER_ERROR })?; @@ -564,11 +592,94 @@ async fn stop_agent( Ok(StatusCode::OK) } +#[utoipa::path( + post, + path = "/agent/read_resource", + request_body = ReadResourceRequest, + responses( + (status = 200, description = "Resource read successfully", body = ReadResourceResponse), + (status = 401, description = "Unauthorized - invalid secret key"), + (status = 424, description = "Agent not initialized"), + (status = 404, description = "Resource not found"), + (status = 500, description = "Internal server error") + ) +)] +async fn read_resource( + State(state): State>, + Json(payload): Json, +) -> Result, StatusCode> { + let agent = state + .get_agent_for_route(payload.session_id.clone()) + .await?; + + let html = agent + .extension_manager + .read_ui_resource( + &payload.uri, + &payload.extension_name, + CancellationToken::default(), + ) + .await + .map_err(|_e| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(ReadResourceResponse { html })) +} + +#[utoipa::path( + post, + path = "/agent/call_tool", + request_body = CallToolRequest, + responses( + (status = 200, description = "Resource read successfully", body = CallToolResponse), + (status = 401, description = "Unauthorized - invalid secret key"), + (status = 424, description = "Agent not initialized"), + (status = 404, description = "Resource not found"), + (status = 500, description = "Internal server error") + ) +)] +async fn call_tool( + State(state): State>, + Json(payload): Json, +) -> Result, StatusCode> { + let agent = state + .get_agent_for_route(payload.session_id.clone()) + .await?; + + let arguments = match payload.arguments { + Value::Object(map) => Some(map), + _ => None, + }; + + let tool_call = CallToolRequestParam { + name: payload.name.into(), + arguments, + }; + + let tool_result = agent + .extension_manager + .dispatch_tool_call(tool_call, CancellationToken::default()) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let result = tool_result + .result + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(CallToolResponse { + content: result.content, + structured_content: result.structured_content, + is_error: result.is_error.unwrap_or(false), + })) +} + pub fn routes(state: Arc) -> Router { Router::new() .route("/agent/start", post(start_agent)) .route("/agent/resume", post(resume_agent)) .route("/agent/tools", get(get_tools)) + .route("/agent/read_resource", post(read_resource)) + .route("/agent/call_tool", post(call_tool)) .route("/agent/update_provider", post(update_agent_provider)) .route( "/agent/update_router_tool_selector", diff --git a/crates/goose-server/src/routes/errors.rs b/crates/goose-server/src/routes/errors.rs index c265482d05..fb6ab9b8be 100644 --- a/crates/goose-server/src/routes/errors.rs +++ b/crates/goose-server/src/routes/errors.rs @@ -3,6 +3,7 @@ use axum::{ response::{IntoResponse, Response}, Json, }; +use goose::config::ConfigError; use serde::Serialize; use utoipa::ToSchema; @@ -37,3 +38,9 @@ impl From for ErrorResponse { Self::internal(err.to_string()) } } + +impl From for ErrorResponse { + fn from(err: ConfigError) -> Self { + Self::internal(err.to_string()) + } +} diff --git a/crates/goose/examples/image_tool.rs b/crates/goose/examples/image_tool.rs index 517bebd92d..f70e0a0073 100644 --- a/crates/goose/examples/image_tool.rs +++ b/crates/goose/examples/image_tool.rs @@ -37,8 +37,15 @@ async fn main() -> Result<()> { arguments: Some(object!({"path": "./test_image.png"})), }), ), - Message::user() - .with_tool_response("000", Ok(vec![Content::image(base64_image, "image/png")])), + Message::user().with_tool_response( + "000", + Ok(rmcp::model::CallToolResult { + content: vec![Content::image(base64_image, "image/png")], + structured_content: None, + is_error: Some(false), + meta: None, + }), + ), ]; // Get a response from the model about the image diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index ef4d5f6919..edfe875bc7 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -58,7 +58,7 @@ use crate::tool_monitor::RepetitionInspector; use crate::utils::is_token_cancelled; use regex::Regex; use rmcp::model::{ - CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, + CallToolRequestParam, CallToolResult, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ServerNotification, Tool, }; use serde_json::Value; @@ -100,7 +100,7 @@ pub struct Agent { pub(super) prompt_manager: Mutex, pub(super) confirmation_tx: mpsc::Sender<(String, PermissionConfirmation)>, pub(super) confirmation_rx: Mutex>, - pub(super) tool_result_tx: mpsc::Sender<(String, ToolResult>)>, + pub(super) tool_result_tx: mpsc::Sender<(String, ToolResult)>, pub(super) tool_result_rx: ToolResultReceiver, pub tool_route_manager: Arc, @@ -128,7 +128,8 @@ pub enum ToolStreamItem { Result(T), } -pub type ToolStream = Pin>>> + Send>>; +pub type ToolStream = + Pin>> + Send>>; // tool_stream combines a stream of ServerNotifications with a future representing the // final result of the tool call. MCP notifications are not request-scoped, but @@ -137,7 +138,7 @@ pub type ToolStream = Pin(rx: S, done: F) -> ToolStream where S: Stream + Send + Unpin + 'static, - F: Future>> + Send + 'static, + F: Future> + Send + 'static, { Box::pin(async_stream::stream! { tokio::pin!(done); @@ -360,7 +361,12 @@ impl Agent { let mut response = response_msg.lock().await; *response = response.clone().with_tool_response( request.id.clone(), - Ok(vec![rmcp::model::Content::text(DECLINED_RESPONSE)]), + Ok(CallToolResult { + content: vec![rmcp::model::Content::text(DECLINED_RESPONSE)], + structured_content: None, + is_error: Some(true), + meta: None, + }), ); } } @@ -454,7 +460,13 @@ impl Agent { let result = self .handle_schedule_management(arguments, request_id.clone()) .await; - return (request_id, Ok(ToolCallResult::from(result))); + let wrapped_result = result.map(|content| CallToolResult { + content, + structured_content: None, + is_error: Some(false), + meta: None, + }); + return (request_id, Ok(ToolCallResult::from(wrapped_result))); } if tool_call.name == FINAL_OUTPUT_TOOL_NAME { @@ -1090,7 +1102,12 @@ impl Agent { let mut response = response_msg.lock().await; *response = response.clone().with_tool_response( request.id.clone(), - Ok(vec![Content::text(CHAT_MODE_TOOL_SKIPPED_RESPONSE)]), + Ok(CallToolResult { + content: vec![Content::text(CHAT_MODE_TOOL_SKIPPED_RESPONSE)], + structured_content: None, + is_error: Some(false), + meta: None, + }), ); } } @@ -1421,7 +1438,7 @@ impl Agent { Ok(plan_prompt) } - pub async fn handle_tool_result(&self, id: String, result: ToolResult>) { + pub async fn handle_tool_result(&self, id: String, result: ToolResult) { if let Err(e) = self.tool_result_tx.send((id, result)).await { error!("Failed to send tool result: {}", e); } diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index 4384e9841e..2af90d66cb 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -41,8 +41,8 @@ use crate::oauth::oauth_flow; use crate::prompt_template; use crate::subprocess::configure_command_no_window; use rmcp::model::{ - CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ResourceContents, - ServerInfo, Tool, + CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, RawContent, + Resource, ResourceContents, ServerInfo, Tool, }; use rmcp::transport::auth::AuthClient; use schemars::_private::NoSerialize; @@ -758,6 +758,7 @@ impl ExtensionManager { uri, extension_name.unwrap(), cancellation_token.clone(), + true, ) .await?; return Ok(result); @@ -773,7 +774,12 @@ impl ExtensionManager { for extension_name in extension_names { let result = self - .read_resource_from_extension(uri, &extension_name, cancellation_token.clone()) + .read_resource_from_extension( + uri, + &extension_name, + cancellation_token.clone(), + true, + ) .await; match result { Ok(result) => return Ok(result), @@ -807,6 +813,7 @@ impl ExtensionManager { uri: &str, extension_name: &str, cancellation_token: CancellationToken, + format_with_uri: bool, ) -> Result, ErrorData> { let available_extensions = self .extensions @@ -840,9 +847,12 @@ impl ExtensionManager { let mut result = Vec::new(); for content in read_result.contents { - // Only reading the text resource content; skipping the blob content cause it's too long if let ResourceContents::TextResourceContents { text, .. } = content { - let content_str = format!("{}\n\n{}", uri, text); + let content_str = if format_with_uri { + format!("{}\n\n{}", uri, text) + } else { + text + }; result.push(Content::text(content_str)); } } @@ -850,6 +860,65 @@ impl ExtensionManager { Ok(result) } + pub async fn get_ui_resources(&self) -> Result, ErrorData> { + let mut ui_resources = Vec::new(); + + let extensions_to_check: Vec<(String, McpClientBox)> = { + let extensions = self.extensions.lock().await; + extensions + .iter() + .map(|(name, ext)| (name.clone(), ext.get_client())) + .collect() + }; + + for (extension_name, client) in extensions_to_check { + let client_guard = client.lock().await; + + match client_guard + .list_resources(None, CancellationToken::default()) + .await + { + Ok(list_response) => { + for resource in list_response.resources { + if resource.uri.starts_with("ui://") { + ui_resources.push((extension_name.clone(), resource)); + } + } + } + Err(e) => { + warn!("Failed to list resources for {}: {:?}", extension_name, e); + } + } + } + + Ok(ui_resources) + } + + pub async fn read_ui_resource( + &self, + uri: &str, + extension_name: &str, + cancellation_token: CancellationToken, + ) -> Result { + let contents = self + .read_resource_from_extension(uri, extension_name, cancellation_token, false) + .await?; + + contents + .into_iter() + .find_map(|c| match c.raw { + RawContent::Text(text_content) => Some(text_content.text), + _ => None, + }) + .ok_or_else(|| { + ErrorData::new( + ErrorCode::RESOURCE_NOT_FOUND, + format!("No text content in resource '{}'", uri), + None, + ) + }) + } + async fn list_resources_from_extension( &self, extension_name: &str, @@ -936,7 +1005,6 @@ impl ExtensionManager { } } - // Log any errors that occurred if !errors.is_empty() { tracing::error!( errors = ?errors @@ -998,7 +1066,6 @@ impl ExtensionManager { client_guard .call_tool(&tool_name, arguments, cancellation_token) .await - .map(|call| call.content) .map_err(|e| match e { ServiceError::McpError(error_data) => error_data, _ => { @@ -1077,7 +1144,6 @@ impl ExtensionManager { } } - // Log any errors that occurred if !errors.is_empty() { tracing::debug!( errors = ?errors diff --git a/crates/goose/src/agents/final_output_tool.rs b/crates/goose/src/agents/final_output_tool.rs index 439cde5157..0adea4db4a 100644 --- a/crates/goose/src/agents/final_output_tool.rs +++ b/crates/goose/src/agents/final_output_tool.rs @@ -123,9 +123,14 @@ impl FinalOutputTool { match result { Ok(parsed_value) => { self.final_output = Some(Self::parsed_final_output_string(parsed_value)); - ToolCallResult::from(Ok(vec![Content::text( - "Final output successfully collected.".to_string(), - )])) + ToolCallResult::from(Ok(rmcp::model::CallToolResult { + content: vec![Content::text( + "Final output successfully collected.".to_string(), + )], + structured_content: None, + is_error: Some(false), + meta: None, + })) } Err(error) => ToolCallResult::from(Err(ErrorData { code: ErrorCode::INVALID_PARAMS, diff --git a/crates/goose/src/agents/large_response_handler.rs b/crates/goose/src/agents/large_response_handler.rs index f45dc01abc..e1554ed4e2 100644 --- a/crates/goose/src/agents/large_response_handler.rs +++ b/crates/goose/src/agents/large_response_handler.rs @@ -1,5 +1,5 @@ use chrono::Utc; -use rmcp::model::{Content, ErrorData}; +use rmcp::model::{CallToolResult, Content, ErrorData}; use std::fs::File; use std::io::Write; @@ -7,13 +7,13 @@ const LARGE_TEXT_THRESHOLD: usize = 200_000; /// Process tool response and handle large text content pub fn process_tool_response( - response: Result, ErrorData>, -) -> Result, ErrorData> { + response: Result, +) -> Result { match response { - Ok(contents) => { + Ok(mut result) => { let mut processed_contents = Vec::new(); - for content in contents { + for content in result.content { match content.as_text() { Some(text_content) => { // Check if text exceeds threshold @@ -51,7 +51,8 @@ pub fn process_tool_response( } } - Ok(processed_contents) + result.content = processed_contents; + Ok(result) } Err(e) => Err(e), } @@ -89,14 +90,19 @@ mod tests { let small_text = "This is a small text response"; let content = Content::text(small_text.to_string()); - let response = Ok(vec![content]); + let response = Ok(CallToolResult { + content: vec![content], + structured_content: None, + is_error: Some(false), + meta: None, + }); // Process the response let processed = process_tool_response(response).unwrap(); // Verify the response is unchanged - assert_eq!(processed.len(), 1); - if let Some(text_content) = processed[0].as_text() { + assert_eq!(processed.content.len(), 1); + if let Some(text_content) = processed.content[0].as_text() { assert_eq!(text_content.text, small_text); } else { panic!("Expected text content"); @@ -109,14 +115,19 @@ mod tests { let large_text = "a".repeat(LARGE_TEXT_THRESHOLD + 1000); let content = Content::text(large_text.clone()); - let response = Ok(vec![content]); + let response = Ok(CallToolResult { + content: vec![content], + structured_content: None, + is_error: Some(false), + meta: None, + }); // Process the response let processed = process_tool_response(response).unwrap(); // Verify the response contains a message about the file - assert_eq!(processed.len(), 1); - if let Some(text_content) = processed[0].as_text() { + assert_eq!(processed.content.len(), 1); + if let Some(text_content) = processed.content[0].as_text() { assert!(text_content .text .contains("The response returned from the tool call was larger")); @@ -146,14 +157,19 @@ mod tests { // Create an image content let image_content = Content::image("base64data".to_string(), "image/png".to_string()); - let response = Ok(vec![image_content]); + let response = Ok(CallToolResult { + content: vec![image_content], + structured_content: None, + is_error: Some(false), + meta: None, + }); // Process the response let processed = process_tool_response(response).unwrap(); // Verify the response is unchanged - assert_eq!(processed.len(), 1); - if let Some(img) = processed[0].as_image() { + assert_eq!(processed.content.len(), 1); + if let Some(img) = processed.content[0].as_image() { assert_eq!(img.data, "base64data"); assert_eq!(img.mime_type, "image/png"); } else { @@ -168,23 +184,28 @@ mod tests { let large_text = Content::text("a".repeat(LARGE_TEXT_THRESHOLD + 1000)); let image = Content::image("image_data".to_string(), "image/jpeg".to_string()); - let response = Ok(vec![small_text, large_text, image]); + let response = Ok(CallToolResult { + content: vec![small_text, large_text, image], + structured_content: None, + is_error: Some(false), + meta: None, + }); // Process the response let processed = process_tool_response(response).unwrap(); // Verify each item is handled correctly - assert_eq!(processed.len(), 3); + assert_eq!(processed.content.len(), 3); // First item should be unchanged small text - if let Some(text_content) = processed[0].as_text() { + if let Some(text_content) = processed.content[0].as_text() { assert_eq!(text_content.text, "Small text"); } else { panic!("Expected text content"); } // Second item should be a message about the file - if let Some(text_content) = processed[1].as_text() { + if let Some(text_content) = processed.content[1].as_text() { assert!(text_content .text .contains("The response returned from the tool call was larger")); @@ -201,7 +222,7 @@ mod tests { } // Third item should be unchanged image - if let Some(img) = processed[2].as_image() { + if let Some(img) = processed.content[2].as_image() { assert_eq!(img.data, "image_data"); assert_eq!(img.mime_type, "image/jpeg"); } else { @@ -217,7 +238,7 @@ mod tests { message: Cow::from("Test error"), data: None, }; - let response: Result, ErrorData> = Err(error); + let response: Result = Err(error); // Process the response let processed = process_tool_response(response); diff --git a/crates/goose/src/agents/moim.rs b/crates/goose/src/agents/moim.rs index d920f3b7dc..97f273d524 100644 --- a/crates/goose/src/agents/moim.rs +++ b/crates/goose/src/agents/moim.rs @@ -106,7 +106,15 @@ mod tests { arguments: None, }), ), - Message::user().with_tool_response("search_1", Ok(vec![])), + Message::user().with_tool_response( + "search_1", + Ok(rmcp::model::CallToolResult { + content: vec![], + structured_content: None, + is_error: Some(false), + meta: None, + }), + ), Message::assistant() .with_text("I need to search more") .with_tool_request( @@ -116,7 +124,15 @@ mod tests { arguments: None, }), ), - Message::user().with_tool_response("search_2", Ok(vec![])), + Message::user().with_tool_response( + "search_2", + Ok(rmcp::model::CallToolResult { + content: vec![], + structured_content: None, + is_error: Some(false), + meta: None, + }), + ), ]); let result = inject_moim(conv, &em).await; diff --git a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs index d0dd14a6e9..66729d04dd 100644 --- a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs +++ b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs @@ -370,5 +370,10 @@ pub async fn create_dynamic_task( }; tasks_manager.save_tasks(tasks).await; - ToolCallResult::from(Ok(vec![Content::text(tasks_json)])) + ToolCallResult::from(Ok(rmcp::model::CallToolResult { + content: vec![Content::text(tasks_json)], + structured_content: None, + is_error: Some(false), + meta: None, + })) } diff --git a/crates/goose/src/agents/sub_recipe_manager.rs b/crates/goose/src/agents/sub_recipe_manager.rs index c8603a0090..6e7d8820bb 100644 --- a/crates/goose/src/agents/sub_recipe_manager.rs +++ b/crates/goose/src/agents/sub_recipe_manager.rs @@ -63,7 +63,12 @@ impl SubRecipeManager { .call_sub_recipe_tool(tool_name, params, tasks_manager, parent_working_dir) .await; match result { - Ok(call_result) => ToolCallResult::from(Ok(call_result)), + Ok(content) => ToolCallResult::from(Ok(rmcp::model::CallToolResult { + content, + structured_content: None, + is_error: Some(false), + meta: None, + })), Err(e) => ToolCallResult::from(Err(ErrorData { code: ErrorCode::INTERNAL_ERROR, message: Cow::from(e.to_string()), diff --git a/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs b/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs index b70f71d3f2..2fa463a4da 100644 --- a/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs +++ b/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs @@ -82,7 +82,12 @@ pub async fn run_tasks( { Ok(result) => { let output = serde_json::to_string(&result).unwrap(); - Ok(vec![Content::text(output)]) + Ok(rmcp::model::CallToolResult { + content: vec![Content::text(output)], + structured_content: None, + is_error: Some(false), + meta: None, + }) } Err(e) => Err(ErrorData { code: ErrorCode::INTERNAL_ERROR, diff --git a/crates/goose/src/agents/subagent_handler.rs b/crates/goose/src/agents/subagent_handler.rs index 2d6f7f8167..cd0834a12a 100644 --- a/crates/goose/src/agents/subagent_handler.rs +++ b/crates/goose/src/agents/subagent_handler.rs @@ -61,8 +61,9 @@ pub async fn run_complete_subagent_task( tool_response, ) => { // Extract text from tool response - if let Ok(contents) = &tool_response.tool_result { - let texts: Vec = contents + if let Ok(result) = &tool_response.tool_result { + let texts: Vec = result + .content .iter() .filter_map(|content| { if let rmcp::model::RawContent::Text(raw_text_content) = diff --git a/crates/goose/src/agents/tool_execution.rs b/crates/goose/src/agents/tool_execution.rs index f7fdfa914a..144d3d11ff 100644 --- a/crates/goose/src/agents/tool_execution.rs +++ b/crates/goose/src/agents/tool_execution.rs @@ -16,12 +16,12 @@ use rmcp::model::{Content, ServerNotification}; // ToolCallResult combines the result of a tool call with an optional notification stream that // can be used to receive notifications from the tool. pub struct ToolCallResult { - pub result: Box>> + Send + Unpin>, + pub result: Box> + Send + Unpin>, pub notification_stream: Option + Send + Unpin>>, } -impl From>> for ToolCallResult { - fn from(result: ToolResult>) -> Self { +impl From> for ToolCallResult { + fn from(result: ToolResult) -> Self { Self { result: Box::new(futures::future::ready(result)), notification_stream: None, @@ -122,7 +122,12 @@ impl Agent { let mut response = response_msg.lock().await; *response = response.clone().with_tool_response( request.id.clone(), - Ok(vec![Content::text(DECLINED_RESPONSE)]), + Ok(rmcp::model::CallToolResult { + content: vec![Content::text(DECLINED_RESPONSE)], + structured_content: None, + is_error: Some(true), + meta: None, + }), ); } } diff --git a/crates/goose/src/agents/tool_route_manager.rs b/crates/goose/src/agents/tool_route_manager.rs index 363a5880e7..c757a173ae 100644 --- a/crates/goose/src/agents/tool_route_manager.rs +++ b/crates/goose/src/agents/tool_route_manager.rs @@ -56,7 +56,12 @@ impl ToolRouteManager { let selector = self.router_tool_selector.lock().await.clone(); match selector.as_ref() { Some(selector) => match selector.select_tools(arguments).await { - Ok(tools) => Ok(ToolCallResult::from(Ok(tools))), + Ok(content) => Ok(ToolCallResult::from(Ok(rmcp::model::CallToolResult { + content, + structured_content: None, + is_error: Some(false), + meta: None, + }))), Err(e) => Err(ErrorData::new( ErrorCode::INTERNAL_ERROR, format!("Failed to select tools: {}", e), diff --git a/crates/goose/src/agents/types.rs b/crates/goose/src/agents/types.rs index 0f2902bf9e..13138a7fb7 100644 --- a/crates/goose/src/agents/types.rs +++ b/crates/goose/src/agents/types.rs @@ -1,13 +1,13 @@ use crate::mcp_utils::ToolResult; use crate::providers::base::Provider; -use rmcp::model::{Content, Tool}; +use rmcp::model::{CallToolResult, Tool}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use utoipa::ToSchema; /// Type alias for the tool result channel receiver -pub type ToolResultReceiver = Arc>)>>>; +pub type ToolResultReceiver = Arc)>>>; // We use double Arc here to allow easy provider swaps while sharing concurrent access pub type SharedProvider = Arc>>>; diff --git a/crates/goose/src/context_mgmt/mod.rs b/crates/goose/src/context_mgmt/mod.rs index da682a9d77..4e312a48fa 100644 --- a/crates/goose/src/context_mgmt/mod.rs +++ b/crates/goose/src/context_mgmt/mod.rs @@ -355,8 +355,9 @@ fn format_message_for_compacting(msg: &Message) -> String { } } MessageContent::ToolResponse(res) => { - if let Ok(contents) = &res.tool_result { - let text_items: Vec = contents + if let Ok(result) = &res.tool_result { + let text_items: Vec = result + .content .iter() .filter_map(|content| { content.as_text().map(|text_str| text_str.text.clone()) @@ -517,7 +518,12 @@ mod tests { ), Message::user().with_tool_response( "tool_0", - Ok(vec![RawContent::text("hello, world").no_annotation()]), + Ok(rmcp::model::CallToolResult { + content: vec![RawContent::text("hello, world").no_annotation()], + structured_content: None, + is_error: Some(false), + meta: None, + }), ), ]; @@ -550,9 +556,12 @@ mod tests { )); messages.push(Message::user().with_tool_response( format!("tool_{}", i), - Ok(vec![ - RawContent::text(format!("response{}", i)).no_annotation(), - ]), + Ok(rmcp::model::CallToolResult { + content: vec![RawContent::text(format!("response{}", i)).no_annotation()], + structured_content: None, + is_error: Some(false), + meta: None, + }), )); } diff --git a/crates/goose/src/conversation/message.rs b/crates/goose/src/conversation/message.rs index 19193f4a6c..20ea358889 100644 --- a/crates/goose/src/conversation/message.rs +++ b/crates/goose/src/conversation/message.rs @@ -1,9 +1,9 @@ use crate::mcp_utils::ToolResult; use chrono::Utc; use rmcp::model::{ - AnnotateAble, CallToolRequestParam, Content, ImageContent, JsonObject, PromptMessage, - PromptMessageContent, PromptMessageRole, RawContent, RawImageContent, RawTextContent, - ResourceContents, Role, TextContent, + AnnotateAble, CallToolRequestParam, CallToolResult, Content, ImageContent, JsonObject, + PromptMessage, PromptMessageContent, PromptMessageRole, RawContent, RawImageContent, + RawTextContent, ResourceContents, Role, TextContent, }; use serde::{Deserialize, Deserializer, Serialize}; use std::collections::HashSet; @@ -88,7 +88,7 @@ pub struct ToolResponse { pub id: String, #[serde(with = "tool_result_serde")] #[schema(value_type = Object)] - pub tool_result: ToolResult>, + pub tool_result: ToolResult, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -190,7 +190,7 @@ impl fmt::Display for MessageContent { f, "[ToolResponse: {}]", match &r.tool_result { - Ok(contents) => format!("{} content item(s)", contents.len()), + Ok(result) => format!("{} content item(s)", result.content.len()), Err(e) => format!("Error: {e}"), } ), @@ -266,7 +266,7 @@ impl MessageContent { }) } - pub fn tool_response>(id: S, tool_result: ToolResult>) -> Self { + pub fn tool_response>(id: S, tool_result: ToolResult) -> Self { MessageContent::ToolResponse(ToolResponse { id: id.into(), tool_result, @@ -380,8 +380,9 @@ impl MessageContent { pub fn as_tool_response_text(&self) -> Option { if let Some(tool_response) = self.as_tool_response() { - if let Ok(contents) = &tool_response.tool_result { - let texts: Vec = contents + if let Ok(result) = &tool_response.tool_result { + let texts: Vec = result + .content .iter() .filter_map(|content| content.as_text().map(|t| t.text.to_string())) .collect(); @@ -644,7 +645,7 @@ impl Message { pub fn with_tool_response>( self, id: S, - result: ToolResult>, + result: ToolResult, ) -> Self { self.with_content(MessageContent::tool_response(id, result)) } diff --git a/crates/goose/src/conversation/mod.rs b/crates/goose/src/conversation/mod.rs index 14d9e5c281..524fbce39d 100644 --- a/crates/goose/src/conversation/mod.rs +++ b/crates/goose/src/conversation/mod.rs @@ -555,7 +555,15 @@ mod tests { arguments: Some(object!({"query": "rust programming"})), }), ), - Message::user().with_tool_response("search_1", Ok(vec![])), + Message::user().with_tool_response( + "search_1", + Ok(rmcp::model::CallToolResult { + content: vec![], + structured_content: None, + is_error: Some(false), + meta: None, + }), + ), Message::assistant().with_text("Based on the search results, here's what I found..."), ]; @@ -592,7 +600,15 @@ mod tests { Message::user().with_text("Another user message"), Message::assistant() .with_text("Response") - .with_tool_response("orphan_1", Ok(vec![])), // Wrong role + .with_tool_response( + "orphan_1", + Ok(rmcp::model::CallToolResult { + content: vec![], + structured_content: None, + is_error: Some(false), + meta: None, + }), + ), // Wrong role Message::assistant().with_thinking("Let me think", "sig"), Message::user() .with_tool_request( @@ -642,7 +658,15 @@ mod tests { }), ), Message::user(), - Message::user().with_tool_response("wrong_id", Ok(vec![])), + Message::user().with_tool_response( + "wrong_id", + Ok(rmcp::model::CallToolResult { + content: vec![], + structured_content: None, + is_error: Some(false), + meta: None, + }), + ), Message::assistant().with_tool_request( "search_2", Ok(CallToolRequestParam { @@ -687,7 +711,12 @@ mod tests { .with_tool_request("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(CallToolRequestParam { name: "developer__shell".into(), arguments: Some(object!({"command": "wc slack.yaml"})) })), Message::user() - .with_tool_response("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(vec![])), + .with_tool_response("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(rmcp::model::CallToolResult { + content: vec![], + structured_content: None, + is_error: Some(false), + meta: None, + })), Message::assistant() .with_text("I ran `ls -la` in the current directory and found several files. Looking at the file sizes, I can see that both `slack.yaml` and `subrecipes.yaml` are 0 bytes (the smallest files). I ran a word count on `slack.yaml` which shows: **0 lines**, **0 words**, **0 characters**"), @@ -718,7 +747,15 @@ mod tests { arguments: Some(object!({})), }), ), - Message::user().with_tool_response("search_1", Ok(vec![])), + Message::user().with_tool_response( + "search_1", + Ok(rmcp::model::CallToolResult { + content: vec![], + structured_content: None, + is_error: Some(false), + meta: None, + }), + ), Message::user().with_text("Thanks!"), ]; diff --git a/crates/goose/src/permission/permission_store.rs b/crates/goose/src/permission/permission_store.rs index 4dee06ff8a..fded07081c 100644 --- a/crates/goose/src/permission/permission_store.rs +++ b/crates/goose/src/permission/permission_store.rs @@ -14,7 +14,7 @@ pub struct ToolPermissionRecord { allowed: bool, context_hash: String, // Hash of the tool's arguments/context to differentiate similar calls #[serde(skip_serializing_if = "Option::is_none")] // Don't serialize if None - readable_context: Option, // Add this field + readable_context: Option, timestamp: i64, expiry: Option, // Optional expiry timestamp } diff --git a/crates/goose/src/providers/claude_code.rs b/crates/goose/src/providers/claude_code.rs index 7ed9963377..6c148aaa1f 100644 --- a/crates/goose/src/providers/claude_code.rs +++ b/crates/goose/src/providers/claude_code.rs @@ -74,9 +74,10 @@ impl ClaudeCodeProvider { } } MessageContent::ToolResponse(tool_response) => { - if let Ok(tool_contents) = &tool_response.tool_result { + if let Ok(result) = &tool_response.tool_result { // Convert tool result contents to text - let content_text = tool_contents + let content_text = result + .content .iter() .filter_map(|content| match &content.raw { rmcp::model::RawContent::Text(text_content) => { diff --git a/crates/goose/src/providers/cursor_agent.rs b/crates/goose/src/providers/cursor_agent.rs index c6cdd0ab0f..ffe7c1058d 100644 --- a/crates/goose/src/providers/cursor_agent.rs +++ b/crates/goose/src/providers/cursor_agent.rs @@ -86,8 +86,9 @@ impl CursorAgentProvider { } } MessageContent::ToolResponse(tool_response) => { - if let Ok(tool_contents) = &tool_response.tool_result { - let content_text = tool_contents + if let Ok(result) = &tool_response.tool_result { + let content_text = result + .content .iter() .filter_map(|content| match &content.raw { rmcp::model::RawContent::Text(text_content) => { diff --git a/crates/goose/src/providers/formats/anthropic.rs b/crates/goose/src/providers/formats/anthropic.rs index da573678b3..3771b3adad 100644 --- a/crates/goose/src/providers/formats/anthropic.rs +++ b/crates/goose/src/providers/formats/anthropic.rs @@ -68,6 +68,7 @@ pub fn format_messages(messages: &[Message]) -> Vec { MessageContent::ToolResponse(tool_response) => match &tool_response.tool_result { Ok(result) => { let text = result + .content .iter() .filter_map(|c| c.as_text().map(|t| t.text.clone())) .collect::>() diff --git a/crates/goose/src/providers/formats/bedrock.rs b/crates/goose/src/providers/formats/bedrock.rs index f343059df5..d81d7b8b5b 100644 --- a/crates/goose/src/providers/formats/bedrock.rs +++ b/crates/goose/src/providers/formats/bedrock.rs @@ -86,8 +86,9 @@ pub fn to_bedrock_message_content(content: &MessageContent) -> Result { let content = match &tool_res.tool_result { - Ok(content) => Some( - content + Ok(result) => Some( + result + .content .iter() // Filter out content items that have User in their audience .filter(|c| { @@ -318,6 +319,12 @@ pub fn from_bedrock_content_block(block: &bedrock::ContentBlock) -> Result>>() + .map(|content| rmcp::model::CallToolResult { + content, + structured_content: None, + is_error: Some(false), + meta: None, + }) }, ), _ => bail!("Unsupported content block type from Bedrock"), diff --git a/crates/goose/src/providers/formats/databricks.rs b/crates/goose/src/providers/formats/databricks.rs index 4083fbef7e..9b980c3ae2 100644 --- a/crates/goose/src/providers/formats/databricks.rs +++ b/crates/goose/src/providers/formats/databricks.rs @@ -133,9 +133,10 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec { match &response.tool_result { - Ok(contents) => { + Ok(call_result) => { // Send only contents with no audience or with Assistant in the audience - let abridged: Vec<_> = contents + let abridged: Vec<_> = call_result + .content .iter() .filter(|content| { content @@ -638,6 +639,7 @@ pub fn create_request( mod tests { use super::*; use crate::conversation::message::Message; + use rmcp::model::CallToolResult; use rmcp::object; use serde_json::json; @@ -727,8 +729,15 @@ mod tests { panic!("should be tool request"); }; - messages - .push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")]))); + messages.push(Message::user().with_tool_response( + tool_id, + Ok(CallToolResult { + content: vec![Content::text("Result")], + structured_content: None, + is_error: Some(false), + meta: None, + }), + )); let as_value = serde_json::to_value(format_messages(&messages, &ImageFormat::OpenAi)).unwrap(); @@ -764,8 +773,15 @@ mod tests { panic!("should be tool request"); }; - messages - .push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")]))); + messages.push(Message::user().with_tool_response( + tool_id, + Ok(CallToolResult { + content: vec![Content::text("Result")], + structured_content: None, + is_error: Some(false), + meta: None, + }), + )); let as_value = serde_json::to_value(format_messages(&messages, &ImageFormat::OpenAi)).unwrap(); diff --git a/crates/goose/src/providers/formats/google.rs b/crates/goose/src/providers/formats/google.rs index a0b4a16cf3..1e9a85dd9e 100644 --- a/crates/goose/src/providers/formats/google.rs +++ b/crates/goose/src/providers/formats/google.rs @@ -70,9 +70,10 @@ pub fn format_messages(messages: &[Message]) -> Vec { }, MessageContent::ToolResponse(response) => { match &response.tool_result { - Ok(contents) => { + Ok(result) => { // Send only contents with no audience or with Assistant in the audience - let abridged: Vec<_> = contents + let abridged: Vec<_> = result + .content .iter() .filter(|content| { content.audience().is_none_or(|audience| { @@ -394,7 +395,7 @@ pub fn create_request( mod tests { use super::*; use crate::conversation::message::Message; - use rmcp::model::CallToolRequestParam; + use rmcp::model::{CallToolRequestParam, CallToolResult}; use rmcp::{model::Content, object}; use serde_json::json; @@ -429,7 +430,12 @@ mod tests { 0, vec![MessageContent::tool_response( id.to_string(), - Ok(tool_response), + Ok(CallToolResult { + content: tool_response, + structured_content: None, + is_error: Some(false), + meta: None, + }), )], ) } diff --git a/crates/goose/src/providers/formats/openai.rs b/crates/goose/src/providers/formats/openai.rs index fab1b8d5ed..04fd962a1b 100644 --- a/crates/goose/src/providers/formats/openai.rs +++ b/crates/goose/src/providers/formats/openai.rs @@ -128,9 +128,10 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec< }, MessageContent::ToolResponse(response) => { match &response.tool_result { - Ok(contents) => { + Ok(result) => { // Send only contents with no audience or with Assistant in the audience - let abridged: Vec<_> = contents + let abridged: Vec<_> = result + .content .iter() .filter(|content| { content @@ -710,6 +711,7 @@ pub fn create_request( mod tests { use super::*; use crate::conversation::message::Message; + use rmcp::model::CallToolResult; use rmcp::object; use serde_json::json; use tokio::pin; @@ -868,8 +870,15 @@ mod tests { panic!("should be tool request"); }; - messages - .push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")]))); + messages.push(Message::user().with_tool_response( + tool_id, + Ok(CallToolResult { + content: vec![Content::text("Result")], + structured_content: None, + is_error: Some(false), + meta: None, + }), + )); let spec = format_messages(&messages, &ImageFormat::OpenAi); @@ -904,8 +913,15 @@ mod tests { panic!("should be tool request"); }; - messages - .push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")]))); + messages.push(Message::user().with_tool_response( + tool_id, + Ok(CallToolResult { + content: vec![Content::text("Result")], + structured_content: None, + is_error: Some(false), + meta: None, + }), + )); let spec = format_messages(&messages, &ImageFormat::OpenAi); diff --git a/crates/goose/src/providers/formats/snowflake.rs b/crates/goose/src/providers/formats/snowflake.rs index 19f3012f10..bfe69e34cc 100644 --- a/crates/goose/src/providers/formats/snowflake.rs +++ b/crates/goose/src/providers/formats/snowflake.rs @@ -37,6 +37,7 @@ pub fn format_messages(messages: &[Message]) -> Vec { MessageContent::ToolResponse(tool_response) => { if let Ok(result) = &tool_response.tool_result { let text = result + .content .iter() .filter_map(|c| c.as_text().map(|t| t.text.clone())) .collect::>() diff --git a/crates/goose/src/providers/lead_worker.rs b/crates/goose/src/providers/lead_worker.rs index 1dc8fe7a8b..e0786b4186 100644 --- a/crates/goose/src/providers/lead_worker.rs +++ b/crates/goose/src/providers/lead_worker.rs @@ -215,9 +215,9 @@ impl LeadWorkerProvider { if let Err(tool_error) = &tool_response.tool_result { failure_indicators += 1; tracing::debug!("Tool execution failure detected: {:?}", tool_error); - } else if let Ok(contents) = &tool_response.tool_result { + } else if let Ok(result) = &tool_response.tool_result { // Check tool output for error indicators - if self.contains_error_indicators(contents) { + if self.contains_error_indicators(&result.content) { failure_indicators += 1; tracing::debug!("Tool output contains error indicators"); } diff --git a/crates/goose/src/providers/toolshim.rs b/crates/goose/src/providers/toolshim.rs index 1ec958c397..b4dc3cb11b 100644 --- a/crates/goose/src/providers/toolshim.rs +++ b/crates/goose/src/providers/toolshim.rs @@ -343,8 +343,9 @@ pub fn convert_tool_messages_to_text(messages: &[Message]) -> Conversation { has_tool_content = true; // Convert tool response to text format let text = match &res.tool_result { - Ok(contents) => { - let text_contents: Vec = contents + Ok(result) => { + let text_contents: Vec = result + .content .iter() .filter_map(|c| match c.deref() { RawContent::Text(t) => Some(t.text.clone()), diff --git a/crates/goose/tests/dynamic_task_tools_tests.rs b/crates/goose/tests/dynamic_task_tools_tests.rs index f195d8b166..7d0180cc38 100644 --- a/crates/goose/tests/dynamic_task_tools_tests.rs +++ b/crates/goose/tests/dynamic_task_tools_tests.rs @@ -118,10 +118,10 @@ mod tests { let tool_result = result.result.await; assert!(tool_result.is_ok()); let contents = tool_result.unwrap(); - assert!(!contents.is_empty()); + assert!(!contents.content.is_empty()); // Parse the returned JSON to verify task creation - if let Some(text_content) = contents.first().and_then(|c| c.as_text()) { + if let Some(text_content) = contents.content.first().and_then(|c| c.as_text()) { let task_payload: serde_json::Value = serde_json::from_str(&text_content.text).unwrap(); assert!(task_payload.get("task_ids").is_some()); let task_ids = task_payload.get("task_ids").unwrap().as_array().unwrap(); diff --git a/crates/goose/tests/mcp_integration_test.rs b/crates/goose/tests/mcp_integration_test.rs index df1b70d36f..57c0ef5944 100644 --- a/crates/goose/tests/mcp_integration_test.rs +++ b/crates/goose/tests/mcp_integration_test.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::{env, fs}; -use rmcp::model::{CallToolRequestParam, Content, Tool}; +use rmcp::model::{CallToolRequestParam, CallToolResult, Tool}; use rmcp::object; use tokio_util::sync::CancellationToken; @@ -108,7 +108,7 @@ fn build_and_get_binary_path() -> PathBuf { } }) .next() - .expect("failed to parase binary path") + .expect("failed to parse binary path") } static REPLAY_BINARY_PATH: Lazy = Lazy::new(build_and_get_binary_path); @@ -284,7 +284,7 @@ async fn test_replayed_session( serde_json::to_writer_pretty(File::create(results_path)?, &results)? } TestMode::Playback => assert_eq!( - serde_json::from_reader::<_, Vec>>(File::open(results_path)?)?, + serde_json::from_reader::<_, Vec>(File::open(results_path)?)?, results ), }; diff --git a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper index 214866a2bf..39b1f2561c 100644 --- a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper +++ b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper @@ -1,28 +1,26 @@ -STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} -STDERR: 2025-10-30T14:41:09.117156Z  INFO goose_mcp::mcp_server_runner: Starting MCP server -STDERR: at crates/goose-mcp/src/mcp_server_runner.rs:18 +STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} +STDERR: 2025-12-11T19:43:38.682870Z DEBUG goose_mcp::developer::analyze: Initializing CodeAnalyzer +STDERR: at crates/goose-mcp/src/developer/analyze/mod.rs:57 STDERR: -STDERR: 2025-10-30T14:41:09.120902Z  INFO goose_mcp::developer::analyze::cache: Initializing analysis cache with size 100 -STDERR: at crates/goose-mcp/src/developer/analyze/cache.rs:26 +STDERR: 2025-12-11T19:43:38.683063Z DEBUG goose_mcp::developer::analyze::parser: Initializing ParserManager +STDERR: at crates/goose-mcp/src/developer/analyze/parser.rs:19 STDERR: -STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"tools":{}},"serverInfo":{"name":"goose-developer","version":"1.11.0"},"instructions":" The developer extension gives you the capabilities to edit code files and run shell commands,\n and can be used to solve a wide range of problems.\n\nYou can use the shell tool to run any command that would work on the relevant operating system.\nUse the shell tool as needed to locate files or interact with the project.\n\nLeverage `analyze` through `return_last_only=true` subagents for deep codebase understanding with lean context\n- delegate analysis, retain summaries\n\nYour windows/screen tools can be used for visual debugging. You should not use these tools unless\nprompted to, but you can mention they are available if they are relevant.\n\nAlways prefer ripgrep (rg -C 3) to grep.\n\noperating system: macos\ncurrent directory: /Users/alexhancock/Development/goose/crates/goose\nshell: zsh\n\n \n\nAdditional Text Editor Tool Instructions:\n\nPerform text editing operations on files.\n\nThe `command` parameter specifies the operation to perform. Allowed options are:\n- `view`: View the content of a file.\n- `write`: Create or overwrite a file with the given content\n- `str_replace`: Replace text in one or more files.\n- `insert`: Insert text at a specific line location in the file.\n- `undo_edit`: Undo the last edit made to a file.\n\nTo use the write command, you must specify `file_text` which will become the new content of the file. Be careful with\nexisting files! This is a full overwrite, so you must include everything - not just sections you are modifying.\n\nTo use the str_replace command to edit multiple files, use the `diff` parameter with a unified diff.\nTo use the str_replace command to edit one file, you must specify both `old_str` and `new_str` - the `old_str` needs to exactly match one\nunique section of the original file, including any whitespace. Make sure to include enough context that the match is not\nambiguous. The entire original string will be replaced with `new_str`\n\nWhen possible, batch file edits together by using a multi-file unified `diff` within a single str_replace tool call.\n\nTo use the insert command, you must specify both `insert_line` (the line number after which to insert, 0 for beginning, -1 for end)\nand `new_str` (the text to insert).\n\n\n\nAdditional Shell Tool Instructions:\nExecute a command in the shell.\n\nThis will return the output and error concatenated into a single string, as\nyou would see from running on the command line. There will also be an indication\nof if the command succeeded or failed.\n\nAvoid commands that produce a large amount of output, and consider piping those outputs to files.\n\n**Important**: Each shell command runs in its own process. Things like directory changes or\nsourcing files do not persist between tool calls. So you may need to repeat them each time by\nstringing together commands.\nIf you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that\nthis tool does not run indefinitely.\n\n**Important**: Use ripgrep - `rg` - exclusively when you need to locate a file or a code reference,\nother solutions may produce too large output because of hidden files! For example *do not* use `find` or `ls -r`\n - List files by name: `rg --files | rg `\n - List files that contain a regex: `rg '' -l`\n\n - Multiple commands: Use && to chain commands, avoid newlines\n - Example: `cd example && ls` or `source env/bin/activate && pip install numpy`\n\n\n### Global Hints\nThe developer extension includes some global hints that apply to all projects & directories.\nThese are my global goose hints.\n\n### Project Hints\nThe developer extension includes some hints for working on the project in this directory.\n# AGENTS Instructions\n\ngoose is an AI agent framework in Rust with CLI and Electron desktop interfaces.\n\n## Setup\n```bash\nsource bin/activate-hermit\ncargo build\n```\n\n## Commands\n\n### Build\n```bash\ncargo build # debug\ncargo build --release # release \njust release-binary # release + openapi\n```\n\n### Test\n```bash\ncargo test # all tests\ncargo test -p goose # specific crate\ncargo test --package goose --test mcp_integration_test\njust record-mcp-tests # record MCP\n```\n\n### Lint/Format\n```bash\ncargo fmt\n./scripts/clippy-lint.sh\ncargo clippy --fix\n```\n\n### UI\n```bash\njust generate-openapi # after server changes\njust run-ui # start desktop\ncd ui/desktop && npm test # test UI\n```\n\n## Structure\n```\ncrates/\n├── goose # core logic\n├── goose-bench # benchmarking\n├── goose-cli # CLI entry\n├── goose-server # backend (binary: goosed)\n├── goose-mcp # MCP extensions\n├── goose-test # test utilities\n├── mcp-client # MCP client\n├── mcp-core # MCP shared\n└── mcp-server # MCP server\n\ntemporal-service/ # Go scheduler\nui/desktop/ # Electron app\n```\n\n## Development Loop\n```bash\n# 1. source bin/activate-hermit\n# 2. Make changes\n# 3. cargo fmt\n# 4. cargo build\n# 5. cargo test -p \n# 6. ./scripts/clippy-lint.sh\n# 7. [if server] just generate-openapi\n```\n\n## Rules\n\nTest: Prefer tests/ folder, e.g. crates/goose/tests/\nTest: When adding features, update goose-self-test.yaml, rebuild, then run `goose run --recipe goose-self-test.yaml` to validate\nError: Use anyhow::Result\nProvider: Implement Provider trait see providers/base.rs\nMCP: Extensions in crates/goose-mcp/\nServer: Changes need just generate-openapi\n\n## Never\n\nNever: Edit ui/desktop/openapi.json manually\nNever: Edit Cargo.toml use cargo add\nNever: Skip cargo fmt\nNever: Merge without ./scripts/clippy-lint.sh\nNever: Comment self-evident operations (`// Initialize`, `// Return result`), getters/setters, constructors, or standard Rust idioms\n\n## Entry Points\n- CLI: crates/goose-cli/src/main.rs\n- Server: crates/goose-server/src/main.rs\n- UI: ui/desktop/src/main.ts\n- Agent: crates/goose/src/agents/agent.rs\n\nThis is a rust project with crates in the crates dir:\ngoose: the main code for goose, contains all the core logic\ngoose-bench: bench marking\ngoose-cli: the command line interface, use goose crate\ngoose-mcp: the mcp servers that ship with goose. the developer sub system is of special interest\ngoose-server: the server that suports the desktop (electron) app. also known as goosed\n\n\nui/desktop has an electron app in typescript. \n\nnon trivial features should be implemented in the goose crate and then be called from the goose-cli crate for the cli. for the desktop, you want to add routes to \ngoose-server/src/routes. you can then run `just generate-openapi` to generate the openapi spec which will modify the ui/desktop/src/api files. once you have\nthat you can call the functionality from the server from the typescript.\n\ntips: \n- can look at unstaged changes for what is being worked on if starting\n- always check rust compiles, cargo fmt etc and `./scripts/clippy-lint.sh` (as well as run tests in files you are working on)\n- in ui/desktop, look at how you can run lint checks and if other tests can run\n"}} +STDERR: 2025-12-11T19:43:38.683096Z INFO goose_mcp::developer::analyze::cache: Initializing analysis cache with size 100 +STDERR: at crates/goose-mcp/src/developer/analyze/cache.rs:26 +STDERR: +STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"tools":{}},"serverInfo":{"name":"goose-developer","version":"1.16.0"},"instructions":" The developer extension gives you the capabilities to edit code files and run shell commands,\n and can be used to solve a wide range of problems.\n\nYou can use the shell tool to run any command that would work on the relevant operating system.\nUse the shell tool as needed to locate files or interact with the project.\n\nLeverage `analyze` through `return_last_only=true` subagents for deep codebase understanding with lean context\n- delegate analysis, retain summaries\n\nYour windows/screen tools can be used for visual debugging. You should not use these tools unless\nprompted to, but you can mention they are available if they are relevant.\n\nAlways prefer ripgrep (rg -C 3) to grep.\n\noperating system: macos\ncurrent directory: /Users/douwe/proj/goose/crates/goose\nshell: /bin/zsh\n\n \nAdditional Text Editor Tool Instructions:\n\nPerform text editing operations on files.\n\nThe `command` parameter specifies the operation to perform. Allowed options are:\n- `view`: View the content of a file.\n- `write`: Create or overwrite a file with the given content\n- `str_replace`: Replace text in one or more files.\n- `insert`: Insert text at a specific line location in the file.\n- `undo_edit`: Undo the last edit made to a file.\n\nTo use the write command, you must specify `file_text` which will become the new content of the file. Be careful with\nexisting files! This is a full overwrite, so you must include everything - not just sections you are modifying.\n\nTo use the str_replace command to edit multiple files, use the `diff` parameter with a unified diff.\nTo use the str_replace command to edit one file, you must specify both `old_str` and `new_str` - the `old_str` needs to exactly match one\nunique section of the original file, including any whitespace. Make sure to include enough context that the match is not\nambiguous. The entire original string will be replaced with `new_str`\n\nWhen possible, batch file edits together by using a multi-file unified `diff` within a single str_replace tool call.\n\nTo use the insert command, you must specify both `insert_line` (the line number after which to insert, 0 for beginning, -1 for end)\nand `new_str` (the text to insert).\n\n\n\nAdditional Shell Tool Instructions:\nExecute a command in the shell.\n\nThis will return the output and error concatenated into a single string, as\nyou would see from running on the command line. There will also be an indication\nof if the command succeeded or failed.\n\nAvoid commands that produce a large amount of output, and consider piping those outputs to files.\n\n**Important**: Each shell command runs in its own process. Things like directory changes or\nsourcing files do not persist between tool calls. So you may need to repeat them each time by\nstringing together commands.\n\nIf fetching web content, consider adding Accept: text/markdown header\nIf you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that\nthis tool does not run indefinitely.\n\n**Important**: Use ripgrep - `rg` - exclusively when you need to locate a file or a code reference,\nother solutions may produce too large output because of hidden files! For example *do not* use `find` or `ls -r`\n - List files by name: `rg --files | rg `\n - List files that contain a regex: `rg '' -l`\n\n - Multiple commands: Use && to chain commands, avoid newlines\n - Example: `cd example && ls` or `source env/bin/activate && pip install numpy`\n"}} STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"} -STDERR: 2025-10-30T14:41:09.126014Z  INFO rmcp::handler::server: client initialized -STDERR: at /Users/alexhancock/Development/goose/.hermit/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-0.8.1/src/handler/server.rs:218 -STDERR: STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"text_editor","arguments":{"command":"view","path":"/tmp/goose_test/goose.txt"}}} -STDERR: 2025-10-30T14:41:09.126196Z  INFO rmcp::service: Service initialized as server, peer_info: Some(InitializeRequestParam { protocol_version: ProtocolVersion("2025-03-26"), capabilities: ClientCapabilities { experimental: None, roots: None, sampling: Some({}), elicitation: None }, client_info: Implementation { name: "goose", title: None, version: "0.0.0", icons: None, website_url: None } }) -STDERR: at /Users/alexhancock/Development/goose/.hermit/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-0.8.1/src/service.rs:562 -STDERR: in rmcp::service::serve_inner -STDERR: STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"resource","resource":{"uri":"file:///tmp/goose_test/goose.txt","mimeType":"text","text":"# goose\n"},"annotations":{"audience":["assistant"]}},{"type":"text","text":"### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}} STDIN: {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"_meta":{"progressToken":1},"name":"text_editor","arguments":{"command":"str_replace","new_str":"# goose (modified by test)","old_str":"# goose","path":"/tmp/goose_test/goose.txt"}}} STDOUT: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n","annotations":{"audience":["assistant"]}},{"type":"text","text":"```\n# goose (modified by test)\n```\n","annotations":{"audience":["user"],"priority":0.2}}],"isError":false}} STDIN: {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"_meta":{"progressToken":2},"name":"shell","arguments":{"command":"cat /tmp/goose_test/goose.txt"}}} +STDERR: 2025-12-11T19:43:39.019022Z DEBUG goose_mcp::developer::rmcp_developer: Shell process spawned with PID: 78321 +STDERR: at crates/goose-mcp/src/developer/rmcp_developer.rs:997 +STDERR: STDOUT: {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","logger":"shell_tool","data":{"type":"shell_output","stream":"stdout","output":"# goose (modified by test)"}}} STDOUT: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"# goose (modified by test)\n","annotations":{"audience":["assistant"]}},{"type":"text","text":"# goose (modified by test)\n","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}} STDIN: {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"_meta":{"progressToken":3},"name":"text_editor","arguments":{"command":"str_replace","new_str":"# goose","old_str":"# goose (modified by test)","path":"/tmp/goose_test/goose.txt"}}} STDOUT: {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n","annotations":{"audience":["assistant"]}},{"type":"text","text":"```\n# goose\n```\n","annotations":{"audience":["user"],"priority":0.2}}],"isError":false}} STDIN: {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"_meta":{"progressToken":4},"name":"list_windows","arguments":{}}} -STDOUT: {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests","annotations":{"audience":["assistant"]}},{"type":"text","text":"Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}} -STDERR: 2025-10-30T14:41:09.200915Z  INFO rmcp::service: input stream terminated +STDOUT: {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"Available windows:\nMenubar","annotations":{"audience":["assistant"]}},{"type":"text","text":"Available windows:\nMenubar","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}} diff --git a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json index e3d4e6a355..169b8d04e3 100644 --- a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json +++ b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json @@ -1,111 +1,126 @@ [ - [ - { - "type": "resource", - "resource": { - "uri": "file:///tmp/goose_test/goose.txt", - "mimeType": "text", - "text": "# goose\n" + { + "content": [ + { + "type": "resource", + "resource": { + "uri": "file:///tmp/goose_test/goose.txt", + "mimeType": "text", + "text": "# goose\n" + }, + "annotations": { + "audience": [ + "assistant" + ] + } }, - "annotations": { - "audience": [ - "assistant" - ] + { + "type": "text", + "text": "### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n", + "annotations": { + "audience": [ + "user" + ], + "priority": 0.0 + } } - }, - { - "type": "text", - "text": "### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n", - "annotations": { - "audience": [ - "user" - ], - "priority": 0.0 + ], + "isError": false + }, + { + "content": [ + { + "type": "text", + "text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n", + "annotations": { + "audience": [ + "assistant" + ] + } + }, + { + "type": "text", + "text": "```\n# goose (modified by test)\n```\n", + "annotations": { + "audience": [ + "user" + ], + "priority": 0.2 + } } - } - ], - [ - { - "type": "text", - "text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n", - "annotations": { - "audience": [ - "assistant" - ] + ], + "isError": false + }, + { + "content": [ + { + "type": "text", + "text": "# goose (modified by test)\n", + "annotations": { + "audience": [ + "assistant" + ] + } + }, + { + "type": "text", + "text": "# goose (modified by test)\n", + "annotations": { + "audience": [ + "user" + ], + "priority": 0.0 + } } - }, - { - "type": "text", - "text": "```\n# goose (modified by test)\n```\n", - "annotations": { - "audience": [ - "user" - ], - "priority": 0.2 + ], + "isError": false + }, + { + "content": [ + { + "type": "text", + "text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n", + "annotations": { + "audience": [ + "assistant" + ] + } + }, + { + "type": "text", + "text": "```\n# goose\n```\n", + "annotations": { + "audience": [ + "user" + ], + "priority": 0.2 + } } - } - ], - [ - { - "type": "text", - "text": "# goose (modified by test)\n", - "annotations": { - "audience": [ - "assistant" - ] + ], + "isError": false + }, + { + "content": [ + { + "type": "text", + "text": "Available windows:\nMenubar", + "annotations": { + "audience": [ + "assistant" + ] + } + }, + { + "type": "text", + "text": "Available windows:\nMenubar", + "annotations": { + "audience": [ + "user" + ], + "priority": 0.0 + } } - }, - { - "type": "text", - "text": "# goose (modified by test)\n", - "annotations": { - "audience": [ - "user" - ], - "priority": 0.0 - } - } - ], - [ - { - "type": "text", - "text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n", - "annotations": { - "audience": [ - "assistant" - ] - } - }, - { - "type": "text", - "text": "```\n# goose\n```\n", - "annotations": { - "audience": [ - "user" - ], - "priority": 0.2 - } - } - ], - [ - { - "type": "text", - "text": "Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests", - "annotations": { - "audience": [ - "assistant" - ] - } - }, - { - "type": "text", - "text": "Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests", - "annotations": { - "audience": [ - "user" - ], - "priority": 0.0 - } - } - ] + ], + "isError": false + } ] \ No newline at end of file diff --git a/crates/goose/tests/mcp_replays/github-mcp-serverstdio b/crates/goose/tests/mcp_replays/github-mcp-serverstdio index 0c5cfbba90..0acc0f2737 100644 --- a/crates/goose/tests/mcp_replays/github-mcp-serverstdio +++ b/crates/goose/tests/mcp_replays/github-mcp-serverstdio @@ -1,6 +1,12 @@ -STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} +STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} +STDERR: time=2025-12-11T17:58:47.636-05:00 level=INFO msg="starting server" version=0.24.1 host="" dynamicToolsets=false readOnly=false lockdownEnabled=false STDERR: GitHub MCP Server running on stdio -STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"logging":{},"prompts":{},"resources":{"subscribe":true,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"github-mcp-server","version":"version"}}} +STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server run start" +STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server connecting" +STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server session connected" session_id="" +STDOUT: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"completions":{},"logging":{},"prompts":{"listChanged":true},"resources":{"listChanged":true},"tools":{"listChanged":true}},"instructions":"The GitHub MCP Server provides tools to interact with GitHub platform.\n\nTool selection guidance:\n\t1. Use 'list_*' tools for broad, simple retrieval and pagination of all items of a type (e.g., all issues, all PRs, all branches) with basic filtering.\n\t2. Use 'search_*' tools for targeted queries with specific criteria, keywords, or complex filters (e.g., issues with certain text, PRs by author, code containing functions).\n\nContext management:\n\t1. Use pagination whenever possible with batches of 5-10 items.\n\t2. Use minimal_output parameter set to true if the full information is not needed to accomplish a task.\n\nTool usage guidance:\n\t1. For 'search_*' tools: Use separate 'sort' and 'order' parameters if available for sorting results - do not include 'sort:' syntax in query strings. Query strings should contain only search criteria (e.g., 'org:google language:python'), not sorting instructions. Always call 'get_me' first to understand current user permissions and context. ## Issues\n\nCheck 'list_issue_types' first for organizations to use proper issue types. Use 'search_issues' before creating new issues to avoid duplicates. Always set 'state_reason' when closing issues. ## Pull Requests\n\nPR review workflow: Always use 'pull_request_review_write' with method 'create' to create a pending review, then 'add_comment_to_pending_review' to add comments, and finally 'pull_request_review_write' with method 'submit_pending' to submit the review for complex reviews with line-specific comments.\n\nBefore creating a pull request, search for pull request templates in the repository. Template files are called pull_request_template.md or they're located in '.github/PULL_REQUEST_TEMPLATE' directory. Use the template content to structure the PR description and then call create_pull_request tool.","protocolVersion":"2025-03-26","serverInfo":{"name":"github-mcp-server","title":"GitHub MCP Server","version":"0.24.1"}}} STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"} +STDERR: time=2025-12-11T17:58:47.642-05:00 level=INFO msg="session initialized" STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"get_file_contents","arguments":{"owner":"block","path":"README.md","repo":"goose","sha":"ab62b863c1666232a67048b6c4e10007a2a5b83c"}}} -STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"successfully downloaded text file"},{"type":"resource","resource":{"uri":"repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md","mimeType":"text/plain; charset=utf-8","text":"\u003cdiv align=\"center\"\u003e\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n\u003cp align=\"center\"\u003e\n \u003ca href=\"https://opensource.org/licenses/Apache-2.0\"\u003e\n \u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://discord.gg/7GaTvbDwga\"\u003e\n \u003cimg src=\"https://img.shields.io/discord/1287729918100246654?logo=discord\u0026logoColor=white\u0026label=Join+Us\u0026color=blueviolet\" alt=\"Discord\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://github.com/block/goose/actions/workflows/ci.yml\"\u003e\n \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/block/goose/ci.yml?branch=main\" alt=\"CI\"\u003e\n \u003c/a\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# Goose Around with Us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@blockopensource)\n- [LinkedIn](https://www.linkedin.com/company/block-opensource)\n- [Twitter/X](https://x.com/blockopensource)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"}}]}} +STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"successfully downloaded text file (SHA: de9bdde7f260549bf3a083651842f30ab29cf4e9)"},{"type":"resource","resource":{"uri":"repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md","mimeType":"text/plain; charset=utf-8","text":"\u003cdiv align=\"center\"\u003e\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n\u003cp align=\"center\"\u003e\n \u003ca href=\"https://opensource.org/licenses/Apache-2.0\"\u003e\n \u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://discord.gg/7GaTvbDwga\"\u003e\n \u003cimg src=\"https://img.shields.io/discord/1287729918100246654?logo=discord\u0026logoColor=white\u0026label=Join+Us\u0026color=blueviolet\" alt=\"Discord\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://github.com/block/goose/actions/workflows/ci.yml\"\u003e\n \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/block/goose/ci.yml?branch=main\" alt=\"CI\"\u003e\n \u003c/a\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n[![Watch the video](https://github.com/user-attachments/assets/ddc71240-3928-41b5-8210-626dfb28af7a)](https://youtu.be/D-DpDunrbpo)\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# a little goose humor 🦢\n\n\u003e Why did the developer choose goose as their AI agent?\n\u003e \n\u003e Because it always helps them \"migrate\" their code to production! 🚀\n\n# goose around with us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@goose-oss)\n- [LinkedIn](https://www.linkedin.com/company/goose-oss)\n- [Twitter/X](https://x.com/goose_oss)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"}}]}} +STDERR: time=2025-12-11T17:58:48.133-05:00 level=INFO msg="server session disconnected" session_id="" diff --git a/crates/goose/tests/mcp_replays/github-mcp-serverstdio.results.json b/crates/goose/tests/mcp_replays/github-mcp-serverstdio.results.json index 99a4448993..bede7cc955 100644 --- a/crates/goose/tests/mcp_replays/github-mcp-serverstdio.results.json +++ b/crates/goose/tests/mcp_replays/github-mcp-serverstdio.results.json @@ -1,16 +1,18 @@ [ - [ - { - "type": "text", - "text": "successfully downloaded text file" - }, - { - "type": "resource", - "resource": { - "uri": "repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md", - "mimeType": "text/plain; charset=utf-8", - "text": "
\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n

\n \n \n \n \n \"Discord\"\n \n \n \"CI\"\n \n

\n
\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# Goose Around with Us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@blockopensource)\n- [LinkedIn](https://www.linkedin.com/company/block-opensource)\n- [Twitter/X](https://x.com/blockopensource)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n" + { + "content": [ + { + "type": "text", + "text": "successfully downloaded text file (SHA: de9bdde7f260549bf3a083651842f30ab29cf4e9)" + }, + { + "type": "resource", + "resource": { + "uri": "repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md", + "mimeType": "text/plain; charset=utf-8", + "text": "
\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n

\n \n \n \n \n \"Discord\"\n \n \n \"CI\"\n \n

\n
\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n[![Watch the video](https://github.com/user-attachments/assets/ddc71240-3928-41b5-8210-626dfb28af7a)](https://youtu.be/D-DpDunrbpo)\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# a little goose humor 🦢\n\n> Why did the developer choose goose as their AI agent?\n> \n> Because it always helps them \"migrate\" their code to production! 🚀\n\n# goose around with us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@goose-oss)\n- [LinkedIn](https://www.linkedin.com/company/goose-oss)\n- [Twitter/X](https://x.com/goose_oss)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n" + } } - } - ] -] + ] + } +] \ No newline at end of file diff --git a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything index 819b3f2626..811a47d3a8 100644 --- a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything +++ b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything @@ -1,10 +1,10 @@ -STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} +STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} STDERR: Starting default (STDIO) server... STDERR: Starting logs update interval STDOUT: {"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"resources":{"subscribe":true},"tools":{},"logging":{},"completions":{}},"serverInfo":{"name":"example-servers/everything","title":"Everything Example Server","version":"1.0.0"},"instructions":"Testing and demonstration server for MCP protocol features.\n\n## Resources\n\nResources 1-100 follow pattern: even IDs contain text, odd IDs contain binary data. Resources are paginated at 10 items per page with cursor-based navigation.\n\n## Key dependencies\n\nProgress notifications require `_meta.progressToken` in tool calls. Resource subscriptions generate updates every 10 seconds.\n\n## Performance characteristics\n\nServer generates automatic log messages every 20 seconds (filtered by current log level), stderr notifications every 30 seconds, and resource update notifications every 10 seconds for active subscriptions. Log level changes affect message filtering in real-time.\n\n## Multi-modal testing\n\n`complex_prompt` includes both text arguments and image content for testing client multi-modal handling. `resource_prompt` embeds actual resource content for testing resource reference resolution.\n\nArgument completion is available for prompt parameters and resource IDs. Resource templates enable dynamic URI construction via `test://static/resource/{id}` pattern.\n\n## Easter egg\n\nIf asked about server instructions, respond with \"🎉 Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action.\"\n"},"jsonrpc":"2.0","id":0} STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"} -STDOUT: {"method":"notifications/message","params":{"level":"info","logger":"everything-server","data":"Client does not support MCP roots protocol"},"jsonrpc":"2.0"} STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"echo","arguments":{"message":"Hello, world!"}}} +STDOUT: {"method":"notifications/message","params":{"level":"info","logger":"everything-server","data":"Client does not support MCP roots protocol"},"jsonrpc":"2.0"} STDOUT: {"result":{"content":[{"type":"text","text":"Echo: Hello, world!"}]},"jsonrpc":"2.0","id":1} STDIN: {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"_meta":{"progressToken":1},"name":"add","arguments":{"a":1,"b":2}}} STDOUT: {"result":{"content":[{"type":"text","text":"The sum of 1 and 2 is 3."}]},"jsonrpc":"2.0","id":2} @@ -21,5 +21,5 @@ STDIN: {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"_meta":{"progres STDOUT: {"method":"sampling/createMessage","params":{"messages":[{"role":"user","content":{"type":"text","text":"Resource sampleLLM context: Please provide a quote from The Great Gatsby"}}],"systemPrompt":"You are a helpful test server.","maxTokens":100,"temperature":0.7,"includeContext":"thisServer"},"jsonrpc":"2.0","id":0} STDIN: {"jsonrpc":"2.0","id":0,"result":{"model":"mock","stopReason":"endTurn","role":"assistant","content":{"type":"text","text":"\"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)"}}} STDOUT: {"result":{"content":[{"type":"text","text":"LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)"}]},"jsonrpc":"2.0","id":5} -STDOUT: {"method":"notifications/message","params":{"level":"error","data":"Error-level message"},"jsonrpc":"2.0"} -STDERR: node:events:486 +STDOUT: {"method":"notifications/message","params":{"level":"critical","data":"Critical-level message"},"jsonrpc":"2.0"} +STDERR: node:events:485 diff --git a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json index 2b7f47b8d8..2730d607b5 100644 --- a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json +++ b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json @@ -1,32 +1,47 @@ [ - [ - { - "type": "text", - "text": "Echo: Hello, world!" + { + "content": [ + { + "type": "text", + "text": "Echo: Hello, world!" + } + ] + }, + { + "content": [ + { + "type": "text", + "text": "The sum of 1 and 2 is 3." + } + ] + }, + { + "content": [ + { + "type": "text", + "text": "Long running operation completed. Duration: 1 seconds, Steps: 5." + } + ] + }, + { + "content": [ + { + "type": "text", + "text": "{\"temperature\":22.5,\"conditions\":\"Partly cloudy\",\"humidity\":65}" + } + ], + "structuredContent": { + "conditions": "Partly cloudy", + "humidity": 65, + "temperature": 22.5 } - ], - [ - { - "type": "text", - "text": "The sum of 1 and 2 is 3." - } - ], - [ - { - "type": "text", - "text": "Long running operation completed. Duration: 1 seconds, Steps: 5." - } - ], - [ - { - "type": "text", - "text": "{\"temperature\":22.5,\"conditions\":\"Partly cloudy\",\"humidity\":65}" - } - ], - [ - { - "type": "text", - "text": "LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)" - } - ] + }, + { + "content": [ + { + "type": "text", + "text": "LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)" + } + ] + } ] \ No newline at end of file diff --git a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch index 58ec42bbaa..913f5603f8 100644 --- a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch +++ b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch @@ -1,5 +1,5 @@ -STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} -STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-fetch","version":"1.19.0"}}} +STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} +STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-fetch","version":"1.23.3"}}} STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"} STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"fetch","arguments":{"url":"https://example.com"}}} -STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Failed to fetch robots.txt https://example.com/robots.txt due to a connection issue"}],"isError":true}} +STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Contents of https://example.com/:\nThis domain is for use in documentation examples without needing permission. Avoid use in operations.\n\n[Learn more](https://iana.org/domains/example)"}],"isError":false}} diff --git a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch.results.json b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch.results.json index cec82b5da5..098fb77251 100644 --- a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch.results.json +++ b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch.results.json @@ -1,8 +1,11 @@ [ - [ - { - "type": "text", - "text": "Failed to fetch robots.txt https://example.com/robots.txt due to a connection issue" - } - ] + { + "content": [ + { + "type": "text", + "text": "Contents of https://example.com/:\nThis domain is for use in documentation examples without needing permission. Avoid use in operations.\n\n[Learn more](https://iana.org/domains/example)" + } + ], + "isError": false + } ] \ No newline at end of file diff --git a/crates/goose/tests/providers.rs b/crates/goose/tests/providers.rs index 7863943da5..2de492ad99 100644 --- a/crates/goose/tests/providers.rs +++ b/crates/goose/tests/providers.rs @@ -166,8 +166,9 @@ impl ProviderTester { let weather = Message::user().with_tool_response( id, - Ok(vec![Content::text( - " + Ok(rmcp::model::CallToolResult { + content: vec![Content::text( + " 50°F°C Precipitation: 0% Humidity: 84% @@ -175,7 +176,11 @@ impl ProviderTester { Weather Saturday 9:00 PM Clear", - )]), + )], + structured_content: None, + is_error: Some(false), + meta: None, + }), ); let (response2, _) = self @@ -318,10 +323,15 @@ impl ProviderTester { ); let tool_response = Message::user().with_tool_response( "test_id", - Ok(vec![Content::image( - image_content.data.clone(), - image_content.mime_type.clone(), - )]), + Ok(rmcp::model::CallToolResult { + content: vec![Content::image( + image_content.data.clone(), + image_content.mime_type.clone(), + )], + structured_content: None, + is_error: Some(false), + meta: None, + }), ); let result2 = self diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 3de24b346f..03c3c2553d 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -86,6 +86,90 @@ } } }, + "/agent/call_tool": { + "post": { + "tags": [ + "super::routes::agent" + ], + "operationId": "call_tool", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CallToolRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Resource read successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CallToolResponse" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid secret key" + }, + "404": { + "description": "Resource not found" + }, + "424": { + "description": "Agent not initialized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/agent/read_resource": { + "post": { + "tags": [ + "super::routes::agent" + ], + "operationId": "read_resource", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReadResourceRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Resource read successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReadResourceResponse" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid secret key" + }, + "404": { + "description": "Resource not found" + }, + "424": { + "description": "Agent not initialized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, "/agent/remove_extension": { "post": { "tags": [ @@ -2537,6 +2621,44 @@ } } }, + "CallToolRequest": { + "type": "object", + "required": [ + "session_id", + "name", + "arguments" + ], + "properties": { + "arguments": {}, + "name": { + "type": "string" + }, + "session_id": { + "type": "string" + } + } + }, + "CallToolResponse": { + "type": "object", + "required": [ + "content", + "is_error" + ], + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Content" + } + }, + "is_error": { + "type": "boolean" + }, + "structured_content": { + "nullable": true + } + } + }, "ChatRequest": { "type": "object", "required": [ @@ -4286,6 +4408,36 @@ } } }, + "ReadResourceRequest": { + "type": "object", + "required": [ + "session_id", + "extension_name", + "uri" + ], + "properties": { + "extension_name": { + "type": "string" + }, + "session_id": { + "type": "string" + }, + "uri": { + "type": "string" + } + } + }, + "ReadResourceResponse": { + "type": "object", + "required": [ + "html" + ], + "properties": { + "html": { + "type": "string" + } + } + }, "Recipe": { "type": "object", "required": [ diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index 8eea0f9ec7..d7394952c9 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; +import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; export type Options = Options2 & { /** @@ -36,6 +36,24 @@ export const agentAddExtension = (options: } }); +export const callTool = (options: Options) => (options.client ?? client).post({ + url: '/agent/call_tool', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +export const readResource = (options: Options) => (options.client ?? client).post({ + url: '/agent/read_resource', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + export const agentRemoveExtension = (options: Options) => (options.client ?? client).post({ url: '/agent/remove_extension', ...options, diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index b064063699..81b9987e32 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -46,6 +46,18 @@ export type AuthorRequest = { metadata?: string | null; }; +export type CallToolRequest = { + arguments: unknown; + name: string; + session_id: string; +}; + +export type CallToolResponse = { + content: Array; + is_error: boolean; + structured_content?: unknown; +}; + export type ChatRequest = { messages: Array; recipe_name?: string | null; @@ -603,6 +615,16 @@ export type RawTextContent = { text: string; }; +export type ReadResourceRequest = { + extension_name: string; + session_id: string; + uri: string; +}; + +export type ReadResourceResponse = { + html: string; +}; + export type Recipe = { activities?: Array | null; author?: Author | null; @@ -1078,6 +1100,76 @@ export type AgentAddExtensionResponses = { export type AgentAddExtensionResponse = AgentAddExtensionResponses[keyof AgentAddExtensionResponses]; +export type CallToolData = { + body: CallToolRequest; + path?: never; + query?: never; + url: '/agent/call_tool'; +}; + +export type CallToolErrors = { + /** + * Unauthorized - invalid secret key + */ + 401: unknown; + /** + * Resource not found + */ + 404: unknown; + /** + * Agent not initialized + */ + 424: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type CallToolResponses = { + /** + * Resource read successfully + */ + 200: CallToolResponse; +}; + +export type CallToolResponse2 = CallToolResponses[keyof CallToolResponses]; + +export type ReadResourceData = { + body: ReadResourceRequest; + path?: never; + query?: never; + url: '/agent/read_resource'; +}; + +export type ReadResourceErrors = { + /** + * Unauthorized - invalid secret key + */ + 401: unknown; + /** + * Resource not found + */ + 404: unknown; + /** + * Agent not initialized + */ + 424: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type ReadResourceResponses = { + /** + * Resource read successfully + */ + 200: ReadResourceResponse; +}; + +export type ReadResourceResponse2 = ReadResourceResponses[keyof ReadResourceResponses]; + export type AgentRemoveExtensionData = { body: RemoveExtensionRequest; path?: never;