@ai-sdk/harness 1.0.22 → 1.0.24
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 +16 -0
- package/README.md +59 -49
- package/dist/agent/index.js +63 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.d.ts +18 -0
- package/dist/bridge/index.js +69 -4
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +40 -1
- package/dist/utils/index.js +240 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
- package/src/agent/internal/run-prompt.ts +19 -0
- package/src/bridge/index.ts +86 -3
- package/src/utils/bridge-diagnostics.ts +213 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/sandbox-channel.ts +72 -0
- package/src/v1/harness-v1-bridge-protocol.ts +17 -0
package/dist/utils/index.d.ts
CHANGED
|
@@ -52,6 +52,9 @@ interface SandboxChannelOptions<TOut> {
|
|
|
52
52
|
onDiagnostic?: (event: Extract<TOut, {
|
|
53
53
|
type: 'sandbox-log' | 'debug-event';
|
|
54
54
|
}>) => void;
|
|
55
|
+
onBridgeError?: (event: Extract<TOut, {
|
|
56
|
+
type: 'error';
|
|
57
|
+
}>) => void;
|
|
55
58
|
/**
|
|
56
59
|
* Seed the host-side cursor before the first connect. Pass the
|
|
57
60
|
* `lastSeenEventId` persisted from a prior process so the bridge replays only
|
|
@@ -98,6 +101,7 @@ declare class SandboxChannel<TOut extends {
|
|
|
98
101
|
private readonly outboundSchema;
|
|
99
102
|
private readonly onDebug;
|
|
100
103
|
private readonly onDiagnostic;
|
|
104
|
+
private readonly onBridgeError;
|
|
101
105
|
private readonly maxElapsedMs;
|
|
102
106
|
private readonly initialDelayMs;
|
|
103
107
|
private readonly maxDelayMs;
|
|
@@ -149,6 +153,9 @@ declare class SandboxChannel<TOut extends {
|
|
|
149
153
|
*/
|
|
150
154
|
beginClose(): void;
|
|
151
155
|
close(): void;
|
|
156
|
+
interrupt(options?: {
|
|
157
|
+
timeoutMs?: number;
|
|
158
|
+
}): Promise<void>;
|
|
152
159
|
/**
|
|
153
160
|
* Gracefully suspend at a slice boundary: stop processing inbound frames
|
|
154
161
|
* (so the cursor freezes at the last delivered event), drain any frames
|
|
@@ -287,4 +294,36 @@ declare function markBridgeStarting({ sandbox, bridgeStateDir, bridgeType, abort
|
|
|
287
294
|
}): Promise<void>;
|
|
288
295
|
declare function waitForBridgeReady({ proc, sandbox, bridgeStateDir, bridgeType, timeoutMs, abortSignal, pollIntervalMs, createTimeoutError, createExitError, }: WaitForBridgeReadyOptions): Promise<WaitForBridgeReadyResult>;
|
|
289
296
|
|
|
290
|
-
|
|
297
|
+
type BridgeProcessStreamName = 'stdout' | 'stderr';
|
|
298
|
+
declare function formatBridgeError(error: unknown): string;
|
|
299
|
+
declare function logBridgeError({ harnessId, sessionId, context, error, write, }: {
|
|
300
|
+
harnessId: string;
|
|
301
|
+
sessionId?: string;
|
|
302
|
+
context?: string;
|
|
303
|
+
error: unknown;
|
|
304
|
+
write?: (line: string) => void;
|
|
305
|
+
}): void;
|
|
306
|
+
declare function createBridgeErrorHandler({ harnessId, sessionId, }: {
|
|
307
|
+
harnessId: string;
|
|
308
|
+
sessionId?: string;
|
|
309
|
+
}): (event: {
|
|
310
|
+
type: 'error';
|
|
311
|
+
error: unknown;
|
|
312
|
+
}) => void;
|
|
313
|
+
declare function createBridgeStartupError({ message, proc, stdoutTail, stderrTail, stderrDone, }: {
|
|
314
|
+
message: string;
|
|
315
|
+
proc: Experimental_SandboxProcess;
|
|
316
|
+
stdoutTail: string[];
|
|
317
|
+
stderrTail: string[];
|
|
318
|
+
stderrDone?: Promise<void>;
|
|
319
|
+
}): Promise<Error>;
|
|
320
|
+
declare function forwardBridgeProcessStream({ stream, streamName, source, collectTail, tailLimit, }: {
|
|
321
|
+
stream: ReadableStream<Uint8Array>;
|
|
322
|
+
streamName: BridgeProcessStreamName;
|
|
323
|
+
source?: string;
|
|
324
|
+
collectTail?: string[];
|
|
325
|
+
tailLimit?: number;
|
|
326
|
+
}): Promise<void>;
|
|
327
|
+
declare function drainBridgeProcessStream(stream: ReadableStream<Uint8Array>): Promise<void>;
|
|
328
|
+
|
|
329
|
+
export { type BridgeReadyErrorContext, type BridgeReadySource, type DiskLogRecoveryMode, SandboxChannel, type SandboxChannelDebugEvent, type SandboxChannelOptions, type SandboxChannelReconnectOptions, type SkillFilePathMode, type WaitForBridgeReadyOptions, type WaitForBridgeReadyResult, type WriteSkillsOptions, classifyDiskLog, createBridgeErrorHandler, createBridgeStartupError, drainBridgeProcessStream, formatBridgeError, forwardBridgeProcessStream, getAiGatewayAuthFromEnv, logBridgeError, markBridgeStarting, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, writeSkills };
|
package/dist/utils/index.js
CHANGED
|
@@ -33,6 +33,7 @@ var SandboxChannel = class {
|
|
|
33
33
|
this.outboundSchema = options.outboundSchema;
|
|
34
34
|
this.onDebug = options.onDebug;
|
|
35
35
|
this.onDiagnostic = options.onDiagnostic;
|
|
36
|
+
this.onBridgeError = options.onBridgeError;
|
|
36
37
|
this.maxElapsedMs = (_b = (_a = options.reconnect) == null ? void 0 : _a.maxElapsedMs) != null ? _b : 3e4;
|
|
37
38
|
this.initialDelayMs = (_d = (_c = options.reconnect) == null ? void 0 : _c.initialDelayMs) != null ? _d : 50;
|
|
38
39
|
this.maxDelayMs = (_f = (_e = options.reconnect) == null ? void 0 : _e.maxDelayMs) != null ? _f : 2e3;
|
|
@@ -122,6 +123,54 @@ var SandboxChannel = class {
|
|
|
122
123
|
}
|
|
123
124
|
this.enqueue(() => this.finalizeClose(1e3, "closed"));
|
|
124
125
|
}
|
|
126
|
+
interrupt(options) {
|
|
127
|
+
var _a;
|
|
128
|
+
const timeoutMs = (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 5e3;
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
var _a2;
|
|
131
|
+
let settled = false;
|
|
132
|
+
let unsub = () => {
|
|
133
|
+
};
|
|
134
|
+
const timer = setTimeout(() => {
|
|
135
|
+
complete(
|
|
136
|
+
new Error(
|
|
137
|
+
`SandboxChannel: interrupt was not acknowledged within ${timeoutMs}ms.`
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
(_a2 = timer.unref) == null ? void 0 : _a2.call(timer);
|
|
142
|
+
const complete = (error) => {
|
|
143
|
+
if (settled) return;
|
|
144
|
+
settled = true;
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
unsub();
|
|
147
|
+
if (error) {
|
|
148
|
+
reject(error);
|
|
149
|
+
} else {
|
|
150
|
+
resolve();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
unsub = this.on("bridge-interrupted", (event) => {
|
|
154
|
+
const response = event;
|
|
155
|
+
if (response.ok) {
|
|
156
|
+
complete();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
complete(
|
|
160
|
+
new Error(
|
|
161
|
+
`SandboxChannel: interrupt failed: ${formatControlError(
|
|
162
|
+
response.error
|
|
163
|
+
)}`
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
try {
|
|
168
|
+
this.send({ type: "interrupt" });
|
|
169
|
+
} catch (err) {
|
|
170
|
+
complete(err);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
125
174
|
/**
|
|
126
175
|
* Gracefully suspend at a slice boundary: stop processing inbound frames
|
|
127
176
|
* (so the cursor freezes at the last delivered event), drain any frames
|
|
@@ -276,7 +325,7 @@ var SandboxChannel = class {
|
|
|
276
325
|
}
|
|
277
326
|
}
|
|
278
327
|
dispatch(message) {
|
|
279
|
-
var _a;
|
|
328
|
+
var _a, _b;
|
|
280
329
|
if (message.type === "sandbox-log" || message.type === "debug-event") {
|
|
281
330
|
(_a = this.onDiagnostic) == null ? void 0 : _a.call(
|
|
282
331
|
this,
|
|
@@ -284,6 +333,9 @@ var SandboxChannel = class {
|
|
|
284
333
|
);
|
|
285
334
|
return;
|
|
286
335
|
}
|
|
336
|
+
if (message.type === "error") {
|
|
337
|
+
(_b = this.onBridgeError) == null ? void 0 : _b.call(this, message);
|
|
338
|
+
}
|
|
287
339
|
const type = message.type;
|
|
288
340
|
const set = this.listeners.get(type);
|
|
289
341
|
if (!set || set.size === 0) {
|
|
@@ -306,6 +358,15 @@ var SandboxChannel = class {
|
|
|
306
358
|
for (const h of this.onCloseHandlers) h(code, reason);
|
|
307
359
|
}
|
|
308
360
|
};
|
|
361
|
+
function formatControlError(error) {
|
|
362
|
+
if (error instanceof Error) return error.message;
|
|
363
|
+
if (error && typeof error === "object") {
|
|
364
|
+
const message = error.message;
|
|
365
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
366
|
+
}
|
|
367
|
+
if (typeof error === "string" && error.length > 0) return error;
|
|
368
|
+
return "unknown error";
|
|
369
|
+
}
|
|
309
370
|
|
|
310
371
|
// src/utils/classify-disk-log.ts
|
|
311
372
|
import { safeParseJSON as safeParseJSON2 } from "@ai-sdk/provider-utils";
|
|
@@ -707,6 +768,11 @@ var harnessV1BridgeThreadSchema = z3.object({
|
|
|
707
768
|
type: z3.literal("bridge-thread"),
|
|
708
769
|
threadId: z3.string()
|
|
709
770
|
});
|
|
771
|
+
var harnessV1BridgeInterruptedSchema = z3.object({
|
|
772
|
+
type: z3.literal("bridge-interrupted"),
|
|
773
|
+
ok: z3.boolean(),
|
|
774
|
+
error: z3.unknown().optional()
|
|
775
|
+
});
|
|
710
776
|
var harnessV1BridgeSandboxLogSchema = z3.object({
|
|
711
777
|
type: z3.literal("sandbox-log"),
|
|
712
778
|
source: z3.string(),
|
|
@@ -747,6 +813,7 @@ var harnessV1BridgeOutboundMessageSchema = z3.discriminatedUnion(
|
|
|
747
813
|
harnessV1BridgeHelloSchema,
|
|
748
814
|
harnessV1BridgeDetachSchema,
|
|
749
815
|
harnessV1BridgeThreadSchema,
|
|
816
|
+
harnessV1BridgeInterruptedSchema,
|
|
750
817
|
harnessV1BridgeSandboxLogSchema,
|
|
751
818
|
harnessV1BridgeDebugEventSchema
|
|
752
819
|
]
|
|
@@ -770,6 +837,9 @@ var harnessV1BridgeUserMessageInboundSchema = z3.object({
|
|
|
770
837
|
var harnessV1BridgeAbortInboundSchema = z3.object({
|
|
771
838
|
type: z3.literal("abort")
|
|
772
839
|
});
|
|
840
|
+
var harnessV1BridgeInterruptInboundSchema = z3.object({
|
|
841
|
+
type: z3.literal("interrupt")
|
|
842
|
+
});
|
|
773
843
|
var harnessV1BridgeShutdownInboundSchema = z3.object({
|
|
774
844
|
type: z3.literal("shutdown")
|
|
775
845
|
});
|
|
@@ -989,10 +1059,179 @@ function lineDecoder() {
|
|
|
989
1059
|
}
|
|
990
1060
|
};
|
|
991
1061
|
}
|
|
1062
|
+
|
|
1063
|
+
// src/utils/bridge-diagnostics.ts
|
|
1064
|
+
var DEFAULT_TAIL_LIMIT = 20;
|
|
1065
|
+
function isSerializedError(error) {
|
|
1066
|
+
return typeof error === "object" && error != null && typeof error.message === "string";
|
|
1067
|
+
}
|
|
1068
|
+
function stringifyUnknown(value) {
|
|
1069
|
+
if (typeof value === "string") return value;
|
|
1070
|
+
try {
|
|
1071
|
+
return JSON.stringify(value);
|
|
1072
|
+
} catch (e) {
|
|
1073
|
+
return String(value);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
function formatBridgeError(error) {
|
|
1077
|
+
var _a, _b;
|
|
1078
|
+
if (error instanceof Error) {
|
|
1079
|
+
return (_a = error.stack) != null ? _a : `${error.name}: ${error.message}`;
|
|
1080
|
+
}
|
|
1081
|
+
if (isSerializedError(error)) {
|
|
1082
|
+
return (_b = error.stack) != null ? _b : `${error.name ? `${error.name}: ` : ""}${error.message}`;
|
|
1083
|
+
}
|
|
1084
|
+
return stringifyUnknown(error);
|
|
1085
|
+
}
|
|
1086
|
+
function writeToStderr(line) {
|
|
1087
|
+
try {
|
|
1088
|
+
process.stderr.write(line);
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function logBridgeError({
|
|
1093
|
+
harnessId,
|
|
1094
|
+
sessionId,
|
|
1095
|
+
context,
|
|
1096
|
+
error,
|
|
1097
|
+
write = writeToStderr
|
|
1098
|
+
}) {
|
|
1099
|
+
const prefix = `[harness:${harnessId}:error${sessionId ? ` session=${sessionId}` : ""}]`;
|
|
1100
|
+
const message = context ? `${context}: ${formatBridgeError(error)}` : formatBridgeError(error);
|
|
1101
|
+
for (const line of message.split("\n")) {
|
|
1102
|
+
if (line.trim().length > 0) {
|
|
1103
|
+
write(`${prefix} ${line}
|
|
1104
|
+
`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function createBridgeErrorHandler({
|
|
1109
|
+
harnessId,
|
|
1110
|
+
sessionId
|
|
1111
|
+
}) {
|
|
1112
|
+
return (event) => {
|
|
1113
|
+
logBridgeError({
|
|
1114
|
+
harnessId,
|
|
1115
|
+
sessionId,
|
|
1116
|
+
context: "bridge emitted an error frame",
|
|
1117
|
+
error: event.error
|
|
1118
|
+
});
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
async function createBridgeStartupError({
|
|
1122
|
+
message,
|
|
1123
|
+
proc,
|
|
1124
|
+
stdoutTail,
|
|
1125
|
+
stderrTail,
|
|
1126
|
+
stderrDone
|
|
1127
|
+
}) {
|
|
1128
|
+
if (stderrDone) {
|
|
1129
|
+
await Promise.race([stderrDone, sleep3(250)]).catch(() => {
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
let exitStatus = "";
|
|
1133
|
+
try {
|
|
1134
|
+
const result = await Promise.race([
|
|
1135
|
+
proc.wait(),
|
|
1136
|
+
sleep3(250).then(() => void 0)
|
|
1137
|
+
]);
|
|
1138
|
+
if ((result == null ? void 0 : result.exitCode) !== void 0) {
|
|
1139
|
+
exitStatus = ` Exit code: ${result.exitCode}.`;
|
|
1140
|
+
}
|
|
1141
|
+
} catch (e) {
|
|
1142
|
+
}
|
|
1143
|
+
const details = [];
|
|
1144
|
+
if (stdoutTail.length > 0) {
|
|
1145
|
+
details.push(`stdout:
|
|
1146
|
+
${stdoutTail.join("\n")}`);
|
|
1147
|
+
}
|
|
1148
|
+
if (stderrTail.length > 0) {
|
|
1149
|
+
details.push(`stderr:
|
|
1150
|
+
${stderrTail.join("\n")}`);
|
|
1151
|
+
}
|
|
1152
|
+
return new Error(
|
|
1153
|
+
`${message}${exitStatus}${details.length > 0 ? `
|
|
1154
|
+
|
|
1155
|
+
${details.join("\n\n")}` : ""}`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
async function forwardBridgeProcessStream({
|
|
1159
|
+
stream,
|
|
1160
|
+
streamName,
|
|
1161
|
+
source = "bridge",
|
|
1162
|
+
collectTail,
|
|
1163
|
+
tailLimit = DEFAULT_TAIL_LIMIT
|
|
1164
|
+
}) {
|
|
1165
|
+
try {
|
|
1166
|
+
const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
|
|
1167
|
+
const decoder = lineDecoder2();
|
|
1168
|
+
while (true) {
|
|
1169
|
+
const { value, done } = await reader.read();
|
|
1170
|
+
const lines = done ? decoder.flush() : value ? decoder.push(value) : [];
|
|
1171
|
+
for (const line of lines) {
|
|
1172
|
+
recordTail({ lines: collectTail, line, limit: tailLimit });
|
|
1173
|
+
writeToStderr(`[harness:${source}:${streamName}] ${line}
|
|
1174
|
+
`);
|
|
1175
|
+
}
|
|
1176
|
+
if (done) return;
|
|
1177
|
+
}
|
|
1178
|
+
} catch (e) {
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
async function drainBridgeProcessStream(stream) {
|
|
1182
|
+
try {
|
|
1183
|
+
const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
|
|
1184
|
+
while (true) {
|
|
1185
|
+
const { done } = await reader.read();
|
|
1186
|
+
if (done) return;
|
|
1187
|
+
}
|
|
1188
|
+
} catch (e) {
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function recordTail({
|
|
1192
|
+
lines,
|
|
1193
|
+
line,
|
|
1194
|
+
limit
|
|
1195
|
+
}) {
|
|
1196
|
+
if (!lines) return;
|
|
1197
|
+
lines.push(line);
|
|
1198
|
+
while (lines.length > limit) lines.shift();
|
|
1199
|
+
}
|
|
1200
|
+
function lineDecoder2() {
|
|
1201
|
+
let buffer = "";
|
|
1202
|
+
return {
|
|
1203
|
+
push(chunk) {
|
|
1204
|
+
buffer += chunk;
|
|
1205
|
+
const lines = [];
|
|
1206
|
+
let nl;
|
|
1207
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
1208
|
+
const raw = buffer.slice(0, nl);
|
|
1209
|
+
buffer = buffer.slice(nl + 1);
|
|
1210
|
+
const line = raw.replace(/\r$/, "").trim();
|
|
1211
|
+
if (line.length > 0) lines.push(line);
|
|
1212
|
+
}
|
|
1213
|
+
return lines;
|
|
1214
|
+
},
|
|
1215
|
+
flush() {
|
|
1216
|
+
const line = buffer.replace(/\r$/, "").trim();
|
|
1217
|
+
buffer = "";
|
|
1218
|
+
return line.length > 0 ? [line] : [];
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
function sleep3(ms) {
|
|
1223
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1224
|
+
}
|
|
992
1225
|
export {
|
|
993
1226
|
SandboxChannel,
|
|
994
1227
|
classifyDiskLog,
|
|
1228
|
+
createBridgeErrorHandler,
|
|
1229
|
+
createBridgeStartupError,
|
|
1230
|
+
drainBridgeProcessStream,
|
|
1231
|
+
formatBridgeError,
|
|
1232
|
+
forwardBridgeProcessStream,
|
|
995
1233
|
getAiGatewayAuthFromEnv,
|
|
1234
|
+
logBridgeError,
|
|
996
1235
|
markBridgeStarting,
|
|
997
1236
|
resolveSandboxHomeDir,
|
|
998
1237
|
shellQuote,
|