deepline 0.1.301 → 0.1.303

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.
Files changed (40) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +8 -0
  2. package/dist/bundling-sources/sdk/src/errors.ts +96 -36
  3. package/dist/bundling-sources/sdk/src/http.ts +82 -5
  4. package/dist/bundling-sources/sdk/src/index.ts +12 -0
  5. package/dist/bundling-sources/sdk/src/play.ts +29 -1
  6. package/dist/bundling-sources/sdk/src/release.ts +5 -3
  7. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +120 -14
  8. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +59 -11
  10. package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +3 -3
  11. package/dist/bundling-sources/shared_libs/play-runtime/execution-plan.ts +0 -15
  12. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +3 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +136 -36
  15. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +17 -12
  16. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +210 -5
  17. package/dist/bundling-sources/shared_libs/play-runtime/tool-result-paths.ts +15 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/tool-result-types.ts +4 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +33 -24
  20. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +4 -0
  21. package/dist/bundling-sources/shared_libs/plays/artifact-contract-version.ts +6 -0
  22. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +4 -0
  23. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +159 -12
  24. package/dist/bundling-sources/shared_libs/plays/contracts.ts +69 -1
  25. package/dist/bundling-sources/shared_libs/tool-execution-error.ts +557 -0
  26. package/dist/cli/index.js +556 -183
  27. package/dist/cli/index.mjs +556 -183
  28. package/dist/index.d.mts +69 -40
  29. package/dist/index.d.ts +69 -40
  30. package/dist/index.js +480 -80
  31. package/dist/index.mjs +477 -80
  32. package/dist/plays/bundle-play-file.d.mts +4 -2
  33. package/dist/plays/bundle-play-file.d.ts +4 -2
  34. package/dist/plays/bundle-play-file.mjs +116 -19
  35. package/dist/tool-execution-error-YDz7UMl-.d.mts +440 -0
  36. package/dist/tool-execution-error-YDz7UMl-.d.ts +440 -0
  37. package/package.json +1 -1
  38. package/dist/bundling-sources/shared_libs/play-runtime/completed-receipt-cache.ts +0 -166
  39. package/dist/compiler-manifest-BjopNIqx.d.mts +0 -240
  40. package/dist/compiler-manifest-BjopNIqx.d.ts +0 -240
@@ -3,6 +3,7 @@ import {
3
3
  normalizeToolHttpErrorMessage,
4
4
  type ToolHttpError,
5
5
  } from './tool-http-errors';
6
+ import type { ToolExecutionErrorSchemaVersion } from '../tool-execution-error';
6
7
 
7
8
  export const TOOL_EXECUTE_TRANSIENT_HTTP_MAX_ATTEMPTS = 2;
8
9
  // A provider may need to finish the original request before it can replay the
