From 738f5f647cb7a67a57d4074749dbc7f54a64796b Mon Sep 17 00:00:00 2001 From: Abhijay Jain Date: Thu, 12 Feb 2026 20:48:44 +0530 Subject: [PATCH] feat: reasoning_content in API for reasoning models (#6322) Signed-off-by: Abhijay007 --- crates/goose-server/src/openapi.rs | 6 +- crates/goose/src/context_mgmt/mod.rs | 37 ++--- crates/goose/src/conversation/message.rs | 19 +++ .../goose/src/providers/formats/anthropic.rs | 4 + crates/goose/src/providers/formats/bedrock.rs | 5 + .../goose/src/providers/formats/databricks.rs | 4 + crates/goose/src/providers/formats/openai.rs | 152 ++++++++++++++++-- .../goose/src/providers/formats/snowflake.rs | 4 + ui/desktop/openapi.json | 32 ++++ ui/desktop/src/api/index.ts | 2 +- ui/desktop/src/api/types.gen.ts | 6 + ui/desktop/src/components/GooseMessage.tsx | 13 ++ .../sessions/SessionViewComponents.tsx | 15 ++ ui/desktop/src/types/message.ts | 12 ++ 14 files changed, 276 insertions(+), 35 deletions(-) diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index a7ab06252b..1fa80ab31a 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -21,8 +21,9 @@ use goose::config::declarative_providers::{ }; use goose::conversation::message::{ ActionRequired, ActionRequiredData, FrontendToolRequest, Message, MessageContent, - MessageMetadata, RedactedThinkingContent, SystemNotificationContent, SystemNotificationType, - ThinkingContent, TokenState, ToolConfirmationRequest, ToolRequest, ToolResponse, + MessageMetadata, ReasoningContent, RedactedThinkingContent, SystemNotificationContent, + SystemNotificationType, ThinkingContent, TokenState, ToolConfirmationRequest, ToolRequest, + ToolResponse, }; use crate::routes::recipe_utils::RecipeManifest; @@ -480,6 +481,7 @@ derive_utoipa!(Icon as IconSchema); ActionRequiredData, ThinkingContent, RedactedThinkingContent, + ReasoningContent, FrontendToolRequest, ResourceContentsSchema, SystemNotificationType, diff --git a/crates/goose/src/context_mgmt/mod.rs b/crates/goose/src/context_mgmt/mod.rs index ffc01cfa2a..305b817b60 100644 --- a/crates/goose/src/context_mgmt/mod.rs +++ b/crates/goose/src/context_mgmt/mod.rs @@ -336,19 +336,19 @@ fn format_message_for_compacting(msg: &Message) -> String { let content_parts: Vec = msg .content .iter() - .map(|content| match content { - MessageContent::Text(text) => text.text.clone(), - MessageContent::Image(img) => format!("[image: {}]", img.mime_type), + .filter_map(|content| match content { + MessageContent::Text(text) => Some(text.text.clone()), + MessageContent::Image(img) => Some(format!("[image: {}]", img.mime_type)), MessageContent::ToolRequest(req) => { if let Ok(call) = &req.tool_call { - format!( + Some(format!( "tool_request({}): {}", call.name, serde_json::to_string(&call.arguments) .unwrap_or_else(|_| "<>".to_string()) - ) + )) } else { - "tool_request: [error]".to_string() + Some("tool_request: [error]".to_string()) } } MessageContent::ToolResponse(res) => { @@ -362,40 +362,41 @@ fn format_message_for_compacting(msg: &Message) -> String { .collect(); if !text_items.is_empty() { - format!("tool_response: {}", text_items.join("\n")) + Some(format!("tool_response: {}", text_items.join("\n"))) } else { - "tool_response: [non-text content]".to_string() + Some("tool_response: [non-text content]".to_string()) } } else { - "tool_response: [error]".to_string() + Some("tool_response: [error]".to_string()) } } MessageContent::ToolConfirmationRequest(req) => { - format!("tool_confirmation_request: {}", req.tool_name) + Some(format!("tool_confirmation_request: {}", req.tool_name)) } MessageContent::ActionRequired(action) => match &action.data { ActionRequiredData::ToolConfirmation { tool_name, .. } => { - format!("action_required(tool_confirmation): {}", tool_name) + Some(format!("action_required(tool_confirmation): {}", tool_name)) } ActionRequiredData::Elicitation { message, .. } => { - format!("action_required(elicitation): {}", message) + Some(format!("action_required(elicitation): {}", message)) } ActionRequiredData::ElicitationResponse { id, .. } => { - format!("action_required(elicitation_response): {}", id) + Some(format!("action_required(elicitation_response): {}", id)) } }, MessageContent::FrontendToolRequest(req) => { if let Ok(call) = &req.tool_call { - format!("frontend_tool_request: {}", call.name) + Some(format!("frontend_tool_request: {}", call.name)) } else { - "frontend_tool_request: [error]".to_string() + Some("frontend_tool_request: [error]".to_string()) } } - MessageContent::Thinking(_) => "thinking".to_string(), - MessageContent::RedactedThinking(_) => "redacted_thinking".to_string(), + MessageContent::Thinking(_) => None, + MessageContent::RedactedThinking(_) => None, MessageContent::SystemNotification(notification) => { - format!("system_notification: {}", notification.msg) + Some(format!("system_notification: {}", notification.msg)) } + MessageContent::Reasoning(_) => None, }) .collect(); diff --git a/crates/goose/src/conversation/message.rs b/crates/goose/src/conversation/message.rs index e116a7b9b8..c9b28d50fa 100644 --- a/crates/goose/src/conversation/message.rs +++ b/crates/goose/src/conversation/message.rs @@ -175,6 +175,11 @@ pub struct SystemNotificationContent { pub data: Option, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)] +pub struct ReasoningContent { + pub text: String, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)] /// Content passed inside a message, which can be both simple content and tool content #[serde(tag = "type", rename_all = "camelCase")] @@ -189,6 +194,7 @@ pub enum MessageContent { Thinking(ThinkingContent), RedactedThinking(RedactedThinkingContent), SystemNotification(SystemNotificationContent), + Reasoning(ReasoningContent), } impl fmt::Display for MessageContent { @@ -230,6 +236,7 @@ impl fmt::Display for MessageContent { MessageContent::SystemNotification(r) => { write!(f, "[SystemNotification: {}]", r.msg) } + MessageContent::Reasoning(r) => write!(f, "[Reasoning: {}]", r.text), } } } @@ -443,6 +450,10 @@ impl MessageContent { }) } + pub fn reasoning>(text: S) -> Self { + MessageContent::Reasoning(ReasoningContent { text: text.into() }) + } + pub fn as_system_notification(&self) -> Option<&SystemNotificationContent> { if let MessageContent::SystemNotification(ref notification) = self { Some(notification) @@ -514,6 +525,14 @@ impl MessageContent { _ => None, } } + + /// Get the reasoning content if this is a ReasoningContent variant + pub fn as_reasoning(&self) -> Option<&ReasoningContent> { + match self { + MessageContent::Reasoning(reasoning) => Some(reasoning), + _ => None, + } + } } impl From for MessageContent { diff --git a/crates/goose/src/providers/formats/anthropic.rs b/crates/goose/src/providers/formats/anthropic.rs index 6b20e93662..67aa58780b 100644 --- a/crates/goose/src/providers/formats/anthropic.rs +++ b/crates/goose/src/providers/formats/anthropic.rs @@ -124,6 +124,10 @@ pub fn format_messages(messages: &[Message]) -> Vec { })); } } + MessageContent::Reasoning(_reasoning) => { + // Reasoning content is for OpenAI-compatible APIs (e.g., DeepSeek) + // Anthropic doesn't use this format, so skip it + } } } diff --git a/crates/goose/src/providers/formats/bedrock.rs b/crates/goose/src/providers/formats/bedrock.rs index 1018591ec2..9eb6a2c245 100644 --- a/crates/goose/src/providers/formats/bedrock.rs +++ b/crates/goose/src/providers/formats/bedrock.rs @@ -113,6 +113,11 @@ pub fn to_bedrock_message_content(content: &MessageContent) -> Result { + // Reasoning content is for OpenAI-compatible APIs (e.g., DeepSeek) + // Bedrock doesn't use this format, so skip + bedrock::ContentBlock::Text("".to_string()) + } }) } diff --git a/crates/goose/src/providers/formats/databricks.rs b/crates/goose/src/providers/formats/databricks.rs index 2243794b70..51a1265c81 100644 --- a/crates/goose/src/providers/formats/databricks.rs +++ b/crates/goose/src/providers/formats/databricks.rs @@ -205,6 +205,10 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec {} + MessageContent::Reasoning(_reasoning) => { + // Reasoning content is for OpenAI-compatible APIs (e.g., DeepSeek) + // Databricks doesn't use this format, so skip + } } } diff --git a/crates/goose/src/providers/formats/openai.rs b/crates/goose/src/providers/formats/openai.rs index 3103527305..685f43dbef 100644 --- a/crates/goose/src/providers/formats/openai.rs +++ b/crates/goose/src/providers/formats/openai.rs @@ -52,6 +52,7 @@ struct Delta { role: Option, tool_calls: Option>, reasoning_details: Option>, + reasoning_content: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -80,6 +81,7 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec< let mut output = Vec::new(); let mut content_array = Vec::new(); let mut text_array = Vec::new(); + let mut reasoning_text: Option = None; for content in &message.content { match content { @@ -112,6 +114,9 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec< MessageContent::SystemNotification(_) => { continue; } + MessageContent::Reasoning(r) => { + reasoning_text = Some(r.text.clone()); + } MessageContent::ToolRequest(request) => match &request.tool_call { Ok(tool_call) => { let sanitized_name = sanitize_function_name(&tool_call.name); @@ -277,6 +282,17 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec< converted["content"] = json!(null); } + // DeepSeek requires reasoning_content field when tool_calls are present + // Set it to the captured reasoning text, or empty string if not present + if converted.get("tool_calls").is_some() { + let reasoning = reasoning_text.unwrap_or_default(); + converted["reasoning_content"] = json!(reasoning); + } else if let Some(reasoning) = reasoning_text { + if !reasoning.is_empty() { + converted["reasoning_content"] = json!(reasoning); + } + } + if converted.get("content").is_some() || converted.get("tool_calls").is_some() { output.insert(0, converted); } @@ -330,6 +346,15 @@ pub fn response_to_message(response: &Value) -> anyhow::Result { let mut content = Vec::new(); + // Capture reasoning_content if present (for DeepSeek reasoning models) + if let Some(reasoning_content) = original.get("reasoning_content") { + if let Some(reasoning_str) = reasoning_content.as_str() { + if !reasoning_str.is_empty() { + content.push(MessageContent::reasoning(reasoning_str)); + } + } + } + if let Some(text) = original.get("content") { if let Some(text_str) = text.as_str() { content.push(MessageContent::text(text_str)); @@ -678,23 +703,44 @@ where Some(msg), usage, ) - } else if chunk.choices[0].delta.content.is_some() { - let text = chunk.choices[0].delta.content.as_ref().unwrap(); - let mut msg = Message::new( - Role::Assistant, - chrono::Utc::now().timestamp(), - vec![MessageContent::text(text)], - ); + } else if chunk.choices[0].delta.content.is_some() || chunk.choices[0].delta.reasoning_content.is_some() { + let mut content = Vec::new(); - // Add ID if present - if let Some(id) = chunk.id { - msg = msg.with_id(id); + if let Some(reasoning) = &chunk.choices[0].delta.reasoning_content { + if !reasoning.is_empty() { + content.push(MessageContent::reasoning(reasoning)); + } } - yield ( - Some(msg), - usage, - ) + if let Some(text) = &chunk.choices[0].delta.content { + if !text.is_empty() { + content.push(MessageContent::text(text)); + } + } + + if !content.is_empty() { + let mut msg = Message::new( + Role::Assistant, + chrono::Utc::now().timestamp(), + content, + ); + + // Add ID if present + if let Some(id) = chunk.id { + msg = msg.with_id(id); + } + + yield ( + Some(msg), + if chunk.choices[0].finish_reason.is_some() { + usage + } else { + None + }, + ) + } else if usage.is_some() { + yield (None, usage) + } } else if usage.is_some() { yield (None, usage) } @@ -1834,4 +1880,82 @@ data: [DONE]"#; panic!("Expected tool call message with nested extra_content metadata"); } + + #[test] + fn test_response_to_message_with_reasoning_content() -> anyhow::Result<()> { + // Test capturing reasoning_content from DeepSeek reasoning models + let response = json!({ + "choices": [{ + "role": "assistant", + "message": { + "reasoning_content": "Let me think about this step by step...", + "content": "The answer is 9.11 is greater than 9.8" + } + }], + "usage": { + "input_tokens": 10, + "output_tokens": 25, + "total_tokens": 35 + } + }); + + let message = response_to_message(&response)?; + assert_eq!(message.content.len(), 2); + + // First should be reasoning content + if let MessageContent::Reasoning(reasoning) = &message.content[0] { + assert_eq!(reasoning.text, "Let me think about this step by step..."); + } else { + panic!("Expected Reasoning content"); + } + + // Second should be text content + if let MessageContent::Text(text) = &message.content[1] { + assert_eq!(text.text, "The answer is 9.11 is greater than 9.8"); + } else { + panic!("Expected Text content"); + } + + Ok(()) + } + + #[test] + fn test_format_messages_with_reasoning_content() -> anyhow::Result<()> { + // Test that reasoning_content is properly included in formatted messages + let mut message = Message::assistant() + .with_content(MessageContent::reasoning("Thinking through the problem...")) + .with_text("The result is 42"); + + // Add a tool call to test that reasoning_content works with tool calls + message = message.with_tool_request( + "tool1", + Ok(rmcp::model::CallToolRequestParams { + meta: None, + task: None, + name: "test_tool".into(), + arguments: Some(rmcp::object!({"param": "value"})), + }), + ); + + let spec = format_messages(&[message], &ImageFormat::OpenAi); + + assert_eq!(spec.len(), 1); + assert_eq!(spec[0]["role"], "assistant"); + + // Should have reasoning_content field + assert!(spec[0].get("reasoning_content").is_some()); + assert_eq!( + spec[0]["reasoning_content"], + "Thinking through the problem..." + ); + + // Should have content + assert_eq!(spec[0]["content"], "The result is 42"); + + // Should have tool_calls + assert!(spec[0]["tool_calls"].is_array()); + assert_eq!(spec[0]["tool_calls"][0]["function"]["name"], "test_tool"); + + Ok(()) + } } diff --git a/crates/goose/src/providers/formats/snowflake.rs b/crates/goose/src/providers/formats/snowflake.rs index ac3cd191ff..b3bb6202c6 100644 --- a/crates/goose/src/providers/formats/snowflake.rs +++ b/crates/goose/src/providers/formats/snowflake.rs @@ -65,6 +65,10 @@ pub fn format_messages(messages: &[Message]) -> Vec { MessageContent::FrontendToolRequest(_tool_request) => { // Skip frontend tool requests } + MessageContent::Reasoning(_reasoning) => { + // Reasoning content is for OpenAI-compatible APIs (e.g., DeepSeek) + // Snowflake doesn't use this format, so skip + } } } diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index d85209fd43..ec5512b1f3 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -5042,6 +5042,27 @@ } } ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/ReasoningContent" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "reasoning" + ] + } + } + } + ] } ], "description": "Content passed inside a message, which can be both simple content and tool content", @@ -5724,6 +5745,17 @@ } } }, + "ReasoningContent": { + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string" + } + } + }, "Recipe": { "type": "object", "required": [ diff --git a/ui/desktop/src/api/index.ts b/ui/desktop/src/api/index.ts index 70ff840fd0..7089867c1e 100644 --- a/ui/desktop/src/api/index.ts +++ b/ui/desktop/src/api/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts export { addExtension, agentAddExtension, agentRemoveExtension, backupConfig, callTool, cancelDownload, checkProvider, configureProviderOauth, confirmToolAction, createCustomProvider, createRecipe, createSchedule, decodeRecipe, deleteModel, deleteRecipe, deleteSchedule, deleteSession, detectProvider, diagnostics, downloadModel, encodeRecipe, exportApp, exportSession, forkSession, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getPricing, getPrompt, getPrompts, getProviderModels, getSession, getSessionExtensions, getSessionInsights, getSlashCommands, getTools, getTunnelStatus, importApp, importSession, initConfig, inspectRunningJob, killRunningJob, listApps, listModels, listRecipes, listSchedules, listSessions, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, readResource, recipeToYaml, recoverConfig, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchSessions, sendTelemetryEvent, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, startOpenrouterSetup, startTetrateSetup, startTunnel, status, stopAgent, stopTunnel, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateSchedule, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, upsertPermissions, validateConfig } from './sdk.gen'; -export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, BackupConfigData, BackupConfigErrors, BackupConfigResponse, BackupConfigResponses, CallToolData, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, ChatRequest, CheckProviderData, CheckProviderRequest, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderRequest, DetectProviderResponse, DetectProviderResponse2, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadModelData, DownloadModelErrors, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetPricingData, GetPricingResponse, GetPricingResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, Icon, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponse, InitConfigResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelConfig, ModelInfo, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PricingData, PricingQuery, PricingResponse, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponse, RecoverConfigResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponse, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionDisplayInfo, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; +export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, BackupConfigData, BackupConfigErrors, BackupConfigResponse, BackupConfigResponses, CallToolData, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, ChatRequest, CheckProviderData, CheckProviderRequest, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderRequest, DetectProviderResponse, DetectProviderResponse2, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadModelData, DownloadModelErrors, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetPricingData, GetPricingResponse, GetPricingResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, Icon, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponse, InitConfigResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelConfig, ModelInfo, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PricingData, PricingQuery, PricingResponse, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, ReasoningContent, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponse, RecoverConfigResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponse, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionDisplayInfo, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index b3c40b587e..3dbe519027 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -564,6 +564,8 @@ export type MessageContent = (TextContent & { type: 'redactedThinking'; }) | (SystemNotificationContent & { type: 'systemNotification'; +}) | (ReasoningContent & { + type: 'reasoning'; }); export type MessageEvent = { @@ -833,6 +835,10 @@ export type ReadResourceResponse = { uri: string; }; +export type ReasoningContent = { + text: string; +}; + export type Recipe = { activities?: Array | null; author?: Author | null; diff --git a/ui/desktop/src/components/GooseMessage.tsx b/ui/desktop/src/components/GooseMessage.tsx index 6c5d7990b0..b6f04f8345 100644 --- a/ui/desktop/src/components/GooseMessage.tsx +++ b/ui/desktop/src/components/GooseMessage.tsx @@ -5,6 +5,7 @@ import MarkdownContent from './MarkdownContent'; import ToolCallWithResponse from './ToolCallWithResponse'; import { getTextAndImageContent, + getReasoningContent, getToolRequests, getToolResponses, getToolConfirmationContent, @@ -47,6 +48,7 @@ export default function GooseMessage({ const contentRef = useRef(null); let { textContent, imagePaths } = getTextAndImageContent(message); + const reasoningContent = getReasoningContent(message); const splitChainOfThought = (text: string): { displayText: string; cotText: string | null } => { const regex = /([\s\S]*?)<\/think>/i; @@ -129,6 +131,17 @@ export default function GooseMessage({ return (
+ {reasoningContent && ( +
+ + Show reasoning + +
+ +
+
+ )} + {cotText && (
diff --git a/ui/desktop/src/components/sessions/SessionViewComponents.tsx b/ui/desktop/src/components/sessions/SessionViewComponents.tsx index 76ce46475c..76e05d41ea 100644 --- a/ui/desktop/src/components/sessions/SessionViewComponents.tsx +++ b/ui/desktop/src/components/sessions/SessionViewComponents.tsx @@ -8,6 +8,7 @@ import ToolCallWithResponse from '../ToolCallWithResponse'; import ImagePreview from '../ImagePreview'; import { getTextAndImageContent, + getReasoningContent, ToolRequestMessageContent, ToolResponseMessageContent, } from '../../types/message'; @@ -82,6 +83,7 @@ export const SessionMessages: React.FC = ({ messages .map((message, index) => { const { textContent, imagePaths } = getTextAndImageContent(message); + const reasoningContent = getReasoningContent(message); // Get tool requests from the message const toolRequests = message.content @@ -119,6 +121,19 @@ export const SessionMessages: React.FC = ({
+ {/* Reasoning content */} + {reasoningContent && ( +
+ + Show reasoning + +
+ +
+
+ )} + + {/* Text content */} {textContent && (
0 || imagePaths.length > 0 ? 'mb-4' : ''}`} diff --git a/ui/desktop/src/types/message.ts b/ui/desktop/src/types/message.ts index 845096ace6..5f3a2b552d 100644 --- a/ui/desktop/src/types/message.ts +++ b/ui/desktop/src/types/message.ts @@ -97,6 +97,18 @@ export function getTextAndImageContent(message: Message): { return { textContent, imagePaths }; } +export function getReasoningContent(message: Message): string | null { + const reasoningContents = message.content + .filter((content) => content.type === 'reasoning') + .map((content) => { + if ('text' in content) return content.text; + return ''; + }) + .filter((text) => text.length > 0); + + return reasoningContents.length > 0 ? reasoningContents.join('') : null; +} + export function getToolRequests(message: Message): (ToolRequest & { type: 'toolRequest' })[] { return message.content.filter( (content): content is ToolRequest & { type: 'toolRequest' } => content.type === 'toolRequest'