deepline 0.1.19 → 0.1.21

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.
@@ -163,6 +163,15 @@ function mapLegacyTemporalStatus(status: string): PlayStatus['status'] {
163
163
  }
164
164
  }
165
165
 
166
+ function decodeBase64Bytes(value: string): Uint8Array {
167
+ const binary = atob(value);
168
+ const bytes = new Uint8Array(binary.length);
169
+ for (let index = 0; index < binary.length; index += 1) {
170
+ bytes[index] = binary.charCodeAt(index);
171
+ }
172
+ return bytes;
173
+ }
174
+
166
175
  /**
167
176
  * Low-level client for the Deepline REST API.
168
177
  *
@@ -849,9 +858,34 @@ export class DeeplineClient {
849
858
  bytes: number;
850
859
  }>,
851
860
  ): Promise<PlayStagedFileRef[]> {
852
- const response = await this.http.post<{ files: PlayStagedFileRef[] }>(
861
+ const formData = new FormData();
862
+ formData.set(
863
+ 'metadata',
864
+ JSON.stringify({
865
+ files: files.map((file, index) => ({
866
+ index,
867
+ logicalPath: file.logicalPath,
868
+ contentHash: file.contentHash,
869
+ contentType: file.contentType,
870
+ bytes: file.bytes,
871
+ })),
872
+ }),
873
+ );
874
+ for (const [index, file] of files.entries()) {
875
+ const bytes = decodeBase64Bytes(file.contentBase64);
876
+ const body = bytes.buffer.slice(
877
+ bytes.byteOffset,
878
+ bytes.byteOffset + bytes.byteLength,
879
+ ) as ArrayBuffer;
880
+ formData.set(
881
+ `file:${index}`,
882
+ new Blob([body], { type: file.contentType }),
883
+ file.logicalPath,
884
+ );
885
+ }
886
+ const response = await this.http.postFormData<{ files: PlayStagedFileRef[] }>(
853
887
  '/api/v2/plays/files/stage',
854
- { files },
888
+ formData,
855
889
  );
856
890
  return response.files;
857
891
  }
@@ -896,9 +930,17 @@ export class DeeplineClient {
896
930
  * console.log(`Logs: ${status.progress?.logs.length ?? 0} lines`);
897
931
  * ```
898
932
  */
899
- async getPlayStatus(workflowId: string): Promise<PlayStatus> {
933
+ async getPlayStatus(
934
+ workflowId: string,
935
+ options?: { billing?: boolean },
936
+ ): Promise<PlayStatus> {
937
+ const params = new URLSearchParams();
938
+ if (options?.billing === false) {
939
+ params.set('billing', 'false');
940
+ }
941
+ const query = params.size > 0 ? `?${params.toString()}` : '';
900
942
  const response = await this.http.get<Record<string, unknown>>(
901
- `/api/v2/plays/run/${encodeURIComponent(workflowId)}`,
943
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}${query}`,
902
944
  );
903
945
  return normalizePlayStatus(response);
904
946
  }
@@ -31,6 +31,7 @@ import {
31
31
  interface RequestOptions {
32
32
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
33
33
  body?: unknown;
34
+ formData?: FormData;
34
35
  headers?: Record<string, string>;
35
36
  /** Per-request timeout override in milliseconds. */
36
37
  timeout?: number;
@@ -150,7 +151,12 @@ export class HttpClient {
150
151
  const response = await fetch(candidateUrl, {
151
152
  method,
152
153
  headers,
153
- body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
154
+ body:
155
+ options?.formData !== undefined
156
+ ? options.formData
157
+ : options?.body !== undefined
158
+ ? JSON.stringify(options.body)
159
+ : undefined,
154
160
  signal: controller.signal,
155
161
  });
156
162
 
@@ -179,10 +185,26 @@ export class HttpClient {
179
185
  }
180
186
 
181
187
  if (!response.ok) {
182
- const msg =
188
+ const errorValue =
183
189
  typeof parsed === 'object' && parsed && 'error' in parsed
184
- ? String((parsed as Record<string, unknown>).error)
185
- : `HTTP ${response.status}`;
190
+ ? (parsed as Record<string, unknown>).error
191
+ : undefined;
192
+ const msg =
193
+ typeof errorValue === 'string'
194
+ ? errorValue
195
+ : errorValue &&
196
+ typeof errorValue === 'object' &&
197
+ 'message' in errorValue &&
198
+ typeof (errorValue as Record<string, unknown>).message ===
199
+ 'string'
200
+ ? (errorValue as Record<string, string>).message
201
+ : typeof parsed === 'object' &&
202
+ parsed &&
203
+ 'message' in parsed &&
204
+ typeof (parsed as Record<string, unknown>).message ===
205
+ 'string'
206
+ ? (parsed as Record<string, string>).message
207
+ : `HTTP ${response.status}`;
186
208
  throw new DeeplineError(msg, response.status, 'API_ERROR', {
187
209
  response: parsed,
188
210
  });
@@ -301,6 +323,18 @@ export class HttpClient {
301
323
  });
302
324
  }
303
325
 
326
+ async postFormData<T = unknown>(
327
+ path: string,
328
+ formData: FormData,
329
+ headers?: Record<string, string>,
330
+ ): Promise<T> {
331
+ return this.request<T>(path, {
332
+ method: 'POST',
333
+ formData,
334
+ headers,
335
+ });
336
+ }
337
+
304
338
  /**
305
339
  * Send a DELETE request.
306
340
  *
@@ -33,6 +33,7 @@
33
33
 
34
34
  import type {
35
35
  PlayHarnessRpc,
36
+ PreloadedRuntimeDbSessionInput,
36
37
  RuntimeApiCallInput,
37
38
  RuntimeApiCallResult,
38
39
  RuntimePayloadSchemaId,
@@ -121,6 +122,17 @@ export async function harnessRuntimeApiCall(
121
122
  return requireBinding().runtimeApiCall(input);
122
123
  }
123
124
 
125
+ /**
126
+ * Warm Postgres sessions in the long-lived harness Worker. This preserves the
127
+ * map fast path without bundling the Neon client into every dynamic play.
128
+ */
129
+ export async function harnessPrewarmPostgresSessions(input: {
130
+ executorToken: string;
131
+ sessions: PreloadedRuntimeDbSessionInput[];
132
+ }): Promise<{ ok: true; sessions: number }> {
133
+ return requireBinding().prewarmPostgresSessions(input);
134
+ }
135
+
124
136
  /**
125
137
  * Start or continue a map dataset write inside the harness, so this call
126
138
  * skips per-play callback overhead for heavy Postgres writes.
@@ -1,2 +1,2 @@
1
- export const SDK_VERSION = "0.1.19";
1
+ export const SDK_VERSION = "0.1.21";
2
2
  export const SDK_API_CONTRACT = "2026-05-runs-v2";
@@ -17,6 +17,7 @@ import type {
17
17
  } from './ctx-types';
18
18
  import type { ExecutionPlan } from './execution-plan';
19
19
  import type { PlayRuntimeManifestMap } from '../plays/compiler-manifest';
20
+ import type { PreloadedRuntimeDbSession } from './db-session';
20
21
 
21
22
  export const PLAY_SCHEDULER_BACKENDS = {
22
23
  temporal: 'temporal',
@@ -79,6 +80,7 @@ export type PlaySchedulerSubmitInput = {
79
80
  executionPlan?: ExecutionPlan | null;
80
81
  childPlayManifests?: PlayRuntimeManifestMap | null;
81
82
  playCallGovernance?: PlayCallGovernanceSnapshot | null;
83
+ preloadedDbSessions?: PreloadedRuntimeDbSession[] | null;
82
84
  /** Optional immutable Worker module source for local Dynamic Worker loading. */
83
85
  dynamicWorkerCode?: string | null;
84
86
  executorToken: string;
@@ -109,12 +111,6 @@ export type PlaySchedulerProgressEvent =
109
111
 
110
112
  export type PlaySchedulerRunHandle = {
111
113
  runId: string;
112
- /**
113
- * Optional snapshot of the run's state captured during submit() so callers
114
- * can skip an immediate follow-up status read. Populated by backends that
115
- * piggyback the read on the create round-trip (currently cf-workflows).
116
- */
117
- initialState?: Record<string, unknown> | null;
118
114
  /**
119
115
  * Stream live progress events. Implementations may use SSE, polling, etc.
120
116
  * The contract: yields events in order until terminal status.
@@ -156,6 +152,7 @@ export interface PlaySchedulerBackend {
156
152
  options?: {
157
153
  coordinatorUrl?: string | null;
158
154
  coordinatorInternalToken?: string | null;
155
+ initialState?: Record<string, unknown> | null;
159
156
  },
160
157
  ): Promise<PlaySchedulerRunHandle>;
161
158
  }
@@ -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.21",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {