deepline 0.1.85 → 0.1.89

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.
@@ -24,6 +24,16 @@ export const COORDINATOR_RUN_SCOPE_HEADER = 'x-deepline-run-scope';
24
24
  export const COORDINATOR_URL_OVERRIDE_HEADER = 'x-deepline-coordinator-url';
25
25
  export const WORKER_CALLBACK_URL_OVERRIDE_HEADER =
26
26
  'x-deepline-worker-callback-url';
27
+ /**
28
+ * CLI→app marker (NOT a coordinator header — it lives here only because both
29
+ * the SDK HTTP client and the run route already import this module). Set by
30
+ * automated test harnesses (e.g. `tests/v2-plays`) so their intentionally
31
+ * failing plays — depth-guard probes, error-path scenarios — do not page the
32
+ * SDK CLI error channel as if a real customer run had failed. The run route
33
+ * honors it ONLY in non-prod (see run/route.ts); a forged header from a real
34
+ * prod customer can never suppress their own failure alerts.
35
+ */
36
+ export const SYNTHETIC_RUN_HEADER = 'x-deepline-synthetic-run';
27
37
 
28
38
  let warnedAboutMissingInternalToken = false;
29
39
 
@@ -136,7 +136,16 @@ export type PlaySchedulerSubmitInput = {
136
136
  };
137
137
 
138
138
  export type PlaySchedulerProgressEvent =
139
- | { type: 'status'; status: string; logs?: string[]; ts: number }
139
+ | {
140
+ type: 'status';
141
+ status: string;
142
+ logs?: string[];
143
+ ts: number;
144
+ activeNodeId?: string | null;
145
+ activeArtifactTableNamespace?: string | null;
146
+ updatedAt?: number | null;
147
+ liveNodeProgress?: unknown;
148
+ }
140
149
  | { type: 'log'; line: string; ts: number }
141
150
  | { type: 'row'; update: PlayRowUpdate; ts: number }
142
151
  | { type: 'execution_event'; event: PlayExecutionEvent; ts: number }
@@ -17,7 +17,10 @@ export type {
17
17
  EmailStatusVerdict,
18
18
  } from './email-status';
19
19
 