@@ -296,6 +297,7 @@ export function classifyToolExecuteHttpFailure(input: {
296
297
  status: number;
297
298
  attempt: number;
298
299
  bodyText: string;
300
+ schemaVersion?: ToolExecutionErrorSchemaVersion;
299
301
  retryAfterHeader?: string | null;
300
302
  transientHttpRetrySafe?: boolean;
301
303
  providerLatencyMs?: number;
@@ -318,12 +320,20 @@ export function classifyToolExecuteHttpFailure(input: {
318
320
  hasRetryAfterHeader,
319
321
  transientHttpRetrySafe,
320
322
  });
323
+ const retryAfterMs = parseToolExecuteRetryAfterMs(
324
+ input.retryAfterHeader,
325
+ input.nowMs,
326
+ );
321
327
  const error = normalizeToolHttpErrorMessage({
322
328
  toolId: input.toolId,
323
329
  status: input.status,
324
330
  attempt: input.attempt,
325
331
  maxAttempts: initialRetryDecision.attemptCap,
326
332
  bodyText: input.bodyText,
333
+ schemaVersion: input.schemaVersion,
334
+ retryable: initialRetryDecision.retryable,
335
+ retryAfterMs:
336
+ input.status === 429 && hasRetryAfterHeader ? retryAfterMs : null,
327
337
  });
328
338
  if (
329
339
  typeof input.providerLatencyMs === 'number' &&
@@ -355,10 +365,6 @@ export function classifyToolExecuteHttpFailure(input: {
355
365
  ) {
356
366
  error.receiptFailureKind = 'repairable';
357
367
  }
358
- const retryAfterMs = parseToolExecuteRetryAfterMs(
359
- input.retryAfterHeader,
360
- input.nowMs,
361
- );
362
368
  const retryDelayMs =
363
369
  input.status === 429
364
370
  ? Math.min(
@@ -369,14 +375,14 @@ export function classifyToolExecuteHttpFailure(input: {
369
375
  ),
370
376
  )
371
377
  : retryDecision.reason === 'idempotency_in_progress' &&
372
- !hasRetryAfterHeader
373
- ? idempotencyInProgressRetryDelayMs(input.attempt)
374
- : retryDecision.reason === 'gateway_invocation_in_progress' &&
375
378
  !hasRetryAfterHeader
376
379
  ? idempotencyInProgressRetryDelayMs(input.attempt)
377
- : retryAfterMs > 0
378
- ? Math.min(TOOL_EXECUTE_RETRY_DELAY_MAX_MS, retryAfterMs)
379
- : TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS;
380
+ : retryDecision.reason === 'gateway_invocation_in_progress' &&
381
+ !hasRetryAfterHeader
382
+ ? idempotencyInProgressRetryDelayMs(input.attempt)
383
+ : retryAfterMs > 0
384
+ ? Math.min(TOOL_EXECUTE_RETRY_DELAY_MAX_MS, retryAfterMs)
385
+ : TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS;
380
386
  return {
381
387
  ...retryDecision,
382
388
  error,
@@ -395,7 +401,6 @@ export function classifyToolExecuteHttpFailure(input: {
395
401
  : TOOL_EXECUTE_BARE_RATE_LIMIT_BACKPRESSURE_MS
396
402
  : null,
397
403
  chargeRetryBudget:
398
- shouldRetry &&
399
- retryDecision.reason !== 'gateway_invocation_in_progress',
404
+ shouldRetry && retryDecision.reason !== 'gateway_invocation_in_progress',
400
405
  };
401
406
  }
@@ -1,5 +1,50 @@
1
1
  import type { WorkReceiptFailureKind } from './work-receipts';
2
+ import {
3
+ LEGACY_TOOL_EXECUTION_ERROR_SCHEMA_VERSION,
4
+ ToolExecutionError,
5
+ deserializeToolExecutionFailure,
6
+ isProviderTransientFailure,
7
+ normalizeToolExecutionCategory,
8
+ normalizeToolExecutionOrigin,
9
+ type ToolExecutionErrorSchemaVersion,
10
+ type ToolExecutionErrorOptions,
11
+ } from '../tool-execution-error';
2
12
 
13
+ const TOOL_HTTP_ERROR_BRAND = Symbol.for('deepline.tool-http-error.v1');
14
+
15
+ type ToolHttpErrorConstructorInput = {
16
+ message: string;
17
+ billing: Record<string, unknown> | null;
18
+ status: number;
19
+ receiptFailureKind: WorkReceiptFailureKind;
20
+ options: Partial<ToolExecutionErrorOptions>;
21
+ };
22
+
23
+ function applyToolHttpErrorFields(
24
+ error: {
25
+ name: string;
26
+ billing: Record<string, unknown> | null;
27
+ status: number;
28
+ receiptFailureKind: WorkReceiptFailureKind;
29
+ },
30
+ input: ToolHttpErrorConstructorInput,
31
+ ): void {
32
+ error.name = 'ToolHttpError';
33
+ error.billing = input.billing;
34
+ error.status = input.status;
35
+ error.receiptFailureKind = input.receiptFailureKind;
36
+ Object.defineProperty(error, TOOL_HTTP_ERROR_BRAND, {
37
+ configurable: false,
38
+ enumerable: false,
39
+ writable: false,
40
+ value: true,
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Historical play-runtime HTTP error. Existing artifacts must keep this exact
46
+ * Error prototype; typed errors are an explicit artifact-contract opt-in.
47
+ */
3
48
  export class ToolHttpError extends Error {
4
49
  readonly billing: Record<string, unknown> | null;
5
50
  /** HTTP status of the failed tool-execute response (e.g. 429, 502). */
@@ -11,15 +56,89 @@ export class ToolHttpError extends Error {
11
56
  billing: Record<string, unknown> | null,
12
57
  status: number,
13
58
  receiptFailureKind: WorkReceiptFailureKind = 'terminal',
59
+ _options: Partial<ToolExecutionErrorOptions> = {},
14
60
  ) {
15
61
  super(message);
16
- this.name = 'ToolHttpError';
17
62
  this.billing = billing;
18
63
  this.status = status;
19
64
  this.receiptFailureKind = receiptFailureKind;
65
+ applyToolHttpErrorFields(this, {
66
+ message,
67
+ billing,
68
+ status,
69
+ receiptFailureKind,
70
+ options: _options,
71
+ });
72
+ }
73
+
74
+ static [Symbol.hasInstance](value: unknown): boolean {
75
+ return Boolean(
76
+ value &&
77
+ typeof value === 'object' &&
78
+ (value as Record<symbol, unknown>)[TOOL_HTTP_ERROR_BRAND] === true,
79
+ );
20
80
  }
21
81
  }
22
82
 
83
+ class StructuredToolHttpError extends ToolExecutionError {
84
+ readonly billing: Record<string, unknown> | null;
85
+ readonly status: number;
86
+ receiptFailureKind: WorkReceiptFailureKind;
87
+
88
+ constructor(input: ToolHttpErrorConstructorInput) {
89
+ super(input.message, {
90
+ toolId: input.options.toolId ?? 'unknown_tool',
91
+ provider: input.options.provider ?? null,
92
+ operation: input.options.operation ?? null,
93
+ code: input.options.code ?? null,
94
+ origin: input.options.origin ?? 'unknown',
95
+ category: input.options.category ?? 'unknown',
96
+ retryable: input.options.retryable === true,
97
+ statusCode:
98
+ input.options.statusCode === undefined
99
+ ? input.status
100
+ : input.options.statusCode,
101
+ requestId: input.options.requestId ?? null,
102
+ retryAfterMs: input.options.retryAfterMs ?? null,
103
+ networkKind: input.options.networkKind ?? null,
104
+ networkScope: input.options.networkScope ?? null,
105
+ });
106
+ this.billing = input.billing;
107
+ this.status = input.status;
108
+ this.receiptFailureKind = input.receiptFailureKind;
109
+ applyToolHttpErrorFields(this, input);
110
+ this.name = isProviderTransientFailure(this)
111
+ ? 'ProviderTransientError'
112
+ : 'ToolExecutionError';
113
+ }
114
+ }
115
+
116
+ export function createToolHttpError(
117
+ schemaVersion: ToolExecutionErrorSchemaVersion,
118
+ message: string,
119
+ billing: Record<string, unknown> | null,
120
+ status: number,
121
+ receiptFailureKind: WorkReceiptFailureKind = 'terminal',
122
+ options: Partial<ToolExecutionErrorOptions> = {},
123
+ ): ToolHttpError {
124
+ if (schemaVersion === LEGACY_TOOL_EXECUTION_ERROR_SCHEMA_VERSION) {
125
+ return new ToolHttpError(
126
+ message,
127
+ billing,
128
+ status,
129
+ receiptFailureKind,
130
+ options,
131
+ );
132
+ }
133
+ return new StructuredToolHttpError({
134
+ message,
135
+ billing,
136
+ status,
137
+ receiptFailureKind,
138
+ options,
139
+ }) as ToolHttpError;
140
+ }
141
+
23
142
  function formatCreditAmount(value: unknown): string {
24
143
  if (typeof value !== 'number' || !Number.isFinite(value)) {
25
144
  return String(value ?? '-');
@@ -222,7 +341,12 @@ export function normalizeToolHttpErrorMessage(input: {
222
341
  attempt: number;
223
342
  maxAttempts: number;
224
343
  bodyText: string;
344
+ retryable?: boolean;
345
+ retryAfterMs?: number | null;
346
+ schemaVersion?: ToolExecutionErrorSchemaVersion;
225
347
  }): ToolHttpError {
348
+ const schemaVersion =
349
+ input.schemaVersion ?? LEGACY_TOOL_EXECUTION_ERROR_SCHEMA_VERSION;
226
350
  let parsed: Record<string, unknown> | null = null;
227
351
  try {
228
352
  const candidate = JSON.parse(input.bodyText);
@@ -230,9 +354,64 @@ export function normalizeToolHttpErrorMessage(input: {
230
354
  } catch {
231
355
  parsed = null;
232
356
  }
357
+ const serializedFailure = parsed?.tool_error;
358
+ const hydratedFailure = deserializeToolExecutionFailure(
359
+ '',
360
+ serializedFailure,
361
+ schemaVersion,
362
+ );
363
+ const provider =
364
+ getStringField(parsed, 'provider') ??
365
+ hydratedFailure?.provider ??
366
+ input.toolId.split(/[._:./-]+/)[0]?.trim() ??
367
+ null;
368
+ const operation =
369
+ getStringField(parsed, 'operation') ??
370
+ hydratedFailure?.operation ??
371
+ input.toolId;
372
+ const code = getStringField(parsed, 'code') ?? hydratedFailure?.code ?? null;
373
+ const rawOrigin =
374
+ getStringField(parsed, 'failure_origin') ?? hydratedFailure?.origin;
375
+ const rawCategory =
376
+ getStringField(parsed, 'error_category') ?? hydratedFailure?.category;
377
+ const origin = normalizeToolExecutionOrigin(rawOrigin, {
378
+ provider,
379
+ operation,
380
+ });
381
+ const category = normalizeToolExecutionCategory(rawCategory);
382
+ const trustworthy = origin !== 'unknown' && category !== 'unknown';
383
+ const publicOptions: Partial<ToolExecutionErrorOptions> = {
384
+ toolId: input.toolId,
385
+ provider,
386
+ operation,
387
+ code,
388
+ origin,
389
+ category,
390
+ retryable: trustworthy
391
+ ? (hydratedFailure?.retryable ?? input.retryable === true)
392
+ : false,
393
+ statusCode: input.status,
394
+ requestId:
395
+ getStringField(parsed, 'request_id') ??
396
+ getStringField(parsed, 'requestId') ??
397
+ hydratedFailure?.requestId ??
398
+ null,
399
+ retryAfterMs: hydratedFailure?.retryAfterMs ?? input.retryAfterMs ?? null,
400
+ networkKind:
401
+ hydratedFailure?.networkKind ??
402
+ (category === 'network' && code?.toUpperCase().includes('TIMEOUT')
403
+ ? 'timeout'
404
+ : null),
405
+ networkScope:
406
+ hydratedFailure?.networkScope ??
407
+ (origin === 'provider' && category === 'network'
408
+ ? 'deepline_to_provider'
409
+ : null),
410
+ };
233
411
  const billing = getObjectField(parsed, 'billing');
234
412
  if (isInsufficientCreditsBilling(billing)) {
235
- return new ToolHttpError(
413
+ return createToolHttpError(
414
+ schemaVersion,
236
415
  `tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${formatInsufficientCreditsMessage(
237
416
  {
238
417
  billing,
@@ -241,6 +420,14 @@ export function normalizeToolHttpErrorMessage(input: {
241
420
  )}`,
242
421
  billing,
243
422
  input.status,
423
+ 'terminal',
424
+ {
425
+ ...publicOptions,
426
+ origin: 'deepline',
427
+ category: 'billing',
428
+ code: publicOptions.code ?? 'INSUFFICIENT_CREDITS',
429
+ retryable: false,
430
+ },
244
431
  );
245
432
  }
246
433
  const hardBillingPayload = isHardBillingFailurePayload(billing)
@@ -249,7 +436,8 @@ export function normalizeToolHttpErrorMessage(input: {
249
436
  ? normalizeHardBillingPayload(parsed)
250
437
  : null;
251
438
  if (hardBillingPayload) {
252
- return new ToolHttpError(
439
+ return createToolHttpError(
440
+ schemaVersion,
253
441
  formatHardBillingFailureMessage({
254
442
  billing: hardBillingPayload,
255
443
  toolId: input.toolId,
@@ -259,9 +447,17 @@ export function normalizeToolHttpErrorMessage(input: {
259
447
  }),
260
448
  hardBillingPayload,
261
449
  input.status,
450
+ 'terminal',
451
+ {
452
+ ...publicOptions,
453
+ origin: 'deepline',
454
+ category: 'billing',
455
+ retryable: false,
456
+ },
262
457
  );
263
458
  }
264
- return new ToolHttpError(
459
+ return createToolHttpError(
460
+ schemaVersion,
265
461
  `tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${formatPublicToolErrorPayload(
266
462
  {
267
463
  parsed,
@@ -271,6 +467,7 @@ export function normalizeToolHttpErrorMessage(input: {
271
467
  billing,
272
468
  input.status,
273
469
  receiptFailureKindForToolErrorPayload(parsed),
470
+ publicOptions,
274
471
  );
275
472
  }
276
473
 
@@ -281,10 +478,18 @@ export function extractErrorBilling(
281
478
  }
282
479
 
283
480
  export function isHardBillingToolHttpError(error: unknown): boolean {
284
- return (
481
+ if (
285
482
  error instanceof ToolHttpError &&
286
483
  (isInsufficientCreditsBilling(error.billing) ||
287
484
  isHardBillingFailurePayload(error.billing))
485
+ ) {
486
+ return true;
487
+ }
488
+ return (
489
+ error instanceof ToolExecutionError &&
490
+ error.origin === 'deepline' &&
491
+ error.category === 'billing' &&
492
+ error.code !== 'BILLING_UNAVAILABLE'
288
493
  );
289
494
  }
290
495
 
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Derive the public semantic accessor name from a declared list path.
3
+ *
4
+ * Keep this independent from path normalization: `result.data` may resolve to
5
+ * `toolResponse.raw`, but its public getter remains `extractedLists.data`.
6
+ */
7
+ export function listNameFromDeclaredPath(path: string): string | null {
8
+ return (
9
+ String(path || '')
10
+ .split('.')
11
+ .filter(Boolean)
12
+ .at(-1)
13
+ ?.replace(/[^A-Za-z0-9_$]/g, '') || null
14
+ );
15
+ }
@@ -42,6 +42,8 @@ export type ToolResultMetadata = {
42
42
  execution: ToolResultExecutionMetadata;
43
43
  targets: Record<string, ToolResultTargetMetadata>;
44
44
  lists: Record<string, ToolResultListMetadata>;
45
+ /** Original declarations preserve semantic accessor names across replay. */
46
+ listExtractorPaths?: readonly string[];
45
47
  extractors?: Record<string, ToolResultExtractorDescriptor>;
46
48
  };
47
49
 
@@ -130,6 +132,8 @@ export type ToolExecuteResultBase<
130
132
  keys: Record<string, string>;
131
133
  }
132
134
  >;
135
+ /** Original declarations preserve semantic accessor names across replay. */
136
+ listExtractorPaths?: readonly string[];
133
137
  };
134
138
  };
135
139
 
@@ -47,6 +47,7 @@ import {
47
47
  type SerializedPlayDataset,
48
48
  } from '../plays/dataset';
49
49
  import { normalizeTableNamespace, sha256Hex } from '../plays/row-identity';
50
+ import { listNameFromDeclaredPath } from './tool-result-paths';
50
51
 
51
52
  type PathSegment = string | number | '*';
52
53
 
@@ -744,43 +745,35 @@ function resolveListRows(
744
745
  { path: string; rows: Record<string, unknown>[] }
745
746
  > = {};
746
747
  for (const rawPath of listExtractorPaths ?? []) {
748
+ const listName = listNameFromDeclaredPath(rawPath);
749
+ if (!listName) continue;
747
750
  const path = normalizeResultPath(rawPath);
748
751
  if (!path) continue;
749
752
  const candidates = [...candidateResultPaths(rawPath)].filter(
750
753
  (candidate, index, all) => candidate && all.indexOf(candidate) === index,
751
754
  );
752
- let resolvedPath: string | null = null;
753
- let rows: Record<string, unknown>[] | null = null;
755
+ let resolved: { path: string; rows: Record<string, unknown>[] } | null =
756
+ null;
754
757
  let emptyMatch: { path: string; rows: Record<string, unknown>[] } | null =
755
758
  null;
756
759
  for (const candidate of candidates) {
757
- rows = normalizeRows(getAtPath(result, candidate));
758
- if (!rows) {
760
+ const candidateRows = normalizeRows(getAtPath(result, candidate));
761
+ if (!candidateRows) {
759
762
  continue;
760
763
  }
761
- if (rows.length > 0) {
762
- resolvedPath = candidate;
764
+ if (candidateRows.length > 0) {
765
+ resolved = { path: candidate, rows: candidateRows };
763
766
  break;
764
767
  }
765
- emptyMatch ??= { path: candidate, rows };
766
- }
767
- if (!rows && emptyMatch) {
768
- resolvedPath = emptyMatch.path;
769
- rows = emptyMatch.rows;
768
+ emptyMatch ??= { path: candidate, rows: candidateRows };
770
769
  }
771
- if (!rows) continue;
772
- const storedPath = resolvedPath ?? path;
773
- const name = storedPath
774
- .split('.')
775
- .filter(Boolean)
776
- .at(-1)
777
- ?.replace(/\[\d+\]$/, '');
778
- const listName = name || storedPath;
770
+ resolved ??= emptyMatch;
771
+ if (!resolved) continue;
779
772
  const existing = lists[listName];
780
- if (existing?.rows.length && rows.length === 0) {
773
+ if (existing?.rows.length && resolved.rows.length === 0) {
781
774
  continue;
782
775
  }
783
- lists[listName] = { path: storedPath, rows };
776
+ lists[listName] = resolved;
784
777
  }
785
778
  return lists;
786
779
  }
@@ -1084,6 +1077,7 @@ export function createToolExecuteResult<TResult = unknown>(input: {
1084
1077
  toolId: input.metadata.toolId,
1085
1078
  execution: input.execution,
1086
1079
  targets,
1080
+ listExtractorPaths: [...(input.metadata.listExtractorPaths ?? [])],
1087
1081
  ...(input.metadata.extractors
1088
1082
  ? { extractors: input.metadata.extractors }
1089
1083
  : {}),
@@ -1208,6 +1202,21 @@ export function attachToolResultListDataset<T extends Record<string, unknown>>(
1208
1202
  count: input.count,
1209
1203
  keys: input.keys ?? existing?.keys ?? {},
1210
1204
  };
1205
+ const declaredPaths = result._metadata.listExtractorPaths ?? [];
1206
+ const inputCandidates = new Set(candidateResultPaths(input.path));
1207
+ const equivalentDeclaration = declaredPaths.find((path) =>
1208
+ candidateResultPaths(path).some((candidate) =>
1209
+ inputCandidates.has(candidate),
1210
+ ),
1211
+ );
1212
+ result._metadata.listExtractorPaths = equivalentDeclaration
1213
+ ? [...declaredPaths]
1214
+ : [
1215
+ ...declaredPaths.filter(
1216
+ (path) => listNameFromDeclaredPath(path) !== input.name,
1217
+ ),
1218
+ input.path,
1219
+ ];
1211
1220
  const accessor = {
1212
1221
  path: input.path,
1213
1222
  count: input.count,
@@ -1585,9 +1594,9 @@ function metadataInputFromToolExecuteResult(
1585
1594
  [info.path],
1586
1595
  ]),
1587
1596
  ),
1588
- listExtractorPaths: Object.values(value._metadata.lists).map(
1589
- (list) => list.path,
1590
- ),
1597
+ listExtractorPaths:
1598
+ value._metadata.listExtractorPaths ??
1599
+ Object.values(value._metadata.lists).map((list) => list.path),
1591
1600
  listIdentityGetters: Object.fromEntries(
1592
1601
  Object.values(value._metadata.lists)
1593
1602
  .flatMap((list) => Object.entries(list.keys))
@@ -1,4 +1,5 @@
1
1
  import { isReusableWorkReceiptState } from './work-receipt-state-machine';
2
+ import type { ToolExecutionFailureV1 } from '../tool-execution-error';
2
3
 
3
4
  export type WorkReceiptFailureKind = 'terminal' | 'repairable';
4
5
 
@@ -37,6 +38,7 @@ export type WorkReceipt = {
37
38
  status: WorkReceiptStatus;
38
39
  output?: unknown;
39
40
  error?: string | null;
41
+ errorPayload?: ToolExecutionFailureV1 | null;
40
42
  failureKind?: WorkReceiptFailureKind | null;
41
43
  runId?: string | null;
42
44
  leaseId?: string | null;
@@ -89,6 +91,7 @@ export type WorkReceiptBatchFailCommand = {
89
91
  runId: string;
90
92
  key: string;
91
93
  error: string;
94
+ errorPayload?: ToolExecutionFailureV1 | null;
92
95
  failureKind?: WorkReceiptFailureKind | null;
93
96
  leaseId?: string | null;
94
97
  runAttempt?: number | null;
@@ -161,6 +164,7 @@ export type WorkReceiptStore = {
161
164
  failReceipt(
162
165
  input: WorkReceiptCommand & {
163
166
  error: string;
167
+ errorPayload?: ToolExecutionFailureV1 | null;
164
168
  failureKind?: WorkReceiptFailureKind | null;
165
169
  },
166
170
  ): Promise<WorkReceipt | null>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Immutable Play artifact ABI version shared by bundling and SDK release
3
+ * policy. Keep this module dependency-free: shared Play code must not import
4
+ * the SDK release manifest or create a package cycle.
5
+ */
6
+ export const CURRENT_PLAY_ARTIFACT_CONTRACT_VERSION = 2 as const;
@@ -1,3 +1,5 @@
1
+ import type { ToolExecutionErrorSchemaVersion } from '../tool-execution-error';
2
+
1
3
  export type PlayPackageImport = {
2
4
  name: string;
3
5
  version: string | null;
@@ -21,6 +23,8 @@ export type PlayArtifactCompatibility = {
21
23
  minRunnerVersion: number;
22
24
  runtimeFeatures: PlayRuntimeFeature[];
23
25
  runtimeBackend?: string | null;
26
+ /** Missing preserves the legacy schema used before this field existed. */
27
+ toolErrorSchemaVersion?: ToolExecutionErrorSchemaVersion;
24
28
  };
25
29
 
26
30
  /** The only executable Play artifact contract. */