@ai-sdk/harness-claude-code 1.0.121 → 1.0.122

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.d.ts CHANGED
@@ -3,6 +3,7 @@ import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
3
3
  import { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
4
4
  import * as _ai_sdk_harness from '@ai-sdk/harness';
5
5
  import { HarnessV1Authentication, HarnessV1CredentialForwarding, HarnessV1PortEndpoint, HarnessV1MintBridgeTokenCallback, HarnessV1, HarnessV1BuiltinTool, HARNESS_V1_BUILTIN_TOOLS } from '@ai-sdk/harness';
6
+ import { SandboxChannelReconnectOptions } from '@ai-sdk/harness/utils';
6
7
 
7
8
  type ClaudeCodeAuthenticationMode = HarnessV1Authentication;
8
9
 
@@ -60,6 +61,13 @@ type ClaudeCodeHarnessSettings = {
60
61
  readonly portEndpoint?: HarnessV1PortEndpoint;
61
62
  /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */
62
63
  readonly startupTimeoutMs?: number;
64
+ /**
65
+ * Configures reconnection attempts after an established bridge connection
66
+ * drops. The reconnect window includes connection establishment and
67
+ * backoff delays. Defaults to 30 seconds with exponential backoff from 50
68
+ * milliseconds up to 2 seconds.
69
+ */
70
+ readonly reconnect?: SandboxChannelReconnectOptions;
63
71
  /**
64
72
  * Creates the authentication token used by the sandbox bridge. Defaults to
65
73
  * a random 32-byte hexadecimal token.
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  resolveSandboxHomeDir,
23
23
  SandboxChannel,
24
24
  shellQuote,
25
+ sleep,
25
26
  warnCredentialBrokeringUnavailable,
26
27
  waitForBridgeReady,
27
28
  withBridgeToken,
@@ -442,7 +443,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
442
443
  ]);
443
444
 
444
445
  // src/version.ts
445
- var VERSION = true ? "1.0.121" : "0.0.0-test";
446
+ var VERSION = true ? "1.0.122" : "0.0.0-test";
446
447
 
447
448
  // src/claude-code-harness.ts
448
449
  var CLAUDE_CODE_CLIENT_APP = `ai-sdk/harness-claude-code/${VERSION}`;
@@ -1164,10 +1165,13 @@ function createClaudeCode(settings = {}) {
1164
1165
  sessionId: startOpts.sessionId
1165
1166
  });
1166
1167
  let supportsUserMessageResponses = false;
1167
- const buildConnect = (endpoint2) => async () => {
1168
+ const buildConnect = (endpoint2) => async ({
1169
+ abortSignal
1170
+ }) => {
1168
1171
  return openBridgeWebSocket({
1169
1172
  endpoint: endpoint2,
1170
1173
  timeoutMs,
1174
+ abortSignal,
1171
1175
  onHello: (supportsResponses) => {
1172
1176
  supportsUserMessageResponses = supportsResponses;
1173
1177
  }
@@ -1189,7 +1193,8 @@ function createClaudeCode(settings = {}) {
1189
1193
  outboundSchema: outboundMessageSchema,
1190
1194
  initialLastSeenEventId: coords.lastSeenEventId,
1191
1195
  onDiagnostic,
1192
- onBridgeError
1196
+ onBridgeError,
1197
+ reconnect: settings.reconnect
1193
1198
  });
1194
1199
  await attachChannel.open(isContinue ? { resume: true } : void 0);
1195
1200
  return createSession({
@@ -1303,6 +1308,7 @@ function createClaudeCode(settings = {}) {
1303
1308
  outboundSchema: outboundMessageSchema,
1304
1309
  onDiagnostic,
1305
1310
  onBridgeError,
1311
+ reconnect: settings.reconnect,
1306
1312
  // In replay mode the respawned bridge reloaded the finished turn from
1307
1313
  // disk; seed the cursor and resume so it streams the tail (incl.
1308
1314
  // `finish`) rather than starting empty.
@@ -1388,9 +1394,15 @@ function openWebSocketAndWaitForBridgeHello({
1388
1394
  endpoint,
1389
1395
  openTimeoutMs,
1390
1396
  getHelloTimeoutMs,
1397
+ abortSignal,
1391
1398
  onHello
1392
1399
  }) {
1393
1400
  return new Promise((resolve2, reject) => {
1401
+ const abortReason = () => abortSignal.reason ?? new Error("WebSocket connection aborted");
1402
+ if (abortSignal.aborted) {
1403
+ reject(abortReason());
1404
+ return;
1405
+ }
1394
1406
  const ws = new WebSocket(endpoint.url, {
1395
1407
  headers: endpoint.headers == null ? void 0 : { ...endpoint.headers }
1396
1408
  });
@@ -1399,6 +1411,7 @@ function openWebSocketAndWaitForBridgeHello({
1399
1411
  let settled = false;
1400
1412
  let openTimer;
1401
1413
  let helloTimer;
1414
+ let onAbort;
1402
1415
  const cleanup = ({
1403
1416
  keepTerminationListeners = false
1404
1417
  } = {}) => {
@@ -1406,6 +1419,9 @@ function openWebSocketAndWaitForBridgeHello({
1406
1419
  if (helloTimer) clearTimeout(helloTimer);
1407
1420
  ws.off("open", onOpen);
1408
1421
  ws.off("message", onMessage);
1422
+ if (onAbort != null) {
1423
+ abortSignal.removeEventListener("abort", onAbort);
1424
+ }
1409
1425
  if (!keepTerminationListeners) {
1410
1426
  ws.off("close", onClose);
1411
1427
  ws.off("error", onError);
@@ -1474,6 +1490,7 @@ function openWebSocketAndWaitForBridgeHello({
1474
1490
  cleanup();
1475
1491
  };
1476
1492
  const onError = (err) => settle(err);
1493
+ onAbort = () => settle(abortReason());
1477
1494
  openTimer = setTimeout(
1478
1495
  () => settle(new Error(`WebSocket open timed out after ${openTimeoutMs}ms`)),
1479
1496
  openTimeoutMs
@@ -1483,17 +1500,19 @@ function openWebSocketAndWaitForBridgeHello({
1483
1500
  ws.on("message", onMessage);
1484
1501
  ws.on("close", onClose);
1485
1502
  ws.on("error", onError);
1503
+ abortSignal.addEventListener("abort", onAbort, { once: true });
1486
1504
  });
1487
1505
  }
1488
1506
  async function openBridgeWebSocket({
1489
1507
  endpoint,
1490
1508
  timeoutMs,
1509
+ abortSignal,
1491
1510
  onHello
1492
1511
  }) {
1493
1512
  const deadline = Date.now() + timeoutMs;
1494
1513
  let attempt = 0;
1495
1514
  let lastError;
1496
- while (Date.now() < deadline) {
1515
+ while (!abortSignal.aborted && Date.now() < deadline) {
1497
1516
  attempt++;
1498
1517
  try {
1499
1518
  const remaining = Math.max(1, deadline - Date.now());
@@ -1501,15 +1520,25 @@ async function openBridgeWebSocket({
1501
1520
  endpoint,
1502
1521
  openTimeoutMs: Math.min(1e4, remaining),
1503
1522
  getHelloTimeoutMs: () => Math.min(5e3, Math.max(1, deadline - Date.now())),
1523
+ abortSignal,
1504
1524
  onHello
1505
1525
  });
1506
1526
  } catch (err) {
1527
+ if (abortSignal.aborted) {
1528
+ throw abortSignal.reason ?? new Error("WebSocket connection aborted");
1529
+ }
1507
1530
  lastError = err;
1508
1531
  const remaining = deadline - Date.now();
1509
1532
  if (remaining <= 0) break;
1510
- await sleep(Math.min(250 * attempt, 1e3, remaining));
1533
+ await sleep({
1534
+ ms: Math.min(250 * attempt, 1e3, remaining),
1535
+ abortSignal
1536
+ });
1511
1537
  }
1512
1538
  }
1539
+ if (abortSignal.aborted) {
1540
+ throw abortSignal.reason ?? new Error("WebSocket connection aborted");
1541
+ }
1513
1542
  throw new Error(
1514
1543
  `claude-code bridge did not complete WebSocket handshake within ${timeoutMs}ms after ${attempt} attempt(s). Last error: ${formatUnknownError(lastError)}`
1515
1544
  );
@@ -1526,12 +1555,6 @@ function webSocketMessageToString(raw) {
1526
1555
  }
1527
1556
  return String(raw);
1528
1557
  }
1529
- function sleep(ms) {
1530
- return new Promise((resolve2) => {
1531
- const timer = setTimeout(resolve2, ms);
1532
- timer.unref?.();
1533
- });
1534
- }
1535
1558
  function formatUnknownError(error) {
1536
1559
  if (error instanceof Error) return error.message;
1537
1560
  return String(error);