diff --git a/crates/goose-cli/src/session/export.rs b/crates/goose-cli/src/session/export.rs index 8c9636807594..4f2cd8120ff1 100644 --- a/crates/goose-cli/src/session/export.rs +++ b/crates/goose-cli/src/session/export.rs @@ -512,6 +512,7 @@ mod tests { let tool_request = ToolRequest { id: "test-id".to_string(), tool_call: Ok(tool_call), + thought_signature: None, }; let result = tool_request_to_markdown(&tool_request, true); @@ -535,6 +536,7 @@ mod tests { let tool_request = ToolRequest { id: "test-id".to_string(), tool_call: Ok(tool_call), + thought_signature: None, }; let result = tool_request_to_markdown(&tool_request, true); @@ -662,6 +664,7 @@ mod tests { let tool_request = ToolRequest { id: "shell-cat".to_string(), tool_call: Ok(tool_call), + thought_signature: None, }; let python_code = r#"#!/usr/bin/env python3 @@ -708,6 +711,7 @@ if __name__ == "__main__": let tool_request = ToolRequest { id: "git-status".to_string(), tool_call: Ok(git_status_call), + thought_signature: None, }; let git_output = " M src/main.rs\n?? temp.txt\n A new_feature.rs"; @@ -746,6 +750,7 @@ if __name__ == "__main__": let _tool_request = ToolRequest { id: "cargo-build".to_string(), tool_call: Ok(cargo_build_call), + thought_signature: None, }; let build_output = r#" Compiling goose-cli v0.1.0 (/Users/user/goose) @@ -790,6 +795,7 @@ warning: unused variable `x` let _tool_request = ToolRequest { id: "curl-api".to_string(), tool_call: Ok(curl_call), + thought_signature: None, }; let api_response = r#"{ @@ -838,6 +844,7 @@ warning: unused variable `x` let tool_request = ToolRequest { id: "editor-write".to_string(), tool_call: Ok(editor_call), + thought_signature: None, }; let text_content = TextContent { @@ -878,6 +885,7 @@ warning: unused variable `x` let _tool_request = ToolRequest { id: "editor-view".to_string(), tool_call: Ok(editor_call), + thought_signature: None, }; let python_code = r#"import os @@ -927,6 +935,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, }; let error_output = r#"python: can't open file 'nonexistent_script.py': [Errno 2] No such file or directory @@ -962,6 +971,7 @@ Command failed with exit code 2"#; let tool_request = ToolRequest { id: "script-exec".to_string(), tool_call: Ok(script_call), + thought_signature: None, }; let script_output = r#"Python 3.11.5 (main, Aug 24 2023, 15:18:16) [Clang 14.0.3 ] @@ -1008,6 +1018,7 @@ Command failed with exit code 2"#; let _tool_request = ToolRequest { id: "multi-cmd".to_string(), tool_call: Ok(multi_call), + thought_signature: None, }; let multi_output = r#"total 24 @@ -1052,6 +1063,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, }; let grep_output = r#"src/main.rs:15:async fn process_request(req: Request) -> Result { @@ -1095,6 +1107,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, }; let json_output = r#"{"status": "success", "data": {"count": 42}}"#; @@ -1129,6 +1142,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, }; let npm_output = r#"added 57 packages, and audited 58 packages in 3s diff --git a/crates/goose/src/conversation/message.rs b/crates/goose/src/conversation/message.rs index 2f18d038836f..f41e6e98bc6a 100644 --- a/crates/goose/src/conversation/message.rs +++ b/crates/goose/src/conversation/message.rs @@ -53,6 +53,8 @@ pub struct ToolRequest { #[serde(with = "tool_result_serde")] #[schema(value_type = Object)] pub tool_call: ToolResult, + #[serde(skip_serializing_if = "Option::is_none")] + pub thought_signature: Option, } impl ToolRequest { @@ -201,6 +203,19 @@ impl MessageContent { MessageContent::ToolRequest(ToolRequest { id: id.into(), tool_call, + thought_signature: None, + }) + } + + pub fn tool_request_with_signature, S2: Into>( + id: S1, + tool_call: ToolResult, + thought_signature: Option, + ) -> Self { + MessageContent::ToolRequest(ToolRequest { + id: id.into(), + tool_call, + thought_signature: thought_signature.map(|s| s.into()), }) } diff --git a/crates/goose/src/providers/formats/google.rs b/crates/goose/src/providers/formats/google.rs index 786762328b15..afd0d536ac4e 100644 --- a/crates/goose/src/providers/formats/google.rs +++ b/crates/goose/src/providers/formats/google.rs @@ -53,9 +53,14 @@ pub fn format_messages(messages: &[Message]) -> Vec { } } - parts.push(json!({ - "functionCall": function_call_part - })); + 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)); + } + + parts.push(json!(part)); } Err(e) => { parts.push(json!({"text":format!("Error: {}", e)})); @@ -121,6 +126,12 @@ pub fn format_messages(messages: &[Message]) -> Vec { } } } + MessageContent::Thinking(thinking) => { + let mut part = Map::new(); + part.insert("text".to_string(), json!(thinking.thinking)); + part.insert("thoughtSignature".to_string(), json!(thinking.signature)); + parts.push(json!(part)); + } _ => {} } @@ -269,8 +280,17 @@ pub fn response_to_message(response: Value) -> Result { .unwrap_or(&binding); for part in parts { + let thought_signature = part + .get("thoughtSignature") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + if let Some(text) = part.get("text").and_then(|v| v.as_str()) { - content.push(MessageContent::text(text.to_string())); + if let Some(sig) = thought_signature { + content.push(MessageContent::thinking(text.to_string(), sig)); + } else { + content.push(MessageContent::text(text.to_string())); + } } else if let Some(function_call) = part.get("functionCall") { let id: String = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -294,12 +314,13 @@ pub fn response_to_message(response: Value) -> Result { } else { let parameters = function_call.get("args"); if let Some(params) = parameters { - content.push(MessageContent::tool_request( + content.push(MessageContent::tool_request_with_signature( id, Ok(CallToolRequestParam { name: name.into(), arguments: Some(object(params.clone())), }), + thought_signature, )); } } diff --git a/crates/goose/src/providers/google.rs b/crates/goose/src/providers/google.rs index 6f73268aa5e2..95d3ff26378c 100644 --- a/crates/goose/src/providers/google.rs +++ b/crates/goose/src/providers/google.rs @@ -30,6 +30,7 @@ pub const GOOGLE_KNOWN_MODELS: &[&str] = &[ "gemini-2.0-flash-exp", "gemini-2.0-flash-preview-image-generation", "gemini-2.0-flash-lite", + "gemini-3-pro-preview", ]; pub const GOOGLE_DOC_URL: &str = "https://ai.google.dev/gemini-api/docs/models"; diff --git a/crates/goose/src/security/security_inspector.rs b/crates/goose/src/security/security_inspector.rs index 41a1fcad14b6..2a83cd1e0f7d 100644 --- a/crates/goose/src/security/security_inspector.rs +++ b/crates/goose/src/security/security_inspector.rs @@ -113,6 +113,7 @@ mod tests { name: "shell".into(), arguments: Some(object!({"command": "rm -rf /"})), }), + thought_signature: 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 2e372c91ae96..cfc9bd9939b1 100644 --- a/crates/goose/src/tool_inspection.rs +++ b/crates/goose/src/tool_inspection.rs @@ -295,6 +295,7 @@ mod tests { name: "test_tool".into(), arguments: Some(object!({})), }), + thought_signature: None, }; let permission_result = PermissionCheckResult { diff --git a/scripts/test_providers.sh b/scripts/test_providers.sh index a7d62b0daff1..13e5e7431a52 100755 --- a/scripts/test_providers.sh +++ b/scripts/test_providers.sh @@ -19,7 +19,7 @@ PROVIDERS=( "xai:grok-3" "openai:gpt-4o:gpt-4o-mini:gpt-3.5-turbo:gpt-5" "anthropic:claude-sonnet-4-5-20250929:claude-opus-4-1-20250805" - "google:gemini-2.5-pro:gemini-2.5-flash" + "google:gemini-2.5-pro:gemini-2.5-flash:gemini-3-pro-preview" "tetrate:claude-sonnet-4-20250514" ) diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 8b87f06badb4..539f9faf1ba0 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -4781,6 +4781,10 @@ "id": { "type": "string" }, + "thoughtSignature": { + "type": "string", + "nullable": true + }, "toolCall": { "type": "object" } diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index c1e9b62014ac..52e9282cd33e 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -860,6 +860,7 @@ export type ToolPermission = { export type ToolRequest = { id: string; + thoughtSignature?: string | null; toolCall: { [key: string]: unknown; };