deepline 0.1.38 → 0.1.40

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/index.mjs CHANGED
@@ -169,8 +169,8 @@ function resolveConfig(options) {
169
169
  }
170
170
 
171
171
  // src/version.ts
172
- var SDK_VERSION = "0.1.38";
173
- var SDK_API_CONTRACT = "2026-05-v2-tool-response-play-guardrails";
172
+ var SDK_VERSION = "0.1.40";
173
+ var SDK_API_CONTRACT = "2026-05-cloud-play-search";
174
174
 
175
175
  // ../shared_libs/play-runtime/coordinator-headers.ts
176
176
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -1333,23 +1333,15 @@ var DeeplineClient = class {
1333
1333
  return response.plays ?? [];
1334
1334
  }
1335
1335
  async searchPlays(options) {
1336
- const query = options.query.trim().toLowerCase();
1337
- const terms = query.split(/\s+/).filter(Boolean);
1338
- const plays = await this.listPlays();
1339
- return plays.filter((play) => {
1340
- if (options.origin && (play.origin ?? "owned") !== options.origin) {
1341
- return false;
1342
- }
1343
- const haystack = [
1344
- play.name,
1345
- play.reference,
1346
- play.displayName,
1347
- play.origin,
1348
- ...play.aliases ?? [],
1349
- play.inputSchema ? JSON.stringify(play.inputSchema) : ""
1350
- ].filter(Boolean).join(" ").toLowerCase();
1351
- return terms.every((term) => haystack.includes(term));
1352
- }).map((play) => this.summarizePlayListItem(play, options));
1336
+ const params = new URLSearchParams();
1337
+ params.set("search", options.query.trim());
1338
+ if (options.origin) params.set("origin", options.origin);
1339
+ const response = await this.http.get(
1340
+ `/api/v2/plays?${params.toString()}`
1341
+ );
1342
+ return (response.plays ?? []).map(
1343
+ (play) => this.summarizePlayListItem(play, options)
1344
+ );
1353
1345
  }
1354
1346
  /**
1355
1347
  * Get the full definition and state of a named play.
@@ -1845,8 +1837,9 @@ function buildTargets(result, resultIdentityGetters) {
1845
1837
  }
1846
1838
  return targets;
1847
1839
  }
1848
- function buildLists(resolved, metadata) {
1840
+ function buildLists(result, metadata) {
1849
1841
  const lists = {};
1842
+ const resolved = resolveListRows(result, metadata.listExtractorPaths);
1850
1843
  for (const [name, list] of Object.entries(resolved)) {
1851
1844
  lists[name] = {
1852
1845
  path: list.path,
@@ -1881,10 +1874,9 @@ function buildExtractedAccessors(targets) {
1881
1874
  })
1882
1875
  );
1883
1876
  }
1884
- function buildListAccessors(resolved, lists) {
1877
+ function buildListAccessors(result, lists) {
1885
1878
  return Object.fromEntries(
1886
1879
  Object.entries(lists).map(([name, metadata]) => {
1887
- const rows = resolved[name]?.rows ?? [];
1888
1880
  const accessor = {
1889
1881
  path: metadata.path,
1890
1882
  count: metadata.count,
@@ -1892,7 +1884,7 @@ function buildListAccessors(resolved, lists) {
1892
1884
  };
1893
1885
  Object.defineProperty(accessor, "get", {
1894
1886
  value() {
1895
- return rows;
1887
+ return normalizeRows(getAtPath(result, metadata.path)) ?? [];
1896
1888
  },
1897
1889
  enumerable: false
1898
1890
  });
@@ -1912,11 +1904,7 @@ function createToolExecuteResult(input) {
1912
1904
  resultRoot,
1913
1905
  input.metadata.resultIdentityGetters
1914
1906
  );
1915
- const resolvedLists = resolveListRows(
1916
- resultRoot,
1917
- input.metadata.listExtractorPaths
1918
- );
1919
- const lists = buildLists(resolvedLists, input.metadata);
1907
+ const lists = buildLists(resultRoot, input.metadata);
1920
1908
  const metadata = {
1921
1909
  toolId: input.metadata.toolId,
1922
1910
  execution: input.execution,
@@ -1928,7 +1916,7 @@ function createToolExecuteResult(input) {
1928
1916
  ...result.meta ? { meta: result.meta } : {}
1929
1917
  };
1930
1918
  const extractedValues = buildExtractedAccessors(targets);
1931
- const extractedLists = buildListAccessors(resolvedLists, lists);
1919
+ const extractedLists = buildListAccessors(resultRoot, lists);
1932
1920
  const wrapper = {
1933
1921
  status: input.status,
1934
1922
  ...input.jobId ? { job_id: input.jobId } : {},
@@ -52,10 +52,7 @@ import {
52
52
  import {
53
53
  adaptV2ExecuteResponseToToolResult,
54
54
  createToolExecuteResult,
55
- deserializeToolExecuteResult,
56
- isSerializedToolExecuteResult,
57
55
  isToolExecuteResult,
58
- serializeToolExecuteResult,
59
56
  type ToolExecuteResult,
60
57
  type ToolResultMetadataInput,
61
58
  } from '../../../shared_libs/play-runtime/tool-result';
@@ -451,7 +448,7 @@ async function probeHarnessOnce(
451
448
  */
