@noetic-tools/types 3.2.0 → 3.2.2
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/README.md +3 -3
- package/dist/errors/noetic-error.js +7 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/schemas/workflow.d.ts +131 -61
- package/dist/schemas/workflow.js +129 -76
- package/dist/types/acp.d.ts +525 -0
- package/dist/types/acp.js +101 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/context-harness.d.ts +172 -0
- package/dist/types/context.d.ts +7 -174
- package/dist/types/error.d.ts +5 -4
- package/dist/types/harness-result.d.ts +6 -0
- package/dist/types/observability.d.ts +2 -1
- package/dist/types/output-codec.d.ts +6 -6
- package/dist/types/output-codec.js +4 -4
- package/dist/types/runtime.d.ts +32 -11
- package/dist/types/steering.d.ts +2 -1
- package/dist/types/step.d.ts +68 -53
- package/dist/types/tool-context.d.ts +0 -7
- package/dist/util/message-helpers.js +8 -1
- package/package.json +2 -1
- package/dist/deprecated.d.ts +0 -39
- package/dist/types/sub-harness.d.ts +0 -273
- package/dist/types/sub-harness.js +0 -106
- /package/dist/{deprecated.js → types/context-harness.js} +0 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Client Protocol (ACP) contract.
|
|
3
|
+
*
|
|
4
|
+
* Noetic drives external coding agents — Claude Code, Codex, Gemini CLI, any
|
|
5
|
+
* ACP-speaking binary — as an **ACP Client**. The agent is a JSON-RPC 2.0 peer
|
|
6
|
+
* reached over a duplex byte stream, so there is no vendor SDK per agent and no
|
|
7
|
+
* closed set of supported agents.
|
|
8
|
+
*
|
|
9
|
+
* This file is the direct analogue of the `ContextLayer` contract: defined in
|
|
10
|
+
* the foundation package so both `@noetic-tools/core` (which executes ACP steps)
|
|
11
|
+
* and `@noetic-tools/acp` (which implements the protocol) depend on it without
|
|
12
|
+
* forming a cycle. Core calls only the methods declared here — the ACP library's
|
|
13
|
+
* `ClientSideConnection` never enters core's dependency graph.
|
|
14
|
+
*
|
|
15
|
+
* Protocol types are re-used verbatim from `@zed-industries/agent-client-protocol`
|
|
16
|
+
* (spec version 1) rather than mirrored, so the wire surface cannot drift from
|
|
17
|
+
* the specification.
|
|
18
|
+
*
|
|
19
|
+
* @see https://agentclientprotocol.com/
|
|
20
|
+
*/
|
|
21
|
+
import type * as acp from '@zed-industries/agent-client-protocol';
|
|
22
|
+
import type { TokenUsage } from './common.js';
|
|
23
|
+
import type { Context } from './context.js';
|
|
24
|
+
import type { FsAdapter } from './fs-adapter.js';
|
|
25
|
+
import type { Item } from './items.js';
|
|
26
|
+
import type { ShellAdapter } from './shell-adapter.js';
|
|
27
|
+
/**
|
|
28
|
+
* The single step kind backing every ACP agent. Unlike the closed harness enum
|
|
29
|
+
* it replaces, the *agent* is identified by a free-form `agentId` on the
|
|
30
|
+
* adapter — so adding an agent never touches core or the published JSON Schema.
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export declare const ACP_AGENT_STEP_KIND = "acp-agent";
|
|
34
|
+
/**
|
|
35
|
+
* A bidirectional byte stream to an ACP agent — the substrate JSON-RPC frames
|
|
36
|
+
* ride on.
|
|
37
|
+
*
|
|
38
|
+
* This is deliberately *not* built on {@link SubprocessAdapter}: that contract
|
|
39
|
+
* returns a durable handle (`id`/`status`/`metadata`) with no attached stdio,
|
|
40
|
+
* and {@link ShellAdapter} is one-shot. ACP needs a long-lived duplex pipe.
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
export interface AcpTransport {
|
|
44
|
+
/** Frames arriving from the agent. */
|
|
45
|
+
readonly readable: ReadableStream<Uint8Array>;
|
|
46
|
+
/** Frames sent to the agent. */
|
|
47
|
+
readonly writable: WritableStream<Uint8Array>;
|
|
48
|
+
/** Tear the stream down and release the underlying resource (process, socket, …). */
|
|
49
|
+
close(): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/** @public Options a transport factory receives when a step opens a connection. */
|
|
52
|
+
export interface AcpTransportOptions {
|
|
53
|
+
/** Working directory the agent process should start in. */
|
|
54
|
+
cwd: string;
|
|
55
|
+
/** Environment overrides layered onto the host environment. */
|
|
56
|
+
env?: Record<string, string | undefined>;
|
|
57
|
+
/** Aborting this signal closes the transport. */
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Opens a transport on demand. Local agents use a stdio transport over a child
|
|
62
|
+
* process; tests use an in-memory loopback; remote agents can use anything that
|
|
63
|
+
* yields a duplex byte stream.
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
66
|
+
export type AcpTransportFactory = (opts: AcpTransportOptions) => Promise<AcpTransport>;
|
|
67
|
+
/** @public How a permission request was resolved. */
|
|
68
|
+
export declare const AcpPermissionDecision: {
|
|
69
|
+
readonly Allow: 'allow';
|
|
70
|
+
readonly Deny: 'deny';
|
|
71
|
+
readonly Cancel: 'cancel';
|
|
72
|
+
};
|
|
73
|
+
/** @public Union of permission decisions. */
|
|
74
|
+
export type AcpPermissionDecision = (typeof AcpPermissionDecision)[keyof typeof AcpPermissionDecision];
|
|
75
|
+
/**
|
|
76
|
+
* The resolved answer to an ACP `session/request_permission` call. The client
|
|
77
|
+
* translates it into the concrete {@link acp.PermissionOption} the agent
|
|
78
|
+
* offered — `optionId` pins an exact option when the resolver picked one.
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
export interface AcpPermissionOutcome {
|
|
82
|
+
decision: AcpPermissionDecision;
|
|
83
|
+
/** Exact option id to select. When omitted the client picks by `decision`. */
|
|
84
|
+
optionId?: string;
|
|
85
|
+
/** Why this decision was made — surfaced on framework events and the steering ledger. */
|
|
86
|
+
reason?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Matches a tool call the agent wants to run. An omitted field matches anything;
|
|
90
|
+
* all present fields must match.
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
93
|
+
export interface AcpPermissionRule {
|
|
94
|
+
/** ACP tool classification (`read`, `edit`, `execute`, …). */
|
|
95
|
+
kind?: acp.ToolKind;
|
|
96
|
+
/** Matches the tool call title — substring (case-insensitive) or pattern. */
|
|
97
|
+
title?: string | RegExp;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Declarative permission policy evaluated before the steering pipeline and the
|
|
101
|
+
* `onPermissionRequest` handler.
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
export interface AcpPermissionPolicy {
|
|
105
|
+
/**
|
|
106
|
+
* Decision when no rule matches and no later resolver decides. Defaults to
|
|
107
|
+
* `'deny'` — an unattended agent must not get blanket approval by omission.
|
|
108
|
+
*/
|
|
109
|
+
default?: AcpPermissionDecision;
|
|
110
|
+
/** Rules granting permission. Evaluated before {@link deny}. */
|
|
111
|
+
allow?: ReadonlyArray<AcpPermissionRule>;
|
|
112
|
+
/** Rules refusing permission. */
|
|
113
|
+
deny?: ReadonlyArray<AcpPermissionRule>;
|
|
114
|
+
/**
|
|
115
|
+
* Prefer the agent's `allow_always` / `reject_always` options over the
|
|
116
|
+
* `*_once` variants, so a decision persists for the rest of the session.
|
|
117
|
+
*/
|
|
118
|
+
persist?: boolean;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Async escape hatch for decisions a declarative policy cannot express —
|
|
122
|
+
* human-in-the-loop approval, a remote policy service, an LLM judge.
|
|
123
|
+
*
|
|
124
|
+
* Receives the executing {@link Context} because reaching a human almost always
|
|
125
|
+
* means reaching a channel: `ctx.send` the request out, `ctx.recv` the decision
|
|
126
|
+
* back. Without it a handler could only answer from what it closed over.
|
|
127
|
+
* @public
|
|
128
|
+
*/
|
|
129
|
+
export type AcpPermissionHandler = (request: acp.RequestPermissionRequest, ctx: Context, info: AcpPermissionRequestInfo) => Promise<AcpPermissionOutcome>;
|
|
130
|
+
/**
|
|
131
|
+
* Which agent and step raised a permission request. The protocol request says
|
|
132
|
+
* what the agent wants to do but not who is asking, and a human answering
|
|
133
|
+
* needs both to make sense of the prompt.
|
|
134
|
+
* @public
|
|
135
|
+
*/
|
|
136
|
+
export interface AcpPermissionRequestInfo {
|
|
137
|
+
/** `agentId` of the adapter whose turn raised the request. */
|
|
138
|
+
agentId: string;
|
|
139
|
+
/** Id of the step whose turn raised it. */
|
|
140
|
+
stepId: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The handler as the protocol client sees it — already bound to the executing
|
|
144
|
+
* context by the runtime. Keeps `Context` out of the client, which only needs
|
|
145
|
+
* to ask the question and get an answer.
|
|
146
|
+
* @public
|
|
147
|
+
*/
|
|
148
|
+
export type AcpBoundPermissionHandler = (request: acp.RequestPermissionRequest) => Promise<AcpPermissionOutcome>;
|
|
149
|
+
/**
|
|
150
|
+
* The steering tier of permission resolution. Returning `undefined` abstains,
|
|
151
|
+
* handing the decision to the next tier. The runtime wires this to the same
|
|
152
|
+
* `beforeToolCall` pipeline that governs first-party tool calls, so one rule set
|
|
153
|
+
* covers both.
|
|
154
|
+
* @public
|
|
155
|
+
*/
|
|
156
|
+
export type AcpPermissionSteerer = (request: acp.RequestPermissionRequest) => Promise<AcpPermissionOutcome | undefined>;
|
|
157
|
+
/**
|
|
158
|
+
* Which ACP client capabilities to advertise during `initialize`. Every flag
|
|
159
|
+
* defaults to enabled when the host can back it; setting one to `false`
|
|
160
|
+
* withdraws it, so the agent never attempts the call.
|
|
161
|
+
* @public
|
|
162
|
+
*/
|
|
163
|
+
export interface AcpClientCapabilityConfig {
|
|
164
|
+
/** Serve `fs/read_text_file` from the host's {@link FsAdapter}. */
|
|
165
|
+
readTextFile?: boolean;
|
|
166
|
+
/** Serve `fs/write_text_file` from the host's {@link FsAdapter}. */
|
|
167
|
+
writeTextFile?: boolean;
|
|
168
|
+
/** Serve the `terminal/*` family from the host's {@link ShellAdapter}. */
|
|
169
|
+
terminal?: boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Directories the agent may reach in addition to the session working
|
|
172
|
+
* directory, which is always allowed. Use for a monorepo sibling, a shared
|
|
173
|
+
* cache, or a scratch dir outside the workspace.
|
|
174
|
+
*/
|
|
175
|
+
additionalDirectories?: ReadonlyArray<string>;
|
|
176
|
+
/**
|
|
177
|
+
* Lift filesystem confinement entirely, letting the agent read and write any
|
|
178
|
+
* absolute path the host process can.
|
|
179
|
+
*
|
|
180
|
+
* Off by default, and worth leaving off: ACP places boundary enforcement on
|
|
181
|
+
* the client, and a `permissions` policy does not substitute for it — that
|
|
182
|
+
* answers `session/request_permission`, which covers the agent's tool calls,
|
|
183
|
+
* not the `fs/*` methods it invokes on the client directly.
|
|
184
|
+
*/
|
|
185
|
+
allowAnyPath?: boolean;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The client methods an agent can invoke on us. Closed by ACP spec version 1,
|
|
189
|
+
* so a literal union rather than `string`: a typo in a descriptor is a compile
|
|
190
|
+
* error, and consumers narrowing on `activity.method` get exhaustiveness.
|
|
191
|
+
* @public
|
|
192
|
+
*/
|
|
193
|
+
export type AcpClientMethod = 'fs/read_text_file' | 'fs/write_text_file' | 'terminal/create' | 'terminal/output' | 'terminal/wait_for_exit' | 'terminal/kill' | 'terminal/release';
|
|
194
|
+
/**
|
|
195
|
+
* One client-side operation an agent performed — or was refused. Emitted for
|
|
196
|
+
* every `fs/*` and `terminal/*` call the agent makes.
|
|
197
|
+
*
|
|
198
|
+
* This is an *observed* record, not the agent's self-report: `tool_call`
|
|
199
|
+
* updates say what the agent claims it did, while this says what it actually
|
|
200
|
+
* asked the client to do, including the calls that were turned down.
|
|
201
|
+
* @public
|
|
202
|
+
*/
|
|
203
|
+
export interface AcpClientActivity {
|
|
204
|
+
/** The ACP client method invoked, e.g. `fs/read_text_file`. */
|
|
205
|
+
method: AcpClientMethod;
|
|
206
|
+
/** Path involved, for the `fs/*` family and `terminal/create`'s cwd. */
|
|
207
|
+
path?: string;
|
|
208
|
+
/** Command line, for `terminal/create`. */
|
|
209
|
+
command?: string;
|
|
210
|
+
/** Terminal handle, for the `terminal/*` family. */
|
|
211
|
+
terminalId?: string;
|
|
212
|
+
/** False when the call was refused — by a withdrawn capability or confinement. */
|
|
213
|
+
allowed: boolean;
|
|
214
|
+
/** Why it was refused. */
|
|
215
|
+
reason?: string;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The runtime surface an ACP connection is given to satisfy client-side
|
|
219
|
+
* requests. Because file reads/writes and terminal commands are served from
|
|
220
|
+
* Noetic's own adapters, everything the sub-agent touches passes through the
|
|
221
|
+
* same sandboxing, virtual-filesystem, and audit machinery as first-party steps.
|
|
222
|
+
* @public
|
|
223
|
+
*/
|
|
224
|
+
export interface AcpClientHost {
|
|
225
|
+
/** Working directory for the session (resolved from `ctx.cwdState.cwd`). */
|
|
226
|
+
readonly cwd: string;
|
|
227
|
+
readonly fs: FsAdapter;
|
|
228
|
+
readonly shell: ShellAdapter;
|
|
229
|
+
/** Conversation thread id, for hosts that scope state per thread. */
|
|
230
|
+
readonly threadId: string;
|
|
231
|
+
/** Abort signal for the surrounding execution. */
|
|
232
|
+
readonly signal?: AbortSignal;
|
|
233
|
+
/**
|
|
234
|
+
* Capability advertisement overrides. Connection-level: ACP negotiates the
|
|
235
|
+
* client capability set once during `initialize`, so this cannot change for
|
|
236
|
+
* the life of a connection.
|
|
237
|
+
*/
|
|
238
|
+
readonly capabilities?: AcpClientCapabilityConfig;
|
|
239
|
+
/**
|
|
240
|
+
* Permission resolution, in the order it is consulted: the declarative
|
|
241
|
+
* policy, then steering, then the async handler. The protocol client owns the
|
|
242
|
+
* resolution logic; the host only supplies the inputs.
|
|
243
|
+
*
|
|
244
|
+
* These are **per-turn**, and deliberately mutable: a session reused across
|
|
245
|
+
* several steps must answer with the CURRENT step's policy, not the one that
|
|
246
|
+
* happened to open the connection. The client reads them at call time, so
|
|
247
|
+
* the runtime updates them before each turn.
|
|
248
|
+
*/
|
|
249
|
+
permissions?: AcpPermissionPolicy;
|
|
250
|
+
steerPermission?: AcpPermissionSteerer;
|
|
251
|
+
onPermissionRequest?: AcpBoundPermissionHandler;
|
|
252
|
+
/**
|
|
253
|
+
* Receive every `session/update` notification for the active session. Also
|
|
254
|
+
* per-turn — it routes to the running step's event bridge, so a reused
|
|
255
|
+
* session's output is attributed to the step that actually asked for it.
|
|
256
|
+
*/
|
|
257
|
+
onSessionUpdate: (notification: acp.SessionNotification) => void;
|
|
258
|
+
/**
|
|
259
|
+
* Receive every `fs/*` and `terminal/*` call the agent makes, allowed or
|
|
260
|
+
* refused. Per-turn like {@link onSessionUpdate}, so activity is attributed
|
|
261
|
+
* to the step that actually caused it.
|
|
262
|
+
*/
|
|
263
|
+
onClientActivity?: (activity: AcpClientActivity) => void;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Why the agent stopped a turn. Derived from `PromptResponse` because the
|
|
267
|
+
* protocol package inlines the union rather than exporting it by name.
|
|
268
|
+
* @public
|
|
269
|
+
*/
|
|
270
|
+
export type AcpStopReason = acp.PromptResponse['stopReason'];
|
|
271
|
+
/** @public Input for one `session/prompt` turn. */
|
|
272
|
+
export interface AcpPromptOptions {
|
|
273
|
+
/**
|
|
274
|
+
* Prompt content blocks. Validated against the agent's advertised
|
|
275
|
+
* `promptCapabilities` before sending — the spec requires clients to restrict
|
|
276
|
+
* content types to what the agent accepts.
|
|
277
|
+
*/
|
|
278
|
+
content: ReadonlyArray<acp.ContentBlock>;
|
|
279
|
+
/** Aborting this signal issues `session/cancel` and awaits the `cancelled` stop reason. */
|
|
280
|
+
signal?: AbortSignal;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Result of a completed turn. The interpreter appends `items` to the item log,
|
|
284
|
+
* charges `usage`/`cost`, records `stopReason`, and returns `text` (or parses it
|
|
285
|
+
* through the step's output schema).
|
|
286
|
+
* @public
|
|
287
|
+
*/
|
|
288
|
+
export interface AcpTurnResult {
|
|
289
|
+
/** Why the agent stopped — drives the interpreter's success/error decision. */
|
|
290
|
+
stopReason: AcpStopReason;
|
|
291
|
+
/** Noetic Items produced this turn (assistant message, tool calls, tool results). */
|
|
292
|
+
items: Item[];
|
|
293
|
+
/** Concatenated assistant text of the turn. */
|
|
294
|
+
text: string;
|
|
295
|
+
usage?: TokenUsage;
|
|
296
|
+
cost?: number;
|
|
297
|
+
/** Plan entries reported via `plan` updates, latest snapshot wins. */
|
|
298
|
+
plan?: ReadonlyArray<acp.PlanEntry>;
|
|
299
|
+
/** Slash commands the agent advertised during the turn. */
|
|
300
|
+
availableCommands?: ReadonlyArray<acp.AvailableCommand>;
|
|
301
|
+
/** Mode the session ended the turn in, when the agent reports modes. */
|
|
302
|
+
currentModeId?: string;
|
|
303
|
+
}
|
|
304
|
+
/** @public Options for creating a session. */
|
|
305
|
+
export interface AcpNewSessionOptions {
|
|
306
|
+
/** Absolute working directory for the session. */
|
|
307
|
+
cwd: string;
|
|
308
|
+
/** MCP servers the agent should connect to, gated by its `mcpCapabilities`. */
|
|
309
|
+
mcpServers?: ReadonlyArray<acp.McpServer>;
|
|
310
|
+
}
|
|
311
|
+
/** @public Options for resuming a previously created session via `session/load`. */
|
|
312
|
+
export interface AcpLoadSessionOptions extends AcpNewSessionOptions {
|
|
313
|
+
sessionId: string;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* One ACP session: a conversation with its own history and state, living on the
|
|
317
|
+
* far side of a connection.
|
|
318
|
+
* @public
|
|
319
|
+
*/
|
|
320
|
+
export interface AcpSession {
|
|
321
|
+
readonly sessionId: string;
|
|
322
|
+
/** Mode state when the agent supports session modes. */
|
|
323
|
+
readonly modes?: acp.SessionModeState;
|
|
324
|
+
/** Slash commands currently offered, refreshed by `available_commands_update`. */
|
|
325
|
+
readonly availableCommands: ReadonlyArray<acp.AvailableCommand>;
|
|
326
|
+
/** Run one prompt turn to completion. */
|
|
327
|
+
prompt(opts: AcpPromptOptions): Promise<AcpTurnResult>;
|
|
328
|
+
/** Send `session/cancel` for an in-flight turn. */
|
|
329
|
+
cancel(): Promise<void>;
|
|
330
|
+
/** Switch mode. Throws when the agent advertised no modes. */
|
|
331
|
+
setMode(modeId: string): Promise<void>;
|
|
332
|
+
/** Switch model. Throws when the agent advertised no models. */
|
|
333
|
+
setModel(modelId: string): Promise<void>;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* A negotiated connection to an ACP agent — one `initialize` handshake and one
|
|
337
|
+
* transport, able to carry several sessions.
|
|
338
|
+
* @public
|
|
339
|
+
*/
|
|
340
|
+
export interface AcpAgentConnection {
|
|
341
|
+
/** Everything the agent advertised during `initialize`. */
|
|
342
|
+
readonly agentCapabilities: acp.AgentCapabilities;
|
|
343
|
+
/** Authentication methods the agent offers, empty when it needs none. */
|
|
344
|
+
readonly authMethods: ReadonlyArray<acp.AuthMethod>;
|
|
345
|
+
/** Protocol version agreed with the agent. */
|
|
346
|
+
readonly protocolVersion: number;
|
|
347
|
+
/** Run the `authenticate` flow for one of {@link authMethods}. */
|
|
348
|
+
authenticate(methodId: string): Promise<void>;
|
|
349
|
+
/** Create a session (`session/new`). */
|
|
350
|
+
newSession(opts: AcpNewSessionOptions): Promise<AcpSession>;
|
|
351
|
+
/**
|
|
352
|
+
* Resume a session (`session/load`). Throws {@link AcpCapabilityError} when
|
|
353
|
+
* the agent did not advertise `loadSession`.
|
|
354
|
+
*/
|
|
355
|
+
loadSession(opts: AcpLoadSessionOptions): Promise<AcpSession>;
|
|
356
|
+
/** Close the connection and release the transport. */
|
|
357
|
+
close(): Promise<void>;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* A live connection plus the session running on it. The runtime keeps these
|
|
361
|
+
* keyed by {@link AcpSessionPolicy.reuse} so a session can span several steps.
|
|
362
|
+
* @public
|
|
363
|
+
*/
|
|
364
|
+
export interface AcpLiveSession {
|
|
365
|
+
connection: AcpAgentConnection;
|
|
366
|
+
session: AcpSession;
|
|
367
|
+
/**
|
|
368
|
+
* The host this connection was opened with. The runtime rebinds its
|
|
369
|
+
* per-turn fields before every turn, so a session shared by several steps
|
|
370
|
+
* honours each step's own permissions, steering, and event stream.
|
|
371
|
+
*/
|
|
372
|
+
host: AcpClientHost;
|
|
373
|
+
/** `agentId` of the adapter that opened this connection, for reuse conflict checks. */
|
|
374
|
+
agentId: string;
|
|
375
|
+
/** How long this connection is kept alive; decides who closes it and when. */
|
|
376
|
+
keepAlive: AcpKeepAlive;
|
|
377
|
+
/** Working directory the session was created in — fixed for its lifetime. */
|
|
378
|
+
cwd: string;
|
|
379
|
+
/** MCP servers attached at creation — also fixed for its lifetime. */
|
|
380
|
+
mcpServers?: ReadonlyArray<acp.McpServer>;
|
|
381
|
+
}
|
|
382
|
+
/** @public Options handed to {@link AcpAgent.connect}. */
|
|
383
|
+
export interface AcpConnectOptions {
|
|
384
|
+
/** The runtime surface backing client-side ACP methods. */
|
|
385
|
+
host: AcpClientHost;
|
|
386
|
+
signal?: AbortSignal;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* An ACP agent adapter. `@noetic-tools/acp` exports presets (`claudeCode()`,
|
|
390
|
+
* `codex()`, `gemini()`, `customAcpAgent()`) that return one of these.
|
|
391
|
+
* @public
|
|
392
|
+
*/
|
|
393
|
+
export interface AcpAgent {
|
|
394
|
+
/** Spec tag for forward-compatibility. */
|
|
395
|
+
readonly specificationVersion: 'acp-v1';
|
|
396
|
+
/**
|
|
397
|
+
* Stable identifier for the agent (`'claude-code'`, `'codex'`, …). Free-form:
|
|
398
|
+
* the set of agents is open, and this is only used for registry lookup,
|
|
399
|
+
* observability, and error messages.
|
|
400
|
+
*/
|
|
401
|
+
readonly agentId: string;
|
|
402
|
+
/** Open a transport, run `initialize`, and return the negotiated connection. */
|
|
403
|
+
connect(opts: AcpConnectOptions): Promise<AcpAgentConnection>;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Controls how an ACP connection is reused and torn down across steps.
|
|
407
|
+
* @public
|
|
408
|
+
*/
|
|
409
|
+
/**
|
|
410
|
+
* How long an ACP connection outlives the step that opened it.
|
|
411
|
+
*
|
|
412
|
+
* A connection owns a live agent — usually a child process whose stdio keeps
|
|
413
|
+
* the event loop alive — so keeping one is never inferred. Every level beyond
|
|
414
|
+
* the default is something the step asks for explicitly.
|
|
415
|
+
* @public
|
|
416
|
+
*/
|
|
417
|
+
export declare const AcpKeepAlive: {
|
|
418
|
+
/** Closed when the step finishes. The default. */
|
|
419
|
+
readonly Step: 'step';
|
|
420
|
+
/** Kept for the rest of the root run, then closed by the harness. */
|
|
421
|
+
readonly Run: 'run';
|
|
422
|
+
/** Kept until {@link AcpSessionDisposer.closeAcpSessions} is called. Nothing closes it for you. */
|
|
423
|
+
readonly Harness: 'harness';
|
|
424
|
+
};
|
|
425
|
+
/** @public Union of connection keep-alive scopes. */
|
|
426
|
+
export type AcpKeepAlive = (typeof AcpKeepAlive)[keyof typeof AcpKeepAlive];
|
|
427
|
+
/**
|
|
428
|
+
* Controls how an ACP connection is shared and how long it is kept alive.
|
|
429
|
+
* @public
|
|
430
|
+
*/
|
|
431
|
+
export interface AcpSessionPolicy {
|
|
432
|
+
/**
|
|
433
|
+
* Share one live connection + session under this id, so later steps take
|
|
434
|
+
* their turns against the same agent. Requires {@link keepAlive} `'run'` or
|
|
435
|
+
* `'harness'` — a connection closed at the end of its step has nothing left
|
|
436
|
+
* to share.
|
|
437
|
+
*/
|
|
438
|
+
reuse?: string;
|
|
439
|
+
/**
|
|
440
|
+
* How long to keep the connection alive. Defaults to `'step'`: the
|
|
441
|
+
* connection is closed as soon as the step finishes, so nothing is
|
|
442
|
+
* maintained unless the step says so.
|
|
443
|
+
*
|
|
444
|
+
* `'harness'` hands ownership to the caller — the runtime will not close it,
|
|
445
|
+
* and an undisposed connection keeps the host process from exiting. Call
|
|
446
|
+
* `harness.closeAcpSessions()` when the work is done.
|
|
447
|
+
*/
|
|
448
|
+
keepAlive?: AcpKeepAlive;
|
|
449
|
+
/**
|
|
450
|
+
* Resume this ACP session id via `session/load` instead of creating a new one.
|
|
451
|
+
* Requires the agent's `loadSession` capability.
|
|
452
|
+
*/
|
|
453
|
+
load?: string;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* A snapshot of one live ACP sub-agent — what a UI needs to show it and offer
|
|
457
|
+
* actions on it, without handing out the connection itself.
|
|
458
|
+
* @public
|
|
459
|
+
*/
|
|
460
|
+
export interface AcpSessionInfo {
|
|
461
|
+
/** Opaque handle for this session — pass it back to `getAcpSession`/`cancelAcpSession`. */
|
|
462
|
+
key: string;
|
|
463
|
+
/** The `session.reuse` key the step named, when it named one. */
|
|
464
|
+
reuseKey?: string;
|
|
465
|
+
/** `agentId` of the adapter driving it. */
|
|
466
|
+
agentId: string;
|
|
467
|
+
/** The agent's own session id. */
|
|
468
|
+
sessionId: string;
|
|
469
|
+
keepAlive: AcpKeepAlive;
|
|
470
|
+
/** Current mode, when the agent supports modes. */
|
|
471
|
+
currentModeId?: string;
|
|
472
|
+
/** Modes the agent offers, for a mode switcher. */
|
|
473
|
+
availableModes: ReadonlyArray<acp.SessionMode>;
|
|
474
|
+
/** Slash commands the agent currently advertises. */
|
|
475
|
+
availableCommands: ReadonlyArray<acp.AvailableCommand>;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* The disposal surface for connections kept past their run. Implemented by
|
|
479
|
+
* `AgentHarness`; declared here so callers can hold the capability without
|
|
480
|
+
* depending on the concrete runtime.
|
|
481
|
+
* @public
|
|
482
|
+
*/
|
|
483
|
+
export interface AcpSessionDisposer {
|
|
484
|
+
/** Close every ACP connection this harness still holds. Idempotent. */
|
|
485
|
+
closeAcpSessions(): Promise<void>;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Thrown when an ACP agent is asked for a capability it did not advertise
|
|
489
|
+
* during `initialize` — loading a session, switching mode, sending image
|
|
490
|
+
* content. Raised before anything reaches the wire.
|
|
491
|
+
* @public
|
|
492
|
+
*/
|
|
493
|
+
export declare class AcpCapabilityError extends Error {
|
|
494
|
+
readonly agentId: string;
|
|
495
|
+
readonly capability: string;
|
|
496
|
+
constructor(opts: {
|
|
497
|
+
agentId: string;
|
|
498
|
+
capability: string;
|
|
499
|
+
message?: string;
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
/** @public Type guard for {@link AcpCapabilityError}. */
|
|
503
|
+
export declare function isAcpCapabilityError(e: unknown): e is AcpCapabilityError;
|
|
504
|
+
/**
|
|
505
|
+
* Thrown when a connection cannot be established — the agent binary is missing,
|
|
506
|
+
* the handshake fails, or authentication is required and unsatisfied.
|
|
507
|
+
* @public
|
|
508
|
+
*/
|
|
509
|
+
export declare class AcpConnectError extends Error {
|
|
510
|
+
readonly agentId: string;
|
|
511
|
+
readonly connectCause?: unknown;
|
|
512
|
+
constructor(opts: {
|
|
513
|
+
agentId: string;
|
|
514
|
+
message: string;
|
|
515
|
+
cause?: unknown;
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
/** @public Type guard for {@link AcpConnectError}. */
|
|
519
|
+
export declare function isAcpConnectError(e: unknown): e is AcpConnectError;
|
|
520
|
+
/**
|
|
521
|
+
* The ACP protocol surface, re-exported so consumers get the wire types without
|
|
522
|
+
* taking a direct dependency on the protocol package.
|
|
523
|
+
* @public
|
|
524
|
+
*/
|
|
525
|
+
export type { AgentCapabilities as AcpAgentCapabilities, AuthMethod as AcpAuthMethod, AvailableCommand as AcpAvailableCommand, ContentBlock as AcpContentBlock, McpServer as AcpMcpServer, PermissionOption as AcpPermissionOption, PlanEntry as AcpPlanEntry, PromptCapabilities as AcpPromptCapabilities, RequestPermissionRequest as AcpRequestPermissionRequest, SessionMode as AcpSessionMode, SessionModeState as AcpSessionModeState, SessionNotification as AcpSessionNotification, ToolCallContent as AcpToolCallContent, ToolCallStatus as AcpToolCallStatus, ToolKind as AcpToolKind, } from '@zed-industries/agent-client-protocol';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Client Protocol (ACP) contract.
|
|
3
|
+
*
|
|
4
|
+
* Noetic drives external coding agents — Claude Code, Codex, Gemini CLI, any
|
|
5
|
+
* ACP-speaking binary — as an **ACP Client**. The agent is a JSON-RPC 2.0 peer
|
|
6
|
+
* reached over a duplex byte stream, so there is no vendor SDK per agent and no
|
|
7
|
+
* closed set of supported agents.
|
|
8
|
+
*
|
|
9
|
+
* This file is the direct analogue of the `ContextLayer` contract: defined in
|
|
10
|
+
* the foundation package so both `@noetic-tools/core` (which executes ACP steps)
|
|
11
|
+
* and `@noetic-tools/acp` (which implements the protocol) depend on it without
|
|
12
|
+
* forming a cycle. Core calls only the methods declared here — the ACP library's
|
|
13
|
+
* `ClientSideConnection` never enters core's dependency graph.
|
|
14
|
+
*
|
|
15
|
+
* Protocol types are re-used verbatim from `@zed-industries/agent-client-protocol`
|
|
16
|
+
* (spec version 1) rather than mirrored, so the wire surface cannot drift from
|
|
17
|
+
* the specification.
|
|
18
|
+
*
|
|
19
|
+
* @see https://agentclientprotocol.com/
|
|
20
|
+
*/
|
|
21
|
+
//#region Step-kind discriminator
|
|
22
|
+
/**
|
|
23
|
+
* The single step kind backing every ACP agent. Unlike the closed harness enum
|
|
24
|
+
* it replaces, the *agent* is identified by a free-form `agentId` on the
|
|
25
|
+
* adapter — so adding an agent never touches core or the published JSON Schema.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export const ACP_AGENT_STEP_KIND = 'acp-agent';
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region Permissions
|
|
31
|
+
/** @public How a permission request was resolved. */
|
|
32
|
+
export const AcpPermissionDecision = {
|
|
33
|
+
Allow: 'allow',
|
|
34
|
+
Deny: 'deny',
|
|
35
|
+
Cancel: 'cancel',
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region Session policy
|
|
39
|
+
/**
|
|
40
|
+
* Controls how an ACP connection is reused and torn down across steps.
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* How long an ACP connection outlives the step that opened it.
|
|
45
|
+
*
|
|
46
|
+
* A connection owns a live agent — usually a child process whose stdio keeps
|
|
47
|
+
* the event loop alive — so keeping one is never inferred. Every level beyond
|
|
48
|
+
* the default is something the step asks for explicitly.
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export const AcpKeepAlive = {
|
|
52
|
+
/** Closed when the step finishes. The default. */
|
|
53
|
+
Step: 'step',
|
|
54
|
+
/** Kept for the rest of the root run, then closed by the harness. */
|
|
55
|
+
Run: 'run',
|
|
56
|
+
/** Kept until {@link AcpSessionDisposer.closeAcpSessions} is called. Nothing closes it for you. */
|
|
57
|
+
Harness: 'harness',
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region Errors
|
|
61
|
+
/**
|
|
62
|
+
* Thrown when an ACP agent is asked for a capability it did not advertise
|
|
63
|
+
* during `initialize` — loading a session, switching mode, sending image
|
|
64
|
+
* content. Raised before anything reaches the wire.
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
export class AcpCapabilityError extends Error {
|
|
68
|
+
agentId;
|
|
69
|
+
capability;
|
|
70
|
+
constructor(opts) {
|
|
71
|
+
super(opts.message ??
|
|
72
|
+
`ACP agent '${opts.agentId}' did not advertise the '${opts.capability}' capability.`);
|
|
73
|
+
this.name = 'AcpCapabilityError';
|
|
74
|
+
this.agentId = opts.agentId;
|
|
75
|
+
this.capability = opts.capability;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** @public Type guard for {@link AcpCapabilityError}. */
|
|
79
|
+
export function isAcpCapabilityError(e) {
|
|
80
|
+
return e instanceof AcpCapabilityError;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Thrown when a connection cannot be established — the agent binary is missing,
|
|
84
|
+
* the handshake fails, or authentication is required and unsatisfied.
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
export class AcpConnectError extends Error {
|
|
88
|
+
agentId;
|
|
89
|
+
connectCause;
|
|
90
|
+
constructor(opts) {
|
|
91
|
+
super(opts.message);
|
|
92
|
+
this.name = 'AcpConnectError';
|
|
93
|
+
this.agentId = opts.agentId;
|
|
94
|
+
this.connectCause = opts.cause;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** @public Type guard for {@link AcpConnectError}. */
|
|
98
|
+
export function isAcpConnectError(e) {
|
|
99
|
+
return e instanceof AcpConnectError;
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
package/dist/types/common.d.ts
CHANGED
|
@@ -54,6 +54,12 @@ export interface TokenUsage {
|
|
|
54
54
|
input: number;
|
|
55
55
|
output: number;
|
|
56
56
|
total: number;
|
|
57
|
+
/**
|
|
58
|
+
* Prompt tokens served from the provider's cache, summed across every model
|
|
59
|
+
* call on this execution. `undefined` — not `0` — while no call has reported a
|
|
60
|
+
* cache figure, preserving the `RoundUsage` distinction between "nothing was
|
|
61
|
+
* cached" and "this provider says nothing about caching".
|
|
62
|
+
*/
|
|
57
63
|
cached?: number;
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
@@ -78,6 +84,12 @@ export interface StepMeta {
|
|
|
78
84
|
usage?: RoundUsage;
|
|
79
85
|
cost?: number;
|
|
80
86
|
responseItems?: ReadonlyArray<Item>;
|
|
87
|
+
/**
|
|
88
|
+
* Why an ACP agent stopped its turn. Set only by `acp-agent` steps, and the
|
|
89
|
+
* only way a caller can tell a truncated turn (`max_tokens`,
|
|
90
|
+
* `max_turn_requests`) from a complete one — both return normally.
|
|
91
|
+
*/
|
|
92
|
+
acpStopReason?: string;
|
|
81
93
|
}
|
|
82
94
|
/** @public Structured response returned by a model adapter after an LLM call. */
|
|
83
95
|
export interface LLMResponse {
|