deepline 0.1.19 → 0.1.20

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.
@@ -63,6 +63,7 @@ import {
63
63
  } from '../../../shared_libs/plays/row-identity';
64
64
  import {
65
65
  getCompiledPipelineSubsteps,
66
+ flattenStaticPipeline,
66
67
  resolveSheetContractForTableNamespace,
67
68
  sqlSafePlayColumnName,
68
69
  type PlayStaticPipeline,
@@ -365,8 +366,9 @@ function cachedVercelProtectionBypassToken(): string | null {
365
366
 
366
367
  const WORKER_PLAY_CALL_LIMITS = {
367
368
  maxPlayCallDepth: 6,
368
- maxPlayCallCount: 32,
369
- maxChildPlayCallsPerParent: 16,
369
+ maxPlayCallCount: 1_000,
370
+ maxChildPlayCallsPerParent: 1_000,
371
+ maxConcurrentPlayCalls: 16,
370
372
  };
371
373
 
372
374
  /**
@@ -637,6 +639,7 @@ async function postRuntimeApiBestEffort(
637
639
  async function submitChildPlayThroughCoordinator(input: {
638
640
  req: RunRequest;
639
641
  body: unknown;
642
+ allowInline?: boolean;
640
643
  }): Promise<{
641
644
  workflowId?: string;
642
645
  runId?: string;
@@ -648,7 +651,7 @@ async function submitChildPlayThroughCoordinator(input: {
648
651
  logs?: string[];
649
652
  timings?: Array<{ phase: string; ms: number }>;
650
653
  }> {
651
- if (cachedCoordinatorBinding) {
654
+ if (cachedCoordinatorBinding && input.allowInline !== false) {
652
655
  if (!isRecord(input.body)) {
653
656
  throw new Error('ctx.runPlay child submit requires an object body.');
654
657
  }
@@ -2461,6 +2464,18 @@ function childPipelineUsesCtxMap(
2461
2464
  );
2462
2465
  }
2463
2466
 
2467
+ function childPipelineNeedsWorkflowScheduler(
2468
+ pipeline: PlayStaticPipeline | null | undefined,
2469
+ ): boolean {
2470
+ if (!pipeline) return false;
2471
+ return flattenStaticPipeline(pipeline).some(
2472
+ (substep) =>
2473
+ substep.type === 'tool' &&
2474
+ (substep.isEventWait === true ||
2475
+ substep.toolId === 'test_wait_for_event'),
2476
+ );
2477
+ }
2478
+
2464
2479
  function releaseChildPlayConcurrency(
2465
2480
  inFlightByPlayName: Record<string, number>,
2466
2481
  playName: string,
@@ -2483,6 +2498,41 @@ function createMinimalWorkerCtx(
2483
2498
  let playCallCount = 0;
2484
2499
  const parentChildCalls: Record<string, number> = {};
2485
2500
  const inFlightChildCallsByPlayName: Record<string, number> = {};
2501
+ let inFlightChildPlayCalls = 0;
2502
+ const childPlaySlotWaiters: Array<() => void> = [];
2503
+
2504
+ const acquireChildPlaySlot = async (): Promise<() => void> => {
2505
+ while (
2506
+ inFlightChildPlayCalls >= WORKER_PLAY_CALL_LIMITS.maxConcurrentPlayCalls
2507
+ ) {
2508
+ await new Promise<void>((resolve, reject) => {
2509
+ const waiter = () => {
2510
+ abortSignal?.removeEventListener('abort', onAbort);
2511
+ resolve();
2512
+ };
2513
+ const onAbort = () => {
2514
+ const index = childPlaySlotWaiters.indexOf(waiter);
2515
+ if (index >= 0) childPlaySlotWaiters.splice(index, 1);
2516
+ reject(
2517
+ abortSignal?.reason instanceof Error
2518
+ ? abortSignal.reason
2519
+ : new WorkflowAbortError(),
2520
+ );
2521
+ };
2522
+ childPlaySlotWaiters.push(waiter);
2523
+ abortSignal?.addEventListener('abort', onAbort, { once: true });
2524
+ });
2525
+ assertNotAborted(abortSignal);
2526
+ }
2527
+ inFlightChildPlayCalls += 1;
2528
+ let released = false;
2529
+ return () => {
2530
+ if (released) return;
2531
+ released = true;
2532
+ inFlightChildPlayCalls = Math.max(0, inFlightChildPlayCalls - 1);
2533
+ childPlaySlotWaiters.shift()?.();
2534
+ };
2535
+ };
2486
2536
  const rootGovernance = req.playCallGovernance;
2487
2537
  const rootRunId = rootGovernance?.rootRunId ?? req.runId;
2488
2538
  // Local ancestry chain that always ENDS with the currently-executing play
@@ -2579,6 +2629,7 @@ function createMinimalWorkerCtx(
2579
2629
  : JSON.stringify(normalizedParts);
2580
2630
  return keyValue;
2581
2631
  };
2632
+ const mapLogicFingerprint = req.graphHash ?? null;
2582
2633
  const resolveRowKey = (
2583
2634
  row: Record<string, unknown>,
2584
2635
  index: number,
@@ -2586,8 +2637,12 @@ function createMinimalWorkerCtx(
2586
2637
  const inputRow = publicCsvInputRow(row);
2587
2638
  const explicitKeyValue = resolveExplicitKeyValue(row, index);
2588
2639
  return explicitKeyValue == null
2589
- ? derivePlayRowIdentity(inputRow, name)
2590
- : derivePlayRowIdentityFromKey(explicitKeyValue, name);
2640
+ ? derivePlayRowIdentity(inputRow, name, mapLogicFingerprint)
2641
+ : derivePlayRowIdentityFromKey(
2642
+ explicitKeyValue,
2643
+ name,
2644
+ mapLogicFingerprint,
2645
+ );
2591
2646
  };
2592
2647
  const assertUniqueExplicitRowKeys = (
2593
2648
  chunkRows: readonly Record<string, unknown>[],
@@ -2635,7 +2690,11 @@ function createMinimalWorkerCtx(
2635
2690
  const key =
2636
2691
  typeof row.__deeplineRowKey === 'string'
2637
2692
  ? row.__deeplineRowKey
2638
- : derivePlayRowIdentity(publicCsvInputRow(row), name);
2693
+ : derivePlayRowIdentity(
2694
+ publicCsvInputRow(row),
2695
+ name,
2696
+ mapLogicFingerprint,
2697
+ );
2639
2698
  if (key) {
2640
2699
  pendingKeys.add(key);
2641
2700
  preparedKeys.add(key);
@@ -2645,7 +2704,11 @@ function createMinimalWorkerCtx(
2645
2704
  const key =
2646
2705
  typeof row.__deeplineRowKey === 'string'
2647
2706
  ? row.__deeplineRowKey
2648
- : derivePlayRowIdentity(publicCsvInputRow(row), name);
2707
+ : derivePlayRowIdentity(
2708
+ publicCsvInputRow(row),
2709
+ name,
2710
+ mapLogicFingerprint,
2711
+ );
2649
2712
  if (key) {
2650
2713
  completedKeys.add(key);
2651
2714
  preparedKeys.add(key);
@@ -2852,7 +2915,11 @@ function createMinimalWorkerCtx(
2852
2915
  const key =
2853
2916
  typeof completedRow.__deeplineRowKey === 'string'
2854
2917
  ? completedRow.__deeplineRowKey
2855
- : derivePlayRowIdentity(publicCsvInputRow(completedRow), name);
2918
+ : derivePlayRowIdentity(
2919
+ publicCsvInputRow(completedRow),
2920
+ name,
2921
+ mapLogicFingerprint,
2922
+ );
2856
2923
  if (key) {
2857
2924
  const { __deeplineRowKey: _rowKey, ...cleanedRow } =
2858
2925
  publicCsvInputRow(completedRow);
@@ -3227,7 +3294,11 @@ function createMinimalWorkerCtx(
3227
3294
  const completedKeys = new Set<string>();
3228
3295
  const preparedKeys = new Set<string>();
3229
3296
  for (const row of prepared.pendingRows) {
3230
- const key = derivePlayRowIdentity(publicCsvInputRow(row), name);
3297
+ const key = derivePlayRowIdentity(
3298
+ publicCsvInputRow(row),
3299
+ name,
3300
+ mapLogicFingerprint,
3301
+ );
3231
3302
  if (key) {
3232
3303
  pendingKeys.add(key);
3233
3304
  preparedKeys.add(key);
@@ -3237,18 +3308,30 @@ function createMinimalWorkerCtx(
3237
3308
  const key =
3238
3309
  typeof row.__deeplineRowKey === 'string'
3239
3310
  ? row.__deeplineRowKey
3240
- : derivePlayRowIdentity(publicCsvInputRow(row), name);
3311
+ : derivePlayRowIdentity(
3312
+ publicCsvInputRow(row),
3313
+ name,
3314
+ mapLogicFingerprint,
3315
+ );
3241
3316
  if (key) {
3242
3317
  completedKeys.add(key);
3243
3318
  preparedKeys.add(key);
3244
3319
  }
3245
3320
  }
3246
3321
  const missingPreparedRows = chunkRows.filter((row) => {
3247
- const key = derivePlayRowIdentity(publicCsvInputRow(row), name);
3322
+ const key = derivePlayRowIdentity(
3323
+ publicCsvInputRow(row),
3324
+ name,
3325
+ mapLogicFingerprint,
3326
+ );
3248
3327
  return !key || !preparedKeys.has(key);
3249
3328
  });
3250
3329
  const rowsToExecute = chunkRows.filter((row) => {
3251
- const key = derivePlayRowIdentity(publicCsvInputRow(row), name);
3330
+ const key = derivePlayRowIdentity(
3331
+ publicCsvInputRow(row),
3332
+ name,
3333
+ mapLogicFingerprint,
3334
+ );
3252
3335
  return !key || pendingKeys.has(key) || !completedKeys.has(key);
3253
3336
  });
3254
3337
  const rowsInserted = prepared.inserted + missingPreparedRows.length;
@@ -3331,6 +3414,7 @@ function createMinimalWorkerCtx(
3331
3414
  __deeplineRowKey: derivePlayRowIdentity(
3332
3415
  publicCsvInputRow(rowsToExecute[executedIndex]!),
3333
3416
  name,
3417
+ mapLogicFingerprint,
3334
3418
  ),
3335
3419
  })),
3336
3420
  });
@@ -3340,7 +3424,11 @@ function createMinimalWorkerCtx(
3340
3424
  const key =
3341
3425
  typeof completedRow.__deeplineRowKey === 'string'
3342
3426
  ? completedRow.__deeplineRowKey
3343
- : derivePlayRowIdentity(publicCsvInputRow(completedRow), name);
3427
+ : derivePlayRowIdentity(
3428
+ publicCsvInputRow(completedRow),
3429
+ name,
3430
+ mapLogicFingerprint,
3431
+ );
3344
3432
  if (key) {
3345
3433
  const { __deeplineRowKey: _rowKey, ...cleanedRow } =
3346
3434
  publicCsvInputRow(completedRow);
@@ -3357,12 +3445,17 @@ function createMinimalWorkerCtx(
3357
3445
  const key = derivePlayRowIdentity(
3358
3446
  publicCsvInputRow(rowsToExecute[executedIndex]!),
3359
3447
  name,
3448
+ mapLogicFingerprint,
3360
3449
  );
3361
3450
  if (key) resultByKey.set(key, executedRow);
3362
3451
  }
3363
3452
  const out = chunkRows
3364
3453
  .map((row) => {
3365
- const key = derivePlayRowIdentity(publicCsvInputRow(row), name);
3454
+ const key = derivePlayRowIdentity(
3455
+ publicCsvInputRow(row),
3456
+ name,
3457
+ mapLogicFingerprint,
3458
+ );
3366
3459
  return key ? resultByKey.get(key) : undefined;
3367
3460
  })
3368
3461
  .filter((row): row is T & Record<string, unknown> => Boolean(row));
@@ -3620,7 +3713,11 @@ function createMinimalWorkerCtx(
3620
3713
  const childIsMapBacked = childPipelineUsesCtxMap(
3621
3714
  childManifest.staticPipeline,
3622
3715
  );
3716
+ const childNeedsWorkflowScheduler = childPipelineNeedsWorkflowScheduler(
3717
+ childManifest.staticPipeline,
3718
+ );
3623
3719
  let childConcurrencyAcquired = false;
3720
+ let releaseChildPlaySlot: (() => void) | null = null;
3624
3721
  if (childIsMapBacked) {
3625
3722
  const nextInFlight =
3626
3723
  (inFlightChildCallsByPlayName[resolvedName] ?? 0) + 1;
@@ -3635,11 +3732,21 @@ function createMinimalWorkerCtx(
3635
3732
  childConcurrencyAcquired = true;
3636
3733
  }
3637
3734
  try {
3735
+ releaseChildPlaySlot = await acquireChildPlaySlot();
3638
3736
  const childSubmitStartedAt = nowMs();
3639
- let started: { workflowId?: string; runId?: string; error?: unknown };
3737
+ let started: {
3738
+ workflowId?: string;
3739
+ runId?: string;
3740
+ status?: string;
3741
+ output?: unknown;
3742
+ result?: unknown;
3743
+ error?: unknown;
3744
+ };
3640
3745
  try {
3641
3746
  started = await submitChildPlayThroughCoordinator({
3642
3747
  req,
3748
+ allowInline:
3749
+ options?.timeoutMs == null && !childNeedsWorkflowScheduler,
3643
3750
  body: {
3644
3751
  name: resolvedName,
3645
3752
  input: isRecord(input) ? input : {},
@@ -3709,6 +3816,27 @@ function createMinimalWorkerCtx(
3709
3816
  ms: nowMs() - childSubmitStartedAt,
3710
3817
  status: 'ok',
3711
3818
  });
3819
+ const startedStatus = String(started.status ?? '').toLowerCase();
3820
+ if (startedStatus === 'completed') {
3821
+ emitEvent({
3822
+ type: 'log',
3823
+ level: 'info',
3824
+ message: `Completed child play ${resolvedName} (${normalizedKey})`,
3825
+ ts: nowMs(),
3826
+ });
3827
+ return started.output ?? extractChildPlayOutput(started);
3828
+ }
3829
+ if (startedStatus === 'failed') {
3830
+ const startedError = isRecord(started.error)
3831
+ ? started.error
3832
+ : { message: started.error };
3833
+ const startedErrorMessage =
3834
+ typeof startedError.message === 'string' &&
3835
+ startedError.message.trim()
3836
+ ? startedError.message.trim()
3837
+ : `Child play ${resolvedName} (${workflowId}) failed.`;
3838
+ throw new Error(startedErrorMessage);
3839
+ }
3712
3840
  const childWaitStartedAt = nowMs();
3713
3841
  let result: unknown;
3714
3842
  try {
@@ -3761,6 +3889,7 @@ function createMinimalWorkerCtx(
3761
3889
  });
3762
3890
  return result;
3763
3891
  } finally {
3892
+ releaseChildPlaySlot?.();
3764
3893
  if (childConcurrencyAcquired) {
3765
3894
  releaseChildPlayConcurrency(
3766
3895
  inFlightChildCallsByPlayName,
@@ -179,10 +179,26 @@ export class HttpClient {
179
179
  }
180
180
 
181
181
  if (!response.ok) {
182
- const msg =
182
+ const errorValue =
183
183
  typeof parsed === 'object' && parsed && 'error' in parsed
184
- ? String((parsed as Record<string, unknown>).error)
185
- : `HTTP ${response.status}`;
184
+ ? (parsed as Record<string, unknown>).error
185
+ : undefined;
186
+ const msg =
187
+ typeof errorValue === 'string'
188
+ ? errorValue
189
+ : errorValue &&
190
+ typeof errorValue === 'object' &&
191
+ 'message' in errorValue &&
192
+ typeof (errorValue as Record<string, unknown>).message ===
193
+ 'string'
194
+ ? (errorValue as Record<string, string>).message
195
+ : typeof parsed === 'object' &&
196
+ parsed &&
197
+ 'message' in parsed &&
198
+ typeof (parsed as Record<string, unknown>).message ===
199
+ 'string'
200
+ ? (parsed as Record<string, string>).message
201
+ : `HTTP ${response.status}`;
186
202
  throw new DeeplineError(msg, response.status, 'API_ERROR', {
187
203
  response: parsed,
188
204
  });
@@ -1,2 +1,2 @@
1
- export const SDK_VERSION = "0.1.19";
1
+ export const SDK_VERSION = "0.1.20";
2
2
  export const SDK_API_CONTRACT = "2026-05-runs-v2";
@@ -276,13 +276,64 @@ export function resolvePlayRunTableNamespace(
276
276
  export function derivePlayRowIdentity(
277
277
  row: Record<string, unknown>,
278
278
  tableNamespace: string,
279
+ logicFingerprint?: string | null,
279
280
  ): string {
280
- const normalizedNamespace = normalizeTableNamespace(tableNamespace);
281
- const canonicalRow = stableStringify(row);
282
- const digest = sha256Hex(`${normalizedNamespace}\n${canonicalRow}`);
281
+ return deriveDerivedOutputIdentity({
282
+ inputItem: row,
283
+ operationNamespace: tableNamespace,
284
+ logicFingerprint,
285
+ });
286
+ }
287
+
288
+ export function deriveDerivedOutputIdentity(input: {
289
+ inputItem: Record<string, unknown>;
290
+ operationNamespace: string;
291
+ logicFingerprint?: string | null;
292
+ }): string {
293
+ const normalizedNamespace = normalizeTableNamespace(
294
+ input.operationNamespace,
295
+ );
296
+ const canonicalRow = stableStringify(input.inputItem);
297
+ const fingerprint = input.logicFingerprint?.trim()
298
+ ? `\nlogic:${input.logicFingerprint.trim()}`
299
+ : '';
300
+ const digest = sha256Hex(
301
+ `${normalizedNamespace}${fingerprint}\n${canonicalRow}`,
302
+ );
283
303
  return `${normalizedNamespace}:${digest}`;
284
304
  }
285
305
 
306
+ export function deriveToolRequestIdentity(input: {
307
+ toolId: string;
308
+ requestInput: Record<string, unknown>;
309
+ effectiveAccountContext?: string | null;
310
+ toolContractRevision?: string | number | null;
311
+ reuseSafetyPolicy?: string | null;
312
+ }): string {
313
+ const toolId = input.toolId.trim();
314
+ if (!toolId) {
315
+ throw new Error('Tool request identity requires a non-empty tool id.');
316
+ }
317
+ const accountContext =
318
+ input.effectiveAccountContext?.trim() || 'default_account_context';
319
+ const contractRevision =
320
+ input.toolContractRevision == null
321
+ ? 'default_tool_contract'
322
+ : String(input.toolContractRevision).trim() || 'default_tool_contract';
323
+ const reuseSafetyPolicy =
324
+ input.reuseSafetyPolicy?.trim() || 'default_reuse_policy';
325
+ const digest = sha256Hex(
326
+ stableStringify({
327
+ accountContext,
328
+ requestInput: input.requestInput,
329
+ reuseSafetyPolicy,
330
+ toolContractRevision: contractRevision,
331
+ toolId,
332
+ }),
333
+ );
334
+ return `tool:${normalizeTableNamespace(toolId)}:${digest}`;
335
+ }
336
+
286
337
  /**
287
338
  * Build a stable row identity from an explicit user-provided key string.
288
339
  *
@@ -293,9 +344,13 @@ export function derivePlayRowIdentity(
293
344
  export function derivePlayRowIdentityFromKey(
294
345
  key: string,
295
346
  tableNamespace: string,
347
+ logicFingerprint?: string | null,
296
348
  ): string {
297
349
  const normalizedNamespace = normalizeTableNamespace(tableNamespace);
298
- const digest = sha256Hex(`${normalizedNamespace}\nkey:${key}`);
350
+ const fingerprint = logicFingerprint?.trim()
351
+ ? `\nlogic:${logicFingerprint.trim()}`
352
+ : '';
353
+ const digest = sha256Hex(`${normalizedNamespace}${fingerprint}\nkey:${key}`);
299
354
  return `${normalizedNamespace}:${digest}`;
300
355
  }
301
356
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {