@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/CHANGELOG.md +22 -0
- package/dist/bridge/index.mjs +77 -14
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +66 -38
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/create-emit-stream-event.ts +74 -13
- package/src/bridge/index.ts +65 -12
- package/src/claude-code-bootstrap.ts +19 -15
- package/src/claude-code-bridge-protocol.ts +8 -0
- package/src/claude-code-harness.ts +75 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @ai-sdk/harness-claude-code
|
|
2
2
|
|
|
3
|
+
## 1.0.99
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 371e954: feat(harness): add `createBridgeToken()` and `withBridgeToken()` helpers for bridge backed harness adapters
|
|
8
|
+
- 62f481f: fix(harness): fix bridge resolution to no longer look for an alternative path which could cause Turbopack errors
|
|
9
|
+
- Updated dependencies [371e954]
|
|
10
|
+
- Updated dependencies [87b4858]
|
|
11
|
+
- @ai-sdk/harness@1.0.95
|
|
12
|
+
|
|
13
|
+
## 1.0.98
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- e79bc7a: fix(harness-claude-code): resume the exact conversation instead of the most recent one in the working directory
|
|
18
|
+
- 8961fde: feat(harness): allow changing `model` between turns via call options
|
|
19
|
+
- eb59f2a: fix(harness): ensure harness adapters can stream tool input deltas before the complete tool call arrives
|
|
20
|
+
- Updated dependencies [8961fde]
|
|
21
|
+
- Updated dependencies [eb59f2a]
|
|
22
|
+
- @ai-sdk/harness@1.0.94
|
|
23
|
+
- @ai-sdk/provider-utils@5.0.34
|
|
24
|
+
|
|
3
25
|
## 1.0.97
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -735,7 +735,12 @@ function createEmitStreamEvent({
|
|
|
735
735
|
return;
|
|
736
736
|
}
|
|
737
737
|
if (type === "stream_event") {
|
|
738
|
-
handleStreamEvent(
|
|
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
|
-
|
|
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
|
|
1134
|
-
//
|
|
1135
|
-
|
|
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 (
|
|
1240
|
-
const
|
|
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
|
|
1328
|
-
//
|
|
1329
|
-
//
|
|
1330
|
-
//
|
|
1331
|
-
|
|
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
|
|
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({
|