@ai-sdk/harness-claude-code 1.0.79 → 1.0.81

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,23 @@
1
1
  # @ai-sdk/harness-claude-code
2
2
 
3
+ ## 1.0.81
4
+
5
+ ### Patch Changes
6
+
7
+ - eace6fb: feat(harness): add experimental support for steering agent conversations mid-turn
8
+ - c0595b4: feat(harness): support passing a filesystem and process restricted sandbox session to `HarnessAgentSession`, using fallbacks in favor of the preferred network sandbox session methods
9
+ - Updated dependencies [eace6fb]
10
+ - Updated dependencies [c0595b4]
11
+ - @ai-sdk/harness@1.0.78
12
+
13
+ ## 1.0.80
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [e6087c9]
18
+ - @ai-sdk/provider-utils@5.0.28
19
+ - @ai-sdk/harness@1.0.77
20
+
3
21
  ## 1.0.79
4
22
 
5
23
  ### Patch Changes
@@ -1,6 +1,7 @@
1
1
  // ../harness/dist/bridge/index.js
2
2
  import { appendFile, mkdir, writeFile } from "fs/promises";
3
3
  import { existsSync, readFileSync } from "fs";
4
+ import { randomUUID } from "crypto";
4
5
  import { env as procEnv, pid, stdout } from "process";
5
6
  import { WebSocketServer } from "ws";
6
7
  var DEBUG_LEVEL_WEIGHT = {
@@ -31,6 +32,106 @@ function formatBridgeError(err) {
31
32
  }
32
33
  return { message: String(err) };
33
34
  }
