use schemars::JsonSchema; use serde::Deserialize; use serde::Deserializer; use serde::Serialize; use serde_json::Value as JsonValue; use ts_rs::TS; #[derive(Debug, Clone, Serialize, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct DynamicToolSpec { pub name: String, pub description: String, pub input_schema: JsonValue, #[serde(default)] pub defer_loading: bool, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct DynamicToolCallRequest { pub call_id: String, pub turn_id: String, pub tool: String, pub arguments: JsonValue, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct DynamicToolResponse { pub content_items: Vec, pub success: bool, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] pub enum DynamicToolCallOutputContentItem { #[serde(rename_all = "camelCase")] InputText { text: String }, #[serde(rename_all = "camelCase")] InputImage { image_url: String }, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct DynamicToolSpecDe { name: String, description: String, input_schema: JsonValue, defer_loading: Option, expose_to_context: Option, } impl<'de> Deserialize<'de> for DynamicToolSpec { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let DynamicToolSpecDe { name, description, input_schema, defer_loading, expose_to_context, } = DynamicToolSpecDe::deserialize(deserializer)?; Ok(Self { name, description, input_schema, defer_loading: defer_loading .unwrap_or_else(|| expose_to_context.map(|visible| !visible).unwrap_or(false)), }) } } #[cfg(test)] mod tests { use super::DynamicToolSpec; use pretty_assertions::assert_eq; use serde_json::json; #[test] fn dynamic_tool_spec_deserializes_defer_loading() { let value = json!({ "name": "lookup_ticket", "description": "Fetch a ticket", "inputSchema": { "type": "object", "properties": { "id": { "type": "string" } } }, "deferLoading": true, }); let actual: DynamicToolSpec = serde_json::from_value(value).expect("deserialize"); assert_eq!( actual, DynamicToolSpec { name: "lookup_ticket".to_string(), description: "Fetch a ticket".to_string(), input_schema: json!({ "type": "object", "properties": { "id": { "type": "string" } } }), defer_loading: true, } ); } #[test] fn dynamic_tool_spec_legacy_expose_to_context_inverts_to_defer_loading() { let value = json!({ "name": "lookup_ticket", "description": "Fetch a ticket", "inputSchema": { "type": "object", "properties": {} }, "exposeToContext": false, }); let actual: DynamicToolSpec = serde_json::from_value(value).expect("deserialize"); assert!(actual.defer_loading); } }