Tool reply meta (#6074)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2025-12-11 19:43:36 -05:00
committed by GitHub
parent 7dd244eff6
commit cdab2fc3a6
52 changed files with 1852 additions and 1083 deletions
+6
View File
@@ -354,6 +354,8 @@ derive_utoipa!(Icon as IconSchema);
super::routes::agent::start_agent,
super::routes::agent::resume_agent,
super::routes::agent::get_tools,
super::routes::agent::read_resource,
super::routes::agent::call_tool,
super::routes::agent::update_from_session,
super::routes::agent::agent_add_extension,
super::routes::agent::agent_remove_extension,
@@ -514,6 +516,10 @@ derive_utoipa!(Icon as IconSchema);
goose::agents::types::SuccessCheck,
super::routes::agent::UpdateProviderRequest,
super::routes::agent::GetToolsQuery,
super::routes::agent::ReadResourceRequest,
super::routes::agent::ReadResourceResponse,
super::routes::agent::CallToolRequest,
super::routes::agent::CallToolResponse,
super::routes::agent::UpdateRouterToolSelectorRequest,
super::routes::agent::StartAgentRequest,
super::routes::agent::ResumeAgentRequest,
+113 -2
View File
@@ -25,12 +25,14 @@ use goose::{
agents::{extension::ToolInfo, extension_manager::get_parameter_names},
config::permission::PermissionLevel,
};
use serde::Deserialize;
use rmcp::model::{CallToolRequestParam, Content};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use tracing::{error, warn};
#[derive(Deserialize, utoipa::ToSchema)]
@@ -90,6 +92,32 @@ pub struct RemoveExtensionRequest {
session_id: String,
}
#[derive(Deserialize, utoipa::ToSchema)]
pub struct ReadResourceRequest {
session_id: String,
extension_name: String,
uri: String,
}
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
pub struct ReadResourceResponse {
html: String,
}
#[derive(Deserialize, utoipa::ToSchema)]
pub struct CallToolRequest {
session_id: String,
name: String,
arguments: Value,
}
#[derive(Serialize, utoipa::ToSchema)]
pub struct CallToolResponse {
content: Vec<Content>,
structured_content: Option<Value>,
is_error: bool,
}
#[utoipa::path(
post,
path = "/agent/start",
@@ -484,7 +512,7 @@ async fn update_router_tool_selector(
.update_router_tool_selector(None, Some(true))
.await
.map_err(|e| {
tracing::error!("Failed to update tool selection strategy: {}", e);
error!("Failed to update tool selection strategy: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
@@ -564,11 +592,94 @@ async fn stop_agent(
Ok(StatusCode::OK)
}
#[utoipa::path(
post,
path = "/agent/read_resource",
request_body = ReadResourceRequest,
responses(
(status = 200, description = "Resource read successfully", body = ReadResourceResponse),
(status = 401, description = "Unauthorized - invalid secret key"),
(status = 424, description = "Agent not initialized"),
(status = 404, description = "Resource not found"),
(status = 500, description = "Internal server error")
)
)]
async fn read_resource(
State(state): State<Arc<AppState>>,
Json(payload): Json<ReadResourceRequest>,
) -> Result<Json<ReadResourceResponse>, StatusCode> {
let agent = state
.get_agent_for_route(payload.session_id.clone())
.await?;
let html = agent
.extension_manager
.read_ui_resource(
&payload.uri,
&payload.extension_name,
CancellationToken::default(),
)
.await
.map_err(|_e| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(ReadResourceResponse { html }))
}
#[utoipa::path(
post,
path = "/agent/call_tool",
request_body = CallToolRequest,
responses(
(status = 200, description = "Resource read successfully", body = CallToolResponse),
(status = 401, description = "Unauthorized - invalid secret key"),
(status = 424, description = "Agent not initialized"),
(status = 404, description = "Resource not found"),
(status = 500, description = "Internal server error")
)
)]
async fn call_tool(
State(state): State<Arc<AppState>>,
Json(payload): Json<CallToolRequest>,
) -> Result<Json<CallToolResponse>, StatusCode> {
let agent = state
.get_agent_for_route(payload.session_id.clone())
.await?;
let arguments = match payload.arguments {
Value::Object(map) => Some(map),
_ => None,
};
let tool_call = CallToolRequestParam {
name: payload.name.into(),
arguments,
};
let tool_result = agent
.extension_manager
.dispatch_tool_call(tool_call, CancellationToken::default())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let result = tool_result
.result
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(CallToolResponse {
content: result.content,
structured_content: result.structured_content,
is_error: result.is_error.unwrap_or(false),
}))
}
pub fn routes(state: Arc<AppState>) -> Router {
Router::new()
.route("/agent/start", post(start_agent))
.route("/agent/resume", post(resume_agent))
.route("/agent/tools", get(get_tools))
.route("/agent/read_resource", post(read_resource))
.route("/agent/call_tool", post(call_tool))
.route("/agent/update_provider", post(update_agent_provider))
.route(
"/agent/update_router_tool_selector",
+7
View File
@@ -3,6 +3,7 @@ use axum::{
response::{IntoResponse, Response},
Json,
};
use goose::config::ConfigError;
use serde::Serialize;
use utoipa::ToSchema;
@@ -37,3 +38,9 @@ impl From<anyhow::Error> for ErrorResponse {
Self::internal(err.to_string())
}
}
impl From<ConfigError> for ErrorResponse {
fn from(err: ConfigError) -> Self {
Self::internal(err.to_string())
}
}