@theokit/acp 3.0.2 → 4.0.1-next.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/CHANGELOG.md +176 -0
- package/LICENSE +2 -2
- package/README.md +15 -2
- package/dist/index.cjs +58 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -30
- package/dist/index.d.ts +168 -30
- package/dist/index.js +58 -35
- package/dist/index.js.map +1 -1
- package/package.json +19 -15
- package/registry/README.md +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -7,84 +7,154 @@ import { SDKAgent } from '@theokit/sdk';
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
*
|
|
27
|
-
*
|
|
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
|
-
*
|
|
34
|
-
*
|
|
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
|
-
|
|
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
|
|
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 `
|
|
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
|
-
/**
|
|
108
|
+
/** The agent, or a per-session factory. Required; an unusable value throws before serving. */
|
|
65
109
|
agent: AgentOrFactory;
|
|
66
|
-
/**
|
|
110
|
+
/** Name, title and version advertised as `agentInfo` in the `initialize` handshake. */
|
|
67
111
|
info?: AcpAgentInfo;
|
|
68
|
-
/**
|
|
112
|
+
/** Handshake capability overrides. Only a subset is honoured — see {@link AcpCapabilities}. */
|
|
69
113
|
capabilities?: AcpCapabilities;
|
|
70
|
-
/**
|
|
114
|
+
/** Default `"ask"`. Read {@link PermissionMode} before choosing: `"auto"` disables the two fields below. */
|
|
71
115
|
permissionDefault?: PermissionMode;
|
|
72
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
143
|
+
/** Override the protocol input stream. Defaults to `process.stdin`. Test seam. */
|
|
81
144
|
stdin?: NodeJS.ReadableStream;
|
|
82
|
-
/** Override stdout
|
|
145
|
+
/** Override the protocol output stream. Defaults to `process.stdout`. Test seam. */
|
|
83
146
|
stdout?: NodeJS.WritableStream;
|
|
84
147
|
}
|
|
85
148
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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`.
|
|
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
|
-
*
|
|
110
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
*
|
|
27
|
-
*
|
|
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
|
-
*
|
|
34
|
-
*
|
|
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
|
-
|
|
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
|
|
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 `
|
|
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
|
-
/**
|
|
108
|
+
/** The agent, or a per-session factory. Required; an unusable value throws before serving. */
|
|
65
109
|
agent: AgentOrFactory;
|
|
66
|
-
/**
|
|
110
|
+
/** Name, title and version advertised as `agentInfo` in the `initialize` handshake. */
|
|
67
111
|
info?: AcpAgentInfo;
|
|
68
|
-
/**
|
|
112
|
+
/** Handshake capability overrides. Only a subset is honoured — see {@link AcpCapabilities}. */
|
|
69
113
|
capabilities?: AcpCapabilities;
|
|
70
|
-
/**
|
|
114
|
+
/** Default `"ask"`. Read {@link PermissionMode} before choosing: `"auto"` disables the two fields below. */
|
|
71
115
|
permissionDefault?: PermissionMode;
|
|
72
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
143
|
+
/** Override the protocol input stream. Defaults to `process.stdin`. Test seam. */
|
|
81
144
|
stdin?: NodeJS.ReadableStream;
|
|
82
|
-
/** Override stdout
|
|
145
|
+
/** Override the protocol output stream. Defaults to `process.stdout`. Test seam. */
|
|
83
146
|
stdout?: NodeJS.WritableStream;
|
|
84
147
|
}
|
|
85
148
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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`.
|
|
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
|
-
*
|
|
110
|
-
*
|
|
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 };
|