@mono-agent/agent-runtime 0.20.11 → 0.21.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/ARCHITECTURE.md +50 -11
- package/MIGRATION.md +288 -26
- package/README.md +352 -477
- package/package.json +13 -44
- package/src/agent/tool-bloat.js +145 -9
- package/src/agent/tools/agent-tool.js +108 -9
- package/src/agent/tools/bash.js +11 -26
- package/src/agent/tools/codex-subscription-search.js +123 -29
- package/src/agent/tools/exec.js +10 -2
- package/src/agent/tools/index.js +7 -0
- package/src/agent/tools/monitor.js +149 -0
- package/src/agent/tools/pi-bridge.js +123 -19
- package/src/agent/tools/shared/bash-environment.js +31 -0
- package/src/agent/tools/shared/monitors.js +293 -0
- package/src/agent/tools/shared/path-resolver.js +25 -6
- package/src/agent/tools/shared/process-jobs.js +6 -1
- package/src/agent/tools/shared/process-runner.js +26 -6
- package/src/agent/tools/shared/tool-context.js +8 -0
- package/src/agent/tools/web-access-interstitial.js +70 -0
- package/src/agent/tools/web-browser-render.js +83 -58
- package/src/agent/tools/web-controller.js +112 -21
- package/src/agent/tools/web-document-extractor.js +379 -0
- package/src/agent/tools/web-fetch.js +271 -243
- package/src/agent/tools/web-request.js +65 -0
- package/src/agent/tools/web-search-output.js +165 -0
- package/src/agent/tools/web-search-state.js +75 -0
- package/src/agent/tools/web-search.js +532 -71
- package/src/ai/cost.js +13 -68
- package/src/ai/failure.js +3 -3
- package/src/ai/index.js +5 -17
- package/src/ai/observer.js +8 -0
- package/src/ai/pi-interop.js +221 -1
- package/src/ai/pi-oauth-compat.js +1 -1
- package/src/ai/provider-check.js +131 -0
- package/src/ai/providers/codex/app-server-client.js +592 -0
- package/src/ai/providers/pi-models.js +18 -10
- package/src/ai/providers/pi-native/compaction-driver.js +94 -42
- package/src/ai/providers/pi-native/compaction-summary.js +140 -0
- package/src/ai/providers/pi-native/harness-adapter.js +376 -0
- package/src/ai/providers/pi-native/prompt-cache-diagnostics.js +103 -0
- package/src/ai/providers/pi-native/provider-attribution.js +102 -0
- package/src/ai/providers/pi-native/result-builder.js +38 -14
- package/src/ai/providers/pi-native/session-lifecycle.js +253 -55
- package/src/ai/providers/pi-native/stream-subscriber.js +52 -6
- package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
- package/src/ai/providers/pi-native/turn-runner.js +279 -28
- package/src/ai/providers/pi-native.js +206 -61
- package/src/ai/runtime/capabilities.js +11 -56
- package/src/ai/runtime/live-input-events.js +250 -54
- package/src/ai/runtime/model-refs.js +118 -153
- package/src/ai/runtime/registry.js +22 -56
- package/src/ai/runtime/router.js +76 -417
- package/src/ai/runtime/session-liveness.js +3 -4
- package/src/ai/runtime/sessions.js +4 -5
- package/src/ai/runtime/tool-policy.js +0 -2
- package/src/ai/tool-lifecycle.js +32 -18
- package/src/ai/types.js +37 -112
- package/src/index.js +0 -6
- package/src/runtime.js +29 -16
- package/types/agent/tool-bloat.d.ts +1 -1
- package/types/agent/tools/agent-tool.d.ts +4 -2
- package/types/agent/tools/bash.d.ts +5 -3
- package/types/agent/tools/codex-subscription-search.d.ts +7 -3
- package/types/agent/tools/exec.d.ts +5 -3
- package/types/agent/tools/index.d.ts +1 -0
- package/types/agent/tools/monitor.d.ts +47 -0
- package/types/agent/tools/pi-bridge.d.ts +7 -4
- package/types/agent/tools/shared/bash-environment.d.ts +4 -0
- package/types/agent/tools/shared/monitors.d.ts +98 -0
- package/types/agent/tools/shared/process-jobs.d.ts +5 -1
- package/types/agent/tools/shared/process-runner.d.ts +14 -4
- package/types/agent/tools/shared/tool-context.d.ts +2 -0
- package/types/agent/tools/web-access-interstitial.d.ts +23 -0
- package/types/agent/tools/web-browser-render.d.ts +4 -1
- package/types/agent/tools/web-controller.d.ts +4 -2
- package/types/agent/tools/web-document-extractor.d.ts +27 -0
- package/types/agent/tools/web-fetch.d.ts +19 -24
- package/types/agent/tools/web-request.d.ts +20 -0
- package/types/agent/tools/web-search-output.d.ts +31 -0
- package/types/agent/tools/web-search-state.d.ts +21 -0
- package/types/agent/tools/web-search.d.ts +10 -45
- package/types/ai/cost.d.ts +1 -2
- package/types/ai/index.d.ts +2 -4
- package/types/ai/observer.d.ts +6 -0
- package/types/ai/pi-interop.d.ts +81 -0
- package/types/ai/provider-check.d.ts +53 -0
- package/types/ai/providers/codex/app-server-client.d.ts +37 -0
- package/types/ai/providers/pi-native/compaction-driver.d.ts +2 -1
- package/types/ai/providers/pi-native/compaction-summary.d.ts +19 -0
- package/types/ai/providers/pi-native/harness-adapter.d.ts +58 -0
- package/types/ai/providers/pi-native/prompt-cache-diagnostics.d.ts +3 -0
- package/types/ai/providers/pi-native/provider-attribution.d.ts +26 -0
- package/types/ai/providers/pi-native/result-builder.d.ts +14 -4
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +25 -6
- package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
- package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
- package/types/ai/providers/pi-native/turn-runner.d.ts +68 -10
- package/types/ai/providers/pi-native.d.ts +21 -4
- package/types/ai/runtime/capabilities.d.ts +21 -70
- package/types/ai/runtime/live-input-events.d.ts +32 -8
- package/types/ai/runtime/model-refs.d.ts +0 -24
- package/types/ai/runtime/router.d.ts +3 -10
- package/types/ai/runtime/tool-policy.d.ts +0 -2
- package/types/ai/tool-lifecycle.d.ts +4 -3
- package/types/ai/types.d.ts +162 -256
- package/types/index.d.ts +0 -1
- package/src/ai/providers/acp-client.js +0 -1149
- package/src/ai/providers/acp-privacy.js +0 -124
- package/src/ai/providers/acp-public.js +0 -21
- package/src/ai/providers/acp-session-tokens.js +0 -282
- package/src/ai/providers/acp-transport.js +0 -356
- package/src/ai/providers/acp.js +0 -543
- package/src/ai/providers/claude-cli.js +0 -883
- package/src/ai/providers/claude-sandbox.js +0 -71
- package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
- package/src/ai/providers/claude-sdk-discovery.js +0 -352
- package/src/ai/providers/claude-sdk.js +0 -1127
- package/src/ai/providers/claude-subagent-activity.js +0 -719
- package/src/ai/providers/claude-subagents.js +0 -88
- package/src/ai/providers/codex-app.js +0 -2946
- package/src/ai/providers/opencode-app.js +0 -1109
- package/src/ai/providers/opencode-discovery.js +0 -39
- package/src/ai/providers/opencode-server.js +0 -508
- package/src/ai/runtime/context-windows.js +0 -46
- package/src/ai/runtime/fast-mode.js +0 -8
- package/src/ai/streaming/codex-events.js +0 -146
- package/src/ai/streaming/opencode-events.js +0 -59
- package/types/ai/providers/acp-client.d.ts +0 -227
- package/types/ai/providers/acp-privacy.d.ts +0 -25
- package/types/ai/providers/acp-public.d.ts +0 -7
- package/types/ai/providers/acp-session-tokens.d.ts +0 -41
- package/types/ai/providers/acp-transport.d.ts +0 -45
- package/types/ai/providers/acp.d.ts +0 -93
- package/types/ai/providers/claude-cli.d.ts +0 -305
- package/types/ai/providers/claude-sandbox.d.ts +0 -79
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
- package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
- package/types/ai/providers/claude-sdk.d.ts +0 -138
- package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
- package/types/ai/providers/claude-subagents.d.ts +0 -18
- package/types/ai/providers/codex-app.d.ts +0 -151
- package/types/ai/providers/opencode-app.d.ts +0 -96
- package/types/ai/providers/opencode-discovery.d.ts +0 -4
- package/types/ai/providers/opencode-server.d.ts +0 -20
- package/types/ai/runtime/context-windows.d.ts +0 -9
- package/types/ai/runtime/fast-mode.d.ts +0 -2
- package/types/ai/streaming/codex-events.d.ts +0 -40
- package/types/ai/streaming/opencode-events.d.ts +0 -42
package/src/ai/providers/acp.js
DELETED
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import { isAbsolute } from "node:path";
|
|
4
|
-
import { runtimeCapabilities } from "../runtime/capabilities.js";
|
|
5
|
-
import { resolveSandboxPolicy } from "../../agent/tools/shared/tool-context.js";
|
|
6
|
-
import {
|
|
7
|
-
AcpClientError,
|
|
8
|
-
connectAcpProfile,
|
|
9
|
-
} from "./acp-client.js";
|
|
10
|
-
import {
|
|
11
|
-
decodeAcpProviderSessionId,
|
|
12
|
-
encodeAcpProviderSessionId,
|
|
13
|
-
validateAcpProviderSessionId,
|
|
14
|
-
validateAcpSessionTokenKey,
|
|
15
|
-
} from "./acp-session-tokens.js";
|
|
16
|
-
import {
|
|
17
|
-
ownAcpSessionUpdateKind,
|
|
18
|
-
sanitizeAcpHostValueWithStatus,
|
|
19
|
-
} from "./acp-privacy.js";
|
|
20
|
-
import { toolLifecycleMetadata } from "../tool-lifecycle.js";
|
|
21
|
-
|
|
22
|
-
/** @param {any} callback @param {any} event */
|
|
23
|
-
function emit(callback, event) {
|
|
24
|
-
try { callback?.(event); } catch { /* observers cannot break a provider turn */ }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** @param {any} value */
|
|
28
|
-
function jsonSafe(value) {
|
|
29
|
-
try { return JSON.parse(JSON.stringify(value)); } catch { return null; }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** @param {any} content @returns {string} */
|
|
33
|
-
function textFromContent(content) {
|
|
34
|
-
if (typeof content === "string") return content;
|
|
35
|
-
if (!Array.isArray(content)) return content == null ? "" : String(content);
|
|
36
|
-
return content
|
|
37
|
-
.filter((block) => block?.type === "text" && typeof block.text === "string")
|
|
38
|
-
.map((block) => block.text)
|
|
39
|
-
.join("");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** @param {any} block @param {any} promptCapabilities */
|
|
43
|
-
function normalizePromptBlock(block, promptCapabilities) {
|
|
44
|
-
if (!block || typeof block !== "object" || Array.isArray(block)) {
|
|
45
|
-
throw new AcpClientError("invalid_request", "ACP prompt content blocks must be objects.");
|
|
46
|
-
}
|
|
47
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
48
|
-
return { type: "text", text: block.text };
|
|
49
|
-
}
|
|
50
|
-
// Resource links are ACP baseline content and must survive normalization.
|
|
51
|
-
if (block.type === "resource_link"
|
|
52
|
-
&& typeof block.uri === "string"
|
|
53
|
-
&& typeof block.name === "string") {
|
|
54
|
-
return jsonSafe(block);
|
|
55
|
-
}
|
|
56
|
-
if (block.type === "image" && promptCapabilities?.image === true) return jsonSafe(block);
|
|
57
|
-
if (block.type === "audio" && promptCapabilities?.audio === true) return jsonSafe(block);
|
|
58
|
-
if (block.type === "resource" && promptCapabilities?.embeddedContext === true) return jsonSafe(block);
|
|
59
|
-
throw new AcpClientError("capability_missing", `ACP agent cannot accept prompt content type '${block.type || "unknown"}'.`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Convert runtime history to one ACP user prompt. A fresh ACP session has no
|
|
64
|
-
* protocol method for importing arbitrary prior assistant turns, so the
|
|
65
|
-
* client-owned path sends them as a labelled transcript while preserving
|
|
66
|
-
* baseline resource_link blocks as real blocks. A resumed session sends only
|
|
67
|
-
* the latest user content because the agent already owns prior history.
|
|
68
|
-
* @param {string} systemPrompt
|
|
69
|
-
* @param {any[]} messages
|
|
70
|
-
* @param {{includeHistory: boolean, includeSystem: boolean, promptCapabilities: any}} options
|
|
71
|
-
*/
|
|
72
|
-
function runtimePrompt(systemPrompt, messages, options) {
|
|
73
|
-
const source = Array.isArray(messages) ? messages : [];
|
|
74
|
-
const selected = options.includeHistory
|
|
75
|
-
? source
|
|
76
|
-
: (() => {
|
|
77
|
-
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
78
|
-
if (source[index]?.role === "user") return [source[index]];
|
|
79
|
-
}
|
|
80
|
-
return source.length > 0 ? [source[source.length - 1]] : [];
|
|
81
|
-
})();
|
|
82
|
-
/** @type {any[]} */
|
|
83
|
-
const blocks = [];
|
|
84
|
-
if (options.includeSystem && systemPrompt.trim()) {
|
|
85
|
-
blocks.push({ type: "text", text: `[System]\n${systemPrompt}` });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
selected.forEach((message, messageIndex) => {
|
|
89
|
-
const role = typeof message?.role === "string" ? message.role : "user";
|
|
90
|
-
const content = message?.content;
|
|
91
|
-
if (Array.isArray(content)) {
|
|
92
|
-
let labelled = false;
|
|
93
|
-
for (const block of content) {
|
|
94
|
-
const normalized = normalizePromptBlock(block, options.promptCapabilities);
|
|
95
|
-
if (normalized.type === "text") {
|
|
96
|
-
blocks.push({
|
|
97
|
-
...normalized,
|
|
98
|
-
text: `${labelled ? "" : `[${role}]\n`}${normalized.text}`,
|
|
99
|
-
});
|
|
100
|
-
labelled = true;
|
|
101
|
-
} else {
|
|
102
|
-
blocks.push(normalized);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (content.length === 0) blocks.push({ type: "text", text: `[${role}]\n` });
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
const text = textFromContent(content);
|
|
109
|
-
if (text || messageIndex === selected.length - 1) {
|
|
110
|
-
blocks.push({ type: "text", text: `[${role}]\n${text}` });
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
if (blocks.length === 0) blocks.push({ type: "text", text: "" });
|
|
114
|
-
return blocks;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** @param {unknown} value @param {string} key */
|
|
118
|
-
function ownValue(value, key) {
|
|
119
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || !Object.hasOwn(value, key)) {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
return /** @type {Record<string, unknown>} */ (value)[key];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** @param {any} update @param {any} state */
|
|
126
|
-
function normalizeUpdate(update, state) {
|
|
127
|
-
const protocolBody = update.update;
|
|
128
|
-
const updateKind = ownAcpSessionUpdateKind(protocolBody);
|
|
129
|
-
if (updateKind === null) {
|
|
130
|
-
state.protocolError ||= new AcpClientError("protocol", "ACP session update has no valid own discriminator.");
|
|
131
|
-
return [{ type: "acp_session_update_rejected", reason: "invalid_discriminator" }];
|
|
132
|
-
}
|
|
133
|
-
const sanitized = sanitizeAcpHostValueWithStatus(protocolBody, [update.sessionId]);
|
|
134
|
-
if (sanitized.truncated) {
|
|
135
|
-
state.protocolError ||= new AcpClientError(
|
|
136
|
-
"protocol",
|
|
137
|
-
"ACP session update exceeded safe host normalization limits.",
|
|
138
|
-
);
|
|
139
|
-
return [{ type: "acp_session_update_rejected", reason: "normalization_limit" }];
|
|
140
|
-
}
|
|
141
|
-
const publicBody = sanitized.value;
|
|
142
|
-
/** @param {unknown} value */
|
|
143
|
-
const publicValue = (value) => sanitizeAcpHostValueWithStatus(value, [update.sessionId]).value;
|
|
144
|
-
const raw = {
|
|
145
|
-
type: "acp_session_update",
|
|
146
|
-
update: jsonSafe({ ...publicBody, sessionUpdate: updateKind }),
|
|
147
|
-
};
|
|
148
|
-
/** @type {any[]} */
|
|
149
|
-
const events = [raw];
|
|
150
|
-
switch (updateKind) {
|
|
151
|
-
case "agent_message_chunk": {
|
|
152
|
-
const content = publicValue(ownValue(protocolBody, "content"));
|
|
153
|
-
if (content?.type === "text" && typeof content.text === "string") state.text.push(content.text);
|
|
154
|
-
events.push({ type: "assistant", message: { content: [jsonSafe(content)] } });
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
case "agent_thought_chunk": {
|
|
158
|
-
const content = publicValue(ownValue(protocolBody, "content"));
|
|
159
|
-
if (content?.type === "text" && typeof content.text === "string") state.thinking.push(content.text);
|
|
160
|
-
events.push({
|
|
161
|
-
type: "assistant",
|
|
162
|
-
message: { content: [{ type: "thinking", text: content?.type === "text" ? content.text : "" }] },
|
|
163
|
-
});
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
case "tool_call":
|
|
167
|
-
events.push({
|
|
168
|
-
type: "assistant",
|
|
169
|
-
message: {
|
|
170
|
-
content: [{
|
|
171
|
-
type: "tool_use",
|
|
172
|
-
id: publicValue(ownValue(protocolBody, "toolCallId")),
|
|
173
|
-
name: publicValue(ownValue(protocolBody, "name"))
|
|
174
|
-
|| publicValue(ownValue(protocolBody, "title"))
|
|
175
|
-
|| "acp_tool",
|
|
176
|
-
input: jsonSafe(publicValue(ownValue(protocolBody, "rawInput") ?? {})),
|
|
177
|
-
}],
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
break;
|
|
181
|
-
case "tool_call_update":
|
|
182
|
-
if (["completed", "failed"].includes(/** @type {any} */ (ownValue(protocolBody, "status")))) {
|
|
183
|
-
const rawOutput = ownValue(protocolBody, "rawOutput") ?? ownValue(protocolBody, "content") ?? "";
|
|
184
|
-
const output = publicValue(rawOutput);
|
|
185
|
-
events.push({
|
|
186
|
-
type: "user",
|
|
187
|
-
message: {
|
|
188
|
-
content: [{
|
|
189
|
-
type: "tool_result",
|
|
190
|
-
tool_use_id: publicValue(ownValue(protocolBody, "toolCallId")),
|
|
191
|
-
content: typeof output === "string" ? output : JSON.stringify(jsonSafe(output)),
|
|
192
|
-
is_error: ownValue(protocolBody, "status") === "failed",
|
|
193
|
-
tool_lifecycle: toolLifecycleMetadata(ownValue(protocolBody, "status") === "failed"
|
|
194
|
-
? { state: "error", failure_kind: "runtime_error", detail_code: "acp_tool_failed" }
|
|
195
|
-
: { state: "success" }),
|
|
196
|
-
}],
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
break;
|
|
201
|
-
case "usage_update":
|
|
202
|
-
state.usage = {
|
|
203
|
-
totalTokens: Number(ownValue(protocolBody, "used")) || 0,
|
|
204
|
-
contextWindow: Number(ownValue(protocolBody, "size")) || 0,
|
|
205
|
-
cost: publicValue(ownValue(protocolBody, "cost")) || null,
|
|
206
|
-
};
|
|
207
|
-
events.push({
|
|
208
|
-
type: "context_usage",
|
|
209
|
-
model: state.model,
|
|
210
|
-
source: "acp",
|
|
211
|
-
context: {
|
|
212
|
-
used: Number(ownValue(protocolBody, "used")) || 0,
|
|
213
|
-
window: Number(ownValue(protocolBody, "size")) || 0,
|
|
214
|
-
},
|
|
215
|
-
cost: publicValue(ownValue(protocolBody, "cost")) || null,
|
|
216
|
-
});
|
|
217
|
-
break;
|
|
218
|
-
case "plan":
|
|
219
|
-
case "plan_update":
|
|
220
|
-
case "plan_removed":
|
|
221
|
-
events.push({
|
|
222
|
-
type: "plan",
|
|
223
|
-
source: "acp",
|
|
224
|
-
update: jsonSafe({ ...publicBody, sessionUpdate: updateKind }),
|
|
225
|
-
});
|
|
226
|
-
break;
|
|
227
|
-
default:
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
return events;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** @param {any} descriptor @param {any} req */
|
|
234
|
-
function workspaceFor(descriptor, req) {
|
|
235
|
-
if (descriptor.workspaceOwner === "agent") return descriptor.workspacePath;
|
|
236
|
-
const cwd = req.cwd || descriptor.workspacePath || descriptor.cwd || process.cwd();
|
|
237
|
-
if (typeof cwd !== "string" || !isAbsolute(cwd)) {
|
|
238
|
-
throw new AcpClientError("invalid_request", "ACP runtime cwd must be absolute.");
|
|
239
|
-
}
|
|
240
|
-
return cwd;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** @param {any} descriptor */
|
|
244
|
-
function sessionConfig(descriptor) {
|
|
245
|
-
const config = descriptor.sessionConfig || {};
|
|
246
|
-
return {
|
|
247
|
-
additionalDirectories: [...(config.additionalDirectories || [])],
|
|
248
|
-
mcpServers: descriptor.mcpOwner === "agent" ? [] : [...(config.mcpServers || [])],
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/** @param {any} connection @param {string} sessionId @param {any} setupResponse @param {any} descriptor */
|
|
253
|
-
async function applyClientConfiguration(connection, sessionId, setupResponse, descriptor) {
|
|
254
|
-
if (descriptor.configurationOwner === "agent") return { modeApplied: false, configOptionsApplied: [] };
|
|
255
|
-
const config = descriptor.sessionConfig || {};
|
|
256
|
-
let modes = setupResponse?.modes || null;
|
|
257
|
-
let configOptions = Array.isArray(setupResponse?.configOptions) ? setupResponse.configOptions : [];
|
|
258
|
-
let modeApplied = false;
|
|
259
|
-
const applied = [];
|
|
260
|
-
if (config.modeId !== undefined) {
|
|
261
|
-
const available = modes?.availableModes?.some((mode) => mode?.id === config.modeId) === true;
|
|
262
|
-
if (!available) throw new AcpClientError("capability_missing", "Configured ACP session mode was not advertised.");
|
|
263
|
-
await connection.setSessionMode(sessionId, config.modeId);
|
|
264
|
-
modeApplied = true;
|
|
265
|
-
}
|
|
266
|
-
for (const [configId, value] of Object.entries(config.configOptions || {})) {
|
|
267
|
-
const option = configOptions.find((candidate) => candidate?.id === configId);
|
|
268
|
-
if (!option) throw new AcpClientError("capability_missing", "Configured ACP session option was not advertised.");
|
|
269
|
-
if (option.type === "boolean") {
|
|
270
|
-
if (typeof value !== "boolean") {
|
|
271
|
-
throw new AcpClientError("invalid_profile", "ACP boolean config option requires a boolean value.");
|
|
272
|
-
}
|
|
273
|
-
} else {
|
|
274
|
-
if (typeof value !== "string" || !selectValues(option.options).includes(value)) {
|
|
275
|
-
throw new AcpClientError("invalid_profile", "ACP select config option value was not advertised.");
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
const response = await connection.setSessionConfigOption(sessionId, configId, value);
|
|
279
|
-
configOptions = Array.isArray(response?.configOptions) ? response.configOptions : configOptions;
|
|
280
|
-
applied.push(configId);
|
|
281
|
-
}
|
|
282
|
-
return { modeApplied, configOptionsApplied: applied };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/** @param {any[]} options */
|
|
286
|
-
function selectValues(options) {
|
|
287
|
-
if (!Array.isArray(options)) return [];
|
|
288
|
-
const values = [];
|
|
289
|
-
for (const item of options) {
|
|
290
|
-
if (typeof item?.value === "string") values.push(item.value);
|
|
291
|
-
if (Array.isArray(item?.options)) values.push(...selectValues(item.options));
|
|
292
|
-
}
|
|
293
|
-
return values;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/** @param {any} connection @param {any} descriptor @param {any} req @param {string} profileId @param {Uint8Array} key @param {(notification:any)=>void} onUpdate */
|
|
297
|
-
async function openSession(connection, descriptor, req, profileId, key, onUpdate) {
|
|
298
|
-
const cwd = workspaceFor(descriptor, req);
|
|
299
|
-
const config = sessionConfig(descriptor);
|
|
300
|
-
const baseRequest = { cwd, ...config };
|
|
301
|
-
if (!req.providerSessionId) {
|
|
302
|
-
const response = await connection.newSession(baseRequest);
|
|
303
|
-
return { sessionId: response.sessionId, response, resumed: false, resumeMethod: null };
|
|
304
|
-
}
|
|
305
|
-
const decoded = decodeAcpProviderSessionId(req.providerSessionId, key);
|
|
306
|
-
if (decoded.profileId !== profileId) {
|
|
307
|
-
throw new AcpClientError("invalid_session_id", "ACP provider session belongs to a different profile.");
|
|
308
|
-
}
|
|
309
|
-
const remove = connection.onSessionUpdate(decoded.sessionId, onUpdate);
|
|
310
|
-
try {
|
|
311
|
-
const strategy = descriptor.sessionConfig?.resumeStrategy || "auto";
|
|
312
|
-
if (strategy === "resume" || (strategy === "auto" && connection.hasCapability("resume"))) {
|
|
313
|
-
if (!connection.hasCapability("resume")) {
|
|
314
|
-
throw new AcpClientError("capability_missing", "ACP agent did not advertise session/resume.");
|
|
315
|
-
}
|
|
316
|
-
const response = await connection.resumeSession({ sessionId: decoded.sessionId, ...baseRequest });
|
|
317
|
-
return { sessionId: decoded.sessionId, response, resumed: true, resumeMethod: "resume" };
|
|
318
|
-
}
|
|
319
|
-
if (strategy === "load" || (strategy === "auto" && connection.hasCapability("load"))) {
|
|
320
|
-
if (!connection.hasCapability("load")) {
|
|
321
|
-
throw new AcpClientError("capability_missing", "ACP agent did not advertise session/load.");
|
|
322
|
-
}
|
|
323
|
-
const response = await connection.loadSession({ sessionId: decoded.sessionId, ...baseRequest });
|
|
324
|
-
return { sessionId: decoded.sessionId, response, resumed: true, resumeMethod: "load" };
|
|
325
|
-
}
|
|
326
|
-
if (strategy === "auto") {
|
|
327
|
-
const response = await connection.newSession(baseRequest);
|
|
328
|
-
return {
|
|
329
|
-
sessionId: response.sessionId,
|
|
330
|
-
response,
|
|
331
|
-
resumed: false,
|
|
332
|
-
resumeMethod: "new_fallback",
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
throw new AcpClientError("invalid_profile", "Invalid ACP resume strategy.");
|
|
336
|
-
} finally {
|
|
337
|
-
remove();
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/** @param {unknown} error @param {boolean} aborted */
|
|
342
|
-
function failureFor(error, aborted) {
|
|
343
|
-
const coded = /** @type {any} */ (error);
|
|
344
|
-
if (aborted || coded?.code === "cancelled") return { cancelled: true, failureKind: null, message: null };
|
|
345
|
-
if (coded?.code === "timeout") return { cancelled: false, failureKind: "timeout", message: coded.message };
|
|
346
|
-
if (coded?.code === "spawn") return { cancelled: false, failureKind: "spawn", message: coded.message };
|
|
347
|
-
return {
|
|
348
|
-
cancelled: false,
|
|
349
|
-
failureKind: "provider_protocol",
|
|
350
|
-
message: error instanceof Error ? error.message : "ACP provider protocol failed.",
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Runtime bridge for one ACP v1 profile turn.
|
|
356
|
-
* @param {string} systemPrompt
|
|
357
|
-
* @param {any} req
|
|
358
|
-
*/
|
|
359
|
-
export async function generateAcpResponse(systemPrompt, req) {
|
|
360
|
-
const start = Date.now();
|
|
361
|
-
const profileId = req?.model?.model;
|
|
362
|
-
const reference = req?.model?.reference || `acp:${profileId || "unknown"}`;
|
|
363
|
-
const events = [];
|
|
364
|
-
const state = { text: [], thinking: [], model: reference, usage: null, protocolError: null };
|
|
365
|
-
const onEvent = req?.onEvent;
|
|
366
|
-
const capture = (event) => {
|
|
367
|
-
events.push(event);
|
|
368
|
-
emit(onEvent, event);
|
|
369
|
-
};
|
|
370
|
-
capture({ type: "provider_request_started", sdk: "acp", model: reference, runtime: "acp-stdio", timestamp: start });
|
|
371
|
-
let connection;
|
|
372
|
-
let sessionId = null;
|
|
373
|
-
let providerSessionId = null;
|
|
374
|
-
let setup = null;
|
|
375
|
-
let configuration = { modeApplied: false, configOptionsApplied: [] };
|
|
376
|
-
try {
|
|
377
|
-
const sessionTokenKey = validateAcpSessionTokenKey(req?.acpSessionTokenKey);
|
|
378
|
-
if (req?.providerSessionId != null) {
|
|
379
|
-
providerSessionId = validateAcpProviderSessionId(
|
|
380
|
-
req.providerSessionId,
|
|
381
|
-
profileId,
|
|
382
|
-
sessionTokenKey,
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
connection = await connectAcpProfile(profileId, {
|
|
386
|
-
resolveAcpProfile: req.resolveAcpProfile,
|
|
387
|
-
onAcpInteractionRequest: req.onAcpInteractionRequest,
|
|
388
|
-
sandbox: req.toolContext?.sandbox || req.sandbox,
|
|
389
|
-
sandboxPolicy: resolveSandboxPolicy(req.toolContext, req.sandboxPolicy),
|
|
390
|
-
sandboxEngine: req.sandboxEngine || req.toolContext?.sandboxEngine,
|
|
391
|
-
cwd: req.cwd,
|
|
392
|
-
signal: req.abortSignal,
|
|
393
|
-
context: { operation: "run", model: reference },
|
|
394
|
-
operation: "run",
|
|
395
|
-
acpSessionTokenKey: sessionTokenKey,
|
|
396
|
-
});
|
|
397
|
-
capture({
|
|
398
|
-
type: "capabilities_resolved",
|
|
399
|
-
sdk: "acp",
|
|
400
|
-
model: reference,
|
|
401
|
-
capabilitiesUsed: {
|
|
402
|
-
protocol_version: connection.initializeResult.protocolVersion,
|
|
403
|
-
agent_capabilities: jsonSafe(connection.initializeResult.agentCapabilities || {}),
|
|
404
|
-
},
|
|
405
|
-
});
|
|
406
|
-
const onUpdate = (notification) => {
|
|
407
|
-
for (const event of normalizeUpdate(notification, state)) capture(event);
|
|
408
|
-
};
|
|
409
|
-
setup = await openSession(
|
|
410
|
-
connection,
|
|
411
|
-
connection.descriptor,
|
|
412
|
-
req,
|
|
413
|
-
profileId,
|
|
414
|
-
sessionTokenKey,
|
|
415
|
-
onUpdate,
|
|
416
|
-
);
|
|
417
|
-
sessionId = setup.sessionId;
|
|
418
|
-
providerSessionId = setup.resumed && providerSessionId
|
|
419
|
-
? providerSessionId
|
|
420
|
-
: encodeAcpProviderSessionId(profileId, sessionId, sessionTokenKey);
|
|
421
|
-
configuration = await applyClientConfiguration(
|
|
422
|
-
connection,
|
|
423
|
-
sessionId,
|
|
424
|
-
setup.response,
|
|
425
|
-
connection.descriptor,
|
|
426
|
-
);
|
|
427
|
-
const remove = connection.onSessionUpdate(sessionId, onUpdate);
|
|
428
|
-
let promptResponse;
|
|
429
|
-
try {
|
|
430
|
-
promptResponse = await connection.prompt(
|
|
431
|
-
sessionId,
|
|
432
|
-
runtimePrompt(systemPrompt, req.messages || [], {
|
|
433
|
-
includeHistory: !setup.resumed,
|
|
434
|
-
includeSystem: !setup.resumed && connection.descriptor.configurationOwner === "client",
|
|
435
|
-
promptCapabilities: connection.initializeResult.agentCapabilities?.promptCapabilities || {},
|
|
436
|
-
}),
|
|
437
|
-
{ signal: req.abortSignal },
|
|
438
|
-
);
|
|
439
|
-
} finally {
|
|
440
|
-
remove();
|
|
441
|
-
}
|
|
442
|
-
if (state.protocolError) throw state.protocolError;
|
|
443
|
-
// PromptResponse.usage is explicitly unstable in ACP 1.3.0. The stable
|
|
444
|
-
// cumulative source is the latest top-level usage_update notification.
|
|
445
|
-
const usage = state.usage;
|
|
446
|
-
capture({
|
|
447
|
-
type: "provider_request_completed",
|
|
448
|
-
sdk: "acp",
|
|
449
|
-
model: reference,
|
|
450
|
-
runtime: "acp-stdio",
|
|
451
|
-
timestamp: Date.now(),
|
|
452
|
-
durationMs: Date.now() - start,
|
|
453
|
-
cancelled: promptResponse.stopReason === "cancelled",
|
|
454
|
-
});
|
|
455
|
-
const limited = promptResponse.stopReason === "max_tokens" || promptResponse.stopReason === "max_turn_requests";
|
|
456
|
-
return {
|
|
457
|
-
text: state.text.join("") || null,
|
|
458
|
-
thinking: state.thinking.join(""),
|
|
459
|
-
events,
|
|
460
|
-
usage: usage ? {
|
|
461
|
-
total_tokens: usage.totalTokens || null,
|
|
462
|
-
context_window: usage.contextWindow || null,
|
|
463
|
-
cost: usage.cost,
|
|
464
|
-
} : {},
|
|
465
|
-
durationMs: Date.now() - start,
|
|
466
|
-
numTurns: 1,
|
|
467
|
-
model: reference,
|
|
468
|
-
effort: req.effort || null,
|
|
469
|
-
sdk: "acp",
|
|
470
|
-
cancelled: promptResponse.stopReason === "cancelled",
|
|
471
|
-
error: limited ? `ACP agent stopped with ${promptResponse.stopReason}.` : null,
|
|
472
|
-
failureKind: limited ? "usage_limit" : null,
|
|
473
|
-
providerSessionId,
|
|
474
|
-
runtimeWarnings: [],
|
|
475
|
-
diagnostics: {
|
|
476
|
-
acp_protocol_version: connection.initializeResult.protocolVersion,
|
|
477
|
-
acp_profile_id: profileId,
|
|
478
|
-
acp_session_id_encoded: true,
|
|
479
|
-
acp_stop_reason: promptResponse.stopReason,
|
|
480
|
-
acp_resume_method: setup.resumeMethod,
|
|
481
|
-
acp_mode_applied: configuration.modeApplied,
|
|
482
|
-
acp_config_options_applied: configuration.configOptionsApplied,
|
|
483
|
-
},
|
|
484
|
-
capabilitiesUsed: {
|
|
485
|
-
session_resume: setup.resumed,
|
|
486
|
-
session_load: setup.resumeMethod === "load",
|
|
487
|
-
session_config: configuration.modeApplied || configuration.configOptionsApplied.length > 0,
|
|
488
|
-
mcp: connection.descriptor.mcpOwner === "client"
|
|
489
|
-
&& (connection.descriptor.sessionConfig?.mcpServers || []).length > 0,
|
|
490
|
-
},
|
|
491
|
-
structuredResult: undefined,
|
|
492
|
-
structuredResultSource: null,
|
|
493
|
-
};
|
|
494
|
-
} catch (error) {
|
|
495
|
-
const failure = failureFor(error, req?.abortSignal?.aborted === true);
|
|
496
|
-
capture({
|
|
497
|
-
type: "provider_request_completed",
|
|
498
|
-
sdk: "acp",
|
|
499
|
-
model: reference,
|
|
500
|
-
runtime: "acp-stdio",
|
|
501
|
-
timestamp: Date.now(),
|
|
502
|
-
durationMs: Date.now() - start,
|
|
503
|
-
cancelled: failure.cancelled,
|
|
504
|
-
});
|
|
505
|
-
return {
|
|
506
|
-
text: state.text.join("") || null,
|
|
507
|
-
thinking: state.thinking.join(""),
|
|
508
|
-
events,
|
|
509
|
-
usage: {},
|
|
510
|
-
durationMs: Date.now() - start,
|
|
511
|
-
numTurns: 0,
|
|
512
|
-
model: reference,
|
|
513
|
-
effort: req?.effort || null,
|
|
514
|
-
sdk: "acp",
|
|
515
|
-
cancelled: failure.cancelled,
|
|
516
|
-
error: failure.message,
|
|
517
|
-
errorDetails: failure.message ? {
|
|
518
|
-
acp_error_code: typeof error?.code === "string" ? error.code : "protocol",
|
|
519
|
-
} : null,
|
|
520
|
-
failureKind: failure.failureKind,
|
|
521
|
-
providerSessionId,
|
|
522
|
-
runtimeWarnings: [],
|
|
523
|
-
diagnostics: {
|
|
524
|
-
acp_profile_id: typeof profileId === "string" ? profileId : null,
|
|
525
|
-
acp_session_id_encoded: providerSessionId != null,
|
|
526
|
-
acp_stop_reason: failure.cancelled ? "cancelled" : "error",
|
|
527
|
-
},
|
|
528
|
-
capabilitiesUsed: {},
|
|
529
|
-
structuredResult: undefined,
|
|
530
|
-
structuredResultSource: null,
|
|
531
|
-
};
|
|
532
|
-
} finally {
|
|
533
|
-
await connection?.close().catch(() => {});
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
export const acpRuntimeBridge = {
|
|
538
|
-
id: "acp-stdio",
|
|
539
|
-
kind: "acp",
|
|
540
|
-
capabilities: runtimeCapabilities("acp"),
|
|
541
|
-
supports: (ref, options) => ref?.sdk === "acp" && options?.executionMode === "acp",
|
|
542
|
-
execute: generateAcpResponse,
|
|
543
|
-
};
|