deepline 0.1.140 → 0.1.141
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/apps/play-runner-workers/src/entry.ts +46 -22
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/live-progress.ts +4 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-http-errors.ts +7 -262
- package/dist/bundling-sources/sdk/src/client.ts +24 -0
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +32 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +54 -34
- package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +32 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +55 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +248 -0
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +4 -0
- package/dist/cli/index.js +165 -42
- package/dist/cli/index.mjs +165 -42
- package/dist/index.d.mts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +36 -2
- package/dist/index.mjs +36 -2
- package/package.json +1 -1
|
@@ -21,6 +21,10 @@ export type PlayVisualNodeProgressSnapshot = {
|
|
|
21
21
|
completed?: number;
|
|
22
22
|
total?: number;
|
|
23
23
|
failed?: number;
|
|
24
|
+
startedRows?: number;
|
|
25
|
+
activeRows?: number;
|
|
26
|
+
waitingRows?: number;
|
|
27
|
+
completedRows?: number;
|
|
24
28
|
message?: string;
|
|
25
29
|
updatedAt?: number | null;
|
|
26
30
|
startedAt?: number | null;
|
|
@@ -28,6 +28,10 @@ export type PlayRunLedgerStepProgress = {
|
|
|
28
28
|
completed?: number;
|
|
29
29
|
total?: number;
|
|
30
30
|
failed?: number;
|
|
31
|
+
startedRows?: number;
|
|
32
|
+
activeRows?: number;
|
|
33
|
+
waitingRows?: number;
|
|
34
|
+
completedRows?: number;
|
|
31
35
|
message?: string;
|
|
32
36
|
artifactTableNamespace?: string | null;
|
|
33
37
|
startedAt?: number | null;
|
|
@@ -430,6 +434,18 @@ function normalizeStepProgress(
|
|
|
430
434
|
...(optionalFiniteNumber(value.failed) !== undefined
|
|
431
435
|
? { failed: optionalFiniteNumber(value.failed) }
|
|
432
436
|
: {}),
|
|
437
|
+
...(optionalFiniteNumber(value.startedRows) !== undefined
|
|
438
|
+
? { startedRows: optionalFiniteNumber(value.startedRows) }
|
|
439
|
+
: {}),
|
|
440
|
+
...(optionalFiniteNumber(value.activeRows) !== undefined
|
|
441
|
+
? { activeRows: optionalFiniteNumber(value.activeRows) }
|
|
442
|
+
: {}),
|
|
443
|
+
...(optionalFiniteNumber(value.waitingRows) !== undefined
|
|
444
|
+
? { waitingRows: optionalFiniteNumber(value.waitingRows) }
|
|
445
|
+
: {}),
|
|
446
|
+
...(optionalFiniteNumber(value.completedRows) !== undefined
|
|
447
|
+
? { completedRows: optionalFiniteNumber(value.completedRows) }
|
|
448
|
+
: {}),
|
|
433
449
|
...(optionalString(value.message)
|
|
434
450
|
? { message: optionalString(value.message) }
|
|
435
451
|
: {}),
|
|
@@ -933,6 +949,10 @@ function progressSignature(
|
|
|
933
949
|
completed: progress?.completed ?? null,
|
|
934
950
|
total: progress?.total ?? null,
|
|
935
951
|
failed: progress?.failed ?? null,
|
|
952
|
+
startedRows: progress?.startedRows ?? null,
|
|
953
|
+
activeRows: progress?.activeRows ?? null,
|
|
954
|
+
waitingRows: progress?.waitingRows ?? null,
|
|
955
|
+
completedRows: progress?.completedRows ?? null,
|
|
936
956
|
message: progress?.message ?? null,
|
|
937
957
|
artifactTableNamespace: progress?.artifactTableNamespace ?? null,
|
|
938
958
|
startedAt: progress?.startedAt ?? null,
|
|
@@ -1152,6 +1172,18 @@ export function buildPlayRunLedgerEventsFromStatusPatch(input: {
|
|
|
1152
1172
|
: {}),
|
|
1153
1173
|
...(progress.total !== undefined ? { total: progress.total } : {}),
|
|
1154
1174
|
...(progress.failed !== undefined ? { failed: progress.failed } : {}),
|
|
1175
|
+
...(progress.startedRows !== undefined
|
|
1176
|
+
? { startedRows: progress.startedRows }
|
|
1177
|
+
: {}),
|
|
1178
|
+
...(progress.activeRows !== undefined
|
|
1179
|
+
? { activeRows: progress.activeRows }
|
|
1180
|
+
: {}),
|
|
1181
|
+
...(progress.waitingRows !== undefined
|
|
1182
|
+
? { waitingRows: progress.waitingRows }
|
|
1183
|
+
: {}),
|
|
1184
|
+
...(progress.completedRows !== undefined
|
|
1185
|
+
? { completedRows: progress.completedRows }
|
|
1186
|
+
: {}),
|
|
1155
1187
|
...(progress.message !== undefined ? { message: progress.message } : {}),
|
|
1156
1188
|
...(progress.artifactTableNamespace !== undefined
|
|
1157
1189
|
? { artifactTableNamespace: progress.artifactTableNamespace }
|
|
@@ -31,6 +31,10 @@ export type PlayRunStreamNodeProgress = {
|
|
|
31
31
|
completed?: number;
|
|
32
32
|
total?: number;
|
|
33
33
|
failed?: number;
|
|
34
|
+
startedRows?: number;
|
|
35
|
+
activeRows?: number;
|
|
36
|
+
waitingRows?: number;
|
|
37
|
+
completedRows?: number;
|
|
34
38
|
message?: string;
|
|
35
39
|
updatedAt?: number | null;
|
|
36
40
|
startedAt?: number | null;
|
|
@@ -192,6 +196,10 @@ function buildSnapshotFromLedger(
|
|
|
192
196
|
completed: step.progress.completed,
|
|
193
197
|
total: step.progress.total,
|
|
194
198
|
failed: step.progress.failed,
|
|
199
|
+
startedRows: step.progress.startedRows,
|
|
200
|
+
activeRows: step.progress.activeRows,
|
|
201
|
+
waitingRows: step.progress.waitingRows,
|
|
202
|
+
completedRows: step.progress.completedRows,
|
|
195
203
|
message: step.progress.message,
|
|
196
204
|
artifactTableNamespace:
|
|
197
205
|
step.progress.artifactTableNamespace ??
|
|
@@ -544,6 +552,10 @@ export function diffPlayRunStreamEvents(input: {
|
|
|
544
552
|
completed: state.progress.completed,
|
|
545
553
|
total: state.progress.total,
|
|
546
554
|
failed: state.progress.failed,
|
|
555
|
+
startedRows: state.progress.startedRows,
|
|
556
|
+
activeRows: state.progress.activeRows,
|
|
557
|
+
waitingRows: state.progress.waitingRows,
|
|
558
|
+
completedRows: state.progress.completedRows,
|
|
547
559
|
message: state.progress.message,
|
|
548
560
|
artifactTableNamespace:
|
|
549
561
|
state.progress.artifactTableNamespace ??
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const TOOL_EXECUTE_TRANSIENT_HTTP_MAX_ATTEMPTS = 3;
|
|
2
|
+
export const TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS = 8;
|
|
3
|
+
export const TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS = 3;
|
|
4
|
+
export const TOOL_EXECUTE_TRANSPORT_RETRY_DELAY_MS = 1_000;
|
|
5
|
+
|
|
6
|
+
export type ToolExecuteHttpRetryDecision = {
|
|
7
|
+
retryable: boolean;
|
|
8
|
+
attemptCap: number;
|
|
9
|
+
reason:
|
|
10
|
+
| 'rate_limit'
|
|
11
|
+
| 'retry_safe_transient_5xx'
|
|
12
|
+
| 'hard_billing_error'
|
|
13
|
+
| 'unsafe_transient_5xx'
|
|
14
|
+
| 'non_retryable_status';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function decideToolExecuteHttpRetry(input: {
|
|
18
|
+
toolId: string;
|
|
19
|
+
status: number;
|
|
20
|
+
hardBillingFailure?: boolean;
|
|
21
|
+
}): ToolExecuteHttpRetryDecision {
|
|
22
|
+
if (input.status === 429) {
|
|
23
|
+
if (input.hardBillingFailure) {
|
|
24
|
+
return {
|
|
25
|
+
retryable: false,
|
|
26
|
+
attemptCap: TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS,
|
|
27
|
+
reason: 'hard_billing_error',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
retryable: true,
|
|
32
|
+
attemptCap: TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS,
|
|
33
|
+
reason: 'rate_limit',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (input.status >= 500 && input.status < 600) {
|
|
37
|
+
if (input.toolId === 'test_transient_500') {
|
|
38
|
+
return {
|
|
39
|
+
retryable: true,
|
|
40
|
+
attemptCap: TOOL_EXECUTE_TRANSIENT_HTTP_MAX_ATTEMPTS,
|
|
41
|
+
reason: 'retry_safe_transient_5xx',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
retryable: false,
|
|
46
|
+
attemptCap: 1,
|
|
47
|
+
reason: 'unsafe_transient_5xx',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
retryable: false,
|
|
52
|
+
attemptCap: 1,
|
|
53
|
+
reason: 'non_retryable_status',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
export class ToolHttpError extends Error {
|
|
2
|
+
readonly billing: Record<string, unknown> | null;
|
|
3
|
+
/** HTTP status of the failed tool-execute response (e.g. 429, 502). */
|
|
4
|
+
readonly status: number;
|
|
5
|
+
|
|
6
|
+
constructor(
|
|
7
|
+
message: string,
|
|
8
|
+
billing: Record<string, unknown> | null,
|
|
9
|
+
status: number,
|
|
10
|
+
) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'ToolHttpError';
|
|
13
|
+
this.billing = billing;
|
|
14
|
+
this.status = status;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatCreditAmount(value: unknown): string {
|
|
19
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
20
|
+
return String(value ?? '-');
|
|
21
|
+
}
|
|
22
|
+
return Number(value.toFixed(8)).toString();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
26
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getStringField(value: unknown, key: string): string | null {
|
|
30
|
+
if (!isRecord(value)) return null;
|
|
31
|
+
const field = value[key];
|
|
32
|
+
return typeof field === 'string' && field.trim() ? field : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getObjectField(
|
|
36
|
+
value: unknown,
|
|
37
|
+
key: string,
|
|
38
|
+
): Record<string, unknown> | null {
|
|
39
|
+
if (!isRecord(value)) return null;
|
|
40
|
+
const field = value[key];
|
|
41
|
+
return isRecord(field) ? field : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isInsufficientCreditsBilling(
|
|
45
|
+
billing: Record<string, unknown> | null,
|
|
46
|
+
): billing is Record<string, unknown> {
|
|
47
|
+
return billing?.kind === 'insufficient_credits';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isHardBillingFailurePayload(
|
|
51
|
+
payload: Record<string, unknown> | null,
|
|
52
|
+
): payload is Record<string, unknown> {
|
|
53
|
+
if (!payload) return false;
|
|
54
|
+
const category = String(
|
|
55
|
+
payload.error_category ?? payload.errorCategory ?? '',
|
|
56
|
+
).toLowerCase();
|
|
57
|
+
const code = String(payload.code ?? payload.error_code ?? '').toUpperCase();
|
|
58
|
+
const message = String(
|
|
59
|
+
payload.error ?? payload.message ?? payload.failure_description ?? '',
|
|
60
|
+
).toLowerCase();
|
|
61
|
+
if (category === 'billing') return true;
|
|
62
|
+
if (
|
|
63
|
+
code === 'INSUFFICIENT_CREDITS' ||
|
|
64
|
+
code === 'BILLING_CAP_EXCEEDED' ||
|
|
65
|
+
code === 'MONTHLY_BILLING_LIMIT_EXCEEDED'
|
|
66
|
+
) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return (
|
|
70
|
+
(message.includes('billing cap') ||
|
|
71
|
+
message.includes('monthly billing limit') ||
|
|
72
|
+
message.includes('rolling 30-day organization billing cap') ||
|
|
73
|
+
message.includes('insufficient credits')) &&
|
|
74
|
+
!message.includes('rate limit')
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeHardBillingPayload(
|
|
79
|
+
payload: Record<string, unknown>,
|
|
80
|
+
): Record<string, unknown> {
|
|
81
|
+
return {
|
|
82
|
+
kind: 'billing_cap_exceeded',
|
|
83
|
+
code: getStringField(payload, 'code') ?? 'MONTHLY_BILLING_LIMIT_EXCEEDED',
|
|
84
|
+
error_category: 'billing',
|
|
85
|
+
failure_origin:
|
|
86
|
+
getStringField(payload, 'failure_origin') ?? 'deepline_billing',
|
|
87
|
+
message:
|
|
88
|
+
getStringField(payload, 'error') ??
|
|
89
|
+
getStringField(payload, 'message') ??
|
|
90
|
+
'Deepline billing cap exceeded.',
|
|
91
|
+
...payload,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatHardBillingFailureMessage(input: {
|
|
96
|
+
billing: Record<string, unknown>;
|
|
97
|
+
toolId: string;
|
|
98
|
+
status: number;
|
|
99
|
+
attempt: number;
|
|
100
|
+
maxAttempts: number;
|
|
101
|
+
}): string {
|
|
102
|
+
const code = getStringField(input.billing, 'code');
|
|
103
|
+
const message =
|
|
104
|
+
getStringField(input.billing, 'message') ??
|
|
105
|
+
getStringField(input.billing, 'error') ??
|
|
106
|
+
'Deepline billing cap exceeded.';
|
|
107
|
+
return `tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: Deepline billing cap exceeded. Run halted before marking remaining rows processed. ${code ? `code=${code}. ` : ''}${message}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatInsufficientCreditsMessage(input: {
|
|
111
|
+
billing: Record<string, unknown>;
|
|
112
|
+
toolId: string;
|
|
113
|
+
}): string {
|
|
114
|
+
const operation =
|
|
115
|
+
getStringField(input.billing, 'operation_id') ??
|
|
116
|
+
getStringField(input.billing, 'operation') ??
|
|
117
|
+
input.toolId;
|
|
118
|
+
const balance = formatCreditAmount(input.billing.balance_credits);
|
|
119
|
+
const required = formatCreditAmount(input.billing.required_credits);
|
|
120
|
+
const recommended = formatCreditAmount(
|
|
121
|
+
input.billing.recommended_add_credits ?? input.billing.needed_credits,
|
|
122
|
+
);
|
|
123
|
+
const billingUrl = getStringField(input.billing, 'billing_url');
|
|
124
|
+
const addSuffix =
|
|
125
|
+
billingUrl && recommended !== '-'
|
|
126
|
+
? ` Add >=${recommended} at ${billingUrl}.`
|
|
127
|
+
: billingUrl
|
|
128
|
+
? ` Add credits at ${billingUrl}.`
|
|
129
|
+
: '';
|
|
130
|
+
return `Workspace balance ${balance} < required ${required} for ${operation}.${addSuffix}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatPublicToolErrorPayload(input: {
|
|
134
|
+
parsed: Record<string, unknown> | null;
|
|
135
|
+
bodyText: string;
|
|
136
|
+
}): string {
|
|
137
|
+
if (!input.parsed) {
|
|
138
|
+
return input.bodyText.slice(0, 500);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const selected: Record<string, unknown> = {};
|
|
142
|
+
for (const key of [
|
|
143
|
+
'error',
|
|
144
|
+
'message',
|
|
145
|
+
'code',
|
|
146
|
+
'failure_origin',
|
|
147
|
+
'error_category',
|
|
148
|
+
'failure_description',
|
|
149
|
+
'operator_hint',
|
|
150
|
+
'failure_hint',
|
|
151
|
+
'details',
|
|
152
|
+
'provider',
|
|
153
|
+
'operation',
|
|
154
|
+
'request_id',
|
|
155
|
+
'requestId',
|
|
156
|
+
'credential_source',
|
|
157
|
+
'credential_owner',
|
|
158
|
+
]) {
|
|
159
|
+
const value = input.parsed[key];
|
|
160
|
+
if (typeof value === 'string' && value.trim()) {
|
|
161
|
+
selected[key] = value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return JSON.stringify(
|
|
166
|
+
Object.keys(selected).length > 0 ? selected : input.parsed,
|
|
167
|
+
).slice(0, 1_500);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function normalizeToolHttpErrorMessage(input: {
|
|
171
|
+
toolId: string;
|
|
172
|
+
status: number;
|
|
173
|
+
attempt: number;
|
|
174
|
+
maxAttempts: number;
|
|
175
|
+
bodyText: string;
|
|
176
|
+
}): ToolHttpError {
|
|
177
|
+
let parsed: Record<string, unknown> | null = null;
|
|
178
|
+
try {
|
|
179
|
+
const candidate = JSON.parse(input.bodyText);
|
|
180
|
+
parsed = isRecord(candidate) ? candidate : null;
|
|
181
|
+
} catch {
|
|
182
|
+
parsed = null;
|
|
183
|
+
}
|
|
184
|
+
const billing = getObjectField(parsed, 'billing');
|
|
185
|
+
if (isInsufficientCreditsBilling(billing)) {
|
|
186
|
+
return new ToolHttpError(
|
|
187
|
+
`tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${formatInsufficientCreditsMessage(
|
|
188
|
+
{
|
|
189
|
+
billing,
|
|
190
|
+
toolId: input.toolId,
|
|
191
|
+
},
|
|
192
|
+
)}`,
|
|
193
|
+
billing,
|
|
194
|
+
input.status,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
const hardBillingPayload = isHardBillingFailurePayload(billing)
|
|
198
|
+
? normalizeHardBillingPayload(billing)
|
|
199
|
+
: isHardBillingFailurePayload(parsed)
|
|
200
|
+
? normalizeHardBillingPayload(parsed)
|
|
201
|
+
: null;
|
|
202
|
+
if (hardBillingPayload) {
|
|
203
|
+
return new ToolHttpError(
|
|
204
|
+
formatHardBillingFailureMessage({
|
|
205
|
+
billing: hardBillingPayload,
|
|
206
|
+
toolId: input.toolId,
|
|
207
|
+
status: input.status,
|
|
208
|
+
attempt: input.attempt,
|
|
209
|
+
maxAttempts: input.maxAttempts,
|
|
210
|
+
}),
|
|
211
|
+
hardBillingPayload,
|
|
212
|
+
input.status,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return new ToolHttpError(
|
|
216
|
+
`tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${formatPublicToolErrorPayload(
|
|
217
|
+
{
|
|
218
|
+
parsed,
|
|
219
|
+
bodyText: input.bodyText,
|
|
220
|
+
},
|
|
221
|
+
)}`,
|
|
222
|
+
billing,
|
|
223
|
+
input.status,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function extractErrorBilling(
|
|
228
|
+
error: unknown,
|
|
229
|
+
): Record<string, unknown> | null {
|
|
230
|
+
return error instanceof ToolHttpError ? error.billing : null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function isHardBillingToolHttpError(error: unknown): boolean {
|
|
234
|
+
return (
|
|
235
|
+
error instanceof ToolHttpError &&
|
|
236
|
+
(isInsufficientCreditsBilling(error.billing) ||
|
|
237
|
+
isHardBillingFailurePayload(error.billing))
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A tool call that ultimately failed with HTTP 429 — provider or
|
|
243
|
+
* Deepline-internal rate-limit pushback that survived the in-process retry
|
|
244
|
+
* budget. This is run-level throughput pressure, never a row-specific defect.
|
|
245
|
+
*/
|
|
246
|
+
export function isRateLimitToolHttpError(error: unknown): boolean {
|
|
247
|
+
return error instanceof ToolHttpError && error.status === 429;
|
|
248
|
+
}
|
|
@@ -12,6 +12,10 @@ export type PlayVisualNodeProgress = {
|
|
|
12
12
|
completed?: number;
|
|
13
13
|
total?: number;
|
|
14
14
|
failed?: number;
|
|
15
|
+
startedRows?: number;
|
|
16
|
+
activeRows?: number;
|
|
17
|
+
waitingRows?: number;
|
|
18
|
+
completedRows?: number;
|
|
15
19
|
message?: string;
|
|
16
20
|
updatedAt?: number;
|
|
17
21
|
startedAt?: number;
|