deepline 0.1.301 → 0.1.302
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/dist/bundling-sources/sdk/src/client.ts +8 -0
- package/dist/bundling-sources/sdk/src/errors.ts +57 -15
- package/dist/bundling-sources/sdk/src/http.ts +82 -5
- package/dist/bundling-sources/sdk/src/index.ts +4 -0
- package/dist/bundling-sources/sdk/src/play.ts +29 -1
- package/dist/bundling-sources/sdk/src/release.ts +5 -3
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +120 -14
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +59 -11
- package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +3 -3
- package/dist/bundling-sources/shared_libs/play-runtime/execution-plan.ts +0 -15
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +136 -36
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +17 -12
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +210 -5
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +4 -0
- package/dist/bundling-sources/shared_libs/plays/artifact-contract-version.ts +6 -0
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +4 -0
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +159 -12
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +69 -1
- package/dist/bundling-sources/shared_libs/tool-execution-error.ts +428 -0
- package/dist/cli/index.js +501 -179
- package/dist/cli/index.mjs +501 -179
- package/dist/index.d.mts +39 -18
- package/dist/index.d.ts +39 -18
- package/dist/index.js +394 -59
- package/dist/index.mjs +391 -59
- package/dist/plays/bundle-play-file.d.mts +4 -2
- package/dist/plays/bundle-play-file.d.ts +4 -2
- package/dist/plays/bundle-play-file.mjs +116 -19
- package/dist/{compiler-manifest-BjopNIqx.d.mts → tool-execution-error-9mH4i-mJ.d.mts} +79 -1
- package/dist/{compiler-manifest-BjopNIqx.d.ts → tool-execution-error-9mH4i-mJ.d.ts} +79 -1
- package/package.json +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/completed-receipt-cache.ts +0 -166
|
@@ -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
|
-
:
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -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. */
|
|
@@ -28,10 +28,13 @@ import type {
|
|
|
28
28
|
PlayRuntimeFeature,
|
|
29
29
|
} from '../artifact-types';
|
|
30
30
|
import { buildPlayContractCompatibility } from '../contracts';
|
|
31
|
+
import type { ToolExecutionErrorSchemaVersion } from '../../tool-execution-error';
|
|
31
32
|
import { validatePlaySourceFilesHaveNoInlineSecrets } from '../secret-guardrails';
|
|
32
33
|
import { MAX_PLAY_BUNDLE_BYTES } from './limits';
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
// The authored tool-error schema is part of the artifact bytes. Do not reuse a
|
|
36
|
+
// local bundle cached before compatibility selection entered graph analysis.
|
|
37
|
+
const PLAY_BUNDLE_CACHE_VERSION = 27;
|
|
35
38
|
const PLAY_ARTIFACT_CACHE_DIR = join(
|
|
36
39
|
tmpdir(),
|
|
37
40
|
`deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION}`,
|
|
@@ -152,6 +155,7 @@ type SourceGraphAnalysis = {
|
|
|
152
155
|
importPolicy: PlayImportPolicy;
|
|
153
156
|
playName: string | null;
|
|
154
157
|
playDescription: string | null;
|
|
158
|
+
toolErrorSchemaVersion: ToolExecutionErrorSchemaVersion | null;
|
|
155
159
|
importedPlayDependencies: ImportedPlayDependency[];
|
|
156
160
|
};
|
|
157
161
|
|
|
@@ -501,6 +505,8 @@ type PlayMetadataExtractionContext = {
|
|
|
501
505
|
type ExtractedPlayMetadata = {
|
|
502
506
|
name: string | null;
|
|
503
507
|
description: string | null;
|
|
508
|
+
toolErrorSchemaVersion: ToolExecutionErrorSchemaVersion | null;
|
|
509
|
+
toolErrorSchemaVersionUnknown: boolean;
|
|
504
510
|
};
|
|
505
511
|
|
|
506
512
|
const TypeScriptParser = Parser.extend(
|
|
@@ -795,9 +801,13 @@ function staticStringFromExpression(
|
|
|
795
801
|
}
|
|
796
802
|
|
|
797
803
|
function propertyNameFromKey(property: AstNode): string | null {
|
|
798
|
-
if (property.computed) return null;
|
|
799
804
|
const key = isAstNode(property.key) ? property.key : null;
|
|
800
805
|
if (!key) return null;
|
|
806
|
+
if (property.computed) {
|
|
807
|
+
return key.type === 'Literal' && typeof key.value === 'string'
|
|
808
|
+
? key.value
|
|
809
|
+
: null;
|
|
810
|
+
}
|
|
801
811
|
if (key.type === 'Identifier' && typeof key.name === 'string') {
|
|
802
812
|
return key.name;
|
|
803
813
|
}
|
|
@@ -826,6 +836,109 @@ function objectExpressionFromNode(
|
|
|
826
836
|
);
|
|
827
837
|
}
|
|
828
838
|
|
|
839
|
+
type StaticPropertyResolution =
|
|
840
|
+
| { kind: 'absent' }
|
|
841
|
+
| { kind: 'found'; value: AstNode }
|
|
842
|
+
| { kind: 'unknown' };
|
|
843
|
+
|
|
844
|
+
function resolveStaticProperty(
|
|
845
|
+
node: AstNode | null | undefined,
|
|
846
|
+
propertyName: string,
|
|
847
|
+
context: PlayMetadataExtractionContext,
|
|
848
|
+
ancestors = new Set<AstNode>(),
|
|
849
|
+
): StaticPropertyResolution {
|
|
850
|
+
if (!node) return { kind: 'absent' };
|
|
851
|
+
const object = objectExpressionFromNode(node, context);
|
|
852
|
+
if (!object) return { kind: 'unknown' };
|
|
853
|
+
if (ancestors.has(object)) return { kind: 'unknown' };
|
|
854
|
+
const nextAncestors = new Set(ancestors).add(object);
|
|
855
|
+
|
|
856
|
+
let resolution: StaticPropertyResolution = { kind: 'absent' };
|
|
857
|
+
for (const property of astArray(object.properties)) {
|
|
858
|
+
if (property.type === 'SpreadElement') {
|
|
859
|
+
const spreadResolution = resolveStaticProperty(
|
|
860
|
+
isAstNode(property.argument) ? property.argument : null,
|
|
861
|
+
propertyName,
|
|
862
|
+
context,
|
|
863
|
+
nextAncestors,
|
|
864
|
+
);
|
|
865
|
+
if (spreadResolution.kind !== 'absent') {
|
|
866
|
+
resolution = spreadResolution;
|
|
867
|
+
}
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
if (property.type !== 'Property') continue;
|
|
871
|
+
const resolvedPropertyName = propertyNameFromKey(property);
|
|
872
|
+
if (resolvedPropertyName !== propertyName) {
|
|
873
|
+
if (property.computed && resolvedPropertyName === null) {
|
|
874
|
+
resolution = { kind: 'unknown' };
|
|
875
|
+
}
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
const propertyValue = property.shorthand ? property.key : property.value;
|
|
879
|
+
resolution = isAstNode(propertyValue)
|
|
880
|
+
? { kind: 'found', value: propertyValue }
|
|
881
|
+
: { kind: 'unknown' };
|
|
882
|
+
}
|
|
883
|
+
return resolution;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function staticNumberFromExpression(
|
|
887
|
+
node: AstNode | null | undefined,
|
|
888
|
+
context: PlayMetadataExtractionContext,
|
|
889
|
+
seen = new Set<string>(),
|
|
890
|
+
): number | null {
|
|
891
|
+
const expression = unwrapStaticExpression(node);
|
|
892
|
+
if (!expression) return null;
|
|
893
|
+
if (expression.type === 'Literal' && typeof expression.value === 'number') {
|
|
894
|
+
return expression.value;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const identifier = getIdentifierName(expression);
|
|
898
|
+
if (!identifier || seen.has(identifier)) return null;
|
|
899
|
+
seen.add(identifier);
|
|
900
|
+
return staticNumberFromExpression(
|
|
901
|
+
context.declarations.get(identifier),
|
|
902
|
+
context,
|
|
903
|
+
seen,
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function toolErrorSchemaVersionFromOptions(
|
|
908
|
+
node: AstNode | null | undefined,
|
|
909
|
+
context: PlayMetadataExtractionContext,
|
|
910
|
+
): ToolExecutionErrorSchemaVersion | null | undefined {
|
|
911
|
+
const directCompatibility = resolveStaticProperty(
|
|
912
|
+
node,
|
|
913
|
+
'compatibility',
|
|
914
|
+
context,
|
|
915
|
+
);
|
|
916
|
+
if (directCompatibility.kind === 'unknown') return null;
|
|
917
|
+
|
|
918
|
+
const bindings = resolveStaticProperty(node, 'bindings', context);
|
|
919
|
+
if (bindings.kind === 'unknown') return null;
|
|
920
|
+
const bindingCompatibility =
|
|
921
|
+
bindings.kind === 'found'
|
|
922
|
+
? resolveStaticProperty(bindings.value, 'compatibility', context)
|
|
923
|
+
: ({ kind: 'absent' } satisfies StaticPropertyResolution);
|
|
924
|
+
if (bindingCompatibility.kind === 'unknown') return null;
|
|
925
|
+
|
|
926
|
+
const compatibility =
|
|
927
|
+
directCompatibility.kind === 'found'
|
|
928
|
+
? directCompatibility
|
|
929
|
+
: bindingCompatibility;
|
|
930
|
+
if (compatibility.kind === 'absent') return undefined;
|
|
931
|
+
const schema = resolveStaticProperty(
|
|
932
|
+
compatibility.value,
|
|
933
|
+
'toolErrorSchemaVersion',
|
|
934
|
+
context,
|
|
935
|
+
);
|
|
936
|
+
if (schema.kind !== 'found') return null;
|
|
937
|
+
const schemaVersion = staticNumberFromExpression(schema.value, context);
|
|
938
|
+
if (schemaVersion === 0 || schemaVersion === 1) return schemaVersion;
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
|
|
829
942
|
function stringPropertyFromObjectExpression(
|
|
830
943
|
node: AstNode | null | undefined,
|
|
831
944
|
propertyName: string,
|
|
@@ -895,12 +1008,16 @@ function playMetadataFromDefinePlayCall(
|
|
|
895
1008
|
|
|
896
1009
|
const args = astArray(expression.arguments);
|
|
897
1010
|
const firstArg = args[0] ?? null;
|
|
1011
|
+
const isObjectForm =
|
|
1012
|
+
args.length === 1 || objectExpressionFromNode(firstArg, context) !== null;
|
|
898
1013
|
const objectName = stringPropertyFromObjectExpression(
|
|
899
1014
|
firstArg,
|
|
900
1015
|
'id',
|
|
901
1016
|
context,
|
|
902
1017
|
);
|
|
903
|
-
const name =
|
|
1018
|
+
const name = isObjectForm
|
|
1019
|
+
? objectName
|
|
1020
|
+
: staticStringFromExpression(firstArg, context);
|
|
904
1021
|
|
|
905
1022
|
let description =
|
|
906
1023
|
stringPropertyFromObjectExpression(firstArg, 'description', context) ??
|
|
@@ -913,8 +1030,27 @@ function playMetadataFromDefinePlayCall(
|
|
|
913
1030
|
);
|
|
914
1031
|
}
|
|
915
1032
|
|
|
916
|
-
|
|
917
|
-
|
|
1033
|
+
const options = isObjectForm ? firstArg : (args[2] ?? null);
|
|
1034
|
+
const toolErrorSchemaVersion = toolErrorSchemaVersionFromOptions(
|
|
1035
|
+
options,
|
|
1036
|
+
context,
|
|
1037
|
+
);
|
|
1038
|
+
const toolErrorSchemaVersionUnknown = toolErrorSchemaVersion === null;
|
|
1039
|
+
|
|
1040
|
+
if (
|
|
1041
|
+
!name &&
|
|
1042
|
+
!description &&
|
|
1043
|
+
toolErrorSchemaVersion === undefined &&
|
|
1044
|
+
!toolErrorSchemaVersionUnknown
|
|
1045
|
+
) {
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
name,
|
|
1050
|
+
description,
|
|
1051
|
+
toolErrorSchemaVersion: toolErrorSchemaVersion ?? null,
|
|
1052
|
+
toolErrorSchemaVersionUnknown,
|
|
1053
|
+
};
|
|
918
1054
|
}
|
|
919
1055
|
|
|
920
1056
|
function resolveExportExpression(
|
|
@@ -1397,14 +1533,18 @@ async function analyzeSourceGraph(
|
|
|
1397
1533
|
.sort((left, right) => left.filePath.localeCompare(right.filePath)),
|
|
1398
1534
|
}),
|
|
1399
1535
|
);
|
|
1400
|
-
const
|
|
1401
|
-
|
|
1402
|
-
(exportName === 'default' ? extractDefinedPlayName(sourceCode) : null);
|
|
1403
|
-
const playDescription =
|
|
1404
|
-
extractDefinedPlayDescriptionForExport(sourceCode, exportName) ??
|
|
1536
|
+
const metadata =
|
|
1537
|
+
extractDefinedPlayMetadata(sourceCode, exportName) ??
|
|
1405
1538
|
(exportName === 'default'
|
|
1406
|
-
?
|
|
1539
|
+
? extractDefinedPlayMetadata(sourceCode, null)
|
|
1407
1540
|
: null);
|
|
1541
|
+
if (metadata?.toolErrorSchemaVersionUnknown) {
|
|
1542
|
+
throw new Error(
|
|
1543
|
+
'definePlay compatibility.toolErrorSchemaVersion must be the static literal 0 or 1.',
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
const playName = metadata?.name ?? null;
|
|
1547
|
+
const playDescription = metadata?.description ?? null;
|
|
1408
1548
|
|
|
1409
1549
|
return {
|
|
1410
1550
|
sourceCode,
|
|
@@ -1424,6 +1564,7 @@ async function analyzeSourceGraph(
|
|
|
1424
1564
|
},
|
|
1425
1565
|
playName,
|
|
1426
1566
|
playDescription,
|
|
1567
|
+
toolErrorSchemaVersion: metadata?.toolErrorSchemaVersion ?? null,
|
|
1427
1568
|
importedPlayDependencies: [...importedPlayDependencies.values()].sort(
|
|
1428
1569
|
(left, right) => left.filePath.localeCompare(right.filePath),
|
|
1429
1570
|
),
|
|
@@ -1672,6 +1813,10 @@ export async function bundlePlayFile(
|
|
|
1672
1813
|
entryFile: absolutePath,
|
|
1673
1814
|
sourceHash: analysis.sourceHash,
|
|
1674
1815
|
importPolicy: analysis.importPolicy,
|
|
1816
|
+
compatibility: buildPlayContractCompatibility({
|
|
1817
|
+
toolErrorSchemaVersion:
|
|
1818
|
+
analysis.toolErrorSchemaVersion ?? undefined,
|
|
1819
|
+
}),
|
|
1675
1820
|
cacheHit: true,
|
|
1676
1821
|
},
|
|
1677
1822
|
sourceCode: analysis.sourceCode,
|
|
@@ -1736,7 +1881,9 @@ export async function bundlePlayFile(
|
|
|
1736
1881
|
bundledCode: executableCode,
|
|
1737
1882
|
sourceMap: normalizedSourceMap,
|
|
1738
1883
|
importPolicy: analysis.importPolicy,
|
|
1739
|
-
compatibility: buildPlayContractCompatibility(
|
|
1884
|
+
compatibility: buildPlayContractCompatibility({
|
|
1885
|
+
toolErrorSchemaVersion: analysis.toolErrorSchemaVersion ?? undefined,
|
|
1886
|
+
}),
|
|
1740
1887
|
generatedAt: Date.now(),
|
|
1741
1888
|
cacheHit: false,
|
|
1742
1889
|
};
|