@ai-sdk/harness-claude-code 1.0.96 → 1.0.97
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 +16 -0
- package/dist/bridge/index.mjs +60 -13
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/bridge/package.json +3 -3
- package/dist/bridge/pnpm-lock.yaml +128 -126
- package/dist/bridge/pnpm-workspace.yaml +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +32 -25
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/bridge/index.ts +69 -8
- package/src/bridge/package.json +3 -3
- package/src/bridge/pnpm-lock.yaml +128 -126
- package/src/bridge/pnpm-workspace.yaml +1 -1
- package/src/claude-code-harness.ts +49 -35
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.97",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,16 +26,16 @@
|
|
|
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.93",
|
|
30
|
+
"@ai-sdk/provider-utils": "5.0.33",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"zod": "^3.25.76 || ^4.1.8"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
38
|
-
"@modelcontextprotocol/sdk": "1.
|
|
37
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.245",
|
|
38
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
39
39
|
"@types/node": "22.19.19",
|
|
40
40
|
"@types/ws": "^8.5.13",
|
|
41
41
|
"@vercel/ai-tsconfig": "0.0.0",
|
package/src/bridge/index.ts
CHANGED
|
@@ -253,12 +253,27 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
253
253
|
// Local controller for the Claude query. Aborted either by the host (via the
|
|
254
254
|
// shared runtime's `turn.abortSignal`) or by us on a terminal error.
|
|
255
255
|
const abortCtl = new AbortController();
|
|
256
|
+
// A host abort prefers the SDK's graceful `interrupt()` — Esc semantics: the
|
|
257
|
+
// in-flight turn is persisted to the session transcript and settles with an
|
|
258
|
+
// interrupted result, so a later resume (including the user's own
|
|
259
|
+
// `claude --resume`) still sees the work done before the interrupt. The hard
|
|
260
|
+
// abort kills the CLI process and loses that turn's records, so it is only
|
|
261
|
+
// the fallback — armed unconditionally, because aborting an already-settled
|
|
262
|
+
// query is a no-op — and the immediate path when the abort arrives before
|
|
263
|
+
// the query exists.
|
|
264
|
+
let gracefulAbort: (() => void) | undefined;
|
|
265
|
+
let hardAbortTimer: ReturnType<typeof setTimeout> | undefined;
|
|
266
|
+
const onHostAbort = (): void => {
|
|
267
|
+
if (gracefulAbort) {
|
|
268
|
+
gracefulAbort();
|
|
269
|
+
} else {
|
|
270
|
+
abortCtl.abort();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
256
273
|
if (turn.abortSignal.aborted) {
|
|
257
274
|
abortCtl.abort();
|
|
258
275
|
} else {
|
|
259
|
-
turn.abortSignal.addEventListener('abort',
|
|
260
|
-
once: true,
|
|
261
|
-
});
|
|
276
|
+
turn.abortSignal.addEventListener('abort', onHostAbort, { once: true });
|
|
262
277
|
}
|
|
263
278
|
|
|
264
279
|
const streamEventState = createClaudeStreamEventState();
|
|
@@ -388,6 +403,19 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
388
403
|
abortSignal: abortCtl.signal,
|
|
389
404
|
},
|
|
390
405
|
});
|
|
406
|
+
|
|
407
|
+
gracefulAbort = () => {
|
|
408
|
+
// Backstop for the whole teardown, not just the interrupt call: if the
|
|
409
|
+
// stream has not settled five seconds after a graceful interrupt was
|
|
410
|
+
// requested, fall back to the hard abort. Aborting an already-settled
|
|
411
|
+
// query is a no-op, and `unref` keeps the timer from pinning the bridge
|
|
412
|
+
// process open on its own.
|
|
413
|
+
hardAbortTimer = setTimeout(() => abortCtl.abort(), 5000);
|
|
414
|
+
hardAbortTimer.unref?.();
|
|
415
|
+
void Promise.resolve()
|
|
416
|
+
.then(() => q.interrupt())
|
|
417
|
+
.catch(() => abortCtl.abort());
|
|
418
|
+
};
|
|
391
419
|
let turnUsage: Record<string, unknown> | undefined;
|
|
392
420
|
let totalCostUsd: number | undefined;
|
|
393
421
|
let emittedTerminalError = false;
|
|
@@ -398,10 +426,17 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
398
426
|
if (!normalized || emittedTerminalError || emittedTerminalFinish) return;
|
|
399
427
|
streamEventState.observedTerminalError = normalized;
|
|
400
428
|
emittedTerminalError = true;
|
|
401
|
-
turn
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
429
|
+
// A turn the host itself stopped ends with an error-shaped result by
|
|
430
|
+
// construction (an interrupted query reports a diagnostic, not success);
|
|
431
|
+
// reporting the host's own stop as a terminal error makes every clean
|
|
432
|
+
// interrupt look like a malfunction. The host has already settled the
|
|
433
|
+
// turn on its side.
|
|
434
|
+
if (!turn.abortSignal.aborted) {
|
|
435
|
+
turn.emitError({
|
|
436
|
+
error: normalized,
|
|
437
|
+
message: 'claude-code terminal error',
|
|
438
|
+
});
|
|
439
|
+
}
|
|
405
440
|
queryInput.close();
|
|
406
441
|
abortCtl.abort();
|
|
407
442
|
};
|
|
@@ -483,12 +518,38 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
483
518
|
}
|
|
484
519
|
}
|
|
485
520
|
} catch (err) {
|
|
486
|
-
|
|
521
|
+
// Same reasoning as `emitTerminalError`: a throw after the host's own
|
|
522
|
+
// abort (e.g. the hard-abort fallback killing the CLI mid-iteration, or
|
|
523
|
+
// a rejected `interrupt()`) is the stop the host asked for, not a
|
|
524
|
+
// malfunction. The host has already settled the turn on its side.
|
|
525
|
+
if (
|
|
526
|
+
!turn.abortSignal.aborted &&
|
|
527
|
+
!(abortCtl.signal.aborted && emittedTerminalError)
|
|
528
|
+
) {
|
|
487
529
|
turn.emitError({ error: err, message: 'claude-code turn failed' });
|
|
488
530
|
}
|
|
489
531
|
return;
|
|
490
532
|
} finally {
|
|
533
|
+
// The turn is over; disarm the host-abort path first. An abort of this
|
|
534
|
+
// turn's signal arriving after this point (e.g. an `abort` message racing
|
|
535
|
+
// the next `start`) must not interrupt the disposed query or arm the
|
|
536
|
+
// hard-abort fallback timer for it.
|
|
537
|
+
gracefulAbort = undefined;
|
|
538
|
+
if (hardAbortTimer != null) clearTimeout(hardAbortTimer);
|
|
539
|
+
turn.abortSignal.removeEventListener('abort', onHostAbort);
|
|
491
540
|
queryInput.close();
|
|
541
|
+
// Dispose the query explicitly: with streaming input the SDK keeps its
|
|
542
|
+
// CLI subprocess alive for more user messages, and a turn that ended
|
|
543
|
+
// through an interrupt or error path can otherwise leak that process —
|
|
544
|
+
// observed as orphaned `claude` processes holding the very conversation
|
|
545
|
+
// the next turn continues.
|
|
546
|
+
try {
|
|
547
|
+
await (q as { return?: (value?: unknown) => Promise<unknown> }).return?.(
|
|
548
|
+
undefined,
|
|
549
|
+
);
|
|
550
|
+
} catch {
|
|
551
|
+
// Best effort; the abort controller tears the process down otherwise.
|
|
552
|
+
}
|
|
492
553
|
}
|
|
493
554
|
|
|
494
555
|
if (emittedTerminalError) return;
|
package/src/bridge/package.json
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
8
|
-
"@anthropic-ai/claude-code": "2.1.
|
|
9
|
-
"@modelcontextprotocol/sdk": "1.
|
|
7
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.245",
|
|
8
|
+
"@anthropic-ai/claude-code": "2.1.245",
|
|
9
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
10
10
|
"ws": "8.21.0",
|
|
11
11
|
"zod": "4.4.3"
|
|
12
12
|
}
|