diff --git a/crates/goose-cli/src/session/export.rs b/crates/goose-cli/src/session/export.rs index 724691360637..df72c3f8965f 100644 --- a/crates/goose-cli/src/session/export.rs +++ b/crates/goose-cli/src/session/export.rs @@ -536,7 +536,7 @@ mod tests { let tool_request = ToolRequest { id: "test-id".to_string(), tool_call: Ok(tool_call), - thought_signature: None, + metadata: None, }; let result = tool_request_to_markdown(&tool_request, true); @@ -560,7 +560,7 @@ mod tests { let tool_request = ToolRequest { id: "test-id".to_string(), tool_call: Ok(tool_call), - thought_signature: None, + metadata: None, }; let result = tool_request_to_markdown(&tool_request, true); @@ -580,6 +580,7 @@ mod tests { annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "test-id".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -605,6 +606,7 @@ mod tests { annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "test-id".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -698,7 +700,7 @@ mod tests { let tool_request = ToolRequest { id: "shell-cat".to_string(), tool_call: Ok(tool_call), - thought_signature: None, + metadata: None, }; let python_code = r#"#!/usr/bin/env python3 @@ -716,6 +718,7 @@ if __name__ == "__main__": annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "shell-cat".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -750,7 +753,7 @@ if __name__ == "__main__": let tool_request = ToolRequest { id: "git-status".to_string(), tool_call: Ok(git_status_call), - thought_signature: None, + metadata: None, }; let git_output = " M src/main.rs\n?? temp.txt\n A new_feature.rs"; @@ -762,6 +765,7 @@ if __name__ == "__main__": annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "git-status".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -794,7 +798,7 @@ if __name__ == "__main__": let _tool_request = ToolRequest { id: "cargo-build".to_string(), tool_call: Ok(cargo_build_call), - thought_signature: None, + metadata: None, }; let build_output = r#" Compiling goose-cli v0.1.0 (/Users/user/goose) @@ -816,6 +820,7 @@ warning: unused variable `x` annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "cargo-build".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -844,7 +849,7 @@ warning: unused variable `x` let _tool_request = ToolRequest { id: "curl-api".to_string(), tool_call: Ok(curl_call), - thought_signature: None, + metadata: None, }; let api_response = r#"{ @@ -868,6 +873,7 @@ warning: unused variable `x` annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "curl-api".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -898,7 +904,7 @@ warning: unused variable `x` let tool_request = ToolRequest { id: "editor-write".to_string(), tool_call: Ok(editor_call), - thought_signature: None, + metadata: None, }; let text_content = TextContent { @@ -909,6 +915,7 @@ warning: unused variable `x` annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "editor-write".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -944,7 +951,7 @@ warning: unused variable `x` let _tool_request = ToolRequest { id: "editor-view".to_string(), tool_call: Ok(editor_call), - thought_signature: None, + metadata: None, }; let python_code = r#"import os @@ -971,6 +978,7 @@ def process_data(data: List[Dict]) -> List[Dict]: annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "editor-view".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -999,7 +1007,7 @@ def process_data(data: List[Dict]) -> List[Dict]: let _tool_request = ToolRequest { id: "shell-error".to_string(), tool_call: Ok(error_call), - thought_signature: None, + metadata: None, }; let error_output = r#"python: can't open file 'nonexistent_script.py': [Errno 2] No such file or directory @@ -1013,6 +1021,7 @@ Command failed with exit code 2"#; annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "shell-error".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -1040,7 +1049,7 @@ Command failed with exit code 2"#; let tool_request = ToolRequest { id: "script-exec".to_string(), tool_call: Ok(script_call), - thought_signature: None, + metadata: None, }; let script_output = r#"Python 3.11.5 (main, Aug 24 2023, 15:18:16) [Clang 14.0.3 ] @@ -1058,6 +1067,7 @@ Command failed with exit code 2"#; annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "script-exec".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -1092,7 +1102,7 @@ Command failed with exit code 2"#; let _tool_request = ToolRequest { id: "multi-cmd".to_string(), tool_call: Ok(multi_call), - thought_signature: None, + metadata: None, }; let multi_output = r#"total 24 @@ -1110,6 +1120,7 @@ drwx------ 3 user staff 96 Dec 6 16:20 com.apple.launchd.abc annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "multi-cmd".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -1142,7 +1153,7 @@ drwx------ 3 user staff 96 Dec 6 16:20 com.apple.launchd.abc let tool_request = ToolRequest { id: "grep-search".to_string(), tool_call: Ok(grep_call), - thought_signature: None, + metadata: None, }; let grep_output = r#"src/main.rs:15:async fn process_request(req: Request) -> Result { @@ -1158,6 +1169,7 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "grep-search".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -1191,7 +1203,7 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul let _tool_request = ToolRequest { id: "json-test".to_string(), tool_call: Ok(tool_call), - thought_signature: None, + metadata: None, }; let json_output = r#"{"status": "success", "data": {"count": 42}}"#; @@ -1203,6 +1215,7 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "json-test".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], @@ -1231,7 +1244,7 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul let tool_request = ToolRequest { id: "npm-install".to_string(), tool_call: Ok(npm_call), - thought_signature: None, + metadata: None, }; let npm_output = r#"added 57 packages, and audited 58 packages in 3s @@ -1249,6 +1262,7 @@ found 0 vulnerabilities"#; annotations: None, }; let tool_response = ToolResponse { + metadata: None, id: "npm-install".to_string(), tool_result: Ok(rmcp::model::CallToolResult { content: vec![Content::text(text_content.raw.text)], diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index edfe875bc7e7..4e18926e9dd4 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -39,7 +39,8 @@ use crate::context_mgmt::{ check_if_compaction_needed, compact_messages, DEFAULT_COMPACTION_THRESHOLD, }; use crate::conversation::message::{ - ActionRequiredData, Message, MessageContent, SystemNotificationType, ToolRequest, + ActionRequiredData, Message, MessageContent, ProviderMetadata, SystemNotificationType, + ToolRequest, }; use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation}; use crate::mcp_utils::ToolResult; @@ -359,7 +360,7 @@ impl Agent { for request in &permission_check_result.denied { if let Some(response_msg) = request_to_response_map.get(&request.id) { let mut response = response_msg.lock().await; - *response = response.clone().with_tool_response( + *response = response.clone().with_tool_response_with_metadata( request.id.clone(), Ok(CallToolResult { content: vec![rmcp::model::Content::text(DECLINED_RESPONSE)], @@ -367,6 +368,7 @@ impl Agent { is_error: Some(true), meta: None, }), + request.metadata.as_ref(), ); } } @@ -1081,8 +1083,10 @@ impl Agent { .collect(); let mut request_to_response_map = HashMap::new(); + let mut request_metadata: HashMap> = HashMap::new(); for (idx, request) in frontend_requests.iter().chain(remaining_requests.iter()).enumerate() { request_to_response_map.insert(request.id.clone(), tool_response_messages[idx].clone()); + request_metadata.insert(request.id.clone(), request.metadata.clone()); } for (idx, request) in frontend_requests.iter().enumerate() { @@ -1100,7 +1104,7 @@ impl Agent { for request in remaining_requests.iter() { if let Some(response_msg) = request_to_response_map.get(&request.id) { let mut response = response_msg.lock().await; - *response = response.clone().with_tool_response( + *response = response.clone().with_tool_response_with_metadata( request.id.clone(), Ok(CallToolResult { content: vec![Content::text(CHAT_MODE_TOOL_SKIPPED_RESPONSE)], @@ -1108,6 +1112,7 @@ impl Agent { is_error: Some(false), meta: None, }), + request.metadata.as_ref(), ); } } @@ -1199,8 +1204,9 @@ impl Agent { all_install_successful = false; } if let Some(response_msg) = request_to_response_map.get(&request_id) { + let metadata = request_metadata.get(&request_id).and_then(|m| m.as_ref()); let mut response = response_msg.lock().await; - *response = response.clone().with_tool_response(request_id, output); + *response = response.clone().with_tool_response_with_metadata(request_id, output, metadata); } } ToolStreamItem::Message(msg) => { @@ -1222,11 +1228,30 @@ impl Agent { } } + // Preserve thinking content from the original response + // Gemini (and other thinking models) require thinking to be echoed back + let thinking_content: Vec = response.content.iter() + .filter(|c| matches!(c, MessageContent::Thinking(_))) + .cloned() + .collect(); + if !thinking_content.is_empty() { + let thinking_msg = Message::new( + response.role.clone(), + response.created, + thinking_content, + ).with_id(format!("msg_{}", Uuid::new_v4())); + messages_to_add.push(thinking_msg); + } + for (idx, request) in frontend_requests.iter().chain(remaining_requests.iter()).enumerate() { if request.tool_call.is_ok() { let request_msg = Message::assistant() .with_id(format!("msg_{}", Uuid::new_v4())) - .with_tool_request(request.id.clone(), request.tool_call.clone()); + .with_tool_request_with_metadata( + request.id.clone(), + request.tool_call.clone(), + request.metadata.as_ref(), + ); messages_to_add.push(request_msg); let final_response = tool_response_messages[idx] .lock().await.clone(); diff --git a/crates/goose/src/agents/tool_execution.rs b/crates/goose/src/agents/tool_execution.rs index 144d3d11ff1d..5323561cd3f0 100644 --- a/crates/goose/src/agents/tool_execution.rs +++ b/crates/goose/src/agents/tool_execution.rs @@ -120,7 +120,7 @@ impl Agent { // User declined - update the specific response message for this request if let Some(response_msg) = request_to_response_map.get(&request.id) { let mut response = response_msg.lock().await; - *response = response.clone().with_tool_response( + *response = response.clone().with_tool_response_with_metadata( request.id.clone(), Ok(rmcp::model::CallToolResult { content: vec![Content::text(DECLINED_RESPONSE)], @@ -128,6 +128,7 @@ impl Agent { is_error: Some(true), meta: None, }), + request.metadata.as_ref(), ); } } @@ -155,7 +156,11 @@ impl Agent { if let Some((id, result)) = self.tool_result_rx.lock().await.recv().await { let mut response = message_tool_response.lock().await; - *response = response.clone().with_tool_response(id, result); + *response = response.clone().with_tool_response_with_metadata( + id, + result, + tool_request.metadata.as_ref(), + ); } } } diff --git a/crates/goose/src/conversation/message.rs b/crates/goose/src/conversation/message.rs index 20ea3588898c..074dc2e7771c 100644 --- a/crates/goose/src/conversation/message.rs +++ b/crates/goose/src/conversation/message.rs @@ -53,6 +53,10 @@ where Ok(content) } +/// Provider-specific metadata for tool requests/responses. +/// Allows providers to store custom data without polluting the core model. +pub type ProviderMetadata = serde_json::Map; + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[derive(ToSchema)] @@ -62,7 +66,8 @@ pub struct ToolRequest { #[schema(value_type = Object)] pub tool_call: ToolResult, #[serde(skip_serializing_if = "Option::is_none")] - pub thought_signature: Option, + #[schema(value_type = Object)] + pub metadata: Option, } impl ToolRequest { @@ -89,6 +94,9 @@ pub struct ToolResponse { #[serde(with = "tool_result_serde")] #[schema(value_type = Object)] pub tool_result: ToolResult, + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(value_type = Object)] + pub metadata: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -250,19 +258,19 @@ impl MessageContent { MessageContent::ToolRequest(ToolRequest { id: id.into(), tool_call, - thought_signature: None, + metadata: None, }) } - pub fn tool_request_with_signature, S2: Into>( - id: S1, + pub fn tool_request_with_metadata>( + id: S, tool_call: ToolResult, - thought_signature: Option, + metadata: Option<&ProviderMetadata>, ) -> Self { MessageContent::ToolRequest(ToolRequest { id: id.into(), tool_call, - thought_signature: thought_signature.map(|s| s.into()), + metadata: metadata.cloned(), }) } @@ -270,6 +278,19 @@ impl MessageContent { MessageContent::ToolResponse(ToolResponse { id: id.into(), tool_result, + metadata: None, + }) + } + + pub fn tool_response_with_metadata>( + id: S, + tool_result: ToolResult, + metadata: Option<&ProviderMetadata>, + ) -> Self { + MessageContent::ToolResponse(ToolResponse { + id: id.into(), + tool_result, + metadata: metadata.cloned(), }) } @@ -641,6 +662,17 @@ impl Message { self.with_content(MessageContent::tool_request(id, tool_call)) } + pub fn with_tool_request_with_metadata>( + self, + id: S, + tool_call: ToolResult, + metadata: Option<&ProviderMetadata>, + ) -> Self { + self.with_content(MessageContent::tool_request_with_metadata( + id, tool_call, metadata, + )) + } + /// Add a tool response to the message pub fn with_tool_response>( self, @@ -650,6 +682,17 @@ impl Message { self.with_content(MessageContent::tool_response(id, result)) } + pub fn with_tool_response_with_metadata>( + self, + id: S, + result: ToolResult, + metadata: Option<&ProviderMetadata>, + ) -> Self { + self.with_content(MessageContent::tool_response_with_metadata( + id, result, metadata, + )) + } + /// Add an action required message for tool confirmation pub fn with_action_required>( self, diff --git a/crates/goose/src/providers/formats/google.rs b/crates/goose/src/providers/formats/google.rs index 1e9a85dd9ec4..380fbd882907 100644 --- a/crates/goose/src/providers/formats/google.rs +++ b/crates/goose/src/providers/formats/google.rs @@ -9,10 +9,25 @@ use rmcp::model::{ }; use std::borrow::Cow; -use crate::conversation::message::{Message, MessageContent}; +use crate::conversation::message::{Message, MessageContent, ProviderMetadata}; use serde_json::{json, Map, Value}; use std::ops::Deref; +pub const THOUGHT_SIGNATURE_KEY: &str = "thoughtSignature"; + +pub fn metadata_with_signature(signature: &str) -> ProviderMetadata { + let mut map = ProviderMetadata::new(); + map.insert(THOUGHT_SIGNATURE_KEY.to_string(), json!(signature)); + map +} + +pub fn get_thought_signature(metadata: &Option) -> Option<&str> { + metadata + .as_ref() + .and_then(|m| m.get(THOUGHT_SIGNATURE_KEY)) + .and_then(|v| v.as_str()) +} + /// Convert internal Message format to Google's API message specification pub fn format_messages(messages: &[Message]) -> Vec { messages @@ -58,8 +73,8 @@ pub fn format_messages(messages: &[Message]) -> Vec { let mut part = Map::new(); part.insert("functionCall".to_string(), json!(function_call_part)); - if let Some(signature) = &request.thought_signature { - part.insert("thoughtSignature".to_string(), json!(signature)); + if let Some(signature) = get_thought_signature(&request.metadata) { + part.insert(THOUGHT_SIGNATURE_KEY.to_string(), json!(signature)); } parts.push(json!(part)); @@ -117,15 +132,44 @@ pub fn format_messages(messages: &[Message]) -> Vec { if text.is_empty() { text = "Tool call is done.".to_string(); } - parts.push(json!({ - "functionResponse": { - "name": response.id, - "response": {"content": {"text": text}}, - }} - )); + let mut part = Map::new(); + let mut function_response = Map::new(); + function_response.insert("name".to_string(), json!(response.id)); + function_response.insert( + "response".to_string(), + json!({"content": {"text": text}}), + ); + part.insert( + "functionResponse".to_string(), + json!(function_response), + ); + if let Some(signature) = get_thought_signature(&response.metadata) { + part.insert( + THOUGHT_SIGNATURE_KEY.to_string(), + json!(signature), + ); + } + parts.push(json!(part)); } Err(e) => { - parts.push(json!({"text":format!("Error: {}", e)})); + let mut part = Map::new(); + let mut function_response = Map::new(); + function_response.insert("name".to_string(), json!(response.id)); + function_response.insert( + "response".to_string(), + json!({"content": {"text": format!("Error: {}", e)}}), + ); + part.insert( + "functionResponse".to_string(), + json!(function_response), + ); + if let Some(signature) = get_thought_signature(&response.metadata) { + part.insert( + THOUGHT_SIGNATURE_KEY.to_string(), + json!(signature), + ); + } + parts.push(json!(part)); } } } @@ -282,15 +326,30 @@ pub fn response_to_message(response: Value) -> Result { .and_then(|parts| parts.as_array()) .unwrap_or(&binding); + // Track the last seen thought signature to use as fallback for function calls without one + // This handles cases where Google's API returns multiple function calls but only includes + // thoughtSignature on some of them + let mut last_signature: Option = None; + + let has_function_calls = parts.iter().any(|p| p.get("functionCall").is_some()); + for part in parts { - let thought_signature = part - .get("thoughtSignature") + let signature = part + .get(THOUGHT_SIGNATURE_KEY) .and_then(|v| v.as_str()) .map(|s| s.to_string()); + if signature.is_some() { + last_signature = signature.clone(); + } + if let Some(text) = part.get("text").and_then(|v| v.as_str()) { - if let Some(sig) = thought_signature { - content.push(MessageContent::thinking(text.to_string(), sig)); + // Text is "thinking" only if: + // 1. It has a signature AND + // 2. The response also contains function calls (meaning this is reasoning before acting) + // If there are no function calls, this is the final response and should be shown + if let (Some(sig), true) = (&signature, has_function_calls) { + content.push(MessageContent::thinking(text.to_string(), sig.clone())); } else { content.push(MessageContent::text(text.to_string())); } @@ -316,16 +375,17 @@ pub fn response_to_message(response: Value) -> Result { content.push(MessageContent::tool_request(id, Err(error))); } else { let parameters = function_call.get("args"); - if let Some(params) = parameters { - content.push(MessageContent::tool_request_with_signature( - id, - Ok(CallToolRequestParam { - name: name.into(), - arguments: Some(object(params.clone())), - }), - thought_signature, - )); - } + let arguments = parameters.map(|params| object(params.clone())); + let effective_signature = signature.as_deref().or(last_signature.as_deref()); + let metadata = effective_signature.map(metadata_with_signature); + content.push(MessageContent::tool_request_with_metadata( + id, + Ok(CallToolRequestParam { + name: name.into(), + arguments, + }), + metadata.as_ref(), + )); } } } @@ -919,4 +979,66 @@ mod tests { assert_eq!(regular_field["type"], "number"); assert_eq!(regular_field["description"], "A regular number field"); } + + fn google_response(parts: Vec) -> Value { + json!({"candidates": [{"content": {"role": "model", "parts": parts}}]}) + } + + fn tool_result(text: &str) -> CallToolResult { + CallToolResult { + content: vec![Content::text(text)], + structured_content: None, + is_error: Some(false), + meta: None, + } + } + + #[test] + fn test_thought_signature_roundtrip() { + const SIG: &str = "thought_sig_abc"; + + let response_with_tools = google_response(vec![ + json!({"text": "Let me think...", "thoughtSignature": SIG}), + json!({"functionCall": {"name": "shell", "args": {"cmd": "ls"}}, "thoughtSignature": SIG}), + json!({"functionCall": {"name": "read", "args": {}}}), + ]); + + let native = response_to_message(response_with_tools).unwrap(); + assert_eq!(native.content.len(), 3, "Expected thinking + 2 tool calls"); + + let thinking = native.content[0] + .as_thinking() + .expect("Text with function calls should be Thinking"); + assert_eq!(thinking.signature, SIG); + + let req1 = native.content[1] + .as_tool_request() + .expect("Second part should be ToolRequest"); + let req2 = native.content[2] + .as_tool_request() + .expect("Third part should be ToolRequest"); + assert_eq!(get_thought_signature(&req1.metadata), Some(SIG)); + assert_eq!( + get_thought_signature(&req2.metadata), + Some(SIG), + "Should inherit" + ); + + let tool_response = Message::user().with_tool_response_with_metadata( + req1.id.clone(), + Ok(tool_result("output")), + req1.metadata.as_ref(), + ); + let google_out = format_messages(&[native, tool_response]); + assert_eq!(google_out[0]["parts"][0]["thoughtSignature"], SIG); + assert_eq!(google_out[1]["parts"][0]["thoughtSignature"], SIG); + + let final_response = + google_response(vec![json!({"text": "Done!", "thoughtSignature": SIG})]); + let final_native = response_to_message(final_response).unwrap(); + assert!( + final_native.content[0].as_text().is_some(), + "Text-only = final answer" + ); + } } diff --git a/crates/goose/src/security/security_inspector.rs b/crates/goose/src/security/security_inspector.rs index 2a83cd1e0f7d..7b9ba6333bed 100644 --- a/crates/goose/src/security/security_inspector.rs +++ b/crates/goose/src/security/security_inspector.rs @@ -113,7 +113,7 @@ mod tests { name: "shell".into(), arguments: Some(object!({"command": "rm -rf /"})), }), - thought_signature: None, + metadata: None, }]; let results = inspector.inspect(&tool_requests, &[]).await.unwrap(); diff --git a/crates/goose/src/tool_inspection.rs b/crates/goose/src/tool_inspection.rs index cfc9bd9939b1..277c83a0ba5d 100644 --- a/crates/goose/src/tool_inspection.rs +++ b/crates/goose/src/tool_inspection.rs @@ -295,7 +295,7 @@ mod tests { name: "test_tool".into(), arguments: Some(object!({})), }), - thought_signature: None, + metadata: None, }; let permission_result = PermissionCheckResult { diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 03c3c2553db3..4efe91f9e383 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -5523,9 +5523,8 @@ "id": { "type": "string" }, - "thoughtSignature": { - "type": "string", - "nullable": true + "metadata": { + "type": "object" }, "toolCall": { "type": "object" @@ -5542,6 +5541,9 @@ "id": { "type": "string" }, + "metadata": { + "type": "object" + }, "toolResult": { "type": "object" } diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 81b9987e32d7..bf733d9dfe0b 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -962,7 +962,9 @@ export type ToolPermission = { export type ToolRequest = { id: string; - thoughtSignature?: string | null; + metadata?: { + [key: string]: unknown; + }; toolCall: { [key: string]: unknown; }; @@ -970,6 +972,9 @@ export type ToolRequest = { export type ToolResponse = { id: string; + metadata?: { + [key: string]: unknown; + }; toolResult: { [key: string]: unknown; };