@ai-sdk/harness-claude-code 1.0.97 → 1.0.99

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
@@ -1,5 +1,4 @@
1
1
  // src/claude-code-harness.ts
2
- import { randomBytes } from "crypto";
3
2
  import { posix } from "path";
4
3
  import {
5
4
  commonTool,
@@ -10,6 +9,7 @@ import {
10
9
  applyCredentialForwarding,
11
10
  classifyDiskLog,
12
11
  createSandboxCredentialEnvironment,
12
+ createBridgeToken,
13
13
  experimental_createBridgeUserMessageSubmitter,
14
14
  createBridgeErrorHandler,
15
15
  createBridgeStartupError,
@@ -23,6 +23,7 @@ import {
23
23
  shellQuote,
24
24
  warnCredentialBrokeringUnavailable,
25
25
  waitForBridgeReady,
26
+ withBridgeToken,
26
27
  writeSkills as writeHarnessSkills
27
28
  } from "@ai-sdk/harness/utils";
28
29
  import {
@@ -69,21 +70,21 @@ async function getClaudeCodeBootstrap() {
69
70
  return cachedBootstrap;
70
71
  }
71
72
  async function readBridgeAsset(name) {
72
- const candidates = [
73
- new URL(`./bridge/${name}`, import.meta.url),
74
- new URL(`../bridge/${name}`, import.meta.url)
75
- ];
76
- let lastErr;
77
- for (const url of candidates) {
78
- try {
79
- return await readFile(fileURLToPath(url), "utf8");
80
- } catch (err) {
81
- const code = err.code;
82
- if (code !== "ENOENT") throw err;
83
- lastErr = err;
84
- }
85
- }
86
- throw lastErr ?? new Error(`bridge asset not found: ${name}`);
73
+ return readFile(
74
+ fileURLToPath(
75
+ resolveBridgeAssetUrl({
76
+ name,
77
+ moduleUrl: import.meta.url
78
+ })
79
+ ),
80
+ "utf8"
81
+ );
82
+ }
83
+ function resolveBridgeAssetUrl({
84
+ name,
85
+ moduleUrl
86
+ }) {
87
+ return new URL(`./bridge/${name}`, moduleUrl);
87
88
  }
88
89
 
89
90
  // src/claude-code-auth.ts
@@ -258,7 +259,15 @@ var startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
258
259
  // Resume signal. When true, the bridge passes `{ continue: true }` to the
259
260
  // Claude SDK so the in-workdir thread state is rehydrated. The host sets this
260
261
  // on the first prompt after a cross-process resume.
261
- continue: z.boolean().optional()
262
+ continue: z.boolean().optional(),
263
+ // Exact conversation to resume, as the Claude SDK's `resume` option.
264
+ //
265
+ // Preferred over `continue` whenever the host knows which conversation it
266
+ // means: `continue` resolves to the most recent thread in the workdir, which
267
+ // is the wrong one as soon as anything else has run there since — including
268
+ // a resume that itself started a new thread. Mutually exclusive with
269
+ // `continue` in the SDK, so the bridge sends one or the other.
270
+ resumeSessionId: z.string().optional()
262
271
  });
263
272
  var inboundMessageSchema = z.discriminatedUnion("type", [
264
273
  startMessageSchema,
@@ -266,7 +275,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
266
275
  ]);
267
276
 
268
277
  // src/version.ts
269
- var VERSION = true ? "1.0.97" : "0.0.0-test";
278
+ var VERSION = true ? "1.0.99" : "0.0.0-test";
270
279
 
271
280
  // src/claude-code-harness.ts
272
281
  var CLAUDE_CODE_CLIENT_APP = `ai-sdk/harness-claude-code/${VERSION}`;
@@ -871,7 +880,18 @@ var claudeCodeBridgeCoordsSchema = z2.object({
871
880
  });
872
881
  var claudeCodeResumeStateSchema = z2.looseObject({
873
882
  bridge: claudeCodeBridgeCoordsSchema.optional(),
874
- sandboxCredentialEnvironment: z2.record(z2.string(), z2.string()).optional()
883
+ sandboxCredentialEnvironment: z2.record(z2.string(), z2.string()).optional(),
884
+ /**
885
+ * The exact Claude conversation to rehydrate on resume. Written by the
886
+ * adapter on `doStop()`/`doDetach()`/`doSuspendTurn()` from the session id
887
+ * the bridge observed, so a resume names the conversation instead of
888
+ * relying on the SDK's `continue` flag — "most recent thread in this
889
+ * workdir" — which silently picks the wrong one once a second thread
890
+ * exists there. Hosts that captured the id themselves (it is surfaced as
891
+ * `harnessMetadata['claude-code'].sessionId` on `finish` parts) may also
892
+ * set it explicitly.
893
+ */
894
+ claudeSessionId: z2.string().optional()
875
895
  });
876
896
  function createClaudeCode(settings = {}) {
877
897
  if (settings.mcpServers != null && Object.prototype.hasOwnProperty.call(settings.mcpServers, "harness-tools")) {
@@ -892,7 +912,6 @@ function createClaudeCode(settings = {}) {
892
912
  lifecycleStateSchema: claudeCodeResumeStateSchema,
893
913
  getBootstrap: getClaudeCodeBootstrap,
894
914
  doStart: async (startOpts) => {
895
- const model = startOpts.model ?? settings.model;
896
915
  const sandboxSession = startOpts.sandboxSession;
897
916
  const toolSafeSandboxSession = getRestrictedSandboxSession(sandboxSession);
898
917
  const sandboxId = "id" in sandboxSession ? sandboxSession.id : void 0;
@@ -965,6 +984,7 @@ function createClaudeCode(settings = {}) {
965
984
  defaultWorkingDirectory,
966
985
  CLAUDE_CODE_BOOTSTRAP_DIR
967
986
  );
987
+ const resumeSessionId = resumeData?.claudeSessionId;
968
988
  const workDir = startOpts.sessionWorkDir;
969
989
  const sandboxHomeDir = await resolveSandboxHomeDir({
970
990
  sandbox: toolSafeSandboxSession,
@@ -1016,11 +1036,12 @@ function createClaudeCode(settings = {}) {
1016
1036
  return createSession({
1017
1037
  sessionId: startOpts.sessionId,
1018
1038
  channel: attachChannel,
1039
+ ...resumeSessionId ? { resumeSessionId } : {},
1019
1040
  // The live bridge was spawned by another process; this one owns no
1020
1041
  // process handle. The session lifecycle method decides whether the
1021
1042
  // sandbox is left running, stopped, or destroyed.
1022
1043
  proc: void 0,
1023
- model,
1044
+ model: settings.model,
1024
1045
  maxTurns: settings.maxTurns,
1025
1046
  env: sandboxClaudeEnvironment,
1026
1047
  thinking,
@@ -1059,7 +1080,7 @@ function createClaudeCode(settings = {}) {
1059
1080
  sandboxSession,
1060
1081
  override: settings.port
1061
1082
  });
1062
- const token = settings.mintBridgeToken == null ? randomBytes(32).toString("hex") : settings.mintBridgeToken(sandboxId);
1083
+ const token = settings.mintBridgeToken == null ? createBridgeToken() : settings.mintBridgeToken(sandboxId);
1063
1084
  const env = {
1064
1085
  BRIDGE_CHANNEL_TOKEN: token,
1065
1086
  BRIDGE_WS_PORT: String(port),
@@ -1136,13 +1157,14 @@ function createClaudeCode(settings = {}) {
1136
1157
  sessionId: startOpts.sessionId,
1137
1158
  channel,
1138
1159
  proc,
1139
- model,
1160
+ model: settings.model,
1140
1161
  maxTurns: settings.maxTurns,
1141
1162
  env: sandboxClaudeEnvironment,
1142
1163
  thinking,
1143
1164
  effort: settings.effort,
1144
1165
  isResume: respawnStrategy !== void 0,
1145
1166
  continueOnFirstPrompt: respawnStrategy !== void 0,
1167
+ ...resumeSessionId ? { resumeSessionId } : {},
1146
1168
  rerunContinue: respawnStrategy === "rerun",
1147
1169
  bridgePort: boundPort,
1148
1170
  bridgeToken: token,
@@ -1363,14 +1385,6 @@ function webSocketMessageToString(raw) {
1363
1385
  }
1364
1386
  return String(raw);
1365
1387
  }
1366
- function withBridgeToken({
1367
- endpoint,
1368
- token
1369
- }) {
1370
- const bridgeUrl = new URL(endpoint.url);
1371
- bridgeUrl.searchParams.set("agent_bridge_token", token);
1372
- return { ...endpoint, url: bridgeUrl.toString() };
1373
- }
1374
1388
  function sleep(ms) {
1375
1389
  return new Promise((resolve) => {
1376
1390
  const timer = setTimeout(resolve, ms);
@@ -1392,6 +1406,7 @@ function createSession({
1392
1406
  effort,
1393
1407
  isResume,
1394
1408
  continueOnFirstPrompt,
1409
+ resumeSessionId,
1395
1410
  rerunContinue,
1396
1411
  bridgePort,
1397
1412
  bridgeToken,
@@ -1408,6 +1423,7 @@ function createSession({
1408
1423
  let stopped = false;
1409
1424
  let stopPromise;
1410
1425
  let pendingResumeFlag = continueOnFirstPrompt;
1426
+ let lastClaudeSessionId = resumeSessionId;
1411
1427
  const wireTurn = (turnOpts) => {
1412
1428
  let pendingResolve;
1413
1429
  let pendingReject;
@@ -1450,6 +1466,9 @@ function createSession({
1450
1466
  "reasoning-start",
1451
1467
  "reasoning-delta",
1452
1468
  "reasoning-end",
1469
+ "tool-input-start",
1470
+ "tool-input-delta",
1471
+ "tool-input-end",
1453
1472
  "tool-call",
1454
1473
  "tool-approval-request",
1455
1474
  "tool-result",
@@ -1465,6 +1484,10 @@ function createSession({
1465
1484
  }
1466
1485
  unsubs.push(
1467
1486
  channel.on("finish", (msg) => {
1487
+ const metadata = msg.harnessMetadata?.["claude-code"]?.sessionId;
1488
+ if (typeof metadata === "string" && metadata.length > 0) {
1489
+ lastClaudeSessionId = metadata;
1490
+ }
1468
1491
  forward(msg);
1469
1492
  settleSuccess();
1470
1493
  })
@@ -1533,7 +1556,6 @@ function createSession({
1533
1556
  return {
1534
1557
  sessionId,
1535
1558
  isResume,
1536
- modelId: model,
1537
1559
  doPromptTurn: async (promptOpts) => {
1538
1560
  if (promptOpts.responseFormat?.type === "json" && promptOpts.responseFormat.schema == null) {
1539
1561
  throw new HarnessCapabilityUnsupportedError({
@@ -1564,7 +1586,7 @@ function createSession({
1564
1586
  })),
1565
1587
  ...promptOpts.responseFormat == null ? {} : { responseFormat: promptOpts.responseFormat },
1566
1588
  ...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
1567
- model,
1589
+ model: promptOpts.model ?? model,
1568
1590
  maxTurns,
1569
1591
  ...env !== void 0 ? { env } : {},
1570
1592
  thinking,
@@ -1574,7 +1596,7 @@ function createSession({
1574
1596
  ...permissionMode ? { permissionMode } : {},
1575
1597
  ...builtinToolFiltering ? { builtinToolFiltering } : {},
1576
1598
  ...debug ? { debug } : {},
1577
- ...pendingResumeFlag ? { continue: true } : {}
1599
+ ...lastClaudeSessionId ? { resumeSessionId: lastClaudeSessionId } : pendingResumeFlag ? { continue: true } : {}
1578
1600
  };
1579
1601
  pendingResumeFlag = false;
1580
1602
  channel.send(startMessage);
@@ -1616,7 +1638,7 @@ function createSession({
1616
1638
  })),
1617
1639
  ...continueOpts.responseFormat == null ? {} : { responseFormat: continueOpts.responseFormat },
1618
1640
  ...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
1619
- model,
1641
+ model: continueOpts.model ?? model,
1620
1642
  maxTurns,
1621
1643
  ...env !== void 0 ? { env } : {},
1622
1644
  thinking,
@@ -1626,7 +1648,7 @@ function createSession({
1626
1648
  ...permissionMode ? { permissionMode } : {},
1627
1649
  ...builtinToolFiltering ? { builtinToolFiltering } : {},
1628
1650
  ...debug ? { debug } : {},
1629
- continue: true
1651
+ ...lastClaudeSessionId ? { resumeSessionId: lastClaudeSessionId } : { continue: true }
1630
1652
  });
1631
1653
  }
1632
1654
  return control;
@@ -1654,7 +1676,8 @@ function createSession({
1654
1676
  token: bridgeToken,
1655
1677
  lastSeenEventId,
1656
1678
  ...sandboxId == null ? {} : { sandboxId }
1657
- }
1679
+ },
1680
+ ...lastClaudeSessionId ? { claudeSessionId: lastClaudeSessionId } : {}
1658
1681
  }
1659
1682
  };
1660
1683
  return payload;
@@ -1747,7 +1770,11 @@ function createSession({
1747
1770
  type: "resume-session",
1748
1771
  harnessId: "claude-code",
1749
1772
  specificationVersion: "harness-v1",
1773
+ // The bridge's stop reply carries the session id it observed; the
1774
+ // adapter's own record backfills it when the reply predates the
1775
+ // field or the channel was already closed.
1750
1776
  data: {
1777
+ ...lastClaudeSessionId ? { claudeSessionId: lastClaudeSessionId } : {},
1751
1778
  ...lifecycleData,
1752
1779
  ...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment }
1753
1780
  }
@@ -1773,7 +1800,8 @@ function createSession({
1773
1800
  token: bridgeToken,
1774
1801
  lastSeenEventId,
1775
1802
  ...sandboxId == null ? {} : { sandboxId }
1776
- }
1803
+ },
1804
+ ...lastClaudeSessionId ? { claudeSessionId: lastClaudeSessionId } : {}
1777
1805
  }
1778
1806
  };
1779
1807
  return payload;