@ai-sdk/harness 0.0.0 → 1.0.0-canary.3
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 +24 -0
- package/LICENSE +13 -0
- package/README.md +142 -0
- package/agent/index.ts +17 -0
- package/bridge/index.ts +10 -0
- package/dist/agent/index.d.ts +1480 -0
- package/dist/agent/index.js +2554 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/bridge/index.d.ts +111 -0
- package/dist/bridge/index.js +414 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/index.d.ts +1510 -0
- package/dist/index.js +15834 -0
- package/dist/index.js.map +1 -0
- package/dist/observability/index.d.ts +97 -0
- package/dist/observability/index.js +225 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/utils/index.d.ts +196 -0
- package/dist/utils/index.js +327 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +104 -1
- package/src/agent/harness-agent-session.ts +352 -0
- package/src/agent/harness-agent-settings.ts +131 -0
- package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
- package/src/agent/harness-agent.ts +750 -0
- package/src/agent/harness-diagnostics.ts +88 -0
- package/src/agent/internal/bootstrap-recipe.ts +124 -0
- package/src/agent/internal/bridge-port-registry.ts +52 -0
- package/src/agent/internal/harness-stream-text-result.ts +720 -0
- package/src/agent/internal/permission-mode.ts +50 -0
- package/src/agent/internal/resolve-observability.ts +128 -0
- package/src/agent/internal/resume-state-validation.ts +51 -0
- package/src/agent/internal/run-prompt.ts +811 -0
- package/src/agent/internal/strip-work-dir.ts +68 -0
- package/src/agent/internal/to-harness-stream.ts +75 -0
- package/src/agent/internal/translate-stream-part.ts +221 -0
- package/src/agent/internal/turn-telemetry.ts +359 -0
- package/src/agent/prewarm.ts +46 -0
- package/src/bridge/index.ts +700 -0
- package/src/errors/harness-capability-unsupported-error.ts +41 -0
- package/src/errors/harness-error.ts +22 -0
- package/src/index.ts +3 -0
- package/src/observability/file-reporter.ts +209 -0
- package/src/observability/index.ts +13 -0
- package/src/observability/trace-tree-reporter.ts +122 -0
- package/src/utils/classify-disk-log.ts +43 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/sandbox-channel.ts +453 -0
- package/src/v1/harness-v1-bootstrap.ts +46 -0
- package/src/v1/harness-v1-bridge-protocol.ts +310 -0
- package/src/v1/harness-v1-builtin-tool.ts +138 -0
- package/src/v1/harness-v1-call-warning.ts +22 -0
- package/src/v1/harness-v1-diagnostic.ts +66 -0
- package/src/v1/harness-v1-metadata.ts +13 -0
- package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
- package/src/v1/harness-v1-observability.ts +20 -0
- package/src/v1/harness-v1-permission-mode.ts +11 -0
- package/src/v1/harness-v1-prompt-control.ts +41 -0
- package/src/v1/harness-v1-prompt.ts +11 -0
- package/src/v1/harness-v1-resume-state.ts +46 -0
- package/src/v1/harness-v1-sandbox-provider.ts +76 -0
- package/src/v1/harness-v1-session.ts +268 -0
- package/src/v1/harness-v1-skill.ts +22 -0
- package/src/v1/harness-v1-stream-part.ts +363 -0
- package/src/v1/harness-v1-tool-spec.ts +31 -0
- package/src/v1/harness-v1.ts +83 -0
- package/src/v1/index.ts +93 -0
- package/utils/index.ts +1 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// src/utils/sandbox-channel.ts
|
|
2
|
+
import {
|
|
3
|
+
safeParseJSON,
|
|
4
|
+
safeValidateTypes
|
|
5
|
+
} from "@ai-sdk/provider-utils";
|
|
6
|
+
var sleep = (ms) => new Promise((resolve) => {
|
|
7
|
+
var _a;
|
|
8
|
+
const t = setTimeout(resolve, ms);
|
|
9
|
+
(_a = t.unref) == null ? void 0 : _a.call(t);
|
|
10
|
+
});
|
|
11
|
+
var SandboxChannel = class {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
14
|
+
this.buffered = /* @__PURE__ */ new Map();
|
|
15
|
+
this.onCloseHandlers = /* @__PURE__ */ new Set();
|
|
16
|
+
this.connected = false;
|
|
17
|
+
/** Host has begun teardown; suppresses reconnect so a bridge-side close finalises. */
|
|
18
|
+
this.closing = false;
|
|
19
|
+
/**
|
|
20
|
+
* Host has gracefully suspended (slice boundary). Inbound frames are ignored
|
|
21
|
+
* from this point so the cursor stops advancing exactly at the last delivered
|
|
22
|
+
* event — the bridge keeps the turn running and the not-yet-delivered tail is
|
|
23
|
+
* replayed to the next process on `resume`.
|
|
24
|
+
*/
|
|
25
|
+
this.suspended = false;
|
|
26
|
+
/** Channel is fully torn down; `send` throws and `onClose` has fired. */
|
|
27
|
+
this.terminal = false;
|
|
28
|
+
this._lastSeenEventId = 0;
|
|
29
|
+
this.pendingSends = [];
|
|
30
|
+
this.dispatchChain = Promise.resolve();
|
|
31
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
32
|
+
this.connectThunk = options.connect;
|
|
33
|
+
this.outboundSchema = options.outboundSchema;
|
|
34
|
+
this.onDebug = options.onDebug;
|
|
35
|
+
this.onDiagnostic = options.onDiagnostic;
|
|
36
|
+
this.maxElapsedMs = (_b = (_a = options.reconnect) == null ? void 0 : _a.maxElapsedMs) != null ? _b : 3e4;
|
|
37
|
+
this.initialDelayMs = (_d = (_c = options.reconnect) == null ? void 0 : _c.initialDelayMs) != null ? _d : 50;
|
|
38
|
+
this.maxDelayMs = (_f = (_e = options.reconnect) == null ? void 0 : _e.maxDelayMs) != null ? _f : 2e3;
|
|
39
|
+
this._lastSeenEventId = (_g = options.initialLastSeenEventId) != null ? _g : 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Highest bridge event `seq` this channel has observed. Persist it (e.g. via
|
|
43
|
+
* the adapter's resume handle) so a future process can seed
|
|
44
|
+
* {@link SandboxChannelOptions.initialLastSeenEventId} and attach.
|
|
45
|
+
*/
|
|
46
|
+
get lastSeenEventId() {
|
|
47
|
+
return this._lastSeenEventId;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Establish the initial connection. A single attempt — startup failures
|
|
51
|
+
* reject so the caller can fail `doStart` cleanly. Reconnect retries apply
|
|
52
|
+
* only to drops after a successful open.
|
|
53
|
+
*
|
|
54
|
+
* Pass `{ resume: true }` to attach to a bridge that is already mid-session:
|
|
55
|
+
* after the socket opens, the channel sends `{ type: 'resume', lastSeenEventId }`
|
|
56
|
+
* so the bridge replays everything past the seeded cursor. This is the
|
|
57
|
+
* cross-process attach handshake — identical to what a transient reconnect
|
|
58
|
+
* does, but triggered by the initial open from a new process.
|
|
59
|
+
*/
|
|
60
|
+
async open(opts) {
|
|
61
|
+
if (this.terminal) {
|
|
62
|
+
throw new Error("SandboxChannel: cannot open a closed channel.");
|
|
63
|
+
}
|
|
64
|
+
const ws = await this.connectThunk();
|
|
65
|
+
this.wire(ws);
|
|
66
|
+
this.ws = ws;
|
|
67
|
+
this.connected = true;
|
|
68
|
+
if (opts == null ? void 0 : opts.resume) {
|
|
69
|
+
this.rawSend(
|
|
70
|
+
JSON.stringify({
|
|
71
|
+
type: "resume",
|
|
72
|
+
lastSeenEventId: this._lastSeenEventId
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
on(type, listener) {
|
|
78
|
+
let set = this.listeners.get(type);
|
|
79
|
+
if (!set) {
|
|
80
|
+
set = /* @__PURE__ */ new Set();
|
|
81
|
+
this.listeners.set(type, set);
|
|
82
|
+
}
|
|
83
|
+
set.add(listener);
|
|
84
|
+
const buffered = this.buffered.get(type);
|
|
85
|
+
if (buffered) {
|
|
86
|
+
this.buffered.delete(type);
|
|
87
|
+
for (const event of buffered) {
|
|
88
|
+
listener(event);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return () => {
|
|
92
|
+
set.delete(listener);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
onClose(handler) {
|
|
96
|
+
this.onCloseHandlers.add(handler);
|
|
97
|
+
}
|
|
98
|
+
send(message) {
|
|
99
|
+
if (this.terminal) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`SandboxChannel: cannot send ${message.type} \u2014 channel is closed.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
this.rawSend(JSON.stringify(message));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Mark that the host is tearing the session down. The next socket close is
|
|
108
|
+
* then treated as terminal rather than triggering a reconnect. Call before
|
|
109
|
+
* sending a `shutdown` / `detach` message whose ack the bridge follows with a
|
|
110
|
+
* socket close.
|
|
111
|
+
*/
|
|
112
|
+
beginClose() {
|
|
113
|
+
this.closing = true;
|
|
114
|
+
}
|
|
115
|
+
close() {
|
|
116
|
+
var _a;
|
|
117
|
+
if (this.terminal) return;
|
|
118
|
+
this.closing = true;
|
|
119
|
+
try {
|
|
120
|
+
(_a = this.ws) == null ? void 0 : _a.close();
|
|
121
|
+
} catch (e) {
|
|
122
|
+
}
|
|
123
|
+
this.enqueue(() => this.finalizeClose(1e3, "closed"));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Gracefully suspend at a slice boundary: stop processing inbound frames
|
|
127
|
+
* (so the cursor freezes at the last delivered event), drain any frames
|
|
128
|
+
* already queued for dispatch, then close the socket and finalise with the
|
|
129
|
+
* close reason `'suspended'`. Resolves with the final `lastSeenEventId`.
|
|
130
|
+
*
|
|
131
|
+
* The bridge keeps the in-flight turn running (a host socket close never
|
|
132
|
+
* aborts it) and accumulates events past the cursor for the next process to
|
|
133
|
+
* `resume`. Unlike {@link close}, the consumer's active turn is wound down
|
|
134
|
+
* cleanly — adapters distinguish a suspend from an unexpected drop via the
|
|
135
|
+
* `'suspended'` close reason and resolve `done` successfully.
|
|
136
|
+
*/
|
|
137
|
+
suspend() {
|
|
138
|
+
return new Promise((resolve) => {
|
|
139
|
+
if (this.terminal) {
|
|
140
|
+
resolve(this._lastSeenEventId);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.suspended = true;
|
|
144
|
+
this.closing = true;
|
|
145
|
+
this.onClose(() => resolve(this._lastSeenEventId));
|
|
146
|
+
this.enqueue(() => {
|
|
147
|
+
var _a;
|
|
148
|
+
try {
|
|
149
|
+
(_a = this.ws) == null ? void 0 : _a.close();
|
|
150
|
+
} catch (e) {
|
|
151
|
+
}
|
|
152
|
+
this.finalizeClose(1e3, "suspended");
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
isClosed() {
|
|
157
|
+
return this.terminal;
|
|
158
|
+
}
|
|
159
|
+
// ─── internals ──────────────────────────────────────────────────────
|
|
160
|
+
wire(ws) {
|
|
161
|
+
let dropped = false;
|
|
162
|
+
const onDrop = (code, reason) => {
|
|
163
|
+
if (dropped) return;
|
|
164
|
+
dropped = true;
|
|
165
|
+
if (ws !== this.ws) return;
|
|
166
|
+
this.connected = false;
|
|
167
|
+
if (this.closing) {
|
|
168
|
+
this.enqueue(() => this.finalizeClose(code, reason));
|
|
169
|
+
} else {
|
|
170
|
+
this.enqueue(() => this.reconnectLoop());
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
ws.on("message", (raw) => {
|
|
174
|
+
if (this.suspended) return;
|
|
175
|
+
const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
|
|
176
|
+
this.enqueue(() => this.handleIncoming(text));
|
|
177
|
+
});
|
|
178
|
+
ws.on(
|
|
179
|
+
"close",
|
|
180
|
+
(code, reason) => {
|
|
181
|
+
var _a, _b;
|
|
182
|
+
return onDrop(code, (_b = (_a = reason == null ? void 0 : reason.toString) == null ? void 0 : _a.call(reason, "utf8")) != null ? _b : "");
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
ws.on("error", () => onDrop(1006, "socket error"));
|
|
186
|
+
}
|
|
187
|
+
async reconnectLoop() {
|
|
188
|
+
var _a, _b, _c;
|
|
189
|
+
if (this.terminal || this.closing) return;
|
|
190
|
+
const start = Date.now();
|
|
191
|
+
let attempt = 0;
|
|
192
|
+
let delay = this.initialDelayMs;
|
|
193
|
+
while (!this.terminal && !this.closing) {
|
|
194
|
+
attempt++;
|
|
195
|
+
(_a = this.onDebug) == null ? void 0 : _a.call(this, {
|
|
196
|
+
event: "reconnect-attempt",
|
|
197
|
+
attempt,
|
|
198
|
+
lastSeenEventId: this._lastSeenEventId
|
|
199
|
+
});
|
|
200
|
+
try {
|
|
201
|
+
const ws = await this.connectThunk();
|
|
202
|
+
if (this.terminal || this.closing) {
|
|
203
|
+
try {
|
|
204
|
+
ws.close();
|
|
205
|
+
} catch (e) {
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this.wire(ws);
|
|
210
|
+
this.ws = ws;
|
|
211
|
+
this.connected = true;
|
|
212
|
+
this.rawSend(
|
|
213
|
+
JSON.stringify({
|
|
214
|
+
type: "resume",
|
|
215
|
+
lastSeenEventId: this._lastSeenEventId
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
this.flushPending();
|
|
219
|
+
(_b = this.onDebug) == null ? void 0 : _b.call(this, {
|
|
220
|
+
event: "reconnected",
|
|
221
|
+
attempt,
|
|
222
|
+
lastSeenEventId: this._lastSeenEventId
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
} catch (cause) {
|
|
226
|
+
if (Date.now() - start >= this.maxElapsedMs) {
|
|
227
|
+
this.finalizeClose(1006, "reconnect failed");
|
|
228
|
+
(_c = this.onDebug) == null ? void 0 : _c.call(this, {
|
|
229
|
+
event: "reconnect-failed",
|
|
230
|
+
attempts: attempt,
|
|
231
|
+
lastSeenEventId: this._lastSeenEventId,
|
|
232
|
+
cause
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
await sleep(delay);
|
|
237
|
+
delay = Math.min(delay * 1.5, this.maxDelayMs);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
rawSend(text) {
|
|
242
|
+
if (this.connected && this.ws) {
|
|
243
|
+
this.ws.send(text);
|
|
244
|
+
} else {
|
|
245
|
+
this.pendingSends.push(text);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
flushPending() {
|
|
249
|
+
if (!this.connected || !this.ws) return;
|
|
250
|
+
const queued = this.pendingSends.splice(0);
|
|
251
|
+
for (const text of queued) this.ws.send(text);
|
|
252
|
+
}
|
|
253
|
+
enqueue(work) {
|
|
254
|
+
this.dispatchChain = this.dispatchChain.then(work);
|
|
255
|
+
}
|
|
256
|
+
async handleIncoming(text) {
|
|
257
|
+
const raw = await safeParseJSON({ text });
|
|
258
|
+
let seq;
|
|
259
|
+
if (raw.success && raw.value != null && typeof raw.value === "object" && typeof raw.value.seq === "number") {
|
|
260
|
+
seq = raw.value.seq;
|
|
261
|
+
}
|
|
262
|
+
const validated = await safeValidateTypes({
|
|
263
|
+
value: raw.success ? raw.value : void 0,
|
|
264
|
+
schema: this.outboundSchema
|
|
265
|
+
});
|
|
266
|
+
if (validated.success) {
|
|
267
|
+
this.dispatch(validated.value);
|
|
268
|
+
} else {
|
|
269
|
+
this.dispatch({
|
|
270
|
+
type: "error",
|
|
271
|
+
error: validated.error
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (seq !== void 0 && seq > this._lastSeenEventId) {
|
|
275
|
+
this._lastSeenEventId = seq;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
dispatch(message) {
|
|
279
|
+
var _a;
|
|
280
|
+
if (message.type === "sandbox-log" || message.type === "debug-event") {
|
|
281
|
+
(_a = this.onDiagnostic) == null ? void 0 : _a.call(
|
|
282
|
+
this,
|
|
283
|
+
message
|
|
284
|
+
);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const type = message.type;
|
|
288
|
+
const set = this.listeners.get(type);
|
|
289
|
+
if (!set || set.size === 0) {
|
|
290
|
+
let bucket = this.buffered.get(type);
|
|
291
|
+
if (!bucket) {
|
|
292
|
+
bucket = [];
|
|
293
|
+
this.buffered.set(type, bucket);
|
|
294
|
+
}
|
|
295
|
+
bucket.push(message);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
for (const listener of set) {
|
|
299
|
+
listener(message);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
finalizeClose(code, reason) {
|
|
303
|
+
if (this.terminal) return;
|
|
304
|
+
this.terminal = true;
|
|
305
|
+
this.connected = false;
|
|
306
|
+
for (const h of this.onCloseHandlers) h(code, reason);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/utils/classify-disk-log.ts
|
|
311
|
+
import { safeParseJSON as safeParseJSON2 } from "@ai-sdk/provider-utils";
|
|
312
|
+
async function classifyDiskLog(eventLog) {
|
|
313
|
+
if (!eventLog) return "rerun";
|
|
314
|
+
const lines = eventLog.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
315
|
+
const lastLine = lines.at(-1);
|
|
316
|
+
if (lastLine == null) return "rerun";
|
|
317
|
+
const parsed = await safeParseJSON2({ text: lastLine });
|
|
318
|
+
if (parsed.success && parsed.value != null && typeof parsed.value === "object" && parsed.value.type === "finish") {
|
|
319
|
+
return "replay";
|
|
320
|
+
}
|
|
321
|
+
return "rerun";
|
|
322
|
+
}
|
|
323
|
+
export {
|
|
324
|
+
SandboxChannel,
|
|
325
|
+
classifyDiskLog
|
|
326
|
+
};
|
|
327
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/sandbox-channel.ts","../../src/utils/classify-disk-log.ts"],"sourcesContent":["import {\n safeParseJSON,\n safeValidateTypes,\n type FlexibleSchema,\n} from '@ai-sdk/provider-utils';\nimport type { WebSocket } from 'ws';\n\n/**\n * Diagnostic event surfaced by {@link SandboxChannel} during its connection\n * lifecycle. Silent unless a consumer wires `onDebug`. Reconnects are otherwise\n * invisible — the channel reconnects transparently and the in-flight turn keeps\n * streaming.\n */\nexport type SandboxChannelDebugEvent =\n | { event: 'reconnect-attempt'; attempt: number; lastSeenEventId: number }\n | { event: 'reconnected'; attempt: number; lastSeenEventId: number }\n | {\n event: 'reconnect-failed';\n attempts: number;\n lastSeenEventId: number;\n cause: unknown;\n };\n\nexport interface SandboxChannelReconnectOptions {\n /** Give up reconnecting after this many milliseconds. Default 30_000. */\n readonly maxElapsedMs?: number;\n /** First backoff delay. Default 50. */\n readonly initialDelayMs?: number;\n /** Backoff ceiling. Default 2_000. */\n readonly maxDelayMs?: number;\n}\n\nexport interface SandboxChannelOptions<TOut> {\n /**\n * Open a fresh WebSocket to the bridge and resolve once it is ready to carry\n * frames (i.e. after any adapter-specific handshake such as Claude Code's\n * `bridge-hello`). Called once by {@link SandboxChannel.open} and again on\n * every transient reconnect. Must reject if the connection cannot be\n * established.\n */\n connect: () => Promise<WebSocket>;\n\n /** Schema validating inbound (bridge → host) frames. */\n outboundSchema: FlexibleSchema<TOut>;\n\n reconnect?: SandboxChannelReconnectOptions;\n\n onDebug?: (event: SandboxChannelDebugEvent) => void;\n\n /**\n * Sink for forwarded bridge diagnostics — `sandbox-log` (captured\n * console lines) and `debug-event` (structured) frames. When set, these\n * frame types are routed here instead of the per-type listener dispatch, so\n * they never reach the consumer's stream. Typed to the diagnostic members of\n * `TOut`, so it is a no-op union for channels whose protocol has none.\n */\n onDiagnostic?: (\n event: Extract<TOut, { type: 'sandbox-log' | 'debug-event' }>,\n ) => void;\n\n /**\n * Seed the host-side cursor before the first connect. Pass the\n * `lastSeenEventId` persisted from a prior process so the bridge replays only\n * events past it when this channel opens with `{ resume: true }` — the\n * cross-process attach handshake. Defaults to `0` (fresh session).\n */\n initialLastSeenEventId?: number;\n}\n\ntype EventTypeOf<TOut extends { type: string }> = TOut['type'];\n\ntype Listener<TOut extends { type: string }, T extends EventTypeOf<TOut>> = (\n event: Extract<TOut, { type: T }>,\n) => void;\n\nconst sleep = (ms: number): Promise<void> =>\n new Promise(resolve => {\n const t = setTimeout(resolve, ms);\n (t as { unref?: () => void }).unref?.();\n });\n\n/**\n * Host-side typed wrapper around the bridge WebSocket connection.\n *\n * Buffers inbound messages until a listener for their type is registered, so\n * callers that subscribe asynchronously do not miss early frames. Inbound\n * dispatch is serialised through a promise chain so a `close` event that\n * arrives on the same microtask as the final `finish` message does not fire\n * close handlers until the message has been dispatched.\n *\n * Survives transient disconnects. The bridge keeps running and\n * accumulates events in an in-memory log keyed by a monotonic `seq`; on an\n * unexpected socket drop this channel re-invokes `connect`, re-wires the new\n * socket, and asks the bridge to replay everything past `lastSeenEventId`. The\n * in-flight turn never observes the blip — `onClose` fires only after a\n * host-initiated close or once the reconnect budget is exhausted.\n */\nexport class SandboxChannel<\n TOut extends { type: string },\n TIn extends { type: string } = { type: string },\n> {\n private readonly listeners = new Map<\n EventTypeOf<TOut>,\n Set<Listener<TOut, EventTypeOf<TOut>>>\n >();\n private readonly buffered = new Map<EventTypeOf<TOut>, TOut[]>();\n private readonly onCloseHandlers = new Set<\n (code: number, reason: string) => void\n >();\n\n private readonly connectThunk: () => Promise<WebSocket>;\n private readonly outboundSchema: FlexibleSchema<TOut>;\n private readonly onDebug:\n | ((event: SandboxChannelDebugEvent) => void)\n | undefined;\n private readonly onDiagnostic:\n | ((event: Extract<TOut, { type: 'sandbox-log' | 'debug-event' }>) => void)\n | undefined;\n private readonly maxElapsedMs: number;\n private readonly initialDelayMs: number;\n private readonly maxDelayMs: number;\n\n private ws: WebSocket | undefined;\n private connected = false;\n /** Host has begun teardown; suppresses reconnect so a bridge-side close finalises. */\n private closing = false;\n /**\n * Host has gracefully suspended (slice boundary). Inbound frames are ignored\n * from this point so the cursor stops advancing exactly at the last delivered\n * event — the bridge keeps the turn running and the not-yet-delivered tail is\n * replayed to the next process on `resume`.\n */\n private suspended = false;\n /** Channel is fully torn down; `send` throws and `onClose` has fired. */\n private terminal = false;\n private _lastSeenEventId = 0;\n private readonly pendingSends: string[] = [];\n private dispatchChain: Promise<void> = Promise.resolve();\n\n constructor(options: SandboxChannelOptions<TOut>) {\n this.connectThunk = options.connect;\n this.outboundSchema = options.outboundSchema;\n this.onDebug = options.onDebug;\n this.onDiagnostic = options.onDiagnostic;\n this.maxElapsedMs = options.reconnect?.maxElapsedMs ?? 30_000;\n this.initialDelayMs = options.reconnect?.initialDelayMs ?? 50;\n this.maxDelayMs = options.reconnect?.maxDelayMs ?? 2_000;\n this._lastSeenEventId = options.initialLastSeenEventId ?? 0;\n }\n\n /**\n * Highest bridge event `seq` this channel has observed. Persist it (e.g. via\n * the adapter's resume handle) so a future process can seed\n * {@link SandboxChannelOptions.initialLastSeenEventId} and attach.\n */\n get lastSeenEventId(): number {\n return this._lastSeenEventId;\n }\n\n /**\n * Establish the initial connection. A single attempt — startup failures\n * reject so the caller can fail `doStart` cleanly. Reconnect retries apply\n * only to drops after a successful open.\n *\n * Pass `{ resume: true }` to attach to a bridge that is already mid-session:\n * after the socket opens, the channel sends `{ type: 'resume', lastSeenEventId }`\n * so the bridge replays everything past the seeded cursor. This is the\n * cross-process attach handshake — identical to what a transient reconnect\n * does, but triggered by the initial open from a new process.\n */\n async open(opts?: { resume?: boolean }): Promise<void> {\n if (this.terminal) {\n throw new Error('SandboxChannel: cannot open a closed channel.');\n }\n const ws = await this.connectThunk();\n this.wire(ws);\n this.ws = ws;\n this.connected = true;\n if (opts?.resume) {\n this.rawSend(\n JSON.stringify({\n type: 'resume',\n lastSeenEventId: this._lastSeenEventId,\n }),\n );\n }\n }\n\n on<T extends EventTypeOf<TOut>>(\n type: T,\n listener: Listener<TOut, T>,\n ): () => void {\n let set = this.listeners.get(type);\n if (!set) {\n set = new Set();\n this.listeners.set(type, set);\n }\n set.add(listener as unknown as Listener<TOut, EventTypeOf<TOut>>);\n\n const buffered = this.buffered.get(type);\n if (buffered) {\n this.buffered.delete(type);\n for (const event of buffered) {\n listener(event as Extract<TOut, { type: T }>);\n }\n }\n\n return () => {\n set!.delete(listener as unknown as Listener<TOut, EventTypeOf<TOut>>);\n };\n }\n\n onClose(handler: (code: number, reason: string) => void): void {\n this.onCloseHandlers.add(handler);\n }\n\n send(message: TIn): void {\n if (this.terminal) {\n throw new Error(\n `SandboxChannel: cannot send ${message.type} — channel is closed.`,\n );\n }\n this.rawSend(JSON.stringify(message));\n }\n\n /**\n * Mark that the host is tearing the session down. The next socket close is\n * then treated as terminal rather than triggering a reconnect. Call before\n * sending a `shutdown` / `detach` message whose ack the bridge follows with a\n * socket close.\n */\n beginClose(): void {\n this.closing = true;\n }\n\n close(): void {\n if (this.terminal) return;\n this.closing = true;\n try {\n this.ws?.close();\n } catch {\n // best-effort\n }\n this.enqueue(() => this.finalizeClose(1000, 'closed'));\n }\n\n /**\n * Gracefully suspend at a slice boundary: stop processing inbound frames\n * (so the cursor freezes at the last delivered event), drain any frames\n * already queued for dispatch, then close the socket and finalise with the\n * close reason `'suspended'`. Resolves with the final `lastSeenEventId`.\n *\n * The bridge keeps the in-flight turn running (a host socket close never\n * aborts it) and accumulates events past the cursor for the next process to\n * `resume`. Unlike {@link close}, the consumer's active turn is wound down\n * cleanly — adapters distinguish a suspend from an unexpected drop via the\n * `'suspended'` close reason and resolve `done` successfully.\n */\n suspend(): Promise<number> {\n return new Promise<number>(resolve => {\n if (this.terminal) {\n resolve(this._lastSeenEventId);\n return;\n }\n // Stop counting/dispatching further inbound frames immediately, and\n // suppress reconnect so the socket close finalises.\n this.suspended = true;\n this.closing = true;\n this.onClose(() => resolve(this._lastSeenEventId));\n // Queue the close behind any already-dispatched frames so everything\n // delivered to the consumer is reflected in the final cursor.\n this.enqueue(() => {\n try {\n this.ws?.close();\n } catch {\n // best-effort\n }\n this.finalizeClose(1000, 'suspended');\n });\n });\n }\n\n isClosed(): boolean {\n return this.terminal;\n }\n\n // ─── internals ──────────────────────────────────────────────────────\n\n private wire(ws: WebSocket): void {\n let dropped = false;\n const onDrop = (code: number, reason: string) => {\n if (dropped) return;\n dropped = true;\n if (ws !== this.ws) return;\n this.connected = false;\n if (this.closing) {\n this.enqueue(() => this.finalizeClose(code, reason));\n } else {\n this.enqueue(() => this.reconnectLoop());\n }\n };\n\n ws.on('message', (raw: ArrayBufferLike | string) => {\n // Once suspended, ignore further frames so the cursor freezes exactly at\n // the last delivered event (the bridge replays the rest to the next slice).\n if (this.suspended) return;\n const text =\n typeof raw === 'string' ? raw : Buffer.from(raw).toString('utf8');\n this.enqueue(() => this.handleIncoming(text));\n });\n ws.on('close', (code: number, reason: Buffer) =>\n onDrop(code, reason?.toString?.('utf8') ?? ''),\n );\n ws.on('error', () => onDrop(1006, 'socket error'));\n }\n\n private async reconnectLoop(): Promise<void> {\n if (this.terminal || this.closing) return;\n const start = Date.now();\n let attempt = 0;\n let delay = this.initialDelayMs;\n while (!this.terminal && !this.closing) {\n attempt++;\n this.onDebug?.({\n event: 'reconnect-attempt',\n attempt,\n lastSeenEventId: this._lastSeenEventId,\n });\n try {\n const ws = await this.connectThunk();\n if (this.terminal || this.closing) {\n try {\n ws.close();\n } catch {\n // best-effort\n }\n return;\n }\n this.wire(ws);\n this.ws = ws;\n this.connected = true;\n // Ask the bridge to replay everything we have not seen, then flush any\n // host → bridge frames produced while we were disconnected.\n this.rawSend(\n JSON.stringify({\n type: 'resume',\n lastSeenEventId: this._lastSeenEventId,\n }),\n );\n this.flushPending();\n this.onDebug?.({\n event: 'reconnected',\n attempt,\n lastSeenEventId: this._lastSeenEventId,\n });\n return;\n } catch (cause) {\n if (Date.now() - start >= this.maxElapsedMs) {\n this.finalizeClose(1006, 'reconnect failed');\n this.onDebug?.({\n event: 'reconnect-failed',\n attempts: attempt,\n lastSeenEventId: this._lastSeenEventId,\n cause,\n });\n return;\n }\n await sleep(delay);\n delay = Math.min(delay * 1.5, this.maxDelayMs);\n }\n }\n }\n\n private rawSend(text: string): void {\n if (this.connected && this.ws) {\n this.ws.send(text);\n } else {\n this.pendingSends.push(text);\n }\n }\n\n private flushPending(): void {\n if (!this.connected || !this.ws) return;\n const queued = this.pendingSends.splice(0);\n for (const text of queued) this.ws.send(text);\n }\n\n private enqueue(work: () => void | Promise<void>): void {\n this.dispatchChain = this.dispatchChain.then(work);\n }\n\n private async handleIncoming(text: string): Promise<void> {\n const raw = await safeParseJSON({ text });\n let seq: number | undefined;\n if (\n raw.success &&\n raw.value != null &&\n typeof raw.value === 'object' &&\n typeof (raw.value as { seq?: unknown }).seq === 'number'\n ) {\n seq = (raw.value as { seq: number }).seq;\n }\n\n const validated = await safeValidateTypes({\n value: raw.success ? raw.value : undefined,\n schema: this.outboundSchema,\n });\n if (validated.success) {\n this.dispatch(validated.value);\n } else {\n this.dispatch({\n type: 'error',\n error: validated.error,\n } as unknown as TOut);\n }\n\n if (seq !== undefined && seq > this._lastSeenEventId) {\n this._lastSeenEventId = seq;\n }\n }\n\n private dispatch(message: TOut): void {\n // Diagnostics are routed to their own sink and never enter the\n // per-type listener/buffer path, so they stay off the consumer stream.\n if (message.type === 'sandbox-log' || message.type === 'debug-event') {\n this.onDiagnostic?.(\n message as Extract<TOut, { type: 'sandbox-log' | 'debug-event' }>,\n );\n return;\n }\n const type = message.type as EventTypeOf<TOut>;\n const set = this.listeners.get(type);\n if (!set || set.size === 0) {\n let bucket = this.buffered.get(type);\n if (!bucket) {\n bucket = [];\n this.buffered.set(type, bucket);\n }\n bucket.push(message);\n return;\n }\n for (const listener of set) {\n listener(message as Extract<TOut, { type: EventTypeOf<TOut> }>);\n }\n }\n\n private finalizeClose(code: number, reason: string): void {\n if (this.terminal) return;\n this.terminal = true;\n this.connected = false;\n for (const h of this.onCloseHandlers) h(code, reason);\n }\n}\n","import { safeParseJSON } from '@ai-sdk/provider-utils';\n\n/**\n * Recovery rung selected from an on-disk bridge event log when attach is not\n * possible (the bridge process is gone): `'replay'` when the log holds a\n * complete turn the host can resume from its cursor, `'rerun'` otherwise.\n */\nexport type DiskLogRecoveryMode = 'replay' | 'rerun';\n\n/**\n * Decide whether a respawned bridge can `replay` a turn from its persisted\n * `event-log.ndjson`, or must `rerun` it from scratch.\n *\n * A turn is replayable only when its log ends in a terminal `finish` event —\n * a log that is missing, empty, or ends mid-turn means the bridge died before\n * completing the turn, so there is no coherent tail to deliver and the runtime\n * must re-run it (continuing its own thread from the sandbox snapshot).\n *\n * @param eventLog Raw contents of `event-log.ndjson` (newline-delimited JSON),\n * or `null`/`undefined`/empty when the file is absent.\n */\nexport async function classifyDiskLog(\n eventLog: string | null | undefined,\n): Promise<DiskLogRecoveryMode> {\n if (!eventLog) return 'rerun';\n const lines = eventLog\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n const lastLine = lines.at(-1);\n if (lastLine == null) return 'rerun';\n\n const parsed = await safeParseJSON({ text: lastLine });\n if (\n parsed.success &&\n parsed.value != null &&\n typeof parsed.value === 'object' &&\n (parsed.value as { type?: unknown }).type === 'finish'\n ) {\n return 'replay';\n }\n return 'rerun';\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAuEP,IAAM,QAAQ,CAAC,OACb,IAAI,QAAQ,aAAW;AA5EzB;AA6EI,QAAM,IAAI,WAAW,SAAS,EAAE;AAChC,GAAC,OAA6B,UAA7B;AACH,CAAC;AAkBI,IAAM,iBAAN,MAGL;AAAA,EAuCA,YAAY,SAAsC;AAtClD,SAAiB,YAAY,oBAAI,IAG/B;AACF,SAAiB,WAAW,oBAAI,IAA+B;AAC/D,SAAiB,kBAAkB,oBAAI,IAErC;AAeF,SAAQ,YAAY;AAEpB;AAAA,SAAQ,UAAU;AAOlB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,YAAY;AAEpB;AAAA,SAAQ,WAAW;AACnB,SAAQ,mBAAmB;AAC3B,SAAiB,eAAyB,CAAC;AAC3C,SAAQ,gBAA+B,QAAQ,QAAQ;AAzIzD;AA4II,SAAK,eAAe,QAAQ;AAC5B,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,UAAU,QAAQ;AACvB,SAAK,eAAe,QAAQ;AAC5B,SAAK,gBAAe,mBAAQ,cAAR,mBAAmB,iBAAnB,YAAmC;AACvD,SAAK,kBAAiB,mBAAQ,cAAR,mBAAmB,mBAAnB,YAAqC;AAC3D,SAAK,cAAa,mBAAQ,cAAR,mBAAmB,eAAnB,YAAiC;AACnD,SAAK,oBAAmB,aAAQ,2BAAR,YAAkC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,kBAA0B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,KAAK,MAA4C;AACrD,QAAI,KAAK,UAAU;AACjB,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AACA,UAAM,KAAK,MAAM,KAAK,aAAa;AACnC,SAAK,KAAK,EAAE;AACZ,SAAK,KAAK;AACV,SAAK,YAAY;AACjB,QAAI,6BAAM,QAAQ;AAChB,WAAK;AAAA,QACH,KAAK,UAAU;AAAA,UACb,MAAM;AAAA,UACN,iBAAiB,KAAK;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,GACE,MACA,UACY;AACZ,QAAI,MAAM,KAAK,UAAU,IAAI,IAAI;AACjC,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,WAAK,UAAU,IAAI,MAAM,GAAG;AAAA,IAC9B;AACA,QAAI,IAAI,QAAwD;AAEhE,UAAM,WAAW,KAAK,SAAS,IAAI,IAAI;AACvC,QAAI,UAAU;AACZ,WAAK,SAAS,OAAO,IAAI;AACzB,iBAAW,SAAS,UAAU;AAC5B,iBAAS,KAAmC;AAAA,MAC9C;AAAA,IACF;AAEA,WAAO,MAAM;AACX,UAAK,OAAO,QAAwD;AAAA,IACtE;AAAA,EACF;AAAA,EAEA,QAAQ,SAAuD;AAC7D,SAAK,gBAAgB,IAAI,OAAO;AAAA,EAClC;AAAA,EAEA,KAAK,SAAoB;AACvB,QAAI,KAAK,UAAU;AACjB,YAAM,IAAI;AAAA,QACR,+BAA+B,QAAQ,IAAI;AAAA,MAC7C;AAAA,IACF;AACA,SAAK,QAAQ,KAAK,UAAU,OAAO,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAmB;AACjB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,QAAc;AA3OhB;AA4OI,QAAI,KAAK,SAAU;AACnB,SAAK,UAAU;AACf,QAAI;AACF,iBAAK,OAAL,mBAAS;AAAA,IACX,SAAQ;AAAA,IAER;AACA,SAAK,QAAQ,MAAM,KAAK,cAAc,KAAM,QAAQ,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,UAA2B;AACzB,WAAO,IAAI,QAAgB,aAAW;AACpC,UAAI,KAAK,UAAU;AACjB,gBAAQ,KAAK,gBAAgB;AAC7B;AAAA,MACF;AAGA,WAAK,YAAY;AACjB,WAAK,UAAU;AACf,WAAK,QAAQ,MAAM,QAAQ,KAAK,gBAAgB,CAAC;AAGjD,WAAK,QAAQ,MAAM;AA/QzB;AAgRQ,YAAI;AACF,qBAAK,OAAL,mBAAS;AAAA,QACX,SAAQ;AAAA,QAER;AACA,aAAK,cAAc,KAAM,WAAW;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAIQ,KAAK,IAAqB;AAChC,QAAI,UAAU;AACd,UAAM,SAAS,CAAC,MAAc,WAAmB;AAC/C,UAAI,QAAS;AACb,gBAAU;AACV,UAAI,OAAO,KAAK,GAAI;AACpB,WAAK,YAAY;AACjB,UAAI,KAAK,SAAS;AAChB,aAAK,QAAQ,MAAM,KAAK,cAAc,MAAM,MAAM,CAAC;AAAA,MACrD,OAAO;AACL,aAAK,QAAQ,MAAM,KAAK,cAAc,CAAC;AAAA,MACzC;AAAA,IACF;AAEA,OAAG,GAAG,WAAW,CAAC,QAAkC;AAGlD,UAAI,KAAK,UAAW;AACpB,YAAM,OACJ,OAAO,QAAQ,WAAW,MAAM,OAAO,KAAK,GAAG,EAAE,SAAS,MAAM;AAClE,WAAK,QAAQ,MAAM,KAAK,eAAe,IAAI,CAAC;AAAA,IAC9C,CAAC;AACD,OAAG;AAAA,MAAG;AAAA,MAAS,CAAC,MAAc,WAAgB;AAtTlD;AAuTM,sBAAO,OAAM,4CAAQ,aAAR,gCAAmB,YAAnB,YAA8B,EAAE;AAAA;AAAA,IAC/C;AACA,OAAG,GAAG,SAAS,MAAM,OAAO,MAAM,cAAc,CAAC;AAAA,EACnD;AAAA,EAEA,MAAc,gBAA+B;AA5T/C;AA6TI,QAAI,KAAK,YAAY,KAAK,QAAS;AACnC,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI,UAAU;AACd,QAAI,QAAQ,KAAK;AACjB,WAAO,CAAC,KAAK,YAAY,CAAC,KAAK,SAAS;AACtC;AACA,iBAAK,YAAL,8BAAe;AAAA,QACb,OAAO;AAAA,QACP;AAAA,QACA,iBAAiB,KAAK;AAAA,MACxB;AACA,UAAI;AACF,cAAM,KAAK,MAAM,KAAK,aAAa;AACnC,YAAI,KAAK,YAAY,KAAK,SAAS;AACjC,cAAI;AACF,eAAG,MAAM;AAAA,UACX,SAAQ;AAAA,UAER;AACA;AAAA,QACF;AACA,aAAK,KAAK,EAAE;AACZ,aAAK,KAAK;AACV,aAAK,YAAY;AAGjB,aAAK;AAAA,UACH,KAAK,UAAU;AAAA,YACb,MAAM;AAAA,YACN,iBAAiB,KAAK;AAAA,UACxB,CAAC;AAAA,QACH;AACA,aAAK,aAAa;AAClB,mBAAK,YAAL,8BAAe;AAAA,UACb,OAAO;AAAA,UACP;AAAA,UACA,iBAAiB,KAAK;AAAA,QACxB;AACA;AAAA,MACF,SAAS,OAAO;AACd,YAAI,KAAK,IAAI,IAAI,SAAS,KAAK,cAAc;AAC3C,eAAK,cAAc,MAAM,kBAAkB;AAC3C,qBAAK,YAAL,8BAAe;AAAA,YACb,OAAO;AAAA,YACP,UAAU;AAAA,YACV,iBAAiB,KAAK;AAAA,YACtB;AAAA,UACF;AACA;AAAA,QACF;AACA,cAAM,MAAM,KAAK;AACjB,gBAAQ,KAAK,IAAI,QAAQ,KAAK,KAAK,UAAU;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,QAAQ,MAAoB;AAClC,QAAI,KAAK,aAAa,KAAK,IAAI;AAC7B,WAAK,GAAG,KAAK,IAAI;AAAA,IACnB,OAAO;AACL,WAAK,aAAa,KAAK,IAAI;AAAA,IAC7B;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,GAAI;AACjC,UAAM,SAAS,KAAK,aAAa,OAAO,CAAC;AACzC,eAAW,QAAQ,OAAQ,MAAK,GAAG,KAAK,IAAI;AAAA,EAC9C;AAAA,EAEQ,QAAQ,MAAwC;AACtD,SAAK,gBAAgB,KAAK,cAAc,KAAK,IAAI;AAAA,EACnD;AAAA,EAEA,MAAc,eAAe,MAA6B;AACxD,UAAM,MAAM,MAAM,cAAc,EAAE,KAAK,CAAC;AACxC,QAAI;AACJ,QACE,IAAI,WACJ,IAAI,SAAS,QACb,OAAO,IAAI,UAAU,YACrB,OAAQ,IAAI,MAA4B,QAAQ,UAChD;AACA,YAAO,IAAI,MAA0B;AAAA,IACvC;AAEA,UAAM,YAAY,MAAM,kBAAkB;AAAA,MACxC,OAAO,IAAI,UAAU,IAAI,QAAQ;AAAA,MACjC,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,QAAI,UAAU,SAAS;AACrB,WAAK,SAAS,UAAU,KAAK;AAAA,IAC/B,OAAO;AACL,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,OAAO,UAAU;AAAA,MACnB,CAAoB;AAAA,IACtB;AAEA,QAAI,QAAQ,UAAa,MAAM,KAAK,kBAAkB;AACpD,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AAAA,EAEQ,SAAS,SAAqB;AAraxC;AAwaI,QAAI,QAAQ,SAAS,iBAAiB,QAAQ,SAAS,eAAe;AACpE,iBAAK,iBAAL;AAAA;AAAA,QACE;AAAA;AAEF;AAAA,IACF;AACA,UAAM,OAAO,QAAQ;AACrB,UAAM,MAAM,KAAK,UAAU,IAAI,IAAI;AACnC,QAAI,CAAC,OAAO,IAAI,SAAS,GAAG;AAC1B,UAAI,SAAS,KAAK,SAAS,IAAI,IAAI;AACnC,UAAI,CAAC,QAAQ;AACX,iBAAS,CAAC;AACV,aAAK,SAAS,IAAI,MAAM,MAAM;AAAA,MAChC;AACA,aAAO,KAAK,OAAO;AACnB;AAAA,IACF;AACA,eAAW,YAAY,KAAK;AAC1B,eAAS,OAAqD;AAAA,IAChE;AAAA,EACF;AAAA,EAEQ,cAAc,MAAc,QAAsB;AACxD,QAAI,KAAK,SAAU;AACnB,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,eAAW,KAAK,KAAK,gBAAiB,GAAE,MAAM,MAAM;AAAA,EACtD;AACF;;;ACpcA,SAAS,iBAAAA,sBAAqB;AAqB9B,eAAsB,gBACpB,UAC8B;AAC9B,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,QAAQ,SACX,MAAM,IAAI,EACV,IAAI,UAAQ,KAAK,KAAK,CAAC,EACvB,OAAO,OAAO;AACjB,QAAM,WAAW,MAAM,GAAG,EAAE;AAC5B,MAAI,YAAY,KAAM,QAAO;AAE7B,QAAM,SAAS,MAAMA,eAAc,EAAE,MAAM,SAAS,CAAC;AACrD,MACE,OAAO,WACP,OAAO,SAAS,QAChB,OAAO,OAAO,UAAU,YACvB,OAAO,MAA6B,SAAS,UAC9C;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":["safeParseJSON"]}
|
package/package.json
CHANGED
|
@@ -1 +1,104 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-sdk/harness",
|
|
3
|
+
"version": "1.0.0-canary.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"source": "./src/index.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/**/*",
|
|
12
|
+
"src",
|
|
13
|
+
"agent",
|
|
14
|
+
"utils",
|
|
15
|
+
"bridge",
|
|
16
|
+
"!src/**/*.test.ts",
|
|
17
|
+
"!src/**/*.test-d.ts",
|
|
18
|
+
"!src/**/__snapshots__",
|
|
19
|
+
"!src/**/__fixtures__",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"exports": {
|
|
24
|
+
"./package.json": "./package.json",
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./agent": {
|
|
31
|
+
"types": "./dist/agent/index.d.ts",
|
|
32
|
+
"import": "./dist/agent/index.js",
|
|
33
|
+
"default": "./dist/agent/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./utils": {
|
|
36
|
+
"types": "./dist/utils/index.d.ts",
|
|
37
|
+
"import": "./dist/utils/index.js",
|
|
38
|
+
"default": "./dist/utils/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./observability": {
|
|
41
|
+
"types": "./dist/observability/index.d.ts",
|
|
42
|
+
"import": "./dist/observability/index.js",
|
|
43
|
+
"default": "./dist/observability/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./bridge": {
|
|
46
|
+
"types": "./dist/bridge/index.d.ts",
|
|
47
|
+
"import": "./dist/bridge/index.js",
|
|
48
|
+
"default": "./dist/bridge/index.js"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@ai-sdk/provider": "4.0.0-canary.18",
|
|
53
|
+
"@ai-sdk/provider-utils": "5.0.0-canary.46",
|
|
54
|
+
"ai": "7.0.0-canary.169"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"ws": "^8.20.1"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"ws": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "22.19.19",
|
|
66
|
+
"@types/ws": "^8.5.13",
|
|
67
|
+
"tsup": "^8.5.1",
|
|
68
|
+
"typescript": "5.8.3",
|
|
69
|
+
"ws": "^8.20.1",
|
|
70
|
+
"zod": "3.25.76",
|
|
71
|
+
"@vercel/ai-tsconfig": "0.0.0"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=22"
|
|
75
|
+
},
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public",
|
|
78
|
+
"provenance": true
|
|
79
|
+
},
|
|
80
|
+
"homepage": "https://ai-sdk.dev/docs",
|
|
81
|
+
"repository": {
|
|
82
|
+
"type": "git",
|
|
83
|
+
"url": "https://github.com/vercel/ai",
|
|
84
|
+
"directory": "packages/harness"
|
|
85
|
+
},
|
|
86
|
+
"bugs": {
|
|
87
|
+
"url": "https://github.com/vercel/ai/issues"
|
|
88
|
+
},
|
|
89
|
+
"keywords": [
|
|
90
|
+
"ai",
|
|
91
|
+
"harness",
|
|
92
|
+
"agent"
|
|
93
|
+
],
|
|
94
|
+
"scripts": {
|
|
95
|
+
"build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
|
|
96
|
+
"build:watch": "pnpm clean && tsup --watch",
|
|
97
|
+
"clean": "del-cli dist *.tsbuildinfo",
|
|
98
|
+
"type-check": "tsc --build",
|
|
99
|
+
"test": "pnpm test:node && pnpm test:edge",
|
|
100
|
+
"test:watch": "vitest --config vitest.node.config.js",
|
|
101
|
+
"test:edge": "vitest --config vitest.edge.config.js --run",
|
|
102
|
+
"test:node": "vitest --config vitest.node.config.js --run"
|
|
103
|
+
}
|
|
104
|
+
}
|