agents 0.26.0 → 0.27.0
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/README.md +8 -9
- package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-Bhdbvf7s.d.ts} +13 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-YrATZJu2.d.ts → agent-tools-bJZwBMfq.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/ai-sdk-OoGkFy3s.js +2444 -0
- package/dist/ai-sdk-OoGkFy3s.js.map +1 -0
- package/dist/apply-chunks-1FheRb9W.js +99 -0
- package/dist/apply-chunks-1FheRb9W.js.map +1 -0
- package/dist/broadcast-state-CEyI4uAK.js +69 -0
- package/dist/broadcast-state-CEyI4uAK.js.map +1 -0
- package/dist/browser/ai-sdk.d.ts +1 -1
- package/dist/browser/ai-sdk.js +2 -2
- package/dist/browser/ai-sdk.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/tanstack-ai.d.ts +1 -1
- package/dist/browser/tanstack-ai.js +1 -1
- package/dist/{browser-DE3d-RLk.d.ts → browser-tmkcAAI3.d.ts} +2 -2
- package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-Bn-nzJQI.d.ts} +3 -3
- package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-CBqs6hqa.js} +58 -18
- package/dist/browser-tool-CBqs6hqa.js.map +1 -0
- package/dist/capability-B4WbF81e.js.map +1 -1
- package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-O5VbQzqg.d.ts} +9 -1
- package/dist/channel-Bak_6zDP.d.ts +333 -0
- package/dist/chat/index.d.ts +11 -9
- package/dist/chat/index.js +5 -194
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +2 -4
- package/dist/chat/react.js +3 -1
- package/dist/chat/react.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +144 -0
- package/dist/{client-fyQrLGkD.js → client-1tEQ1Oqz.js} +27 -2
- package/dist/{client-fyQrLGkD.js.map → client-1tEQ1Oqz.js.map} +1 -1
- package/dist/{client-je3koD9Z.d.ts → client-DS3Dz9l0.d.ts} +2 -2
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +1 -1
- package/dist/conversations-DH0ut_8n.d.ts +192 -0
- package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-C07C5o5Y.d.ts} +3 -3
- package/dist/daemon-core-Buubj-y1.js +481 -0
- package/dist/daemon-core-Buubj-y1.js.map +1 -0
- package/dist/daemon-core-CbohVPAu.d.ts +132 -0
- package/dist/{do-oauth-client-provider-Tmf1vgKz.d.ts → do-oauth-client-provider-DfsUcpc3.d.ts} +7 -1
- package/dist/{durable-object-lifecycle-HhgOMWq9.d.ts → durable-object-lifecycle-DRqp475_.d.ts} +2 -2
- package/dist/{channels/email.d.ts → experimental/channels/email/index.d.ts} +25 -12
- package/dist/{channels/email.js → experimental/channels/email/index.js} +13 -35
- package/dist/experimental/channels/email/index.js.map +1 -0
- package/dist/experimental/channels/index.d.ts +270 -0
- package/dist/experimental/channels/index.js +1186 -0
- package/dist/experimental/channels/index.js.map +1 -0
- package/dist/{channels/slack.d.ts → experimental/channels/slack/index.d.ts} +19 -9
- package/dist/experimental/channels/slack/index.js +267 -0
- package/dist/experimental/channels/slack/index.js.map +1 -0
- package/dist/{channels/telegram.d.ts → experimental/channels/telegram/index.d.ts} +19 -9
- package/dist/experimental/channels/telegram/index.js +198 -0
- package/dist/experimental/channels/telegram/index.js.map +1 -0
- package/dist/experimental/channels/web/ai-sdk.d.ts +55 -0
- package/dist/experimental/channels/web/ai-sdk.js +266 -0
- package/dist/experimental/channels/web/ai-sdk.js.map +1 -0
- package/dist/experimental/channels/web/client.d.ts +97 -0
- package/dist/experimental/channels/web/client.js +349 -0
- package/dist/experimental/channels/web/client.js.map +1 -0
- package/dist/experimental/channels/web/index.d.ts +189 -0
- package/dist/experimental/channels/web/index.js +332 -0
- package/dist/experimental/channels/web/index.js.map +1 -0
- package/dist/gateway-BDC07gYO.d.ts +100 -0
- package/dist/handle-zAzh0UW2.d.ts +460 -0
- package/dist/harness/ai-sdk/index.d.ts +175 -0
- package/dist/harness/ai-sdk/index.js +605 -0
- package/dist/harness/ai-sdk/index.js.map +1 -0
- package/dist/harness/container/daemon/index.d.ts +76 -0
- package/dist/harness/container/daemon/index.js +3 -0
- package/dist/harness/container/index.d.ts +740 -0
- package/dist/harness/container/index.js +2152 -0
- package/dist/harness/container/index.js.map +1 -0
- package/dist/harness/container/runtime/index.d.ts +144 -0
- package/dist/harness/container/runtime/index.js +1082 -0
- package/dist/harness/container/runtime/index.js.map +1 -0
- package/dist/harness/opencode/index.d.ts +369 -0
- package/dist/harness/opencode/index.js +1072 -0
- package/dist/harness/opencode/index.js.map +1 -0
- package/dist/harness/pi/index.d.ts +2 -1
- package/dist/harness/store/index.d.ts +24 -0
- package/dist/harness/store/index.js +2 -0
- package/dist/harness/think/index.d.ts +821 -0
- package/dist/harness/think/index.js +2529 -0
- package/dist/harness/think/index.js.map +1 -0
- package/dist/harness-B6B9S3rb.d.ts +203 -0
- package/dist/{images-iiTDSyRu.js → images-Ce9SCu3X.js} +2 -248
- package/dist/images-Ce9SCu3X.js.map +1 -0
- package/dist/{index-BreL0YrQ.d.ts → index-D0A5tbWu.d.ts} +2 -2
- package/dist/index-Ib_4dwOo.d.ts +200 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/ingress-EFbrS547.js +9 -0
- package/dist/ingress-EFbrS547.js.map +1 -0
- package/dist/input-schema-DNrpTu49.js +23 -0
- package/dist/input-schema-DNrpTu49.js.map +1 -0
- package/dist/internal-kmw70xwR.js +46 -0
- package/dist/internal-kmw70xwR.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +3 -3
- package/dist/mcp/client/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js +9 -0
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/client/x402.d.ts +35 -4
- package/dist/mcp/client/x402.js +24 -3
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/models/ai-sdk/index.d.ts +16 -14
- package/dist/models/ai-sdk/index.js +1 -2442
- package/dist/models/opencode/index.d.ts +65 -0
- package/dist/models/opencode/index.js +285 -0
- package/dist/models/opencode/index.js.map +1 -0
- package/dist/models/pi-ai/index.d.ts +9 -7
- package/dist/models/pi-ai/index.js +2 -1
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/options-pwTNq9z6.d.ts +134 -0
- package/dist/protocol-BEgrJYC5.d.ts +331 -0
- package/dist/protocol-CukuI73M.d.ts +386 -0
- package/dist/protocol-cOklVBBD.js +227 -0
- package/dist/protocol-cOklVBBD.js.map +1 -0
- package/dist/{protocol-kHsr-f0X.js → protocol-eXeJxhgu.js} +2 -67
- package/dist/protocol-eXeJxhgu.js.map +1 -0
- package/dist/protocol-y2sSY584.js +148 -0
- package/dist/protocol-y2sSY584.js.map +1 -0
- package/dist/queue/index.d.ts +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +2 -2
- package/dist/{scheduler-RdACNXDP.d.ts → scheduler-CrFCnv_J.d.ts} +2 -2
- package/dist/schedules/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +11 -434
- package/dist/sessions/index.js +1 -2271
- package/dist/sessions-B9pNuxdT.js +2272 -0
- package/dist/sessions-B9pNuxdT.js.map +1 -0
- package/dist/settings-C45hDliX.js +253 -0
- package/dist/settings-C45hDliX.js.map +1 -0
- package/dist/{transport-PfFnt-Ld.d.ts → settings-NgGwF7Qt.d.ts} +14 -208
- package/dist/{src-BZeu-SCD.js → src-DgCbZtIR.js} +4 -4
- package/dist/{src-BZeu-SCD.js.map → src-DgCbZtIR.js.map} +1 -1
- package/dist/stall-watchdog-BpRF2uwG.js +195 -0
- package/dist/stall-watchdog-BpRF2uwG.js.map +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/store-DLeqKuj3.d.ts +310 -0
- package/dist/store-vgcPYo8K.js +432 -0
- package/dist/store-vgcPYo8K.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-DW-UCOUc.js → streams-CITClVer.js} +16 -3
- package/dist/streams-CITClVer.js.map +1 -0
- package/dist/{streams-Cmkj9QfW.d.ts → streams-fbG61qM8.d.ts} +2 -2
- package/dist/sub-routing.d.ts +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-68DESxf0.js → tasks-CkTZNaV1.js} +2 -1
- package/dist/tasks-CkTZNaV1.js.map +1 -0
- package/dist/{tasks-9YxYtByh.d.ts → tasks-PtlB4Uyn.d.ts} +2 -2
- package/dist/tool-3PA8xjB_.js +421 -0
- package/dist/tool-3PA8xjB_.js.map +1 -0
- package/dist/tool-BPJEbwaX.d.ts +278 -0
- package/dist/transport-k2mFrtL8.d.ts +200 -0
- package/dist/turns-DCV3ycDl.js +317 -0
- package/dist/turns-DCV3ycDl.js.map +1 -0
- package/dist/voice/client.js +1 -1
- package/dist/voice/index.d.ts +2 -2
- package/dist/voice/index.js +1 -1
- package/dist/websearch/index.d.ts +58 -0
- package/dist/websearch/index.js +2 -0
- package/dist/websearch/tools/ai-sdk.d.ts +77 -0
- package/dist/websearch/tools/ai-sdk.js +46 -0
- package/dist/websearch/tools/ai-sdk.js.map +1 -0
- package/dist/websearch/tools/pi.d.ts +82 -0
- package/dist/websearch/tools/pi.js +89 -0
- package/dist/websearch/tools/pi.js.map +1 -0
- package/dist/websearch/tools/tanstack-ai.d.ts +71 -0
- package/dist/websearch/tools/tanstack-ai.js +46 -0
- package/dist/websearch/tools/tanstack-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +12 -296
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-Ci-LZFHH.js → websockets-CmENGb-Z.js} +18 -4
- package/dist/websockets-CmENGb-Z.js.map +1 -0
- package/dist/{wire-types-CrjKvDyi.d.ts → wire-types-Cv0iwjk5.d.ts} +3 -51
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +1 -1
- package/docs/browse-the-web.md +3 -0
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +2 -1
- package/docs/lifecycle.md +40 -0
- package/docs/search-the-web.md +133 -0
- package/package.json +117 -38
- package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
- package/dist/channel-Bnm4S7T2.d.ts +0 -491
- package/dist/channels/ai-sdk.d.ts +0 -46
- package/dist/channels/ai-sdk.js +0 -120
- package/dist/channels/ai-sdk.js.map +0 -1
- package/dist/channels/email.js.map +0 -1
- package/dist/channels/index.d.ts +0 -233
- package/dist/channels/index.js +0 -608
- package/dist/channels/index.js.map +0 -1
- package/dist/channels/slack.js +0 -614
- package/dist/channels/slack.js.map +0 -1
- package/dist/channels/tanstack-ai.d.ts +0 -39
- package/dist/channels/tanstack-ai.js +0 -17
- package/dist/channels/tanstack-ai.js.map +0 -1
- package/dist/channels/telegram.js +0 -427
- package/dist/channels/telegram.js.map +0 -1
- package/dist/channels/voice.d.ts +0 -45
- package/dist/channels/voice.js +0 -122
- package/dist/channels/voice.js.map +0 -1
- package/dist/images-iiTDSyRu.js.map +0 -1
- package/dist/index-BB0kqhIz.d.ts +0 -101
- package/dist/ingress-BfetZbMO.js +0 -83
- package/dist/ingress-BfetZbMO.js.map +0 -1
- package/dist/internal-CYlgHl1l.js +0 -59
- package/dist/internal-CYlgHl1l.js.map +0 -1
- package/dist/models/ai-sdk/index.js.map +0 -1
- package/dist/protocol-kHsr-f0X.js.map +0 -1
- package/dist/sessions/index.js.map +0 -1
- package/dist/streams-DW-UCOUc.js.map +0 -1
- package/dist/surface-bZZJqBka.js +0 -17
- package/dist/surface-bZZJqBka.js.map +0 -1
- package/dist/tasks-68DESxf0.js.map +0 -1
- package/dist/tool-schema-CBjGPrsQ.js +0 -31
- package/dist/tool-schema-CBjGPrsQ.js.map +0 -1
- package/dist/websockets-Ci-LZFHH.js.map +0 -1
- package/docs/channels.md +0 -323
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../../../classPrivateFieldGet2-DZBYAB34.js";
|
|
2
|
+
import { t as _classPrivateMethodInitSpec } from "../../../classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
3
|
+
import { n as CLOSE_UNAUTHORIZED, o as CONTAINER_SESSION_PATH, r as CONTAINER_ENV, s as CONTAINER_TOKEN_HEADER, u as inputText } from "../../../protocol-cOklVBBD.js";
|
|
4
|
+
import { t as ContainerDaemon } from "../../../daemon-core-Buubj-y1.js";
|
|
5
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { chown, copyFile, mkdir, open, readdir, readlink, rm, stat, symlink } from "node:fs/promises";
|
|
7
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
8
|
+
import { createServer } from "node:http";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { constants } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { createInterface } from "node:readline";
|
|
13
|
+
//#region src/harness/container/runtime/websocket.ts
|
|
14
|
+
/**
|
|
15
|
+
* The server half of RFC 6455, for one kind of peer: the Durable Object,
|
|
16
|
+
* which sends text messages and closes cleanly. Enough of the protocol for
|
|
17
|
+
* that (handshake, masked client frames, fragmentation, ping, close) and
|
|
18
|
+
* nothing else, so the daemon needs no dependencies and runs on the plain
|
|
19
|
+
* Node in `cloudflare/debian-trixie`.
|
|
20
|
+
*/
|
|
21
|
+
const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
22
|
+
/** Largest message accepted. Restore messages are chunked well below this. */
|
|
23
|
+
const MAX_MESSAGE_BYTES = 64 * 1024 * 1024;
|
|
24
|
+
const OP_CONTINUATION = 0;
|
|
25
|
+
const OP_TEXT = 1;
|
|
26
|
+
const OP_BINARY = 2;
|
|
27
|
+
const OP_CLOSE = 8;
|
|
28
|
+
const OP_PING = 9;
|
|
29
|
+
const OP_PONG = 10;
|
|
30
|
+
function frame(opcode, payload) {
|
|
31
|
+
const length = payload.length;
|
|
32
|
+
const header = length < 126 ? Buffer.from([128 | opcode, length]) : length < 65536 ? Buffer.from([
|
|
33
|
+
128 | opcode,
|
|
34
|
+
126,
|
|
35
|
+
length >> 8,
|
|
36
|
+
length & 255
|
|
37
|
+
]) : Buffer.concat([Buffer.from([128 | opcode, 127]), (() => {
|
|
38
|
+
const size = Buffer.alloc(8);
|
|
39
|
+
size.writeBigUInt64BE(BigInt(length));
|
|
40
|
+
return size;
|
|
41
|
+
})()]);
|
|
42
|
+
return Buffer.concat([header, payload]);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Complete the upgrade handshake and serve the connection.
|
|
46
|
+
*
|
|
47
|
+
* @param request - The upgrade request.
|
|
48
|
+
* @param socket - The raw socket.
|
|
49
|
+
* @param head - Bytes read past the request head.
|
|
50
|
+
* @param handlers - Called for each message and on close.
|
|
51
|
+
* @returns The connection, or undefined when the request was not a valid
|
|
52
|
+
* upgrade (the socket is destroyed).
|
|
53
|
+
*/
|
|
54
|
+
function acceptWebSocket(request, socket, head, handlers) {
|
|
55
|
+
const key = request.headers["sec-websocket-key"];
|
|
56
|
+
if (typeof key !== "string" || request.headers.upgrade?.toLowerCase() !== "websocket") {
|
|
57
|
+
socket.destroy();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const accept = createHash("sha1").update(key + GUID).digest("base64");
|
|
61
|
+
socket.write(`HTTP/1.1 101 Switching Protocols\r
|
|
62
|
+
Upgrade: websocket\r
|
|
63
|
+
Connection: Upgrade\r
|
|
64
|
+
Sec-WebSocket-Accept: ${accept}\r\n\r\n`);
|
|
65
|
+
let open = true;
|
|
66
|
+
let ended = false;
|
|
67
|
+
const end = () => {
|
|
68
|
+
if (ended) return;
|
|
69
|
+
ended = true;
|
|
70
|
+
open = false;
|
|
71
|
+
handlers.closed();
|
|
72
|
+
};
|
|
73
|
+
const write = (opcode, payload) => {
|
|
74
|
+
if (!open) return;
|
|
75
|
+
socket.write(frame(opcode, payload));
|
|
76
|
+
};
|
|
77
|
+
const connection = {
|
|
78
|
+
send: (text) => write(OP_TEXT, Buffer.from(text, "utf8")),
|
|
79
|
+
close: (code, reason) => {
|
|
80
|
+
if (!open) return;
|
|
81
|
+
const payload = Buffer.alloc(2 + Buffer.byteLength(reason));
|
|
82
|
+
payload.writeUInt16BE(code);
|
|
83
|
+
payload.write(reason, 2);
|
|
84
|
+
write(OP_CLOSE, payload);
|
|
85
|
+
open = false;
|
|
86
|
+
socket.end();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
let buffered = head.length > 0 ? Buffer.from(head) : Buffer.alloc(0);
|
|
90
|
+
let fragments = [];
|
|
91
|
+
let fragmentBytes = 0;
|
|
92
|
+
const drain = () => {
|
|
93
|
+
for (;;) {
|
|
94
|
+
if (buffered.length < 2) return;
|
|
95
|
+
const first = buffered[0] ?? 0;
|
|
96
|
+
const second = buffered[1] ?? 0;
|
|
97
|
+
const fin = (first & 128) !== 0;
|
|
98
|
+
const opcode = first & 15;
|
|
99
|
+
const masked = (second & 128) !== 0;
|
|
100
|
+
let length = second & 127;
|
|
101
|
+
let offset = 2;
|
|
102
|
+
if (length === 126) {
|
|
103
|
+
if (buffered.length < 4) return;
|
|
104
|
+
length = buffered.readUInt16BE(2);
|
|
105
|
+
offset = 4;
|
|
106
|
+
} else if (length === 127) {
|
|
107
|
+
if (buffered.length < 10) return;
|
|
108
|
+
const big = buffered.readBigUInt64BE(2);
|
|
109
|
+
if (big > BigInt(MAX_MESSAGE_BYTES)) {
|
|
110
|
+
connection.close(1009, "message too big");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
length = Number(big);
|
|
114
|
+
offset = 10;
|
|
115
|
+
}
|
|
116
|
+
const maskOffset = offset;
|
|
117
|
+
if (masked) offset += 4;
|
|
118
|
+
if (buffered.length < offset + length) return;
|
|
119
|
+
const payload = Buffer.from(buffered.subarray(offset, offset + length));
|
|
120
|
+
if (masked) for (let i = 0; i < payload.length; i++) payload[i] = (payload[i] ?? 0) ^ (buffered[maskOffset + i % 4] ?? 0);
|
|
121
|
+
buffered = buffered.subarray(offset + length);
|
|
122
|
+
switch (opcode) {
|
|
123
|
+
case OP_TEXT:
|
|
124
|
+
case OP_BINARY:
|
|
125
|
+
case OP_CONTINUATION:
|
|
126
|
+
fragments.push(payload);
|
|
127
|
+
fragmentBytes += payload.length;
|
|
128
|
+
if (fragmentBytes > MAX_MESSAGE_BYTES) {
|
|
129
|
+
connection.close(1009, "message too big");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (fin) {
|
|
133
|
+
const message = Buffer.concat(fragments).toString("utf8");
|
|
134
|
+
fragments = [];
|
|
135
|
+
fragmentBytes = 0;
|
|
136
|
+
handlers.message(message);
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
case OP_PING:
|
|
140
|
+
write(OP_PONG, payload);
|
|
141
|
+
break;
|
|
142
|
+
case OP_PONG: break;
|
|
143
|
+
case OP_CLOSE:
|
|
144
|
+
if (open) {
|
|
145
|
+
write(OP_CLOSE, payload.subarray(0, 2));
|
|
146
|
+
open = false;
|
|
147
|
+
}
|
|
148
|
+
socket.end();
|
|
149
|
+
end();
|
|
150
|
+
return;
|
|
151
|
+
default:
|
|
152
|
+
connection.close(1002, "unsupported opcode");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
socket.on("data", (chunk) => {
|
|
158
|
+
buffered = buffered.length === 0 ? chunk : Buffer.concat([buffered, chunk]);
|
|
159
|
+
drain();
|
|
160
|
+
});
|
|
161
|
+
socket.on("close", end);
|
|
162
|
+
socket.on("error", end);
|
|
163
|
+
if (buffered.length > 0) queueMicrotask(drain);
|
|
164
|
+
return connection;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/harness/container/runtime/server.ts
|
|
168
|
+
/**
|
|
169
|
+
* The daemon's transport: an HTTP server that answers the health check and
|
|
170
|
+
* upgrades `/sessions/<id>` to a WebSocket for the daemon core. The Durable
|
|
171
|
+
* Object reaches it through `ctx.container.getTcpPort(port)`.
|
|
172
|
+
*
|
|
173
|
+
* Every request except the health check must carry the token the object
|
|
174
|
+
* started the container with. The health check stays open so that a wrong
|
|
175
|
+
* token looks different from a dead container.
|
|
176
|
+
*/
|
|
177
|
+
function tokenMatches(expected, given) {
|
|
178
|
+
if (given === void 0) return false;
|
|
179
|
+
const a = Buffer.from(expected);
|
|
180
|
+
const b = Buffer.from(given);
|
|
181
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Start the daemon.
|
|
185
|
+
*
|
|
186
|
+
* @param options - The adapter, identity, secret, and port.
|
|
187
|
+
* @returns The running server, once it listens.
|
|
188
|
+
*
|
|
189
|
+
* @experimental The API may change before it stabilizes.
|
|
190
|
+
*/
|
|
191
|
+
async function serve(options) {
|
|
192
|
+
const daemon = new ContainerDaemon({
|
|
193
|
+
adapter: options.adapter,
|
|
194
|
+
runtimeId: options.runtimeId
|
|
195
|
+
});
|
|
196
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
197
|
+
const server = createServer((request, response) => {
|
|
198
|
+
if (request.url === "/healthz") {
|
|
199
|
+
response.writeHead(200, { "content-type": "text/plain" });
|
|
200
|
+
response.end("ok");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
response.writeHead(404);
|
|
204
|
+
response.end();
|
|
205
|
+
});
|
|
206
|
+
server.on("upgrade", (request, socket, head) => {
|
|
207
|
+
const url = new URL(request.url ?? "/", "http://container");
|
|
208
|
+
if (!url.pathname.startsWith("/sessions/")) {
|
|
209
|
+
socket.destroy();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const session = decodeURIComponent(url.pathname.slice(CONTAINER_SESSION_PATH.length));
|
|
213
|
+
const header = request.headers[CONTAINER_TOKEN_HEADER];
|
|
214
|
+
if (!tokenMatches(options.token, Array.isArray(header) ? header[0] : header) || session.length === 0) {
|
|
215
|
+
acceptWebSocket(request, socket, head, {
|
|
216
|
+
message: () => {},
|
|
217
|
+
closed: () => {}
|
|
218
|
+
})?.close(CLOSE_UNAUTHORIZED, "unauthorized");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
let connection;
|
|
222
|
+
const ws = acceptWebSocket(request, socket, head, {
|
|
223
|
+
message: (text) => connection?.receive(text),
|
|
224
|
+
closed: () => {
|
|
225
|
+
if (ws) sockets.delete(ws);
|
|
226
|
+
connection?.closed();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
if (!ws) return;
|
|
230
|
+
sockets.add(ws);
|
|
231
|
+
connection = daemon.connect(session, ws);
|
|
232
|
+
});
|
|
233
|
+
await new Promise((resolve) => {
|
|
234
|
+
server.listen(options.port, options.host ?? "0.0.0.0", resolve);
|
|
235
|
+
});
|
|
236
|
+
const address = server.address();
|
|
237
|
+
return {
|
|
238
|
+
port: typeof address === "object" && address ? address.port : 0,
|
|
239
|
+
async close() {
|
|
240
|
+
for (const client of sockets) client.close(1001, "shutting down");
|
|
241
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
242
|
+
await daemon.close();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Start the daemon with the identity, secret, and port the Durable Object
|
|
248
|
+
* started the container with, and stop it on SIGTERM. The entrypoint of an
|
|
249
|
+
* image: `serveFromEnv(cliAdapter({ ... }))`.
|
|
250
|
+
*
|
|
251
|
+
* @param adapter - The agent to run.
|
|
252
|
+
* @param env - The environment. Default `process.env`.
|
|
253
|
+
* @returns The running server.
|
|
254
|
+
*
|
|
255
|
+
* @experimental The API may change before it stabilizes.
|
|
256
|
+
*/
|
|
257
|
+
async function serveFromEnv(adapter, env = process.env) {
|
|
258
|
+
const token = env[CONTAINER_ENV.token];
|
|
259
|
+
const runtimeId = env[CONTAINER_ENV.runtimeId];
|
|
260
|
+
const port = Number(env[CONTAINER_ENV.port] ?? "8080");
|
|
261
|
+
if (!token || !runtimeId || !Number.isInteger(port) || port <= 0) throw new Error(`${CONTAINER_ENV.token}, ${CONTAINER_ENV.runtimeId} and ${CONTAINER_ENV.port} must be set; ContainerHarness sets them`);
|
|
262
|
+
delete env[CONTAINER_ENV.token];
|
|
263
|
+
const server = await serve({
|
|
264
|
+
adapter,
|
|
265
|
+
runtimeId,
|
|
266
|
+
token,
|
|
267
|
+
port
|
|
268
|
+
});
|
|
269
|
+
console.log(`harness-daemon: ${adapter.id} on ${server.port} (${runtimeId})`);
|
|
270
|
+
const stop = () => {
|
|
271
|
+
server.close().finally(() => process.exit(0));
|
|
272
|
+
};
|
|
273
|
+
process.once("SIGTERM", stop);
|
|
274
|
+
process.once("SIGINT", stop);
|
|
275
|
+
return server;
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/harness/container/runtime/json.ts
|
|
279
|
+
/**
|
|
280
|
+
* Whether a JSON value is an object (not an array or a primitive).
|
|
281
|
+
*
|
|
282
|
+
* @param value - The value, or undefined for a missing field.
|
|
283
|
+
* @returns True for an object.
|
|
284
|
+
*/
|
|
285
|
+
function isJsonObject(value) {
|
|
286
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The strings of a JSON array field.
|
|
290
|
+
*
|
|
291
|
+
* @param value - The field.
|
|
292
|
+
* @returns Its string elements, or undefined when it is not an array.
|
|
293
|
+
*/
|
|
294
|
+
function stringsOf(value) {
|
|
295
|
+
if (!Array.isArray(value)) return void 0;
|
|
296
|
+
return value.filter((each) => typeof each === "string");
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/harness/container/runtime/file-mirror.ts
|
|
300
|
+
/**
|
|
301
|
+
* Mirrors a CLI's state directories into the Durable Object and back.
|
|
302
|
+
*
|
|
303
|
+
* Agent CLIs keep their sessions as files: Claude Code under
|
|
304
|
+
* `~/.claude/projects`, Codex under `~/.codex/sessions`. A container's disk
|
|
305
|
+
* does not outlive the container, so the mirror ships every change to
|
|
306
|
+
* those files out as persisted entries, and writes them back, in order,
|
|
307
|
+
* when the session opens in a new container. The CLI then resumes its own
|
|
308
|
+
* session from its own files and never knows it moved.
|
|
309
|
+
*
|
|
310
|
+
* Session files are append-only logs, so a file that grew ships only the
|
|
311
|
+
* bytes it gained. A file that shrank, or changed without growing, ships
|
|
312
|
+
* whole. Data travels base64-encoded in pieces small enough for one
|
|
313
|
+
* WebSocket message.
|
|
314
|
+
*/
|
|
315
|
+
/** Most bytes of one file in one entry. */
|
|
316
|
+
const PIECE_BYTES = 128 * 1024;
|
|
317
|
+
function isFileEntry(value) {
|
|
318
|
+
return isJsonObject(value) && value.kind === "file" && typeof value.path === "string" && typeof value.offset === "number" && typeof value.data === "string";
|
|
319
|
+
}
|
|
320
|
+
var _root = /* @__PURE__ */ new WeakMap();
|
|
321
|
+
var _dirs = /* @__PURE__ */ new WeakMap();
|
|
322
|
+
var _seen$1 = /* @__PURE__ */ new WeakMap();
|
|
323
|
+
var _FileMirror_brand = /* @__PURE__ */ new WeakSet();
|
|
324
|
+
/** One session's mirrored directories, under the session's home. */
|
|
325
|
+
var FileMirror = class {
|
|
326
|
+
/**
|
|
327
|
+
* @param root - The session's home directory.
|
|
328
|
+
* @param dirs - Directories to mirror, relative to `root`.
|
|
329
|
+
*/
|
|
330
|
+
constructor(root, dirs) {
|
|
331
|
+
_classPrivateMethodInitSpec(this, _FileMirror_brand);
|
|
332
|
+
_classPrivateFieldInitSpec(this, _root, void 0);
|
|
333
|
+
_classPrivateFieldInitSpec(this, _dirs, void 0);
|
|
334
|
+
_classPrivateFieldInitSpec(this, _seen$1, /* @__PURE__ */ new Map());
|
|
335
|
+
_classPrivateFieldSet2(_root, this, resolve(root));
|
|
336
|
+
_classPrivateFieldSet2(_dirs, this, dirs);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Replace the mirrored directories with the persisted entries, applied in
|
|
340
|
+
* order. Entries that are not file entries are skipped. Whatever the
|
|
341
|
+
* directories held before (a reset session's old files, say) is removed.
|
|
342
|
+
*
|
|
343
|
+
* @param entries - Everything persisted for the session, oldest first.
|
|
344
|
+
*/
|
|
345
|
+
async restore(entries) {
|
|
346
|
+
for (const dir of _classPrivateFieldGet2(_dirs, this)) {
|
|
347
|
+
const path = _assertClassBrand(_FileMirror_brand, this, _inside).call(this, dir);
|
|
348
|
+
if (path !== void 0 && path !== _classPrivateFieldGet2(_root, this)) await rm(path, {
|
|
349
|
+
recursive: true,
|
|
350
|
+
force: true
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
for (const entry of entries) {
|
|
354
|
+
if (!isFileEntry(entry)) continue;
|
|
355
|
+
const path = _assertClassBrand(_FileMirror_brand, this, _inside).call(this, entry.path);
|
|
356
|
+
if (path === void 0) continue;
|
|
357
|
+
await mkdir(dirname(path), { recursive: true });
|
|
358
|
+
const handle = entry.offset === 0 ? await open(path, "w") : await open(path, "r+").catch(() => open(path, "w"));
|
|
359
|
+
try {
|
|
360
|
+
if (entry.offset > 0) await handle.truncate(entry.offset);
|
|
361
|
+
await handle.write(Buffer.from(entry.data, "base64"), 0, void 0, entry.offset);
|
|
362
|
+
} finally {
|
|
363
|
+
await handle.close();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const file of await _assertClassBrand(_FileMirror_brand, this, _files).call(this)) {
|
|
367
|
+
const info = await stat(file);
|
|
368
|
+
_classPrivateFieldGet2(_seen$1, this).set(file, {
|
|
369
|
+
size: info.size,
|
|
370
|
+
mtimeMs: info.mtimeMs
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* The changes since the last call (or the restore), as entries to persist.
|
|
376
|
+
*
|
|
377
|
+
* @returns File entries, in the order they must be applied.
|
|
378
|
+
*/
|
|
379
|
+
async collect() {
|
|
380
|
+
const entries = [];
|
|
381
|
+
for (const file of await _assertClassBrand(_FileMirror_brand, this, _files).call(this)) {
|
|
382
|
+
const info = await stat(file).catch(() => void 0);
|
|
383
|
+
if (!info) continue;
|
|
384
|
+
const seen = _classPrivateFieldGet2(_seen$1, this).get(file);
|
|
385
|
+
if (seen && seen.size === info.size && seen.mtimeMs === info.mtimeMs) continue;
|
|
386
|
+
const from = seen && info.size > seen.size ? seen.size : 0;
|
|
387
|
+
const bytes = await _assertClassBrand(_FileMirror_brand, this, _read).call(this, file, from, info.size);
|
|
388
|
+
const pieces = [];
|
|
389
|
+
for (let at = 0; at < bytes.length; at += PIECE_BYTES) pieces.push(bytes.subarray(at, at + PIECE_BYTES));
|
|
390
|
+
if (pieces.length === 0) pieces.push(bytes);
|
|
391
|
+
pieces.forEach((piece, index) => {
|
|
392
|
+
entries.push(_assertClassBrand(_FileMirror_brand, this, _entry).call(this, file, from + index * PIECE_BYTES, piece));
|
|
393
|
+
});
|
|
394
|
+
_classPrivateFieldGet2(_seen$1, this).set(file, {
|
|
395
|
+
size: from + bytes.length,
|
|
396
|
+
mtimeMs: info.mtimeMs
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
return entries;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function _entry(file, offset, data) {
|
|
403
|
+
return {
|
|
404
|
+
kind: "file",
|
|
405
|
+
path: relative(_classPrivateFieldGet2(_root, this), file).split(sep).join("/"),
|
|
406
|
+
offset,
|
|
407
|
+
data: data.toString("base64")
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async function _read(file, from, to) {
|
|
411
|
+
const handle = await open(file, "r");
|
|
412
|
+
try {
|
|
413
|
+
const buffer = Buffer.alloc(Math.max(0, to - from));
|
|
414
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, from);
|
|
415
|
+
return buffer.subarray(0, bytesRead);
|
|
416
|
+
} finally {
|
|
417
|
+
await handle.close();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async function _files() {
|
|
421
|
+
const files = [];
|
|
422
|
+
const walk = async (dir) => {
|
|
423
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
424
|
+
for (const entry of entries) {
|
|
425
|
+
const path = join(dir, entry.name);
|
|
426
|
+
if (entry.isDirectory()) await walk(path);
|
|
427
|
+
else if (entry.isFile()) files.push(path);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
for (const dir of _classPrivateFieldGet2(_dirs, this)) {
|
|
431
|
+
const path = _assertClassBrand(_FileMirror_brand, this, _inside).call(this, dir);
|
|
432
|
+
if (path !== void 0) await walk(path);
|
|
433
|
+
}
|
|
434
|
+
return files.sort();
|
|
435
|
+
}
|
|
436
|
+
/** `path` under the root, or undefined if it would escape it. */
|
|
437
|
+
function _inside(path) {
|
|
438
|
+
const full = resolve(_classPrivateFieldGet2(_root, this), path);
|
|
439
|
+
return full === _classPrivateFieldGet2(_root, this) || full.startsWith(_classPrivateFieldGet2(_root, this) + sep) ? full : void 0;
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/harness/container/runtime/cli-adapter.ts
|
|
443
|
+
/**
|
|
444
|
+
* An adapter for any agent CLI that runs one turn per process and prints
|
|
445
|
+
* JSON lines: Claude Code (`claude -p --output-format stream-json`), Codex
|
|
446
|
+
* (`codex exec --json`), or your own.
|
|
447
|
+
*
|
|
448
|
+
* A CLI is described by three things:
|
|
449
|
+
*
|
|
450
|
+
* - `command`: the argv (and stdin) for one turn, given the prompt, the
|
|
451
|
+
* session's settings, and the state earlier turns reported, such as the
|
|
452
|
+
* CLI's own session id to resume.
|
|
453
|
+
* - `parser`: turns each stdout line into `ContainerEvent`s, state updates,
|
|
454
|
+
* and the turn's outcome.
|
|
455
|
+
* - `stateDirs`: where the CLI keeps its sessions, relative to `$HOME`.
|
|
456
|
+
*
|
|
457
|
+
* Each harness session gets its own `$HOME`, so sessions sharing a
|
|
458
|
+
* container never see each other's CLI state. The working directory is
|
|
459
|
+
* shared. Everything the CLI writes under `stateDirs`, and every state
|
|
460
|
+
* update, is persisted to the Durable Object while the turn runs; in a new
|
|
461
|
+
* container both are restored before the first turn, so the CLI resumes
|
|
462
|
+
* its own session. Forks need nothing special: a fork's home starts as a
|
|
463
|
+
* copy of its parent's.
|
|
464
|
+
*/
|
|
465
|
+
/** How long a CLI has to exit after an interrupt before it is killed. */
|
|
466
|
+
const KILL_AFTER_MS = 5e3;
|
|
467
|
+
/** The session state folded from persisted `{ kind: "state" }` entries. */
|
|
468
|
+
function foldState(entries) {
|
|
469
|
+
const state = {};
|
|
470
|
+
for (const entry of entries) {
|
|
471
|
+
if (!isJsonObject(entry) || entry.kind !== "state") continue;
|
|
472
|
+
const values = entry.values;
|
|
473
|
+
if (!isJsonObject(values)) continue;
|
|
474
|
+
for (const [key, value] of Object.entries(values)) if (typeof value === "string") state[key] = value;
|
|
475
|
+
}
|
|
476
|
+
return state;
|
|
477
|
+
}
|
|
478
|
+
/** Give a directory tree to a user. */
|
|
479
|
+
async function chownTree(path, user) {
|
|
480
|
+
await chown(path, user.uid, user.gid);
|
|
481
|
+
const entries = await readdir(path, { withFileTypes: true }).catch(() => []);
|
|
482
|
+
for (const entry of entries) {
|
|
483
|
+
const child = join(path, entry.name);
|
|
484
|
+
if (entry.isDirectory()) await chownTree(child, user);
|
|
485
|
+
else await chown(child, user.uid, user.gid).catch(() => void 0);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Copy a skeleton home into a session home, keeping what is already there
|
|
490
|
+
* and leaving out the session homes themselves when they live inside it.
|
|
491
|
+
* (`fs.cp` refuses to copy a directory into its own subdirectory, filter
|
|
492
|
+
* or not, so this walks the tree itself.)
|
|
493
|
+
*/
|
|
494
|
+
async function seed(skeleton, home, homes) {
|
|
495
|
+
const excluded = resolve(homes);
|
|
496
|
+
const walk = async (from, to) => {
|
|
497
|
+
const entries = await readdir(from, { withFileTypes: true }).catch(() => []);
|
|
498
|
+
for (const entry of entries) {
|
|
499
|
+
const source = join(from, entry.name);
|
|
500
|
+
const path = resolve(source);
|
|
501
|
+
if (path === excluded || path.startsWith(excluded + sep)) continue;
|
|
502
|
+
const target = join(to, entry.name);
|
|
503
|
+
if (entry.isDirectory()) {
|
|
504
|
+
await mkdir(target, { recursive: true });
|
|
505
|
+
await walk(source, target);
|
|
506
|
+
} else if (entry.isSymbolicLink()) await symlink(await readlink(source), target).catch(() => void 0);
|
|
507
|
+
else if (entry.isFile()) await copyFile(source, target, constants.COPYFILE_EXCL).catch(() => void 0);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
await walk(resolve(skeleton), home);
|
|
511
|
+
}
|
|
512
|
+
function homeFor(root, session) {
|
|
513
|
+
return join(root, createHash("sha256").update(session).digest("hex").slice(0, 32));
|
|
514
|
+
}
|
|
515
|
+
var _options = /* @__PURE__ */ new WeakMap();
|
|
516
|
+
var _context = /* @__PURE__ */ new WeakMap();
|
|
517
|
+
var _home = /* @__PURE__ */ new WeakMap();
|
|
518
|
+
var _mirror = /* @__PURE__ */ new WeakMap();
|
|
519
|
+
var _state = /* @__PURE__ */ new WeakMap();
|
|
520
|
+
var _settings = /* @__PURE__ */ new WeakMap();
|
|
521
|
+
var _syncing = /* @__PURE__ */ new WeakMap();
|
|
522
|
+
var _CliSession_brand = /* @__PURE__ */ new WeakSet();
|
|
523
|
+
var CliSession = class {
|
|
524
|
+
constructor(options, context, home, mirror) {
|
|
525
|
+
_classPrivateMethodInitSpec(this, _CliSession_brand);
|
|
526
|
+
_classPrivateFieldInitSpec(this, _options, void 0);
|
|
527
|
+
_classPrivateFieldInitSpec(this, _context, void 0);
|
|
528
|
+
_classPrivateFieldInitSpec(this, _home, void 0);
|
|
529
|
+
_classPrivateFieldInitSpec(this, _mirror, void 0);
|
|
530
|
+
_classPrivateFieldInitSpec(this, _state, void 0);
|
|
531
|
+
_classPrivateFieldInitSpec(this, _settings, void 0);
|
|
532
|
+
_classPrivateFieldInitSpec(this, _syncing, Promise.resolve());
|
|
533
|
+
_classPrivateFieldSet2(_options, this, options);
|
|
534
|
+
_classPrivateFieldSet2(_context, this, context);
|
|
535
|
+
_classPrivateFieldSet2(_home, this, home);
|
|
536
|
+
_classPrivateFieldSet2(_mirror, this, mirror);
|
|
537
|
+
_classPrivateFieldSet2(_state, this, foldState(context.restore));
|
|
538
|
+
_classPrivateFieldSet2(_settings, this, context.settings);
|
|
539
|
+
}
|
|
540
|
+
async run(turn) {
|
|
541
|
+
const cliTurn = {
|
|
542
|
+
operationId: turn.operationId,
|
|
543
|
+
prompt: inputText(turn.input),
|
|
544
|
+
settings: _classPrivateFieldGet2(_settings, this),
|
|
545
|
+
state: _classPrivateFieldGet2(_state, this)
|
|
546
|
+
};
|
|
547
|
+
const command = _classPrivateFieldGet2(_options, this).command(cliTurn);
|
|
548
|
+
const parser = _classPrivateFieldGet2(_options, this).parser(cliTurn);
|
|
549
|
+
const [file, ...args] = command.argv;
|
|
550
|
+
if (file === void 0) throw new Error("The CLI command is empty");
|
|
551
|
+
const user = _classPrivateFieldGet2(_options, this).user;
|
|
552
|
+
const child = spawn(file, args, {
|
|
553
|
+
cwd: _classPrivateFieldGet2(_options, this).cwd ?? "/workspace",
|
|
554
|
+
env: {
|
|
555
|
+
...process.env,
|
|
556
|
+
...command.env,
|
|
557
|
+
HOME: _classPrivateFieldGet2(_home, this)
|
|
558
|
+
},
|
|
559
|
+
stdio: [
|
|
560
|
+
"pipe",
|
|
561
|
+
"pipe",
|
|
562
|
+
"pipe"
|
|
563
|
+
],
|
|
564
|
+
...user ? {
|
|
565
|
+
uid: user.uid,
|
|
566
|
+
gid: user.gid
|
|
567
|
+
} : {}
|
|
568
|
+
});
|
|
569
|
+
child.stdin.on("error", () => void 0);
|
|
570
|
+
child.stdin.end(command.stdin ?? "");
|
|
571
|
+
let stderr = "";
|
|
572
|
+
child.stderr.setEncoding("utf8");
|
|
573
|
+
child.stderr.on("data", (chunk) => {
|
|
574
|
+
stderr = (stderr + chunk).slice(-4096);
|
|
575
|
+
});
|
|
576
|
+
let outcome;
|
|
577
|
+
createInterface({ input: child.stdout }).on("line", (text) => {
|
|
578
|
+
if (text.trim() === "") return;
|
|
579
|
+
let update;
|
|
580
|
+
try {
|
|
581
|
+
update = parser.line(text);
|
|
582
|
+
} catch (error) {
|
|
583
|
+
_classPrivateFieldGet2(_context, this).log("warn", `unreadable CLI output: ${String(error)}`);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
for (const event of update.events ?? []) _classPrivateFieldGet2(_context, this).emit(event);
|
|
587
|
+
if (update.state) _assertClassBrand(_CliSession_brand, this, _remember).call(this, update.state);
|
|
588
|
+
if (update.outcome) outcome = update.outcome;
|
|
589
|
+
});
|
|
590
|
+
const onAbort = () => {
|
|
591
|
+
child.kill("SIGINT");
|
|
592
|
+
setTimeout(() => child.kill("SIGKILL"), KILL_AFTER_MS).unref();
|
|
593
|
+
};
|
|
594
|
+
turn.signal.addEventListener("abort", onAbort, { once: true });
|
|
595
|
+
const sync = setInterval(() => void _assertClassBrand(_CliSession_brand, this, _sync).call(this), _classPrivateFieldGet2(_options, this).syncIntervalMs ?? 5e3);
|
|
596
|
+
const code = await new Promise((resolve) => {
|
|
597
|
+
child.on("error", (error) => {
|
|
598
|
+
stderr += `\n${error.message}`;
|
|
599
|
+
resolve(null);
|
|
600
|
+
});
|
|
601
|
+
child.on("close", (exitCode) => resolve(exitCode));
|
|
602
|
+
});
|
|
603
|
+
clearInterval(sync);
|
|
604
|
+
turn.signal.removeEventListener("abort", onAbort);
|
|
605
|
+
await _assertClassBrand(_CliSession_brand, this, _sync).call(this);
|
|
606
|
+
if (turn.signal.aborted) return {
|
|
607
|
+
status: "unanswered",
|
|
608
|
+
reason: "aborted"
|
|
609
|
+
};
|
|
610
|
+
return outcome ?? parser.end({
|
|
611
|
+
code,
|
|
612
|
+
stderr
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
configure(settings) {
|
|
616
|
+
_classPrivateFieldSet2(_settings, this, settings);
|
|
617
|
+
}
|
|
618
|
+
async close() {
|
|
619
|
+
await _classPrivateFieldGet2(_syncing, this);
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
function _remember(update) {
|
|
623
|
+
if (!Object.entries(update).some(([key, value]) => _classPrivateFieldGet2(_state, this)[key] !== value)) return;
|
|
624
|
+
_classPrivateFieldSet2(_state, this, {
|
|
625
|
+
..._classPrivateFieldGet2(_state, this),
|
|
626
|
+
...update
|
|
627
|
+
});
|
|
628
|
+
_classPrivateFieldGet2(_context, this).persist([{
|
|
629
|
+
kind: "state",
|
|
630
|
+
values: { ...update }
|
|
631
|
+
}]);
|
|
632
|
+
}
|
|
633
|
+
function _sync() {
|
|
634
|
+
_classPrivateFieldSet2(_syncing, this, _classPrivateFieldGet2(_syncing, this).then(async () => {
|
|
635
|
+
try {
|
|
636
|
+
const entries = await _classPrivateFieldGet2(_mirror, this).collect();
|
|
637
|
+
if (entries.length > 0) _classPrivateFieldGet2(_context, this).persist(entries);
|
|
638
|
+
} catch (error) {
|
|
639
|
+
_classPrivateFieldGet2(_context, this).log("warn", `state sync failed: ${String(error)}`);
|
|
640
|
+
}
|
|
641
|
+
}));
|
|
642
|
+
return _classPrivateFieldGet2(_syncing, this);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* An adapter for an agent CLI.
|
|
646
|
+
*
|
|
647
|
+
* @param options - The CLI's command, parser, and state directories.
|
|
648
|
+
* @returns The adapter.
|
|
649
|
+
*
|
|
650
|
+
* @experimental The API may change before it stabilizes.
|
|
651
|
+
*/
|
|
652
|
+
function cliAdapter(options) {
|
|
653
|
+
const homes = options.homes ?? join(homedir(), ".harness", "sessions");
|
|
654
|
+
return {
|
|
655
|
+
id: options.id,
|
|
656
|
+
version: options.version ?? "1",
|
|
657
|
+
capabilities: [
|
|
658
|
+
"resume",
|
|
659
|
+
"fork",
|
|
660
|
+
"model"
|
|
661
|
+
],
|
|
662
|
+
async open(context) {
|
|
663
|
+
const home = homeFor(homes, context.session);
|
|
664
|
+
await mkdir(home, { recursive: true });
|
|
665
|
+
if (options.skeleton) await seed(options.skeleton, home, homes);
|
|
666
|
+
const mirror = new FileMirror(home, options.stateDirs ?? []);
|
|
667
|
+
await mirror.restore(context.restore);
|
|
668
|
+
if (options.user) await chownTree(home, options.user);
|
|
669
|
+
return new CliSession(options, context, home, mirror);
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/harness/container/runtime/presets/claude-code-events.ts
|
|
675
|
+
function json(value) {
|
|
676
|
+
if (value === void 0) return null;
|
|
677
|
+
try {
|
|
678
|
+
return JSON.parse(JSON.stringify(value));
|
|
679
|
+
} catch {
|
|
680
|
+
return String(value);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function toolOutput(content) {
|
|
684
|
+
if (typeof content === "string") return content;
|
|
685
|
+
if (Array.isArray(content)) {
|
|
686
|
+
const texts = content.map((block) => typeof block === "object" && block !== null && "type" in block && block.type === "text" && "text" in block && typeof block.text === "string" ? block.text : void 0).filter((text) => text !== void 0);
|
|
687
|
+
if (texts.length === content.length) return texts.join("\n");
|
|
688
|
+
}
|
|
689
|
+
return json(content);
|
|
690
|
+
}
|
|
691
|
+
var _drafts = /* @__PURE__ */ new WeakMap();
|
|
692
|
+
var _toolOwners = /* @__PURE__ */ new WeakMap();
|
|
693
|
+
var _seen = /* @__PURE__ */ new WeakMap();
|
|
694
|
+
var _streaming = /* @__PURE__ */ new WeakMap();
|
|
695
|
+
var _now = /* @__PURE__ */ new WeakMap();
|
|
696
|
+
var _ClaudeProjection_brand = /* @__PURE__ */ new WeakSet();
|
|
697
|
+
/** The projection state of one adapter session. */
|
|
698
|
+
var ClaudeProjection = class {
|
|
699
|
+
/**
|
|
700
|
+
* @param now - The clock, for message timestamps.
|
|
701
|
+
*/
|
|
702
|
+
constructor(now = Date.now) {
|
|
703
|
+
_classPrivateMethodInitSpec(this, _ClaudeProjection_brand);
|
|
704
|
+
_classPrivateFieldInitSpec(this, _drafts, /* @__PURE__ */ new Map());
|
|
705
|
+
_classPrivateFieldInitSpec(this, _toolOwners, /* @__PURE__ */ new Map());
|
|
706
|
+
_classPrivateFieldInitSpec(this, _seen, /* @__PURE__ */ new Set());
|
|
707
|
+
_classPrivateFieldInitSpec(this, _streaming, void 0);
|
|
708
|
+
_classPrivateFieldInitSpec(this, _now, void 0);
|
|
709
|
+
_classPrivateFieldSet2(_now, this, now);
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* The events one SDK message produces.
|
|
713
|
+
*
|
|
714
|
+
* @param message - The SDK message.
|
|
715
|
+
* @param operationId - The operation it belongs to.
|
|
716
|
+
* @returns The events, in order.
|
|
717
|
+
*/
|
|
718
|
+
project(message, operationId) {
|
|
719
|
+
if (message.parent_tool_use_id) return [];
|
|
720
|
+
switch (message.type) {
|
|
721
|
+
case "stream_event": return _assertClassBrand(_ClaudeProjection_brand, this, _stream).call(this, message);
|
|
722
|
+
case "assistant": return _assertClassBrand(_ClaudeProjection_brand, this, _assistant).call(this, message, operationId);
|
|
723
|
+
case "user": return _assertClassBrand(_ClaudeProjection_brand, this, _toolResults).call(this, message);
|
|
724
|
+
case "result": return _assertClassBrand(_ClaudeProjection_brand, this, _usage).call(this, message);
|
|
725
|
+
default: return [];
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* How a turn ended, from its `result` message.
|
|
730
|
+
*
|
|
731
|
+
* @param message - The result message.
|
|
732
|
+
* @param aborted - Whether the turn was interrupted.
|
|
733
|
+
* @returns The outcome.
|
|
734
|
+
*/
|
|
735
|
+
outcome(message, aborted) {
|
|
736
|
+
if (aborted) return {
|
|
737
|
+
status: "unanswered",
|
|
738
|
+
reason: "aborted"
|
|
739
|
+
};
|
|
740
|
+
if (message.subtype === "success" && message.is_error !== true) return {
|
|
741
|
+
status: "done",
|
|
742
|
+
text: message.result ?? ""
|
|
743
|
+
};
|
|
744
|
+
const detail = message.errors?.join("; ") || message.result;
|
|
745
|
+
return {
|
|
746
|
+
status: "unanswered",
|
|
747
|
+
reason: [message.subtype && message.subtype !== "success" ? message.subtype : "", detail].filter((part) => part).join(": ") || "error"
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
function _stream(message) {
|
|
752
|
+
const event = message.event;
|
|
753
|
+
if (!event) return [];
|
|
754
|
+
if (event.type === "message_start") {
|
|
755
|
+
_classPrivateFieldSet2(_streaming, this, event.message?.id);
|
|
756
|
+
return [];
|
|
757
|
+
}
|
|
758
|
+
const messageId = _classPrivateFieldGet2(_streaming, this);
|
|
759
|
+
if (event.type !== "content_block_delta" || !messageId || !event.delta) return [];
|
|
760
|
+
if (event.delta.type === "text_delta" && event.delta.text) return [{
|
|
761
|
+
type: "text-delta",
|
|
762
|
+
messageId,
|
|
763
|
+
delta: event.delta.text
|
|
764
|
+
}];
|
|
765
|
+
if (event.delta.type === "thinking_delta" && event.delta.thinking) return [{
|
|
766
|
+
type: "reasoning-delta",
|
|
767
|
+
messageId,
|
|
768
|
+
delta: event.delta.thinking
|
|
769
|
+
}];
|
|
770
|
+
return [];
|
|
771
|
+
}
|
|
772
|
+
function _assistant(message, operationId) {
|
|
773
|
+
const id = message.message?.id;
|
|
774
|
+
const content = message.message?.content;
|
|
775
|
+
if (!id || !Array.isArray(content)) return [];
|
|
776
|
+
if (message.uuid !== void 0) {
|
|
777
|
+
if (_classPrivateFieldGet2(_seen, this).has(message.uuid)) return [];
|
|
778
|
+
_classPrivateFieldGet2(_seen, this).add(message.uuid);
|
|
779
|
+
}
|
|
780
|
+
let draft = _classPrivateFieldGet2(_drafts, this).get(id);
|
|
781
|
+
if (!draft) {
|
|
782
|
+
draft = {
|
|
783
|
+
id,
|
|
784
|
+
operationId,
|
|
785
|
+
createdAt: _classPrivateFieldGet2(_now, this).call(this),
|
|
786
|
+
parts: []
|
|
787
|
+
};
|
|
788
|
+
_classPrivateFieldGet2(_drafts, this).set(id, draft);
|
|
789
|
+
}
|
|
790
|
+
for (const block of content) if (block.type === "text" && block.text) draft.parts.push({
|
|
791
|
+
type: "text",
|
|
792
|
+
text: block.text
|
|
793
|
+
});
|
|
794
|
+
else if (block.type === "thinking" && block.thinking) draft.parts.push({
|
|
795
|
+
type: "reasoning",
|
|
796
|
+
text: block.thinking
|
|
797
|
+
});
|
|
798
|
+
else if (block.type === "tool_use" && block.id) {
|
|
799
|
+
draft.parts.push({
|
|
800
|
+
type: "tool",
|
|
801
|
+
toolCallId: block.id,
|
|
802
|
+
toolName: block.name ?? "tool",
|
|
803
|
+
input: json(block.input),
|
|
804
|
+
state: "running"
|
|
805
|
+
});
|
|
806
|
+
_classPrivateFieldGet2(_toolOwners, this).set(block.id, id);
|
|
807
|
+
}
|
|
808
|
+
return [{
|
|
809
|
+
type: "message",
|
|
810
|
+
message: _assertClassBrand(_ClaudeProjection_brand, this, _message).call(this, draft)
|
|
811
|
+
}];
|
|
812
|
+
}
|
|
813
|
+
function _toolResults(message) {
|
|
814
|
+
const content = message.message?.content;
|
|
815
|
+
if (!Array.isArray(content)) return [];
|
|
816
|
+
const changed = /* @__PURE__ */ new Set();
|
|
817
|
+
for (const block of content) {
|
|
818
|
+
if (block.type !== "tool_result" || !block.tool_use_id) continue;
|
|
819
|
+
const owner = _classPrivateFieldGet2(_toolOwners, this).get(block.tool_use_id);
|
|
820
|
+
const draft = owner === void 0 ? void 0 : _classPrivateFieldGet2(_drafts, this).get(owner);
|
|
821
|
+
if (!draft) continue;
|
|
822
|
+
draft.parts = draft.parts.map((part) => part.type === "tool" && part.toolCallId === block.tool_use_id ? {
|
|
823
|
+
...part,
|
|
824
|
+
state: block.is_error ? "error" : "done",
|
|
825
|
+
output: toolOutput(block.content)
|
|
826
|
+
} : part);
|
|
827
|
+
changed.add(draft);
|
|
828
|
+
}
|
|
829
|
+
return [...changed].map((draft) => ({
|
|
830
|
+
type: "message",
|
|
831
|
+
message: _assertClassBrand(_ClaudeProjection_brand, this, _message).call(this, draft)
|
|
832
|
+
}));
|
|
833
|
+
}
|
|
834
|
+
function _usage(message) {
|
|
835
|
+
return [{
|
|
836
|
+
type: "usage",
|
|
837
|
+
...message.usage?.input_tokens === void 0 ? {} : { inputTokens: message.usage.input_tokens },
|
|
838
|
+
...message.usage?.output_tokens === void 0 ? {} : { outputTokens: message.usage.output_tokens },
|
|
839
|
+
...message.total_cost_usd === void 0 ? {} : { costUsd: message.total_cost_usd }
|
|
840
|
+
}];
|
|
841
|
+
}
|
|
842
|
+
function _message(draft) {
|
|
843
|
+
return {
|
|
844
|
+
id: draft.id,
|
|
845
|
+
role: "assistant",
|
|
846
|
+
parts: [...draft.parts],
|
|
847
|
+
...draft.operationId === void 0 ? {} : { operationId: draft.operationId },
|
|
848
|
+
createdAt: draft.createdAt
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
//#endregion
|
|
852
|
+
//#region src/harness/container/runtime/presets/claude-code.ts
|
|
853
|
+
function parseOptions$1(value) {
|
|
854
|
+
if (!isJsonObject(value)) return {};
|
|
855
|
+
const allowed = stringsOf(value.allowedTools);
|
|
856
|
+
const disallowed = stringsOf(value.disallowedTools);
|
|
857
|
+
return {
|
|
858
|
+
...typeof value.appendSystemPrompt === "string" ? { appendSystemPrompt: value.appendSystemPrompt } : {},
|
|
859
|
+
...allowed ? { allowedTools: allowed } : {},
|
|
860
|
+
...disallowed ? { disallowedTools: disallowed } : {},
|
|
861
|
+
...typeof value.maxTurns === "number" ? { maxTurns: value.maxTurns } : {}
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function parseLine$1(text) {
|
|
865
|
+
const value = JSON.parse(text);
|
|
866
|
+
if (typeof value !== "object" || value === null || !("type" in value)) return;
|
|
867
|
+
return value;
|
|
868
|
+
}
|
|
869
|
+
/** The Claude Code preset's description, for `cliAdapter`. */
|
|
870
|
+
const claudeCodeCli = {
|
|
871
|
+
id: "claude-code",
|
|
872
|
+
stateDirs: [".claude/projects"],
|
|
873
|
+
command(turn) {
|
|
874
|
+
const options = parseOptions$1(turn.settings.options);
|
|
875
|
+
const argv = [
|
|
876
|
+
"claude",
|
|
877
|
+
"-p",
|
|
878
|
+
"--output-format",
|
|
879
|
+
"stream-json",
|
|
880
|
+
"--verbose",
|
|
881
|
+
"--include-partial-messages",
|
|
882
|
+
"--dangerously-skip-permissions"
|
|
883
|
+
];
|
|
884
|
+
if (turn.settings.model) argv.push("--model", turn.settings.model);
|
|
885
|
+
if (turn.state.sessionId) argv.push("--resume", turn.state.sessionId);
|
|
886
|
+
if (options.appendSystemPrompt) argv.push("--append-system-prompt", options.appendSystemPrompt);
|
|
887
|
+
if (options.allowedTools?.length) argv.push("--allowedTools", options.allowedTools.join(","));
|
|
888
|
+
if (options.disallowedTools?.length) argv.push("--disallowedTools", options.disallowedTools.join(","));
|
|
889
|
+
if (options.maxTurns !== void 0) argv.push("--max-turns", String(options.maxTurns));
|
|
890
|
+
return {
|
|
891
|
+
argv,
|
|
892
|
+
stdin: turn.prompt,
|
|
893
|
+
env: { DISABLE_AUTOUPDATER: "1" }
|
|
894
|
+
};
|
|
895
|
+
},
|
|
896
|
+
parser(turn) {
|
|
897
|
+
const projection = new ClaudeProjection();
|
|
898
|
+
return {
|
|
899
|
+
line(text) {
|
|
900
|
+
const message = parseLine$1(text);
|
|
901
|
+
if (!message) return {};
|
|
902
|
+
return {
|
|
903
|
+
events: projection.project(message, turn.operationId),
|
|
904
|
+
...message.session_id ? { state: { sessionId: message.session_id } } : {},
|
|
905
|
+
...message.type === "result" ? { outcome: projection.outcome(message, false) } : {}
|
|
906
|
+
};
|
|
907
|
+
},
|
|
908
|
+
end({ code, stderr }) {
|
|
909
|
+
return {
|
|
910
|
+
status: "unanswered",
|
|
911
|
+
reason: `claude exited with ${code ?? "a signal"}: ${stderr.trim() || "no output"}`
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
//#endregion
|
|
918
|
+
//#region src/harness/container/runtime/presets/codex.ts
|
|
919
|
+
function parseLine(text) {
|
|
920
|
+
const value = JSON.parse(text);
|
|
921
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
922
|
+
return value;
|
|
923
|
+
}
|
|
924
|
+
function toolState(status) {
|
|
925
|
+
if (status === "completed") return "done";
|
|
926
|
+
if (status === "failed" || status === "declined") return "error";
|
|
927
|
+
return "running";
|
|
928
|
+
}
|
|
929
|
+
/** One item as a transcript part, or undefined for items that are not. */
|
|
930
|
+
function partOf(item) {
|
|
931
|
+
const id = item.id ?? "item";
|
|
932
|
+
const state = toolState(item.status);
|
|
933
|
+
switch (item.type) {
|
|
934
|
+
case "agent_message": return {
|
|
935
|
+
type: "text",
|
|
936
|
+
text: item.text ?? ""
|
|
937
|
+
};
|
|
938
|
+
case "reasoning": return {
|
|
939
|
+
type: "reasoning",
|
|
940
|
+
text: item.text ?? ""
|
|
941
|
+
};
|
|
942
|
+
case "command_execution": return {
|
|
943
|
+
type: "tool",
|
|
944
|
+
toolCallId: id,
|
|
945
|
+
toolName: "shell",
|
|
946
|
+
input: { command: item.command ?? "" },
|
|
947
|
+
state: state === "done" && item.exit_code !== 0 && item.exit_code != null ? "error" : state,
|
|
948
|
+
...item.aggregated_output ? { output: item.aggregated_output } : {}
|
|
949
|
+
};
|
|
950
|
+
case "file_change": return {
|
|
951
|
+
type: "tool",
|
|
952
|
+
toolCallId: id,
|
|
953
|
+
toolName: "file_change",
|
|
954
|
+
input: item.changes ?? null,
|
|
955
|
+
state
|
|
956
|
+
};
|
|
957
|
+
case "mcp_tool_call": return {
|
|
958
|
+
type: "tool",
|
|
959
|
+
toolCallId: id,
|
|
960
|
+
toolName: `${item.server ?? "mcp"}.${item.tool ?? "tool"}`,
|
|
961
|
+
input: item.arguments ?? null,
|
|
962
|
+
state,
|
|
963
|
+
...item.result !== void 0 || item.error !== void 0 ? { output: item.result ?? item.error ?? null } : {}
|
|
964
|
+
};
|
|
965
|
+
case "web_search": return {
|
|
966
|
+
type: "tool",
|
|
967
|
+
toolCallId: id,
|
|
968
|
+
toolName: "web_search",
|
|
969
|
+
input: { query: item.query ?? "" },
|
|
970
|
+
state
|
|
971
|
+
};
|
|
972
|
+
case "todo_list": return {
|
|
973
|
+
type: "tool",
|
|
974
|
+
toolCallId: id,
|
|
975
|
+
toolName: "todo_list",
|
|
976
|
+
input: item.items ?? null,
|
|
977
|
+
state
|
|
978
|
+
};
|
|
979
|
+
default: return;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
function parseOptions(value) {
|
|
983
|
+
if (!isJsonObject(value)) return {};
|
|
984
|
+
const config = stringsOf(value.config);
|
|
985
|
+
return config ? { config } : {};
|
|
986
|
+
}
|
|
987
|
+
/** The Codex preset's description, for `cliAdapter`. */
|
|
988
|
+
const codexCli = {
|
|
989
|
+
id: "codex",
|
|
990
|
+
stateDirs: [".codex/sessions"],
|
|
991
|
+
command(turn) {
|
|
992
|
+
const argv = ["codex", "exec"];
|
|
993
|
+
if (turn.state.threadId) argv.push("resume", turn.state.threadId);
|
|
994
|
+
argv.push("--json", "--skip-git-repo-check", "--dangerously-bypass-approvals-and-sandbox");
|
|
995
|
+
const baseUrl = process.env.OPENAI_BASE_URL;
|
|
996
|
+
if (baseUrl) argv.push("-c", "model_provider=gateway", "-c", `model_providers.gateway={name="AI Gateway",base_url=${JSON.stringify(baseUrl)},env_key="OPENAI_API_KEY",wire_api="responses"}`);
|
|
997
|
+
for (const config of parseOptions(turn.settings.options).config ?? []) argv.push("-c", config);
|
|
998
|
+
if (turn.settings.model) argv.push("-m", turn.settings.model);
|
|
999
|
+
argv.push("-");
|
|
1000
|
+
return {
|
|
1001
|
+
argv,
|
|
1002
|
+
stdin: turn.prompt
|
|
1003
|
+
};
|
|
1004
|
+
},
|
|
1005
|
+
parser(turn) {
|
|
1006
|
+
const messageId = `${turn.operationId}:assistant`;
|
|
1007
|
+
const createdAt = Date.now();
|
|
1008
|
+
const parts = /* @__PURE__ */ new Map();
|
|
1009
|
+
let lastText = "";
|
|
1010
|
+
let failure;
|
|
1011
|
+
const message = () => {
|
|
1012
|
+
return {
|
|
1013
|
+
type: "message",
|
|
1014
|
+
message: {
|
|
1015
|
+
id: messageId,
|
|
1016
|
+
role: "assistant",
|
|
1017
|
+
parts: [...parts.values()],
|
|
1018
|
+
operationId: turn.operationId,
|
|
1019
|
+
createdAt
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
};
|
|
1023
|
+
return {
|
|
1024
|
+
line(text) {
|
|
1025
|
+
const event = parseLine(text);
|
|
1026
|
+
if (!event) return {};
|
|
1027
|
+
switch (event.type) {
|
|
1028
|
+
case "thread.started": return event.thread_id ? { state: { threadId: event.thread_id } } : {};
|
|
1029
|
+
case "item.started":
|
|
1030
|
+
case "item.updated":
|
|
1031
|
+
case "item.completed": {
|
|
1032
|
+
const item = event.item;
|
|
1033
|
+
if (!item) return {};
|
|
1034
|
+
if (item.type === "error") return { events: [{
|
|
1035
|
+
type: "log",
|
|
1036
|
+
level: "warn",
|
|
1037
|
+
message: item.message ?? "error"
|
|
1038
|
+
}] };
|
|
1039
|
+
const part = partOf(item);
|
|
1040
|
+
if (!part) return {};
|
|
1041
|
+
parts.set(item.id ?? String(parts.size), part);
|
|
1042
|
+
if (part.type === "text" && event.type === "item.completed") lastText = part.text;
|
|
1043
|
+
return { events: [message()] };
|
|
1044
|
+
}
|
|
1045
|
+
case "turn.completed": return {
|
|
1046
|
+
events: [{
|
|
1047
|
+
type: "usage",
|
|
1048
|
+
...event.usage?.input_tokens === void 0 ? {} : { inputTokens: event.usage.input_tokens },
|
|
1049
|
+
...event.usage?.output_tokens === void 0 ? {} : { outputTokens: event.usage.output_tokens }
|
|
1050
|
+
}],
|
|
1051
|
+
outcome: {
|
|
1052
|
+
status: "done",
|
|
1053
|
+
text: lastText
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
case "turn.failed": return { outcome: {
|
|
1057
|
+
status: "unanswered",
|
|
1058
|
+
reason: event.error?.message ?? "turn failed"
|
|
1059
|
+
} };
|
|
1060
|
+
case "error":
|
|
1061
|
+
failure = event.message;
|
|
1062
|
+
return { events: [{
|
|
1063
|
+
type: "log",
|
|
1064
|
+
level: "warn",
|
|
1065
|
+
message: event.message ?? "error"
|
|
1066
|
+
}] };
|
|
1067
|
+
default: return {};
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
end({ code, stderr }) {
|
|
1071
|
+
return {
|
|
1072
|
+
status: "unanswered",
|
|
1073
|
+
reason: `codex exited with ${code ?? "a signal"}: ${failure ?? (stderr.trim() || "no output")}`
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
//#endregion
|
|
1080
|
+
export { claudeCodeCli, cliAdapter, codexCli, serve, serveFromEnv };
|
|
1081
|
+
|
|
1082
|
+
//# sourceMappingURL=index.js.map
|