Files
zesdex/src/dto/chat/tool.rs
T

63 lines
2.4 KiB
Rust
Raw Normal View History

//! Tool-call DTOs embedded in assistant chat messages.
//!
//! Flow: provider response/stream carries `tool_calls` on an assistant
//! message → deserialized into `ToolCall`/`ToolFunction` → harness resolves
//! `function.name` against `all_tools()` and runs it with
//! `sanitize_tool_arguments(function.arguments)`.
//!
//! Why: kept separate from `dto::provider` because tool calls are a property
//! of a chat *message*, not of the request/response envelope.
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// A single tool-call request emitted by the model in an assistant message.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
#[serde(rename = "type")]
pub type_: String,
pub function: ToolFunction,
}
/// The function name and raw arguments payload for a `ToolCall`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolFunction {
pub name: String,
pub arguments: Value,
}
/// Normalize tool-call arguments into a JSON object/value.
///
/// Flow: some providers send `arguments` as a JSON-encoded string rather
/// than a nested object; if `args` is a string, attempt to parse it as
/// JSON. Objects and other value types pass through unchanged.
///
/// Security: on parse failure we wrap the raw string in `{ "_raw": "..." }`
/// instead of passing it through as a raw string, so tools that expect a
/// JSON object (via `args.get("key")`) get `None` rather than unexpectedly
/// receiving a plain string value.
///
/// Return: the parsed `Value`, or a wrapper object on parse failure.
pub fn sanitize_tool_arguments(args: &Value) -> Value {
match args {
Value::String(s) => {
match serde_json::from_str::<Value>(s) {
Ok(v) => v,
Err(e) => {
tracing::error!(
"tool argument is a JSON string but failed to parse: {}. \
Wrapping in object to prevent tool misbehaviour. Raw was: {}",
e, s.chars().take(200).collect::<String>(),
);
// Wrap in a safe object so tools don't receive a raw
// string that could be misinterpreted as an object key.
serde_json::json!({"_raw": s, "_parse_error": e.to_string()})
}
}
}
obj @ Value::Object(_) => obj.clone(),
other => other.clone(),
}
}