@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 +10 -0
- package/dist/bridge/index.mjs +142 -8
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +83 -26
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/index.ts +0 -2
- package/src/codex-harness.ts +111 -30
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
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 ??
|
|
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,
|