@ai-sdk/harness 1.0.138 → 1.0.139

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness",
3
- "version": "1.0.138",
3
+ "version": "1.0.139",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -44,9 +44,9 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@ai-sdk/provider": "4.0.21",
48
- "@ai-sdk/provider-utils": "5.0.53",
49
- "ai": "7.0.127"
47
+ "@ai-sdk/provider": "4.0.22",
48
+ "@ai-sdk/provider-utils": "5.0.54",
49
+ "ai": "7.0.128"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "ws": "^8.21.0",
@@ -58,7 +58,7 @@
58
58
  }
59
59
  },
60
60
  "devDependencies": {
61
- "@ai-sdk/otel": "1.0.127",
61
+ "@ai-sdk/otel": "1.0.128",
62
62
  "@opentelemetry/sdk-trace-base": "2.7.1",
63
63
  "@types/node": "22.19.19",
64
64
  "@types/ws": "^8.5.13",
@@ -234,6 +234,14 @@ export function runPrompt<
234
234
  result.fail(err);
235
235
  };
236
236
 
237
+ const settleLifecycle = async (err: unknown): Promise<void> => {
238
+ if (input.abortSignal?.aborted) {
239
+ await lifecycle.abort(input.abortSignal.reason);
240
+ } else {
241
+ await lifecycle.error(err);
242
+ }
243
+ };
244
+
237
245
  const done = (async () => {
238
246
  let bridge: Awaited<ReturnType<typeof toHarnessStream>>;
239
247
  try {
@@ -269,7 +277,7 @@ export function runPrompt<
269
277
  },
270
278
  });
271
279
  } catch (err) {
272
- await lifecycle.error(err);
280
+ await settleLifecycle(err);
273
281
  logBridgeError({
274
282
  harnessId: input.harness.harnessId,
275
283
  sessionId: input.session.sessionId,
@@ -998,7 +1006,7 @@ export function runPrompt<
998
1006
  // Telemetry and stderr diagnostics keep the raw error (absolute
999
1007
  // paths help debugging); the consumer-facing settle uses the
1000
1008
  // workDir-stripped one, like every other forwarded part.
1001
- await lifecycle.error(value.error);
1009
+ await settleLifecycle(value.error);
1002
1010
  // A turn the caller itself aborted ends with an error-shaped part by
1003
1011
  // construction; diagnosing the caller's own signal to stderr reads
1004
1012
  // as a malfunction. `settleFailure` below still reports it as an
@@ -1477,7 +1485,7 @@ export function runPrompt<
1477
1485
  } catch {
1478
1486
  // Preserve the error that stopped the reader loop.
1479
1487
  }
1480
- await lifecycle.error(err);
1488
+ await settleLifecycle(err);
1481
1489
  logBridgeError({
1482
1490
  harnessId: input.harness.harnessId,
1483
1491
  sessionId: input.session.sessionId,
@@ -71,6 +71,7 @@ export interface TurnLifecycle<
71
71
  toolCallId: string;
72
72
  execute: () => PromiseLike<T>;
73
73
  }): Promise<T>;
74
+ abort(reason?: unknown): Promise<void>;
74
75
  error(error: unknown): Promise<void>;
75
76
  }
76
77
 
@@ -358,11 +359,22 @@ export function createTurnLifecycle<
358
359
  });
359
360
  },
360
361
 
362
+ async abort(reason) {
363
+ if (ended) return;
364
+ if (!started) await start();
365
+ ended = true;
366
+ await telemetry.onAbort?.({
367
+ callId: options.callId,
368
+ steps: [...completedSteps],
369
+ ...(reason !== undefined ? { reason } : {}),
370
+ });
371
+ },
372
+
361
373
  async error(error) {
362
374
  if (ended) return;
363
375
  if (!started) await start();
364
376
  ended = true;
365
- await telemetry.onError?.(error);
377
+ await telemetry.onError?.({ callId: options.callId, error });
366
378
  },
367
379
  };
368
380
  }
@@ -30,6 +30,12 @@ type Record_ = { ts: number } & Record<string, unknown>;
30
30
 
31
31
  export type FileReporter = Telemetry & HarnessDiagnosticConsumer;
32
32
 
33
+ function serializeError(error: unknown): unknown {
34
+ return error instanceof Error
35
+ ? { name: error.name, message: error.message }
36
+ : error;
37
+ }
38
+
33
39
  export function createFileReporter(options: FileReporterOptions): FileReporter {
34
40
  const fileName = options.fileName ?? 'events.jsonl';
35
41
  const path = `${options.dir}/${fileName}`;
@@ -181,16 +187,25 @@ export function createFileReporter(options: FileReporterOptions): FileReporter {
181
187
  });
182
188
  finishTurn(e.callId);
183
189
  },
184
- onError(error) {
185
- if (lastOpenCallId != null) bucketFor(lastOpenCallId).errored = true;
186
- record(lastOpenCallId, {
190
+ onError(event) {
191
+ const { callId, error } = event as { callId: string; error: unknown };
192
+ bucketFor(callId).errored = true;
193
+ record(callId, {
187
194
  ts: Date.now(),
188
195
  kind: 'error',
189
- error:
190
- error instanceof Error
191
- ? { name: error.name, message: error.message }
192
- : error,
196
+ callId,
197
+ error: serializeError(error),
198
+ });
199
+ finishTurn(callId);
200
+ },
201
+ onAbort({ callId, reason }) {
202
+ record(callId, {
203
+ ts: Date.now(),
204
+ kind: 'turn-abort',
205
+ callId,
206
+ ...(reason !== undefined ? { reason: serializeError(reason) } : {}),
193
207
  });
208
+ finishTurn(callId);
194
209
  },
195
210
  ingestDiagnostic(diagnostic: HarnessDiagnostic) {
196
211
  if (diagnostic.level === 'error' && lastOpenCallId != null) {