diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index 7dd154acdc70..7167dcae5737 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -10,6 +10,7 @@ use goose::config::Config; use goose::config::PermissionManager; use goose::model::ModelConfig; use goose::providers::create; +use goose::recipe::Response; use goose::{ agents::{extension::ToolInfo, extension_manager::get_parameter_names}, config::permission::PermissionLevel, @@ -62,6 +63,11 @@ struct UpdateProviderRequest { model: Option, } +#[derive(Deserialize)] +struct SessionConfigRequest { + response: Option, +} + #[derive(Deserialize)] pub struct GetToolsQuery { extension_name: Option, @@ -262,6 +268,44 @@ async fn update_router_tool_selector( )) } +#[utoipa::path( + post, + path = "/agent/session_config", + responses( + (status = 200, description = "Session config updated successfully", body = String), + (status = 500, description = "Internal server error") + ) +)] +async fn update_session_config( + State(state): State>, + headers: HeaderMap, + Json(payload): Json, +) -> Result, Json> { + verify_secret_key(&headers, &state).map_err(|_| { + Json(ErrorResponse { + error: "Unauthorized - Invalid or missing API key".to_string(), + }) + })?; + + let agent = state.get_agent().await.map_err(|e| { + tracing::error!("Failed to get agent: {}", e); + Json(ErrorResponse { + error: format!("Failed to get agent: {}", e), + }) + })?; + + if let Some(response) = payload.response { + agent.add_final_output_tool(response).await; + + tracing::info!("Added final output tool with response config"); + Ok(Json( + "Session config updated with final output tool".to_string(), + )) + } else { + Ok(Json("Nothing provided to update.".to_string())) + } +} + pub fn routes(state: Arc) -> Router { Router::new() .route("/agent/versions", get(get_versions)) @@ -273,5 +317,6 @@ pub fn routes(state: Arc) -> Router { "/agent/update_router_tool_selector", post(update_router_tool_selector), ) + .route("/agent/session_config", post(update_session_config)) .with_state(state) } diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index e87083e2382d..4e6cee8c604e 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -1321,7 +1321,9 @@ mod tests { agent.add_final_output_tool(response).await; let tools = agent.list_tools(None).await; - let final_output_tool = tools.iter().find(|tool| tool.name == "final_output"); + let final_output_tool = tools + .iter() + .find(|tool| tool.name == FINAL_OUTPUT_TOOL_NAME); assert!( final_output_tool.is_some(), diff --git a/crates/goose/src/agents/final_output_tool.rs b/crates/goose/src/agents/final_output_tool.rs index 9e27299815ad..abac6d2d370a 100644 --- a/crates/goose/src/agents/final_output_tool.rs +++ b/crates/goose/src/agents/final_output_tool.rs @@ -7,7 +7,7 @@ use mcp_core::{ }; use serde_json::Value; -pub const FINAL_OUTPUT_TOOL_NAME: &str = "final_output"; +pub const FINAL_OUTPUT_TOOL_NAME: &str = "recipe__final_output"; pub const FINAL_OUTPUT_CONTINUATION_MESSAGE: &str = "You MUST call the `final_output` tool with your final output for the user."; diff --git a/ui/desktop/src/components/ToolCallWithResponse.tsx b/ui/desktop/src/components/ToolCallWithResponse.tsx index ec598d19fd21..971bfea58f5b 100644 --- a/ui/desktop/src/components/ToolCallWithResponse.tsx +++ b/ui/desktop/src/components/ToolCallWithResponse.tsx @@ -285,6 +285,9 @@ function ToolCallView({ } break; + case 'final_output': + return 'final output'; + case 'computer_control': return 'poking around...'; diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index a26f9f1a542a..02ceadafab14 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -140,6 +140,8 @@ export const initializeSystem = async ( // Get recipeConfig directly here const recipeConfig = window.appConfig?.get?.('recipeConfig'); const botPrompt = (recipeConfig as { instructions?: string })?.instructions; + const responseConfig = (recipeConfig as { response?: { json_schema?: unknown } })?.response; + // Extend the system prompt with desktop-specific information const response = await fetch(getApiUrl('/agent/prompt'), { method: 'POST', @@ -162,6 +164,25 @@ export const initializeSystem = async ( } } + // Configure session with response config if present + if (responseConfig?.json_schema) { + const sessionConfigResponse = await fetch(getApiUrl('/agent/session_config'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Secret-Key': getSecretKey(), + }, + body: JSON.stringify({ + response: responseConfig, + }), + }); + if (!sessionConfigResponse.ok) { + console.warn(`Failed to configure session: ${sessionConfigResponse.statusText}`); + } else { + console.log('Configured session with response schema'); + } + } + if (!options?.getExtensions || !options?.addExtension) { console.warn('Extension helpers not provided in alpha mode'); return;