Skip to main content

monoloop_contracts/
dialect.rs

1//! Dialect binding reported by a Connector after open/negotiation.
2
3use serde::{Deserialize, Serialize};
4
5/// High-level dialect family (not an encoder/decoder implementation).
6#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum DialectFamily {
8    /// OpenAI Chat Completions v1 (streaming SSE) — first direct-LLM dialect.
9    OpenAiChatCompletions,
10    /// OpenAI Responses-style SSE (later dialect).
11    OpenAiResponses,
12    /// Anthropic Messages SSE.
13    AnthropicMessages,
14    /// Agent Client Protocol / JSON-RPC.
15    Acp,
16    /// Cursor ACP profile.
17    CursorAcp,
18    /// Google Antigravity (`agy`) ACP profile (stdio NDJSON / agy-acp bridge).
19    AgyAcp,
20    /// OpenAI Codex ACP profile (stdio NDJSON / codex-acp adapter).
21    CodexAcp,
22    /// Z.ai CLI headless profile: OpenAI-compatible chat message NDJSON on stdout.
23    ZaiCli,
24    /// Claude Code headless profile: `claude -p --output-format stream-json` NDJSON.
25    ClaudeCode,
26    /// Grok Build ACP/JSONL profile family tag.
27    GrokBuild,
28    /// Deterministic test dialect.
29    Test,
30    /// Extension point with bounded name.
31    Other(String),
32}
33
34/// How the dialect was selected for a connection.
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub enum DialectNegotiation {
37    /// Fixed by connector configuration / profile.
38    Fixed,
39    /// Negotiated during open/handshake and then frozen.
40    Negotiated,
41}
42
43/// Stable, bounded, versioned dialect descriptor.
44#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
45pub struct DialectDescriptor {
46    /// Dialect family.
47    pub family: DialectFamily,
48    /// Version string (e.g. `"v1"`).
49    pub version: String,
50    /// Framing (e.g. `"sse"`, `"json_rpc"`, `"jsonl"`).
51    pub framing: String,
52    /// Optional profile qualifier (e.g. `"grok_build"`).
53    pub profile: Option<String>,
54}
55
56impl DialectDescriptor {
57    /// OpenAI Chat Completions streaming SSE (direct-LLM).
58    pub fn openai_chat_completions(version: impl Into<String>) -> Self {
59        Self {
60            family: DialectFamily::OpenAiChatCompletions,
61            version: version.into(),
62            framing: "sse".into(),
63            profile: Some("openai_chat_completions".into()),
64        }
65    }
66
67    /// ACP / JSON-RPC dialect used by Grok Build.
68    pub fn acp_json_rpc(version: impl Into<String>) -> Self {
69        Self {
70            family: DialectFamily::Acp,
71            version: version.into(),
72            framing: "json_rpc".into(),
73            profile: Some("grok_build".into()),
74        }
75    }
76
77    /// Cursor Agent ACP over stdio (newline-delimited JSON-RPC).
78    pub fn cursor_acp(version: impl Into<String>) -> Self {
79        Self {
80            family: DialectFamily::CursorAcp,
81            version: version.into(),
82            framing: "ndjson".into(),
83            profile: Some("cursor".into()),
84        }
85    }
86
87    /// Antigravity / agy ACP over stdio (native or `agy-acp` bridge).
88    pub fn agy_acp(version: impl Into<String>) -> Self {
89        Self {
90            family: DialectFamily::AgyAcp,
91            version: version.into(),
92            framing: "ndjson".into(),
93            profile: Some("antigravity".into()),
94        }
95    }
96
97    /// OpenAI Codex ACP over stdio (`@agentclientprotocol/codex-acp` adapter).
98    pub fn codex_acp(version: impl Into<String>) -> Self {
99        Self {
100            family: DialectFamily::CodexAcp,
101            version: version.into(),
102            framing: "ndjson".into(),
103            profile: Some("codex".into()),
104        }
105    }
106
107    /// Z.ai CLI headless (`zai -p`): OpenAI-compatible chat messages as NDJSON lines.
108    pub fn zai_cli(version: impl Into<String>) -> Self {
109        Self {
110            family: DialectFamily::ZaiCli,
111            version: version.into(),
112            framing: "ndjson".into(),
113            profile: Some("zai".into()),
114        }
115    }
116
117    /// Claude Code headless (`claude -p --output-format stream-json --verbose`).
118    pub fn claude_code(version: impl Into<String>) -> Self {
119        Self {
120            family: DialectFamily::ClaudeCode,
121            version: version.into(),
122            framing: "ndjson".into(),
123            profile: Some("claude_code".into()),
124        }
125    }
126
127    /// Deterministic in-memory test dialect.
128    pub fn test_raw() -> Self {
129        Self {
130            family: DialectFamily::Test,
131            version: "v1".into(),
132            framing: "raw".into(),
133            profile: Some("fake".into()),
134        }
135    }
136}
137
138/// Immutable input/output dialect pair for one opened connection.
139#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
140pub struct DialectBinding {
141    /// Bytes written to the external system use this dialect.
142    pub input: DialectDescriptor,
143    /// Bytes read from the external system use this dialect.
144    pub output: DialectDescriptor,
145    /// Whether dialects were fixed or negotiated.
146    pub negotiation: DialectNegotiation,
147}
148
149impl DialectBinding {
150    /// Fixed identical input/output dialect.
151    pub fn fixed(dialect: DialectDescriptor) -> Self {
152        Self {
153            input: dialect.clone(),
154            output: dialect,
155            negotiation: DialectNegotiation::Fixed,
156        }
157    }
158
159    /// Negotiated identical input/output dialect (frozen at open).
160    pub fn negotiated(dialect: DialectDescriptor) -> Self {
161        Self {
162            input: dialect.clone(),
163            output: dialect,
164            negotiation: DialectNegotiation::Negotiated,
165        }
166    }
167}