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 98fa73b2c5e0..a9178f43f686 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 8c9684a037c8..89242bc6560b 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 1a128963eb45..68ce092dce49 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,8 +323,56 @@ "model": "claude-sonnet-4-20250514", "usage": { "input_tokens": 2406, - "output_tokens": 29, - "total_tokens": 2435 + "output_tokens": 55, + "total_tokens": 2461 + } + } + } + }, + "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": { + "input": { + "system": "Reply with only a description in four words or less", + "messages": [ + { + "id": null, + "role": "user", + "created": 1765495263, + "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": 1765495265, + "content": [ + { + "type": "text", + "text": "Berlin weather inquiry" + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + }, + "usage": { + "model": "claude-sonnet-4-20250514", + "usage": { + "input_tokens": 84, + "output_tokens": 6, + "total_tokens": 90 } } } @@ -332,9 +382,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_8_0", + "id": "msg_20251211_27_0", "role": "user", - "created": 1763989805, + "created": 1765495263, "content": [ { "type": "text", @@ -585,15 +635,15 @@ "message": { "id": null, "role": "assistant", - "created": 1763989808, + "created": 1765495266, "content": [ { "type": "text", - "text": "I'll get the current weather information for Berlin, Germany." + "text": "I'll get the current weather information for Berlin, Germany for you." }, { "type": "toolRequest", - "id": "toolu_01VxSzmLcPK7UNNE4ms8XRa6", + "id": "toolu_01CDkt9pjh8oC9C1984uhvuG", "toolCall": { "status": "success", "value": { @@ -614,56 +664,8 @@ "model": "claude-sonnet-4-20250514", "usage": { "input_tokens": 2320, - "output_tokens": 72, - "total_tokens": 2392 - } - } - } - }, - "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": { - "input": { - "system": "Reply with only a description in four words or less", - "messages": [ - { - "id": null, - "role": "user", - "created": 1763989805, - "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": 1763989810, - "content": [ - { - "type": "text", - "text": "Berlin weather inquiry" - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - }, - "usage": { - "model": "claude-sonnet-4-20250514", - "usage": { - "input_tokens": 84, - "output_tokens": 6, - "total_tokens": 90 + "output_tokens": 74, + "total_tokens": 2394 } } } 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 fffc7b746b63..105749bcdfa2 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 7a6542184457..d32272fdac1a 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,12 +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, + "created": 1765495334, "content": [ { "type": "text", @@ -17,6 +17,56 @@ "userVisible": true, "agentVisible": true } + }, + { + "id": "msg_6147fd68-5a38-4528-9924-d4dbaf5e7f44", + "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 + } + }, + { + "id": "msg_0fac763c-62c3-4a33-a15b-27d605c5b81d", + "role": "user", + "created": 1765495335, + "content": [ + { + "type": "toolResponse", + "id": "zemx6n94z", + "toolResult": { + "status": "success", + "value": { + "content": [ + { + "type": "text", + "text": "The weather in Berlin, Germany is cloudy and 18°C" + } + ] + } + } + } + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } } ], "tools": [ @@ -257,20 +307,11 @@ "message": { "id": null, "role": "assistant", - "created": 1763989814, + "created": 1765495335, "content": [ { - "type": "toolRequest", - "id": "4kpg6v08d", - "toolCall": { - "status": "success", - "value": { - "name": "weather_extension__get_weather", - "arguments": { - "location": "Berlin, Germany" - } - } - } + "type": "text", + "text": "The current weather in Berlin, Germany is cloudy with a temperature of 18°C." } ], "metadata": { @@ -281,73 +322,73 @@ "usage": { "model": "llama-3.3-70b-versatile", "usage": { - "input_tokens": 2258, - "output_tokens": 20, - "total_tokens": 2278 + "input_tokens": 2299, + "output_tokens": 18, + "total_tokens": 2317 } } } }, - "165258e98e56cdcccdc49bd70ecd2b081b41d35ddb3559ed3bc7b1e420290bed": { + "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": { "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.", + "system": "Reply with only a description in four words or less", "messages": [ { - "id": "msg_20251124_10_0", + "id": null, "role": "user", - "created": 1763989814, + "created": 1765495334, "content": [ { "type": "text", - "text": "tell me what the weather is in Berlin, Germany" + "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 } - }, - { - "id": "msg_9d25f6e9-6d02-4f41-8710-325795e87fbc", - "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 + } + ], + "tools": [] + }, + "output": { + "message": { + "id": null, + "role": "assistant", + "created": 1765495334, + "content": [ + { + "type": "text", + "text": "Berlin weather" } - }, + ], + "metadata": { + "userVisible": true, + "agentVisible": true + } + }, + "usage": { + "model": "llama-3.3-70b-versatile", + "usage": { + "input_tokens": 108, + "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_513af900-f4fe-4ed0-aed8-7ffb4c8c384d", + "id": "msg_20251211_40_0", "role": "user", - "created": 1763989814, + "created": 1765495334, "content": [ { - "type": "toolResponse", - "id": "4kpg6v08d", - "toolResult": { - "status": "success", - "value": [ - { - "type": "text", - "text": "The weather in Berlin, Germany is cloudy and 18°C" - } - ] - } + "type": "text", + "text": "tell me what the weather is in Berlin, Germany" } ], "metadata": { @@ -594,59 +635,20 @@ "message": { "id": null, "role": "assistant", - "created": 1763989815, + "created": 1765495335, "content": [ { - "type": "text", - "text": "The current weather in Berlin, Germany is cloudy with a temperature of 18°C." - } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - }, - "usage": { - "model": "llama-3.3-70b-versatile", - "usage": { - "input_tokens": 2299, - "output_tokens": 18, - "total_tokens": 2317 - } - } - } - }, - "8b1b8633232ca390cb3ff37a48daf74168b58f13e2943efcb17957129ee34d1a": { - "input": { - "system": "Reply with only a description in four words or less", - "messages": [ - { - "id": null, - "role": "user", - "created": 1763989814, - "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" + "type": "toolRequest", + "id": "zemx6n94z", + "toolCall": { + "status": "success", + "value": { + "name": "weather_extension__get_weather", + "arguments": { + "location": "Berlin, Germany" + } + } } - ], - "metadata": { - "userVisible": true, - "agentVisible": true - } - } - ], - "tools": [] - }, - "output": { - "message": { - "id": null, - "role": "assistant", - "created": 1763989814, - "content": [ - { - "type": "text", - "text": "Berlin weather query" } ], "metadata": { @@ -657,9 +659,9 @@ "usage": { "model": "llama-3.3-70b-versatile", "usage": { - "input_tokens": 108, - "output_tokens": 4, - "total_tokens": 112 + "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 195be6d27028..10dbf8c397b7 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 61e537d60cfc..724691360637 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 642f78e7a258..a5ef29831166 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 2ff1f1da62ed..0468bdf1e2ae 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 e6f8e57474d4..8911fbd94b06 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 c265482d055e..fb6ab9b8be21 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 517bebd92deb..f70e0a0073fb 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 ef4d5f69197b..edfe875bc7e7 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 4384e9841e36..2af90d66cbb4 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 439cde51579f..0adea4db4aa5 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 f45dc01abcbd..e1554ed4e208 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 d920f3b7dc74..97f273d52412 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 d0dd14a6e974..66729d04dd3e 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 c8603a009025..6e7d8820bb04 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 b70f71d3f205..2fa463a4da4a 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 2d6f7f8167a9..cd0834a12ab8 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 f7fdfa914a2f..144d3d11ff1d 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 363a5880e735..c757a173aefc 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 0f2902bf9e15..13138a7fb732 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 da682a9d77ef..4e312a48fafd 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 19193f4a6c42..20ea3588898c 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 14d9e5c281c3..524fbce39d75 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 4dee06ff8a0c..fded07081c3f 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 7ed9963377a6..6c148aaa1fa7 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 c6cdd0ab0f7c..ffe7c1058de6 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 da573678b39e..3771b3adadca 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 f343059df51d..d81d7b8b5be0 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 4083fbef7e68..9b980c3ae2b9 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 a0b4a16cf3d1..1e9a85dd9ec4 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 fab1b8d5ed6e..04fd962a1b0a 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 19f3012f102c..bfe69e34cc46 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 1dc8fe7a8b7c..e0786b418614 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 1ec958c39702..b4dc3cb11bbd 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 f195d8b16662..7d0180cc3867 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 df1b70d36fa5..57c0ef59441f 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 214866a2bf54..39b1f2561c32 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"}} -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: 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"} 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 e3d4e6a35541..169b8d04e341 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 - } - } - ], - [ - { - "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": "# 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": "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" - ] + ], + "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": "```\n# goose\n```\n", - "annotations": { - "audience": [ - "user" - ], - "priority": 0.2 + ], + "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": "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" - ] + ], + "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": "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 + }, + { + "content": [ + { + "type": "text", + "text": "Available windows:\nMenubar", + "annotations": { + "audience": [ + "assistant" + ] + } + }, + { + "type": "text", + "text": "Available windows:\nMenubar", + "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 0c5cfbba908d..0acc0f27379a 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 99a444899389..bede7cc9553b 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 819b3f262621..811a47d3a862 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 2b7f47b8d8bf..2730d607b5a4 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 58ec42bbaad6..913f5603f807 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 cec82b5da583..098fb7725115 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 7863943da5d0..2de492ad9990 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 3de24b346fd0..03c3c2553db3 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 8eea0f9ec742..d7394952c99a 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 b0640636991e..81b9987e32d7 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;