deepline 0.1.199 → 0.1.201
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/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +5 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +67 -18
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +1 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +16 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +40 -9
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +108 -39
- package/dist/bundling-sources/sdk/src/http.ts +5 -2
- package/dist/bundling-sources/sdk/src/play.ts +1 -1
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +7 -0
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/worker-play-entry.ts +11 -55
- package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +355 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +172 -73
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1794 -377
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +67 -4
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +161 -39
- package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +218 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +115 -25
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +17 -17
- package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +2 -1
- package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +265 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +135 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-input.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +0 -3
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +10 -1
- package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +9 -15
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +262 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-heartbeat-supervisor.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +76 -8
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +59 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +269 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +45 -3
- package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +433 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +227 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +882 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +573 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +264 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runner-events.ts +35 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +53 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +561 -105
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-infra-mode.ts +23 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +23 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +16 -24
- package/dist/bundling-sources/shared_libs/play-runtime/serial-task-queue.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/submit-limits.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +81 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +54 -9
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +17 -3
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +0 -20
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +105 -4
- package/dist/bundling-sources/shared_libs/plays/row-identity.ts +4 -11
- package/dist/cli/index.js +167 -54
- package/dist/cli/index.mjs +167 -54
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -8
- package/dist/index.mjs +8 -8
- package/dist/plays/bundle-play-file.mjs +15 -20
- package/package.json +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-diagnosis.ts +0 -321
- package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-target.ts +0 -158
- package/dist/bundling-sources/shared_libs/temporal/constants.ts +0 -39
- package/dist/bundling-sources/shared_libs/temporal/preview-config.ts +0 -150
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Maximum active user-code runtime for a standard play, in seconds. */
|
|
2
|
+
export const STANDARD_PLAY_RUNTIME_LIMIT_SECONDS = 20 * 60;
|
|
3
|
+
export const STANDARD_PLAY_RUNTIME_LIMIT_LABEL = '20 minutes';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runner timeout includes setup, cleanup, and billing headroom after the
|
|
7
|
+
* user-code runtime cap.
|
|
8
|
+
*/
|
|
9
|
+
export const PLAY_RUNNER_TIMEOUT_SECONDS = 30 * 60;
|
|
10
|
+
|
|
11
|
+
/** TTL for workflow executor tokens, in seconds. */
|
|
12
|
+
export const WORKFLOW_EXECUTOR_TOKEN_TTL_SECONDS = PLAY_RUNNER_TIMEOUT_SECONDS;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const DEEPLINE_V2_INFRA_MODE_ENV = 'DEEPLINE_V2_INFRA_MODE';
|
|
2
|
+
|
|
3
|
+
function currentRuntimeInfraMode(): string {
|
|
4
|
+
return process.env[DEEPLINE_V2_INFRA_MODE_ENV]?.trim().toLowerCase() ?? '';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isPreviewPlaysDeployment(): boolean {
|
|
8
|
+
const mode = currentRuntimeInfraMode();
|
|
9
|
+
return (
|
|
10
|
+
mode === 'external' ||
|
|
11
|
+
mode === 'preview' ||
|
|
12
|
+
mode === 'absurd-preview' ||
|
|
13
|
+
mode === 'absurd:preview'
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isCloudflarePlaysDeployment(): boolean {
|
|
18
|
+
return currentRuntimeInfraMode() === 'cloudflare';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function requiresPublicCallbackBaseUrl(): boolean {
|
|
22
|
+
return isPreviewPlaysDeployment() || isCloudflarePlaysDeployment();
|
|
23
|
+
}
|
package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts
CHANGED
|
@@ -57,6 +57,20 @@ function normalizeAttemptSeq(value: number | null | undefined): number {
|
|
|
57
57
|
return Number.isFinite(value) ? Math.trunc(value as number) : 0;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function isSameOwnerOlderRequest(input: {
|
|
61
|
+
row: RuntimeSheetRowAttemptInput;
|
|
62
|
+
request: RuntimeSheetAttemptIdentity;
|
|
63
|
+
}): boolean {
|
|
64
|
+
const rowOwner =
|
|
65
|
+
normalize(input.row.attemptOwnerRunId) ?? normalize(input.row.runId);
|
|
66
|
+
const requestOwner = normalize(input.request.attemptOwnerRunId);
|
|
67
|
+
return (
|
|
68
|
+
Boolean(rowOwner && requestOwner && rowOwner === requestOwner) &&
|
|
69
|
+
normalizeAttemptSeq(input.request.attemptSeq) <
|
|
70
|
+
normalizeAttemptSeq(input.row.attemptSeq)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
60
74
|
export function classifyRuntimeSheetAttemptRelation(input: {
|
|
61
75
|
row: RuntimeSheetRowAttemptInput;
|
|
62
76
|
request: RuntimeSheetAttemptIdentity;
|
|
@@ -103,13 +117,14 @@ export function decideRuntimeSheetPrepare(input: {
|
|
|
103
117
|
}
|
|
104
118
|
const relation = classifyRuntimeSheetAttemptRelation(input);
|
|
105
119
|
if (input.row.status === 'pending' || input.row.status === 'running') {
|
|
106
|
-
return relation.kind
|
|
120
|
+
return (relation.kind !== 'expired' && isSameOwnerOlderRequest(input)) ||
|
|
121
|
+
relation.kind === 'malformed'
|
|
107
122
|
? { kind: 'blocked_by_active_owner' }
|
|
108
123
|
: { kind: 'claim_executable' };
|
|
109
124
|
}
|
|
110
125
|
if (input.row.status === 'enriched') {
|
|
111
126
|
if (!input.missingRequiredOutput) return { kind: 'reuse_completed' };
|
|
112
|
-
return relation.kind
|
|
127
|
+
return (relation.kind !== 'expired' && isSameOwnerOlderRequest(input)) ||
|
|
113
128
|
relation.kind === 'malformed'
|
|
114
129
|
? { kind: 'blocked_by_active_owner' }
|
|
115
130
|
: { kind: 'claim_executable' };
|
|
@@ -123,6 +138,12 @@ export function decideRuntimeSheetTerminalWrite(input: {
|
|
|
123
138
|
nowMs?: number;
|
|
124
139
|
}): RuntimeSheetTerminalDecision {
|
|
125
140
|
const relation = classifyRuntimeSheetAttemptRelation(input);
|
|
141
|
+
if (input.row.status !== 'enriched') {
|
|
142
|
+
return (relation.kind !== 'expired' && isSameOwnerOlderRequest(input)) ||
|
|
143
|
+
relation.kind === 'malformed'
|
|
144
|
+
? { kind: 'reject_stale_attempt' }
|
|
145
|
+
: { kind: 'write_terminal' };
|
|
146
|
+
}
|
|
126
147
|
if (
|
|
127
148
|
relation.kind === 'unowned' ||
|
|
128
149
|
relation.kind === 'same_attempt' ||
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Selected per-run via PlayExecutionProfile.
|
|
6
6
|
*
|
|
7
7
|
* Cloudflare Workflows is the default production scheduler through the
|
|
8
|
-
* workers_edge profile.
|
|
8
|
+
* workers_edge profile. Absurd is the durable Daytona-backed scheduler.
|
|
9
9
|
*
|
|
10
10
|
* Customer plays are unaffected — this is purely the orchestration layer.
|
|
11
11
|
*/
|
|
@@ -20,23 +20,25 @@ import type { PlayRuntimeManifestMap } from '../plays/compiler-manifest';
|
|
|
20
20
|
import type { PreloadedRuntimeDbSession } from './db-session';
|
|
21
21
|
import type { PlayRunnerRuntimeTiming } from './protocol';
|
|
22
22
|
import type { RuntimeTestPolicyOverrides } from './test-runtime-seams';
|
|
23
|
+
import type { PlayRunInputPayload } from './play-input';
|
|
23
24
|
|
|
24
25
|
export const PLAY_SCHEDULER_BACKENDS = {
|
|
25
|
-
temporal: 'temporal',
|
|
26
26
|
cfWorkflows: 'cf-workflows',
|
|
27
|
+
/** Private legacy id retained only so old persisted rows can be interpreted. */
|
|
28
|
+
postgres: 'postgres',
|
|
27
29
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* Postgres-native durable-execution scheduler (vendored Absurd engine). Runs
|
|
31
|
+
* on the battle-tested run-state store without an external workflow engine.
|
|
32
|
+
* Selected explicitly by the `absurd` profile.
|
|
30
33
|
*/
|
|
31
|
-
|
|
32
|
-
hatchet: 'hatchet',
|
|
34
|
+
absurd: 'absurd',
|
|
33
35
|
inProcess: 'in-process',
|
|
34
36
|
} as const;
|
|
35
37
|
|
|
36
38
|
export type PlaySchedulerBackendId =
|
|
37
39
|
(typeof PLAY_SCHEDULER_BACKENDS)[keyof typeof PLAY_SCHEDULER_BACKENDS];
|
|
38
40
|
|
|
39
|
-
export type PlayRunInputPayload
|
|
41
|
+
export type { PlayRunInputPayload };
|
|
40
42
|
|
|
41
43
|
export type PlayCallGovernanceSnapshot = {
|
|
42
44
|
rootRunId: string;
|
|
@@ -78,9 +80,8 @@ export type PlaySchedulerSubmitInput = {
|
|
|
78
80
|
workflowFamilyKey?: string | null;
|
|
79
81
|
artifactStorageKey: string;
|
|
80
82
|
/**
|
|
81
|
-
* Optional inline artifact for schedulers
|
|
82
|
-
*
|
|
83
|
-
* dynamicWorkerCode instead.
|
|
83
|
+
* Optional inline artifact for node-compatible schedulers. Workers-edge
|
|
84
|
+
* schedulers use artifactStorageKey plus dynamicWorkerCode instead.
|
|
84
85
|
*/
|
|
85
86
|
runtimeArtifact?: unknown;
|
|
86
87
|
artifactHash: string;
|
|
@@ -101,11 +102,6 @@ export type PlaySchedulerSubmitInput = {
|
|
|
101
102
|
bytes?: number;
|
|
102
103
|
} | null;
|
|
103
104
|
inlineCsv?: { name: string; rows: Record<string, unknown>[] } | null;
|
|
104
|
-
/**
|
|
105
|
-
* Temporal-only escape hatch for small legacy inputs. Workers-edge CSV
|
|
106
|
-
* inputs must be staged and streamed by handle instead of carried as raw
|
|
107
|
-
* scheduler payload bytes.
|
|
108
|
-
*/
|
|
109
105
|
inlineInputFile?: {
|
|
110
106
|
logicalPath: string;
|
|
111
107
|
fileName: string;
|
|
@@ -143,8 +139,6 @@ export type PlaySchedulerSubmitInput = {
|
|
|
143
139
|
userId?: string | null;
|
|
144
140
|
source?: 'published' | 'ad_hoc' | 'draft';
|
|
145
141
|
executionProfile?: string | null;
|
|
146
|
-
/** Durable per-workspace active run cap enforced when the run row is projected. */
|
|
147
|
-
activeRunLimit?: number | null;
|
|
148
142
|
/** runner backend to use for executing attempts */
|
|
149
143
|
runtimeBackend: string;
|
|
150
144
|
/** dedup backend for cross-attempt cross-process idempotency */
|
|
@@ -244,24 +238,22 @@ export function normalizePlaySchedulerBackend(
|
|
|
244
238
|
value?: string | null,
|
|
245
239
|
): PlaySchedulerBackendId {
|
|
246
240
|
const normalized = value?.trim().toLowerCase();
|
|
247
|
-
if (!normalized
|
|
248
|
-
return PLAY_SCHEDULER_BACKENDS.
|
|
241
|
+
if (!normalized) {
|
|
242
|
+
return PLAY_SCHEDULER_BACKENDS.cfWorkflows;
|
|
249
243
|
}
|
|
250
244
|
if (normalized === 'cf-workflows' || normalized === 'cf_workflows') {
|
|
251
245
|
return PLAY_SCHEDULER_BACKENDS.cfWorkflows;
|
|
252
246
|
}
|
|
253
|
-
if (normalized === '
|
|
254
|
-
return PLAY_SCHEDULER_BACKENDS.
|
|
247
|
+
if (normalized === 'absurd' || normalized === 'absurd-scheduler') {
|
|
248
|
+
return PLAY_SCHEDULER_BACKENDS.absurd;
|
|
255
249
|
}
|
|
256
250
|
if (normalized === 'in-process' || normalized === 'in_process') {
|
|
257
251
|
return PLAY_SCHEDULER_BACKENDS.inProcess;
|
|
258
252
|
}
|
|
259
253
|
throw new Error(
|
|
260
254
|
`Unsupported scheduler backend "${normalized}". Expected one of: ${[
|
|
261
|
-
PLAY_SCHEDULER_BACKENDS.temporal,
|
|
262
255
|
PLAY_SCHEDULER_BACKENDS.cfWorkflows,
|
|
263
|
-
PLAY_SCHEDULER_BACKENDS.
|
|
264
|
-
PLAY_SCHEDULER_BACKENDS.inProcess,
|
|
256
|
+
PLAY_SCHEDULER_BACKENDS.absurd,
|
|
265
257
|
].join(', ')}.`,
|
|
266
258
|
);
|
|
267
259
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface SerialTaskQueue {
|
|
2
|
+
enqueue<T>(task: () => Promise<T>): Promise<T>;
|
|
3
|
+
idle(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Runs asynchronous tasks one at a time in enqueue order. A rejected task is
|
|
8
|
+
* returned to its caller but does not poison the queue for later work.
|
|
9
|
+
*/
|
|
10
|
+
export function createSerialTaskQueue(): SerialTaskQueue {
|
|
11
|
+
let tail: Promise<void> = Promise.resolve();
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
enqueue<T>(task: () => Promise<T>): Promise<T> {
|
|
15
|
+
const result = tail.then(task);
|
|
16
|
+
tail = result.then(
|
|
17
|
+
() => undefined,
|
|
18
|
+
() => undefined,
|
|
19
|
+
);
|
|
20
|
+
return result;
|
|
21
|
+
},
|
|
22
|
+
idle(): Promise<void> {
|
|
23
|
+
return tail;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* (and the workflow event history). Larger inputs must be staged via
|
|
14
14
|
* `POST /api/v2/plays/files/stage` and referenced by handle.
|
|
15
15
|
*/
|
|
16
|
-
export const
|
|
16
|
+
export const MAX_SUBMITTED_INLINE_INPUT_FILE_BYTES = 64 * 1024;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Max time the run route will block waiting for a play to finish before
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
type FetchLike = typeof fetch;
|
|
2
|
+
|
|
3
|
+
const vercelProtectionCookieCache = new Map<string, Promise<string | null>>();
|
|
4
|
+
|
|
5
|
+
export function addVercelProtectionBypassSearchParams(
|
|
6
|
+
url: URL,
|
|
7
|
+
token: string | null | undefined,
|
|
8
|
+
): void {
|
|
9
|
+
const normalized = token?.trim();
|
|
10
|
+
if (!normalized) return;
|
|
11
|
+
url.searchParams.set('x-vercel-set-bypass-cookie', 'true');
|
|
12
|
+
url.searchParams.set('x-vercel-protection-bypass', normalized);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function vercelProtectionBypassHeader(
|
|
16
|
+
token: string | null | undefined,
|
|
17
|
+
): Record<string, string> {
|
|
18
|
+
const normalized = token?.trim();
|
|
19
|
+
return normalized ? { 'x-vercel-protection-bypass': normalized } : {};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function setCookieHeaders(headers: Headers): string[] {
|
|
23
|
+
const getter = (headers as Headers & { getSetCookie?: () => string[] })
|
|
24
|
+
.getSetCookie;
|
|
25
|
+
if (typeof getter === 'function') {
|
|
26
|
+
return getter.call(headers);
|
|
27
|
+
}
|
|
28
|
+
const single = headers.get('set-cookie');
|
|
29
|
+
return single ? [single] : [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function cookieHeaderFromSetCookie(headers: Headers): string | null {
|
|
33
|
+
const cookies = setCookieHeaders(headers)
|
|
34
|
+
.flatMap((header) => header.split(/,(?=\s*[^;,=]+=[^;,]+)/g))
|
|
35
|
+
.map((header) => header.split(';', 1)[0]?.trim() ?? '')
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
return cookies.length > 0 ? cookies.join('; ') : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function resolveVercelProtectionBypassCookie(input: {
|
|
41
|
+
baseUrl: string | null | undefined;
|
|
42
|
+
token: string | null | undefined;
|
|
43
|
+
fetchImpl?: FetchLike;
|
|
44
|
+
}): Promise<string | null> {
|
|
45
|
+
const token = input.token?.trim();
|
|
46
|
+
const baseUrl = input.baseUrl?.trim().replace(/\/$/, '');
|
|
47
|
+
if (!token || !baseUrl) return null;
|
|
48
|
+
|
|
49
|
+
const cacheKey = `${baseUrl}\n${token}`;
|
|
50
|
+
const cached = vercelProtectionCookieCache.get(cacheKey);
|
|
51
|
+
if (cached) return await cached;
|
|
52
|
+
|
|
53
|
+
const promise = (async () => {
|
|
54
|
+
const url = new URL(`${baseUrl}/api/v2/health`);
|
|
55
|
+
addVercelProtectionBypassSearchParams(url, token);
|
|
56
|
+
const response = await (input.fetchImpl ?? fetch)(url.toString(), {
|
|
57
|
+
headers: { 'x-vercel-protection-bypass': token },
|
|
58
|
+
}).catch(() => null);
|
|
59
|
+
return response ? cookieHeaderFromSetCookie(response.headers) : null;
|
|
60
|
+
})();
|
|
61
|
+
vercelProtectionCookieCache.set(cacheKey, promise);
|
|
62
|
+
return await promise;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function vercelProtectionBypassHeaders(input: {
|
|
66
|
+
baseUrl: string | null | undefined;
|
|
67
|
+
token: string | null | undefined;
|
|
68
|
+
fetchImpl?: FetchLike;
|
|
69
|
+
}): Promise<Record<string, string>> {
|
|
70
|
+
const token = input.token?.trim();
|
|
71
|
+
if (!token) return {};
|
|
72
|
+
const cookie = await resolveVercelProtectionBypassCookie({
|
|
73
|
+
baseUrl: input.baseUrl,
|
|
74
|
+
token,
|
|
75
|
+
fetchImpl: input.fetchImpl,
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
...vercelProtectionBypassHeader(token),
|
|
79
|
+
...(cookie ? { cookie } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type WorkReceiptStateStatus =
|
|
2
|
+
| 'queued'
|
|
2
3
|
| 'pending'
|
|
3
4
|
| 'running'
|
|
4
5
|
| 'completed'
|
|
@@ -51,6 +52,12 @@ export type WorkReceiptInvalidLeaseReason = Extract<
|
|
|
51
52
|
>['reason'];
|
|
52
53
|
|
|
53
54
|
export type WorkReceiptState =
|
|
55
|
+
| {
|
|
56
|
+
kind: 'queued';
|
|
57
|
+
terminal: false;
|
|
58
|
+
reusable: false;
|
|
59
|
+
lease: WorkReceiptLeaseState;
|
|
60
|
+
}
|
|
54
61
|
| {
|
|
55
62
|
kind: 'pending';
|
|
56
63
|
terminal: false;
|
|
@@ -209,7 +216,8 @@ export function classifyWorkReceiptState(
|
|
|
209
216
|
const lease = classifyWorkReceiptLeaseState(receipt, nowMs);
|
|
210
217
|
switch (receipt.status) {
|
|
211
218
|
case 'pending':
|
|
212
|
-
|
|
219
|
+
case 'queued':
|
|
220
|
+
return { kind: 'queued', terminal: false, reusable: false, lease };
|
|
213
221
|
case 'running':
|
|
214
222
|
return { kind: 'running', terminal: false, reusable: false, lease };
|
|
215
223
|
case 'completed':
|
|
@@ -255,7 +263,11 @@ export function decideWorkReceiptClaim(input: {
|
|
|
255
263
|
}
|
|
256
264
|
return { kind: 'blocked_failed' };
|
|
257
265
|
}
|
|
258
|
-
if (
|
|
266
|
+
if (
|
|
267
|
+
state.kind === 'pending' ||
|
|
268
|
+
state.kind === 'queued' ||
|
|
269
|
+
state.kind === 'running'
|
|
270
|
+
) {
|
|
259
271
|
if (state.lease.kind === 'active') {
|
|
260
272
|
if (
|
|
261
273
|
(state.kind !== 'running' ||
|
|
@@ -284,7 +296,7 @@ export function decideWorkReceiptClaim(input: {
|
|
|
284
296
|
if (input.forceRefresh === true && input.reclaimRunning === true) {
|
|
285
297
|
return { kind: 'claim' };
|
|
286
298
|
}
|
|
287
|
-
if (state.kind === 'pending') {
|
|
299
|
+
if (state.kind === 'pending' || state.kind === 'queued') {
|
|
288
300
|
return { kind: 'claim' };
|
|
289
301
|
}
|
|
290
302
|
if (
|
|
@@ -364,6 +376,32 @@ function baseRunningOwnerDecision(
|
|
|
364
376
|
return { kind: 'allow' };
|
|
365
377
|
}
|
|
366
378
|
|
|
379
|
+
function baseActiveOwnerDecision(
|
|
380
|
+
input: WorkReceiptOwnerOperationInput,
|
|
381
|
+
): WorkReceiptOwnerOperationDecision {
|
|
382
|
+
if (!input.receipt) {
|
|
383
|
+
return { kind: 'reject', reason: 'missing_receipt' };
|
|
384
|
+
}
|
|
385
|
+
if (
|
|
386
|
+
input.receipt.status !== 'queued' &&
|
|
387
|
+
input.receipt.status !== 'pending' &&
|
|
388
|
+
input.receipt.status !== 'running'
|
|
389
|
+
) {
|
|
390
|
+
return { kind: 'reject', reason: 'not_running' };
|
|
391
|
+
}
|
|
392
|
+
const ownerRunId = normalize(input.ownerRunId);
|
|
393
|
+
if (!ownerRunId || ownerRunIdForReceipt(input.receipt) !== ownerRunId) {
|
|
394
|
+
return { kind: 'reject', reason: 'owner_mismatch' };
|
|
395
|
+
}
|
|
396
|
+
if (
|
|
397
|
+
normalizeAttempt(input.receipt.leaseOwnerAttempt) !==
|
|
398
|
+
normalizeAttempt(input.ownerRunAttempt)
|
|
399
|
+
) {
|
|
400
|
+
return { kind: 'reject', reason: 'attempt_mismatch' };
|
|
401
|
+
}
|
|
402
|
+
return { kind: 'allow' };
|
|
403
|
+
}
|
|
404
|
+
|
|
367
405
|
/**
|
|
368
406
|
* Law for complete/fail/skip. A terminal write may only be made by the
|
|
369
407
|
* running receipt owner for the same attempt. When the receipt has a lease id,
|
|
@@ -385,14 +423,21 @@ export function decideWorkReceiptTerminalWrite(
|
|
|
385
423
|
}
|
|
386
424
|
|
|
387
425
|
/**
|
|
388
|
-
* Law for a single suspended receipt release.
|
|
389
|
-
*
|
|
390
|
-
* requires the exact lease id.
|
|
426
|
+
* Law for a single suspended receipt release. The owner may release queued or
|
|
427
|
+
* running work, and a leased receipt requires the exact lease id.
|
|
391
428
|
*/
|
|
392
429
|
export function decideWorkReceiptExactRelease(
|
|
393
430
|
input: WorkReceiptOwnerOperationInput,
|
|
394
431
|
): WorkReceiptOwnerOperationDecision {
|
|
395
|
-
|
|
432
|
+
const ownerDecision = baseActiveOwnerDecision(input);
|
|
433
|
+
if (ownerDecision.kind === 'reject') return ownerDecision;
|
|
434
|
+
|
|
435
|
+
const receiptLeaseId = normalize(input.receipt?.leaseId);
|
|
436
|
+
if (!receiptLeaseId) return { kind: 'allow' };
|
|
437
|
+
if (receiptLeaseId !== normalize(input.leaseId)) {
|
|
438
|
+
return { kind: 'reject', reason: 'lease_mismatch' };
|
|
439
|
+
}
|
|
440
|
+
return { kind: 'allow' };
|
|
396
441
|
}
|
|
397
442
|
|
|
398
443
|
/**
|
|
@@ -403,7 +448,7 @@ export function decideWorkReceiptExactRelease(
|
|
|
403
448
|
export function decideWorkReceiptOwnerRelease(
|
|
404
449
|
input: WorkReceiptOwnerOperationInput,
|
|
405
450
|
): WorkReceiptOwnerOperationDecision {
|
|
406
|
-
return
|
|
451
|
+
return baseActiveOwnerDecision(input);
|
|
407
452
|
}
|
|
408
453
|
|
|
409
454
|
/**
|
|
@@ -413,7 +458,7 @@ export function decideWorkReceiptOwnerRelease(
|
|
|
413
458
|
export function decideWorkReceiptHeartbeat(
|
|
414
459
|
input: WorkReceiptOwnerOperationInput,
|
|
415
460
|
): WorkReceiptOwnerOperationDecision {
|
|
416
|
-
const ownerDecision =
|
|
461
|
+
const ownerDecision = baseActiveOwnerDecision(input);
|
|
417
462
|
if (ownerDecision.kind === 'reject') return ownerDecision;
|
|
418
463
|
|
|
419
464
|
const requestLeaseId = normalize(input.leaseId);
|
|
@@ -25,6 +25,7 @@ export function workReceiptFailureKindCode(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export type WorkReceiptStatus =
|
|
28
|
+
| 'queued'
|
|
28
29
|
| 'pending'
|
|
29
30
|
| 'running'
|
|
30
31
|
| 'completed'
|
|
@@ -102,6 +103,17 @@ export type WorkReceiptBatchHeartbeatCommand = {
|
|
|
102
103
|
leaseTtlMs?: number | null;
|
|
103
104
|
};
|
|
104
105
|
|
|
106
|
+
export type WorkReceiptBatchMarkRunningCommand = {
|
|
107
|
+
playName: string;
|
|
108
|
+
receipts: Array<{
|
|
109
|
+
runId: string;
|
|
110
|
+
key: string;
|
|
111
|
+
leaseId?: string | null;
|
|
112
|
+
runAttempt?: number | null;
|
|
113
|
+
}>;
|
|
114
|
+
leaseTtlMs?: number | null;
|
|
115
|
+
};
|
|
116
|
+
|
|
105
117
|
export type WorkReceiptGetCommand = {
|
|
106
118
|
playName: string;
|
|
107
119
|
key: string;
|
|
@@ -111,6 +123,7 @@ export type WorkReceiptClaim =
|
|
|
111
123
|
| {
|
|
112
124
|
disposition: 'claimed';
|
|
113
125
|
receipt: WorkReceipt;
|
|
126
|
+
wasFailed?: boolean;
|
|
114
127
|
}
|
|
115
128
|
| {
|
|
116
129
|
disposition: 'reused';
|
|
@@ -135,6 +148,9 @@ export type WorkReceiptStore = {
|
|
|
135
148
|
claimReceipts?(
|
|
136
149
|
input: WorkReceiptBatchClaimCommand,
|
|
137
150
|
): Promise<WorkReceiptClaim[]>;
|
|
151
|
+
markReceiptsRunning?(
|
|
152
|
+
input: WorkReceiptBatchMarkRunningCommand,
|
|
153
|
+
): Promise<Array<WorkReceipt | null>>;
|
|
138
154
|
completeReceipt(
|
|
139
155
|
input: WorkReceiptCommand & { output: unknown },
|
|
140
156
|
): Promise<WorkReceipt | null>;
|
|
@@ -150,9 +166,7 @@ export type WorkReceiptStore = {
|
|
|
150
166
|
failReceipts?(
|
|
151
167
|
input: WorkReceiptBatchFailCommand,
|
|
152
168
|
): Promise<Array<WorkReceipt | null>>;
|
|
153
|
-
releaseReceipt?(
|
|
154
|
-
input: WorkReceiptCommand,
|
|
155
|
-
): Promise<WorkReceipt | null>;
|
|
169
|
+
releaseReceipt?(input: WorkReceiptCommand): Promise<WorkReceipt | null>;
|
|
156
170
|
heartbeatReceipts?(
|
|
157
171
|
input: WorkReceiptBatchHeartbeatCommand,
|
|
158
172
|
): Promise<Array<WorkReceipt | null>>;
|
|
@@ -41,10 +41,6 @@ const DAYTONA_COMPUTE_PRICING_USD = {
|
|
|
41
41
|
includedStorageGiB: 5,
|
|
42
42
|
} as const;
|
|
43
43
|
|
|
44
|
-
const TEMPORAL_COMPUTE_PRICING_USD = {
|
|
45
|
-
action: 50 / 1_000_000,
|
|
46
|
-
} as const;
|
|
47
|
-
|
|
48
44
|
const NEON_COMPUTE_PRICING_USD = {
|
|
49
45
|
cuHour: 0.106,
|
|
50
46
|
defaultCu: 1,
|
|
@@ -97,22 +93,6 @@ export function resolveDaytonaSandboxComputeItem(input: {
|
|
|
97
93
|
};
|
|
98
94
|
}
|
|
99
95
|
|
|
100
|
-
export function resolveTemporalComputeItem(input: {
|
|
101
|
-
itemId: string;
|
|
102
|
-
actions: number;
|
|
103
|
-
metadata?: unknown;
|
|
104
|
-
}): ComputeBillingItem {
|
|
105
|
-
const actions = Math.max(0, Math.ceil(input.actions));
|
|
106
|
-
return {
|
|
107
|
-
itemId: input.itemId,
|
|
108
|
-
source: 'temporal',
|
|
109
|
-
unit: 'action',
|
|
110
|
-
units: actions,
|
|
111
|
-
providerCostUsd: roundUsd(actions * TEMPORAL_COMPUTE_PRICING_USD.action),
|
|
112
|
-
metadata: input.metadata,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
96
|
export function resolveNeonComputeItem(input: {
|
|
117
97
|
itemId: string;
|
|
118
98
|
activeSeconds: number;
|
|
@@ -3,6 +3,7 @@ import type { PlayExecutionFileRef } from './file-refs';
|
|
|
3
3
|
const PLAY_DATASET_BRAND = Symbol.for('deepline.play.dataset');
|
|
4
4
|
const NODE_INSPECT_CUSTOM = Symbol.for('nodejs.util.inspect.custom');
|
|
5
5
|
const DEFAULT_MATERIALIZE_LIMIT = 10_000;
|
|
6
|
+
export const PLAY_DATASET_EXECUTION_PAGE_ROWS = 1_000;
|
|
6
7
|
|
|
7
8
|
export type PlayDatasetKind = 'csv' | 'map';
|
|
8
9
|
|
|
@@ -538,19 +539,119 @@ export function createPlayDataset<T>(
|
|
|
538
539
|
});
|
|
539
540
|
}
|
|
540
541
|
|
|
542
|
+
export type MaterializePlayDatasetInputOptions<T> = {
|
|
543
|
+
onRow?: (row: T, index: number) => void;
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
export type IteratePlayDatasetInputPagesOptions<T> = {
|
|
547
|
+
pageSize?: number;
|
|
548
|
+
onRow?: (row: T, index: number) => void;
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
function normalizeDatasetExecutionPageSize(value: number | undefined): number {
|
|
552
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
553
|
+
return PLAY_DATASET_EXECUTION_PAGE_ROWS;
|
|
554
|
+
}
|
|
555
|
+
return Math.max(1, Math.floor(value));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export async function* iteratePlayDatasetInputPages<T>(
|
|
559
|
+
input: PlayDatasetInput<T>,
|
|
560
|
+
options?: IteratePlayDatasetInputPagesOptions<T>,
|
|
561
|
+
): AsyncIterable<{ rows: T[]; offset: number }> {
|
|
562
|
+
const pageSize = normalizeDatasetExecutionPageSize(options?.pageSize);
|
|
563
|
+
let page: T[] = [];
|
|
564
|
+
let offset = 0;
|
|
565
|
+
let index = 0;
|
|
566
|
+
const pushRow = (row: T): { rows: T[]; offset: number } | null => {
|
|
567
|
+
options?.onRow?.(row, index);
|
|
568
|
+
index += 1;
|
|
569
|
+
page.push(row);
|
|
570
|
+
if (page.length < pageSize) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
const fullPage = page;
|
|
574
|
+
const fullOffset = offset;
|
|
575
|
+
page = [];
|
|
576
|
+
offset += fullPage.length;
|
|
577
|
+
return { rows: fullPage, offset: fullOffset };
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const flush = (): { rows: T[]; offset: number } | null => {
|
|
581
|
+
if (page.length === 0) return null;
|
|
582
|
+
const finalPage = page;
|
|
583
|
+
const finalOffset = offset;
|
|
584
|
+
page = [];
|
|
585
|
+
offset += finalPage.length;
|
|
586
|
+
return { rows: finalPage, offset: finalOffset };
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const emitRow = async function* (row: T) {
|
|
590
|
+
const ready = pushRow(row);
|
|
591
|
+
if (ready) yield ready;
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
if (isPlayDataset(input)) {
|
|
595
|
+
for await (const row of input) {
|
|
596
|
+
yield* emitRow(row);
|
|
597
|
+
}
|
|
598
|
+
const finalPage = flush();
|
|
599
|
+
if (finalPage) yield finalPage;
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (Array.isArray(input)) {
|
|
604
|
+
for (const row of input) {
|
|
605
|
+
yield* emitRow(row);
|
|
606
|
+
}
|
|
607
|
+
const finalPage = flush();
|
|
608
|
+
if (finalPage) yield finalPage;
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (
|
|
613
|
+
input != null &&
|
|
614
|
+
typeof input === 'object' &&
|
|
615
|
+
Symbol.asyncIterator in input
|
|
616
|
+
) {
|
|
617
|
+
for await (const row of input as AsyncIterable<T>) {
|
|
618
|
+
yield* emitRow(row);
|
|
619
|
+
}
|
|
620
|
+
const finalPage = flush();
|
|
621
|
+
if (finalPage) yield finalPage;
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
for (const row of input as Iterable<T>) {
|
|
626
|
+
yield* emitRow(row);
|
|
627
|
+
}
|
|
628
|
+
const finalPage = flush();
|
|
629
|
+
if (finalPage) yield finalPage;
|
|
630
|
+
}
|
|
631
|
+
|
|
541
632
|
export async function materializePlayDatasetInput<T>(
|
|
542
633
|
input: PlayDatasetInput<T>,
|
|
634
|
+
options?: MaterializePlayDatasetInputOptions<T>,
|
|
543
635
|
): Promise<T[]> {
|
|
636
|
+
const pushRow = (rows: T[], row: T): void => {
|
|
637
|
+
options?.onRow?.(row, rows.length);
|
|
638
|
+
rows.push(row);
|
|
639
|
+
};
|
|
640
|
+
|
|
544
641
|
if (isPlayDataset(input)) {
|
|
545
642
|
const rows: T[] = [];
|
|
546
643
|
for await (const row of input) {
|
|
547
|
-
rows
|
|
644
|
+
pushRow(rows, row);
|
|
548
645
|
}
|
|
549
646
|
return rows;
|
|
550
647
|
}
|
|
551
648
|
|
|
552
649
|
if (Array.isArray(input)) {
|
|
553
|
-
|
|
650
|
+
const rows: T[] = [];
|
|
651
|
+
for (const row of input) {
|
|
652
|
+
pushRow(rows, row);
|
|
653
|
+
}
|
|
654
|
+
return rows;
|
|
554
655
|
}
|
|
555
656
|
|
|
556
657
|
const rows: T[] = [];
|
|
@@ -560,13 +661,13 @@ export async function materializePlayDatasetInput<T>(
|
|
|
560
661
|
Symbol.asyncIterator in input
|
|
561
662
|
) {
|
|
562
663
|
for await (const row of input as AsyncIterable<T>) {
|
|
563
|
-
rows
|
|
664
|
+
pushRow(rows, row);
|
|
564
665
|
}
|
|
565
666
|
return rows;
|
|
566
667
|
}
|
|
567
668
|
|
|
568
669
|
for (const row of input as Iterable<T>) {
|
|
569
|
-
rows
|
|
670
|
+
pushRow(rows, row);
|
|
570
671
|
}
|
|
571
672
|
return rows;
|
|
572
673
|
}
|