feat: GUI can use structured output present in recipes (#3218)

This commit is contained in:
Jarrod Sibbison
2025-07-03 10:40:29 +10:00
committed by GitHub
parent 49d98e061c
commit cf818ada89
5 changed files with 73 additions and 2 deletions

View File

@@ -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<String>,
}
#[derive(Deserialize)]
struct SessionConfigRequest {
response: Option<Response>,
}
#[derive(Deserialize)]
pub struct GetToolsQuery {
extension_name: Option<String>,
@@ -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<Arc<AppState>>,
headers: HeaderMap,
Json(payload): Json<SessionConfigRequest>,
) -> Result<Json<String>, Json<ErrorResponse>> {
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<AppState>) -> Router {
Router::new()
.route("/agent/versions", get(get_versions))
@@ -273,5 +317,6 @@ pub fn routes(state: Arc<AppState>) -> Router {
"/agent/update_router_tool_selector",
post(update_router_tool_selector),
)
.route("/agent/session_config", post(update_session_config))
.with_state(state)
}

View File

@@ -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(),

View File

@@ -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.";

View File

@@ -285,6 +285,9 @@ function ToolCallView({
}
break;
case 'final_output':
return 'final output';
case 'computer_control':
return 'poking around...';

View File

@@ -191,6 +191,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',
@@ -213,6 +215,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;