@ai-sdk/harness-claude-code 1.0.80 → 1.0.82

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/dist/index.js CHANGED
@@ -8,12 +8,15 @@ import {
8
8
  } from "@ai-sdk/harness";
9
9
  import {
10
10
  classifyDiskLog,
11
+ experimental_createBridgeUserMessageSubmitter,
11
12
  createBridgeErrorHandler,
12
13
  createBridgeStartupError,
13
14
  drainBridgeProcessStream,
14
15
  forwardBridgeProcessStream,
16
+ getRestrictedSandboxSession,
15
17
  markBridgeStarting,
16
18
  maskSandboxCredentials,
19
+ resolveSandboxDefaultWorkingDirectory,
17
20
  resolveSandboxHomeDir,
18
21
  SandboxChannel,
19
22
  shellQuote,
@@ -263,7 +266,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
263
266
  ]);
264
267
 
265
268
  // src/version.ts
266
- var VERSION = true ? "1.0.80" : "0.0.0-test";
269
+ var VERSION = true ? "1.0.82" : "0.0.0-test";
267
270
 
268
271
  // src/claude-code-harness.ts
269
272
  var CLAUDE_CODE_CLIENT_APP = `ai-sdk/harness-claude-code/${VERSION}`;
@@ -889,13 +892,30 @@ function createClaudeCode(settings = {}) {
889
892
  getBootstrap: getClaudeCodeBootstrap,
890
893
  doStart: async (startOpts) => {
891
894
  const sandboxSession = startOpts.sandboxSession;
895
+ const toolSafeSandboxSession = getRestrictedSandboxSession(sandboxSession);
896
+ const sandboxId = "id" in sandboxSession ? sandboxSession.id : void 0;
897
+ validateBasicSandboxSettings({
898
+ sandboxSession,
899
+ port: settings.port,
900
+ portEndpoint: settings.portEndpoint
901
+ });
902
+ if (settings.mintBridgeToken != null && sandboxId == null) {
903
+ throw new HarnessCapabilityUnsupportedError({
904
+ harnessId: "claude-code",
905
+ message: "The Claude Code harness cannot use `mintBridgeToken` with a sandbox session that does not expose an id."
906
+ });
907
+ }
908
+ const defaultWorkingDirectory = await resolveSandboxDefaultWorkingDirectory({
909
+ sandboxSession,
910
+ abortSignal: startOpts.abortSignal
911
+ });
892
912
  const authenticationMode = resolveClaudeCodeAuthenticationMode(
893
913
  settings.auth
894
914
  );
895
915
  const resolvedAuthEnvironment = resolveClaudeCodeEnv(settings.auth);
896
916
  let authEnv = resolvedAuthEnvironment;
897
917
  let sandboxTurnEnvironment = settings.env;
898
- if (sandboxSession.addRequestTransformations != null) {
918
+ if ("addRequestTransformations" in sandboxSession && sandboxSession.addRequestTransformations != null) {
899
919
  const requestTransformations = createClaudeCodeRequestTransformations(
900
920
  {
901
921
  ...resolvedAuthEnvironment,
@@ -919,10 +939,8 @@ function createClaudeCode(settings = {}) {
919
939
  } else {
920
940
  warnCredentialBrokeringUnavailable();
921
941
  }
922
- const session = sandboxSession.restricted();
923
- const sandboxId = sandboxSession.id;
924
942
  const bootstrapDir = posix.resolve(
925
- sandboxSession.defaultWorkingDirectory,
943
+ defaultWorkingDirectory,
926
944
  CLAUDE_CODE_BOOTSTRAP_DIR
927
945
  );
928
946
  const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
@@ -930,7 +948,7 @@ function createClaudeCode(settings = {}) {
930
948
  const isContinue = startOpts.continueFrom != null;
931
949
  const coords = isResume ? lifecycleState?.data?.bridge : void 0;
932
950
  const workDir = startOpts.sessionWorkDir;
933
- const sessionDataDir = `${sandboxSession.defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
951
+ const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
934
952
  const bridgeStateDir = `${sessionDataDir}/bridge`;
935
953
  const timeoutMs = settings.startupTimeoutMs ?? 12e4;
936
954
  const report = startOpts.observability?.report;
@@ -944,14 +962,22 @@ function createClaudeCode(settings = {}) {
944
962
  harnessId: "claude-code",
945
963
  sessionId: startOpts.sessionId
946
964
  });
965
+ let supportsUserMessageResponses = false;
947
966
  const buildConnect = (endpoint2) => async () => {
948
- return openBridgeWebSocket({ endpoint: endpoint2, timeoutMs });
967
+ return openBridgeWebSocket({
968
+ endpoint: endpoint2,
969
+ timeoutMs,
970
+ onHello: (supportsResponses) => {
971
+ supportsUserMessageResponses = supportsResponses;
972
+ }
973
+ });
949
974
  };
950
975
  if (coords) {
951
976
  try {
952
- const endpoint2 = await sandboxSession.getPortEndpoint({
953
- port: coords.port,
954
- protocol: "ws"
977
+ const endpoint2 = await resolveBridgeEndpoint({
978
+ sandboxSession,
979
+ override: settings.portEndpoint,
980
+ port: coords.port
955
981
  });
956
982
  const attachEndpoint = withBridgeToken({
957
983
  endpoint: endpoint2,
@@ -987,7 +1013,8 @@ function createClaudeCode(settings = {}) {
987
1013
  permissionMode: startOpts.permissionMode,
988
1014
  builtinToolFiltering: startOpts.builtinToolFiltering,
989
1015
  skills: startOpts.skills ?? [],
990
- mcpServers: settings.mcpServers
1016
+ mcpServers: settings.mcpServers,
1017
+ supportsUserMessageResponses: () => supportsUserMessageResponses
991
1018
  });
992
1019
  } catch {
993
1020
  }
@@ -995,7 +1022,7 @@ function createClaudeCode(settings = {}) {
995
1022
  let respawnStrategy = isResume ? "rerun" : void 0;
996
1023
  if (coords && isContinue) {
997
1024
  const logRaw = await Promise.resolve(
998
- session.readTextFile({
1025
+ toolSafeSandboxSession.readTextFile({
999
1026
  path: `${bridgeStateDir}/event-log.ndjson`,
1000
1027
  abortSignal: startOpts.abortSignal
1001
1028
  })
@@ -1005,10 +1032,13 @@ function createClaudeCode(settings = {}) {
1005
1032
  }
1006
1033
  }
1007
1034
  const sandboxHomeDir = startOpts.skills && startOpts.skills.length > 0 ? await resolveSandboxHomeDir({
1008
- sandbox: session,
1035
+ sandbox: toolSafeSandboxSession,
1009
1036
  abortSignal: startOpts.abortSignal
1010
1037
  }) : void 0;
1011
- const port = resolveBridgePort(sandboxSession, settings.port);
1038
+ const port = resolveBridgePort({
1039
+ sandboxSession,
1040
+ override: settings.port
1041
+ });
1012
1042
  const token = settings.mintBridgeToken == null ? randomBytes(32).toString("hex") : settings.mintBridgeToken(sandboxId);
1013
1043
  const env = {
1014
1044
  ...authEnv,
@@ -1025,7 +1055,7 @@ function createClaudeCode(settings = {}) {
1025
1055
  ...respawnStrategy === "replay" ? { BRIDGE_REPLAY_FROM_DISK: "1" } : {}
1026
1056
  };
1027
1057
  if (respawnStrategy === void 0) {
1028
- await session.run({
1058
+ await toolSafeSandboxSession.run({
1029
1059
  command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
1030
1060
  abortSignal: startOpts.abortSignal
1031
1061
  });
@@ -1034,7 +1064,7 @@ function createClaudeCode(settings = {}) {
1034
1064
  throw new Error("Unable to resolve sandbox HOME directory.");
1035
1065
  }
1036
1066
  await writeClaudeCodeSkills({
1037
- sandbox: session,
1067
+ sandbox: toolSafeSandboxSession,
1038
1068
  homeDir: sandboxHomeDir,
1039
1069
  skills: startOpts.skills,
1040
1070
  abortSignal: startOpts.abortSignal
@@ -1042,12 +1072,12 @@ function createClaudeCode(settings = {}) {
1042
1072
  }
1043
1073
  }
1044
1074
  await markBridgeStarting({
1045
- sandbox: session,
1075
+ sandbox: toolSafeSandboxSession,
1046
1076
  bridgeStateDir,
1047
1077
  bridgeType: "claude-code",
1048
1078
  abortSignal: startOpts.abortSignal
1049
1079
  });
1050
- const proc = await session.spawn({
1080
+ const proc = await toolSafeSandboxSession.spawn({
1051
1081
  command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)}`,
1052
1082
  env,
1053
1083
  abortSignal: startOpts.abortSignal
@@ -1062,7 +1092,7 @@ function createClaudeCode(settings = {}) {
1062
1092
  void bridgeStartupStderrDone;
1063
1093
  const { port: boundPort } = await waitForBridgeReady({
1064
1094
  proc,
1065
- sandbox: session,
1095
+ sandbox: toolSafeSandboxSession,
1066
1096
  bridgeStateDir,
1067
1097
  bridgeType: "claude-code",
1068
1098
  timeoutMs,
@@ -1083,9 +1113,10 @@ function createClaudeCode(settings = {}) {
1083
1113
  })
1084
1114
  });
1085
1115
  void drainBridgeProcessStream(proc.stdout);
1086
- const endpoint = await sandboxSession.getPortEndpoint({
1087
- port: boundPort,
1088
- protocol: "ws"
1116
+ const endpoint = await resolveBridgeEndpoint({
1117
+ sandboxSession,
1118
+ override: settings.portEndpoint,
1119
+ port: boundPort
1089
1120
  });
1090
1121
  const bridgeEndpoint = withBridgeToken({ endpoint, token });
1091
1122
  const channel = new SandboxChannel({
@@ -1120,19 +1151,58 @@ function createClaudeCode(settings = {}) {
1120
1151
  permissionMode: startOpts.permissionMode,
1121
1152
  builtinToolFiltering: startOpts.builtinToolFiltering,
1122
1153
  skills: startOpts.skills ?? [],
1123
- mcpServers: settings.mcpServers
1154
+ mcpServers: settings.mcpServers,
1155
+ supportsUserMessageResponses: () => supportsUserMessageResponses
1124
1156
  });
1125
1157
  }
1126
1158
  };
1127
1159
  }
1128
- function resolveBridgePort(sandboxSession, override) {
1160
+ function resolveBridgePort({
1161
+ sandboxSession,
1162
+ override
1163
+ }) {
1129
1164
  if (override !== void 0) return override;
1130
- if (sandboxSession.ports.length > 0) return sandboxSession.ports[0];
1165
+ if ("ports" in sandboxSession && sandboxSession.ports.length > 0) {
1166
+ return sandboxSession.ports[0];
1167
+ }
1131
1168
  throw new HarnessCapabilityUnsupportedError({
1132
1169
  harnessId: "claude-code",
1133
1170
  message: "The claude-code harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createClaudeCode({ port })`."
1134
1171
  });
1135
1172
  }
1173
+ function validateBasicSandboxSettings({
1174
+ sandboxSession,
1175
+ port,
1176
+ portEndpoint
1177
+ }) {
1178
+ if ("getPortEndpoint" in sandboxSession) return;
1179
+ if (port == null) {
1180
+ throw new HarnessCapabilityUnsupportedError({
1181
+ harnessId: "claude-code",
1182
+ message: "The Claude Code harness requires an explicit `port` when using a basic sandbox session."
1183
+ });
1184
+ }
1185
+ if (portEndpoint == null) {
1186
+ throw new HarnessCapabilityUnsupportedError({
1187
+ harnessId: "claude-code",
1188
+ message: "The Claude Code harness requires an explicit `portEndpoint` when using a basic sandbox session."
1189
+ });
1190
+ }
1191
+ }
1192
+ async function resolveBridgeEndpoint({
1193
+ sandboxSession,
1194
+ override,
1195
+ port
1196
+ }) {
1197
+ if (override != null) return override;
1198
+ if ("getPortEndpoint" in sandboxSession) {
1199
+ return sandboxSession.getPortEndpoint({ port, protocol: "ws" });
1200
+ }
1201
+ throw new HarnessCapabilityUnsupportedError({
1202
+ harnessId: "claude-code",
1203
+ message: "The Claude Code harness requires an explicit `portEndpoint` when using a basic sandbox session."
1204
+ });
1205
+ }
1136
1206
  async function writeClaudeCodeSkills({
1137
1207
  sandbox,
1138
1208
  homeDir,
@@ -1152,7 +1222,8 @@ async function writeClaudeCodeSkills({
1152
1222
  function openWebSocketAndWaitForBridgeHello({
1153
1223
  endpoint,
1154
1224
  openTimeoutMs,
1155
- getHelloTimeoutMs
1225
+ getHelloTimeoutMs,
1226
+ onHello
1156
1227
  }) {
1157
1228
  return new Promise((resolve, reject) => {
1158
1229
  const ws = new WebSocket(endpoint.url, {
@@ -1218,6 +1289,8 @@ function openWebSocketAndWaitForBridgeHello({
1218
1289
  if (!parsed.success || settled) return;
1219
1290
  const value = parsed.value;
1220
1291
  if (typeof value === "object" && value !== null && !Array.isArray(value) && value.type === "bridge-hello") {
1292
+ const capabilities = value.capabilities;
1293
+ onHello(capabilities?.experimental_userMessageResponses === true);
1221
1294
  sawBridgeHello = true;
1222
1295
  tryResolve();
1223
1296
  }
@@ -1242,7 +1315,8 @@ function openWebSocketAndWaitForBridgeHello({
1242
1315
  }
1243
1316
  async function openBridgeWebSocket({
1244
1317
  endpoint,
1245
- timeoutMs
1318
+ timeoutMs,
1319
+ onHello
1246
1320
  }) {
1247
1321
  const deadline = Date.now() + timeoutMs;
1248
1322
  let attempt = 0;
@@ -1254,7 +1328,8 @@ async function openBridgeWebSocket({
1254
1328
  return await openWebSocketAndWaitForBridgeHello({
1255
1329
  endpoint,
1256
1330
  openTimeoutMs: Math.min(1e4, remaining),
1257
- getHelloTimeoutMs: () => Math.min(5e3, Math.max(1, deadline - Date.now()))
1331
+ getHelloTimeoutMs: () => Math.min(5e3, Math.max(1, deadline - Date.now())),
1332
+ onHello
1258
1333
  });
1259
1334
  } catch (err) {
1260
1335
  lastError = err;
@@ -1316,7 +1391,8 @@ function createSession({
1316
1391
  permissionMode,
1317
1392
  builtinToolFiltering,
1318
1393
  skills,
1319
- mcpServers
1394
+ mcpServers,
1395
+ supportsUserMessageResponses
1320
1396
  }) {
1321
1397
  let stopped = false;
1322
1398
  let stopPromise;
@@ -1328,6 +1404,11 @@ function createSession({
1328
1404
  pendingResolve = resolve;
1329
1405
  pendingReject = reject;
1330
1406
  });
1407
+ const userMessageSubmitter = supportsUserMessageResponses() ? experimental_createBridgeUserMessageSubmitter({
1408
+ send: (message) => channel.send(message),
1409
+ onResponse: (listener) => channel.on("user-message-response", listener),
1410
+ onReconnect: (listener) => channel.onReconnect(listener)
1411
+ }) : void 0;
1331
1412
  const unsubs = [];
1332
1413
  const forward = (event) => {
1333
1414
  try {
@@ -1339,12 +1420,14 @@ function createSession({
1339
1420
  const settleSuccess = () => {
1340
1421
  if (isSettled) return;
1341
1422
  isSettled = true;
1423
+ userMessageSubmitter?.close();
1342
1424
  for (const u of unsubs) u();
1343
1425
  pendingResolve();
1344
1426
  };
1345
1427
  const settleError = (err) => {
1346
1428
  if (isSettled) return;
1347
1429
  isSettled = true;
1430
+ userMessageSubmitter?.close(err);
1348
1431
  for (const u of unsubs) u();
1349
1432
  pendingReject(err);
1350
1433
  };
@@ -1428,8 +1511,10 @@ function createSession({
1428
1511
  reason: input.reason
1429
1512
  });
1430
1513
  },
1431
- submitUserMessage: async (text) => {
1432
- channel.send({ type: "user-message", text });
1514
+ ...userMessageSubmitter == null ? {} : {
1515
+ submitUserMessage: async (text) => {
1516
+ await userMessageSubmitter.submit(text);
1517
+ }
1433
1518
  },
1434
1519
  done
1435
1520
  };
@@ -1541,7 +1626,7 @@ function createSession({
1541
1626
  port: bridgePort,
1542
1627
  token: bridgeToken,
1543
1628
  lastSeenEventId,
1544
- sandboxId
1629
+ ...sandboxId == null ? {} : { sandboxId }
1545
1630
  }
1546
1631
  }
1547
1632
  };
@@ -1655,7 +1740,7 @@ function createSession({
1655
1740
  port: bridgePort,
1656
1741
  token: bridgeToken,
1657
1742
  lastSeenEventId,
1658
- sandboxId
1743
+ ...sandboxId == null ? {} : { sandboxId }
1659
1744
  }
1660
1745
  }
1661
1746
  };