@armadra/agent 0.6.7 → 0.7.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 +70 -0
- package/CHANGELOG.zh-CN.md +51 -0
- package/README.md +4 -3
- package/README.zh-CN.md +1 -1
- package/dist/acp.d.ts +2 -2
- package/dist/acp.js +2 -2
- package/dist/ai/apis/shared.d.ts +7 -1
- package/dist/ai/apis/shared.js +10 -0
- package/dist/ai/fake/fake-provider.js +5 -1
- package/dist/ai/fake/fake-script.d.ts +5 -2
- package/dist/ai/fake/fake-script.js +6 -2
- package/dist/ai/types.d.ts +2 -1
- package/dist/bundle/ama.cjs +46181 -44829
- package/dist/cli/args.d.ts +6 -0
- package/dist/cli/args.js +46 -0
- package/dist/cli/bootstrap.js +13 -2
- package/dist/cli/compose-events.d.ts +10 -0
- package/dist/cli/compose-events.js +97 -0
- package/dist/cli/compose-session.d.ts +25 -4
- package/dist/cli/compose-session.js +130 -103
- package/dist/cli/compose.js +6 -1
- package/dist/cli/runtime.d.ts +3 -1
- package/dist/cli/subcommands/auth.d.ts +13 -1
- package/dist/cli/subcommands/auth.js +28 -1
- package/dist/drivers/acp/client.d.ts +28 -4
- package/dist/drivers/acp/client.js +78 -5
- package/dist/drivers/acp/driver.d.ts +15 -4
- package/dist/drivers/acp/driver.js +110 -33
- package/dist/drivers/acp/testing/fake-agent-main.d.ts +1 -1
- package/dist/drivers/acp/testing/fake-agent-main.js +10 -2
- package/dist/drivers/acp/testing/fake-agent.d.ts +24 -0
- package/dist/drivers/acp/testing/fake-agent.js +175 -6
- package/dist/drivers/acp/types.d.ts +156 -9
- package/dist/drivers/acp/types.js +13 -3
- package/dist/drivers/jsonrpc.d.ts +13 -3
- package/dist/drivers/jsonrpc.js +40 -7
- package/dist/drivers/turn.d.ts +15 -2
- package/dist/drivers/turn.js +33 -4
- package/dist/i18n/catalog.d.ts +59 -16
- package/dist/i18n/catalog.js +4 -1
- package/dist/i18n/messages/acp.d.ts +125 -0
- package/dist/i18n/messages/acp.js +126 -0
- package/dist/i18n/messages/auth.d.ts +2 -0
- package/dist/i18n/messages/auth.js +4 -2
- package/dist/i18n/messages/cli-args.d.ts +2 -0
- package/dist/i18n/messages/cli-args.js +2 -0
- package/dist/i18n/messages/cli.d.ts +4 -0
- package/dist/i18n/messages/cli.js +2 -0
- package/dist/i18n/messages/print.d.ts +3 -33
- package/dist/i18n/messages/print.js +3 -33
- package/dist/modes/acp/acp-auth-gate.d.ts +40 -0
- package/dist/modes/acp/acp-auth-gate.js +201 -0
- package/dist/modes/acp/acp-config.d.ts +43 -0
- package/dist/modes/acp/acp-config.js +151 -0
- package/dist/modes/acp/acp-connection.d.ts +29 -0
- package/dist/modes/acp/acp-connection.js +37 -0
- package/dist/modes/acp/acp-events.d.ts +59 -23
- package/dist/modes/acp/acp-events.js +154 -88
- package/dist/modes/acp/acp-mode.d.ts +13 -2
- package/dist/modes/acp/acp-mode.js +23 -8
- package/dist/modes/acp/acp-server.d.ts +59 -32
- package/dist/modes/acp/acp-server.js +322 -128
- package/dist/modes/acp/acp-sessions.d.ts +80 -0
- package/dist/modes/acp/acp-sessions.js +157 -0
- package/dist/modes/acp/acp-tool-text.d.ts +23 -0
- package/dist/modes/acp/acp-tool-text.js +83 -0
- package/dist/modes/print/json-event.d.ts +2 -1
- package/dist/modes/print/json-event.js +6 -1
- package/dist/tools/edit.d.ts +6 -2
- package/dist/tools/edit.js +19 -2
- package/dist/tools/types.d.ts +11 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/write.d.ts +1 -0
- package/dist/tools/write.js +21 -3
- package/docs/acp.md +151 -30
- package/docs/agents.md +2 -0
- package/docs/codemode.md +1 -1
- package/docs/en/acp.md +192 -0
- package/package.json +1 -1
|
@@ -13,16 +13,54 @@
|
|
|
13
13
|
* | `[plan]` | 先发 `plan`(两条)再回文本 |
|
|
14
14
|
* | `[think]` | 先发 `agent_thought_chunk` |
|
|
15
15
|
* | `[refuse]` | `refusal` |
|
|
16
|
+
* | `[elicit]` | `elicitation/create`(选颜色的表单)→ 回 `elicit: <action> <content JSON>`;客户端 |
|
|
17
|
+
* | | 没声明 `elicitation` 能力时回 `elicit: unsupported`;`cancel` 时本回合 `cancelled` |
|
|
18
|
+
* | `[model]` | 回 `model <当前模型>`(`configOptions` 关着时是 `model none`) |
|
|
19
|
+
* | `[env NAME]` | 回 `env NAME <值的 sha256 | absent>`(测环境变量到没到,不回显值) |
|
|
20
|
+
* | `[cancel-request]` | 同 `[permission]` 发权限请求,挂起 `cancelRequestMs`(缺省 2 s)后自己发 |
|
|
21
|
+
* | | `$/cancel_request` 撤回;`tool_call_update` failed,回 `permission withdrawn`、`end_turn` |
|
|
22
|
+
*
|
|
23
|
+
* `configOptions: true`(可执行入口 `--config-options`):开会话答一个 `model` 配置项(分组 `fast` 里有
|
|
24
|
+
* `small`、`big` 里有 `large`,当前 `small`),`session/set_config_option` 可改;缺省不答,线路与之前相同。
|
|
25
|
+
* `configOnly: true`(`--config-only`):开会话不答 `modes`,改在 `configOptions` 里给 category `mode` 的
|
|
26
|
+
* 选择项(id `mode`),经 `session/set_config_option` 切换;可与 `configOptions` 同开(两项都给)。
|
|
27
|
+
* `authRequired: true`(`--auth-required`):`initialize` 给一条 terminal 型认证方法,开会话
|
|
28
|
+
* (new / load / resume)一律回 -32000。
|
|
16
29
|
*
|
|
17
30
|
* 会话:`session/new` 发 `fake-<n>`;`session/resume` / `session/load` 接受任何 `fake-` 开头的 id
|
|
18
31
|
* (load 先回放一条用户消息与一条回复);`session/list` 列本进程建过的会话。
|
|
19
32
|
*/
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
20
34
|
import { ACP_METHODS, ACP_PROTOCOL_VERSION, RPC_ERRORS } from "../types.js";
|
|
21
35
|
import { JsonRpcPeer, RpcError } from "../../jsonrpc.js";
|
|
36
|
+
/** `authRequired` 时 `initialize` 给的认证方法。 */
|
|
37
|
+
export const FAKE_AUTH_METHOD = {
|
|
38
|
+
type: "terminal",
|
|
39
|
+
id: "login",
|
|
40
|
+
name: "Log in",
|
|
41
|
+
description: "Run the fake agent's login command",
|
|
42
|
+
args: ["--login"],
|
|
43
|
+
};
|
|
22
44
|
const MODES = [
|
|
23
45
|
{ id: "default", name: "Default" },
|
|
24
46
|
{ id: "plan", name: "Plan" },
|
|
25
47
|
];
|
|
48
|
+
/** 规范要求选项全部平铺或全部分组(不能混排),所以 `small` 也放进一组。 */
|
|
49
|
+
const MODELS = [
|
|
50
|
+
{ group: "fast", name: "Fast", options: [{ value: "small", name: "Small" }] },
|
|
51
|
+
{ group: "big", name: "Big", options: [{ value: "large", name: "Large", description: "slow" }] },
|
|
52
|
+
];
|
|
53
|
+
const MODEL_VALUES = ["small", "large"];
|
|
54
|
+
const MODE_VALUES = MODES.map((m) => m.id);
|
|
55
|
+
/** 一个表单:必填的颜色(枚举)与可选的数量。 */
|
|
56
|
+
const ELICIT_SCHEMA = {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
color: { type: "string", enum: ["red", "blue"] },
|
|
60
|
+
count: { type: "integer", minimum: 1 },
|
|
61
|
+
},
|
|
62
|
+
required: ["color"],
|
|
63
|
+
};
|
|
26
64
|
function textOf(blocks) {
|
|
27
65
|
return blocks.map((b) => (b.type === "text" ? b.text : `[${b.type}]`)).join("");
|
|
28
66
|
}
|
|
@@ -37,6 +75,43 @@ function waitAbort(signal) {
|
|
|
37
75
|
export function runFakeAcpAgent(input, output, options = {}) {
|
|
38
76
|
const sessions = new Map();
|
|
39
77
|
let counter = 0;
|
|
78
|
+
/** 客户端在 initialize 里声明了 elicitation。 */
|
|
79
|
+
let canElicit = false;
|
|
80
|
+
const configOf = (s) => [
|
|
81
|
+
...(options.configOnly === true
|
|
82
|
+
? [
|
|
83
|
+
{
|
|
84
|
+
id: "mode",
|
|
85
|
+
name: "Mode",
|
|
86
|
+
category: "mode",
|
|
87
|
+
type: "select",
|
|
88
|
+
currentValue: s.mode,
|
|
89
|
+
options: MODES.map((m) => ({ value: m.id, name: m.name })),
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
: []),
|
|
93
|
+
...(options.configOptions === true
|
|
94
|
+
? [
|
|
95
|
+
{
|
|
96
|
+
id: "model",
|
|
97
|
+
name: "Model",
|
|
98
|
+
category: "model",
|
|
99
|
+
type: "select",
|
|
100
|
+
currentValue: s.model,
|
|
101
|
+
options: MODELS,
|
|
102
|
+
},
|
|
103
|
+
]
|
|
104
|
+
: []),
|
|
105
|
+
];
|
|
106
|
+
const hasConfig = options.configOptions === true || options.configOnly === true;
|
|
107
|
+
/** 开会话答复里的 `configOptions`(关着时不加这个键)。 */
|
|
108
|
+
const config = (s) => hasConfig ? { configOptions: configOf(s) } : {};
|
|
109
|
+
/** 开会话答复里的 `modes`(`configOnly` 时不给)。 */
|
|
110
|
+
const modes = (s) => options.configOnly === true ? {} : { modes: { currentModeId: s.mode, availableModes: MODES } };
|
|
111
|
+
const requireAuth = () => {
|
|
112
|
+
if (options.authRequired === true)
|
|
113
|
+
throw new RpcError(RPC_ERRORS.authRequired, "authentication required");
|
|
114
|
+
};
|
|
40
115
|
const update = (sessionId, value) => peer.notify(ACP_METHODS.sessionUpdate, { sessionId, update: value });
|
|
41
116
|
const session = (id) => {
|
|
42
117
|
const found = typeof id === "string" ? sessions.get(id) : undefined;
|
|
@@ -45,7 +120,7 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
45
120
|
return found;
|
|
46
121
|
};
|
|
47
122
|
const open = (id, cwd) => {
|
|
48
|
-
const created = { id, cwd, mode: "default", turns: 0 };
|
|
123
|
+
const created = { id, cwd, mode: "default", model: "small", turns: 0 };
|
|
49
124
|
sessions.set(id, created);
|
|
50
125
|
return created;
|
|
51
126
|
};
|
|
@@ -75,10 +150,77 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
75
150
|
});
|
|
76
151
|
if (text.includes("[refuse]"))
|
|
77
152
|
return done("refusal");
|
|
153
|
+
const say = (line) => update(s.id, { sessionUpdate: "agent_message_chunk", content: { type: "text", text: line } });
|
|
154
|
+
if (text.includes("[elicit]")) {
|
|
155
|
+
if (!canElicit) {
|
|
156
|
+
await say("elicit: unsupported");
|
|
157
|
+
return done("end_turn");
|
|
158
|
+
}
|
|
159
|
+
let answer;
|
|
160
|
+
try {
|
|
161
|
+
answer = await peer.request(ACP_METHODS.elicitationCreate, {
|
|
162
|
+
sessionId: s.id,
|
|
163
|
+
message: "Pick a color",
|
|
164
|
+
requestedSchema: ELICIT_SCHEMA,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return done("cancelled");
|
|
169
|
+
}
|
|
170
|
+
await say(`elicit: ${answer.action} ${JSON.stringify(answer.content ?? null)}`);
|
|
171
|
+
return done(answer.action === "cancel" ? "cancelled" : "end_turn");
|
|
172
|
+
}
|
|
173
|
+
if (text.includes("[model]")) {
|
|
174
|
+
await say(`model ${options.configOptions === true ? s.model : "none"}`);
|
|
175
|
+
return done("end_turn");
|
|
176
|
+
}
|
|
177
|
+
const env = /\[env ([A-Z0-9_]+)\]/.exec(text);
|
|
178
|
+
if (env !== null) {
|
|
179
|
+
const value = process.env[env[1]];
|
|
180
|
+
const digest = value === undefined ? "absent" : createHash("sha256").update(value).digest("hex");
|
|
181
|
+
await say(`env ${env[1]} ${digest}`);
|
|
182
|
+
return done("end_turn");
|
|
183
|
+
}
|
|
78
184
|
if (text.includes("[slow]")) {
|
|
79
185
|
await waitAbort(turn.signal);
|
|
80
186
|
return done("cancelled");
|
|
81
187
|
}
|
|
188
|
+
if (text.includes("[cancel-request]")) {
|
|
189
|
+
const toolCallId = `call-${s.turns}`;
|
|
190
|
+
await update(s.id, {
|
|
191
|
+
sessionUpdate: "tool_call",
|
|
192
|
+
toolCallId,
|
|
193
|
+
title: "Write note.txt",
|
|
194
|
+
kind: "edit",
|
|
195
|
+
status: "pending",
|
|
196
|
+
});
|
|
197
|
+
const withdraw = new AbortController();
|
|
198
|
+
const timer = setTimeout(() => withdraw.abort(), options.cancelRequestMs ?? 2000);
|
|
199
|
+
const asked = peer
|
|
200
|
+
.request(ACP_METHODS.requestPermission, {
|
|
201
|
+
sessionId: s.id,
|
|
202
|
+
toolCall: { toolCallId, title: "Write note.txt", kind: "edit", status: "pending" },
|
|
203
|
+
options: [
|
|
204
|
+
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
205
|
+
{ optionId: "reject", name: "Reject", kind: "reject_once" },
|
|
206
|
+
],
|
|
207
|
+
}, withdraw.signal)
|
|
208
|
+
.then((answer) => answer, () => undefined);
|
|
209
|
+
const answer = await Promise.race([asked, waitAbort(turn.signal).then(() => undefined)]);
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
if (turn.signal.aborted) {
|
|
212
|
+
withdraw.abort();
|
|
213
|
+
return done("cancelled");
|
|
214
|
+
}
|
|
215
|
+
if (answer === undefined) {
|
|
216
|
+
await update(s.id, { sessionUpdate: "tool_call_update", toolCallId, status: "failed" });
|
|
217
|
+
await say("permission withdrawn");
|
|
218
|
+
return done("end_turn");
|
|
219
|
+
}
|
|
220
|
+
await update(s.id, { sessionUpdate: "tool_call_update", toolCallId, status: "completed" });
|
|
221
|
+
await say(`answered ${answer.outcome.outcome}`);
|
|
222
|
+
return done("end_turn");
|
|
223
|
+
}
|
|
82
224
|
if (text.includes("[permission]")) {
|
|
83
225
|
const toolCallId = `call-${s.turns}`;
|
|
84
226
|
await update(s.id, {
|
|
@@ -138,10 +280,14 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
138
280
|
const peer = new JsonRpcPeer({
|
|
139
281
|
input,
|
|
140
282
|
output,
|
|
283
|
+
// 只有 [cancel-request] 会 abort 出站请求;入站撤回目前不影响假 Agent 的处理器
|
|
284
|
+
cancelRequests: true,
|
|
141
285
|
async onRequest(method, raw) {
|
|
142
286
|
const params = (raw ?? {});
|
|
143
287
|
switch (method) {
|
|
144
|
-
case ACP_METHODS.initialize:
|
|
288
|
+
case ACP_METHODS.initialize: {
|
|
289
|
+
const capabilities = params["clientCapabilities"];
|
|
290
|
+
canElicit = capabilities?.["elicitation"] != null;
|
|
145
291
|
return {
|
|
146
292
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
147
293
|
agentCapabilities: options.minimal
|
|
@@ -151,18 +297,21 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
151
297
|
promptCapabilities: { image: false, embeddedContext: false },
|
|
152
298
|
sessionCapabilities: { list: {}, resume: {}, close: {} },
|
|
153
299
|
},
|
|
154
|
-
authMethods: [],
|
|
300
|
+
authMethods: options.authRequired === true ? [FAKE_AUTH_METHOD] : [],
|
|
155
301
|
agentInfo: { name: options.name ?? "fake-acp-agent", version: "1.0.0" },
|
|
156
302
|
};
|
|
303
|
+
}
|
|
157
304
|
case ACP_METHODS.sessionNew: {
|
|
305
|
+
requireAuth();
|
|
158
306
|
counter += 1;
|
|
159
307
|
const s = open(`fake-${counter}`, String(params["cwd"] ?? ""));
|
|
160
308
|
if (options.minimal)
|
|
161
|
-
return { sessionId: s.id };
|
|
162
|
-
return { sessionId: s.id, modes
|
|
309
|
+
return { sessionId: s.id, ...config(s) };
|
|
310
|
+
return { sessionId: s.id, ...modes(s), ...config(s) };
|
|
163
311
|
}
|
|
164
312
|
case ACP_METHODS.sessionResume:
|
|
165
313
|
case ACP_METHODS.sessionLoad: {
|
|
314
|
+
requireAuth();
|
|
166
315
|
const id = String(params["sessionId"] ?? "");
|
|
167
316
|
if (options.minimal || !id.startsWith("fake-"))
|
|
168
317
|
throw new RpcError(RPC_ERRORS.resourceNotFound, `unknown session: ${id}`);
|
|
@@ -177,7 +326,7 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
177
326
|
content: { type: "text", text: "echo: earlier" },
|
|
178
327
|
});
|
|
179
328
|
}
|
|
180
|
-
return { modes
|
|
329
|
+
return { ...modes(s), ...config(s) };
|
|
181
330
|
}
|
|
182
331
|
case ACP_METHODS.sessionList:
|
|
183
332
|
return {
|
|
@@ -193,6 +342,26 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
193
342
|
await update(s.id, { sessionUpdate: "current_mode_update", currentModeId: s.mode });
|
|
194
343
|
return {};
|
|
195
344
|
}
|
|
345
|
+
case ACP_METHODS.sessionSetConfigOption: {
|
|
346
|
+
if (!hasConfig)
|
|
347
|
+
throw new RpcError(RPC_ERRORS.methodNotFound, `method not found: ${method}`);
|
|
348
|
+
const s = session(params["sessionId"]);
|
|
349
|
+
const value = String(params["value"]);
|
|
350
|
+
if (params["configId"] === "model" && options.configOptions === true) {
|
|
351
|
+
if (!MODEL_VALUES.includes(value))
|
|
352
|
+
throw new RpcError(RPC_ERRORS.invalidParams, `unknown model: ${value}`);
|
|
353
|
+
s.model = value;
|
|
354
|
+
}
|
|
355
|
+
else if (params["configId"] === "mode" && options.configOnly === true) {
|
|
356
|
+
if (!MODE_VALUES.includes(value))
|
|
357
|
+
throw new RpcError(RPC_ERRORS.invalidParams, `unknown mode: ${value}`);
|
|
358
|
+
s.mode = value;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
throw new RpcError(RPC_ERRORS.invalidParams, `unknown config: ${String(params["configId"])}`);
|
|
362
|
+
}
|
|
363
|
+
return { configOptions: configOf(s) };
|
|
364
|
+
}
|
|
196
365
|
case ACP_METHODS.sessionPrompt:
|
|
197
366
|
return prompt(raw);
|
|
198
367
|
default:
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* ACP(Agent Client Protocol)v1 的子集类型(docs/wave5-plan.md §5.1,D14)。[W5-E]
|
|
3
3
|
*
|
|
4
4
|
* 手写、零依赖;只收 ama 作为客户端(驱动外部 Agent)与服务端(`ama --mode acp`)两侧用到的部分:
|
|
5
|
-
* `initialize`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode`、
|
|
6
|
-
* `session/update`(含 `usage_update`)、`session/request_permission
|
|
7
|
-
*
|
|
5
|
+
* `initialize`、`authenticate`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode|set_config_option`、
|
|
6
|
+
* `session/update`(含 `usage_update`)、`session/request_permission`、`elicitation/create`、`$/cancel_request`。
|
|
7
|
+
* 字段名与规范一致(camelCase),未知字段一律保留不报错;`_meta` 不解释(ama 自己发的只用 {@link ACP_META_KEY})。
|
|
8
8
|
*
|
|
9
9
|
* 不声明 `fs` / `terminal` 客户端能力(与 Armadra Q5 一致):外部 Agent 自己读写、自己跑命令,
|
|
10
10
|
* 用它自己的权限策略;它要问人的才经 `session/request_permission` 回到 ama。
|
|
@@ -14,6 +14,8 @@ export declare const ACP_PROTOCOL_VERSION: 1;
|
|
|
14
14
|
/** 方法名(客户端 → Agent 的请求 / 通知,Agent → 客户端的请求 / 通知)。 */
|
|
15
15
|
export declare const ACP_METHODS: {
|
|
16
16
|
readonly initialize: "initialize";
|
|
17
|
+
/** ama 不实现(只给 terminal 型认证方法,规范要求这类方法不经 authenticate)。 */
|
|
18
|
+
readonly authenticate: "authenticate";
|
|
17
19
|
readonly sessionNew: "session/new";
|
|
18
20
|
readonly sessionLoad: "session/load";
|
|
19
21
|
readonly sessionResume: "session/resume";
|
|
@@ -22,8 +24,12 @@ export declare const ACP_METHODS: {
|
|
|
22
24
|
readonly sessionPrompt: "session/prompt";
|
|
23
25
|
readonly sessionCancel: "session/cancel";
|
|
24
26
|
readonly sessionSetMode: "session/set_mode";
|
|
27
|
+
readonly sessionSetConfigOption: "session/set_config_option";
|
|
25
28
|
readonly sessionUpdate: "session/update";
|
|
26
29
|
readonly requestPermission: "session/request_permission";
|
|
30
|
+
readonly elicitationCreate: "elicitation/create";
|
|
31
|
+
/** 协议级取消(双向通知,`requestId` 指对端发来的、尚未答复的请求)。 */
|
|
32
|
+
readonly cancelRequest: "$/cancel_request";
|
|
27
33
|
};
|
|
28
34
|
/** JSON-RPC 错误码(规范沿用 JSON-RPC 2.0;`-32000` 为 ACP 的 auth_required)。 */
|
|
29
35
|
export declare const RPC_ERRORS: {
|
|
@@ -34,6 +40,8 @@ export declare const RPC_ERRORS: {
|
|
|
34
40
|
readonly internalError: -32603;
|
|
35
41
|
readonly authRequired: -32000;
|
|
36
42
|
readonly resourceNotFound: -32002;
|
|
43
|
+
/** 请求被 `$/cancel_request` 撤回。 */
|
|
44
|
+
readonly requestCancelled: -32800;
|
|
37
45
|
};
|
|
38
46
|
export interface AcpTextContent {
|
|
39
47
|
type: "text";
|
|
@@ -76,6 +84,18 @@ export interface AcpClientCapabilities {
|
|
|
76
84
|
writeTextFile?: boolean;
|
|
77
85
|
};
|
|
78
86
|
terminal?: boolean;
|
|
87
|
+
/** 能接 `elicitation/create`(存在即支持)。 */
|
|
88
|
+
elicitation?: Record<string, unknown>;
|
|
89
|
+
/** 会话相关的客户端能力;`configOptions.boolean` 存在即能显示 boolean 型配置项。 */
|
|
90
|
+
session?: {
|
|
91
|
+
configOptions?: {
|
|
92
|
+
boolean?: Record<string, unknown> | null;
|
|
93
|
+
} | null;
|
|
94
|
+
} | null;
|
|
95
|
+
/** `terminal: true`:客户端能替 Agent 起终端跑 terminal 型认证方法。 */
|
|
96
|
+
auth?: {
|
|
97
|
+
terminal?: boolean;
|
|
98
|
+
};
|
|
79
99
|
}
|
|
80
100
|
export interface AcpInitializeParams {
|
|
81
101
|
protocolVersion: number;
|
|
@@ -99,11 +119,30 @@ export interface AcpAgentCapabilities {
|
|
|
99
119
|
resume?: Record<string, unknown> | null;
|
|
100
120
|
close?: Record<string, unknown> | null;
|
|
101
121
|
};
|
|
122
|
+
/** Agent 侧认证能力(`logout` 存在即支持 `logout`)。 */
|
|
123
|
+
auth?: {
|
|
124
|
+
logout?: Record<string, unknown> | null;
|
|
125
|
+
};
|
|
102
126
|
}
|
|
103
|
-
|
|
127
|
+
/**
|
|
128
|
+
* 认证方法:缺省 / `agent` 型经 `authenticate` 完成;`terminal` 型由客户端起终端跑
|
|
129
|
+
* `<agent 命令> <args…>`(带 `env`),规范要求这类方法不经 `authenticate`。
|
|
130
|
+
*/
|
|
131
|
+
export type AcpAuthMethod = {
|
|
132
|
+
type?: "agent";
|
|
104
133
|
id: string;
|
|
105
134
|
name: string;
|
|
106
135
|
description?: string | null;
|
|
136
|
+
} | {
|
|
137
|
+
type: "terminal";
|
|
138
|
+
id: string;
|
|
139
|
+
name: string;
|
|
140
|
+
description?: string | null;
|
|
141
|
+
args?: string[];
|
|
142
|
+
env?: Record<string, string>;
|
|
143
|
+
};
|
|
144
|
+
export interface AcpAuthenticateParams {
|
|
145
|
+
methodId: string;
|
|
107
146
|
}
|
|
108
147
|
export interface AcpInitializeResult {
|
|
109
148
|
protocolVersion: number;
|
|
@@ -149,6 +188,7 @@ export interface AcpNewSessionParams {
|
|
|
149
188
|
export interface AcpNewSessionResult {
|
|
150
189
|
sessionId: string;
|
|
151
190
|
modes?: AcpSessionModeState | null;
|
|
191
|
+
configOptions?: AcpSessionConfigOption[] | null;
|
|
152
192
|
}
|
|
153
193
|
export interface AcpLoadSessionParams {
|
|
154
194
|
sessionId: string;
|
|
@@ -157,6 +197,87 @@ export interface AcpLoadSessionParams {
|
|
|
157
197
|
}
|
|
158
198
|
export interface AcpLoadSessionResult {
|
|
159
199
|
modes?: AcpSessionModeState | null;
|
|
200
|
+
configOptions?: AcpSessionConfigOption[] | null;
|
|
201
|
+
}
|
|
202
|
+
/** 配置项里一个可选值。 */
|
|
203
|
+
export interface AcpConfigSelectOption {
|
|
204
|
+
value: string;
|
|
205
|
+
name: string;
|
|
206
|
+
description?: string | null;
|
|
207
|
+
}
|
|
208
|
+
/** 规范允许把可选值分组。 */
|
|
209
|
+
export interface AcpConfigSelectGroup {
|
|
210
|
+
group: string;
|
|
211
|
+
name: string;
|
|
212
|
+
options: AcpConfigSelectOption[];
|
|
213
|
+
}
|
|
214
|
+
/** 配置项的语义分类:只是给客户端排版的提示,不参与正确性。 */
|
|
215
|
+
export type AcpConfigCategory = "mode" | "model" | "model_config" | "thought_level" | (string & {});
|
|
216
|
+
/** `session/new|load|resume` 答的 `configOptions[]` 的一项(ama 只发 `select`)。 */
|
|
217
|
+
export interface AcpSessionConfigOption {
|
|
218
|
+
id: string;
|
|
219
|
+
name: string;
|
|
220
|
+
description?: string | null;
|
|
221
|
+
/** `mode` / `model` / `thought_level`,或 Agent 自己的。 */
|
|
222
|
+
category?: AcpConfigCategory | null;
|
|
223
|
+
type: "select" | (string & {});
|
|
224
|
+
currentValue: string;
|
|
225
|
+
/** 全部平铺或全部分组,规范不允许混排。 */
|
|
226
|
+
options: AcpConfigSelectOption[] | AcpConfigSelectGroup[];
|
|
227
|
+
}
|
|
228
|
+
export interface AcpSetConfigOptionParams {
|
|
229
|
+
sessionId: string;
|
|
230
|
+
configId: string;
|
|
231
|
+
value: string;
|
|
232
|
+
}
|
|
233
|
+
/** 答复带全部配置项的新状态(改一项可能连带别的)。 */
|
|
234
|
+
export interface AcpSetConfigOptionResult {
|
|
235
|
+
configOptions?: AcpSessionConfigOption[] | null;
|
|
236
|
+
}
|
|
237
|
+
/** 表单里的一个字段:扁平的原始类型,规范只允许这几种。 */
|
|
238
|
+
export type AcpElicitationField = {
|
|
239
|
+
type: "string";
|
|
240
|
+
title?: string;
|
|
241
|
+
description?: string;
|
|
242
|
+
enum?: string[];
|
|
243
|
+
enumNames?: string[];
|
|
244
|
+
format?: string;
|
|
245
|
+
minLength?: number;
|
|
246
|
+
maxLength?: number;
|
|
247
|
+
default?: string;
|
|
248
|
+
} | {
|
|
249
|
+
type: "number" | "integer";
|
|
250
|
+
title?: string;
|
|
251
|
+
description?: string;
|
|
252
|
+
minimum?: number;
|
|
253
|
+
maximum?: number;
|
|
254
|
+
default?: number;
|
|
255
|
+
} | {
|
|
256
|
+
type: "boolean";
|
|
257
|
+
title?: string;
|
|
258
|
+
description?: string;
|
|
259
|
+
default?: boolean;
|
|
260
|
+
};
|
|
261
|
+
export interface AcpElicitationSchema {
|
|
262
|
+
type: "object";
|
|
263
|
+
properties: Record<string, AcpElicitationField>;
|
|
264
|
+
required?: string[];
|
|
265
|
+
}
|
|
266
|
+
/** Agent 发来的 `elicitation/create` 参数;未知字段保留。 */
|
|
267
|
+
export interface AcpElicitationParams {
|
|
268
|
+
sessionId?: string;
|
|
269
|
+
message: string;
|
|
270
|
+
/** `form`(缺省)或 `url`。 */
|
|
271
|
+
mode?: "form" | "url" | (string & {});
|
|
272
|
+
requestedSchema?: AcpElicitationSchema;
|
|
273
|
+
url?: string;
|
|
274
|
+
[key: string]: unknown;
|
|
275
|
+
}
|
|
276
|
+
export type AcpElicitationAction = "accept" | "decline" | "cancel";
|
|
277
|
+
export interface AcpElicitationResult {
|
|
278
|
+
action: AcpElicitationAction;
|
|
279
|
+
/** 只随 `accept`。 */
|
|
280
|
+
content?: Record<string, string | number | boolean>;
|
|
160
281
|
}
|
|
161
282
|
export type AcpResumeSessionParams = AcpLoadSessionParams;
|
|
162
283
|
export type AcpResumeSessionResult = AcpLoadSessionResult;
|
|
@@ -186,7 +307,10 @@ export interface AcpPromptParams {
|
|
|
186
307
|
sessionId: string;
|
|
187
308
|
prompt: AcpContentBlock[];
|
|
188
309
|
}
|
|
189
|
-
/**
|
|
310
|
+
/**
|
|
311
|
+
* 本回合用量。UNSTABLE:1.24.1 仍只在 schema.unstable.json 里(稳定 schema 的 PromptResponse
|
|
312
|
+
* 不列它,但允许附加字段,故照发);客户端不应依赖。
|
|
313
|
+
*/
|
|
190
314
|
export interface AcpPromptUsage {
|
|
191
315
|
totalTokens?: number;
|
|
192
316
|
inputTokens?: number;
|
|
@@ -202,6 +326,17 @@ export interface AcpPromptResult {
|
|
|
202
326
|
export interface AcpCancelParams {
|
|
203
327
|
sessionId: string;
|
|
204
328
|
}
|
|
329
|
+
/** `$/cancel_request` 的参数:被撤回的请求 id。 */
|
|
330
|
+
export interface AcpCancelRequestParams {
|
|
331
|
+
requestId: string | number | null;
|
|
332
|
+
}
|
|
333
|
+
/** ama 自己的 `_meta` 命名空间(客户端不得假设其含义)。 */
|
|
334
|
+
export declare const ACP_META_KEY: "ama";
|
|
335
|
+
/** `_meta.ama` 的内容。 */
|
|
336
|
+
export interface AcpAmaMeta {
|
|
337
|
+
/** codemode 内层调用所属的外层工具调用。 */
|
|
338
|
+
parentToolCallId?: string;
|
|
339
|
+
}
|
|
205
340
|
export type { AcpToolKind };
|
|
206
341
|
export type AcpToolCallStatus = "pending" | "in_progress" | "completed" | "failed";
|
|
207
342
|
export interface AcpToolCallLocation {
|
|
@@ -229,6 +364,10 @@ export interface AcpToolCall {
|
|
|
229
364
|
locations?: AcpToolCallLocation[];
|
|
230
365
|
rawInput?: unknown;
|
|
231
366
|
rawOutput?: unknown;
|
|
367
|
+
/** 工具名(规范新增,供客户端按工具分组 / 记忆授权)。 */
|
|
368
|
+
name?: string | null;
|
|
369
|
+
/** 扩展位;ama 只用 `{ [ACP_META_KEY]: AcpAmaMeta }`。 */
|
|
370
|
+
_meta?: Record<string, unknown> | null;
|
|
232
371
|
}
|
|
233
372
|
/** `tool_call_update` 与权限请求里的 toolCall:除 id 外都可缺。 */
|
|
234
373
|
export type AcpToolCallUpdate = Partial<AcpToolCall> & {
|
|
@@ -270,15 +409,23 @@ export type AcpSessionUpdate = {
|
|
|
270
409
|
} | null;
|
|
271
410
|
} | {
|
|
272
411
|
sessionUpdate: "available_commands_update";
|
|
273
|
-
availableCommands:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
412
|
+
availableCommands: AcpAvailableCommand[];
|
|
413
|
+
} | {
|
|
414
|
+
sessionUpdate: "config_option_update";
|
|
415
|
+
configOptions: AcpSessionConfigOption[];
|
|
277
416
|
} | {
|
|
278
417
|
sessionUpdate: "session_info_update";
|
|
279
418
|
title?: string | null;
|
|
280
419
|
updatedAt?: string | null;
|
|
281
420
|
};
|
|
421
|
+
/** 客户端可列给人选的斜杠命令(`/<name>`);`input.hint` 是参数提示。 */
|
|
422
|
+
export interface AcpAvailableCommand {
|
|
423
|
+
name: string;
|
|
424
|
+
description: string;
|
|
425
|
+
input?: {
|
|
426
|
+
hint: string;
|
|
427
|
+
} | null;
|
|
428
|
+
}
|
|
282
429
|
export interface AcpSessionNotification {
|
|
283
430
|
sessionId: string;
|
|
284
431
|
update: AcpSessionUpdate;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* ACP(Agent Client Protocol)v1 的子集类型(docs/wave5-plan.md §5.1,D14)。[W5-E]
|
|
3
3
|
*
|
|
4
4
|
* 手写、零依赖;只收 ama 作为客户端(驱动外部 Agent)与服务端(`ama --mode acp`)两侧用到的部分:
|
|
5
|
-
* `initialize`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode`、
|
|
6
|
-
* `session/update`(含 `usage_update`)、`session/request_permission
|
|
7
|
-
*
|
|
5
|
+
* `initialize`、`authenticate`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode|set_config_option`、
|
|
6
|
+
* `session/update`(含 `usage_update`)、`session/request_permission`、`elicitation/create`、`$/cancel_request`。
|
|
7
|
+
* 字段名与规范一致(camelCase),未知字段一律保留不报错;`_meta` 不解释(ama 自己发的只用 {@link ACP_META_KEY})。
|
|
8
8
|
*
|
|
9
9
|
* 不声明 `fs` / `terminal` 客户端能力(与 Armadra Q5 一致):外部 Agent 自己读写、自己跑命令,
|
|
10
10
|
* 用它自己的权限策略;它要问人的才经 `session/request_permission` 回到 ama。
|
|
@@ -13,6 +13,8 @@ export const ACP_PROTOCOL_VERSION = 1;
|
|
|
13
13
|
/** 方法名(客户端 → Agent 的请求 / 通知,Agent → 客户端的请求 / 通知)。 */
|
|
14
14
|
export const ACP_METHODS = {
|
|
15
15
|
initialize: "initialize",
|
|
16
|
+
/** ama 不实现(只给 terminal 型认证方法,规范要求这类方法不经 authenticate)。 */
|
|
17
|
+
authenticate: "authenticate",
|
|
16
18
|
sessionNew: "session/new",
|
|
17
19
|
sessionLoad: "session/load",
|
|
18
20
|
sessionResume: "session/resume",
|
|
@@ -21,8 +23,12 @@ export const ACP_METHODS = {
|
|
|
21
23
|
sessionPrompt: "session/prompt",
|
|
22
24
|
sessionCancel: "session/cancel",
|
|
23
25
|
sessionSetMode: "session/set_mode",
|
|
26
|
+
sessionSetConfigOption: "session/set_config_option",
|
|
24
27
|
sessionUpdate: "session/update",
|
|
25
28
|
requestPermission: "session/request_permission",
|
|
29
|
+
elicitationCreate: "elicitation/create",
|
|
30
|
+
/** 协议级取消(双向通知,`requestId` 指对端发来的、尚未答复的请求)。 */
|
|
31
|
+
cancelRequest: "$/cancel_request",
|
|
26
32
|
};
|
|
27
33
|
/** JSON-RPC 错误码(规范沿用 JSON-RPC 2.0;`-32000` 为 ACP 的 auth_required)。 */
|
|
28
34
|
export const RPC_ERRORS = {
|
|
@@ -33,4 +39,8 @@ export const RPC_ERRORS = {
|
|
|
33
39
|
internalError: -32603,
|
|
34
40
|
authRequired: -32000,
|
|
35
41
|
resourceNotFound: -32002,
|
|
42
|
+
/** 请求被 `$/cancel_request` 撤回。 */
|
|
43
|
+
requestCancelled: -32800,
|
|
36
44
|
};
|
|
45
|
+
/** ama 自己的 `_meta` 命名空间(客户端不得假设其含义)。 */
|
|
46
|
+
export const ACP_META_KEY = "ama";
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* - 分帧复用 `modes/rpc/jsonl.ts`(只按 `\n` 切行、64 KiB 分片写、背压等 drain),写入串行;
|
|
6
6
|
* - 双向:本端可发请求 / 通知,也处理对端的请求(`onRequest` 抛 {@link RpcError} 即回错误)与通知;
|
|
7
7
|
* - `jsonrpcField: false` 时不写 `"jsonrpc":"2.0"`(Codex app-server 的线上形状),读取两种都认;
|
|
8
|
-
* - 连接关闭(输入流结束)时所有挂起的请求以 `connection_closed`
|
|
8
|
+
* - 连接关闭(输入流结束)时所有挂起的请求以 `connection_closed` 失败;
|
|
9
|
+
* - `cancelRequests: true`(ACP 两侧)时支持协议级取消 `$/cancel_request`:本端请求的 `signal` abort
|
|
10
|
+
* 会通知对端;对端撤回本端正在处理的请求时 abort 该请求的 `ctx.signal`,处理器此后抛错一律回 -32800。
|
|
11
|
+
* 缺省关闭(Codex app-server 不认),线上形状与之前逐字节相同。
|
|
9
12
|
*/
|
|
10
13
|
export type RpcId = string | number;
|
|
11
14
|
export declare class RpcError extends Error {
|
|
@@ -15,7 +18,7 @@ export declare class RpcError extends Error {
|
|
|
15
18
|
}
|
|
16
19
|
export interface IncomingRequestContext {
|
|
17
20
|
readonly id: RpcId;
|
|
18
|
-
/** 连接关闭时 abort。 */
|
|
21
|
+
/** 连接关闭时 abort;`cancelRequests` 开着时对端发 `$/cancel_request` 撤回本请求也 abort。 */
|
|
19
22
|
readonly signal: AbortSignal;
|
|
20
23
|
}
|
|
21
24
|
export interface JsonRpcPeerOptions {
|
|
@@ -29,10 +32,14 @@ export interface JsonRpcPeerOptions {
|
|
|
29
32
|
onClose?(): void;
|
|
30
33
|
/** 无法解析的行(不影响连接)。 */
|
|
31
34
|
onProtocolError?(line: string, reason: string): void;
|
|
35
|
+
/** 协议级取消(ACP `$/cancel_request`):缺省 false(Codex app-server 不认)。 */
|
|
36
|
+
cancelRequests?: boolean;
|
|
32
37
|
}
|
|
33
38
|
export declare class JsonRpcPeer {
|
|
34
39
|
private readonly options;
|
|
35
40
|
private readonly pending;
|
|
41
|
+
/** 正在处理的对端请求(只在 `cancelRequests` 时记录),供 `$/cancel_request` 撤回。 */
|
|
42
|
+
private readonly inflight;
|
|
36
43
|
private readonly reader;
|
|
37
44
|
private readonly lifetime;
|
|
38
45
|
private nextId;
|
|
@@ -43,7 +50,10 @@ export declare class JsonRpcPeer {
|
|
|
43
50
|
readonly closed: Promise<void>;
|
|
44
51
|
constructor(options: JsonRpcPeerOptions);
|
|
45
52
|
get isOpen(): boolean;
|
|
46
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* 发请求;`signal` abort 时本端不再等。`cancelRequests` 开着时同时给对端发
|
|
55
|
+
* `$/cancel_request { requestId }`(已发出的请求才发),否则不通知对端。
|
|
56
|
+
*/
|
|
47
57
|
request<T = unknown>(method: string, params?: unknown, signal?: AbortSignal): Promise<T>;
|
|
48
58
|
/** 输入流结束后仍可写(对端可能只关了自己的写端,还在读);写失败静默。 */
|
|
49
59
|
notify(method: string, params?: unknown): Promise<void>;
|