@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 +12 -0
- package/dist/bridge/index.mjs +77 -14
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +48 -13
- 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-bridge-protocol.ts +8 -0
- package/src/claude-code-harness.ts +72 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-claude-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.98",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
30
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.94",
|
|
30
|
+
"@ai-sdk/provider-utils": "5.0.34",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
@@ -21,8 +21,17 @@ export type ClaudeMessage = {
|
|
|
21
21
|
event?: {
|
|
22
22
|
type?: string;
|
|
23
23
|
index?: number;
|
|
24
|
-
content_block?: {
|
|
25
|
-
|
|
24
|
+
content_block?: {
|
|
25
|
+
type?: string;
|
|
26
|
+
id?: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
};
|
|
29
|
+
delta?: {
|
|
30
|
+
type?: string;
|
|
31
|
+
text?: string;
|
|
32
|
+
thinking?: string;
|
|
33
|
+
partial_json?: string;
|
|
34
|
+
};
|
|
26
35
|
};
|
|
27
36
|
message?: {
|
|
28
37
|
content?: ReadonlyArray<MessageBlock>;
|
|
@@ -56,7 +65,7 @@ export type ClaudeStreamEventState = {
|
|
|
56
65
|
*/
|
|
57
66
|
nativeToolCallNames: Map<string, string>;
|
|
58
67
|
approvalRequestedToolUseIds: Set<string>;
|
|
59
|
-
partialBlocks: Map<number,
|
|
68
|
+
partialBlocks: Map<number, PartialBlock>;
|
|
60
69
|
stepUsage: Record<string, unknown> | undefined;
|
|
61
70
|
pendingStepToolUseIds: Set<string>;
|
|
62
71
|
pendingStepUsage: Record<string, unknown> | undefined;
|
|
@@ -64,9 +73,8 @@ export type ClaudeStreamEventState = {
|
|
|
64
73
|
/*
|
|
65
74
|
* Tool-use ids that originated from the MCP server hosting user-supplied
|
|
66
75
|
* tools. The MCP handler emits its own `tool-call`/`tool-result` pair with
|
|
67
|
-
* the user-facing tool name
|
|
68
|
-
*
|
|
69
|
-
* suppressed.
|
|
76
|
+
* the user-facing tool name, so the duplicate `tool_result` block Claude
|
|
77
|
+
* reports for the underlying native id must be suppressed.
|
|
70
78
|
*/
|
|
71
79
|
mcpToolUseIds: Set<string>;
|
|
72
80
|
externalMcpToolUseIds: Set<string>;
|
|
@@ -74,6 +82,10 @@ export type ClaudeStreamEventState = {
|
|
|
74
82
|
observedTerminalError: string | undefined;
|
|
75
83
|
};
|
|
76
84
|
|
|
85
|
+
type PartialBlock =
|
|
86
|
+
| { id: string; kind: 'text' | 'thinking' }
|
|
87
|
+
| { id: string; kind: 'tool-input' };
|
|
88
|
+
|
|
77
89
|
export function createClaudeStreamEventState(): ClaudeStreamEventState {
|
|
78
90
|
return {
|
|
79
91
|
nativeToolCallNames: new Map(),
|
|
@@ -195,7 +207,12 @@ export function createEmitStreamEvent({
|
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
if (type === 'stream_event') {
|
|
198
|
-
handleStreamEvent(
|
|
210
|
+
handleStreamEvent({
|
|
211
|
+
event: msg.event,
|
|
212
|
+
state,
|
|
213
|
+
send: emit,
|
|
214
|
+
toCommonName,
|
|
215
|
+
});
|
|
199
216
|
return;
|
|
200
217
|
}
|
|
201
218
|
|
|
@@ -371,13 +388,22 @@ function formatApiRetryWarning(msg: ClaudeMessage): string {
|
|
|
371
388
|
: 'Claude Code API retry';
|
|
372
389
|
}
|
|
373
390
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
391
|
+
const HOST_TOOL_PREFIX = 'mcp__harness-tools__';
|
|
392
|
+
|
|
393
|
+
function handleStreamEvent({
|
|
394
|
+
event,
|
|
395
|
+
state,
|
|
396
|
+
send,
|
|
397
|
+
toCommonName,
|
|
398
|
+
}: {
|
|
399
|
+
event: ClaudeMessage['event'] | undefined;
|
|
400
|
+
state: ClaudeStreamEventState;
|
|
401
|
+
send: Emit;
|
|
402
|
+
toCommonName: (nativeName: string) => string;
|
|
403
|
+
}): void {
|
|
379
404
|
if (!event || typeof event.index !== 'number') return;
|
|
380
405
|
const index = event.index;
|
|
406
|
+
const partialBlocks = state.partialBlocks;
|
|
381
407
|
|
|
382
408
|
if (event.type === 'content_block_start') {
|
|
383
409
|
const blockType = event.content_block?.type;
|
|
@@ -389,6 +415,29 @@ function handleStreamEvent(
|
|
|
389
415
|
const id = randomUUID();
|
|
390
416
|
partialBlocks.set(index, { id, kind: 'thinking' });
|
|
391
417
|
send({ type: 'reasoning-start', id });
|
|
418
|
+
} else if (
|
|
419
|
+
blockType === 'tool_use' &&
|
|
420
|
+
typeof event.content_block?.id === 'string' &&
|
|
421
|
+
typeof event.content_block.name === 'string'
|
|
422
|
+
) {
|
|
423
|
+
const id = event.content_block.id;
|
|
424
|
+
const nativeName = event.content_block.name;
|
|
425
|
+
if (nativeName === 'StructuredOutput') {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const hostToolName = nativeName.startsWith(HOST_TOOL_PREFIX)
|
|
429
|
+
? nativeName.slice(HOST_TOOL_PREFIX.length)
|
|
430
|
+
: undefined;
|
|
431
|
+
const dynamic =
|
|
432
|
+
hostToolName === undefined && nativeName.startsWith('mcp__');
|
|
433
|
+
partialBlocks.set(index, { id, kind: 'tool-input' });
|
|
434
|
+
send({
|
|
435
|
+
type: 'tool-input-start',
|
|
436
|
+
id,
|
|
437
|
+
toolName: hostToolName ?? toCommonName(nativeName),
|
|
438
|
+
providerExecuted: hostToolName === undefined,
|
|
439
|
+
...(dynamic ? { dynamic: true } : {}),
|
|
440
|
+
});
|
|
392
441
|
}
|
|
393
442
|
return;
|
|
394
443
|
}
|
|
@@ -412,6 +461,16 @@ function handleStreamEvent(
|
|
|
412
461
|
id: block.id,
|
|
413
462
|
delta: event.delta.thinking,
|
|
414
463
|
});
|
|
464
|
+
} else if (
|
|
465
|
+
block.kind === 'tool-input' &&
|
|
466
|
+
event.delta?.type === 'input_json_delta' &&
|
|
467
|
+
typeof event.delta.partial_json === 'string'
|
|
468
|
+
) {
|
|
469
|
+
send({
|
|
470
|
+
type: 'tool-input-delta',
|
|
471
|
+
id: block.id,
|
|
472
|
+
delta: event.delta.partial_json,
|
|
473
|
+
});
|
|
415
474
|
}
|
|
416
475
|
return;
|
|
417
476
|
}
|
|
@@ -422,8 +481,10 @@ function handleStreamEvent(
|
|
|
422
481
|
partialBlocks.delete(index);
|
|
423
482
|
if (block.kind === 'text') {
|
|
424
483
|
send({ type: 'text-end', id: block.id });
|
|
425
|
-
} else {
|
|
484
|
+
} else if (block.kind === 'thinking') {
|
|
426
485
|
send({ type: 'reasoning-end', id: block.id });
|
|
486
|
+
} else {
|
|
487
|
+
send({ type: 'tool-input-end', id: block.id });
|
|
427
488
|
}
|
|
428
489
|
}
|
|
429
490
|
}
|
package/src/bridge/index.ts
CHANGED
|
@@ -125,13 +125,23 @@ const claudeSdk = claudeAgentSdk as any;
|
|
|
125
125
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
126
126
|
const mcpModule = mcpServerModule as any;
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* The Claude session id most recently reported by the SDK, captured from the
|
|
130
|
+
* message stream. Every turn in this bridge process may fork a new id (the
|
|
131
|
+
* SDK's `continue`/`resume` create a new session linked to the previous one),
|
|
132
|
+
* so the latest observation is the one a later resume must name.
|
|
133
|
+
*/
|
|
134
|
+
let lastClaudeSessionId: string | undefined;
|
|
135
|
+
|
|
128
136
|
await runBridge<StartMessage>({
|
|
129
137
|
bridgeType: 'claude-code',
|
|
130
138
|
bridgeStateDir,
|
|
131
139
|
onStart: runTurn,
|
|
132
|
-
// Claude Code's
|
|
133
|
-
//
|
|
134
|
-
|
|
140
|
+
// Claude Code's conversation state lives in the runtime's own store, keyed
|
|
141
|
+
// by working directory. The resume payload names the exact conversation so a
|
|
142
|
+
// later resume does not have to fall back to "most recent in this workdir".
|
|
143
|
+
onStop: () =>
|
|
144
|
+
lastClaudeSessionId == null ? {} : { claudeSessionId: lastClaudeSessionId },
|
|
135
145
|
});
|
|
136
146
|
|
|
137
147
|
type Emit = (msg: Record<string, unknown>) => void;
|
|
@@ -290,8 +300,18 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
290
300
|
tool.name,
|
|
291
301
|
tool.description ?? '',
|
|
292
302
|
shape,
|
|
293
|
-
async (
|
|
294
|
-
|
|
303
|
+
async (
|
|
304
|
+
...handlerArgs: [
|
|
305
|
+
Record<string, unknown>,
|
|
306
|
+
{ requestId: string | number; _meta?: Record<string, unknown> },
|
|
307
|
+
]
|
|
308
|
+
) => {
|
|
309
|
+
const [input, extra] = handlerArgs;
|
|
310
|
+
const metadataToolCallId = extra._meta?.['claudecode/toolUseId'];
|
|
311
|
+
const toolCallId =
|
|
312
|
+
typeof metadataToolCallId === 'string'
|
|
313
|
+
? metadataToolCallId
|
|
314
|
+
: randomUUID();
|
|
295
315
|
emit({
|
|
296
316
|
type: 'tool-call',
|
|
297
317
|
toolCallId,
|
|
@@ -392,11 +412,24 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
392
412
|
},
|
|
393
413
|
],
|
|
394
414
|
},
|
|
395
|
-
// Continuation rule
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
|
|
415
|
+
// Continuation rule, most specific first.
|
|
416
|
+
//
|
|
417
|
+
// `resumeSessionId` names the exact conversation and is what a
|
|
418
|
+
// cross-process resume should use: `continue` means "most recent thread
|
|
419
|
+
// in this workdir", which silently picks the wrong one once anything
|
|
420
|
+
// else has run there. The bridge also retains the id observed during its
|
|
421
|
+
// previous query, so every later query stays pinned to that conversation
|
|
422
|
+
// even when the host detached and reattached between turns. `resume` and
|
|
423
|
+
// `continue` are mutually exclusive in the SDK.
|
|
424
|
+
//
|
|
425
|
+
// Otherwise the host can force-continue by setting `start.continue`,
|
|
426
|
+
// and turns after the first fall back to the legacy cwd-based behavior
|
|
427
|
+
// when no exact id was observed.
|
|
428
|
+
...((start.resumeSessionId ?? lastClaudeSessionId)
|
|
429
|
+
? { resume: start.resumeSessionId ?? lastClaudeSessionId }
|
|
430
|
+
: start.continue === true || !turn.firstTurn
|
|
431
|
+
? { continue: true }
|
|
432
|
+
: {}),
|
|
400
433
|
...permissionOptions,
|
|
401
434
|
mcpServers,
|
|
402
435
|
cwd: workdir,
|
|
@@ -460,6 +493,14 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
460
493
|
queryInput.handleLifecycle(msg);
|
|
461
494
|
}
|
|
462
495
|
|
|
496
|
+
// Every SDK message carries the session id of the conversation it
|
|
497
|
+
// belongs to. Track the latest so the stop payload and the terminal
|
|
498
|
+
// finish metadata name the exact conversation.
|
|
499
|
+
const sessionId = (msg as { session_id?: unknown }).session_id;
|
|
500
|
+
if (typeof sessionId === 'string' && sessionId.length > 0) {
|
|
501
|
+
lastClaudeSessionId = sessionId;
|
|
502
|
+
}
|
|
503
|
+
|
|
463
504
|
emitStreamEvent(msg);
|
|
464
505
|
|
|
465
506
|
if (type === 'result') {
|
|
@@ -559,8 +600,20 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
559
600
|
type: 'finish',
|
|
560
601
|
finishReason: { unified: 'stop', raw: 'stop' },
|
|
561
602
|
totalUsage: turnUsage ?? streamEventState.stepUsage ?? defaultUsage(),
|
|
562
|
-
...(totalCostUsd !== undefined
|
|
563
|
-
? {
|
|
603
|
+
...(totalCostUsd !== undefined || lastClaudeSessionId !== undefined
|
|
604
|
+
? {
|
|
605
|
+
harnessMetadata: {
|
|
606
|
+
'claude-code': {
|
|
607
|
+
...(totalCostUsd !== undefined ? { costUsd: totalCostUsd } : {}),
|
|
608
|
+
// The conversation this turn belongs to, resumable outside the
|
|
609
|
+
// SDK with `claude --resume <sessionId>` and captured by the
|
|
610
|
+
// adapter for exact cross-process resume.
|
|
611
|
+
...(lastClaudeSessionId !== undefined
|
|
612
|
+
? { sessionId: lastClaudeSessionId }
|
|
613
|
+
: {}),
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
}
|
|
564
617
|
: {}),
|
|
565
618
|
});
|
|
566
619
|
}
|
|
@@ -42,6 +42,14 @@ export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
|
42
42
|
// Claude SDK so the in-workdir thread state is rehydrated. The host sets this
|
|
43
43
|
// on the first prompt after a cross-process resume.
|
|
44
44
|
continue: z.boolean().optional(),
|
|
45
|
+
// Exact conversation to resume, as the Claude SDK's `resume` option.
|
|
46
|
+
//
|
|
47
|
+
// Preferred over `continue` whenever the host knows which conversation it
|
|
48
|
+
// means: `continue` resolves to the most recent thread in the workdir, which
|
|
49
|
+
// is the wrong one as soon as anything else has run there since — including
|
|
50
|
+
// a resume that itself started a new thread. Mutually exclusive with
|
|
51
|
+
// `continue` in the SDK, so the bridge sends one or the other.
|
|
52
|
+
resumeSessionId: z.string().optional(),
|
|
45
53
|
});
|
|
46
54
|
|
|
47
55
|
export type StartMessage = z.infer<typeof startMessageSchema>;
|
|
@@ -802,6 +802,17 @@ const claudeCodeBridgeCoordsSchema = z.object({
|
|
|
802
802
|
const claudeCodeResumeStateSchema = z.looseObject({
|
|
803
803
|
bridge: claudeCodeBridgeCoordsSchema.optional(),
|
|
804
804
|
sandboxCredentialEnvironment: z.record(z.string(), z.string()).optional(),
|
|
805
|
+
/**
|
|
806
|
+
* The exact Claude conversation to rehydrate on resume. Written by the
|
|
807
|
+
* adapter on `doStop()`/`doDetach()`/`doSuspendTurn()` from the session id
|
|
808
|
+
* the bridge observed, so a resume names the conversation instead of
|
|
809
|
+
* relying on the SDK's `continue` flag — "most recent thread in this
|
|
810
|
+
* workdir" — which silently picks the wrong one once a second thread
|
|
811
|
+
* exists there. Hosts that captured the id themselves (it is surfaced as
|
|
812
|
+
* `harnessMetadata['claude-code'].sessionId` on `finish` parts) may also
|
|
813
|
+
* set it explicitly.
|
|
814
|
+
*/
|
|
815
|
+
claudeSessionId: z.string().optional(),
|
|
805
816
|
});
|
|
806
817
|
|
|
807
818
|
type ClaudeCodeBridgeCoords = z.infer<typeof claudeCodeBridgeCoordsSchema>;
|
|
@@ -831,7 +842,6 @@ export function createClaudeCode(
|
|
|
831
842
|
lifecycleStateSchema: claudeCodeResumeStateSchema,
|
|
832
843
|
getBootstrap: getClaudeCodeBootstrap,
|
|
833
844
|
doStart: async startOpts => {
|
|
834
|
-
const model = startOpts.model ?? settings.model;
|
|
835
845
|
const sandboxSession = startOpts.sandboxSession;
|
|
836
846
|
const toolSafeSandboxSession =
|
|
837
847
|
getRestrictedSandboxSession(sandboxSession);
|
|
@@ -860,6 +870,7 @@ export function createClaudeCode(
|
|
|
860
870
|
? (lifecycleState?.data as {
|
|
861
871
|
bridge?: ClaudeCodeBridgeCoords;
|
|
862
872
|
sandboxCredentialEnvironment?: Record<string, string>;
|
|
873
|
+
claudeSessionId?: string;
|
|
863
874
|
})
|
|
864
875
|
: undefined;
|
|
865
876
|
const coords = resumeData?.bridge;
|
|
@@ -921,6 +932,10 @@ export function createClaudeCode(
|
|
|
921
932
|
defaultWorkingDirectory,
|
|
922
933
|
BOOTSTRAP_DIR,
|
|
923
934
|
);
|
|
935
|
+
// The conversation the host wants back, when it is known. Absent on
|
|
936
|
+
// state written before this field existed; those resumes fall back to
|
|
937
|
+
// the `continue` flag as before.
|
|
938
|
+
const resumeSessionId = resumeData?.claudeSessionId;
|
|
924
939
|
|
|
925
940
|
const workDir = startOpts.sessionWorkDir;
|
|
926
941
|
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
@@ -994,11 +1009,12 @@ export function createClaudeCode(
|
|
|
994
1009
|
return createSession({
|
|
995
1010
|
sessionId: startOpts.sessionId,
|
|
996
1011
|
channel: attachChannel,
|
|
1012
|
+
...(resumeSessionId ? { resumeSessionId } : {}),
|
|
997
1013
|
// The live bridge was spawned by another process; this one owns no
|
|
998
1014
|
// process handle. The session lifecycle method decides whether the
|
|
999
1015
|
// sandbox is left running, stopped, or destroyed.
|
|
1000
1016
|
proc: undefined,
|
|
1001
|
-
model,
|
|
1017
|
+
model: settings.model,
|
|
1002
1018
|
maxTurns: settings.maxTurns,
|
|
1003
1019
|
env: sandboxClaudeEnvironment,
|
|
1004
1020
|
thinking,
|
|
@@ -1149,13 +1165,14 @@ export function createClaudeCode(
|
|
|
1149
1165
|
sessionId: startOpts.sessionId,
|
|
1150
1166
|
channel,
|
|
1151
1167
|
proc,
|
|
1152
|
-
model,
|
|
1168
|
+
model: settings.model,
|
|
1153
1169
|
maxTurns: settings.maxTurns,
|
|
1154
1170
|
env: sandboxClaudeEnvironment,
|
|
1155
1171
|
thinking,
|
|
1156
1172
|
effort: settings.effort,
|
|
1157
1173
|
isResume: respawnStrategy !== undefined,
|
|
1158
1174
|
continueOnFirstPrompt: respawnStrategy !== undefined,
|
|
1175
|
+
...(resumeSessionId ? { resumeSessionId } : {}),
|
|
1159
1176
|
rerunContinue: respawnStrategy === 'rerun',
|
|
1160
1177
|
bridgePort: boundPort,
|
|
1161
1178
|
bridgeToken: token,
|
|
@@ -1486,6 +1503,7 @@ function createSession({
|
|
|
1486
1503
|
effort,
|
|
1487
1504
|
isResume,
|
|
1488
1505
|
continueOnFirstPrompt,
|
|
1506
|
+
resumeSessionId,
|
|
1489
1507
|
rerunContinue,
|
|
1490
1508
|
bridgePort,
|
|
1491
1509
|
bridgeToken,
|
|
@@ -1510,6 +1528,12 @@ function createSession({
|
|
|
1510
1528
|
effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | undefined;
|
|
1511
1529
|
isResume: boolean;
|
|
1512
1530
|
continueOnFirstPrompt: boolean;
|
|
1531
|
+
/**
|
|
1532
|
+
* Exact conversation to rehydrate whenever a fresh SDK query starts. Takes
|
|
1533
|
+
* precedence over the `continue` flag, which can only mean "most recent in
|
|
1534
|
+
* this workdir".
|
|
1535
|
+
*/
|
|
1536
|
+
resumeSessionId?: string;
|
|
1513
1537
|
rerunContinue: boolean;
|
|
1514
1538
|
bridgePort: number;
|
|
1515
1539
|
bridgeToken: string;
|
|
@@ -1527,12 +1551,21 @@ function createSession({
|
|
|
1527
1551
|
let stopPromise: Promise<void> | undefined;
|
|
1528
1552
|
/*
|
|
1529
1553
|
* Force the Claude SDK's `continue: true` on the first prompt only when the
|
|
1530
|
-
* bridge was respawned (rerun/replay): a
|
|
1531
|
-
* first turn as new, so it must be told to
|
|
1532
|
-
*
|
|
1554
|
+
* bridge was respawned without an exact conversation id (rerun/replay): a
|
|
1555
|
+
* fresh bridge process treats its first turn as new, so it must be told to
|
|
1556
|
+
* rehydrate the workdir thread. Exact ids are sent independently of this
|
|
1557
|
+
* fallback on every fresh query.
|
|
1533
1558
|
*/
|
|
1534
1559
|
let pendingResumeFlag = continueOnFirstPrompt;
|
|
1535
1560
|
|
|
1561
|
+
/*
|
|
1562
|
+
* The Claude conversation this session currently embodies. Seeded from the
|
|
1563
|
+
* resume state and updated from every `finish` part's metadata — each turn
|
|
1564
|
+
* may fork a new id, so the latest observation is the one a later
|
|
1565
|
+
* stop/detach must record for exact resume.
|
|
1566
|
+
*/
|
|
1567
|
+
let lastClaudeSessionId = resumeSessionId;
|
|
1568
|
+
|
|
1536
1569
|
/*
|
|
1537
1570
|
* Wire the channel into one turn's worth of events and return the control
|
|
1538
1571
|
* surface. Shared by `doPromptTurn` (which sends a `start` afterwards) and
|
|
@@ -1588,6 +1621,9 @@ function createSession({
|
|
|
1588
1621
|
'reasoning-start',
|
|
1589
1622
|
'reasoning-delta',
|
|
1590
1623
|
'reasoning-end',
|
|
1624
|
+
'tool-input-start',
|
|
1625
|
+
'tool-input-delta',
|
|
1626
|
+
'tool-input-end',
|
|
1591
1627
|
'tool-call',
|
|
1592
1628
|
'tool-approval-request',
|
|
1593
1629
|
'tool-result',
|
|
@@ -1603,6 +1639,14 @@ function createSession({
|
|
|
1603
1639
|
}
|
|
1604
1640
|
unsubs.push(
|
|
1605
1641
|
channel.on('finish', msg => {
|
|
1642
|
+
const metadata = (
|
|
1643
|
+
msg as {
|
|
1644
|
+
harnessMetadata?: { 'claude-code'?: { sessionId?: unknown } };
|
|
1645
|
+
}
|
|
1646
|
+
).harnessMetadata?.['claude-code']?.sessionId;
|
|
1647
|
+
if (typeof metadata === 'string' && metadata.length > 0) {
|
|
1648
|
+
lastClaudeSessionId = metadata;
|
|
1649
|
+
}
|
|
1606
1650
|
forward(msg);
|
|
1607
1651
|
settleSuccess();
|
|
1608
1652
|
}),
|
|
@@ -1683,7 +1727,6 @@ function createSession({
|
|
|
1683
1727
|
return {
|
|
1684
1728
|
sessionId,
|
|
1685
1729
|
isResume,
|
|
1686
|
-
modelId: model,
|
|
1687
1730
|
doPromptTurn: async promptOpts => {
|
|
1688
1731
|
if (
|
|
1689
1732
|
promptOpts.responseFormat?.type === 'json' &&
|
|
@@ -1730,7 +1773,7 @@ function createSession({
|
|
|
1730
1773
|
...(promptOpts.instructions
|
|
1731
1774
|
? { instructions: promptOpts.instructions }
|
|
1732
1775
|
: {}),
|
|
1733
|
-
model,
|
|
1776
|
+
model: promptOpts.model ?? model,
|
|
1734
1777
|
maxTurns,
|
|
1735
1778
|
...(env !== undefined ? { env } : {}),
|
|
1736
1779
|
thinking,
|
|
@@ -1742,7 +1785,11 @@ function createSession({
|
|
|
1742
1785
|
...(permissionMode ? { permissionMode } : {}),
|
|
1743
1786
|
...(builtinToolFiltering ? { builtinToolFiltering } : {}),
|
|
1744
1787
|
...(debug ? { debug } : {}),
|
|
1745
|
-
...(
|
|
1788
|
+
...(lastClaudeSessionId
|
|
1789
|
+
? { resumeSessionId: lastClaudeSessionId }
|
|
1790
|
+
: pendingResumeFlag
|
|
1791
|
+
? { continue: true }
|
|
1792
|
+
: {}),
|
|
1746
1793
|
};
|
|
1747
1794
|
pendingResumeFlag = false;
|
|
1748
1795
|
channel.send(startMessage);
|
|
@@ -1809,7 +1856,7 @@ function createSession({
|
|
|
1809
1856
|
...(continueOpts.instructions
|
|
1810
1857
|
? { instructions: continueOpts.instructions }
|
|
1811
1858
|
: {}),
|
|
1812
|
-
model,
|
|
1859
|
+
model: continueOpts.model ?? model,
|
|
1813
1860
|
maxTurns,
|
|
1814
1861
|
...(env !== undefined ? { env } : {}),
|
|
1815
1862
|
thinking,
|
|
@@ -1821,7 +1868,9 @@ function createSession({
|
|
|
1821
1868
|
...(permissionMode ? { permissionMode } : {}),
|
|
1822
1869
|
...(builtinToolFiltering ? { builtinToolFiltering } : {}),
|
|
1823
1870
|
...(debug ? { debug } : {}),
|
|
1824
|
-
|
|
1871
|
+
...(lastClaudeSessionId
|
|
1872
|
+
? { resumeSessionId: lastClaudeSessionId }
|
|
1873
|
+
: { continue: true }),
|
|
1825
1874
|
});
|
|
1826
1875
|
}
|
|
1827
1876
|
|
|
@@ -1863,6 +1912,9 @@ function createSession({
|
|
|
1863
1912
|
lastSeenEventId,
|
|
1864
1913
|
...(sandboxId == null ? {} : { sandboxId }),
|
|
1865
1914
|
},
|
|
1915
|
+
...(lastClaudeSessionId
|
|
1916
|
+
? { claudeSessionId: lastClaudeSessionId }
|
|
1917
|
+
: {}),
|
|
1866
1918
|
},
|
|
1867
1919
|
};
|
|
1868
1920
|
return payload;
|
|
@@ -1974,7 +2026,13 @@ function createSession({
|
|
|
1974
2026
|
type: 'resume-session',
|
|
1975
2027
|
harnessId: 'claude-code',
|
|
1976
2028
|
specificationVersion: 'harness-v1',
|
|
2029
|
+
// The bridge's stop reply carries the session id it observed; the
|
|
2030
|
+
// adapter's own record backfills it when the reply predates the
|
|
2031
|
+
// field or the channel was already closed.
|
|
1977
2032
|
data: {
|
|
2033
|
+
...(lastClaudeSessionId
|
|
2034
|
+
? { claudeSessionId: lastClaudeSessionId }
|
|
2035
|
+
: {}),
|
|
1978
2036
|
...lifecycleData,
|
|
1979
2037
|
...(sandboxCredentialEnvironment == null
|
|
1980
2038
|
? {}
|
|
@@ -2013,6 +2071,9 @@ function createSession({
|
|
|
2013
2071
|
lastSeenEventId,
|
|
2014
2072
|
...(sandboxId == null ? {} : { sandboxId }),
|
|
2015
2073
|
},
|
|
2074
|
+
...(lastClaudeSessionId
|
|
2075
|
+
? { claudeSessionId: lastClaudeSessionId }
|
|
2076
|
+
: {}),
|
|
2016
2077
|
},
|
|
2017
2078
|
};
|
|
2018
2079
|
return payload;
|