@ai-sdk/harness-codex 1.0.79 → 1.0.80

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,15 @@
1
1
  # @ai-sdk/harness-codex
2
2
 
3
+ ## 1.0.80
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
+
3
13
  ## 1.0.79
4
14
 
5
15
  ### 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;
@@ -705,7 +840,7 @@ function defaultUsage() {
705
840
  }
706
841
 
707
842
  // src/bridge/create-emit-stream-event.ts
708
- import { randomUUID } from "crypto";
843
+ import { randomUUID as randomUUID2 } from "crypto";
709
844
  var NATIVE_TO_COMMON = {
710
845
  shell: "bash",
711
846
  web_search: "webSearch"
@@ -749,7 +884,7 @@ function createEmitStreamEvent({
749
884
  }
750
885
  if (!event.item) return;
751
886
  const item = event.item;
752
- const id = item.id ?? randomUUID();
887
+ const id = item.id ?? randomUUID2();
753
888
  const observeStep = () => {
754
889
  stepTracker.observeEvent({ event, itemId: id });
755
890
  };
@@ -1265,7 +1400,6 @@ async function runTurn(start, turn) {
1265
1400
  finishReason: { unified: "stop", raw: "stop" },
1266
1401
  totalUsage: turnUsage ?? defaultUsage()
1267
1402
  });
1268
- void turn.pendingUserMessages;
1269
1403
  }
1270
1404
  async function startToolRelay({
1271
1405
  tools,