deepline 0.1.191 → 0.1.193

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.
@@ -12,6 +12,8 @@
12
12
  * is a coordinator-side safety net for missed signals.
13
13
  */
14
14
 
15
+ import { isRetryableWorkerRuntimeApiError } from './runtime-api-retry';
16
+
15
17
  export type WorkflowStepLike = {
16
18
  waitForEvent: (
17
19
  name: string,
@@ -77,12 +79,6 @@ const CHILD_TERMINAL_DURABLE_VERIFY_GRACE_MS = 5_000;
77
79
  const CHILD_TERMINAL_DURABLE_VERIFY_POLL_MS = 250;
78
80
  const CHILD_TERMINAL_DURABLE_VERIFY_READ_TIMEOUT_MS = 3_000;
79
81
  const CHILD_TERMINAL_DURABLE_VERIFY_MIN_READ_TIMEOUT_MS = 2_000;
80
- const CHILD_TERMINAL_PERMANENT_RUNTIME_API_STATUS_RE =
81
- /runtime API (400|401|403|404):/i;
82
- const CHILD_TERMINAL_RETRYABLE_RUNTIME_API_STATUS_RE =
83
- /runtime API (408|429|500|502|503|504|530):/i;
84
- const CHILD_TERMINAL_RETRYABLE_ERROR_RE =
85
- /timed out|timeout|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT/i;
86
82
 
87
83
  export interface AwaitChildTerminalInput {
88
84
  parentRunId: string;
@@ -140,17 +136,6 @@ function terminalPayloadFromState(
140
136
  return isRecord(state?.data) ? state.data : null;
141
137
  }
142
138
 
143
- function isRetryableChildTerminalSnapshotError(error: unknown): boolean {
144
- const message = error instanceof Error ? error.message : String(error);
145
- if (CHILD_TERMINAL_PERMANENT_RUNTIME_API_STATUS_RE.test(message)) {
146
- return false;
147
- }
148
- return (
149
- CHILD_TERMINAL_RETRYABLE_RUNTIME_API_STATUS_RE.test(message) ||
150
- CHILD_TERMINAL_RETRYABLE_ERROR_RE.test(message)
151
- );
152
- }
153
-
154
139
  function sleep(ms: number): Promise<void> {
155
140
  return new Promise((resolve) => setTimeout(resolve, ms));
156
141
  }
@@ -259,7 +244,7 @@ async function readVerifiedChildTerminalPayload(input: {
259
244
  if (payload) return { payload, lastState, lastError };
260
245
  } catch (error) {
261
246
  lastError = error;
262
- if (!isRetryableChildTerminalSnapshotError(error)) {
247
+ if (!isRetryableWorkerRuntimeApiError(error)) {
263
248
  throw error;
264
249
  }
265
250
  }
@@ -23,7 +23,10 @@ import {
23
23
  } from '@cloudflare/dynamic-workflows';
24
24
  import type { ExecutionPlan } from '../../../shared_libs/play-runtime/execution-plan';
25
25
  import { buildChildRunId as buildSharedChildRunId } from '../../../shared_libs/play-runtime/child-run-id';
26
- import type { PlayCallGovernanceSnapshot } from '../../../shared_libs/play-runtime/scheduler-backend';
26
+ import type {
27
+ PlayCallGovernanceSnapshot,
28
+ PlayRunInputPayload,
29
+ } from '../../../shared_libs/play-runtime/scheduler-backend';
27
30
  import type { RuntimeTestPolicyOverrides } from '../../../shared_libs/play-runtime/test-runtime-seams';
28
31
  import { DYNAMIC_PLAY_WORKER_ARTIFACT_VERSION } from '../../../shared_libs/play-runtime/dynamic-worker-version';
29
32
  import {
@@ -96,7 +99,7 @@ export type PlayWorkflowParams = {
96
99
  artifactStorageKey: string;
97
100
  artifactHash: string;
98
101
  graphHash: string;
99
- input: Record<string, unknown>;
102
+ input: PlayRunInputPayload;
100
103
  force?: boolean;
101
104
  runAttempt?: number;
102
105
  inputFile?: {
@@ -131,6 +134,8 @@ export type PlayWorkflowParams = {
131
134
  dynamicWorkerCode?: string | null;
132
135
  executorToken: string;
133
136
  baseUrl: string;
137
+ runtimeApiBaseUrl?: string | null;
138
+ callbackUrl?: string | null;
134
139
  integrationMode?: 'live' | 'eval_stub' | 'fixture' | null;
135
140
  orgId: string;
136
141
  userEmail: string;
@@ -402,6 +407,15 @@ function recordCoordinatorPerfTrace(event: CoordinatorPerfTraceInput): void {
402
407
  logCoordinatorPerfTrace(payload);
403
408
  }
404
409
 
410
+ function safeOrigin(value: string | null | undefined): string | null {
411
+ if (!value?.trim()) return null;
412
+ try {
413
+ return new URL(value).origin;
414
+ } catch {
415
+ return '<invalid>';
416
+ }
417
+ }
418
+
405
419
  async function appendCoordinatorPerfTrace(
406
420
  env: CoordinatorEnv,
407
421
  payload: CoordinatorPerfTracePayload,
@@ -1330,9 +1344,23 @@ async function markWorkflowRuntimeFailure(input: {
1330
1344
  const backoffMs = [200, 500, 1500];
1331
1345
  let lastError: unknown = null;
1332
1346
  for (let attempt = 0; attempt <= backoffMs.length; attempt += 1) {
1347
+ const requestId = crypto.randomUUID();
1333
1348
  try {
1349
+ headers.set('x-deepline-request-id', requestId);
1334
1350
  const response = await fetch(url, { method: 'POST', headers, body });
1335
- if (response.ok) return;
1351
+ if (response.ok) {
1352
+ if (attempt > 0) {
1353
+ console.info('[coordinator.runtime_api.retry]', {
1354
+ action: 'append_run_events',
1355
+ status: 'ok',
1356
+ attempt,
1357
+ runId,
1358
+ requestId,
1359
+ baseOrigin: safeOrigin(baseUrl),
1360
+ });
1361
+ }
1362
+ return;
1363
+ }
1336
1364
  lastError = new Error(
1337
1365
  `runtime API responded ${response.status}: ${(await response.text().catch(() => '')).slice(0, 400)}`,
1338
1366
  );
@@ -1348,6 +1376,19 @@ async function markWorkflowRuntimeFailure(input: {
1348
1376
  lastError = error;
1349
1377
  }
1350
1378
  if (attempt < backoffMs.length) {
1379
+ console.warn('[coordinator.runtime_api.retry]', {
1380
+ action: 'append_run_events',
1381
+ status: 'retry',
1382
+ attempt: attempt + 1,
1383
+ retryDelayMs: backoffMs[attempt],
1384
+ runId,
1385
+ requestId,
1386
+ baseOrigin: safeOrigin(baseUrl),
1387
+ error:
1388
+ lastError instanceof Error
1389
+ ? lastError.message.slice(0, 300)
1390
+ : String(lastError).slice(0, 300),
1391
+ });
1351
1392
  await new Promise((resolve) => setTimeout(resolve, backoffMs[attempt]));
1352
1393
  }
1353
1394
  }
@@ -2203,6 +2244,26 @@ async function callRuntimeApiFromCoordinator(input: {
2203
2244
  });
2204
2245
 
2205
2246
  const fetchStartedAt = Date.now();
2247
+ const requestId = crypto.randomUUID();
2248
+ const action =
2249
+ body && typeof body === 'object' && !Array.isArray(body)
2250
+ ? String((body as { action?: unknown }).action ?? 'unknown')
2251
+ : 'unknown';
2252
+ const runId =
2253
+ body && typeof body === 'object' && !Array.isArray(body)
2254
+ ? String(
2255
+ (body as { runId?: unknown; playId?: unknown }).runId ??
2256
+ (body as { runId?: unknown; playId?: unknown }).playId ??
2257
+ '',
2258
+ ) || null
2259
+ : null;
2260
+ console.info('[coordinator.runtime_api.breadcrumb]', {
2261
+ phase: 'request',
2262
+ action,
2263
+ runId,
2264
+ requestId,
2265
+ baseOrigin: safeOrigin(input.baseUrl),
2266
+ });
2206
2267
  const response = await input.env.HARNESS.runtimeApiCall({
2207
2268
  contract: PLAY_RUNTIME_CONTRACT,
2208
2269
  executorToken: input.executorToken,
@@ -2210,11 +2271,20 @@ async function callRuntimeApiFromCoordinator(input: {
2210
2271
  path: PLAY_RUNTIME_API_COMPAT_PATH,
2211
2272
  body,
2212
2273
  headers: {
2213
- 'x-deepline-request-id': crypto.randomUUID(),
2274
+ 'x-deepline-request-id': requestId,
2214
2275
  [PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
2215
2276
  },
2216
2277
  });
2217
2278
  recordTiming('coordinator.runtime_api.fetch', fetchStartedAt);
2279
+ console.info('[coordinator.runtime_api.breadcrumb]', {
2280
+ phase: 'response',
2281
+ action,
2282
+ runId,
2283
+ requestId,
2284
+ status: response.status,
2285
+ ms: Date.now() - fetchStartedAt,
2286
+ baseOrigin: safeOrigin(input.baseUrl),
2287
+ });
2218
2288
 
2219
2289
  const bodyStartedAt = Date.now();
2220
2290
  const responseBody = response.body;
@@ -2329,7 +2399,7 @@ function buildChildRunId(input: {
2329
2399
  ? governance.parentPlayName
2330
2400
  : null,
2331
2401
  key: typeof governance?.key === 'string' ? governance.key : null,
2332
- input: isRecord(body.input) ? body.input : {},
2402
+ input: isRecord(body.input) || Array.isArray(body.input) ? body.input : {},
2333
2403
  graphHash: isRecord(body.manifest)
2334
2404
  ? typeof body.manifest.graphHash === 'string'
2335
2405
  ? body.manifest.graphHash
@@ -2361,12 +2431,82 @@ function normalizeRuntimeBaseUrl(value: unknown): string | null {
2361
2431
  return parsed.toString().replace(/\/$/, '');
2362
2432
  }
2363
2433
 
2364
- function resolveRuntimeBaseUrl(
2434
+ const DEEPLINE_PRODUCTION_APP_HOST = 'code.deepline.com';
2435
+ const DEEPLINE_VERCEL_DEPLOYMENT_HOST_RE =
2436
+ /^deepline-api(?:-[a-z0-9-]+)?-aero-team-0f650658\.vercel\.app$/i;
2437
+ const DEEPLINE_VERCEL_PROJECT_HOST_RE =
2438
+ /^deepline-[a-z0-9]+-aero-team-0f650658\.vercel\.app$/i;
2439
+ const DEEPLINE_WORKERS_DEV_SUBDOMAINS = new Set(['chirag-d94']);
2440
+
2441
+ function isDeeplineCoordinatorWorkerHost(hostname: string): boolean {
2442
+ const labels = hostname.toLowerCase().split('.');
2443
+ return (
2444
+ labels.length === 4 &&
2445
+ labels[0]?.startsWith('deepline-play-coordinator-') &&
2446
+ labels[2] === 'workers' &&
2447
+ labels[3] === 'dev' &&
2448
+ DEEPLINE_WORKERS_DEV_SUBDOMAINS.has(labels[1] ?? '')
2449
+ );
2450
+ }
2451
+
2452
+ function shouldCanonicalizeRuntimeApiBaseUrl(input: {
2453
+ configuredBaseUrl: URL;
2454
+ overrideBaseUrl: URL;
2455
+ }): boolean {
2456
+ if (input.overrideBaseUrl.host === input.configuredBaseUrl.host) {
2457
+ return false;
2458
+ }
2459
+ const configuredHostname = input.configuredBaseUrl.hostname.toLowerCase();
2460
+ const overrideHostname = input.overrideBaseUrl.hostname.toLowerCase();
2461
+ if (isDeeplineCoordinatorWorkerHost(overrideHostname)) {
2462
+ return true;
2463
+ }
2464
+ const overrideIsDeeplineVercelHost =
2465
+ DEEPLINE_VERCEL_DEPLOYMENT_HOST_RE.test(overrideHostname) ||
2466
+ DEEPLINE_VERCEL_PROJECT_HOST_RE.test(overrideHostname);
2467
+ if (!overrideIsDeeplineVercelHost) {
2468
+ return false;
2469
+ }
2470
+ const configuredIsDeeplineVercelHost =
2471
+ DEEPLINE_VERCEL_DEPLOYMENT_HOST_RE.test(configuredHostname) ||
2472
+ DEEPLINE_VERCEL_PROJECT_HOST_RE.test(configuredHostname);
2473
+ return (
2474
+ configuredHostname === DEEPLINE_PRODUCTION_APP_HOST ||
2475
+ !configuredIsDeeplineVercelHost
2476
+ );
2477
+ }
2478
+
2479
+ function resolveRuntimeApiBaseUrl(
2480
+ env: CoordinatorEnv,
2481
+ body: Record<string, unknown>,
2482
+ ): string {
2483
+ const configuredBaseUrl =
2484
+ normalizeRuntimeBaseUrl(env.DEEPLINE_API_BASE_URL) ??
2485
+ env.DEEPLINE_API_BASE_URL.replace(/\/$/, '');
2486
+ const overrideBaseUrl =
2487
+ normalizeRuntimeBaseUrl(body.runtimeApiBaseUrl) ??
2488
+ normalizeRuntimeBaseUrl(body.baseUrl);
2489
+ if (!overrideBaseUrl) {
2490
+ return configuredBaseUrl;
2491
+ }
2492
+ if (
2493
+ shouldCanonicalizeRuntimeApiBaseUrl({
2494
+ configuredBaseUrl: new URL(configuredBaseUrl),
2495
+ overrideBaseUrl: new URL(overrideBaseUrl),
2496
+ })
2497
+ ) {
2498
+ return configuredBaseUrl;
2499
+ }
2500
+ return overrideBaseUrl;
2501
+ }
2502
+
2503
+ function resolveWorkflowCallbackBaseUrl(
2365
2504
  env: CoordinatorEnv,
2366
2505
  body: Record<string, unknown>,
2367
2506
  ): string {
2368
2507
  return (
2369
2508
  normalizeRuntimeBaseUrl(body.callbackBaseUrl) ??
2509
+ normalizeRuntimeBaseUrl(body.callbackUrl) ??
2370
2510
  normalizeRuntimeBaseUrl(body.baseUrl) ??
2371
2511
  env.DEEPLINE_API_BASE_URL.replace(/\/$/, '')
2372
2512
  );
@@ -2388,7 +2528,8 @@ function validateChildSubmitBody(input: {
2388
2528
  const { parentRunId, body } = input;
2389
2529
  const manifest = body.manifest as PlayRuntimeManifest | undefined;
2390
2530
  const governance = body.internalRunPlay as
2391
- PlayCallGovernanceSnapshot | undefined;
2531
+ | PlayCallGovernanceSnapshot
2532
+ | undefined;
2392
2533
  const childPlayName =
2393
2534
  typeof body.name === 'string' && body.name.trim()
2394
2535
  ? body.name.trim()
@@ -2480,7 +2621,8 @@ function buildChildWorkflowParams(input: {
2480
2621
  dynamicWorkerCode,
2481
2622
  preloadedDbSessions,
2482
2623
  } = input;
2483
- const baseUrl = resolveRuntimeBaseUrl(env, body);
2624
+ const baseUrl = resolveRuntimeApiBaseUrl(env, body);
2625
+ const callbackUrl = resolveWorkflowCallbackBaseUrl(env, body);
2484
2626
  return {
2485
2627
  runId: childRunId,
2486
2628
  playId: childRunId,
@@ -2488,7 +2630,7 @@ function buildChildWorkflowParams(input: {
2488
2630
  artifactStorageKey: manifest.artifactStorageKey,
2489
2631
  artifactHash: manifest.artifactHash,
2490
2632
  graphHash: manifest.graphHash,
2491
- input: isRecord(body.input) ? body.input : {},
2633
+ input: isRecord(body.input) || Array.isArray(body.input) ? body.input : {},
2492
2634
  contractSnapshot: {
2493
2635
  source: 'published',
2494
2636
  revisionVersion: null,
@@ -2517,7 +2659,9 @@ function buildChildWorkflowParams(input: {
2517
2659
  preloadedDbSessions,
2518
2660
  dynamicWorkerCode,
2519
2661
  executorToken: childToken,
2520
- baseUrl,
2662
+ baseUrl: callbackUrl,
2663
+ runtimeApiBaseUrl: baseUrl,
2664
+ callbackUrl,
2521
2665
  integrationMode: normalizeIntegrationMode(body.integrationMode),
2522
2666
  force: body.force === true || body.forceToolRefresh === true,
2523
2667
  forceToolRefresh: body.forceToolRefresh === true,
@@ -2545,9 +2689,9 @@ function runRequestFromPlayWorkflowParams(
2545
2689
  params.inputFile?.r2Key ?? params.inputFile?.storageKey ?? null;
2546
2690
  return {
2547
2691
  runId: params.runId,
2548
- callbackUrl: params.baseUrl,
2692
+ callbackUrl: params.callbackUrl ?? params.baseUrl,
2549
2693
  executorToken: params.executorToken,
2550
- baseUrl: params.baseUrl,
2694
+ baseUrl: params.runtimeApiBaseUrl ?? params.baseUrl,
2551
2695
  integrationMode: normalizeIntegrationMode(params.integrationMode),
2552
2696
  orgId: params.orgId,
2553
2697
  playName: params.playName,
@@ -2742,7 +2886,7 @@ async function executeChildInline(input: {
2742
2886
  input.body.parentPlayName.trim()
2743
2887
  ? input.body.parentPlayName.trim()
2744
2888
  : governance.parentPlayName;
2745
- const baseUrl = resolveRuntimeBaseUrl(input.env, input.body);
2889
+ const baseUrl = resolveRuntimeApiBaseUrl(input.env, input.body);
2746
2890
  const { childToken, preloadedDbSessions, prepareTimings, transportTimings } =
2747
2891
  await prepareInlineChildRunWithRuntime({
2748
2892
  env: input.env,
@@ -3072,7 +3216,7 @@ async function submitChildWorkflowThroughCoordinator(input: {
3072
3216
  parentRunId: input.parentRunId,
3073
3217
  body: input.body,
3074
3218
  });
3075
- const baseUrl = resolveRuntimeBaseUrl(input.env, input.body);
3219
+ const baseUrl = resolveRuntimeApiBaseUrl(input.env, input.body);
3076
3220
 
3077
3221
  const tokenStartedAt = Date.now();
3078
3222
  const childToken = await mintChildWorkflowExecutorToken({
@@ -65,6 +65,7 @@ import {
65
65
  type WorkflowStepLike,
66
66
  } from './child-play-await';
67
67
  import { submitChildPlayThroughCoordinator } from './child-play-submit';
68
+ import { isRetryableWorkerRuntimeApiError } from './runtime-api-retry';
68
69
  import {
69
70
  attachToolResultListDataset,
70
71
  createToolExecuteResult,
@@ -307,6 +308,7 @@ import {
307
308
  type PreviousCell,
308
309
  } from '../../../shared_libs/play-runtime/cell-staleness';
309
310
  import type { PlayRowUpdate } from '../../../shared_libs/play-runtime/ctx-types';
311
+ import type { PlayRunInputPayload } from '../../../shared_libs/play-runtime/scheduler-backend';
310
312
 
311
313
  // The play's default export. The bundler injects this — see bundle-play-file.ts.
312
314
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -323,7 +325,7 @@ type RunRequest = {
323
325
  playName: string;
324
326
  graphHash?: string | null;
325
327
  userEmail: string | null;
326
- runtimeInput: Record<string, unknown>;
328
+ runtimeInput: PlayRunInputPayload;
327
329
  /** Start a fresh run graph and recompute runtime-sheet rows. */
328
330
  force?: boolean;
329
331
  /** Explicit durable tool receipt refresh that can re-execute completed provider calls. */
@@ -648,8 +650,15 @@ type WorkerEnv = {
648
650
 
649
651
  let cachedRuntimeApiBinding: WorkerEnv['RUNTIME_API'] | null = null;
650
652
  let cachedRuntimeApiVercelBypassToken: string | null = null;
653
+
654
+ function runtimeApiBindingFromEnv(
655
+ env: WorkerEnv,
656
+ ): WorkerEnv['RUNTIME_API'] | null {
657
+ return env.RUNTIME_API ?? env.HARNESS ?? null;
658
+ }
659
+
651
660
  function captureRuntimeApiBinding(env: WorkerEnv): void {
652
- cachedRuntimeApiBinding = env.RUNTIME_API ?? null;
661
+ cachedRuntimeApiBinding = runtimeApiBindingFromEnv(env);
653
662
  cachedRuntimeApiVercelBypassToken =
654
663
  typeof env.VERCEL_PROTECTION_BYPASS_TOKEN === 'string' &&
655
664
  env.VERCEL_PROTECTION_BYPASS_TOKEN.trim()
@@ -831,7 +840,9 @@ async function fetchRuntimeApi(
831
840
  signal: controller.signal,
832
841
  };
833
842
  if (!cachedRuntimeApiBinding) {
834
- throw new Error('[play-harness] RUNTIME_API service binding is required');
843
+ throw new Error(
844
+ '[play-harness] RUNTIME_API/HARNESS service binding is required',
845
+ );
835
846
  }
836
847
  if (abortSignal?.aborted) {
837
848
  throw abortError();
@@ -1360,7 +1371,7 @@ async function postRuntimeApi<T>(
1360
1371
  lastError = error;
1361
1372
  if (
1362
1373
  attempt >= RUNTIME_API_RETRY_DELAYS_MS.length ||
1363
- !isRetryableRuntimeApiError(error)
1374
+ !isRetryableWorkerRuntimeApiError(error)
1364
1375
  ) {
1365
1376
  throw error;
1366
1377
  }
@@ -1386,13 +1397,6 @@ async function postRuntimeApi<T>(
1386
1397
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
1387
1398
  }
1388
1399
 
1389
- function isRetryableRuntimeApiError(error: unknown): boolean {
1390
- const message = error instanceof Error ? error.message : String(error);
1391
- return /timed out|timeout|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT/i.test(
1392
- message,
1393
- );
1394
- }
1395
-
1396
1400
  function isRetryableRuntimePersistenceError(error: unknown): boolean {
1397
1401
  const message = error instanceof Error ? error.message : String(error);
1398
1402
  return /deadlock detected|Runtime Postgres connection timed out|could not serialize access|tuple concurrently updated/i.test(
@@ -1401,21 +1405,17 @@ function isRetryableRuntimePersistenceError(error: unknown): boolean {
1401
1405
  }
1402
1406
 
1403
1407
  function isRetryableRuntimeApiResponse(status: number, body: string): boolean {
1404
- if (
1408
+ return (
1405
1409
  status === 408 ||
1406
1410
  status === 429 ||
1407
- status === 502 ||
1408
- status === 503 ||
1409
- status === 504 ||
1410
- status === 530
1411
- ) {
1412
- return true;
1413
- }
1414
- return (
1415
- status === 500 &&
1416
- /timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|UND_ERR_CONNECT_TIMEOUT/i.test(
1417
- body,
1418
- )
1411
+ (status >= 502 && status <= 504) ||
1412
+ status === 530 ||
1413
+ (status === 404 &&
1414
+ /DEPLOYMENT_NOT_FOUND|DeploymentNotFound|deployment .*not found|requested deployment .*exist/i.test(
1415
+ body,
1416
+ )) ||
1417
+ (status === 500 &&
1418
+ isRetryableWorkerRuntimeApiError(`runtime API ${status}: ${body}`))
1419
1419
  );
1420
1420
  }
1421
1421
 
@@ -2076,7 +2076,7 @@ async function callToolDirect(
2076
2076
  );
2077
2077
  if (
2078
2078
  transportAttempt >= TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS ||
2079
- !isRetryableRuntimeApiError(error)
2079
+ !isRetryableWorkerRuntimeApiError(error)
2080
2080
  ) {
2081
2081
  throw lastError;
2082
2082
  }
@@ -2360,7 +2360,10 @@ function isRuntimeReceiptPersistenceError(error: unknown): boolean {
2360
2360
  }
2361
2361
 
2362
2362
  function isRunFatalWorkerRowError(error: unknown): boolean {
2363
+ const failure = normalizePlayRunFailure(error);
2363
2364
  return (
2365
+ failure.code === 'PLATFORM_DEPLOY_INTERRUPTED' ||
2366
+ failure.code === 'PLATFORM_WORKER_SUBREQUEST_INTERRUPTED' ||
2364
2367
  isCoordinatorProgressAuthorizationError(error) ||
2365
2368
  isRowIsolationExemptError(error) ||
2366
2369
  isRuntimeReceiptPersistenceError(error) ||
@@ -6891,7 +6894,7 @@ function createMinimalWorkerCtx(
6891
6894
  childIdempotencyKey: receiptKey,
6892
6895
  input: isRecord(input) ? input : {},
6893
6896
  orgId: req.orgId,
6894
- callbackBaseUrl: req.callbackUrl,
6897
+ callbackUrl: req.callbackUrl,
6895
6898
  baseUrl: req.baseUrl,
6896
6899
  integrationMode: req.integrationMode ?? null,
6897
6900
  parentExecutorToken: req.executorToken,
@@ -7381,10 +7384,7 @@ async function handleRun(request: Request, env: WorkerEnv): Promise<Response> {
7381
7384
  await probeHarnessOnce(env, runPrefix);
7382
7385
  const ctx = createMinimalWorkerCtx(req, emit, env);
7383
7386
  const result = await (
7384
- playFn as (
7385
- ctx: unknown,
7386
- input: Record<string, unknown>,
7387
- ) => Promise<unknown>
7387
+ playFn as (ctx: unknown, input: PlayRunInputPayload) => Promise<unknown>
7388
7388
  )(ctx, req.runtimeInput);
7389
7389
  emit({
7390
7390
  type: 'result',
@@ -7913,10 +7913,7 @@ async function executeRunRequest(
7913
7913
  try {
7914
7914
  const playStartedAt = nowMs();
7915
7915
  const result = await (
7916
- playFn as (
7917
- ctx: unknown,
7918
- input: Record<string, unknown>,
7919
- ) => Promise<unknown>
7916
+ playFn as (ctx: unknown, input: PlayRunInputPayload) => Promise<unknown>
7920
7917
  )(ctx, req.runtimeInput);
7921
7918
  recordRunnerPerfTrace({
7922
7919
  req,
@@ -8305,9 +8302,9 @@ function runRequestFromWorkflowParams(
8305
8302
  : null;
8306
8303
  return {
8307
8304
  runId: String(params.runId ?? ''),
8308
- callbackUrl: String(params.baseUrl ?? ''),
8305
+ callbackUrl: String(params.callbackUrl ?? params.baseUrl ?? ''),
8309
8306
  executorToken: String(params.executorToken ?? ''),
8310
- baseUrl: String(params.baseUrl ?? ''),
8307
+ baseUrl: String(params.runtimeApiBaseUrl ?? params.baseUrl ?? ''),
8311
8308
  integrationMode: normalizeIntegrationMode(params.integrationMode),
8312
8309
  orgId: String(params.orgId ?? ''),
8313
8310
  playName: String(params.playName ?? ''),
@@ -8315,9 +8312,8 @@ function runRequestFromWorkflowParams(
8315
8312
  force: params.force === true,
8316
8313
  forceToolRefresh: params.forceToolRefresh === true,
8317
8314
  runAttempt: normalizeWorkerRunAttempt(params.runAttempt),
8318
- runtimeInput: isRecord(params.input)
8319
- ? (params.input as Record<string, unknown>)
8320
- : {},
8315
+ runtimeInput:
8316
+ isRecord(params.input) || Array.isArray(params.input) ? params.input : {},
8321
8317
  inlineCsv: isInlineCsv(params.inlineCsv) ? params.inlineCsv : null,
8322
8318
  inputFiles:
8323
8319
  inputFile && inputStorageKey
@@ -0,0 +1,23 @@
1
+ const PERMANENT_RUNTIME_API_STATUS_RE = /runtime API 40[0134]:/i;
2
+ const RETRYABLE_RUNTIME_API_ERROR_RE =
3
+ /runtime API (408|429|500|50[2-4]|530):|time(?:d out|out)|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|DeploymentNotFound|DEPLOYMENT_NOT_FOUND|requested deployment .*exist|The deployment could not be found on Vercel|Authentication infrastructure/i;
4
+
5
+ function isVercelDeploymentMissingRuntimeApi404(message: string): boolean {
6
+ return (
7
+ /runtime API 404:/i.test(message) &&
8
+ /DeploymentNotFound|DEPLOYMENT_NOT_FOUND|requested deployment .*exist|The deployment could not be found on Vercel/i.test(
9
+ message,
10
+ )
11
+ );
12
+ }
13
+
14
+ export function isRetryableWorkerRuntimeApiError(error: unknown): boolean {
15
+ const message = error instanceof Error ? error.message : String(error);
16
+ if (
17
+ PERMANENT_RUNTIME_API_STATUS_RE.test(message) &&
18
+ !isVercelDeploymentMissingRuntimeApi404(message)
19
+ ) {
20
+ return false;
21
+ }
22
+ return RETRYABLE_RUNTIME_API_ERROR_RE.test(message);
23
+ }
@@ -1,5 +1,8 @@
1
1
  import type { ExecutionPlan } from '../../../shared_libs/play-runtime/execution-plan';
2
- import type { PlayCallGovernanceSnapshot } from '../../../shared_libs/play-runtime/scheduler-backend';
2
+ import type {
3
+ PlayCallGovernanceSnapshot,
4
+ PlayRunInputPayload,
5
+ } from '../../../shared_libs/play-runtime/scheduler-backend';
3
6
  import type { PreloadedRuntimeDbSession } from '../../../shared_libs/play-runtime/db-session';
4
7
  import type {
5
8
  PlayRuntimeManifest,
@@ -33,7 +36,7 @@ export type WorkflowRetryPlayParams = {
33
36
  artifactStorageKey: string;
34
37
  artifactHash: string;
35
38
  graphHash: string;
36
- input: Record<string, unknown>;
39
+ input: PlayRunInputPayload;
37
40
  inputFile?: {
38
41
  name?: string;
39
42
  r2Key?: string;
@@ -65,6 +68,8 @@ export type WorkflowRetryPlayParams = {
65
68
  dynamicWorkerCode?: string | null;
66
69
  executorToken: string;
67
70
  baseUrl: string;
71
+ runtimeApiBaseUrl?: string | null;
72
+ callbackUrl?: string | null;
68
73
  orgId: string;
69
74
  userEmail: string;
70
75
  userId?: string | null;
@@ -24,14 +24,19 @@ export function decideWorkflowPlatformRetry(input: {
24
24
  return { action: 'fail', reason: null, message: null };
25
25
  }
26
26
  const failure = normalizePlayRunFailure(input.error);
27
- const retryablePlatformReset = failure.code === 'PLATFORM_DEPLOY_INTERRUPTED';
27
+ const retryablePlatformReset =
28
+ failure.code === 'PLATFORM_DEPLOY_INTERRUPTED' ||
29
+ failure.code === 'PLATFORM_WORKER_SUBREQUEST_INTERRUPTED';
28
30
  if (
29
31
  retryablePlatformReset &&
30
32
  input.retryAttempts < PLATFORM_DEPLOY_WORKFLOW_RETRY_LIMIT
31
33
  ) {
32
34
  return {
33
35
  action: 'retry',
34
- reason: 'cloudflare_durable_object_reset',
36
+ reason:
37
+ failure.code === 'PLATFORM_WORKER_SUBREQUEST_INTERRUPTED'
38
+ ? 'cloudflare_worker_subrequest_limit'
39
+ : 'cloudflare_durable_object_reset',
35
40
  nextAttempt: input.retryAttempts + 1,
36
41
  message: failure.message,
37
42
  };
@@ -39,7 +44,9 @@ export function decideWorkflowPlatformRetry(input: {
39
44
  return {
40
45
  action: 'fail',
41
46
  reason: retryablePlatformReset
42
- ? 'cloudflare_durable_object_reset_retry_exhausted'
47
+ ? failure.code === 'PLATFORM_WORKER_SUBREQUEST_INTERRUPTED'
48
+ ? 'cloudflare_worker_subrequest_limit_retry_exhausted'
49
+ : 'cloudflare_durable_object_reset_retry_exhausted'
43
50
  : null,
44
51
  message: failure.message,
45
52
  };
@@ -105,10 +105,10 @@ export const SDK_RELEASE = {
105
105
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
106
106
  // fields shipped in 0.1.153.
107
107
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
108
- version: '0.1.191',
108
+ version: '0.1.193',
109
109
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
110
110
  supportPolicy: {
111
- latest: '0.1.191',
111
+ latest: '0.1.193',
112
112
  minimumSupported: '0.1.53',
113
113
  deprecatedBelow: '0.1.53',
114
114
  commandMinimumSupported: [
@@ -49,7 +49,7 @@ export interface ChildRunIdInputs {
49
49
  */
50
50
  runPlaySemanticKey?: string | null;
51
51
  /** Child input payload (semantic-fallback input). */
52
- input?: Record<string, unknown> | null;
52
+ input?: unknown;
53
53
  /**
54
54
  * Compiled child graph hash. Only present on workers_edge; `null` in-process.
55
55
  */
@@ -89,11 +89,16 @@ export function buildChildRunId(inputs: ChildRunIdInputs): string {
89
89
  stableStringify({
90
90
  parentRunId: inputs.parentRunId,
91
91
  parentPlayName:
92
- typeof inputs.parentPlayName === 'string' ? inputs.parentPlayName : null,
92
+ typeof inputs.parentPlayName === 'string'
93
+ ? inputs.parentPlayName
94
+ : null,
93
95
  childPlayName: inputs.childPlayName,
94
96
  key: typeof inputs.key === 'string' ? inputs.key : null,
95
97
  ...(runPlaySemanticKey ? { runPlaySemanticKey } : {}),
96
- input: isRecord(inputs.input) ? inputs.input : {},
98
+ input:
99
+ isRecord(inputs.input) || Array.isArray(inputs.input)
100
+ ? inputs.input
101
+ : {},
97
102
  graphHash: typeof inputs.graphHash === 'string' ? inputs.graphHash : null,
98
103
  });
99
104
  const digest = sha256Hex(semanticKey).slice(0, 24);
@@ -11,6 +11,7 @@ import {
11
11
  isPlayExecutionSuspendedError,
12
12
  isPlayRowExecutionSuspendedError,
13
13
  } from './suspension';
14
+ import { getToolHttpErrorReceiptFailureKind } from './tool-http-errors';
14
15
  import type { WorkReceiptFailureKind } from './work-receipts';
15
16
  import {
16
17
  PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
@@ -48,6 +49,8 @@ function isInFlightRuntimeReceipt(
48
49
  export function runtimeReceiptFailureKindForError(
49
50
  error: unknown,
50
51
  ): WorkReceiptFailureKind {
52
+ const toolFailureKind = getToolHttpErrorReceiptFailureKind(error);
53
+ if (toolFailureKind) return toolFailureKind;
51
54
  return isPlayExecutionSuspendedError(error) ||
52
55
  isPlayRowExecutionSuspendedError(error)
53
56
  ? 'repairable'
@@ -1,8 +1,12 @@
1
1
  const CLOUDFLARE_DURABLE_OBJECT_RESET_RE =
2
- /Durable Object.*(?:code was updated|storage caused object)/;
2
+ /Durable Object.*(?:code (?:was|has been) updated|storage caused object)/;
3
+ const CLOUDFLARE_WORKER_SUBREQUEST_LIMIT_RE =
4
+ /Too many subrequests by single Worker invocation/i;
3
5
 
4
6
  export const PLATFORM_DEPLOY_INTERRUPTED_MESSAGE =
5
7
  'Run interrupted by a platform deploy. Deepline retries this automatically when possible; if this error is still visible, re-run the same command.';
8
+ export const PLATFORM_WORKER_SUBREQUEST_INTERRUPTED_MESSAGE =
9
+ 'Worker subrequest limit hit. Deepline retries automatically; if still visible, re-run.';
6
10
 
7
11
  export const INTERNAL_RUNTIME_STORAGE_ERROR_MESSAGE =
8
12
  'Internal play runtime storage failed. Please retry the run; if this keeps happening, contact Deepline support with the run ID.';
@@ -64,6 +68,15 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
64
68
  cause,
65
69
  };
66
70
  }
71
+ if (CLOUDFLARE_WORKER_SUBREQUEST_LIMIT_RE.test(cause)) {
72
+ return {
73
+ code: 'PLATFORM_WORKER_SUBREQUEST_INTERRUPTED',
74
+ phase: 'runtime',
75
+ message: PLATFORM_WORKER_SUBREQUEST_INTERRUPTED_MESSAGE,
76
+ retryable: true,
77
+ cause,
78
+ };
79
+ }
67
80
  const playDepthBudgetMatch = cause.match(
68
81
  /Play execution playDepth budget exceeded \((\d+)\/(\d+)\)\./,
69
82
  );
@@ -36,6 +36,8 @@ export const PLAY_SCHEDULER_BACKENDS = {
36
36
  export type PlaySchedulerBackendId =
37
37
  (typeof PLAY_SCHEDULER_BACKENDS)[keyof typeof PLAY_SCHEDULER_BACKENDS];
38
38
 
39
+ export type PlayRunInputPayload = Record<string, unknown> | unknown[];
40
+
39
41
  export type PlayCallGovernanceSnapshot = {
40
42
  rootRunId: string;
41
43
  parentRunId: string;
@@ -83,7 +85,7 @@ export type PlaySchedulerSubmitInput = {
83
85
  runtimeArtifact?: unknown;
84
86
  artifactHash: string;
85
87
  graphHash: string;
86
- input: Record<string, unknown>;
88
+ input: PlayRunInputPayload;
87
89
  /** Start a fresh run graph and recompute runtime-sheet rows. */
88
90
  force?: boolean;
89
91
  /** Explicit cache bypass for completed ctx.tools.execute receipts. */
@@ -128,6 +130,11 @@ export type PlaySchedulerSubmitInput = {
128
130
  /** Optional immutable Worker module source for local Dynamic Worker loading. */
129
131
  dynamicWorkerCode?: string | null;
130
132
  executorToken: string;
133
+ /**
134
+ * Public app origin used by Workers to call Deepline runtime API routes.
135
+ * When omitted, legacy schedulers fall back to baseUrl.
136
+ */
137
+ runtimeApiBaseUrl?: string | null;
131
138
  baseUrl: string;
132
139
  /** Optional per-run provider execution mode. Defaults to server policy when omitted. */
133
140
  integrationMode?: 'live' | 'eval_stub' | 'fixture';
@@ -204,9 +204,7 @@ export function classifyToolExecuteHttpFailure(input: {
204
204
  });
205
205
  const shouldRetry =
206
206
  retryDecision.retryable && input.attempt < retryDecision.attemptCap;
207
- error.receiptFailureKind = retryDecision.retryable
208
- ? 'repairable'
209
- : 'terminal';
207
+ if (retryDecision.retryable) error.receiptFailureKind = 'repairable';
210
208
  const retryAfterMs = parseToolExecuteRetryAfterMs(
211
209
  input.retryAfterHeader,
212
210
  input.nowMs,
@@ -4,13 +4,13 @@ export class ToolHttpError extends Error {
4
4
  readonly billing: Record<string, unknown> | null;
5
5
  /** HTTP status of the failed tool-execute response (e.g. 429, 502). */
6
6
  readonly status: number;
7
- receiptFailureKind: WorkReceiptFailureKind | null;
7
+ receiptFailureKind: WorkReceiptFailureKind;
8
8
 
9
9
  constructor(
10
10
  message: string,
11
11
  billing: Record<string, unknown> | null,
12
12
  status: number,
13
- receiptFailureKind: WorkReceiptFailureKind | null = null,
13
+ receiptFailureKind: WorkReceiptFailureKind = 'terminal',
14
14
  ) {
15
15
  super(message);
16
16
  this.name = 'ToolHttpError';
package/dist/cli/index.js CHANGED
@@ -623,10 +623,10 @@ var SDK_RELEASE = {
623
623
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
624
624
  // fields shipped in 0.1.153.
625
625
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
626
- version: "0.1.191",
626
+ version: "0.1.193",
627
627
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
628
628
  supportPolicy: {
629
- latest: "0.1.191",
629
+ latest: "0.1.193",
630
630
  minimumSupported: "0.1.53",
631
631
  deprecatedBelow: "0.1.53",
632
632
  commandMinimumSupported: [
@@ -19385,6 +19385,30 @@ function coalesceExportableSheetRows(rows, sourceRowStart = 0) {
19385
19385
  bySourceRowIndex.set(rowIndex, next);
19386
19386
  mergedRows.push(next);
19387
19387
  }
19388
+ if (bySourceRowIndex.size > 0) {
19389
+ mergedRows.sort((left, right) => {
19390
+ const leftIndex = sourceRowIndexFromEnrichRow(
19391
+ left,
19392
+ sourceRowStart,
19393
+ Number.MAX_SAFE_INTEGER
19394
+ );
19395
+ const rightIndex = sourceRowIndexFromEnrichRow(
19396
+ right,
19397
+ sourceRowStart,
19398
+ Number.MAX_SAFE_INTEGER
19399
+ );
19400
+ if (leftIndex !== null && rightIndex !== null) {
19401
+ return leftIndex - rightIndex;
19402
+ }
19403
+ if (leftIndex !== null) {
19404
+ return -1;
19405
+ }
19406
+ if (rightIndex !== null) {
19407
+ return 1;
19408
+ }
19409
+ return 0;
19410
+ });
19411
+ }
19388
19412
  return mergedRows;
19389
19413
  }
19390
19414
  function sourceRowIndexFromEnrichRow(row, start, end) {
@@ -21227,7 +21251,9 @@ function registerEnrichCommand(program) {
21227
21251
  ...finalExportResult ? { rowSetComplete: !finalExportResult.partial } : {}
21228
21252
  });
21229
21253
  if (chunk.exportResult) {
21230
- currentChunkCommittedExportResult = await commitInPlaceOutput(chunk.exportResult);
21254
+ currentChunkCommittedExportResult = await commitInPlaceOutput(
21255
+ chunk.exportResult
21256
+ );
21231
21257
  finalExportResult = currentChunkCommittedExportResult;
21232
21258
  }
21233
21259
  const reportedExportResult = currentChunkCommittedExportResult ?? finalExportResult;
@@ -608,10 +608,10 @@ var SDK_RELEASE = {
608
608
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
609
609
  // fields shipped in 0.1.153.
610
610
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
611
- version: "0.1.191",
611
+ version: "0.1.193",
612
612
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
613
613
  supportPolicy: {
614
- latest: "0.1.191",
614
+ latest: "0.1.193",
615
615
  minimumSupported: "0.1.53",
616
616
  deprecatedBelow: "0.1.53",
617
617
  commandMinimumSupported: [
@@ -19414,6 +19414,30 @@ function coalesceExportableSheetRows(rows, sourceRowStart = 0) {
19414
19414
  bySourceRowIndex.set(rowIndex, next);
19415
19415
  mergedRows.push(next);
19416
19416
  }
19417
+ if (bySourceRowIndex.size > 0) {
19418
+ mergedRows.sort((left, right) => {
19419
+ const leftIndex = sourceRowIndexFromEnrichRow(
19420
+ left,
19421
+ sourceRowStart,
19422
+ Number.MAX_SAFE_INTEGER
19423
+ );
19424
+ const rightIndex = sourceRowIndexFromEnrichRow(
19425
+ right,
19426
+ sourceRowStart,
19427
+ Number.MAX_SAFE_INTEGER
19428
+ );
19429
+ if (leftIndex !== null && rightIndex !== null) {
19430
+ return leftIndex - rightIndex;
19431
+ }
19432
+ if (leftIndex !== null) {
19433
+ return -1;
19434
+ }
19435
+ if (rightIndex !== null) {
19436
+ return 1;
19437
+ }
19438
+ return 0;
19439
+ });
19440
+ }
19417
19441
  return mergedRows;
19418
19442
  }
19419
19443
  function sourceRowIndexFromEnrichRow(row, start, end) {
@@ -21256,7 +21280,9 @@ function registerEnrichCommand(program) {
21256
21280
  ...finalExportResult ? { rowSetComplete: !finalExportResult.partial } : {}
21257
21281
  });
21258
21282
  if (chunk.exportResult) {
21259
- currentChunkCommittedExportResult = await commitInPlaceOutput(chunk.exportResult);
21283
+ currentChunkCommittedExportResult = await commitInPlaceOutput(
21284
+ chunk.exportResult
21285
+ );
21260
21286
  finalExportResult = currentChunkCommittedExportResult;
21261
21287
  }
21262
21288
  const reportedExportResult = currentChunkCommittedExportResult ?? finalExportResult;
package/dist/index.js CHANGED
@@ -422,10 +422,10 @@ var SDK_RELEASE = {
422
422
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
423
423
  // fields shipped in 0.1.153.
424
424
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
425
- version: "0.1.191",
425
+ version: "0.1.193",
426
426
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
427
427
  supportPolicy: {
428
- latest: "0.1.191",
428
+ latest: "0.1.193",
429
429
  minimumSupported: "0.1.53",
430
430
  deprecatedBelow: "0.1.53",
431
431
  commandMinimumSupported: [
package/dist/index.mjs CHANGED
@@ -352,10 +352,10 @@ var SDK_RELEASE = {
352
352
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
353
353
  // fields shipped in 0.1.153.
354
354
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
355
- version: "0.1.191",
355
+ version: "0.1.193",
356
356
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
357
357
  supportPolicy: {
358
- latest: "0.1.191",
358
+ latest: "0.1.193",
359
359
  minimumSupported: "0.1.53",
360
360
  deprecatedBelow: "0.1.53",
361
361
  commandMinimumSupported: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.191",
3
+ "version": "0.1.193",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {