@theokit/acp 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -7,84 +7,154 @@ import { SDKAgent } from '@theokit/sdk';
7
7
  */
8
8
 
9
9
  /**
10
- * Factory function shape: receives the ACP session id (UUID) and returns
11
- * a fresh `SDKAgent` for that session. Per D351, this enforces per-session
12
- * conversation isolation when consumers want it.
10
+ * Builds the agent that will serve one ACP session.
11
+ *
12
+ * Called once per `session/new`, with a freshly generated UUID, and awaited. Returning a NEW agent
13
+ * per call is what gives each session its own conversation state (D351); returning the same instance
14
+ * every time is the shared-state shape described on {@link AgentOrFactory}.
15
+ *
16
+ * A throw or rejection here is reported to the host as JSON-RPC `-32603`, message
17
+ * `agent factory threw: <your message>`, and no session is created.
13
18
  *
14
19
  * @public
15
20
  */
16
21
  type AgentFactory = (sessionId: string) => SDKAgent | Promise<SDKAgent>;
17
22
  /**
18
- * Acceptable shapes for `serveAcp({ agent })`:
19
- * - `AgentFactory`: per-session isolation (recommended).
20
- * - `SDKAgent`: single shared agent emits one-time stderr warning.
23
+ * What `serveAcp({ agent })` accepts.
24
+ *
25
+ * - {@link AgentFactory} — one agent per session. Recommended.
26
+ * - `SDKAgent` — one agent for the whole process. Legal, but every ACP session then appends to the
27
+ * SAME conversation; `serveAcp` writes a one-time warning through its `log` and carries on.
28
+ *
29
+ * Anything else (a plain object, `null`, a string) makes `serveAcp` fail with an
30
+ * `InvalidAgentError` before a single byte of protocol is read. `serveAcp` is `async`, so that
31
+ * arrives as a REJECTED PROMISE, never as a synchronous throw — `try { serveAcp(...) } catch` around
32
+ * the call alone catches nothing. The class is not exported either (see {@link serveAcp}); match on
33
+ * `err.name`. The duck-test is narrow: an object qualifies only when it has a string `agentId` and a
34
+ * callable `send`.
21
35
  *
22
36
  * @public
23
37
  */
24
38
  type AgentOrFactory = AgentFactory | SDKAgent;
25
39
  /**
26
- * Permission mode controlling how tool calls flow through ACP permission
27
- * round-trip (D355).
40
+ * How tool calls are gated before they execute (D355).
41
+ *
42
+ * - `"ask"` (default) — every tool call except the ones named in `trustedTools` round-trips through
43
+ * the host's `session/request_permission`. A `deny` choice, a cancelled request, a transport
44
+ * failure, or `permissionTimeoutMs` elapsing all BLOCK the call.
45
+ * - `"auto"` — no gating whatsoever: the veto plugin is never installed, so `trustedTools` AND
46
+ * `permissionTimeoutMs` are silently ignored in this mode.
47
+ * - `"deny"` — blocks every tool call without asking the host. `trustedTools` does NOT exempt
48
+ * anything here: the deny branch is evaluated before the trusted-tool check.
49
+ *
50
+ * `"ask"` and `"deny"` fail closed. When the runtime has no plugin manager to hang the veto on (a
51
+ * cloud-backed agent, for instance), the prompt is refused with `-32603` rather than executed
52
+ * ungated — a security control that cannot be installed must not silently degrade to `"auto"`.
28
53
  *
29
54
  * @public
30
55
  */
31
56
  type PermissionMode = "ask" | "auto" | "deny";
32
57
  /**
33
- * Capabilities advertised in the ACP `initialize` handshake. Users may
34
- * override any subset; defaults are loaded internally.
58
+ * Overrides for the capabilities advertised in the ACP `initialize` handshake.
59
+ *
60
+ * `loadSession` and the three `prompt` flags reach the wire; they are the only session-shaped
61
+ * capabilities `AgentCapabilities` has a slot for at `@agentclientprotocol/sdk@0.22.1`.
62
+ *
63
+ * `forkSession` and `listSessions` were removed in #350. The protocol has nowhere to advertise
64
+ * them — neither name appears in its generated schema — and neither gated anything here:
65
+ * `session/fork` is refused unconditionally and `session/list` is answered from the in-memory
66
+ * store regardless. They were accepted, typed, documented, and inert.
35
67
  *
36
68
  * @public
37
69
  */
38
70
  interface AcpCapabilities {
71
+ /** Advertise `session/load`. Default `true`. The handler needs the SDK to be able to resume that id. */
39
72
  loadSession?: boolean;
40
- forkSession?: boolean;
41
- listSessions?: boolean;
73
+ /** Prompt content the host may send. Anything disabled here is a hint to the host, not a guard. */
42
74
  prompt?: {
75
+ /** Default `false`. Audio blocks are still accepted and counted against `maxPromptBytes`. */
43
76
  audio?: boolean;
77
+ /** Default `true`. */
44
78
  embeddedContext?: boolean;
79
+ /** Default `true`. Images are accepted but only the TEXT blocks of a prompt reach the agent. */
45
80
  image?: boolean;
46
81
  };
47
82
  }
48
83
  /**
49
- * Display info for the agent in the ACP host UI.
84
+ * Display info for the agent in an ACP host UI.
85
+ *
86
+ * Sent as `InitializeResponse.agentInfo` when you supply it (#350 — it was accepted and read by
87
+ * nothing). Omitted otherwise: defaulting to this package's own metadata would label every agent as
88
+ * the adapter serving it, and a name that is confidently wrong is worse for a host to display than
89
+ * one that is absent.
50
90
  *
51
91
  * @public
52
92
  */
53
93
  interface AcpAgentInfo {
94
+ /** Machine-ish identifier, e.g. `"my-bot"`. */
54
95
  name: string;
96
+ /** Human label preferred by a UI over `name`. */
55
97
  title?: string;
98
+ /** Semver of the agent, not of this package. */
56
99
  version: string;
57
100
  }
58
101
  /**
59
- * Options for `serveAcp()`.
102
+ * Options for {@link serveAcp}. Every field but `agent` is optional and every default is listed
103
+ * below — this object is the whole configuration surface, nothing is read from env or disk.
60
104
  *
61
105
  * @public
62
106
  */
63
107
  interface AcpServerOptions {
64
- /** Single agent OR factory `(sessionId) => SDKAgent`. Required. */
108
+ /** The agent, or a per-session factory. Required; an unusable value throws before serving. */
65
109
  agent: AgentOrFactory;
66
- /** Display info advertised in `initialize`. Defaults to package metadata. */
110
+ /** Name, title and version advertised as `agentInfo` in the `initialize` handshake. */
67
111
  info?: AcpAgentInfo;
68
- /** Capability overrides for the `initialize` handshake. */
112
+ /** Handshake capability overrides. Only a subset is honoured — see {@link AcpCapabilities}. */
69
113
  capabilities?: AcpCapabilities;
70
- /** Permission mode (default `"ask"`). See D355. */
114
+ /** Default `"ask"`. Read {@link PermissionMode} before choosing: `"auto"` disables the two fields below. */
71
115
  permissionDefault?: PermissionMode;
72
- /** Timeout (ms) for `requestPermission` round-trip before vetoing (default 60_000). EC-2. */
116
+ /**
117
+ * How long to wait for the host to answer `session/request_permission` before BLOCKING the tool
118
+ * call (fail-closed, EC-2). Default `60_000` ms. Ignored unless `permissionDefault` is `"ask"`.
119
+ */
73
120
  permissionTimeoutMs?: number;
74
- /** Tool names that bypass `ask` mode. */
121
+ /**
122
+ * Tool names that skip the permission round-trip. Matched by exact tool name.
123
+ *
124
+ * Effective in `"ask"` mode ONLY: `"auto"` never installs the plugin, and `"deny"` blocks these
125
+ * too.
126
+ */
75
127
  trustedTools?: ReadonlyArray<string>;
76
- /** Cap on total prompt bytes (default 2 MiB). D360. */
128
+ /**
129
+ * Ceiling on the ACCUMULATED size of one prompt, in bytes. Default `2 * 1024 * 1024` (2 MiB), D360.
130
+ *
131
+ * Counted per block and summed: UTF-8 bytes for text, base64-DECODED bytes for image and audio,
132
+ * the URI for a resource link, the JSON form for an embedded resource. Crossing it rejects the
133
+ * whole prompt with `-32602` — see {@link PromptTooLargeError}.
134
+ */
77
135
  maxPromptBytes?: number;
78
- /** Logger — defaults to `process.stderr.write` per D359. */
136
+ /**
137
+ * Sink for this package's diagnostics — one line per call, newline NOT included.
138
+ *
139
+ * Defaults to a write to `process.stderr` (D359). Never route this to stdout: stdout carries the
140
+ * JSON-RPC frames.
141
+ */
79
142
  log?: (msg: string) => void;
80
- /** Override stdin (test seam). */
143
+ /** Override the protocol input stream. Defaults to `process.stdin`. Test seam. */
81
144
  stdin?: NodeJS.ReadableStream;
82
- /** Override stdout (test seam). */
145
+ /** Override the protocol output stream. Defaults to `process.stdout`. Test seam. */
83
146
  stdout?: NodeJS.WritableStream;
84
147
  }
