This extends dynamic_tool_calls to allow us to hide a tool from the model context but still use it as part of the general tool calling runtime (for ex from js_repl/code_mode)
131 lines
3.6 KiB
Rust
131 lines
3.6 KiB
Rust
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<DynamicToolCallOutputContentItem>,
|
|
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<bool>,
|
|
expose_to_context: Option<bool>,
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for DynamicToolSpec {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
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);
|
|
}
|
|
}
|