35
+ function createBridgeUserMessageQueue(options) {
36
+ const messages = [];
37
+ const waiters = [];
38
+ const entries = /* @__PURE__ */ new Map();
39
+ let closed = false;
40
+ let pendingCount = 0;
41
+ const enqueue = (input) => {
42
+ const existing = entries.get(input.messageId);
43
+ if (existing != null) {
44
+ if (existing.response != null) {
45
+ options.respond(existing.response);
46
+ }
47
+ return;
48
+ }
49
+ let settled = false;
50
+ const settle = (response) => {
51
+ if (settled) return;
52
+ settled = true;
53
+ pendingCount--;
54
+ const entry = entries.get(input.messageId);
55
+ if (entry != null) entry.response = response;
56
+ options.respond(response);
57
+ };
58
+ const message = {
59
+ messageId: input.messageId,
60
+ text: input.text,
61
+ accept: () => {
62
+ settle({
63
+ type: "user-message-response",
64
+ messageId: input.messageId,
65
+ accepted: true
66
+ });
67
+ },
68
+ reject: (error) => {
69
+ settle({
70
+ type: "user-message-response",
71
+ messageId: input.messageId,
72
+ accepted: false,
73
+ error: { message: formatBridgeError(error).message }
74
+ });
75
+ }
76
+ };
77
+ entries.set(input.messageId, {
78
+ reject: message.reject
79
+ });
80
+ pendingCount++;
81
+ if (closed) {
82
+ message.reject(
83
+ new Error("The bridge turn is no longer accepting user messages.")
84
+ );
85
+ return;
86
+ }
87
+ const waiter = waiters.shift();
88
+ if (waiter != null) {
89
+ waiter({ done: false, value: message });
90
+ } else {
91
+ messages.push(message);
92
+ }
93
+ };
94
+ const close = (error) => {
95
+ if (closed) return;
96
+ closed = true;
97
+ const reason = error ?? new Error("The bridge turn ended before accepting the user message.");
98
+ for (const entry of entries.values()) {
99
+ if (entry.response == null) entry.reject(reason);
100
+ }
101
+ messages.length = 0;
102
+ while (waiters.length > 0) {
103
+ waiters.shift()({ done: true, value: void 0 });
104
+ }
105
+ };
106
+ return {
107
+ get pendingCount() {
108
+ return pendingCount;
109
+ },
110
+ enqueue,
111
+ close,
112
+ [Symbol.asyncIterator]() {
113
+ return {
114
+ next: () => {
115
+ const message = messages.shift();
116
+ if (message != null) {
117
+ return Promise.resolve({ done: false, value: message });
118
+ }
119
+ if (closed) {
120
+ return Promise.resolve({
121
+ done: true,
122
+ value: void 0
123
+ });
124
+ }
125
+ return new Promise(
126
+ (resolve) => {
127
+ waiters.push(resolve);
128
+ }
129
+ );
130
+ }
131
+ };
132
+ }
133
+ };
134
+ }
34
135
  function parseEnvList(value) {
35
136
  if (!value) return void 0;
36
137
  const items = value.split(",").map((item) => item.trim()).filter(Boolean);
@@ -247,6 +348,9 @@ async function runBridge(options) {
247
348
  switch (msg.type) {
248
349
  case "start": {
249
350
  activeSocket = ws;
351
+ currentUserMessages?.close(
352
+ new Error("A new bridge turn replaced the active turn.")
353
+ );
250
354
  const firstTurn = isFirstTurn;
251
355
  isFirstTurn = false;
252
356
  eventLog = [];
@@ -266,6 +370,7 @@ async function runBridge(options) {
266
370
  if (debugConfig.enabled) {
267
371
  installConsoleCapture();
268
372
  }
373
+ const userMessages = createBridgeUserMessageQueue({ respond: emit });
269
374
  const turn = {
270
375
  emit,
271
376
  requestToolResult: (toolCallId) => new Promise((resolve) => {
@@ -274,7 +379,7 @@ async function runBridge(options) {
274
379
  requestToolApproval: (approvalId) => new Promise((resolve) => {
275
380
  pendingToolApprovals.set(approvalId, resolve);
276
381
  }),
277
- pendingUserMessages: [],
382
+ experimental_userMessages: userMessages,
278
383
  abortSignal: turnAbort.signal,
279
384
  firstTurn,
280
385
  bridgeLog: (input) => {
@@ -292,12 +397,16 @@ async function runBridge(options) {
292
397
  emitWarning,
293
398
  emitError
294
399
  };
295
- currentUserMessages = turn.pendingUserMessages;
400
+ currentUserMessages = userMessages;
296
401
  try {
297
402
  await onStart(msg, turn);
298
403
  } catch (err) {
299
404
  emitError({ error: err, message: "bridge turn failed" });
300
405
  } finally {
406
+ userMessages.close();
407
+ if (currentUserMessages === userMessages) {
408
+ currentUserMessages = void 0;
409
+ }
301
410
  currentTurnState = "waiting";
302
411
  void writeBridgeMeta("waiting");
303
412
  }
@@ -319,9 +428,34 @@ async function runBridge(options) {
319
428
  }
320
429
  return;
321
430
  }
322
- case "user-message":
323
- currentUserMessages?.push(msg.text);
431
+ case "user-message": {
432
+ const messageId = msg.messageId ?? randomUUID();
433
+ if (currentUserMessages == null) {
434
+ sendControl(ws, {
435
+ type: "user-message-response",
436
+ messageId,
437
+ accepted: false,
438
+ error: { message: "The bridge has no active turn to steer." }
439
+ });
440
+ return;
441
+ }
442
+ if (ws !== activeSocket) {
443
+ sendControl(ws, {
444
+ type: "user-message-response",
445
+ messageId,
446
+ accepted: false,
447
+ error: {
448
+ message: "The connection does not own the active bridge turn."
449
+ }
450
+ });
451
+ return;
452
+ }
453
+ currentUserMessages.enqueue({
454
+ messageId,
455
+ text: msg.text
456
+ });
324
457
  return;
458
+ }
325
459
  case "abort":
326
460
  turnAbort?.abort();
327
461
  return;
@@ -395,7 +529,8 @@ async function runBridge(options) {
395
529
  sendControl(ws, {
396
530
  type: "bridge-hello",
397
531
  state: currentTurnState,
398
- lastSeq: seqCounter
532
+ lastSeq: seqCounter,
533
+ capabilities: { experimental_userMessageResponses: true }
399
534
  });
400
535
  ws.on("message", (raw) => {
401
536
  let parsed;
@@ -484,7 +619,7 @@ function createCompactionLatch(emit) {
484
619
  }
485
620
 
486
621
  // src/bridge/index.ts
487
- import { randomUUID as randomUUID2 } from "crypto";
622
+ import { randomUUID as randomUUID3 } from "crypto";
488
623
  import { argv, env as procEnv2, stdout as stdout2 } from "process";
489
624
  import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
490
625
  import * as mcpServerModule from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -504,7 +639,7 @@ function toClaudeSkillsOption(skills) {
504
639
  }
505
640
 
506
641
  // src/bridge/create-emit-stream-event.ts
507
- import { randomUUID } from "crypto";
642
+ import { randomUUID as randomUUID2 } from "crypto";
508
643
  function createClaudeStreamEventState() {
509
644
  return {
510
645
  nativeToolCallNames: /* @__PURE__ */ new Map(),
@@ -710,11 +845,11 @@ function handleStreamEvent(event, partialBlocks, send) {
710
845
  if (event.type === "content_block_start") {
711
846
  const blockType = event.content_block?.type;
712
847
  if (blockType === "text") {
713
- const id = randomUUID();
848
+ const id = randomUUID2();
714
849
  partialBlocks.set(index, { id, kind: "text" });
715
850
  send({ type: "text-start", id });
716
851
  } else if (blockType === "thinking") {
717
- const id = randomUUID();
852
+ const id = randomUUID2();
718
853
  partialBlocks.set(index, { id, kind: "thinking" });
719
854
  send({ type: "reasoning-start", id });
720
855
  }
@@ -1071,7 +1206,7 @@ async function runTurn(start, turn) {
1071
1206
  tool.description ?? "",
1072
1207
  shape,
1073
1208
  async (input) => {
1074
- const toolCallId = randomUUID2();
1209
+ const toolCallId = randomUUID3();
1075
1210
  emit({
1076
1211
  type: "tool-call",
1077
1212
  toolCallId,
@@ -1103,7 +1238,7 @@ async function runTurn(start, turn) {
1103
1238
  const compaction = createCompactionLatch((event) => emit(event));
1104
1239
  const queryInput = createQueryInput({
1105
1240
  initialUserMessage: start.prompt,
1106
- pendingUserMessages: turn.pendingUserMessages,
1241
+ userMessages: turn.experimental_userMessages,
1107
1242
  abortSignal: abortCtl.signal
1108
1243
  });
1109
1244
  const skillsOption = toClaudeSkillsOption(start.skills);
@@ -1197,6 +1332,9 @@ async function runTurn(start, turn) {
1197
1332
  for await (const msg of q) {
1198
1333
  if (abortCtl.signal.aborted) break;
1199
1334
  const type = msg.type;
1335
+ if (type === "command_lifecycle") {
1336
+ queryInput.handleLifecycle(msg);
1337
+ }
1200
1338
  emitStreamEvent(msg);
1201
1339
  if (type === "result") {
1202
1340
  if (msg.subtype === "success") {
@@ -1207,12 +1345,12 @@ async function runTurn(start, turn) {
1207
1345
  }
1208
1346
  const usage = msg.usage ?? msg.message?.usage;
1209
1347
  const harnessUsage = mapUsage(usage);
1210
- if (harnessUsage) turnUsage = harnessUsage;
1348
+ if (harnessUsage) turnUsage = addUsage(turnUsage, harnessUsage);
1211
1349
  if (typeof msg.total_cost_usd === "number") {
1212
1350
  totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
1213
1351
  }
1214
1352
  if (start.responseFormat?.type === "json" && msg.structured_output !== void 0) {
1215
- const id = randomUUID2();
1353
+ const id = randomUUID3();
1216
1354
  emit({ type: "text-start", id });
1217
1355
  emit({
1218
1356
  type: "text-delta",
@@ -1229,8 +1367,11 @@ async function runTurn(start, turn) {
1229
1367
  usage: harnessUsage ?? streamEventState.pendingStepUsage
1230
1368
  });
1231
1369
  }
1232
- queryInput.close();
1233
- break;
1370
+ queryInput.observeResult();
1371
+ if (!queryInput.hasActiveUserMessages()) {
1372
+ queryInput.close();
1373
+ break;
1374
+ }
1234
1375
  } else {
1235
1376
  emitTerminalError(
1236
1377
  (Array.isArray(msg.errors) ? msg.errors.join("\n") : void 0) || streamEventState.observedTerminalError || msg.result || "Unknown error"
@@ -1238,6 +1379,10 @@ async function runTurn(start, turn) {
1238
1379
  }
1239
1380
  continue;
1240
1381
  }
1382
+ if (queryInput.hasObservedResult && !queryInput.hasActiveUserMessages()) {
1383
+ queryInput.close();
1384
+ break;
1385
+ }
1241
1386
  }
1242
1387
  } catch (err) {
1243
1388
  if (!(abortCtl.signal.aborted && emittedTerminalError)) {
@@ -1259,55 +1404,124 @@ async function runTurn(start, turn) {
1259
1404
  }
1260
1405
  function createQueryInput({
1261
1406
  initialUserMessage,
1262
- pendingUserMessages,
1407
+ userMessages,
1263
1408
  abortSignal
1264
1409
  }) {
1265
1410
  let closed = false;
1266
- const close = () => {
1411
+ let observedResult = false;
1412
+ const submittedMessages = /* @__PURE__ */ new Map();
1413
+ const close = (error) => {
1414
+ if (closed) return;
1267
1415
  closed = true;
1416
+ userMessages.close(error);
1268
1417
  };
1269
1418
  if (abortSignal.aborted) {
1270
- close();
1419
+ close(abortSignal.reason);
1271
1420
  } else {
1272
- abortSignal.addEventListener("abort", close, { once: true });
1421
+ abortSignal.addEventListener("abort", () => close(abortSignal.reason), {
1422
+ once: true
1423
+ });
1273
1424
  }
1274
- const toUserMessage = (text) => ({
1425
+ const toUserMessage = (options) => ({
1275
1426
  type: "user",
1276
1427
  message: {
1277
1428
  role: "user",
1278
- content: [{ type: "text", text }]
1279
- }
1429
+ content: [{ type: "text", text: options.text }]
1430
+ },
1431
+ parent_tool_use_id: null,
1432
+ uuid: options.messageId,
1433
+ ...options.priority == null ? {} : { priority: options.priority }
1280
1434
  });
1435
+ const messageIterator = userMessages[Symbol.asyncIterator]();
1281
1436
  return {
1282
1437
  close,
1438
+ handleLifecycle: (message) => {
1439
+ const lifecycle = message;
1440
+ if (lifecycle.command_uuid == null || lifecycle.state == null) return;
1441
+ const submitted = submittedMessages.get(lifecycle.command_uuid);
1442
+ if (submitted == null) return;
1443
+ if (lifecycle.state === "queued" || lifecycle.state === "started") {
1444
+ submitted.accept();
1445
+ return;
1446
+ }
1447
+ if (lifecycle.state === "cancelled" || lifecycle.state === "discarded") {
1448
+ submitted.reject(
1449
+ new Error(`Claude Code ${lifecycle.state} the user message.`)
1450
+ );
1451
+ }
1452
+ submittedMessages.delete(lifecycle.command_uuid);
1453
+ },
1454
+ hasActiveUserMessages: () => submittedMessages.size > 0 || userMessages.pendingCount > 0,
1455
+ observeResult: () => {
1456
+ observedResult = true;
1457
+ },
1458
+ get hasObservedResult() {
1459
+ return observedResult;
1460
+ },
1283
1461
  input: {
1284
1462
  [Symbol.asyncIterator]() {
1285
1463
  let sentInitial = false;
1286
1464
  return {
1287
1465
  async next() {
1288
- while (!closed && !abortSignal.aborted) {
1289
- if (!sentInitial) {
1290
- sentInitial = true;
1291
- return {
1292
- value: toUserMessage(initialUserMessage),
1293
- done: false
1294
- };
1295
- }
1296
- if (pendingUserMessages.length > 0) {
1297
- return {
1298
- value: toUserMessage(pendingUserMessages.shift()),
1299
- done: false
1300
- };
1301
- }
1302
- await new Promise((resolve) => setTimeout(resolve, 50));
1466
+ if (closed || abortSignal.aborted) {
1467
+ return {
1468
+ value: void 0,
1469
+ done: true
1470
+ };
1471
+ }
1472
+ if (!sentInitial) {
1473
+ sentInitial = true;
1474
+ return {
1475
+ value: toUserMessage({
1476
+ text: initialUserMessage,
1477
+ messageId: randomUUID3()
1478
+ }),
1479
+ done: false
1480
+ };
1303
1481
  }
1304
- return { value: void 0, done: true };
1482
+ const nextMessage = await messageIterator.next();
1483
+ if (nextMessage.done) {
1484
+ return {
1485
+ value: void 0,
1486
+ done: true
1487
+ };
1488
+ }
1489
+ submittedMessages.set(
1490
+ nextMessage.value.messageId,
1491
+ nextMessage.value
1492
+ );
1493
+ return {
1494
+ value: toUserMessage({
1495
+ text: nextMessage.value.text,
1496
+ messageId: nextMessage.value.messageId,
1497
+ priority: "next"
1498
+ }),
1499
+ done: false
1500
+ };
1305
1501
  }
1306
1502
  };
1307
1503
  }
1308
1504
  }
1309
1505
  };
1310
1506
  }
1507
+ function addUsage(total, usage) {
1508
+ if (total == null) return usage;
1509
+ const result = { ...total };
1510
+ for (const [key, value] of Object.entries(usage)) {
1511
+ const previous = result[key];
1512
+ if (typeof value === "number" && typeof previous === "number") {
1513
+ result[key] = previous + value;
1514
+ } else if (value != null && previous != null && typeof value === "object" && typeof previous === "object" && !Array.isArray(value) && !Array.isArray(previous)) {
1515
+ result[key] = addUsage(
1516
+ previous,
1517
+ value
1518
+ );
1519
+ } else {
1520
+ result[key] = value;
1521
+ }
1522
+ }
1523
+ return result;
1524
+ }
1311
1525
  function parseArgs(args2) {
1312
1526
  const out = {};
1313
1527
  for (let i = 0; i < args2.length; i++) {