452
449
  const RUNTIME_API_TIMEOUT_MS = 30_000;
453
450
  const RUNTIME_API_PLAY_RUN_TIMEOUT_MS = 75_000;
454
- const RUNTIME_API_RETRY_DELAYS_MS = [250, 750, 1500, 3000, 5000, 10000] as const;
451
+ const RUNTIME_API_RETRY_DELAYS_MS = [250, 750, 1500] as const;
455
452
  let loggedMissingRuntimeApiBinding = false;
456
453
 
457
454
  async function fetchRuntimeApi(
@@ -464,17 +461,7 @@ async function fetchRuntimeApi(
464
461
  ? RUNTIME_API_PLAY_RUN_TIMEOUT_MS
465
462
  : RUNTIME_API_TIMEOUT_MS;
466
463
  const controller = new AbortController();
467
- let timeout: ReturnType<typeof setTimeout> | null = null;
468
- const timeoutPromise = new Promise<never>((_, reject) => {
469
- timeout = setTimeout(() => {
470
- controller.abort();
471
- reject(
472
- new Error(
473
- `[play-harness] runtime API call timed out after ${timeoutMs}ms. path=${path} baseUrl=${baseUrl}`,
474
- ),
475
- );
476
- }, timeoutMs);
477
- });
464
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
478
465
  try {
479
466
  const mergedInit: RequestInit = {
480
467
  ...init,
@@ -488,15 +475,11 @@ async function fetchRuntimeApi(
488
475
  `[play-harness] RUNTIME_API binding missing; using public runtime API transport. path=${path}`,
489
476
  );
490
477
  }
491
- return await Promise.race([
492
- fetch(`${baseUrl.replace(/\/$/, '')}${path}`, mergedInit),
493
- timeoutPromise,
494
- ]);
478
+ return await fetch(`${baseUrl.replace(/\/$/, '')}${path}`, mergedInit);
495
479
  }
496
- const responsePromise = cachedRuntimeApiBinding.fetch(
480
+ return await cachedRuntimeApiBinding.fetch(
497
481
  new Request(`${baseUrl.replace(/\/$/, '')}${path}`, mergedInit),
498
482
  );
499
- return await Promise.race([responsePromise, timeoutPromise]);
500
483
  } catch (err) {
501
484
  if (err instanceof Error && err.name === 'AbortError') {
502
485
  throw new Error(
@@ -505,7 +488,7 @@ async function fetchRuntimeApi(
505
488
  }
506
489
  throw err;
507
490
  } finally {
508
- if (timeout) clearTimeout(timeout);
491
+ clearTimeout(timer);
509
492
  }
510
493
  }
511
494
 
@@ -741,8 +724,7 @@ function isRetryableRuntimeApiResponse(status: number, body: string): boolean {
741
724
  status === 429 ||
742
725
  status === 502 ||
743
726
  status === 503 ||
744
- status === 504 ||
745
- status === 530
727
+ status === 504
746
728
  ) {
747
729
  return true;
748
730
  }
@@ -1472,21 +1454,6 @@ function executeSyntheticTransientRetry(
1472
1454
  function executeSyntheticTestRateLimit(
1473
1455
  input: Record<string, unknown>,
1474
1456
  ): Record<string, unknown> {
1475
- if (
1476
- typeof input.key === 'string' &&
1477
- input.key.startsWith('public-error-message-regression')
1478
- ) {
1479
- throw new ToolHttpError(
1480
- [
1481
- 'tool test_rate_limit 422 attempt 1/1:',
1482
- 'Synthetic public test error with a redacted token=[REDACTED].',
1483
- 'code=TEST_PUBLIC_ERROR.',
1484
- 'failure_description=The fake test provider intentionally raised a typed public error so V2 runner output preserves actionable details.',
1485
- 'operator_hint=Use this no-bill test provider fixture when verifying play runner error rendering.',
1486
- ].join(' '),
1487
- null,
1488
- );
1489
- }
1490
1457
  const rowNumber =
1491
1458
  typeof input.row_number === 'number' && Number.isInteger(input.row_number)
1492
1459
  ? input.row_number
@@ -1883,39 +1850,8 @@ type WorkerMapChunkSummary<T extends Record<string, unknown>> = {
1883
1850
  cachedRows?: T[];
1884
1851
  };
1885
1852
 
1886
- function serializeDurableStepValue<T>(value: T, depth = 0): T {
1887
- if (depth > 20 || value == null) return value;
1888
- if (isToolExecuteResult(value)) return serializeToolExecuteResult(value) as T;
1889
- if (isDatasetHandle(value)) return serializeValue(value, depth) as T;
1890
- if (Array.isArray(value)) {
1891
- return value.map((entry) => serializeDurableStepValue(entry, depth + 1)) as T;
1892
- }
1893
- if (typeof value !== 'object') return value;
1894
- return Object.fromEntries(
1895
- Object.entries(value as Record<string, unknown>).map(([key, child]) => [
1896
- key,
1897
- serializeDurableStepValue(child, depth + 1),
1898
- ]),
1899
- ) as T;
1900
- }
1901
-
1902
- function deserializeDurableStepValue<T>(value: T, depth = 0): T {
1903
- if (depth > 20 || value == null) return value;
1904
- if (isSerializedToolExecuteResult(value)) {
1905
- return deserializeToolExecuteResult(value) as T;
1906
- }
1907
- if (Array.isArray(value)) {
1908
- return value.map((entry) =>
1909
- deserializeDurableStepValue(entry, depth + 1),
1910
- ) as T;
1911
- }
1912
- if (typeof value !== 'object') return value;
1913
- return Object.fromEntries(
1914
- Object.entries(value as Record<string, unknown>).map(([key, child]) => [
1915
- key,
1916
- deserializeDurableStepValue(child, depth + 1),
1917
- ]),
1918
- ) as T;
1853
+ function toWorkflowSerializableValue<T>(value: T): T {
1854
+ return JSON.parse(JSON.stringify(serializeValue(value, 0))) as T;
1919
1855
  }
1920
1856
 
1921
1857
  type WorkerStepResolver = (
@@ -2037,8 +1973,8 @@ async function executeWorkerStepProgram(
2037
1973
  let currentRow: Record<string, unknown> = cloneCsvAliasedRow(inputRow);
2038
1974
  for (const step of program.steps) {
2039
1975
  const stepPath = [...(recorder?.path ?? []), step.name];
2040
- const runStep = async (): Promise<WorkerStepResolution> => {
2041
- const resolution = await executeWorkerStepResolver(
1976
+ const runStep = async () =>
1977
+ await executeWorkerStepResolver(
2042
1978
  step.resolver,
2043
1979
  currentRow,
2044
1980
  ctx,
@@ -2046,15 +1982,10 @@ async function executeWorkerStepProgram(
2046
1982
  recorder
2047
1983
  ? {
2048
1984
  ...recorder,
2049
- path: stepPath,
2050
- }
2051
- : undefined,
1985
+ path: stepPath,
1986
+ }
1987
+ : undefined,
2052
1988
  );
2053
- return {
2054
- value: serializeDurableStepValue(resolution.value),
2055
- ...(resolution.status ? { status: resolution.status } : {}),
2056
- };
2057
- };
2058
1989
  const resolution = workflowStep
2059
1990
  ? await (
2060
1991
  workflowStep.do as unknown as (
@@ -2063,7 +1994,7 @@ async function executeWorkerStepProgram(
2063
1994
  ) => Promise<WorkerStepResolution>
2064
1995
  )(stepPath.join('.'), runStep)
2065
1996
  : await runStep();
2066
- const value = deserializeDurableStepValue(resolution.value);
1997
+ const value = resolution.value;
2067
1998
  currentRow = cloneCsvAliasedRow(currentRow, { [step.name]: value });
2068
1999
  if (recorder) {
2069
2000
  const stepId = stepPath.join('.');
@@ -3622,10 +3553,10 @@ function createMinimalWorkerCtx(
3622
3553
  rowsSkipped,
3623
3554
  outputDatasetId: `map:${name}`,
3624
3555
  hash,
3625
- preview: serializeDurableStepValue(out.slice(0, 5)),
3556
+ preview: toWorkflowSerializableValue(out.slice(0, 5)),
3626
3557
  cachedRows:
3627
3558
  out.length <= WORKER_DATASET_IN_MEMORY_ROWS
3628
- ? serializeDurableStepValue(out)
3559
+ ? toWorkflowSerializableValue(out)
3629
3560
  : undefined,
3630
3561
  };
3631
3562
  };
@@ -3853,16 +3784,13 @@ function createMinimalWorkerCtx(
3853
3784
  ts: nowMs(),
3854
3785
  });
3855
3786
  }
3856
- // Static pipeline JS blocks already execute inside a Workflow step.
3857
- // Wrapping each generated waterfall step in another step.do can leave
3858
- // Workers preview runs parked after the last provider callback.
3859
3787
  return (await executeWorkerStepProgram(
3860
3788
  program,
3861
3789
  input,
3862
3790
  ctx,
3863
3791
  0,
3864
3792
  undefined,
3865
- undefined,
3793
+ workflowStep,
3866
3794
  )) as T;
3867
3795
  },
3868
3796
  async csv<T extends Record<string, unknown> = Record<string, unknown>>(
@@ -1377,28 +1377,15 @@ export class DeeplineClient {
1377
1377
  origin?: 'prebuilt' | 'owned';
1378
1378
  compact?: boolean;
1379
1379
  }): Promise<PlayDescription[]> {
1380
- const query = options.query.trim().toLowerCase();
1381
- const terms = query.split(/\s+/).filter(Boolean);
1382
- const plays = await this.listPlays();
1383
- return plays
1384
- .filter((play) => {
1385
- if (options.origin && (play.origin ?? 'owned') !== options.origin) {
1386
- return false;
1387
- }
1388
- const haystack = [
1389
- play.name,
1390
- play.reference,
1391
- play.displayName,
1392
- play.origin,
1393
- ...(play.aliases ?? []),
1394
- play.inputSchema ? JSON.stringify(play.inputSchema) : '',
1395
- ]
1396
- .filter(Boolean)
1397
- .join(' ')
1398
- .toLowerCase();
1399
- return terms.every((term) => haystack.includes(term));
1400
- })
1401
- .map((play) => this.summarizePlayListItem(play, options));
1380
+ const params = new URLSearchParams();
1381
+ params.set('search', options.query.trim());
1382
+ if (options.origin) params.set('origin', options.origin);
1383
+ const response = await this.http.get<{ plays: PlayListItem[] }>(
1384
+ `/api/v2/plays?${params.toString()}`,
1385
+ );
1386
+ return (response.plays ?? []).map((play) =>
1387
+ this.summarizePlayListItem(play, options),
1388
+ );
1402
1389
  }
1403
1390
 
1404
1391
  /**
@@ -626,27 +626,10 @@ export interface PlayCheckResult {
626
626
  valid: boolean;
627
627
  errors: string[];
628
628
  staticPipeline?: Record<string, unknown> | null;
629
- toolGetterHints?: PlayCheckToolGetterHint[];
630
629
  artifactHash?: string | null;
631
630
  graphHash?: string | null;
632
631
  }
633
632
 
634
- export interface PlayCheckToolGetterHint {
635
- toolId: string;
636
- lists: Array<{
637
- name: string;
638
- expression: string;
639
- raw?: string;
640
- }>;
641
- values: Array<{
642
- name: string;
643
- expression: string;
644
- raw?: string;
645
- }>;
646
- raw?: string;
647
- unavailable?: string;
648
- }
649
-
650
633
  /**
651
634
  * Request body for starting a play run via {@link DeeplineClient.startPlayRun}.
652
635
  *
@@ -1,2 +1,2 @@
1
- export const SDK_VERSION = "0.1.38";
2
- export const SDK_API_CONTRACT = "2026-05-v2-tool-response-play-guardrails";
1
+ export const SDK_VERSION = "0.1.40";
2
+ export const SDK_API_CONTRACT = "2026-05-cloud-play-search";
@@ -1,5 +1,4 @@
1
1
  export type {
2
- SerializedToolExecuteResult,
3
2
  ToolExecuteResult,
4
3
  ToolResultExecutionMetadata,
5
4
  ToolResultMetadata,
@@ -11,7 +10,6 @@ export type {
11
10
  } from './tool-result-types';
12
11
 
13
12
  import type {
14
- SerializedToolExecuteResult,
15
13
  ToolExecuteResult,
16
14
  ToolResultExecutionMetadata,
17
15
  ToolResultListAccessor,
@@ -24,8 +22,6 @@ import type {
24
22
 
25
23
  type PathSegment = string | number | '*';
26
24
 
27
- const SERIALIZED_TOOL_EXECUTE_RESULT_KIND = 'deepline.tool_execute_result.v1';
28
-
29
25
  const TARGET_FALLBACK_KEYS: Record<string, readonly RegExp[]> = {
30
26
  email: [/^email$/i, /^address$/i, /email/i],
31
27
  phone: [/^phone$/i, /mobile/i, /phone/i, /telephone/i],
@@ -459,10 +455,11 @@ function buildTargets(
459
455
  }
460
456
 
461
457
  function buildLists(
462
- resolved: Record<string, { path: string; rows: Record<string, unknown>[] }>,
458
+ result: unknown,
463
459
  metadata: ToolResultMetadataInput,
464
460
  ): Record<string, ToolResultListMetadata> {
465
461
  const lists: Record<string, ToolResultListMetadata> = {};
462
+ const resolved = resolveListRows(result, metadata.listExtractorPaths);
466
463
  for (const [name, list] of Object.entries(resolved)) {
467
464
  lists[name] = {
468
465
  path: list.path,
@@ -502,12 +499,11 @@ function buildExtractedAccessors(
502
499
  }
503
500
 
504
501
  function buildListAccessors(
505
- resolved: Record<string, { path: string; rows: Record<string, unknown>[] }>,
502
+ result: unknown,
506
503
  lists: Record<string, ToolResultListMetadata>,
507
504
  ): Record<string, ToolResultListAccessor> {
508
505
  return Object.fromEntries(
509
506
  Object.entries(lists).map(([name, metadata]) => {
510
- const rows = resolved[name]?.rows ?? [];
511
507
  const accessor = {
512
508
  path: metadata.path,
513
509
  count: metadata.count,
@@ -515,7 +511,7 @@ function buildListAccessors(
515
511
  } as ToolResultListAccessor;
516
512
  Object.defineProperty(accessor, 'get', {
517
513
  value() {
518
- return rows;
514
+ return normalizeRows(getAtPath(result, metadata.path)) ?? [];
519
515
  },
520
516
  enumerable: false,
521
517
  });
@@ -543,11 +539,7 @@ export function createToolExecuteResult<TResult = unknown>(input: {
543
539
  resultRoot,
544
540
  input.metadata.resultIdentityGetters,
545
541
  );
546
- const resolvedLists = resolveListRows(
547
- resultRoot,
548
- input.metadata.listExtractorPaths,
549
- );
550
- const lists = buildLists(resolvedLists, input.metadata);
542
+ const lists = buildLists(resultRoot, input.metadata);
551
543
  const metadata = {
552
544
  toolId: input.metadata.toolId,
553
545
  execution: input.execution,
@@ -559,7 +551,7 @@ export function createToolExecuteResult<TResult = unknown>(input: {
559
551
  ...(result.meta ? { meta: result.meta } : {}),
560
552
  };
561
553
  const extractedValues = buildExtractedAccessors(targets);
562
- const extractedLists = buildListAccessors(resolvedLists, lists);
554
+ const extractedLists = buildListAccessors(resultRoot, lists);
563
555
  const wrapper = {
564
556
  status: input.status,
565
557
  ...(input.jobId ? { job_id: input.jobId } : {}),
@@ -594,70 +586,6 @@ export function isToolExecuteResult(
594
586
  );
595
587
  }
596
588
 
597
- function metadataInputFromToolExecuteResult(
598
- value: ToolExecuteResult,
599
- ): ToolResultMetadataInput {
600
- return {
601
- toolId: value._metadata.toolId,
602
- resultIdentityGetters: Object.fromEntries(
603
- Object.entries(value._metadata.targets).map(([target, info]) => [
604
- target,
605
- [info.path],
606
- ]),
607
- ),
608
- listExtractorPaths: Object.values(value._metadata.lists).map(
609
- (list) => list.path,
610
- ),
611
- listIdentityGetters: Object.fromEntries(
612
- Object.values(value._metadata.lists)
613
- .flatMap((list) => Object.entries(list.keys))
614
- .map(([target, path]) => [target, [path]]),
615
- ),
616
- };
617
- }
618
-
619
- export function serializeToolExecuteResult(
620
- value: ToolExecuteResult,
621
- ): SerializedToolExecuteResult {
622
- return {
623
- __kind: SERIALIZED_TOOL_EXECUTE_RESULT_KIND,
624
- status: value.status,
625
- toolResponse: {
626
- raw: value.toolResponse.raw,
627
- ...(value.toolResponse.meta ? { meta: value.toolResponse.meta } : {}),
628
- },
629
- metadata: metadataInputFromToolExecuteResult(value),
630
- execution: value._metadata.execution,
631
- };
632
- }
633
-
634
- export function isSerializedToolExecuteResult(
635
- value: unknown,
636
- ): value is SerializedToolExecuteResult {
637
- return (
638
- isRecord(value) &&
639
- value.__kind === SERIALIZED_TOOL_EXECUTE_RESULT_KIND &&
640
- typeof value.status === 'string' &&
641
- isRecord(value.toolResponse) &&
642
- isRecord(value.metadata) &&
643
- isRecord(value.execution)
644
- );
645
- }
646
-
647
- export function deserializeToolExecuteResult(
648
- value: SerializedToolExecuteResult,
649
- ): ToolExecuteResult {
650
- return createToolExecuteResult({
651
- status: value.status,
652
- result: {
653
- data: value.toolResponse.raw,
654
- ...(value.toolResponse.meta ? { meta: value.toolResponse.meta } : {}),
655
- },
656
- metadata: value.metadata,
657
- execution: value.execution,
658
- });
659
- }
660
-
661
589
  export function cloneToolExecuteResultWithExecution<TResult>(
662
590
  value: ToolExecuteResult<TResult>,
663
591
  execution: ToolResultExecutionMetadata,
@@ -672,7 +600,23 @@ export function cloneToolExecuteResultWithExecution<TResult>(
672
600
  data: value.toolResponse.raw,
673
601
  ...(value.toolResponse.meta ? { meta: value.toolResponse.meta } : {}),
674
602
  } as TResult,
675
- metadata: metadataInputFromToolExecuteResult(value),
603
+ metadata: {
604
+ toolId: value._metadata.toolId,
605
+ resultIdentityGetters: Object.fromEntries(
606
+ Object.entries(value._metadata.targets).map(([target, info]) => [
607
+ target,
608
+ [info.path],
609
+ ]),
610
+ ),
611
+ listExtractorPaths: Object.values(value._metadata.lists).map(
612
+ (list) => list.path,
613
+ ),
614
+ listIdentityGetters: Object.fromEntries(
615
+ Object.values(value._metadata.lists)
616
+ .flatMap((list) => Object.entries(list.keys))
617
+ .map(([target, path]) => [target, [path]]),
618
+ ),
619
+ },
676
620
  execution,
677
621
  meta: isRecord(value.meta) ? value.meta : undefined,
678
622
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {