@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/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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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.
|
|
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 ?
|
|
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;
|