Skip to main content

typesec_agent/interop/
anthropic.rs

1//! Anthropic Messages dialect: `tool_use` content blocks in, `tool_result`
2//! blocks with `is_error: true` out.
3
4use serde_json::{Value, json};
5
6use super::call::{GuardedToolCall, InteropError, ToolCallRequest};
7use super::wire;
8
9/// Dialect name used in error messages and the Python bindings.
10pub const DIALECT: &str = "anthropic";
11
12/// Parse `tool_use` blocks from an Anthropic message (`{"content": [...]}`)
13/// or a bare content-block array. Non-`tool_use` blocks (text, thinking) are
14/// skipped.
15pub fn parse_tool_calls(payload: &Value) -> Result<Vec<ToolCallRequest>, InteropError> {
16    wire::call_array(payload, "content", DIALECT)?
17        .iter()
18        .filter(|block| block.get("type").and_then(Value::as_str) == Some("tool_use"))
19        .map(parse_block)
20        .collect()
21}
22
23fn parse_block(block: &Value) -> Result<ToolCallRequest, InteropError> {
24    let name = wire::required_str(block, "name", DIALECT)?;
25    let arguments = wire::parse_arguments(block.get("input"), DIALECT, name)?;
26    let mut request = ToolCallRequest::new(name, arguments);
27    if let Some(id) = wire::optional_str(block, "id") {
28        request = request.with_call_id(id);
29    }
30    Ok(request)
31}
32
33/// Filter a request's `tools` array (`[{"name", "description",
34/// "input_schema"}]`) down to the definitions `keep` accepts.
35pub fn filter_tools(tools: &Value, keep: &dyn Fn(&str) -> bool) -> Value {
36    wire::filter_tool_array(tools, |item| wire::optional_str(item, "name"), keep)
37}
38
39/// Render a denied/undecided call as the `tool_result` content block the
40/// follow-up user message expects, flagged `is_error` so the model treats it
41/// as a failure. Returns `None` for allowed calls.
42pub fn denial(call: &GuardedToolCall) -> Option<Value> {
43    call.denial_message().map(|content| {
44        json!({
45            "type": "tool_result",
46            "tool_use_id": call.request.call_id.clone().unwrap_or_default(),
47            "content": content,
48            "is_error": true,
49        })
50    })
51}