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.
- package/dist/cli/index.js +391 -129
- package/dist/cli/index.mjs +391 -129
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +53 -7
- package/dist/index.mjs +53 -7
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +999 -257
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +604 -75
- package/dist/repo/apps/play-runner-workers/src/entry.ts +442 -357
- package/dist/repo/sdk/src/client.ts +46 -4
- package/dist/repo/sdk/src/http.ts +38 -4
- package/dist/repo/sdk/src/plays/harness-stub.ts +12 -0
- package/dist/repo/sdk/src/version.ts +1 -1
- package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +3 -6
- package/dist/repo/shared_libs/plays/row-identity.ts +59 -4
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
|
188
|
+
const errorValue =
|
|
183
189
|
typeof parsed === 'object' && parsed && 'error' in parsed
|
|
184
|
-
?
|
|
185
|
-
:
|
|
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.
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
|
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
|
|