20
- import { buildEmailStatus } from './email-status';
20
+ import {
21
+ buildEmailStatus,
22
+ type EmailStatusExtractorConfig,
23
+ } from './email-status';
21
24
  import {
22
25
  JOB_CHANGE_STATUS_VALUES,
23
26
  type JobChangeGetterValue,
@@ -56,12 +59,12 @@ function isRecord(value: unknown): value is Record<string, unknown> {
56
59
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
57
60
  }
58
61
 
59
- export type V2ToolExecuteOutput = {
62
+ type V2ToolExecuteOutput = {
60
63
  raw: unknown;
61
64
  meta?: Record<string, unknown>;
62
65
  };
63
66
 
64
- export function parseV2ToolExecuteOutput(
67
+ function parseV2ToolExecuteOutput(
65
68
  data: Record<string, unknown>,
66
69
  ): V2ToolExecuteOutput | null {
67
70
  const toolResponse = data.toolResponse;
@@ -77,9 +80,10 @@ export function parseV2ToolExecuteOutput(
77
80
  return null;
78
81
  }
79
82
 
80
- export function adaptV2ExecuteResponseToToolResult(
81
- data: Record<string, unknown>,
82
- ): { output?: V2ToolExecuteOutput; result: unknown } {
83
+ function adaptV2ExecuteResponseToToolResult(data: Record<string, unknown>): {
84
+ output?: V2ToolExecuteOutput;
85
+ result: unknown;
86
+ } {
83
87
  const output = parseV2ToolExecuteOutput(data);
84
88
  if (!output) {
85
89
  return { result: data.result ?? data };
@@ -93,7 +97,195 @@ export function adaptV2ExecuteResponseToToolResult(
93
97
  };
94
98
  }
95
99
 
96
- export function toV2RawToolOutputPath(path: string): string {
100
+ /**
101
+ * Parsed view of a raw `/api/v2/integrations/:toolId/execute` JSON body.
102
+ *
103
+ * This is the single runtime-side seam from a V2 execute response to the
104
+ * inputs of {@link createToolExecuteResult}. Both runner substrates (the
105
+ * in-process cjs runtime in `shared_libs/play-runtime/context.ts` and the
106
+ * Workers runtime in `apps/play-runner-workers`) must go through
107
+ * {@link parseToolExecuteResponse}; the intermediate envelope shapes
108
+ * (`toolResponse` adaptation, `_metadata.tool` parsing, status derivation)
109
+ * are private to this module.
110
+ *
111
+ * Boundary note: provider-specific redaction (wiza/apify/bettercontact billing
112
+ * fields) happens server-side in
113
+ * `src/lib/integrations/execute-result-normalization.ts` BEFORE the response
114
+ * leaves the API. This module only ever sees the already-redacted public
115
+ * response — do not move redaction here, it would leak provider internals
116
+ * into the shared runtime bundles.
117
+ */
118
+ export type ParsedToolExecuteResponse = {
119
+ status: string;
120
+ jobId?: string;
121
+ meta?: Record<string, unknown>;
122
+ toolResponse?: {
123
+ raw?: unknown;
124
+ meta?: Record<string, unknown>;
125
+ };
126
+ /** Legacy `{ data, meta }` envelope consumed by createToolExecuteResult. */
127
+ result: unknown;
128
+ /** Tool extractor metadata parsed from `_metadata.tool`, if present. */
129
+ metadata: ToolResultMetadataInput | null;
130
+ };
131
+
132
+ export function parseToolExecuteResponse(
133
+ toolId: string,
134
+ body: Record<string, unknown>,
135
+ ): ParsedToolExecuteResponse {
136
+ const { result } = adaptV2ExecuteResponseToToolResult(body);
137
+ const status =
138
+ typeof body.status === 'string'
139
+ ? body.status
140
+ : result == null
141
+ ? 'no_result'
142
+ : 'completed';
143
+ return {
144
+ status,
145
+ jobId: typeof body.job_id === 'string' ? body.job_id : undefined,
146
+ meta: isRecord(body.meta) ? body.meta : undefined,
147
+ toolResponse: isRecord(body.toolResponse)
148
+ ? (body.toolResponse as ParsedToolExecuteResponse['toolResponse'])
149
+ : undefined,
150
+ result,
151
+ metadata: parseExecuteToolMetadata(toolId, body),
152
+ };
153
+ }
154
+
155
+ function parseExecuteToolMetadata(
156
+ toolId: string,
157
+ data: Record<string, unknown>,
158
+ ): ToolResultMetadataInput | null {
159
+ const metadata = data._metadata;
160
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
161
+ return null;
162
+ }
163
+ const tool = (metadata as Record<string, unknown>).tool;
164
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) return null;
165
+ const record = tool as Record<string, unknown>;
166
+ const metadataToolId =
167
+ typeof record.toolId === 'string' && record.toolId.trim()
168
+ ? record.toolId
169
+ : toolId;
170
+ const readGetters = (value: unknown): Record<string, readonly string[]> => {
171
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
172
+ return Object.fromEntries(
173
+ Object.entries(value as Record<string, unknown>).flatMap(
174
+ ([key, paths]) => {
175
+ if (!Array.isArray(paths)) return [];
176
+ const normalized = paths.filter(
177
+ (path): path is string =>
178
+ typeof path === 'string' && path.trim().length > 0,
179
+ );
180
+ return normalized.length > 0 ? [[key, normalized]] : [];
181
+ },
182
+ ),
183
+ );
184
+ };
185
+ const readExtractors = (
186
+ value: unknown,
187
+ ): ToolResultMetadataInput['extractors'] => {
188
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
189
+ return Object.fromEntries(
190
+ Object.entries(value as Record<string, unknown>).flatMap(
191
+ ([key, entry]) => {
192
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
193
+ return [];
194
+ const recordEntry = entry as Record<string, unknown>;
195
+ if (!Array.isArray(recordEntry.paths)) return [];
196
+ const paths = recordEntry.paths.filter(
197
+ (path): path is string =>
198
+ typeof path === 'string' && path.trim().length > 0,
199
+ );
200
+ if (paths.length === 0) return [];
201
+ const overrides = Array.isArray(recordEntry.overrides)
202
+ ? recordEntry.overrides.flatMap((override) => {
203
+ if (
204
+ !override ||
205
+ typeof override !== 'object' ||
206
+ Array.isArray(override)
207
+ ) {
208
+ return [];
209
+ }
210
+ const overrideRecord = override as Record<string, unknown>;
211
+ const overridePaths = Array.isArray(overrideRecord.paths)
212
+ ? overrideRecord.paths.filter(
213
+ (path): path is string =>
214
+ typeof path === 'string' && path.trim().length > 0,
215
+ )
216
+ : [];
217
+ if (overridePaths.length === 0) return [];
218
+ const readOverridePrimitive = (
219
+ candidate: unknown,
220
+ ): string | number | boolean | null | undefined => {
221
+ if (candidate === null) return null;
222
+ if (typeof candidate === 'string') return candidate;
223
+ if (typeof candidate === 'number') return candidate;
224
+ if (typeof candidate === 'boolean') return candidate;
225
+ return undefined;
226
+ };
227
+ const value = readOverridePrimitive(overrideRecord.value);
228
+ if (value === undefined) {
229
+ return [];
230
+ }
231
+ const equals: string | number | boolean | null =
232
+ readOverridePrimitive(overrideRecord.equals) ?? true;
233
+ return [{ paths: overridePaths, equals, value }];
234
+ })
235
+ : [];
236
+ const emailStatus =
237
+ recordEntry.emailStatus &&
238
+ typeof recordEntry.emailStatus === 'object' &&
239
+ !Array.isArray(recordEntry.emailStatus)
240
+ ? (recordEntry.emailStatus as EmailStatusExtractorConfig)
241
+ : undefined;
242
+ return [
243
+ [
244
+ key,
245
+ {
246
+ paths,
247
+ ...(Array.isArray(recordEntry.transforms)
248
+ ? {
249
+ transforms: recordEntry.transforms.filter(
250
+ (transform): transform is string =>
251
+ typeof transform === 'string' &&
252
+ transform.trim().length > 0,
253
+ ),
254
+ }
255
+ : {}),
256
+ ...(Array.isArray(recordEntry.enum)
257
+ ? {
258
+ enum: recordEntry.enum.filter(
259
+ (value): value is string =>
260
+ typeof value === 'string' && value.trim().length > 0,
261
+ ),
262
+ }
263
+ : {}),
264
+ ...(overrides.length > 0 ? { overrides } : {}),
265
+ ...(emailStatus ? { emailStatus } : {}),
266
+ },
267
+ ],
268
+ ];
269
+ },
270
+ ),
271
+ );
272
+ };
273
+ const listExtractorPaths = Array.isArray(record.listExtractorPaths)
274
+ ? record.listExtractorPaths.filter(
275
+ (path): path is string =>
276
+ typeof path === 'string' && path.trim().length > 0,
277
+ )
278
+ : [];
279
+ return {
280
+ toolId: metadataToolId,
281
+ extractors: readExtractors(record.extractors),
282
+ targetGetters: readGetters(record.targetGetters),
283
+ listExtractorPaths,
284
+ listIdentityGetters: readGetters(record.listIdentityGetters),
285
+ };
286
+ }
287
+
288
+ function toV2RawToolOutputPath(path: string): string {
97
289
  const normalized = String(path || '')
98
290
  .trim()
99
291
  .replace(/^\./, '');
@@ -445,9 +637,7 @@ function normalizeJobChangeStatus(value: unknown): unknown {
445
637
  if (['false', 'no', 'same', 'no_change'].includes(normalized))
446
638
  return 'no_change';
447
639
  if (['left', 'left_company'].includes(normalized)) return 'left_company';
448
- if (
449
- (JOB_CHANGE_STATUS_VALUES as readonly string[]).includes(normalized)
450
- ) {
640
+ if ((JOB_CHANGE_STATUS_VALUES as readonly string[]).includes(normalized)) {
451
641
  return normalized;
452
642
  }
453
643
  return 'unknown';
@@ -481,12 +671,12 @@ function normalizeJobChange(value: unknown): JobChangeGetterValue {
481
671
  return {
482
672
  status,
483
673
  date: moved
484
- ? normalizeString(
674
+ ? (normalizeString(
485
675
  output.date ??
486
676
  output.job_change_date ??
487
677
  output.change_date ??
488
678
  output.changed_at,
489
- ) ?? firstExperienceDate(person.experiences)
679
+ ) ?? firstExperienceDate(person.experiences))
490
680
  : null,
491
681
  new_company: moved
492
682
  ? normalizeString(
@@ -26,10 +26,7 @@ import type {
26
26
  } from '../artifact-types';
27
27
  import { buildPlayContractCompatibility } from '../contracts';
28
28
  import { validatePlaySourceFilesHaveNoInlineSecrets } from '../secret-guardrails';
29
- import {
30
- MAX_ESM_WORKERS_BUNDLE_BYTES,
31
- MAX_PLAY_BUNDLE_BYTES,
32
- } from './limits';
29
+ import { MAX_ESM_WORKERS_BUNDLE_BYTES, MAX_PLAY_BUNDLE_BYTES } from './limits';
33
30
 
34
31
  const PLAY_BUNDLE_CACHE_VERSION = 24;
35
32
  const PLAY_ARTIFACT_CACHE_DIR = join(
@@ -443,7 +440,13 @@ function findPackageJsonPathFrom(
443
440
  startDir: string,
444
441
  packageName: string,
445
442
  ): string | null {
446
- let current = resolve(startDir);
443
+ if (!isAbsolute(startDir)) {
444
+ throw new Error(
445
+ `Package resolution requires an absolute start directory, got ${startDir}`,
446
+ );
447
+ }
448
+
449
+ let current = startDir;
447
450
  while (true) {
448
451
  const packageJsonPath = join(
449
452
  current,
@@ -469,17 +472,15 @@ function findPackageJsonPath(
469
472
  adapter: PlayBundlingAdapter,
470
473
  ): string | null {
471
474
  const startDirs = [
472
- dirname(fromFile),
473
- adapter.projectRoot,
474
- dirname(adapter.sdkPackageJson),
475
- process.cwd(),
475
+ resolve(dirname(fromFile)),
476
+ resolve(adapter.projectRoot),
477
+ resolve(dirname(adapter.sdkPackageJson)),
476
478
  ];
477
479
  const seen = new Set<string>();
478
480
  for (const startDir of startDirs) {
479
- const normalized = resolve(startDir);
480
- if (seen.has(normalized)) continue;
481
- seen.add(normalized);
482
- const packageJsonPath = findPackageJsonPathFrom(normalized, packageName);
481
+ if (seen.has(startDir)) continue;
482
+ seen.add(startDir);
483
+ const packageJsonPath = findPackageJsonPathFrom(startDir, packageName);
483
484
  if (packageJsonPath) return packageJsonPath;
484
485
  }
485
486
 
@@ -1139,7 +1140,10 @@ async function writeArtifactCache(
1139
1140
  );
1140
1141
  }
1141
1142
 
1142
- function normalizeSourceMapForRuntime(sourceMapText: string): string {
1143
+ function normalizeSourceMapForRuntime(
1144
+ sourceMapText: string,
1145
+ projectRoot: string,
1146
+ ): string {
1143
1147
  const parsed = JSON.parse(sourceMapText) as {
1144
1148
  sourceRoot?: string;
1145
1149
  sources?: string[];
@@ -1155,7 +1159,7 @@ function normalizeSourceMapForRuntime(sourceMapText: string): string {
1155
1159
  return sourcePath;
1156
1160
  }
1157
1161
 
1158
- return resolve(process.cwd(), sourcePath);
1162
+ return join(projectRoot, sourcePath);
1159
1163
  });
1160
1164
  parsed.sourceRoot = undefined;
1161
1165
 
@@ -1524,7 +1528,10 @@ export async function bundlePlayFile(
1524
1528
  }
1525
1529
  const { bundledCode, sourceMapText, outputExtension } = buildOutcome;
1526
1530
 
1527
- const normalizedSourceMap = normalizeSourceMapForRuntime(sourceMapText);
1531
+ const normalizedSourceMap = normalizeSourceMapForRuntime(
1532
+ sourceMapText,
1533
+ resolve(adapter.projectRoot),
1534
+ );
1528
1535
  const virtualBaseName =
1529
1536
  exportName === 'default'
1530
1537
  ? basename(absolutePath).replace(/\.[^.]+$/, '')
@@ -74,6 +74,8 @@ export type PlayDatasetTransformOptions = {
74
74
  * small and bounded. `PlayDataset` intentionally does not expose `.rows`,
75
75
  * `.toArray()`, or other array aliases; those hide the runtime cost of loading
76
76
  * persisted rows into memory.
77
+ *
78
+ * @sdkReference runtime 190
77
79
  */
78
80
  export interface PlayDataset<T> extends AsyncIterable<T> {
79
81
  readonly [PLAY_DATASET_BRAND]: true;
@@ -169,19 +169,25 @@ export function normalizePlayName(value: string): string {
169
169
  return validateIdentifierPart(value, 'Play name', PLAY_NAME_MAX_LENGTH);
170
170
  }
171
171
 
172
+ /**
173
+ * Normalize a play name into the leading segment of a physical sheet table name.
174
+ *
175
+ * A qualified reference like "prebuilt/name-and-domain-to-email-waterfall" folds
176
+ * its namespace separator into a plain identifier
177
+ * ("prebuilt_name_and_domain_to_email_waterfall") so the physical table reads
178
+ * cleanly. `validatePlaySheetTableName` enforces the 63-char Postgres identifier
179
+ * limit on the play + namespace combination and fails loudly when a name is
180
+ * genuinely too long — no opaque content digest is mixed into the table name.
181
+ */
172
182
  export function normalizePlayNameForSheet(value: string): string {
173
183
  if (!value.includes('/')) {
174
184
  return normalizePlayName(value);
175
185
  }
176
- const digest = sha256Hex(value).slice(0, 12);
177
- const normalizedReference = sanitizeIdentifierPart(
178
- value.replace(/\//g, '__'),
186
+ return validateIdentifierPart(
187
+ value.replace(/\//g, '_'),
188
+ 'Play name',
189
+ PLAY_NAME_MAX_LENGTH,
179
190
  );
180
- const prefixLength = Math.max(1, PLAY_NAME_MAX_LENGTH - digest.length - 1);
181
- const prefix =
182
- normalizedReference.slice(0, prefixLength).replace(/_+$/g, '') ||
183
- 'qualified_play';
184
- return `${prefix}_${digest}`;
185
191
  }
186
192
 
187
193
  export function normalizeTableNamespace(value: string): string {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.85",
3
+ "version": "0.1.89",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {