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

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,17 @@
1
1
  # @ai-sdk/harness-claude-code
2
2
 
3
+ ## 1.0.98
4
+
5
+ ### Patch Changes
6
+
7
+ - e79bc7a: fix(harness-claude-code): resume the exact conversation instead of the most recent one in the working directory
8
+ - 8961fde: feat(harness): allow changing `model` between turns via call options
9
+ - eb59f2a: fix(harness): ensure harness adapters can stream tool input deltas before the complete tool call arrives
10
+ - Updated dependencies [8961fde]
11
+ - Updated dependencies [eb59f2a]
12
+ - @ai-sdk/harness@1.0.94
13
+ - @ai-sdk/provider-utils@5.0.34
14
+
3
15
  ## 1.0.97
4
16
 
5
17
  ### Patch Changes
@@ -735,7 +735,12 @@ function createEmitStreamEvent({
735
735
  return;
736
736
  }
737
737
  if (type === "stream_event") {
738
- handleStreamEvent(msg.event, state.partialBlocks, emit);
738
+ handleStreamEvent({
739
+ event: msg.event,
740
+ state,
741
+ send: emit,
742
+ toCommonName: toCommonName2
743
+ });
739
744
  return;
740
745
  }
741
746
  if (type === "assistant" && msg.message?.content) {
@@ -863,9 +868,16 @@ function formatApiRetryWarning(msg) {
863
868
  if (msg.error) details.push(msg.error);
864
869
  return details.length > 0 ? `Claude Code API retry: ${details.join("; ")}` : "Claude Code API retry";
865
870
  }
866
- function handleStreamEvent(event, partialBlocks, send) {
871
+ var HOST_TOOL_PREFIX = "mcp__harness-tools__";
872
+ function handleStreamEvent({
873
+ event,
874
+ state,
875
+ send,
876
+ toCommonName: toCommonName2
877
+ }) {
867
878
  if (!event || typeof event.index !== "number") return;
868
879
  const index = event.index;
880
+ const partialBlocks = state.partialBlocks;
869
881
  if (event.type === "content_block_start") {
870
882
  const blockType = event.content_block?.type;
871
883
  if (blockType === "text") {
@@ -876,6 +888,22 @@ function handleStreamEvent(event, partialBlocks, send) {
876
888
  const id = randomUUID2();
877
889
  partialBlocks.set(index, { id, kind: "thinking" });
878
890
  send({ type: "reasoning-start", id });
891
+ } else if (blockType === "tool_use" && typeof event.content_block?.id === "string" && typeof event.content_block.name === "string") {
892
+ const id = event.content_block.id;
893
+ const nativeName = event.content_block.name;
894
+ if (nativeName === "StructuredOutput") {
895
+ return;
896
+ }
897
+ const hostToolName = nativeName.startsWith(HOST_TOOL_PREFIX) ? nativeName.slice(HOST_TOOL_PREFIX.length) : void 0;
898
+ const dynamic = hostToolName === void 0 && nativeName.startsWith("mcp__");
899
+ partialBlocks.set(index, { id, kind: "tool-input" });
900
+ send({
901
+ type: "tool-input-start",
902
+ id,
903
+ toolName: hostToolName ?? toCommonName2(nativeName),
904
+ providerExecuted: hostToolName === void 0,
905
+ ...dynamic ? { dynamic: true } : {}
906
+ });
879
907
  }
880
908
  return;
881
909
  }
@@ -890,6 +918,12 @@ function handleStreamEvent(event, partialBlocks, send) {
890
918
  id: block.id,
891
919
  delta: event.delta.thinking
892
920
  });
921
+ } else if (block.kind === "tool-input" && event.delta?.type === "input_json_delta" && typeof event.delta.partial_json === "string") {
922
+ send({
923
+ type: "tool-input-delta",
924
+ id: block.id,
925
+ delta: event.delta.partial_json
926
+ });
893
927
  }
894
928
  return;
895
929
  }
@@ -899,8 +933,10 @@ function handleStreamEvent(event, partialBlocks, send) {
899
933
  partialBlocks.delete(index);
900
934
  if (block.kind === "text") {
901
935
  send({ type: "text-end", id: block.id });
902
- } else {
936
+ } else if (block.kind === "thinking") {
903
937
  send({ type: "reasoning-end", id: block.id });
938
+ } else {
939
+ send({ type: "tool-input-end", id: block.id });
904
940
  }
905
941
  }
906
942
  }
@@ -1126,13 +1162,15 @@ if (!bridgeStateDir) {
1126
1162
  }
1127
1163
  var claudeSdk = claudeAgentSdk;
1128
1164
  var mcpModule = mcpServerModule;
1165
+ var lastClaudeSessionId;
1129
1166
  await runBridge({
1130
1167
  bridgeType: "claude-code",
1131
1168
  bridgeStateDir,
1132
1169
  onStart: runTurn,
1133
- // Claude Code's session state lives in the workdir on the sandbox filesystem
1134
- // (captured by the sandbox snapshot on stop); the resume payload is empty.
1135
- onStop: () => ({})
1170
+ // Claude Code's conversation state lives in the runtime's own store, keyed
1171
+ // by working directory. The resume payload names the exact conversation so a
1172
+ // later resume does not have to fall back to "most recent in this workdir".
1173
+ onStop: () => lastClaudeSessionId == null ? {} : { claudeSessionId: lastClaudeSessionId }
1136
1174
  });
1137
1175
  function createPermissionOptions(input) {
1138
1176
  const permissionMode = input.start.permissionMode ?? "allow-all";
@@ -1236,8 +1274,10 @@ async function runTurn(start, turn) {
1236
1274
  tool.name,
1237
1275
  tool.description ?? "",
1238
1276
  shape,
1239
- async (input) => {
1240
- const toolCallId = randomUUID3();
1277
+ async (...handlerArgs) => {
1278
+ const [input, extra] = handlerArgs;
1279
+ const metadataToolCallId = extra._meta?.["claudecode/toolUseId"];
1280
+ const toolCallId = typeof metadataToolCallId === "string" ? metadataToolCallId : randomUUID3();
1241
1281
  emit({
1242
1282
  type: "tool-call",
1243
1283
  toolCallId,
@@ -1324,11 +1364,20 @@ async function runTurn(start, turn) {
1324
1364
  }
1325
1365
  ]
1326
1366
  },
1327
- // Continuation rule: the host can force-continue (resume after a
1328
- // cross-process detach) by setting `start.continue: true`; otherwise
1329
- // we continue every subsequent turn after the first one in this
1330
- // bridge process.
1331
- ...start.continue === true || !turn.firstTurn ? { continue: true } : {},
1367
+ // Continuation rule, most specific first.
1368
+ //
1369
+ // `resumeSessionId` names the exact conversation and is what a
1370
+ // cross-process resume should use: `continue` means "most recent thread
1371
+ // in this workdir", which silently picks the wrong one once anything
1372
+ // else has run there. The bridge also retains the id observed during its
1373
+ // previous query, so every later query stays pinned to that conversation
1374
+ // even when the host detached and reattached between turns. `resume` and
1375
+ // `continue` are mutually exclusive in the SDK.
1376
+ //
1377
+ // Otherwise the host can force-continue by setting `start.continue`,
1378
+ // and turns after the first fall back to the legacy cwd-based behavior
1379
+ // when no exact id was observed.
1380
+ ...start.resumeSessionId ?? lastClaudeSessionId ? { resume: start.resumeSessionId ?? lastClaudeSessionId } : start.continue === true || !turn.firstTurn ? { continue: true } : {},
1332
1381
  ...permissionOptions,
1333
1382
  mcpServers,
1334
1383
  cwd: workdir,
@@ -1373,6 +1422,10 @@ async function runTurn(start, turn) {
1373
1422
  if (type === "command_lifecycle") {
1374
1423
  queryInput.handleLifecycle(msg);
1375
1424
  }
1425
+ const sessionId = msg.session_id;
1426
+ if (typeof sessionId === "string" && sessionId.length > 0) {
1427
+ lastClaudeSessionId = sessionId;
1428
+ }
1376
1429
  emitStreamEvent(msg);
1377
1430
  if (type === "result") {
1378
1431
  if (msg.subtype === "success") {
@@ -1446,7 +1499,17 @@ async function runTurn(start, turn) {
1446
1499
  type: "finish",
1447
1500
  finishReason: { unified: "stop", raw: "stop" },
1448
1501
  totalUsage: turnUsage ?? streamEventState.stepUsage ?? defaultUsage(),
1449
- ...totalCostUsd !== void 0 ? { harnessMetadata: { "claude-code": { costUsd: totalCostUsd } } } : {}
1502
+ ...totalCostUsd !== void 0 || lastClaudeSessionId !== void 0 ? {
1503
+ harnessMetadata: {
1504
+ "claude-code": {
1505
+ ...totalCostUsd !== void 0 ? { costUsd: totalCostUsd } : {},
1506
+ // The conversation this turn belongs to, resumable outside the
1507
+ // SDK with `claude --resume <sessionId>` and captured by the
1508
+ // adapter for exact cross-process resume.
1509
+ ...lastClaudeSessionId !== void 0 ? { sessionId: lastClaudeSessionId } : {}
1510
+ }
1511
+ }
1512
+ } : {}
1450
1513
  });
1451
1514
  }
1452
1515
  function createQueryInput({