@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 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
- implementation: {
31
- type: 'npm',
32
- mode: 'simple',
30
+ source: {
31
+ type: 'npm-simple',
33
32
  packageName: '@agentclientprotocol/codex-acp',
34
- version: '1.1.4',
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.0" : "0.0.0-test";
15
+ var VERSION = true ? "1.0.2" : "0.0.0-test";
16
16
  function createHostToolMCPServer({
17
17
  tools: tools2,
18
18
  revision = 1,
@@ -820,10 +820,25 @@ function createACPBridgeError({
820
820
  stage,
821
821
  cause
822
822
  }) {
823
- const error = new Error(`ACP ${stage} failed.`);
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.dynamic = true;
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({ emit, builtinTools });
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
- forwardAgentStderr({ child, turn });
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
  }) {