85
148
  /**
86
- * Thrown by `extractPrompt` when the decoded prompt exceeds `maxPromptBytes`.
87
- * Public so callers can `instanceof` it.
149
+ * Raised when a prompt's accumulated size passes `maxPromptBytes` (D360).
150
+ *
151
+ * `size` is the running total AT the block that crossed the limit, not the size of the full prompt:
152
+ * extraction stops at the first offending block, so blocks after it are never measured.
153
+ *
154
+ * Under {@link serveAcp} this never escapes to the caller — it is caught and returned to the ACP
155
+ * host as JSON-RPC `-32602` carrying `message` verbatim (`prompt exceeds N bytes (got M)`). The
156
+ * class is exported so that text has a named origin; the extractor that throws it is not part of the
157
+ * public surface, so there is no supported call site where a consumer can `instanceof` it directly.
88
158
  *
89
159
  * @public
90
160
  */
@@ -95,22 +165,90 @@ declare class PromptTooLargeError extends Error {
95
165
  constructor(size: number, limit: number);
96
166
  }
97
167
 
168
+ /**
169
+ * Normalize `serveAcp({ agent })` into a uniform `AgentFactory` (D351).
170
+ *
171
+ * - Function input → passed through as-is. NOT duck-tested: any function is accepted, and a bad
172
+ * return value only surfaces when the first session is created.
173
+ * - SDKAgent-shaped object → wrapped in a factory that returns that same instance for every session,
174
+ * plus a one-time warning through `log` (a shared agent defeats per-session isolation).
175
+ * - Anything else → `InvalidAgentError` thrown synchronously.
176
+ *
177
+ * @internal
178
+ */
179
+
180
+ /** Thrown when `serveAcp({ agent })` is neither a function nor an object with `agentId` + `send`. */
181
+ declare class InvalidAgentError extends Error {
182
+ readonly name = "InvalidAgentError";
183
+ }
184
+
98
185
  /**
99
186
  * `serveAcp({ agent })` — top-level entry that wires the ACP server.
100
187
  *
101
188
  * Constructs `AgentSideConnection` from `@agentclientprotocol/sdk@^0.22`,
102
189
  * delegates lifecycle methods to `lifecycle.ts`, and drives the prompt
103
- * translator from `translator.ts`. Per-invocation state (D356).
190
+ * translator from `translator.ts`. All state (sessions, warnings, defaults) is
191
+ * per invocation — nothing is module-global, so two `serveAcp` calls on
192
+ * different stream pairs do not see each other (D356).
104
193
  *
105
194
  * @public
106
195
  */
107
196
 
108
197
  /**
109
- * Block on a stdio JSON-RPC ACP server until stdin closes. Returns ONLY
110
- * after every active session has been disposed (EC-1).
198
+ * Serve `options.agent` to an ACP host over JSON-RPC on stdio, blocking until the input stream ends.
199
+ *
200
+ * ```ts
201
+ * await serveAcp({ agent: (sessionId) => Agent.create({ agentId: sessionId }) });
202
+ * ```
203
+ *
204
+ * Needs `@agentclientprotocol/sdk` and `@theokit/sdk` installed (peer dependencies). It reads no
205
+ * environment variable and no config file of its own; every knob is on {@link AcpServerOptions}.
206
+ * **Do not write to stdout** from the agent or its tools — that stream carries the protocol frames.
207
+ *
208
+ * Resolves once `stdin` emits `end` or `close` AND `dispose()` has settled for every live session
209
+ * (EC-1). A `dispose()` that throws is logged and swallowed, so a bad teardown cannot strand the
210
+ * process.
211
+ *
212
+ * Rejects only when it cannot start: an `InvalidAgentError` when `agent` is neither a function nor
213
+ * an object with a string `agentId` and a callable `send`. That class is exported, so branch on
214
+ * `instanceof InvalidAgentError` rather than on `err.name` (#369). Nothing that happens afterwards
215
+ * rejects this promise — per-request failures go back to
216
+ * the host as JSON-RPC errors:
217
+ *
218
+ * | Condition | Code |
219
+ * |---|---|
220
+ * | `cwd` absent or not on disk (`session/new`, `session/load`, `session/fork`) | `-32602` |
221
+ * | `session/load` for an id already loaded in this process | `-32602` |
222
+ * | prompt with no text block, or over `maxPromptBytes` | `-32602` |
223
+ * | `session/fork` whose parent IS loaded here — deferred to v0.2 | `-32602` |
224
+ * | `session/prompt` or `session/fork` naming a session this process has not loaded | `-32001` |
225
+ * | `session/load` the SDK cannot resume (wrong host, no shared storage) | `-32001` |
226
+ * | agent factory threw | `-32603` |
227
+ * | `permissionDefault` is `"ask"`/`"deny"` and the runtime cannot enforce it | `-32603` |
228
+ * | run ended in a status ACP has no stop reason for | `-32603` |
229
+ *
230
+ * `session/fork` checks the parent BEFORE `cwd`, so a fork naming a parent this process does not
231
+ * hold answers `-32001` and never reaches either `-32602` row above. Because the session store is
232
+ * in-memory and per-process, that is the common case after a restart — `session/fork` is refused in
233
+ * every case, but reading the refusal as "always `-32602`" is wrong.
234
+ *
235
+ * A prompt that completes maps the run onto an ACP stop reason: `end_turn`, `cancelled`, `refusal`
236
+ * (safety), `max_tokens` (context or token cap), `max_turn_requests` (iteration cap).
237
+ *
238
+ * Traps worth knowing before you wire a host to this:
239
+ * - `session/authenticate` always answers success without checking anything (D350) — do not read an
240
+ * empty `authMethods` plus a successful authenticate as "the agent authorised me".
241
+ * - Only the TEXT blocks of a prompt reach the agent. Images, audio and resources are counted
242
+ * against `maxPromptBytes`, then dropped.
243
+ * - `session/cancel` aborts the session's single `AbortController`, and nothing re-arms it. Every
244
+ * later prompt on that session is issued with an already-aborted signal, so treat a cancelled
245
+ * session as spent and open a new one.
246
+ * - Of the SDK stream, only assistant text, thinking and tool calls are forwarded; `status`, `task`
247
+ * and `object_delta` messages have no ACP equivalent and are dropped silently.
248
+ * - `serveAcp` does not close `stdout` and does not call `process.exit` — the caller owns the exit.
111
249
  *
112
250
  * @public
113
251
  */
114
252
  declare function serveAcp(options: AcpServerOptions): Promise<void>;
115
253
 
116
- export { type AcpAgentInfo, type AcpCapabilities, type AcpServerOptions, type AgentFactory, type AgentOrFactory, type PermissionMode, PromptTooLargeError, serveAcp };
254
+ export { type AcpAgentInfo, type AcpCapabilities, type AcpServerOptions, type AgentFactory, type AgentOrFactory, InvalidAgentError, type PermissionMode, PromptTooLargeError, serveAcp };
package/dist/index.d.ts CHANGED
@@ -7,84 +7,154 @@ import { SDKAgent } from '@theokit/sdk';
7
7
  */
8
8
 
9
9
  /**
10
- * Factory function shape: receives the ACP session id (UUID) and returns
11
- * a fresh `SDKAgent` for that session. Per D351, this enforces per-session
12
- * conversation isolation when consumers want it.
10
+ * Builds the agent that will serve one ACP session.
11
+ *
12
+ * Called once per `session/new`, with a freshly generated UUID, and awaited. Returning a NEW agent
13
+ * per call is what gives each session its own conversation state (D351); returning the same instance
14
+ * every time is the shared-state shape described on {@link AgentOrFactory}.
15
+ *
16
+ * A throw or rejection here is reported to the host as JSON-RPC `-32603`, message
17
+ * `agent factory threw: <your message>`, and no session is created.
13
18
  *
14
19
  * @public
15
20
  */
16
21
  type AgentFactory = (sessionId: string) => SDKAgent | Promise<SDKAgent>;
17
22
  /**
18
- * Acceptable shapes for `serveAcp({ agent })`:
19
- * - `AgentFactory`: per-session isolation (recommended).
20
- * - `SDKAgent`: single shared agent emits one-time stderr warning.
23
+ * What `serveAcp({ agent })` accepts.
24
+ *
25
+ * - {@link AgentFactory} — one agent per session. Recommended.
26
+ * - `SDKAgent` — one agent for the whole process. Legal, but every ACP session then appends to the
27
+ * SAME conversation; `serveAcp` writes a one-time warning through its `log` and carries on.
28
+ *
29
+ * Anything else (a plain object, `null`, a string) makes `serveAcp` fail with an
30
+ * `InvalidAgentError` before a single byte of protocol is read. `serveAcp` is `async`, so that
31
+ * arrives as a REJECTED PROMISE, never as a synchronous throw — `try { serveAcp(...) } catch` around
32
+ * the call alone catches nothing. The class is not exported either (see {@link serveAcp}); match on
33
+ * `err.name`. The duck-test is narrow: an object qualifies only when it has a string `agentId` and a
34
+ * callable `send`.
21
35
  *
22
36
  * @public
23
37
  */
24
38
  type AgentOrFactory = AgentFactory | SDKAgent;
25
39
  /**
26
- * Permission mode controlling how tool calls flow through ACP permission
27
- * round-trip (D355).
40
+ * How tool calls are gated before they execute (D355).
41
+ *
42
+ * - `"ask"` (default) — every tool call except the ones named in `trustedTools` round-trips through
43
+ * the host's `session/request_permission`. A `deny` choice, a cancelled request, a transport
44
+ * failure, or `permissionTimeoutMs` elapsing all BLOCK the call.
45
+ * - `"auto"` — no gating whatsoever: the veto plugin is never installed, so `trustedTools` AND
46
+ * `permissionTimeoutMs` are silently ignored in this mode.
47
+ * - `"deny"` — blocks every tool call without asking the host. `trustedTools` does NOT exempt
48
+ * anything here: the deny branch is evaluated before the trusted-tool check.
49
+ *
50
+ * `"ask"` and `"deny"` fail closed. When the runtime has no plugin manager to hang the veto on (a
51
+ * cloud-backed agent, for instance), the prompt is refused with `-32603` rather than executed
52
+ * ungated — a security control that cannot be installed must not silently degrade to `"auto"`.
28
53
  *
29
54
  * @public
30
55
  */
31
56
  type PermissionMode = "ask" | "auto" | "deny";
32
57
  /**
33
- * Capabilities advertised in the ACP `initialize` handshake. Users may
34
- * override any subset; defaults are loaded internally.
58
+ * Overrides for the capabilities advertised in the ACP `initialize` handshake.
59
+ *
60
+ * `loadSession` and the three `prompt` flags reach the wire; they are the only session-shaped
61
+ * capabilities `AgentCapabilities` has a slot for at `@agentclientprotocol/sdk@0.22.1`.
62
+ *
63
+ * `forkSession` and `listSessions` were removed in #350. The protocol has nowhere to advertise
64
+ * them — neither name appears in its generated schema — and neither gated anything here:
65
+ * `session/fork` is refused unconditionally and `session/list` is answered from the in-memory
66
+ * store regardless. They were accepted, typed, documented, and inert.
35
67
  *
36
68
  * @public
37
69
  */
38
70
  interface AcpCapabilities {
71
+ /** Advertise `session/load`. Default `true`. The handler needs the SDK to be able to resume that id. */
39
72
  loadSession?: boolean;
40
- forkSession?: boolean;
41
- listSessions?: boolean;
73
+ /** Prompt content the host may send. Anything disabled here is a hint to the host, not a guard. */
42
74
  prompt?: {
75
+ /** Default `false`. Audio blocks are still accepted and counted against `maxPromptBytes`. */
43
76
  audio?: boolean;
77
+ /** Default `true`. */
44
78
  embeddedContext?: boolean;
79
+ /** Default `true`. Images are accepted but only the TEXT blocks of a prompt reach the agent. */
45
80
  image?: boolean;
46
81
  };
47
82
  }
48
83
  /**
49
- * Display info for the agent in the ACP host UI.
84
+ * Display info for the agent in an ACP host UI.
85
+ *
86
+ * Sent as `InitializeResponse.agentInfo` when you supply it (#350 — it was accepted and read by
87
+ * nothing). Omitted otherwise: defaulting to this package's own metadata would label every agent as
88
+ * the adapter serving it, and a name that is confidently wrong is worse for a host to display than
89
+ * one that is absent.
50
90
  *
51
91
  * @public
52
92
  */
53
93
  interface AcpAgentInfo {
94
+ /** Machine-ish identifier, e.g. `"my-bot"`. */
54
95
  name: string;
96
+ /** Human label preferred by a UI over `name`. */
55
97
  title?: string;
98
+ /** Semver of the agent, not of this package. */
56
99
  version: string;
57
100
  }
58
101
  /**
59
- * Options for `serveAcp()`.
102
+ * Options for {@link serveAcp}. Every field but `agent` is optional and every default is listed
103
+ * below — this object is the whole configuration surface, nothing is read from env or disk.
60
104
  *
61
105
  * @public
62
106
  */
63
107
  interface AcpServerOptions {
64
- /** Single agent OR factory `(sessionId) => SDKAgent`. Required. */
108
+ /** The agent, or a per-session factory. Required; an unusable value throws before serving. */
65
109
  agent: AgentOrFactory;
66
- /** Display info advertised in `initialize`. Defaults to package metadata. */
110
+ /** Name, title and version advertised as `agentInfo` in the `initialize` handshake. */
67
111
  info?: AcpAgentInfo;
68
- /** Capability overrides for the `initialize` handshake. */
112
+ /** Handshake capability overrides. Only a subset is honoured — see {@link AcpCapabilities}. */
69
113
  capabilities?: AcpCapabilities;
70
- /** Permission mode (default `"ask"`). See D355. */
114
+ /** Default `"ask"`. Read {@link PermissionMode} before choosing: `"auto"` disables the two fields below. */
71
115
  permissionDefault?: PermissionMode;
72
- /** Timeout (ms) for `requestPermission` round-trip before vetoing (default 60_000). EC-2. */
116
+ /**
117
+ * How long to wait for the host to answer `session/request_permission` before BLOCKING the tool
118
+ * call (fail-closed, EC-2). Default `60_000` ms. Ignored unless `permissionDefault` is `"ask"`.
119
+ */
73
120
  permissionTimeoutMs?: number;
74
- /** Tool names that bypass `ask` mode. */
121
+ /**
122
+ * Tool names that skip the permission round-trip. Matched by exact tool name.
123
+ *
124
+ * Effective in `"ask"` mode ONLY: `"auto"` never installs the plugin, and `"deny"` blocks these
125
+ * too.
126
+ */
75
127
  trustedTools?: ReadonlyArray<string>;
76
- /** Cap on total prompt bytes (default 2 MiB). D360. */
128
+ /**
129
+ * Ceiling on the ACCUMULATED size of one prompt, in bytes. Default `2 * 1024 * 1024` (2 MiB), D360.
130
+ *
131
+ * Counted per block and summed: UTF-8 bytes for text, base64-DECODED bytes for image and audio,
132
+ * the URI for a resource link, the JSON form for an embedded resource. Crossing it rejects the
133
+ * whole prompt with `-32602` — see {@link PromptTooLargeError}.
134
+ */
77
135
  maxPromptBytes?: number;
78
- /** Logger — defaults to `process.stderr.write` per D359. */
136
+ /**
137
+ * Sink for this package's diagnostics — one line per call, newline NOT included.
138
+ *
139
+ * Defaults to a write to `process.stderr` (D359). Never route this to stdout: stdout carries the
140
+ * JSON-RPC frames.
141
+ */
79
142
  log?: (msg: string) => void;
80
- /** Override stdin (test seam). */
143
+ /** Override the protocol input stream. Defaults to `process.stdin`. Test seam. */
81
144
  stdin?: NodeJS.ReadableStream;
82
- /** Override stdout (test seam). */
145
+ /** Override the protocol output stream. Defaults to `process.stdout`. Test seam. */
83
146
  stdout?: NodeJS.WritableStream;
84
147
  }
85
148
  /**
86
- * Thrown by `extractPrompt` when the decoded prompt exceeds `maxPromptBytes`.
87
- * Public so callers can `instanceof` it.
149
+ * Raised when a prompt's accumulated size passes `maxPromptBytes` (D360).
150
+ *
151
+ * `size` is the running total AT the block that crossed the limit, not the size of the full prompt:
152
+ * extraction stops at the first offending block, so blocks after it are never measured.
153
+ *
154
+ * Under {@link serveAcp} this never escapes to the caller — it is caught and returned to the ACP
155
+ * host as JSON-RPC `-32602` carrying `message` verbatim (`prompt exceeds N bytes (got M)`). The
156
+ * class is exported so that text has a named origin; the extractor that throws it is not part of the
157
+ * public surface, so there is no supported call site where a consumer can `instanceof` it directly.
88
158
  *
89
159
  * @public
90
160
  */
@@ -95,22 +165,90 @@ declare class PromptTooLargeError extends Error {
95
165
  constructor(size: number, limit: number);
96
166
  }
97
167
 
168
+ /**
169
+ * Normalize `serveAcp({ agent })` into a uniform `AgentFactory` (D351).
170
+ *
171
+ * - Function input → passed through as-is. NOT duck-tested: any function is accepted, and a bad
172
+ * return value only surfaces when the first session is created.
173
+ * - SDKAgent-shaped object → wrapped in a factory that returns that same instance for every session,
174
+ * plus a one-time warning through `log` (a shared agent defeats per-session isolation).
175
+ * - Anything else → `InvalidAgentError` thrown synchronously.
176
+ *
177
+ * @internal
178
+ */
179
+
180
+ /** Thrown when `serveAcp({ agent })` is neither a function nor an object with `agentId` + `send`. */
181
+ declare class InvalidAgentError extends Error {
182
+ readonly name = "InvalidAgentError";
183
+ }
184
+
98
185
  /**
99
186
  * `serveAcp({ agent })` — top-level entry that wires the ACP server.
100
187
  *
101
188
  * Constructs `AgentSideConnection` from `@agentclientprotocol/sdk@^0.22`,
102
189
  * delegates lifecycle methods to `lifecycle.ts`, and drives the prompt
103
- * translator from `translator.ts`. Per-invocation state (D356).
190
+ * translator from `translator.ts`. All state (sessions, warnings, defaults) is
191
+ * per invocation — nothing is module-global, so two `serveAcp` calls on
192
+ * different stream pairs do not see each other (D356).
104
193
  *
105
194
  * @public
106
195
  */
107
196
 
