Skip to main content

typesec_agent/interop/
dialects.rs

1//! Named dialect registry: one dispatch table shared by every binding layer
2//! (Python, WASM, the CLI) so dialect names can't drift between them.
3
4use serde_json::Value;
5
6use super::call::{GuardedToolCall, InteropError, ToolCallRequest};
7use super::{anthropic, langchain, mcp, openai, pydantic_ai};
8
9/// Parse a framework payload into normalized tool calls.
10pub type ParseFn = fn(&Value) -> Result<Vec<ToolCallRequest>, InteropError>;
11/// Render a denied call in the framework's feedback shape.
12pub type DenialFn = fn(&GuardedToolCall) -> Option<Value>;
13/// Filter a tool-definition list by name predicate.
14pub type FilterFn = fn(&Value, &dyn Fn(&str) -> bool) -> Value;
15
16/// The codec functions for one framework dialect.
17pub struct Dialect {
18    /// Canonical dialect name (`openai`, `anthropic`, `langchain`,
19    /// `pydantic-ai`, `mcp`).
20    pub name: &'static str,
21    /// Parse the framework's tool-call payload into normalized requests.
22    pub parse: ParseFn,
23    /// Render a denied call in the framework's feedback shape.
24    pub denial: DenialFn,
25    /// Filter a tool-definition list by name predicate.
26    pub filter: FilterFn,
27}
28
29/// Every supported dialect.
30pub const DIALECTS: &[Dialect] = &[
31    Dialect {
32        name: openai::DIALECT,
33        parse: openai::parse_tool_calls,
34        denial: openai::denial,
35        filter: openai::filter_tools,
36    },
37    Dialect {
38        name: anthropic::DIALECT,
39        parse: anthropic::parse_tool_calls,
40        denial: anthropic::denial,
41        filter: anthropic::filter_tools,
42    },
43    Dialect {
44        name: langchain::DIALECT,
45        parse: langchain::parse_tool_calls,
46        denial: langchain::denial,
47        filter: langchain::filter_tools,
48    },
49    Dialect {
50        name: pydantic_ai::DIALECT,
51        parse: pydantic_ai::parse_tool_calls,
52        denial: pydantic_ai::denial,
53        filter: pydantic_ai::filter_tools,
54    },
55    Dialect {
56        name: mcp::DIALECT,
57        parse: mcp::parse_tool_calls,
58        denial: mcp::denial,
59        filter: mcp::filter_tools,
60    },
61];
62
63/// Look up a dialect by name (`pydantic_ai` is accepted for `pydantic-ai`).
64pub fn dialect(name: &str) -> Option<&'static Dialect> {
65    let canonical = if name == "pydantic_ai" {
66        "pydantic-ai"
67    } else {
68        name
69    };
70    DIALECTS.iter().find(|dialect| dialect.name == canonical)
71}
72
73/// The message used when a dialect name is unknown.
74pub fn unknown_dialect_message(name: &str) -> String {
75    format!("unknown dialect '{name}' (expected openai, anthropic, langchain, pydantic-ai, or mcp)")
76}