@ai-sdk/harness 1.0.22 → 1.0.23

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.
@@ -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
- export { type BridgeReadyErrorContext, type BridgeReadySource, type DiskLogRecoveryMode, SandboxChannel, type SandboxChannelDebugEvent, type SandboxChannelOptions, type SandboxChannelReconnectOptions, type SkillFilePathMode, type WaitForBridgeReadyOptions, type WaitForBridgeReadyResult, type WriteSkillsOptions, classifyDiskLog, getAiGatewayAuthFromEnv, markBridgeStarting, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, writeSkills };
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 };
@@ -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,