@ai-sdk/harness-claude-code 0.0.0 → 1.0.0-canary.1
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 +21 -0
- package/LICENSE +13 -0
- package/README.md +62 -0
- package/dist/bridge/index.mjs +1013 -0
- package/dist/bridge/index.mjs.map +1 -0
- package/dist/bridge/package.json +13 -0
- package/dist/bridge/pnpm-lock.yaml +1035 -0
- package/dist/index.d.ts +376 -0
- package/dist/index.js +1274 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -1
- package/src/bridge/compaction-latch.ts +62 -0
- package/src/bridge/index.ts +878 -0
- package/src/bridge/package.json +13 -0
- package/src/bridge/pnpm-lock.yaml +1035 -0
- package/src/claude-code-auth.ts +131 -0
- package/src/claude-code-bridge-protocol.ts +37 -0
- package/src/claude-code-harness.ts +1536 -0
- package/src/index.ts +12 -0
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
// ../harness/dist/bridge/index.js
|
|
2
|
+
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { env as procEnv, pid, stdout } from "process";
|
|
5
|
+
import { WebSocketServer } from "ws";
|
|
6
|
+
var DEBUG_LEVEL_WEIGHT = {
|
|
7
|
+
error: 0,
|
|
8
|
+
warn: 1,
|
|
9
|
+
info: 2,
|
|
10
|
+
debug: 3,
|
|
11
|
+
trace: 4
|
|
12
|
+
};
|
|
13
|
+
function subsystemMatches(filters, subsystem) {
|
|
14
|
+
if (!filters || filters.length === 0) return true;
|
|
15
|
+
return filters.some(
|
|
16
|
+
(filter) => subsystem === filter || subsystem.startsWith(`${filter}.`)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function formatBridgeError(err) {
|
|
20
|
+
if (err instanceof Error) {
|
|
21
|
+
return { name: err.name, message: err.message, stack: err.stack };
|
|
22
|
+
}
|
|
23
|
+
return { message: String(err) };
|
|
24
|
+
}
|
|
25
|
+
function parseEnvList(value) {
|
|
26
|
+
if (!value) return void 0;
|
|
27
|
+
const items = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
28
|
+
return items.length > 0 ? items : void 0;
|
|
29
|
+
}
|
|
30
|
+
var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
31
|
+
var WS_OPEN = 1;
|
|
32
|
+
async function runBridge(options) {
|
|
33
|
+
const { bridgeType, bridgeStateDir: bridgeStateDir2, onStart, onDetach } = options;
|
|
34
|
+
const expectedToken = options.token ?? procEnv.BRIDGE_CHANNEL_TOKEN ?? "";
|
|
35
|
+
const bridgeWsPort = options.port ?? parseInt(procEnv.BRIDGE_WS_PORT ?? "0", 10);
|
|
36
|
+
const bridgeMetaPath = `${bridgeStateDir2}/bridge-meta.json`;
|
|
37
|
+
const startConfigPath = `${bridgeStateDir2}/start-config.json`;
|
|
38
|
+
const rerunStartConfigPath = `${bridgeStateDir2}/rerun-start-config.json`;
|
|
39
|
+
const eventLogPath = `${bridgeStateDir2}/event-log.ndjson`;
|
|
40
|
+
try {
|
|
41
|
+
await mkdir(bridgeStateDir2, { recursive: true });
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
let currentBoundPort = 0;
|
|
45
|
+
let currentTurnState = "init";
|
|
46
|
+
let activeSocket;
|
|
47
|
+
let isFirstTurn = true;
|
|
48
|
+
let turnAbort;
|
|
49
|
+
let currentUserMessages;
|
|
50
|
+
let debugConfig;
|
|
51
|
+
let consoleCaptureInstalled = false;
|
|
52
|
+
const envDebugEnabled = ENV_TRUTHY.has(
|
|
53
|
+
(procEnv.HARNESS_DEBUG ?? "").toLowerCase()
|
|
54
|
+
);
|
|
55
|
+
let seqCounter = 0;
|
|
56
|
+
let eventLog = [];
|
|
57
|
+
let diskBuffer = "";
|
|
58
|
+
let flushPromise = null;
|
|
59
|
+
const flushEventsToDisk = async () => {
|
|
60
|
+
while (diskBuffer.length > 0) {
|
|
61
|
+
const buf = diskBuffer;
|
|
62
|
+
diskBuffer = "";
|
|
63
|
+
await appendFile(eventLogPath, buf).catch(() => {
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const scheduleEventFlush = () => {
|
|
68
|
+
if (flushPromise) return;
|
|
69
|
+
flushPromise = new Promise((resolve) => {
|
|
70
|
+
setImmediate(() => {
|
|
71
|
+
void flushEventsToDisk().finally(resolve);
|
|
72
|
+
});
|
|
73
|
+
}).finally(() => {
|
|
74
|
+
flushPromise = null;
|
|
75
|
+
if (diskBuffer.length > 0) {
|
|
76
|
+
scheduleEventFlush();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
const flushPendingEventsToDisk = async () => {
|
|
81
|
+
if (diskBuffer.length > 0 && !flushPromise) {
|
|
82
|
+
scheduleEventFlush();
|
|
83
|
+
}
|
|
84
|
+
let inFlight = flushPromise;
|
|
85
|
+
while (inFlight) {
|
|
86
|
+
await inFlight;
|
|
87
|
+
inFlight = flushPromise;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const replayFromDisk = procEnv.BRIDGE_REPLAY_FROM_DISK === "1";
|
|
91
|
+
if (replayFromDisk && existsSync(eventLogPath)) {
|
|
92
|
+
try {
|
|
93
|
+
const lines = readFileSync(eventLogPath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
94
|
+
eventLog = lines.map((line) => ({
|
|
95
|
+
seq: JSON.parse(line).seq,
|
|
96
|
+
line
|
|
97
|
+
}));
|
|
98
|
+
seqCounter = eventLog.at(-1)?.seq ?? 0;
|
|
99
|
+
} catch {
|
|
100
|
+
eventLog = [];
|
|
101
|
+
seqCounter = 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const pendingToolResults = /* @__PURE__ */ new Map();
|
|
105
|
+
const pendingToolApprovals = /* @__PURE__ */ new Map();
|
|
106
|
+
const writeBridgeMeta = async (state) => {
|
|
107
|
+
try {
|
|
108
|
+
await writeFile(
|
|
109
|
+
bridgeMetaPath,
|
|
110
|
+
JSON.stringify({
|
|
111
|
+
type: bridgeType,
|
|
112
|
+
port: currentBoundPort,
|
|
113
|
+
state,
|
|
114
|
+
pid
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const writeStartConfig = async (start) => {
|
|
121
|
+
try {
|
|
122
|
+
const serialized = JSON.stringify(start);
|
|
123
|
+
await writeFile(startConfigPath, serialized);
|
|
124
|
+
if (!existsSync(rerunStartConfigPath)) {
|
|
125
|
+
await writeFile(rerunStartConfigPath, serialized);
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const sendControl = (msg) => {
|
|
131
|
+
if (activeSocket?.readyState === WS_OPEN) {
|
|
132
|
+
try {
|
|
133
|
+
activeSocket.send(JSON.stringify(msg));
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const emit = (event) => {
|
|
139
|
+
const seq = ++seqCounter;
|
|
140
|
+
const line = JSON.stringify({ ...event, seq });
|
|
141
|
+
eventLog.push({ seq, line });
|
|
142
|
+
diskBuffer += `${line}
|
|
143
|
+
`;
|
|
144
|
+
scheduleEventFlush();
|
|
145
|
+
if (activeSocket?.readyState === WS_OPEN) {
|
|
146
|
+
try {
|
|
147
|
+
activeSocket.send(line);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const replay = (ws, afterSeq) => {
|
|
153
|
+
for (const entry of eventLog) {
|
|
154
|
+
if (entry.seq > afterSeq && ws.readyState === WS_OPEN) {
|
|
155
|
+
ws.send(entry.line);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const shouldEmitDebugEvent = (level, subsystem) => {
|
|
160
|
+
if (!debugConfig?.enabled) return false;
|
|
161
|
+
const threshold = debugConfig.level ?? "debug";
|
|
162
|
+
if (DEBUG_LEVEL_WEIGHT[level] > DEBUG_LEVEL_WEIGHT[threshold]) return false;
|
|
163
|
+
return subsystemMatches(debugConfig.subsystems, subsystem);
|
|
164
|
+
};
|
|
165
|
+
const rawStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
166
|
+
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
167
|
+
const installConsoleCapture = () => {
|
|
168
|
+
if (consoleCaptureInstalled) return;
|
|
169
|
+
consoleCaptureInstalled = true;
|
|
170
|
+
const buffers = {
|
|
171
|
+
stdout: "",
|
|
172
|
+
stderr: ""
|
|
173
|
+
};
|
|
174
|
+
const patch = (stream, raw) => (chunk, encoding, cb) => {
|
|
175
|
+
if (debugConfig?.enabled) {
|
|
176
|
+
try {
|
|
177
|
+
const enc = typeof encoding === "string" ? encoding : "utf8";
|
|
178
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(
|
|
179
|
+
enc
|
|
180
|
+
);
|
|
181
|
+
const combined = buffers[stream] + text.replace(/\r\n/g, "\n");
|
|
182
|
+
const parts = combined.split("\n");
|
|
183
|
+
buffers[stream] = parts.pop() ?? "";
|
|
184
|
+
for (const line of parts) {
|
|
185
|
+
const trimmed = line.replace(/\s+$/, "");
|
|
186
|
+
if (trimmed) {
|
|
187
|
+
emit({
|
|
188
|
+
type: "sandbox-log",
|
|
189
|
+
source: bridgeType,
|
|
190
|
+
stream,
|
|
191
|
+
line: trimmed
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return raw(
|
|
199
|
+
chunk,
|
|
200
|
+
encoding,
|
|
201
|
+
cb
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
process.stdout.write = patch(
|
|
205
|
+
"stdout",
|
|
206
|
+
rawStdoutWrite
|
|
207
|
+
);
|
|
208
|
+
process.stderr.write = patch(
|
|
209
|
+
"stderr",
|
|
210
|
+
rawStderrWrite
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
const handleInbound = async (msg, ws) => {
|
|
214
|
+
switch (msg.type) {
|
|
215
|
+
case "start": {
|
|
216
|
+
const firstTurn = isFirstTurn;
|
|
217
|
+
isFirstTurn = false;
|
|
218
|
+
eventLog = [];
|
|
219
|
+
diskBuffer = "";
|
|
220
|
+
void writeFile(eventLogPath, "").catch(() => {
|
|
221
|
+
});
|
|
222
|
+
turnAbort = new AbortController();
|
|
223
|
+
currentTurnState = "running";
|
|
224
|
+
void writeStartConfig(msg);
|
|
225
|
+
void writeBridgeMeta("running");
|
|
226
|
+
const startDebug = msg.debug;
|
|
227
|
+
debugConfig = {
|
|
228
|
+
enabled: startDebug?.enabled ?? envDebugEnabled,
|
|
229
|
+
level: startDebug?.level ?? procEnv.HARNESS_DEBUG_LEVEL,
|
|
230
|
+
subsystems: startDebug?.subsystems ?? parseEnvList(procEnv.HARNESS_DEBUG_SUBSYSTEMS)
|
|
231
|
+
};
|
|
232
|
+
if (debugConfig.enabled) {
|
|
233
|
+
installConsoleCapture();
|
|
234
|
+
}
|
|
235
|
+
const turn = {
|
|
236
|
+
emit,
|
|
237
|
+
requestToolResult: (toolCallId) => new Promise((resolve) => {
|
|
238
|
+
pendingToolResults.set(toolCallId, resolve);
|
|
239
|
+
}),
|
|
240
|
+
requestToolApproval: (approvalId) => new Promise((resolve) => {
|
|
241
|
+
pendingToolApprovals.set(approvalId, resolve);
|
|
242
|
+
}),
|
|
243
|
+
pendingUserMessages: [],
|
|
244
|
+
abortSignal: turnAbort.signal,
|
|
245
|
+
firstTurn,
|
|
246
|
+
bridgeLog: (input) => {
|
|
247
|
+
const level = input.level ?? "debug";
|
|
248
|
+
if (!shouldEmitDebugEvent(level, input.subsystem)) return;
|
|
249
|
+
emit({
|
|
250
|
+
type: "debug-event",
|
|
251
|
+
level,
|
|
252
|
+
subsystem: input.subsystem,
|
|
253
|
+
message: input.message,
|
|
254
|
+
...input.attrs ? { attrs: input.attrs } : {},
|
|
255
|
+
...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
currentUserMessages = turn.pendingUserMessages;
|
|
260
|
+
try {
|
|
261
|
+
await onStart(msg, turn);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
emit({ type: "error", error: serialiseError(err) });
|
|
264
|
+
} finally {
|
|
265
|
+
currentTurnState = "waiting";
|
|
266
|
+
void writeBridgeMeta("waiting");
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
case "tool-result": {
|
|
271
|
+
const resolver = pendingToolResults.get(msg.toolCallId);
|
|
272
|
+
if (resolver) {
|
|
273
|
+
pendingToolResults.delete(msg.toolCallId);
|
|
274
|
+
resolver({ output: msg.output, isError: msg.isError });
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
case "tool-approval-response": {
|
|
279
|
+
const resolver = pendingToolApprovals.get(msg.approvalId);
|
|
280
|
+
if (resolver) {
|
|
281
|
+
pendingToolApprovals.delete(msg.approvalId);
|
|
282
|
+
resolver({ approved: msg.approved, reason: msg.reason });
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
case "user-message":
|
|
287
|
+
currentUserMessages?.push(msg.text);
|
|
288
|
+
return;
|
|
289
|
+
case "abort":
|
|
290
|
+
turnAbort?.abort();
|
|
291
|
+
return;
|
|
292
|
+
case "resume":
|
|
293
|
+
replay(ws, msg.lastSeenEventId);
|
|
294
|
+
return;
|
|
295
|
+
case "shutdown":
|
|
296
|
+
currentTurnState = "done";
|
|
297
|
+
void writeBridgeMeta("done");
|
|
298
|
+
drainThenExit(ws, 1e3, "shutdown");
|
|
299
|
+
return;
|
|
300
|
+
case "detach": {
|
|
301
|
+
currentTurnState = "done";
|
|
302
|
+
void writeBridgeMeta("done");
|
|
303
|
+
const data = await onDetach?.() ?? {};
|
|
304
|
+
sendControl({ type: "bridge-detach", data });
|
|
305
|
+
drainThenExit(ws, 1e3, "detach");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
void writeBridgeMeta("init");
|
|
311
|
+
const wss = new WebSocketServer({ port: bridgeWsPort, host: "0.0.0.0" });
|
|
312
|
+
const exit = () => {
|
|
313
|
+
if (options.onExit) {
|
|
314
|
+
options.onExit();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
wss.close(() => process.exit(0));
|
|
318
|
+
setTimeout(() => process.exit(0), 1e3).unref();
|
|
319
|
+
};
|
|
320
|
+
const drainThenExit = (ws, code, reason) => {
|
|
321
|
+
const start = Date.now();
|
|
322
|
+
const tick = () => {
|
|
323
|
+
const drained = ws.bufferedAmount === 0 || ws.readyState !== WS_OPEN;
|
|
324
|
+
if (drained || Date.now() - start >= 5e3) {
|
|
325
|
+
void flushPendingEventsToDisk().finally(() => {
|
|
326
|
+
try {
|
|
327
|
+
ws.close(code, reason);
|
|
328
|
+
} finally {
|
|
329
|
+
exit();
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
setTimeout(tick, 10).unref();
|
|
335
|
+
};
|
|
336
|
+
tick();
|
|
337
|
+
};
|
|
338
|
+
wss.on("listening", () => {
|
|
339
|
+
const addr = wss.address();
|
|
340
|
+
currentBoundPort = typeof addr === "object" && addr ? addr.port : 0;
|
|
341
|
+
currentTurnState = "waiting";
|
|
342
|
+
void writeBridgeMeta("waiting");
|
|
343
|
+
stdout.write(
|
|
344
|
+
JSON.stringify({
|
|
345
|
+
type: "bridge-ready",
|
|
346
|
+
port: currentBoundPort
|
|
347
|
+
}) + "\n"
|
|
348
|
+
);
|
|
349
|
+
options.onListening?.(currentBoundPort);
|
|
350
|
+
});
|
|
351
|
+
wss.on("connection", (ws, req) => {
|
|
352
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
353
|
+
if (url.searchParams.get("agent_bridge_token") !== expectedToken) {
|
|
354
|
+
ws.close(1008, "unauthorized");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
activeSocket = ws;
|
|
358
|
+
sendControl({
|
|
359
|
+
type: "bridge-hello",
|
|
360
|
+
state: currentTurnState,
|
|
361
|
+
lastSeq: seqCounter
|
|
362
|
+
});
|
|
363
|
+
ws.on("message", (raw) => {
|
|
364
|
+
let parsed;
|
|
365
|
+
try {
|
|
366
|
+
const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
|
|
367
|
+
parsed = JSON.parse(text);
|
|
368
|
+
} catch (err) {
|
|
369
|
+
sendControl({
|
|
370
|
+
type: "error",
|
|
371
|
+
error: `protocol parse error: ${err.message}`
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
void handleInbound(parsed, ws);
|
|
376
|
+
});
|
|
377
|
+
ws.on("close", () => {
|
|
378
|
+
if (activeSocket === ws) {
|
|
379
|
+
activeSocket = void 0;
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
ws.on("error", () => {
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
process.on("uncaughtException", (err) => {
|
|
386
|
+
emit({ type: "error", error: serialiseError(err) });
|
|
387
|
+
});
|
|
388
|
+
process.on("unhandledRejection", (err) => {
|
|
389
|
+
emit({ type: "error", error: serialiseError(err) });
|
|
390
|
+
});
|
|
391
|
+
await new Promise((resolve) => {
|
|
392
|
+
if (wss.address() != null) {
|
|
393
|
+
resolve();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
wss.on("listening", () => resolve());
|
|
397
|
+
});
|
|
398
|
+
return {
|
|
399
|
+
port: currentBoundPort,
|
|
400
|
+
close: () => new Promise((resolve) => {
|
|
401
|
+
wss.close(() => resolve());
|
|
402
|
+
})
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function serialiseError(err) {
|
|
406
|
+
if (err instanceof Error) {
|
|
407
|
+
return { name: err.name, message: err.message, stack: err.stack };
|
|
408
|
+
}
|
|
409
|
+
return err;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/bridge/compaction-latch.ts
|
|
413
|
+
function createCompactionLatch(emit) {
|
|
414
|
+
let boundary;
|
|
415
|
+
let summary;
|
|
416
|
+
const tryEmit = () => {
|
|
417
|
+
if (!boundary || summary === void 0) return;
|
|
418
|
+
emit({
|
|
419
|
+
type: "compaction",
|
|
420
|
+
trigger: boundary.trigger,
|
|
421
|
+
summary,
|
|
422
|
+
...boundary.tokensBefore !== void 0 ? { tokensBefore: boundary.tokensBefore } : {},
|
|
423
|
+
...boundary.tokensAfter !== void 0 ? { tokensAfter: boundary.tokensAfter } : {}
|
|
424
|
+
});
|
|
425
|
+
boundary = void 0;
|
|
426
|
+
summary = void 0;
|
|
427
|
+
};
|
|
428
|
+
return {
|
|
429
|
+
onBoundary(next) {
|
|
430
|
+
boundary = next;
|
|
431
|
+
tryEmit();
|
|
432
|
+
},
|
|
433
|
+
onSummary(next) {
|
|
434
|
+
summary = next;
|
|
435
|
+
tryEmit();
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// src/bridge/index.ts
|
|
441
|
+
import { randomUUID } from "crypto";
|
|
442
|
+
import { argv, stdout as stdout2 } from "process";
|
|
443
|
+
import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
|
|
444
|
+
import * as mcpServerModule from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
445
|
+
import { z } from "zod";
|
|
446
|
+
var NATIVE_TO_COMMON = {
|
|
447
|
+
Read: "read",
|
|
448
|
+
Write: "write",
|
|
449
|
+
Edit: "edit",
|
|
450
|
+
Bash: "bash",
|
|
451
|
+
Glob: "glob",
|
|
452
|
+
Grep: "grep",
|
|
453
|
+
WebSearch: "webSearch"
|
|
454
|
+
};
|
|
455
|
+
var NATIVE_TOOL_KINDS = {
|
|
456
|
+
Read: "readonly",
|
|
457
|
+
Glob: "readonly",
|
|
458
|
+
Grep: "readonly",
|
|
459
|
+
WebSearch: "readonly",
|
|
460
|
+
WebFetch: "readonly",
|
|
461
|
+
TaskGet: "readonly",
|
|
462
|
+
TaskList: "readonly",
|
|
463
|
+
TaskOutput: "readonly",
|
|
464
|
+
ListMcpResources: "readonly",
|
|
465
|
+
ReadMcpResource: "readonly",
|
|
466
|
+
Write: "edit",
|
|
467
|
+
Edit: "edit",
|
|
468
|
+
NotebookEdit: "edit",
|
|
469
|
+
TodoWrite: "edit",
|
|
470
|
+
TaskCreate: "edit",
|
|
471
|
+
TaskUpdate: "edit",
|
|
472
|
+
TaskStop: "edit",
|
|
473
|
+
EnterWorktree: "edit",
|
|
474
|
+
ExitWorktree: "edit",
|
|
475
|
+
ExitPlanMode: "edit",
|
|
476
|
+
Skill: "edit",
|
|
477
|
+
AskUserQuestion: "readonly",
|
|
478
|
+
Bash: "bash"
|
|
479
|
+
};
|
|
480
|
+
function toCommonName(nativeName) {
|
|
481
|
+
return NATIVE_TO_COMMON[nativeName] ?? nativeName;
|
|
482
|
+
}
|
|
483
|
+
function toThinkingConfig(thinking) {
|
|
484
|
+
switch (thinking) {
|
|
485
|
+
case "adaptive":
|
|
486
|
+
return { type: "adaptive", display: "summarized" };
|
|
487
|
+
case "on":
|
|
488
|
+
return { type: "enabled", display: "summarized" };
|
|
489
|
+
case "off":
|
|
490
|
+
return { type: "disabled" };
|
|
491
|
+
default:
|
|
492
|
+
return void 0;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
var args = parseArgs(argv.slice(2));
|
|
496
|
+
var workdir = args.workdir;
|
|
497
|
+
var bridgeStateDir = args.bridgeStateDir;
|
|
498
|
+
if (!workdir) {
|
|
499
|
+
emitFatal("Missing --workdir argument.");
|
|
500
|
+
}
|
|
501
|
+
if (!bridgeStateDir) {
|
|
502
|
+
emitFatal("Missing --bridge-state-dir argument.");
|
|
503
|
+
}
|
|
504
|
+
var claudeSdk = claudeAgentSdk;
|
|
505
|
+
var mcpModule = mcpServerModule;
|
|
506
|
+
await runBridge({
|
|
507
|
+
bridgeType: "claude-code",
|
|
508
|
+
bridgeStateDir,
|
|
509
|
+
onStart: runTurn,
|
|
510
|
+
// Claude Code's session state lives in the workdir on the sandbox filesystem
|
|
511
|
+
// (captured by the sandbox snapshot on stop); the resume payload is empty.
|
|
512
|
+
onDetach: () => ({})
|
|
513
|
+
});
|
|
514
|
+
function createPermissionOptions(input) {
|
|
515
|
+
const permissionMode = input.start.permissionMode ?? "allow-all";
|
|
516
|
+
if (permissionMode === "allow-all") {
|
|
517
|
+
return {
|
|
518
|
+
permissionMode: "bypassPermissions",
|
|
519
|
+
allowDangerouslySkipPermissions: true
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
permissionMode: permissionMode === "allow-edits" ? "acceptEdits" : "default",
|
|
524
|
+
allowDangerouslySkipPermissions: false,
|
|
525
|
+
settings: createPermissionSettings({ permissionMode }),
|
|
526
|
+
canUseTool: async (toolName, toolInput, options) => {
|
|
527
|
+
if (toolName.startsWith("mcp__harness-tools__")) {
|
|
528
|
+
return { behavior: "allow", updatedInput: toolInput };
|
|
529
|
+
}
|
|
530
|
+
if (!nativeToolRequiresApproval({
|
|
531
|
+
nativeName: toolName,
|
|
532
|
+
permissionMode
|
|
533
|
+
})) {
|
|
534
|
+
return { behavior: "allow", updatedInput: toolInput };
|
|
535
|
+
}
|
|
536
|
+
const approvalId = options.toolUseID;
|
|
537
|
+
input.approvalRequestedToolUseIds.add(approvalId);
|
|
538
|
+
input.nativeToolCallNames.set(approvalId, toolName);
|
|
539
|
+
input.emit({
|
|
540
|
+
type: "tool-call",
|
|
541
|
+
toolCallId: approvalId,
|
|
542
|
+
toolName: toCommonName(toolName),
|
|
543
|
+
nativeName: toolName,
|
|
544
|
+
input: JSON.stringify(toolInput ?? {}),
|
|
545
|
+
providerExecuted: true
|
|
546
|
+
});
|
|
547
|
+
input.emit({
|
|
548
|
+
type: "tool-approval-request",
|
|
549
|
+
approvalId,
|
|
550
|
+
toolCallId: approvalId
|
|
551
|
+
});
|
|
552
|
+
const decision = await input.turn.requestToolApproval(approvalId);
|
|
553
|
+
return decision.approved ? { behavior: "allow", updatedInput: toolInput, toolUseID: approvalId } : {
|
|
554
|
+
behavior: "deny",
|
|
555
|
+
message: decision.reason ?? "Denied",
|
|
556
|
+
toolUseID: approvalId
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function createPermissionSettings(input) {
|
|
562
|
+
const askRules = Object.entries(NATIVE_TOOL_KINDS).filter(
|
|
563
|
+
([, kind]) => input.permissionMode === "allow-reads" ? kind === "edit" || kind === "bash" : input.permissionMode === "allow-edits" ? kind === "bash" : false
|
|
564
|
+
).map(([nativeName]) => `${nativeName}(*)`);
|
|
565
|
+
if (askRules.length === 0) return void 0;
|
|
566
|
+
return {
|
|
567
|
+
permissions: { ask: askRules },
|
|
568
|
+
sandbox: { autoAllowBashIfSandboxed: false }
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function nativeToolRequiresApproval(input) {
|
|
572
|
+
if (input.permissionMode === "allow-all") return false;
|
|
573
|
+
const kind = NATIVE_TOOL_KINDS[input.nativeName] ?? "edit";
|
|
574
|
+
if (input.permissionMode === "allow-edits") return kind === "bash";
|
|
575
|
+
return kind === "edit" || kind === "bash";
|
|
576
|
+
}
|
|
577
|
+
async function runTurn(start, turn) {
|
|
578
|
+
const emit = (msg) => turn.emit(msg);
|
|
579
|
+
const abortCtl = new AbortController();
|
|
580
|
+
if (turn.abortSignal.aborted) {
|
|
581
|
+
abortCtl.abort();
|
|
582
|
+
} else {
|
|
583
|
+
turn.abortSignal.addEventListener("abort", () => abortCtl.abort(), {
|
|
584
|
+
once: true
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
const nativeToolCallNames = /* @__PURE__ */ new Map();
|
|
588
|
+
const approvalRequestedToolUseIds = /* @__PURE__ */ new Set();
|
|
589
|
+
const mcpToolUseIds = /* @__PURE__ */ new Set();
|
|
590
|
+
const mcpServers = {};
|
|
591
|
+
if (start.tools && start.tools.length > 0) {
|
|
592
|
+
const server = new mcpModule.McpServer({
|
|
593
|
+
name: "harness-tools",
|
|
594
|
+
version: "1.0.0"
|
|
595
|
+
});
|
|
596
|
+
for (const tool of start.tools) {
|
|
597
|
+
const shape = jsonSchemaToZodShape(tool.inputSchema, z);
|
|
598
|
+
server.tool(
|
|
599
|
+
tool.name,
|
|
600
|
+
tool.description ?? "",
|
|
601
|
+
shape,
|
|
602
|
+
async (input) => {
|
|
603
|
+
const toolCallId = randomUUID();
|
|
604
|
+
emit({
|
|
605
|
+
type: "tool-call",
|
|
606
|
+
toolCallId,
|
|
607
|
+
toolName: tool.name,
|
|
608
|
+
input: JSON.stringify(input),
|
|
609
|
+
providerExecuted: false
|
|
610
|
+
});
|
|
611
|
+
const { output, isError } = await turn.requestToolResult(toolCallId);
|
|
612
|
+
emit({
|
|
613
|
+
type: "tool-result",
|
|
614
|
+
toolCallId,
|
|
615
|
+
toolName: tool.name,
|
|
616
|
+
result: output ?? null,
|
|
617
|
+
isError: !!isError
|
|
618
|
+
});
|
|
619
|
+
return {
|
|
620
|
+
content: [{ type: "text", text: JSON.stringify(output ?? null) }],
|
|
621
|
+
isError
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
mcpServers["harness-tools"] = {
|
|
627
|
+
type: "sdk",
|
|
628
|
+
name: "harness-tools",
|
|
629
|
+
instance: server
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
const compaction = createCompactionLatch((event) => emit(event));
|
|
633
|
+
const queryInput = createQueryInput({
|
|
634
|
+
initialUserMessage: start.prompt,
|
|
635
|
+
pendingUserMessages: turn.pendingUserMessages,
|
|
636
|
+
abortSignal: abortCtl.signal
|
|
637
|
+
});
|
|
638
|
+
const permissionOptions = createPermissionOptions({
|
|
639
|
+
start,
|
|
640
|
+
turn,
|
|
641
|
+
emit,
|
|
642
|
+
nativeToolCallNames,
|
|
643
|
+
approvalRequestedToolUseIds
|
|
644
|
+
});
|
|
645
|
+
const q = claudeSdk.query({
|
|
646
|
+
prompt: queryInput.input,
|
|
647
|
+
options: {
|
|
648
|
+
...start.model ? { model: start.model } : {},
|
|
649
|
+
...start.maxTurns !== void 0 ? { maxTurns: start.maxTurns } : {},
|
|
650
|
+
...toThinkingConfig(start.thinking) ? { thinking: toThinkingConfig(start.thinking) } : {},
|
|
651
|
+
includePartialMessages: true,
|
|
652
|
+
// The `PostCompact` hook carries the compaction summary, which the
|
|
653
|
+
// `compact_boundary` system message does not. Latch it for the unified
|
|
654
|
+
// `compaction` event; return an empty output so compaction proceeds.
|
|
655
|
+
hooks: {
|
|
656
|
+
PostCompact: [
|
|
657
|
+
{
|
|
658
|
+
hooks: [
|
|
659
|
+
async (input) => {
|
|
660
|
+
if (typeof input?.compact_summary === "string") {
|
|
661
|
+
compaction.onSummary(input.compact_summary);
|
|
662
|
+
}
|
|
663
|
+
return {};
|
|
664
|
+
}
|
|
665
|
+
]
|
|
666
|
+
}
|
|
667
|
+
]
|
|
668
|
+
},
|
|
669
|
+
// Continuation rule: the host can force-continue (resume after a
|
|
670
|
+
// cross-process detach) by setting `start.continue: true`; otherwise
|
|
671
|
+
// we continue every subsequent turn after the first one in this
|
|
672
|
+
// bridge process.
|
|
673
|
+
...start.continue === true || !turn.firstTurn ? { continue: true } : {},
|
|
674
|
+
...permissionOptions,
|
|
675
|
+
mcpServers,
|
|
676
|
+
cwd: workdir,
|
|
677
|
+
abortSignal: abortCtl.signal
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
let stepUsage;
|
|
681
|
+
let totalCostUsd;
|
|
682
|
+
let observedTerminalError;
|
|
683
|
+
let emittedTerminalError = false;
|
|
684
|
+
let emittedTerminalFinish = false;
|
|
685
|
+
let streamStarted = false;
|
|
686
|
+
const partialBlocks = /* @__PURE__ */ new Map();
|
|
687
|
+
const emitTerminalError = (message) => {
|
|
688
|
+
const normalized = message?.trim();
|
|
689
|
+
if (!normalized || emittedTerminalError || emittedTerminalFinish) return;
|
|
690
|
+
observedTerminalError = normalized;
|
|
691
|
+
emittedTerminalError = true;
|
|
692
|
+
emit({ type: "error", error: normalized });
|
|
693
|
+
queryInput.close();
|
|
694
|
+
abortCtl.abort();
|
|
695
|
+
};
|
|
696
|
+
try {
|
|
697
|
+
for await (const msg of q) {
|
|
698
|
+
if (abortCtl.signal.aborted) break;
|
|
699
|
+
if (typeof msg.error === "string" && msg.error.trim()) {
|
|
700
|
+
observedTerminalError = msg.error.trim();
|
|
701
|
+
}
|
|
702
|
+
const type = msg.type;
|
|
703
|
+
if (!streamStarted) {
|
|
704
|
+
const initModel = type === "system" && msg.subtype === "init" && typeof msg.model === "string" ? msg.model : void 0;
|
|
705
|
+
emit({
|
|
706
|
+
type: "stream-start",
|
|
707
|
+
...initModel ? { modelId: initModel } : {}
|
|
708
|
+
});
|
|
709
|
+
streamStarted = true;
|
|
710
|
+
}
|
|
711
|
+
if (type === "auth_status" && typeof msg.error === "string" && msg.error.trim()) {
|
|
712
|
+
emitTerminalError(msg.error);
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (type === "system" && msg.subtype === "api_retry" && typeof msg.error_status === "number" && [401, 403, 404].includes(msg.error_status)) {
|
|
716
|
+
emitTerminalError(
|
|
717
|
+
`HTTP ${msg.error_status}: ${msg.error ?? "provider request failed"}`
|
|
718
|
+
);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (type === "system" && msg.subtype === "task_updated" && msg.patch?.status === "failed" && typeof msg.patch.error === "string") {
|
|
722
|
+
emitTerminalError(msg.patch.error);
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (type === "system" && msg.subtype === "compact_boundary") {
|
|
726
|
+
const meta = msg.compact_metadata;
|
|
727
|
+
if (meta) {
|
|
728
|
+
compaction.onBoundary({
|
|
729
|
+
trigger: meta.trigger,
|
|
730
|
+
...typeof meta.pre_tokens === "number" ? { tokensBefore: meta.pre_tokens } : {},
|
|
731
|
+
...typeof meta.post_tokens === "number" ? { tokensAfter: meta.post_tokens } : {}
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (type === "stream_event") {
|
|
737
|
+
handleStreamEvent(msg.event, partialBlocks, emit);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (type === "assistant" && msg.message?.content) {
|
|
741
|
+
for (const block of msg.message.content) {
|
|
742
|
+
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
743
|
+
const mcpPrefix = "mcp__harness-tools__";
|
|
744
|
+
if (block.name.startsWith(mcpPrefix)) {
|
|
745
|
+
mcpToolUseIds.add(block.id);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
nativeToolCallNames.set(block.id, block.name);
|
|
749
|
+
if (approvalRequestedToolUseIds.has(block.id)) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
emit({
|
|
753
|
+
type: "tool-call",
|
|
754
|
+
toolCallId: block.id,
|
|
755
|
+
toolName: toCommonName(block.name),
|
|
756
|
+
nativeName: block.name,
|
|
757
|
+
input: JSON.stringify(block.input ?? {}),
|
|
758
|
+
providerExecuted: true
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
if (type === "user" && msg.message?.content) {
|
|
765
|
+
for (const block of msg.message.content) {
|
|
766
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
767
|
+
if (mcpToolUseIds.has(block.tool_use_id)) {
|
|
768
|
+
mcpToolUseIds.delete(block.tool_use_id);
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
approvalRequestedToolUseIds.delete(block.tool_use_id);
|
|
772
|
+
const nativeName = nativeToolCallNames.get(block.tool_use_id) ?? "unknown";
|
|
773
|
+
nativeToolCallNames.delete(block.tool_use_id);
|
|
774
|
+
const toolName = toCommonName(nativeName);
|
|
775
|
+
const isError = !!block.is_error;
|
|
776
|
+
const content = stringifyContent(block.content);
|
|
777
|
+
const result = toolName === "bash" ? { exitCode: isError ? 1 : 0, stdout: content } : content;
|
|
778
|
+
emit({
|
|
779
|
+
type: "tool-result",
|
|
780
|
+
toolCallId: block.tool_use_id,
|
|
781
|
+
toolName,
|
|
782
|
+
result,
|
|
783
|
+
isError
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (type === "result") {
|
|
790
|
+
if (msg.subtype === "success") {
|
|
791
|
+
const emptyResult = !msg.result?.trim?.();
|
|
792
|
+
if (emptyResult && observedTerminalError) {
|
|
793
|
+
emitTerminalError(observedTerminalError);
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
const usage = msg.usage ?? msg.message?.usage;
|
|
797
|
+
const harnessUsage = mapUsage(usage);
|
|
798
|
+
if (harnessUsage) stepUsage = harnessUsage;
|
|
799
|
+
if (typeof msg.total_cost_usd === "number") {
|
|
800
|
+
totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
|
|
801
|
+
}
|
|
802
|
+
const metadata = typeof msg.total_cost_usd === "number" ? { "claude-code": { costUsd: msg.total_cost_usd } } : void 0;
|
|
803
|
+
emit({
|
|
804
|
+
type: "finish-step",
|
|
805
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
806
|
+
usage: harnessUsage ?? defaultUsage(),
|
|
807
|
+
...metadata ? { harnessMetadata: metadata } : {}
|
|
808
|
+
});
|
|
809
|
+
queryInput.close();
|
|
810
|
+
break;
|
|
811
|
+
} else {
|
|
812
|
+
emitTerminalError(
|
|
813
|
+
(Array.isArray(msg.errors) ? msg.errors.join("\n") : void 0) || observedTerminalError || msg.result || "Unknown error"
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
} catch (err) {
|
|
820
|
+
if (!(abortCtl.signal.aborted && emittedTerminalError)) {
|
|
821
|
+
emit({ type: "error", error: serialiseError2(err) });
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
} finally {
|
|
825
|
+
queryInput.close();
|
|
826
|
+
}
|
|
827
|
+
if (emittedTerminalError) return;
|
|
828
|
+
emittedTerminalFinish = true;
|
|
829
|
+
void emittedTerminalFinish;
|
|
830
|
+
emit({
|
|
831
|
+
type: "finish",
|
|
832
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
833
|
+
totalUsage: stepUsage ?? defaultUsage(),
|
|
834
|
+
...totalCostUsd !== void 0 ? { harnessMetadata: { "claude-code": { costUsd: totalCostUsd } } } : {}
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
function handleStreamEvent(event, partialBlocks, send) {
|
|
838
|
+
if (!event || typeof event.index !== "number") return;
|
|
839
|
+
const index = event.index;
|
|
840
|
+
if (event.type === "content_block_start") {
|
|
841
|
+
const blockType = event.content_block?.type;
|
|
842
|
+
if (blockType === "text") {
|
|
843
|
+
const id = randomUUID();
|
|
844
|
+
partialBlocks.set(index, { id, kind: "text" });
|
|
845
|
+
send({ type: "text-start", id });
|
|
846
|
+
} else if (blockType === "thinking") {
|
|
847
|
+
const id = randomUUID();
|
|
848
|
+
partialBlocks.set(index, { id, kind: "thinking" });
|
|
849
|
+
send({ type: "reasoning-start", id });
|
|
850
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (event.type === "content_block_delta") {
|
|
854
|
+
const block = partialBlocks.get(index);
|
|
855
|
+
if (!block) return;
|
|
856
|
+
if (block.kind === "text" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
|
|
857
|
+
send({ type: "text-delta", id: block.id, delta: event.delta.text });
|
|
858
|
+
} else if (block.kind === "thinking" && event.delta?.type === "thinking_delta" && typeof event.delta.thinking === "string") {
|
|
859
|
+
send({
|
|
860
|
+
type: "reasoning-delta",
|
|
861
|
+
id: block.id,
|
|
862
|
+
delta: event.delta.thinking
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
if (event.type === "content_block_stop") {
|
|
868
|
+
const block = partialBlocks.get(index);
|
|
869
|
+
if (!block) return;
|
|
870
|
+
partialBlocks.delete(index);
|
|
871
|
+
if (block.kind === "text") {
|
|
872
|
+
send({ type: "text-end", id: block.id });
|
|
873
|
+
} else {
|
|
874
|
+
send({ type: "reasoning-end", id: block.id });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function createQueryInput({
|
|
879
|
+
initialUserMessage,
|
|
880
|
+
pendingUserMessages,
|
|
881
|
+
abortSignal
|
|
882
|
+
}) {
|
|
883
|
+
let closed = false;
|
|
884
|
+
const close = () => {
|
|
885
|
+
closed = true;
|
|
886
|
+
};
|
|
887
|
+
if (abortSignal.aborted) {
|
|
888
|
+
close();
|
|
889
|
+
} else {
|
|
890
|
+
abortSignal.addEventListener("abort", close, { once: true });
|
|
891
|
+
}
|
|
892
|
+
const toUserMessage = (text) => ({
|
|
893
|
+
type: "user",
|
|
894
|
+
message: {
|
|
895
|
+
role: "user",
|
|
896
|
+
content: [{ type: "text", text }]
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
return {
|
|
900
|
+
close,
|
|
901
|
+
input: {
|
|
902
|
+
[Symbol.asyncIterator]() {
|
|
903
|
+
let sentInitial = false;
|
|
904
|
+
return {
|
|
905
|
+
async next() {
|
|
906
|
+
while (!closed && !abortSignal.aborted) {
|
|
907
|
+
if (!sentInitial) {
|
|
908
|
+
sentInitial = true;
|
|
909
|
+
return {
|
|
910
|
+
value: toUserMessage(initialUserMessage),
|
|
911
|
+
done: false
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
if (pendingUserMessages.length > 0) {
|
|
915
|
+
return {
|
|
916
|
+
value: toUserMessage(pendingUserMessages.shift()),
|
|
917
|
+
done: false
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
921
|
+
}
|
|
922
|
+
return { value: void 0, done: true };
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function stringifyContent(content) {
|
|
930
|
+
if (typeof content === "string") return content;
|
|
931
|
+
if (Array.isArray(content)) {
|
|
932
|
+
return content.map(
|
|
933
|
+
(entry) => entry && typeof entry === "object" && "text" in entry ? String(entry.text ?? "") : JSON.stringify(entry)
|
|
934
|
+
).join("");
|
|
935
|
+
}
|
|
936
|
+
return JSON.stringify(content);
|
|
937
|
+
}
|
|
938
|
+
function mapUsage(usage) {
|
|
939
|
+
if (!usage || typeof usage !== "object") return void 0;
|
|
940
|
+
const u = usage;
|
|
941
|
+
return {
|
|
942
|
+
inputTokens: {
|
|
943
|
+
total: (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0),
|
|
944
|
+
noCache: u.input_tokens ?? 0,
|
|
945
|
+
cacheRead: u.cache_read_input_tokens ?? 0,
|
|
946
|
+
cacheWrite: u.cache_creation_input_tokens ?? 0
|
|
947
|
+
},
|
|
948
|
+
outputTokens: {
|
|
949
|
+
total: u.output_tokens ?? 0,
|
|
950
|
+
text: u.output_tokens ?? 0
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function defaultUsage() {
|
|
955
|
+
return {
|
|
956
|
+
inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
|
|
957
|
+
outputTokens: { total: 0, text: 0 }
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function jsonSchemaToZodShape(schema, z2) {
|
|
961
|
+
if (!schema || typeof schema !== "object") return {};
|
|
962
|
+
const s = schema;
|
|
963
|
+
const shape = {};
|
|
964
|
+
const required = new Set(s.required ?? []);
|
|
965
|
+
for (const [key, val] of Object.entries(s.properties ?? {})) {
|
|
966
|
+
let z_;
|
|
967
|
+
switch (val.type) {
|
|
968
|
+
case "string":
|
|
969
|
+
z_ = z2.string();
|
|
970
|
+
break;
|
|
971
|
+
case "number":
|
|
972
|
+
case "integer":
|
|
973
|
+
z_ = z2.number();
|
|
974
|
+
break;
|
|
975
|
+
case "boolean":
|
|
976
|
+
z_ = z2.boolean();
|
|
977
|
+
break;
|
|
978
|
+
case "array":
|
|
979
|
+
z_ = z2.array(z2.any());
|
|
980
|
+
break;
|
|
981
|
+
default:
|
|
982
|
+
z_ = z2.any();
|
|
983
|
+
}
|
|
984
|
+
if (val.description)
|
|
985
|
+
z_ = z_.describe(
|
|
986
|
+
val.description
|
|
987
|
+
);
|
|
988
|
+
shape[key] = required.has(key) ? z_ : z_.optional();
|
|
989
|
+
}
|
|
990
|
+
return shape;
|
|
991
|
+
}
|
|
992
|
+
function parseArgs(args2) {
|
|
993
|
+
const out = {};
|
|
994
|
+
for (let i = 0; i < args2.length; i++) {
|
|
995
|
+
if (args2[i] === "--workdir" && i + 1 < args2.length) {
|
|
996
|
+
out.workdir = args2[++i];
|
|
997
|
+
} else if (args2[i] === "--bridge-state-dir" && i + 1 < args2.length) {
|
|
998
|
+
out.bridgeStateDir = args2[++i];
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return out;
|
|
1002
|
+
}
|
|
1003
|
+
function serialiseError2(err) {
|
|
1004
|
+
if (err instanceof Error) {
|
|
1005
|
+
return { name: err.name, message: err.message, stack: err.stack };
|
|
1006
|
+
}
|
|
1007
|
+
return err;
|
|
1008
|
+
}
|
|
1009
|
+
function emitFatal(message) {
|
|
1010
|
+
stdout2.write(JSON.stringify({ type: "bridge-fatal", message }) + "\n");
|
|
1011
|
+
process.exit(1);
|
|
1012
|
+
}
|
|
1013
|
+
//# sourceMappingURL=index.mjs.map
|