@ai-sdk/harness-acp 1.0.0 → 1.0.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/CHANGELOG.md +22 -0
- package/README.md +5 -6
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +145 -39
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +137 -19
- package/dist/index.js +187 -110
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/acp-harness.ts +26 -60
- package/src/acp-tool-call.ts +124 -0
- package/src/index.ts +21 -3
- package/src/v1/acp-v1-bridge-protocol.ts +17 -1
- package/src/v1/acp-v1-harness.ts +111 -21
- package/src/v1/acp-v1-settings.ts +27 -20
- package/src/v1/acp-v1-turn-start-config.ts +3 -0
- package/src/v1/bridge/acp-diagnostics.ts +27 -1
- package/src/v1/bridge/agent-stderr-monitor.ts +52 -0
- package/src/v1/bridge/create-emit-stream-event.ts +8 -1
- package/src/v1/bridge/index.ts +66 -34
- package/src/v1/bridge/stream-translator.ts +62 -5
- package/src/v1/implementation.ts +73 -47
- package/src/v1/index.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @ai-sdk/harness-acp
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a03ff6c: feat(harness): add support for per-harness MCP servers
|
|
8
|
+
- fc3baaf: feat(harness): add optional `mintBridgeToken(sandboxId)` to harness settings to control the bridge token value
|
|
9
|
+
- Updated dependencies [401a4ba]
|
|
10
|
+
- Updated dependencies [a03ff6c]
|
|
11
|
+
- @ai-sdk/provider-utils@5.0.26
|
|
12
|
+
- @ai-sdk/harness@1.0.65
|
|
13
|
+
|
|
14
|
+
## 1.0.1
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 6f54e1d: feat(harness-acp): further simplify public `createACP()` API
|
|
19
|
+
- 15d0475: fix(harness-acp): defer auth resolution until session start to correctly recognize available credentials
|
|
20
|
+
- faaabb0: fix(harness-acp): improve bridge error handling to prevent infinite hang on certain errors
|
|
21
|
+
- Updated dependencies [81cd026]
|
|
22
|
+
- @ai-sdk/provider-utils@5.0.25
|
|
23
|
+
- @ai-sdk/harness@1.0.64
|
|
24
|
+
|
|
3
25
|
## 1.0.0
|
|
4
26
|
|
|
5
27
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -27,14 +27,13 @@ import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
|
|
|
27
27
|
|
|
28
28
|
const codexACP = createACP({
|
|
29
29
|
harnessId: 'acp-codex',
|
|
30
|
-
|
|
31
|
-
type: 'npm',
|
|
32
|
-
mode: 'simple',
|
|
30
|
+
source: {
|
|
31
|
+
type: 'npm-simple',
|
|
33
32
|
packageName: '@agentclientprotocol/codex-acp',
|
|
34
|
-
|
|
35
|
-
executable: 'codex-acp',
|
|
36
|
-
forwardEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'],
|
|
33
|
+
packageVersion: '1.1.4',
|
|
37
34
|
},
|
|
35
|
+
executable: 'codex-acp',
|
|
36
|
+
forwardEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'],
|
|
38
37
|
permissionModeMapping: {
|
|
39
38
|
'allow-reads': null,
|
|
40
39
|
'allow-edits': null,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
ListToolsRequestSchema,
|
|
13
13
|
McpError
|
|
14
14
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
-
var VERSION = true ? "1.0.
|
|
15
|
+
var VERSION = true ? "1.0.2" : "0.0.0-test";
|
|
16
16
|
function createHostToolMCPServer({
|
|
17
17
|
tools: tools2,
|
|
18
18
|
revision = 1,
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -820,10 +820,25 @@ function createACPBridgeError({
|
|
|
820
820
|
stage,
|
|
821
821
|
cause
|
|
822
822
|
}) {
|
|
823
|
-
const
|
|
823
|
+
const causeMessage = getErrorMessage({ error: cause });
|
|
824
|
+
const error = new Error(
|
|
825
|
+
causeMessage == null ? `ACP ${stage} failed.` : `ACP ${stage} failed: ${causeMessage}`
|
|
826
|
+
);
|
|
824
827
|
error.cause = cause;
|
|
825
828
|
return error;
|
|
826
829
|
}
|
|
830
|
+
function getErrorMessage({ error }) {
|
|
831
|
+
if (error instanceof Error) {
|
|
832
|
+
return error.message;
|
|
833
|
+
}
|
|
834
|
+
if (error != null && typeof error === "object" && "message" in error && typeof error.message === "string") {
|
|
835
|
+
return error.message;
|
|
836
|
+
}
|
|
837
|
+
if (typeof error === "string") {
|
|
838
|
+
return error;
|
|
839
|
+
}
|
|
840
|
+
return void 0;
|
|
841
|
+
}
|
|
827
842
|
function stripMetadata({ value }) {
|
|
828
843
|
if (Array.isArray(value)) {
|
|
829
844
|
return value.map((item) => stripMetadata({ value: item }));
|
|
@@ -836,9 +851,46 @@ function stripMetadata({ value }) {
|
|
|
836
851
|
return result;
|
|
837
852
|
}
|
|
838
853
|
|
|
854
|
+
// src/v1/bridge/agent-stderr-monitor.ts
|
|
855
|
+
import { stripVTControlCharacters } from "util";
|
|
856
|
+
function monitorACPAgentStderr({
|
|
857
|
+
stderr,
|
|
858
|
+
onStderrLine
|
|
859
|
+
}) {
|
|
860
|
+
let rejectFailure;
|
|
861
|
+
const failure = new Promise((_, reject) => {
|
|
862
|
+
rejectFailure = reject;
|
|
863
|
+
});
|
|
864
|
+
void failure.catch(() => {
|
|
865
|
+
});
|
|
866
|
+
void (async () => {
|
|
867
|
+
stderr.setEncoding("utf8");
|
|
868
|
+
let pending = "";
|
|
869
|
+
for await (const chunk of stderr) {
|
|
870
|
+
pending += chunk;
|
|
871
|
+
const lines = pending.split("\n");
|
|
872
|
+
pending = lines.pop() ?? "";
|
|
873
|
+
for (const line of lines) handleStderrLine({ line });
|
|
874
|
+
}
|
|
875
|
+
if (pending.length > 0) handleStderrLine({ line: pending });
|
|
876
|
+
})().catch((error) => rejectFailure(error));
|
|
877
|
+
return failure;
|
|
878
|
+
function handleStderrLine({ line }) {
|
|
879
|
+
if (line.length === 0) return;
|
|
880
|
+
onStderrLine(line);
|
|
881
|
+
const normalizedLine = stripVTControlCharacters(line);
|
|
882
|
+
if (normalizedLine.toLowerCase().includes("failed to deserialize responsestreamevent from stream")) {
|
|
883
|
+
rejectFailure(
|
|
884
|
+
new Error("ACP agent failed to deserialize a streamed response.")
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
839
890
|
// src/v1/bridge/stream-translator.ts
|
|
840
891
|
function createACPStreamTranslator({
|
|
841
892
|
emit,
|
|
893
|
+
emitToolCallCandidate,
|
|
842
894
|
builtinTools = []
|
|
843
895
|
}) {
|
|
844
896
|
let openBlock;
|
|
@@ -949,13 +1001,12 @@ function createACPStreamTranslator({
|
|
|
949
1001
|
if (builtin != null) {
|
|
950
1002
|
state.toolName = builtin.toolName;
|
|
951
1003
|
state.nativeName = builtin.nativeName != null && builtin.toolName !== builtin.nativeName ? builtin.nativeName : void 0;
|
|
952
|
-
state.dynamic = false;
|
|
953
1004
|
} else {
|
|
954
1005
|
state.toolName = createDynamicToolName({
|
|
955
1006
|
programmaticName,
|
|
956
1007
|
toolCallId: state.toolCallId
|
|
957
1008
|
});
|
|
958
|
-
state
|
|
1009
|
+
emitToolCallCandidate?.({ toolCall: createACPToolCall({ state }) });
|
|
959
1010
|
}
|
|
960
1011
|
emit({
|
|
961
1012
|
type: "tool-call",
|
|
@@ -963,7 +1014,6 @@ function createACPStreamTranslator({
|
|
|
963
1014
|
toolName: state.toolName,
|
|
964
1015
|
input: stringifyToolInput({ input: state.values.rawInput }),
|
|
965
1016
|
providerExecuted: true,
|
|
966
|
-
...state.dynamic ? { dynamic: true } : {},
|
|
967
1017
|
...state.nativeName == null ? {} : { nativeName: state.nativeName }
|
|
968
1018
|
});
|
|
969
1019
|
state.emittedCall = true;
|
|
@@ -978,8 +1028,7 @@ function createACPStreamTranslator({
|
|
|
978
1028
|
toolCallId: state.toolCallId,
|
|
979
1029
|
toolName: state.toolName,
|
|
980
1030
|
result: createToolResult({ state }),
|
|
981
|
-
...state.values.status === "failed" ? { isError: true } : {}
|
|
982
|
-
...state.dynamic ? { dynamic: true } : {}
|
|
1031
|
+
...state.values.status === "failed" ? { isError: true } : {}
|
|
983
1032
|
});
|
|
984
1033
|
state.emittedResult = true;
|
|
985
1034
|
pendingToolCallIds.delete(state.toolCallId);
|
|
@@ -1294,6 +1343,31 @@ function mergeToolUpdate({
|
|
|
1294
1343
|
});
|
|
1295
1344
|
if (programmaticName != null) state.values.name = programmaticName;
|
|
1296
1345
|
}
|
|
1346
|
+
function createACPToolCall({ state }) {
|
|
1347
|
+
const title = getStringProperty2({
|
|
1348
|
+
value: state.values,
|
|
1349
|
+
property: "title"
|
|
1350
|
+
});
|
|
1351
|
+
const kind = state.values.kind;
|
|
1352
|
+
const status = state.values.status;
|
|
1353
|
+
return {
|
|
1354
|
+
toolCallId: state.toolCallId,
|
|
1355
|
+
title: title ?? state.toolName ?? `Tool ${state.toolCallId}`,
|
|
1356
|
+
...isACPToolKind(kind) ? { kind } : {},
|
|
1357
|
+
...isACPToolCallStatus(status) ? { status } : {},
|
|
1358
|
+
...Array.isArray(state.values.content) ? { content: state.values.content } : {},
|
|
1359
|
+
...Array.isArray(state.values.locations) ? { locations: state.values.locations } : {},
|
|
1360
|
+
...Object.prototype.hasOwnProperty.call(state.values, "rawInput") ? { rawInput: state.values.rawInput } : {},
|
|
1361
|
+
...Object.prototype.hasOwnProperty.call(state.values, "rawOutput") ? { rawOutput: state.values.rawOutput } : {},
|
|
1362
|
+
...state.values._meta === null || isRecord3(state.values._meta) ? { _meta: state.values._meta } : {}
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
function isACPToolKind(value) {
|
|
1366
|
+
return value === "read" || value === "edit" || value === "delete" || value === "move" || value === "search" || value === "execute" || value === "think" || value === "fetch" || value === "switch_mode" || value === "other";
|
|
1367
|
+
}
|
|
1368
|
+
function isACPToolCallStatus(value) {
|
|
1369
|
+
return value === "pending" || value === "in_progress" || value === "completed" || value === "failed";
|
|
1370
|
+
}
|
|
1297
1371
|
function emitFileChanges({
|
|
1298
1372
|
state,
|
|
1299
1373
|
emit
|
|
@@ -1895,11 +1969,16 @@ function isTerminalToolUpdate(message) {
|
|
|
1895
1969
|
// src/v1/bridge/create-emit-stream-event.ts
|
|
1896
1970
|
function createEmitStreamEvent({
|
|
1897
1971
|
emit,
|
|
1972
|
+
emitToolCallCandidate,
|
|
1898
1973
|
builtinTools,
|
|
1899
1974
|
hostToolServerName,
|
|
1900
1975
|
hostTools
|
|
1901
1976
|
}) {
|
|
1902
|
-
const translator = createACPStreamTranslator({
|
|
1977
|
+
const translator = createACPStreamTranslator({
|
|
1978
|
+
emit,
|
|
1979
|
+
emitToolCallCandidate,
|
|
1980
|
+
builtinTools
|
|
1981
|
+
});
|
|
1903
1982
|
const correlation = createHostToolCorrelation({
|
|
1904
1983
|
emitSemanticUpdate: ({ message, rawUpdate }) => {
|
|
1905
1984
|
translator.update({
|
|
@@ -2713,6 +2792,7 @@ var implementation = await readImplementationDescriptor({
|
|
|
2713
2792
|
});
|
|
2714
2793
|
var bridgeConfiguration = await readACPBridgeEnvironment({ env: processEnv });
|
|
2715
2794
|
var child;
|
|
2795
|
+
var agentResponseStreamFailure;
|
|
2716
2796
|
var connection;
|
|
2717
2797
|
var session;
|
|
2718
2798
|
var recoveredSessionUpdates;
|
|
@@ -2767,6 +2847,12 @@ async function runTurn(start, turn) {
|
|
|
2767
2847
|
if (activeSession == null) {
|
|
2768
2848
|
throw new Error("ACP session initialization did not produce a session.");
|
|
2769
2849
|
}
|
|
2850
|
+
const activeAgentResponseStreamFailure = agentResponseStreamFailure;
|
|
2851
|
+
if (activeAgentResponseStreamFailure == null) {
|
|
2852
|
+
throw new Error(
|
|
2853
|
+
"ACP session initialization did not start stderr monitoring."
|
|
2854
|
+
);
|
|
2855
|
+
}
|
|
2770
2856
|
if (start.recoveryMode?.type === "lossy-rerun") {
|
|
2771
2857
|
const marker = {
|
|
2772
2858
|
type: "acp-recovery",
|
|
@@ -2848,6 +2934,12 @@ async function runTurn(start, turn) {
|
|
|
2848
2934
|
turn.emit({ type: "stream-start" });
|
|
2849
2935
|
const emitStreamEvent = createEmitStreamEvent({
|
|
2850
2936
|
emit: (event) => turn.emit(event),
|
|
2937
|
+
emitToolCallCandidate: ({ toolCall }) => {
|
|
2938
|
+
turn.emit({
|
|
2939
|
+
type: "acp-tool-call-candidate",
|
|
2940
|
+
toolCall
|
|
2941
|
+
});
|
|
2942
|
+
},
|
|
2851
2943
|
builtinTools: start.builtinTools,
|
|
2852
2944
|
hostToolServerName: HOST_TOOL_MCP_SERVER_NAME,
|
|
2853
2945
|
hostTools: start.tools ?? []
|
|
@@ -2883,7 +2975,8 @@ async function runTurn(start, turn) {
|
|
|
2883
2975
|
try {
|
|
2884
2976
|
message = await Promise.race([
|
|
2885
2977
|
activeSession.nextUpdate(),
|
|
2886
|
-
cancellationFailure
|
|
2978
|
+
cancellationFailure,
|
|
2979
|
+
activeAgentResponseStreamFailure
|
|
2887
2980
|
]);
|
|
2888
2981
|
} catch (error) {
|
|
2889
2982
|
for (const rawValue of streamCapture?.drainRawValues() ?? []) {
|
|
@@ -2932,7 +3025,8 @@ async function ensureSession({
|
|
|
2932
3025
|
providerAuthentication: bridgeConfiguration.providerAuthentication,
|
|
2933
3026
|
sessionMeta: bridgeConfiguration.sessionMeta,
|
|
2934
3027
|
permissionMode: start.permissionMode,
|
|
2935
|
-
permissionModeMapping: start.permissionModeMapping
|
|
3028
|
+
permissionModeMapping: start.permissionModeMapping,
|
|
3029
|
+
mcpServers: start.mcpServers
|
|
2936
3030
|
});
|
|
2937
3031
|
if (session != null) {
|
|
2938
3032
|
if (catalogRefreshError != null) throw catalogRefreshError;
|
|
@@ -2977,7 +3071,16 @@ async function ensureSession({
|
|
|
2977
3071
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2978
3072
|
}
|
|
2979
3073
|
);
|
|
2980
|
-
|
|
3074
|
+
agentResponseStreamFailure = monitorACPAgentStderr({
|
|
3075
|
+
stderr: child.stderr,
|
|
3076
|
+
onStderrLine: (line) => {
|
|
3077
|
+
turn.bridgeLog({
|
|
3078
|
+
level: "warn",
|
|
3079
|
+
subsystem: "acp.agent.stderr",
|
|
3080
|
+
message: line
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
});
|
|
2981
3084
|
const input = Writable.toWeb(child.stdin);
|
|
2982
3085
|
const output = Readable.toWeb(child.stdout);
|
|
2983
3086
|
const capturedStream = captureACPStream({
|
|
@@ -3020,6 +3123,10 @@ async function ensureSession({
|
|
|
3020
3123
|
authentication
|
|
3021
3124
|
});
|
|
3022
3125
|
}
|
|
3126
|
+
const externalMcpServers = createExternalMcpServers({
|
|
3127
|
+
mcpServers: start.mcpServers,
|
|
3128
|
+
initialization
|
|
3129
|
+
});
|
|
3023
3130
|
const tools = start.tools ?? [];
|
|
3024
3131
|
const catalogPath = `${bridgeStateDir}/host-tools.json`;
|
|
3025
3132
|
await writeFile2(catalogPath, JSON.stringify(tools), { mode: 384 });
|
|
@@ -3028,6 +3135,7 @@ async function ensureSession({
|
|
|
3028
3135
|
serverName: HOST_TOOL_MCP_SERVER_NAME
|
|
3029
3136
|
});
|
|
3030
3137
|
const mcpServers = [
|
|
3138
|
+
...externalMcpServers,
|
|
3031
3139
|
{
|
|
3032
3140
|
name: HOST_TOOL_MCP_SERVER_NAME,
|
|
3033
3141
|
command: process.execPath,
|
|
@@ -3129,6 +3237,33 @@ async function ensureSession({
|
|
|
3129
3237
|
});
|
|
3130
3238
|
sessionConfigurationFingerprint = fingerprint;
|
|
3131
3239
|
}
|
|
3240
|
+
function createExternalMcpServers({
|
|
3241
|
+
mcpServers,
|
|
3242
|
+
initialization
|
|
3243
|
+
}) {
|
|
3244
|
+
if (mcpServers == null) return [];
|
|
3245
|
+
return Object.entries(mcpServers).map(([name2, value]) => {
|
|
3246
|
+
if (!isRecord6(value)) {
|
|
3247
|
+
throw new Error(
|
|
3248
|
+
`ACP MCP server ${JSON.stringify(name2)} must be configured with an object value.`
|
|
3249
|
+
);
|
|
3250
|
+
}
|
|
3251
|
+
if (value.type === "acp") {
|
|
3252
|
+
throw new HarnessBridgeCapabilityUnsupportedError({
|
|
3253
|
+
harnessId: bridgeType,
|
|
3254
|
+
message: "ACP-transport MCP servers require client-side mcp/connect handling, which this harness does not provide."
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
const mcpCapabilities = initialization.agentCapabilities?.mcpCapabilities;
|
|
3258
|
+
if (value.type === "http" && mcpCapabilities?.http !== true || value.type === "sse" && mcpCapabilities?.sse !== true) {
|
|
3259
|
+
throw new HarnessBridgeCapabilityUnsupportedError({
|
|
3260
|
+
harnessId: bridgeType,
|
|
3261
|
+
message: `The ACP agent does not advertise support for ${value.type.toUpperCase()} MCP servers.`
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
3264
|
+
return { ...value, name: name2 };
|
|
3265
|
+
});
|
|
3266
|
+
}
|
|
3132
3267
|
function unknownUsage2() {
|
|
3133
3268
|
return {
|
|
3134
3269
|
inputTokens: {
|
|
@@ -3207,35 +3342,6 @@ function createChildEnvironment({
|
|
|
3207
3342
|
function isRecord6(value) {
|
|
3208
3343
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
3209
3344
|
}
|
|
3210
|
-
function forwardAgentStderr({
|
|
3211
|
-
child: child2,
|
|
3212
|
-
turn
|
|
3213
|
-
}) {
|
|
3214
|
-
void (async () => {
|
|
3215
|
-
child2.stderr.setEncoding("utf8");
|
|
3216
|
-
let pending = "";
|
|
3217
|
-
for await (const chunk of child2.stderr) {
|
|
3218
|
-
pending += chunk;
|
|
3219
|
-
const lines = pending.split("\n");
|
|
3220
|
-
pending = lines.pop() ?? "";
|
|
3221
|
-
for (const line of lines) {
|
|
3222
|
-
if (line.length === 0) continue;
|
|
3223
|
-
turn.bridgeLog({
|
|
3224
|
-
level: "warn",
|
|
3225
|
-
subsystem: "acp.agent.stderr",
|
|
3226
|
-
message: line
|
|
3227
|
-
});
|
|
3228
|
-
}
|
|
3229
|
-
}
|
|
3230
|
-
if (pending.length > 0) {
|
|
3231
|
-
turn.bridgeLog({
|
|
3232
|
-
level: "warn",
|
|
3233
|
-
subsystem: "acp.agent.stderr",
|
|
3234
|
-
message: pending
|
|
3235
|
-
});
|
|
3236
|
-
}
|
|
3237
|
-
})();
|
|
3238
|
-
}
|
|
3239
3345
|
async function readImplementationDescriptor({
|
|
3240
3346
|
path
|
|
3241
3347
|
}) {
|