1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//! The Agent Client Protocol wire contract — the ONLY module naming ACP's method strings, its
//! protocol version, and the client capabilities this adapter advertises.
//!
//! Everything ACP-specific about the channel is named here (and consumed by [`crate::translate`],
//! [`crate::harness`], and [`crate::session`]). Nothing above the adapter references these
//! strings; that is the `no-harness-in-platform` invariant the neutral seam exists to preserve.
//!
//! The method names are **not** re-spelled here: they are taken from the published
//! `agent-client-protocol-schema` constants ([`AGENT_METHOD_NAMES`], [`CLIENT_METHOD_NAMES`],
//! [`PROTOCOL_LEVEL_METHOD_NAMES`]), so a protocol rename in a future schema release is a compile
//! error in this file rather than a silent wire mismatch. A hand-written mirror of a moving
//! external protocol is a fossil waiting to happen.
//!
//! # The client capabilities we advertise, and why they are all `false`
//!
//! ACP lets a client offer `fs/read_text_file`, `fs/write_text_file`, and the `terminal/*` family
//! back to the agent. This adapter advertises **none** of them. That is a ruling, not an omission:
//!
//! - The `fs/*` callbacks exist so an **editor** can serve the agent its *unsaved buffers* — the
//! file as the user currently sees it, which is not what is on disk. An Aion worker has no
//! buffers and no editor state; the agent runs as a child process with direct filesystem access
//! under the session `cwd`, so it reads and writes the disk itself. Proxying disk IO back
//! through the worker would add a hop that changes nothing.
//! - The `terminal/*` family exists so an editor can host the agent's shell in an embedded panel.
//! A worker has no panel; the agent spawns its own processes.
//! - ACP v2 removes both surfaces in favour of MCP, which confirms the direction. (Checked against
//! the pinned schema crate's own draft `v2` module: its `ClientMethodNames` lists neither
//! `fs/read_text_file`, `fs/write_text_file`, nor any `terminal/*` method, and carries
//! MCP-over-ACP methods in their place.)
//!
//! Advertising a capability we cannot serve would be worse than advertising none: the agent would
//! call it and block. But a *non-advertised* method can still arrive from a non-conforming agent,
//! and an unanswered peer request wedges the run forever — so [`crate::session`] answers every
//! such request with the `-32601` [`method_not_found_code`] and keeps the run alive.
//!
//! [`method_not_found_code`]: crate::protocol::method_not_found_code
//!
//! [`AGENT_METHOD_NAMES`]: agent_client_protocol_schema::v1::AGENT_METHOD_NAMES
//! [`CLIENT_METHOD_NAMES`]: agent_client_protocol_schema::v1::CLIENT_METHOD_NAMES
//! [`PROTOCOL_LEVEL_METHOD_NAMES`]: agent_client_protocol_schema::v1::PROTOCOL_LEVEL_METHOD_NAMES
use ProtocolVersion;
use ;
use ;
/// The ACP protocol version this adapter speaks: the integer `1`.
///
/// ACP versions are a `u16` bumped only for breaking changes (non-breaking additions arrive as
/// capabilities) — never a semver string, and unrelated to this crate's own version. The client
/// sends its newest supported version; the agent echoes it when supported, or answers with its own
/// latest. A client that cannot speak the echoed version MUST disconnect, which is what
/// [`gate_protocol_version`] enforces.
///
/// [`gate_protocol_version`]: crate::translate::gate_protocol_version
pub const PROTOCOL_VERSION: ProtocolVersion = V1;
/// The `initialize` method — the versioned capability handshake, sent once before anything else.
pub const METHOD_INITIALIZE: &str = AGENT_METHOD_NAMES.initialize;
/// The `session/new` method — mints the session every later request is scoped to.
pub const METHOD_SESSION_NEW: &str = AGENT_METHOD_NAMES.session_new;
/// The `session/load` method — resumes an existing session by id instead of minting a new one.
///
/// Gated on the agent's `initialize` capability `loadSession`: an agent that does not advertise
/// it is never sent this method, and a job that named a session to resume against such an agent
/// is refused rather than silently started fresh.
pub const METHOD_SESSION_LOAD: &str = AGENT_METHOD_NAMES.session_load;
/// The `session/prompt` method — the ONE long-running request whose Response is the terminal
/// result. It stays open for the whole turn.
pub const METHOD_SESSION_PROMPT: &str = AGENT_METHOD_NAMES.session_prompt;
/// The `session/cancel` **notification** (never a request) — the client's cancellation signal.
///
/// The agent must stop, may emit final `session/update`s, and MUST then answer the still-open
/// `session/prompt` with `stopReason: "cancelled"` — never a JSON-RPC error.
pub const METHOD_SESSION_CANCEL: &str = AGENT_METHOD_NAMES.session_cancel;
/// `session/set_config_option` — set one advertised configuration option (the
/// model picker's write side). A request with a response, unlike the cancel
/// notification above: the agent answers with the full option set as it now
/// stands.
pub const METHOD_SESSION_SET_CONFIG_OPTION: &str = AGENT_METHOD_NAMES.session_set_config_option;
/// The `session/update` **notification** — the agent's transcript stream.
pub const METHOD_SESSION_UPDATE: &str = CLIENT_METHOD_NAMES.session_update;
/// The `session/request_permission` request — the agent asking the client to authorise a tool call.
///
/// It MUST be answered or the run wedges: see [`crate::permission`].
pub const METHOD_SESSION_REQUEST_PERMISSION: &str = CLIENT_METHOD_NAMES.session_request_permission;
/// The `fs/read_text_file` request — an unadvertised capability this adapter refuses.
pub const METHOD_FS_READ_TEXT_FILE: &str = CLIENT_METHOD_NAMES.fs_read_text_file;
/// The `fs/write_text_file` request — an unadvertised capability this adapter refuses.
pub const METHOD_FS_WRITE_TEXT_FILE: &str = CLIENT_METHOD_NAMES.fs_write_text_file;
/// The `$/cancel_request` notification either side may send to cancel one in-flight request.
///
/// Notifications in the `$/` namespace are implementation-dependent and may be ignored. This
/// adapter answers every inbound peer request synchronously, so it never has one pending to
/// cancel; the notification is recorded on the transcript rather than acted on.
pub const METHOD_CANCEL_REQUEST: &str = PROTOCOL_LEVEL_METHOD_NAMES.cancel_request;
/// The JSON-RPC code answering a method this adapter did not advertise (`-32601`).
///
/// The honest answer to an `fs/*` or `terminal/*` request from an agent we told `false`: the method
/// does not exist on this peer. It is an answer, which is what matters — silence would block the
/// agent forever.
/// The ACP code (`-32000`) an agent returns to say authentication is required first.
///
/// This is how an agent asks the client to call `authenticate`. An unattended Aion activity cannot
/// run an interactive auth flow, so the adapter surfaces it as a harness failure naming the
/// authentication methods the agent advertised, rather than guessing at credentials.
/// The JSON-RPC code (`-32800`) for a request cancelled before it completed.
///
/// A conforming agent answers a cancelled `session/prompt` with `stopReason: "cancelled"` and
/// never with this error, but the code is recognised so a non-conforming agent's cancellation is
/// still reported as a cancellation rather than as an opaque failure.
/// Widens an ACP [`ErrorCode`] to the `i64` the JSON-RPC envelope carries.
///
/// The number itself comes from the schema crate's own `ErrorCode -> i32` mapping, so no error
/// code is ever respelled in this workspace.
/// The one `-32601` response this adapter uses to refuse a peer request for a capability it does not
/// advertise.
///
/// Built here rather than at each call site so the refusal an agent reads is identical whether the
/// request arrived during the pre-prompt handshake ([`crate::session`]) or mid-turn
/// ([`crate::pump`]) — two spellings of the same refusal would be two things to keep in step.
/// The `name` this adapter identifies itself by in the `initialize` handshake.
pub const CLIENT_NAME: &str = "aion-integration-acp";
/// The `agent_role` label stamped on every event a session produces.
///
/// ACP has no sub-agent attribution: one session is one agent, and no frame carries a producer
/// identity. The label is therefore a constant rather than a value read off the wire — inventing a
/// per-event role would be fabrication. For the same reason every event carries the nil
/// [`uuid::Uuid`] as its `agent_id`.
pub const AGENT_ROLE: &str = "agent";
/// The prefix every cancellation-derived terminal error message starts with.
///
/// `cancelled` is a distinct terminal outcome, not an ordinary failure: an operator who cancelled
/// a run must be able to tell it apart from a refusal or a token-limit stop. The neutral
/// [`HarnessError`] taxonomy is `#[non_exhaustive]` and owned by `aion-integrations`, so this
/// adapter cannot add a variant for it; the discriminating signal is this stable, documented
/// prefix, which callers may match on.
///
/// [`HarnessError`]: aion_integrations::HarnessError
pub const CANCELLED_DETAIL_PREFIX: &str = "the prompt turn was cancelled";
/// The `clientInfo` this adapter sends in `initialize`.
///
/// The version is this crate's own `CARGO_PKG_VERSION`, which is unrelated to
/// [`PROTOCOL_VERSION`]; agents log it when diagnosing a client, so it must be the real build.
/// The `clientCapabilities` this adapter advertises: `fs.readTextFile: false`,
/// `fs.writeTextFile: false`, `terminal: false`.
///
/// See the module docs for why all three are `false`. They are written out explicitly rather than
/// left to [`ClientCapabilities::default`] so the advertisement is a stated decision in the code,
/// not a default that could quietly change under us.