deepline 0.1.56 → 0.1.57
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/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/repo/apps/play-runner-workers/src/entry.ts +7 -83
- package/dist/repo/apps/play-runner-workers/src/runtime/tool-http-errors.ts +198 -0
- package/dist/repo/sdk/src/release.ts +2 -2
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -220,10 +220,10 @@ function resolveConfig(options) {
|
|
|
220
220
|
|
|
221
221
|
// src/release.ts
|
|
222
222
|
var SDK_RELEASE = {
|
|
223
|
-
version: "0.1.
|
|
223
|
+
version: "0.1.57",
|
|
224
224
|
apiContract: "2026-05-play-tool-describe-starters",
|
|
225
225
|
supportPolicy: {
|
|
226
|
-
latest: "0.1.
|
|
226
|
+
latest: "0.1.57",
|
|
227
227
|
minimumSupported: "0.1.53",
|
|
228
228
|
deprecatedBelow: "0.1.53"
|
|
229
229
|
}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -197,10 +197,10 @@ function resolveConfig(options) {
|
|
|
197
197
|
|
|
198
198
|
// src/release.ts
|
|
199
199
|
var SDK_RELEASE = {
|
|
200
|
-
version: "0.1.
|
|
200
|
+
version: "0.1.57",
|
|
201
201
|
apiContract: "2026-05-play-tool-describe-starters",
|
|
202
202
|
supportPolicy: {
|
|
203
|
-
latest: "0.1.
|
|
203
|
+
latest: "0.1.57",
|
|
204
204
|
minimumSupported: "0.1.53",
|
|
205
205
|
deprecatedBelow: "0.1.53"
|
|
206
206
|
}
|
package/dist/index.js
CHANGED
|
@@ -216,10 +216,10 @@ function resolveConfig(options) {
|
|
|
216
216
|
|
|
217
217
|
// src/release.ts
|
|
218
218
|
var SDK_RELEASE = {
|
|
219
|
-
version: "0.1.
|
|
219
|
+
version: "0.1.57",
|
|
220
220
|
apiContract: "2026-05-play-tool-describe-starters",
|
|
221
221
|
supportPolicy: {
|
|
222
|
-
latest: "0.1.
|
|
222
|
+
latest: "0.1.57",
|
|
223
223
|
minimumSupported: "0.1.53",
|
|
224
224
|
deprecatedBelow: "0.1.53"
|
|
225
225
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -170,10 +170,10 @@ function resolveConfig(options) {
|
|
|
170
170
|
|
|
171
171
|
// src/release.ts
|
|
172
172
|
var SDK_RELEASE = {
|
|
173
|
-
version: "0.1.
|
|
173
|
+
version: "0.1.57",
|
|
174
174
|
apiContract: "2026-05-play-tool-describe-starters",
|
|
175
175
|
supportPolicy: {
|
|
176
|
-
latest: "0.1.
|
|
176
|
+
latest: "0.1.57",
|
|
177
177
|
minimumSupported: "0.1.53",
|
|
178
178
|
deprecatedBelow: "0.1.53"
|
|
179
179
|
}
|
|
@@ -130,6 +130,12 @@ import type {
|
|
|
130
130
|
LiveNodeProgressMap,
|
|
131
131
|
LiveNodeProgressSnapshot,
|
|
132
132
|
} from './runtime/live-progress';
|
|
133
|
+
import {
|
|
134
|
+
ToolHttpError,
|
|
135
|
+
extractErrorBilling,
|
|
136
|
+
isHardBillingToolHttpError,
|
|
137
|
+
normalizeToolHttpErrorMessage,
|
|
138
|
+
} from './runtime/tool-http-errors';
|
|
133
139
|
|
|
134
140
|
// The play's default export. The bundler injects this — see bundle-play-file.ts.
|
|
135
141
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
@@ -195,23 +201,6 @@ const EXECUTE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
|
|
|
195
201
|
const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
|
|
196
202
|
const V2_EXECUTE_RESPONSE_CONTRACT = 'v2-tool-execution-result';
|
|
197
203
|
|
|
198
|
-
class ToolHttpError extends Error {
|
|
199
|
-
readonly billing: Record<string, unknown> | null;
|
|
200
|
-
|
|
201
|
-
constructor(message: string, billing: Record<string, unknown> | null) {
|
|
202
|
-
super(message);
|
|
203
|
-
this.name = 'ToolHttpError';
|
|
204
|
-
this.billing = billing;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function formatCreditAmount(value: unknown): string {
|
|
209
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
210
|
-
return String(value ?? '-');
|
|
211
|
-
}
|
|
212
|
-
return Number(value.toFixed(8)).toString();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
204
|
function getStringField(value: unknown, key: string): string | null {
|
|
216
205
|
if (!isRecord(value)) return null;
|
|
217
206
|
const field = value[key];
|
|
@@ -227,71 +216,6 @@ function getObjectField(
|
|
|
227
216
|
return isRecord(field) ? field : null;
|
|
228
217
|
}
|
|
229
218
|
|
|
230
|
-
function isInsufficientCreditsBilling(
|
|
231
|
-
billing: Record<string, unknown> | null,
|
|
232
|
-
): billing is Record<string, unknown> {
|
|
233
|
-
return billing?.kind === 'insufficient_credits';
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function formatInsufficientCreditsMessage(input: {
|
|
237
|
-
billing: Record<string, unknown>;
|
|
238
|
-
toolId: string;
|
|
239
|
-
}): string {
|
|
240
|
-
const operation =
|
|
241
|
-
getStringField(input.billing, 'operation_id') ??
|
|
242
|
-
getStringField(input.billing, 'operation') ??
|
|
243
|
-
input.toolId;
|
|
244
|
-
const balance = formatCreditAmount(input.billing.balance_credits);
|
|
245
|
-
const required = formatCreditAmount(input.billing.required_credits);
|
|
246
|
-
const recommended = formatCreditAmount(
|
|
247
|
-
input.billing.recommended_add_credits ?? input.billing.needed_credits,
|
|
248
|
-
);
|
|
249
|
-
const billingUrl = getStringField(input.billing, 'billing_url');
|
|
250
|
-
const addSuffix =
|
|
251
|
-
billingUrl && recommended !== '-'
|
|
252
|
-
? ` Add >=${recommended} at ${billingUrl}.`
|
|
253
|
-
: billingUrl
|
|
254
|
-
? ` Add credits at ${billingUrl}.`
|
|
255
|
-
: '';
|
|
256
|
-
return `Workspace balance ${balance} < required ${required} for ${operation}.${addSuffix}`;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function normalizeToolHttpErrorMessage(input: {
|
|
260
|
-
toolId: string;
|
|
261
|
-
status: number;
|
|
262
|
-
attempt: number;
|
|
263
|
-
maxAttempts: number;
|
|
264
|
-
bodyText: string;
|
|
265
|
-
}): ToolHttpError {
|
|
266
|
-
let parsed: Record<string, unknown> | null = null;
|
|
267
|
-
try {
|
|
268
|
-
const candidate = JSON.parse(input.bodyText);
|
|
269
|
-
parsed = isRecord(candidate) ? candidate : null;
|
|
270
|
-
} catch {
|
|
271
|
-
parsed = null;
|
|
272
|
-
}
|
|
273
|
-
const billing = getObjectField(parsed, 'billing');
|
|
274
|
-
if (isInsufficientCreditsBilling(billing)) {
|
|
275
|
-
return new ToolHttpError(
|
|
276
|
-
`tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${formatInsufficientCreditsMessage(
|
|
277
|
-
{
|
|
278
|
-
billing,
|
|
279
|
-
toolId: input.toolId,
|
|
280
|
-
},
|
|
281
|
-
)}`,
|
|
282
|
-
billing,
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
return new ToolHttpError(
|
|
286
|
-
`tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${input.bodyText.slice(0, 500)}`,
|
|
287
|
-
billing,
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function extractErrorBilling(error: unknown): Record<string, unknown> | null {
|
|
292
|
-
return error instanceof ToolHttpError ? error.billing : null;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
219
|
/** R2 binding injected by the Worker runtime (when present in deploy metadata). */
|
|
296
220
|
type WorkerEnv = {
|
|
297
221
|
PLAYS_BUCKET?: R2Bucket;
|
|
@@ -1348,7 +1272,7 @@ async function callToolDirect(
|
|
|
1348
1272
|
bodyText: text,
|
|
1349
1273
|
});
|
|
1350
1274
|
const retryable =
|
|
1351
|
-
res.status === 429 ||
|
|
1275
|
+
(res.status === 429 && !isHardBillingToolHttpError(lastError)) ||
|
|
1352
1276
|
(res.status >= 500 && WORKER_RETRY_SAFE_5XX_TOOLS.has(toolId));
|
|
1353
1277
|
if (!retryable || attempt >= maxAttempts) {
|
|
1354
1278
|
throw lastError;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export class ToolHttpError extends Error {
|
|
2
|
+
readonly billing: Record<string, unknown> | null;
|
|
3
|
+
|
|
4
|
+
constructor(message: string, billing: Record<string, unknown> | null) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'ToolHttpError';
|
|
7
|
+
this.billing = billing;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatCreditAmount(value: unknown): string {
|
|
12
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
13
|
+
return String(value ?? '-');
|
|
14
|
+
}
|
|
15
|
+
return Number(value.toFixed(8)).toString();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
19
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getStringField(value: unknown, key: string): string | null {
|
|
23
|
+
if (!isRecord(value)) return null;
|
|
24
|
+
const field = value[key];
|
|
25
|
+
return typeof field === 'string' && field.trim() ? field : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getObjectField(
|
|
29
|
+
value: unknown,
|
|
30
|
+
key: string,
|
|
31
|
+
): Record<string, unknown> | null {
|
|
32
|
+
if (!isRecord(value)) return null;
|
|
33
|
+
const field = value[key];
|
|
34
|
+
return isRecord(field) ? field : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isInsufficientCreditsBilling(
|
|
38
|
+
billing: Record<string, unknown> | null,
|
|
39
|
+
): billing is Record<string, unknown> {
|
|
40
|
+
return billing?.kind === 'insufficient_credits';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isHardBillingFailurePayload(
|
|
44
|
+
payload: Record<string, unknown> | null,
|
|
45
|
+
): payload is Record<string, unknown> {
|
|
46
|
+
if (!payload) return false;
|
|
47
|
+
const category = String(
|
|
48
|
+
payload.error_category ?? payload.errorCategory ?? '',
|
|
49
|
+
).toLowerCase();
|
|
50
|
+
const code = String(payload.code ?? payload.error_code ?? '').toUpperCase();
|
|
51
|
+
const message = String(
|
|
52
|
+
payload.error ?? payload.message ?? payload.failure_description ?? '',
|
|
53
|
+
).toLowerCase();
|
|
54
|
+
if (category === 'billing') return true;
|
|
55
|
+
if (
|
|
56
|
+
code === 'INSUFFICIENT_CREDITS' ||
|
|
57
|
+
code === 'BILLING_CAP_EXCEEDED' ||
|
|
58
|
+
code === 'MONTHLY_BILLING_LIMIT_EXCEEDED'
|
|
59
|
+
) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return (
|
|
63
|
+
(message.includes('billing cap') ||
|
|
64
|
+
message.includes('monthly billing limit') ||
|
|
65
|
+
message.includes('rolling 30-day organization billing cap') ||
|
|
66
|
+
message.includes('insufficient credits')) &&
|
|
67
|
+
!message.includes('rate limit')
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeHardBillingPayload(
|
|
72
|
+
payload: Record<string, unknown>,
|
|
73
|
+
): Record<string, unknown> {
|
|
74
|
+
return {
|
|
75
|
+
kind: 'billing_cap_exceeded',
|
|
76
|
+
code:
|
|
77
|
+
typeof payload.code === 'string' && payload.code.trim()
|
|
78
|
+
? payload.code
|
|
79
|
+
: 'MONTHLY_BILLING_LIMIT_EXCEEDED',
|
|
80
|
+
error_category: 'billing',
|
|
81
|
+
failure_origin:
|
|
82
|
+
typeof payload.failure_origin === 'string' && payload.failure_origin.trim()
|
|
83
|
+
? payload.failure_origin
|
|
84
|
+
: 'deepline_billing',
|
|
85
|
+
message:
|
|
86
|
+
typeof payload.error === 'string' && payload.error.trim()
|
|
87
|
+
? payload.error
|
|
88
|
+
: typeof payload.message === 'string' && payload.message.trim()
|
|
89
|
+
? 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 [
|
|
108
|
+
`tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}:`,
|
|
109
|
+
'Deepline billing cap exceeded.',
|
|
110
|
+
'Run halted before marking remaining rows processed.',
|
|
111
|
+
code ? `code=${code}.` : '',
|
|
112
|
+
message,
|
|
113
|
+
]
|
|
114
|
+
.filter(Boolean)
|
|
115
|
+
.join(' ');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatInsufficientCreditsMessage(input: {
|
|
119
|
+
billing: Record<string, unknown>;
|
|
120
|
+
toolId: string;
|
|
121
|
+
}): string {
|
|
122
|
+
const operation =
|
|
123
|
+
getStringField(input.billing, 'operation_id') ??
|
|
124
|
+
getStringField(input.billing, 'operation') ??
|
|
125
|
+
input.toolId;
|
|
126
|
+
const balance = formatCreditAmount(input.billing.balance_credits);
|
|
127
|
+
const required = formatCreditAmount(input.billing.required_credits);
|
|
128
|
+
const recommended = formatCreditAmount(
|
|
129
|
+
input.billing.recommended_add_credits ?? input.billing.needed_credits,
|
|
130
|
+
);
|
|
131
|
+
const billingUrl = getStringField(input.billing, 'billing_url');
|
|
132
|
+
const addSuffix =
|
|
133
|
+
billingUrl && recommended !== '-'
|
|
134
|
+
? ` Add >=${recommended} at ${billingUrl}.`
|
|
135
|
+
: billingUrl
|
|
136
|
+
? ` Add credits at ${billingUrl}.`
|
|
137
|
+
: '';
|
|
138
|
+
return `Workspace balance ${balance} < required ${required} for ${operation}.${addSuffix}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function normalizeToolHttpErrorMessage(input: {
|
|
142
|
+
toolId: string;
|
|
143
|
+
status: number;
|
|
144
|
+
attempt: number;
|
|
145
|
+
maxAttempts: number;
|
|
146
|
+
bodyText: string;
|
|
147
|
+
}): ToolHttpError {
|
|
148
|
+
let parsed: Record<string, unknown> | null = null;
|
|
149
|
+
try {
|
|
150
|
+
const candidate = JSON.parse(input.bodyText);
|
|
151
|
+
parsed = isRecord(candidate) ? candidate : null;
|
|
152
|
+
} catch {
|
|
153
|
+
parsed = null;
|
|
154
|
+
}
|
|
155
|
+
const billing = getObjectField(parsed, 'billing');
|
|
156
|
+
if (isInsufficientCreditsBilling(billing)) {
|
|
157
|
+
return new ToolHttpError(
|
|
158
|
+
`tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${formatInsufficientCreditsMessage(
|
|
159
|
+
{
|
|
160
|
+
billing,
|
|
161
|
+
toolId: input.toolId,
|
|
162
|
+
},
|
|
163
|
+
)}`,
|
|
164
|
+
billing,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const hardBillingPayload = isHardBillingFailurePayload(billing)
|
|
168
|
+
? normalizeHardBillingPayload(billing)
|
|
169
|
+
: isHardBillingFailurePayload(parsed)
|
|
170
|
+
? normalizeHardBillingPayload(parsed)
|
|
171
|
+
: null;
|
|
172
|
+
if (hardBillingPayload) {
|
|
173
|
+
return new ToolHttpError(
|
|
174
|
+
formatHardBillingFailureMessage({
|
|
175
|
+
billing: hardBillingPayload,
|
|
176
|
+
toolId: input.toolId,
|
|
177
|
+
status: input.status,
|
|
178
|
+
attempt: input.attempt,
|
|
179
|
+
maxAttempts: input.maxAttempts,
|
|
180
|
+
}),
|
|
181
|
+
hardBillingPayload,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return new ToolHttpError(
|
|
185
|
+
`tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${input.bodyText.slice(0, 500)}`,
|
|
186
|
+
billing,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function extractErrorBilling(
|
|
191
|
+
error: unknown,
|
|
192
|
+
): Record<string, unknown> | null {
|
|
193
|
+
return error instanceof ToolHttpError ? error.billing : null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function isHardBillingToolHttpError(error: unknown): boolean {
|
|
197
|
+
return error instanceof ToolHttpError && isHardBillingFailurePayload(error.billing);
|
|
198
|
+
}
|
|
@@ -50,10 +50,10 @@ export type SdkRelease = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export const SDK_RELEASE = {
|
|
53
|
-
version: '0.1.
|
|
53
|
+
version: '0.1.57',
|
|
54
54
|
apiContract: '2026-05-play-tool-describe-starters',
|
|
55
55
|
supportPolicy: {
|
|
56
|
-
latest: '0.1.
|
|
56
|
+
latest: '0.1.57',
|
|
57
57
|
minimumSupported: '0.1.53',
|
|
58
58
|
deprecatedBelow: '0.1.53',
|
|
59
59
|
},
|