deepline 0.3.22 → 0.3.24

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.
@@ -1033,6 +1033,28 @@ export type TargetBillingStatusResult = {
1033
1033
  as_of: string | null;
1034
1034
  };
1035
1035
 
1036
+ export type TargetAutoRechargeResult = {
1037
+ org_id: string;
1038
+ enabled: boolean;
1039
+ available: boolean;
1040
+ reason: string | null;
1041
+ threshold_credits: number | null;
1042
+ refill_to_credits: number | null;
1043
+ rolling_limit_cents: null;
1044
+ };
1045
+
1046
+ export type TargetAutoRechargeUpdateOptions =
1047
+ | {
1048
+ enabled: false;
1049
+ idempotencyKey: string;
1050
+ }
1051
+ | {
1052
+ enabled: true;
1053
+ thresholdCredits: number;
1054
+ refillToCredits: number;
1055
+ idempotencyKey: string;
1056
+ };
1057
+
1036
1058
  export type TargetBillingMutationResult = {
1037
1059
  data: Record<string, unknown>;
1038
1060
  operation: TargetBillingOperation;
@@ -1158,6 +1180,13 @@ export type BillingNamespace = {
1158
1180
  targetPlans: () => Promise<TargetBillingPlansResult>;
1159
1181
  /** Normalized target billing state. */
1160
1182
  targetStatus: () => Promise<TargetBillingStatusResult>;
1183
+ /** Read and manage the Metronome-backed automatic recharge configuration. */
1184
+ autoRecharge: {
1185
+ get: () => Promise<TargetAutoRechargeResult>;
1186
+ update: (
1187
+ options: TargetAutoRechargeUpdateOptions,
1188
+ ) => Promise<TargetAutoRechargeResult>;
1189
+ };
1161
1190
  /** Buy Deepline credits through a payment-gated Metronome commit. */
1162
1191
  purchaseCredits: (options: {
1163
1192
  credits: number;
@@ -1750,6 +1779,10 @@ export class DeeplineClient {
1750
1779
  },
1751
1780
  targetPlans: () => this.getTargetBillingPlans(),
1752
1781
  targetStatus: () => this.getTargetBillingStatus(),
1782
+ autoRecharge: {
1783
+ get: () => this.getTargetAutoRecharge(),
1784
+ update: (options) => this.updateTargetAutoRecharge(options),
1785
+ },
1753
1786
  purchaseCredits: (options) => this.purchaseTargetBillingCredits(options),
1754
1787
  transitionPlan: (options) => this.transitionTargetBillingPlan(options),
1755
1788
  portalSession: () => this.createTargetBillingPortalSession(),
@@ -4537,6 +4570,37 @@ export class DeeplineClient {
4537
4570
  return this.http.get<TargetBillingStatusResult>('/api/v2/billing/status');
4538
4571
  }
4539
4572
 
4573
+ /** Read the canonical Metronome automatic recharge configuration. */
4574
+ async getTargetAutoRecharge(): Promise<TargetAutoRechargeResult> {
4575
+ return this.http.get<TargetAutoRechargeResult>(
4576
+ '/api/v2/billing/auto-recharge',
4577
+ );
4578
+ }
4579
+
4580
+ /** Update automatic recharge and return the server-verified configuration. */
4581
+ async updateTargetAutoRecharge(
4582
+ options: TargetAutoRechargeUpdateOptions,
4583
+ ): Promise<TargetAutoRechargeResult> {
4584
+ const idempotencyKey = requireTargetBillingIdempotencyKey(
4585
+ options.idempotencyKey,
4586
+ );
4587
+ const response = await this.http.put<{
4588
+ data: TargetAutoRechargeResult;
4589
+ request_id?: string;
4590
+ }>(
4591
+ '/api/v2/billing/auto-recharge',
4592
+ options.enabled
4593
+ ? {
4594
+ enabled: true,
4595
+ threshold_credits: options.thresholdCredits,
4596
+ refill_to_credits: options.refillToCredits,
4597
+ }
4598
+ : { enabled: false },
4599
+ { 'Idempotency-Key': idempotencyKey },
4600
+ );
4601
+ return response.data;
4602
+ }
4603
+
4540
4604
  /**
4541
4605
  * Purchase target-billing credits through the durable commercial operation
4542
4606
  * flow. The caller supplies an idempotency key for safe retries.
@@ -18,9 +18,6 @@
18
18
  *
19
19
  * @module
20
20
  */
21
- import { existsSync, readFileSync } from 'node:fs';
22
- import { homedir } from 'node:os';
23
- import { join } from 'node:path';
24
21
  import type { ResolvedConfig } from './types.js';
25
22
  import {
26
23
  AuthError,
@@ -36,8 +33,8 @@ import {
36
33
  } from '../../shared_libs/tool-execution-error.js';
37
34
  import { SDK_API_CONTRACT, SDK_VERSION } from './version.js';
38
35
  import type { LiveEventEnvelope } from './types.js';
39
- import { baseUrlSlug, sdkCliStateDirPath } from './config.js';
40
36
  import { detectAgentRuntime, isCoworkLikeSandbox } from './agent-runtime.js';
37
+ import { readSdkSkillsLocalVersion } from './skills-version.js';
41
38
  import {
42
39
  ABSURD_RELEASE_OVERRIDE_HEADER,
43
40
  COORDINATOR_INTERNAL_TOKEN_HEADER,
@@ -201,23 +198,9 @@ export class HttpClient {
201
198
  );
202
199
  if (explicit) return explicit;
203
200
  try {
204
- const versionPath = join(
205
- sdkCliStateDirPath(this.config.baseUrl),
206
- 'skills-version',
201
+ return this.cleanDiagnosticHeader(
202
+ readSdkSkillsLocalVersion(this.config.baseUrl),
207
203
  );
208
- const legacyVersionPath = join(
209
- process.env.HOME?.trim() || homedir(),
210
- '.local',
211
- 'deepline',
212
- baseUrlSlug(this.config.baseUrl),
213
- 'sdk-skills',
214
- '.version',
215
- );
216
- const resolvedPath = existsSync(versionPath)
217
- ? versionPath
218
- : legacyVersionPath;
219
- if (!existsSync(resolvedPath)) return null;
220
- return this.cleanDiagnosticHeader(readFileSync(resolvedPath, 'utf-8'));
221
204
  } catch {
222
205
  return null;
223
206
  }
@@ -192,7 +192,7 @@ export const SDK_RELEASE = {
192
192
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
193
193
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
194
194
  // getters keep their established compatibility behavior.
195
- version: '0.3.22',
195
+ version: '0.3.24',
196
196
  updateSummary:
197
197
  'New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.',
198
198
  contracts: {
@@ -0,0 +1,107 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { detectAgentRuntime } from './agent-runtime.js';
4
+ import { sdkCliStateDirPath } from './config.js';
5
+
6
+ function activePluginSkillsDir(): string {
7
+ const pluginMode = process.env.DEEPLINE_PLUGIN_MODE?.trim().toLowerCase();
8
+ if (
9
+ pluginMode !== 'true' &&
10
+ pluginMode !== '1' &&
11
+ pluginMode !== 'yes' &&
12
+ pluginMode !== 'on'
13
+ ) {
14
+ return '';
15
+ }
16
+ const dir = process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim() ?? '';
17
+ return dir && existsSync(dir) ? dir : '';
18
+ }
19
+
20
+ export function hasActivePluginSkills(): boolean {
21
+ return Boolean(activePluginSkillsDir());
22
+ }
23
+
24
+ function readPluginSkillsVersion(): string {
25
+ const dir = activePluginSkillsDir();
26
+ if (!dir) return '';
27
+ try {
28
+ return readFileSync(join(dir, '.version'), 'utf-8').trim();
29
+ } catch {
30
+ return '';
31
+ }
32
+ }
33
+
34
+ function sdkSkillsVersionPath(
35
+ baseUrl: string,
36
+ agents: readonly string[] = [],
37
+ ): string {
38
+ const suffix = agents.length > 0 ? `-${agents.join('-')}` : '';
39
+ return join(sdkCliStateDirPath(baseUrl), `skills${suffix}-version`);
40
+ }
41
+
42
+ function legacySdkSkillsVersionPath(baseUrl: string): string {
43
+ return join(dirname(sdkCliStateDirPath(baseUrl)), 'sdk-skills', '.version');
44
+ }
45
+
46
+ export function resolveAutoSyncSkillAgents(): string[] {
47
+ switch (detectAgentRuntime()) {
48
+ case 'codex':
49
+ return ['codex'];
50
+ case 'claude_code':
51
+ return ['claude-code'];
52
+ case 'cursor':
53
+ return ['cursor'];
54
+ case 'gemini':
55
+ return ['gemini-cli'];
56
+ case 'antigravity':
57
+ return ['antigravity'];
58
+ default:
59
+ return [];
60
+ }
61
+ }
62
+
63
+ export function readSdkSkillsLocalVersion(baseUrl: string): string {
64
+ const pluginVersion = readPluginSkillsVersion();
65
+ if (pluginVersion) return pluginVersion;
66
+
67
+ const agents = resolveAutoSyncSkillAgents();
68
+ const scopedPath = sdkSkillsVersionPath(baseUrl, agents);
69
+ if (agents.length > 0 && existsSync(scopedPath)) {
70
+ try {
71
+ return readFileSync(scopedPath, 'utf-8').trim();
72
+ } catch {
73
+ return '';
74
+ }
75
+ }
76
+ // Legacy clients wrote a host-wide version after updating only their active
77
+ // agent. A detected agent without its own marker must therefore re-check
78
+ // instead of assuming another agent's legacy install applies to it.
79
+ if (agents.length > 0) {
80
+ const legacyPath = legacySdkSkillsVersionPath(baseUrl);
81
+ if (!existsSync(legacyPath)) return '';
82
+ try {
83
+ return readFileSync(legacyPath, 'utf-8').trim();
84
+ } catch {
85
+ return '';
86
+ }
87
+ }
88
+ const path = existsSync(sdkSkillsVersionPath(baseUrl))
89
+ ? sdkSkillsVersionPath(baseUrl)
90
+ : legacySdkSkillsVersionPath(baseUrl);
91
+ if (!existsSync(path)) return '';
92
+ try {
93
+ return readFileSync(path, 'utf-8').trim();
94
+ } catch {
95
+ return '';
96
+ }
97
+ }
98
+
99
+ export function writeSdkSkillsLocalVersion(
100
+ baseUrl: string,
101
+ version: string,
102
+ agents: readonly string[],
103
+ ): void {
104
+ const path = sdkSkillsVersionPath(baseUrl, agents);
105
+ mkdirSync(dirname(path), { recursive: true });
106
+ writeFileSync(path, `${version}\n`, 'utf-8');
107
+ }
@@ -1280,6 +1280,17 @@ export interface PlayCheckResult {
1280
1280
  valid: boolean;
1281
1281
  errors: string[];
1282
1282
  warnings?: string[];
1283
+ /**
1284
+ * Effective sandbox limits selected for this Play when authoring-contract
1285
+ * preflight succeeded. A valid modern check includes the default 30-minute
1286
+ * timeout when the author did not declare `runtime`.
1287
+ */
1288
+ runtimeLimit?: {
1289
+ timeoutSeconds: number;
1290
+ memoryGiB: number;
1291
+ cpu: number;
1292
+ diskGiB: number;
1293
+ } | null;
1283
1294
  staticPipeline?: Record<string, unknown> | null;
1284
1295
  toolGetterHints?: PlayCheckToolGetterHint[];
1285
1296
  /**
@@ -1308,6 +1319,11 @@ export interface PlayCheckResult {
1308
1319
  * `1 trigger · 2 tools · 1 dataset · 14 columns`.
1309
1320
  */
1310
1321
  summary?: string;
1322
+ /**
1323
+ * Feature-gated validation paths that were enabled for this exact cloud
1324
+ * check. Present as an empty array when no feature gate affected the result.
1325
+ */
1326
+ featureFlags?: PlayCheckFeatureFlag[];
1311
1327
  artifactHash?: string | null;
1312
1328
  graphHash?: string | null;
1313
1329
  /** SHA-256 of the exact source bytes checked by Deepline. */
@@ -1338,9 +1354,24 @@ export interface PlayCheckResult {
1338
1354
  limitBytes: number;
1339
1355
  withinLimit: boolean;
1340
1356
  };
1357
+ /** Present when this Play declares a cron binding. Advisory only; publish reserves capacity. */
1358
+ activeScheduledPlays?: {
1359
+ used: number;
1360
+ limit: number;
1361
+ remaining: number;
1362
+ approachingLimit: boolean;
1363
+ };
1341
1364
  };
1342
1365
  }
1343
1366
 
1367
+ /** An enabled server-side feature flag that affected a Play check. */
1368
+ export interface PlayCheckFeatureFlag {
1369
+ id: string;
1370
+ label: string;
1371
+ enabled: true;
1372
+ reason: string;
1373
+ }
1374
+
1344
1375
  /**
1345
1376
  * One exported play's check result inside a multi-play file. Carries the same
1346
1377
  * per-play fields as {@link PlayCheckResult}, unprefixed and unaggregated, so a
@@ -1360,6 +1391,7 @@ export interface PlayCheckExportResult {
1360
1391
  graphHash?: string | null;
1361
1392
  sourceHash?: string | null;
1362
1393
  summary?: string;
1394
+ featureFlags?: PlayCheckFeatureFlag[];
1363
1395
  recognized?: PlayCheckRecognizedSummary;
1364
1396
  triggers?: PlayCheckTriggersSummary | null;
1365
1397
  }
@@ -52,6 +52,7 @@ import {
52
52
  import { vercelProtectionBypassHeaders } from '@shared_libs/play-runtime/vercel-protection';
53
53
  import type { RuntimeReceiptAction } from '@shared_libs/play-runtime/runtime-actions';
54
54
  import { RUNTIME_CAPACITY_POLICY } from '@shared_libs/play-runtime/runtime-capacity-policy';
55
+ import { RUNTIME_RELIABILITY_POLICY } from '@shared_libs/play-runtime/runtime-reliability-policy';
55
56
  import {
56
57
  DEFAULT_RUNTIME_TRAFFIC_POLICY,
57
58
  isRuntimeTrafficPolicy,
@@ -258,6 +259,10 @@ function applyRetryJitter(delayMs: number): number {
258
259
  }
259
260
  const APP_RUNTIME_API_DEFAULT_REQUEST_TIMEOUT_MS =
260
261
  RUNTIME_CAPACITY_POLICY.receiptGateway.requestTimeoutMs;
262
+ const SIGNED_R2_FETCH_HEADERS_TIMEOUT_MS =
263
+ RUNTIME_RELIABILITY_POLICY.egress.fetchHeadersTimeoutMs;
264
+ const SIGNED_R2_FETCH_BODY_TIMEOUT_MS =
265
+ RUNTIME_RELIABILITY_POLICY.egress.fetchBodyTimeoutMs;
261
266
  const APP_RUNTIME_RECEIPT_RETRY_TELEMETRY_TAG =
262
267
  '[perf][worker.receipt_api.transport]';
263
268
  const RUN_STATUS_LEDGER_SNAPSHOT_CACHE_LIMIT = 1_000;
@@ -1338,6 +1343,19 @@ type SignedR2ReadUrlResponse = {
1338
1343
  expiresAt: string;
1339
1344
  };
1340
1345
 
1346
+ class SignedR2FetchTimeoutError extends Error {
1347
+ constructor(
1348
+ kind: 'artifact' | 'staged_file',
1349
+ phase: 'headers' | 'body',
1350
+ timeoutMs: number,
1351
+ ) {
1352
+ super(
1353
+ `Signed R2 ${kind} fetch exceeded its ${phase} deadline after ${timeoutMs}ms.`,
1354
+ );
1355
+ this.name = 'SignedR2FetchTimeoutError';
1356
+ }
1357
+ }
1358
+
1341
1359
  function logSignedR2FetchPerf(input: {
1342
1360
  kind: 'artifact' | 'staged_file';
1343
1361
  storageKey: string;
@@ -1352,18 +1370,67 @@ function logSignedR2FetchPerf(input: {
1352
1370
  });
1353
1371
  }
1354
1372
 
1373
+ async function runSignedR2FetchPhase<T>(input: {
1374
+ controller: AbortController;
1375
+ kind: 'artifact' | 'staged_file';
1376
+ phase: 'headers' | 'body';
1377
+ run: () => Promise<T>;
1378
+ timeoutMs: number;
1379
+ }): Promise<T> {
1380
+ let timeout: ReturnType<typeof setTimeout> | null = null;
1381
+ const timeoutPromise = new Promise<never>((_resolve, reject) => {
1382
+ timeout = setTimeout(() => {
1383
+ const error = new SignedR2FetchTimeoutError(
1384
+ input.kind,
1385
+ input.phase,
1386
+ input.timeoutMs,
1387
+ );
1388
+ input.controller.abort(error);
1389
+ reject(error);
1390
+ }, input.timeoutMs);
1391
+ });
1392
+ try {
1393
+ return await Promise.race([input.run(), timeoutPromise]);
1394
+ } finally {
1395
+ if (timeout) clearTimeout(timeout);
1396
+ }
1397
+ }
1398
+
1399
+ async function fetchSignedR2Response(input: {
1400
+ kind: 'artifact' | 'staged_file';
1401
+ signed: SignedR2ReadUrlResponse;
1402
+ }): Promise<{ controller: AbortController; response: Response }> {
1403
+ const controller = new AbortController();
1404
+ const response = await runSignedR2FetchPhase({
1405
+ controller,
1406
+ kind: input.kind,
1407
+ phase: 'headers',
1408
+ timeoutMs: SIGNED_R2_FETCH_HEADERS_TIMEOUT_MS,
1409
+ run: () => fetch(input.signed.url, { signal: controller.signal }),
1410
+ });
1411
+ return { controller, response };
1412
+ }
1413
+
1355
1414
  async function fetchSignedR2Buffer(input: {
1356
1415
  kind: 'artifact' | 'staged_file';
1357
1416
  signed: SignedR2ReadUrlResponse;
1358
1417
  }): Promise<Buffer> {
1359
1418
  const startedAt = Date.now();
1360
- const response = await fetch(input.signed.url);
1419
+ const { controller, response } = await fetchSignedR2Response(input);
1361
1420
  if (!response.ok) {
1362
1421
  throw new Error(
1363
1422
  `Signed R2 ${input.kind} fetch failed for ${input.signed.storageKey} with status ${response.status}: ${await response.text()}`,
1364
1423
  );
1365
1424
  }
1366
- const buffer = Buffer.from(await response.arrayBuffer());
1425
+ const buffer = Buffer.from(
1426
+ await runSignedR2FetchPhase({
1427
+ controller,
1428
+ kind: input.kind,
1429
+ phase: 'body',
1430
+ timeoutMs: SIGNED_R2_FETCH_BODY_TIMEOUT_MS,
1431
+ run: () => response.arrayBuffer(),
1432
+ }),
1433
+ );
1367
1434
  logSignedR2FetchPerf({
1368
1435
  kind: input.kind,
1369
1436
  storageKey: input.signed.storageKey,
@@ -1379,7 +1446,7 @@ async function fetchSignedR2ToFile(input: {
1379
1446
  targetPath: string;
1380
1447
  }): Promise<void> {
1381
1448
  const startedAt = Date.now();
1382
- const response = await fetch(input.signed.url);
1449
+ const { controller, response } = await fetchSignedR2Response(input);
1383
1450
  if (!response.ok) {
1384
1451
  throw new Error(
1385
1452
  `Signed R2 ${input.kind} fetch failed for ${input.signed.storageKey} with status ${response.status}: ${await response.text()}`,
@@ -1390,10 +1457,19 @@ async function fetchSignedR2ToFile(input: {
1390
1457
  `Signed R2 ${input.kind} fetch returned an empty response body for ${input.signed.storageKey}.`,
1391
1458
  );
1392
1459
  }
1393
- await pipeline(
1394
- Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]),
1395
- createWriteStream(input.targetPath),
1396
- );
1460
+ await runSignedR2FetchPhase({
1461
+ controller,
1462
+ kind: input.kind,
1463
+ phase: 'body',
1464
+ timeoutMs: SIGNED_R2_FETCH_BODY_TIMEOUT_MS,
1465
+ run: async () =>
1466
+ await pipeline(
1467
+ Readable.fromWeb(
1468
+ response.body as Parameters<typeof Readable.fromWeb>[0],
1469
+ ),
1470
+ createWriteStream(input.targetPath),
1471
+ ),
1472
+ });
1397
1473
  const written = await stat(input.targetPath);
1398
1474
  logSignedR2FetchPerf({
1399
1475
  kind: input.kind,
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // "Projection" is the step that turns a provider response into the value a
4
4
  // target (email, email_status, phone, ...) resolves to. Historically this was
5
- // reimplemented in three runtimes (the V2 tool-result runtime, the playground
5
+ // reimplemented in three runtimes (the V2 tool-result runtime, the portable
6
6
  // waterfall runtime, and the emitted V1-enrich play) with divergent precedence
7
7
  // — a latent drift bug class. This module is the one authoritative
8
8
  // implementation: callers supply a `ProjectionLookup` that knows how to walk
@@ -29,7 +29,7 @@ export type ProjectionHit = { value: unknown; path: string } | null;
29
29
  * The only seam-crossing dependency. Each runtime supplies an adapter that
30
30
  * resolves a list of candidate paths against its own payload shape and returns
31
31
  * the first meaningful hit (or null). This absorbs the input-shape difference
32
- * (V2 `{toolResponse:{raw}}` envelope vs playground raw payload vs the enrich
32
+ * (V2 `{toolResponse:{raw}}` envelope vs portable raw payload vs the enrich
33
33
  * play's pre-projected getters); the interpreter itself is payload-agnostic.
34
34
  */
35
35
  export type ProjectionLookup = (paths: readonly string[]) => ProjectionHit;
@@ -18,6 +18,7 @@ const RUNTIME_SANDBOX_START_FAILED_RE = /\bRUNTIME_SANDBOX_START_FAILED\b/i;
18
18
  const RUNTIME_SANDBOX_OOM_RE =
19
19
  /\bRUNTIME_SANDBOX_OOM\b|(?:javascript heap out of memory|fatal error:.*(?:heap|allocation).*memory)/i;
20
20
  const RUNTIME_SANDBOX_KILLED_RE = /\bRUNTIME_SANDBOX_KILLED\b/i;
21
+ const MODAL_PAYLOAD_UPLOAD_TIMEOUT_RE = /\bMODAL_PAYLOAD_UPLOAD_TIMEOUT\b/i;
21
22
  const OUTPUT_TOO_LARGE_RE = /\b(?:OUTPUT_TOO_LARGE|OutputTooLarge)\b/;
22
23
 
23
24
  export const PLATFORM_DEPLOY_INTERRUPTED_MESSAGE =
@@ -48,6 +49,8 @@ export const RUNTIME_SANDBOX_INSPECTION_UNAVAILABLE_MESSAGE =
48
49
  // carries: nothing ran, so no provider call can already exist.
49
50
  export const RUNTIME_SANDBOX_START_FAILED_MESSAGE =
50
51
  'The execution sandbox never finished starting, so this play never began running. Re-run the same command; if this keeps happening, contact Deepline support with the run ID.';
52
+ export const MODAL_PAYLOAD_UPLOAD_TIMEOUT_MESSAGE =
53
+ 'The execution sandbox payload upload timed out before this play began. Re-run the same command; if this keeps happening, contact Deepline support with the run ID.';
51
54
 
52
55
  export const WORKSPACE_STORAGE_NOT_READY_CODE = 'WORKSPACE_STORAGE_NOT_READY';
53
56
 
@@ -371,6 +374,15 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
371
374
  ...(causes.length > 0 ? { causes } : {}),
372
375
  };
373
376
  }
377
+ if (MODAL_PAYLOAD_UPLOAD_TIMEOUT_RE.test(rawCause)) {
378
+ return {
379
+ code: 'MODAL_PAYLOAD_UPLOAD_TIMEOUT',
380
+ phase: 'infrastructure',
381
+ message: MODAL_PAYLOAD_UPLOAD_TIMEOUT_MESSAGE,
382
+ retryable: true,
383
+ cause,
384
+ };
385
+ }
374
386
  if (RUNTIME_SANDBOX_INSPECTION_UNAVAILABLE_RE.test(rawCause)) {
375
387
  return {
376
388
  code: 'RUNTIME_SANDBOX_INSPECTION_UNAVAILABLE',
@@ -417,6 +417,14 @@ async function createRetriedOneShotDaytonaSandbox(input: {
417
417
  attempt,
418
418
  error: message,
419
419
  });
420
+ const immediateFallbackReason =
421
+ resolveDaytonaSandboxAcquisitionUnavailableReason([message]);
422
+ if (immediateFallbackReason === 'daytona_total_cpu_limit_exceeded') {
423
+ throw new DaytonaSandboxAcquisitionUnavailableError(
424
+ immediateFallbackReason,
425
+ `Daytona sandbox create rejected by the organization CPU limit on provider attempt ${attempt}: ${message}`,
426
+ );
427
+ }
420
428
  if (isDaytonaSandboxStartTimeout(message)) {
421
429
  const deleted = await reconcileAndDeleteTimedOutDaytonaSandbox({
422
430
  daytona: input.daytona,