108
197
  /**
109
- * Block on a stdio JSON-RPC ACP server until stdin closes. Returns ONLY
110
- * after every active session has been disposed (EC-1).
198
+ * Serve `options.agent` to an ACP host over JSON-RPC on stdio, blocking until the input stream ends.
199
+ *
200
+ * ```ts
201
+ * await serveAcp({ agent: (sessionId) => Agent.create({ agentId: sessionId }) });
202
+ * ```
203
+ *
204
+ * Needs `@agentclientprotocol/sdk` and `@theokit/sdk` installed (peer dependencies). It reads no
205
+ * environment variable and no config file of its own; every knob is on {@link AcpServerOptions}.
206
+ * **Do not write to stdout** from the agent or its tools — that stream carries the protocol frames.
207
+ *
208
+ * Resolves once `stdin` emits `end` or `close` AND `dispose()` has settled for every live session
209
+ * (EC-1). A `dispose()` that throws is logged and swallowed, so a bad teardown cannot strand the
210
+ * process.
211
+ *
212
+ * Rejects only when it cannot start: an `InvalidAgentError` when `agent` is neither a function nor
213
+ * an object with a string `agentId` and a callable `send`. That class is exported, so branch on
214
+ * `instanceof InvalidAgentError` rather than on `err.name` (#369). Nothing that happens afterwards
215
+ * rejects this promise — per-request failures go back to
216
+ * the host as JSON-RPC errors:
217
+ *
218
+ * | Condition | Code |
219
+ * |---|---|
220
+ * | `cwd` absent or not on disk (`session/new`, `session/load`, `session/fork`) | `-32602` |
221
+ * | `session/load` for an id already loaded in this process | `-32602` |
222
+ * | prompt with no text block, or over `maxPromptBytes` | `-32602` |
223
+ * | `session/fork` whose parent IS loaded here — deferred to v0.2 | `-32602` |
224
+ * | `session/prompt` or `session/fork` naming a session this process has not loaded | `-32001` |
225
+ * | `session/load` the SDK cannot resume (wrong host, no shared storage) | `-32001` |
226
+ * | agent factory threw | `-32603` |
227
+ * | `permissionDefault` is `"ask"`/`"deny"` and the runtime cannot enforce it | `-32603` |
228
+ * | run ended in a status ACP has no stop reason for | `-32603` |
229
+ *
230
+ * `session/fork` checks the parent BEFORE `cwd`, so a fork naming a parent this process does not
231
+ * hold answers `-32001` and never reaches either `-32602` row above. Because the session store is
232
+ * in-memory and per-process, that is the common case after a restart — `session/fork` is refused in
233
+ * every case, but reading the refusal as "always `-32602`" is wrong.
234
+ *
235
+ * A prompt that completes maps the run onto an ACP stop reason: `end_turn`, `cancelled`, `refusal`
236
+ * (safety), `max_tokens` (context or token cap), `max_turn_requests` (iteration cap).
237
+ *
238
+ * Traps worth knowing before you wire a host to this:
239
+ * - `session/authenticate` always answers success without checking anything (D350) — do not read an
240
+ * empty `authMethods` plus a successful authenticate as "the agent authorised me".
241
+ * - Only the TEXT blocks of a prompt reach the agent. Images, audio and resources are counted
242
+ * against `maxPromptBytes`, then dropped.
243
+ * - `session/cancel` aborts the session's single `AbortController`, and nothing re-arms it. Every
244
+ * later prompt on that session is issued with an already-aborted signal, so treat a cancelled
245
+ * session as spent and open a new one.
246
+ * - Of the SDK stream, only assistant text, thinking and tool calls are forwarded; `status`, `task`
247
+ * and `object_delta` messages have no ACP equivalent and are dropped silently.
248
+ * - `serveAcp` does not close `stdout` and does not call `process.exit` — the caller owns the exit.
111
249
  *
112
250
  * @public
113
251
  */
114
252
  declare function serveAcp(options: AcpServerOptions): Promise<void>;
115
253
 
116
- export { type AcpAgentInfo, type AcpCapabilities, type AcpServerOptions, type AgentFactory, type AgentOrFactory, type PermissionMode, PromptTooLargeError, serveAcp };
254
+ export { type AcpAgentInfo, type AcpCapabilities, type AcpServerOptions, type AgentFactory, type AgentOrFactory, InvalidAgentError, type PermissionMode, PromptTooLargeError, serveAcp };