deepline 0.1.198 → 0.1.200
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 +51 -17
- 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 +2 -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 +459 -103
- 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 +441 -101
- package/dist/cli/index.mjs +441 -101
- 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
|
@@ -13,6 +13,9 @@ export interface MapExecutionFrame {
|
|
|
13
13
|
totalRows: number;
|
|
14
14
|
completedRowKeys: string[];
|
|
15
15
|
pendingRowKeys: string[];
|
|
16
|
+
completedRowsCount?: number;
|
|
17
|
+
pendingRowsCount?: number;
|
|
18
|
+
failedRowsCount?: number;
|
|
16
19
|
activeBoundaryId?: string | null;
|
|
17
20
|
startedAt: number;
|
|
18
21
|
updatedAt: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Core types for PlayContext
|
|
1
|
+
// Core types for PlayContext.
|
|
2
2
|
import type { PlayBundleArtifact } from '../plays/artifact-types';
|
|
3
3
|
import type { PlayRunContractSnapshot } from '../plays/contracts';
|
|
4
4
|
import type { PlayStructuredDefinition } from '../plays/definition';
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
RateStateBackend,
|
|
14
14
|
} from './governor/rate-state-backend';
|
|
15
15
|
import type { GovernanceSnapshot } from './governor/governor';
|
|
16
|
+
import type { RuntimeMapMemoryLimits } from './map-memory-limits';
|
|
16
17
|
import type { AnyBatchOperationStrategy } from './batching-types';
|
|
17
18
|
import type { ToolResultMetadataInput } from './tool-result';
|
|
18
19
|
import type { PreviousCell } from './cell-staleness';
|
|
@@ -30,6 +31,7 @@ export interface ToolCallRequest {
|
|
|
30
31
|
receiptKey?: string | null;
|
|
31
32
|
executionAuthScopeDigest?: string | null;
|
|
32
33
|
receiptLeaseId?: string | null;
|
|
34
|
+
receiptLeaseExpiresAt?: string | null;
|
|
33
35
|
force?: boolean;
|
|
34
36
|
forceFailedRefresh?: boolean;
|
|
35
37
|
rowId: number;
|
|
@@ -50,7 +52,7 @@ export interface ToolBatchResult {
|
|
|
50
52
|
|
|
51
53
|
export interface RuntimeStepReceipt {
|
|
52
54
|
key: string;
|
|
53
|
-
status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
55
|
+
status: 'queued' | 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
54
56
|
output?: unknown;
|
|
55
57
|
error?: string;
|
|
56
58
|
failureKind?: WorkReceiptFailureKind | null;
|
|
@@ -76,6 +78,13 @@ export interface ClaimRuntimeStepReceiptInput {
|
|
|
76
78
|
forceFailedRefresh?: boolean;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
export interface MarkRuntimeStepReceiptRunningInput {
|
|
82
|
+
key: string;
|
|
83
|
+
runId: string;
|
|
84
|
+
runAttempt?: number | null;
|
|
85
|
+
leaseId?: string | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
79
88
|
export interface CompleteRuntimeStepReceiptInput {
|
|
80
89
|
key: string;
|
|
81
90
|
runId: string;
|
|
@@ -122,6 +131,24 @@ export interface ClaimRuntimeStepReceiptsInput {
|
|
|
122
131
|
forceFailedRefresh?: boolean;
|
|
123
132
|
}
|
|
124
133
|
|
|
134
|
+
export interface MarkRuntimeStepReceiptsRunningInput {
|
|
135
|
+
receipts: Array<{
|
|
136
|
+
key: string;
|
|
137
|
+
runId: string;
|
|
138
|
+
runAttempt?: number | null;
|
|
139
|
+
leaseId?: string | null;
|
|
140
|
+
}>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface MarkRuntimeStepReceiptsQueuedInput {
|
|
144
|
+
receipts: Array<{
|
|
145
|
+
key: string;
|
|
146
|
+
runId: string;
|
|
147
|
+
runAttempt?: number | null;
|
|
148
|
+
leaseId?: string | null;
|
|
149
|
+
}>;
|
|
150
|
+
}
|
|
151
|
+
|
|
125
152
|
export interface CompleteRuntimeStepReceiptsInput {
|
|
126
153
|
receipts: Array<{
|
|
127
154
|
key: string;
|
|
@@ -332,6 +359,9 @@ export interface MapExecutionFrame {
|
|
|
332
359
|
totalRows: number;
|
|
333
360
|
completedRowKeys: string[];
|
|
334
361
|
pendingRowKeys: string[];
|
|
362
|
+
completedRowsCount?: number;
|
|
363
|
+
pendingRowsCount?: number;
|
|
364
|
+
failedRowsCount?: number;
|
|
335
365
|
activeBoundaryId?: string | null;
|
|
336
366
|
startedAt: number;
|
|
337
367
|
updatedAt: number;
|
|
@@ -452,11 +482,18 @@ export interface ContextOptions {
|
|
|
452
482
|
artifactHash?: string | null;
|
|
453
483
|
convexUrl?: string;
|
|
454
484
|
runtimeSchedulerSchema?: string | null;
|
|
485
|
+
/**
|
|
486
|
+
* Execution profile for scheduler-backed `ctx.runPlay` child launches. Threaded
|
|
487
|
+
* from the parent launch so a child inherits the parent's scheduler profile
|
|
488
|
+
* (e.g. `absurd`) on the child `/api/v2/plays/run` submission. Absent defaults
|
|
489
|
+
* to the `absurd` profile.
|
|
490
|
+
*/
|
|
491
|
+
childRunProfile?: string | null;
|
|
455
492
|
staticPipeline?: PlayStaticPipeline | null;
|
|
456
493
|
workflowId?: string;
|
|
457
494
|
sessionId?: string;
|
|
458
495
|
verbose?: boolean;
|
|
459
|
-
/**
|
|
496
|
+
/** Checkpoint from a previous scheduler attempt. */
|
|
460
497
|
checkpoint?: PlayCheckpoint;
|
|
461
498
|
/** Enables durable boundary replay such as ctx.sleep() resumptions. */
|
|
462
499
|
durableBoundaries?: boolean;
|
|
@@ -470,7 +507,7 @@ export interface ContextOptions {
|
|
|
470
507
|
forceToolRefresh?: boolean;
|
|
471
508
|
forceFailedToolRefresh?: boolean;
|
|
472
509
|
};
|
|
473
|
-
/** Called after each durable batch completes
|
|
510
|
+
/** Called after each durable batch completes for scheduler checkpointing. */
|
|
474
511
|
onBatchComplete?: (checkpoint: PlayCheckpoint) => void;
|
|
475
512
|
/** Called when the runtime emits a new execution log line. */
|
|
476
513
|
onLog?: (line: string) => void;
|
|
@@ -497,6 +534,7 @@ export interface ContextOptions {
|
|
|
497
534
|
executorToken?: string;
|
|
498
535
|
staticPipeline?: PlayStaticPipeline | null;
|
|
499
536
|
forceRefresh?: boolean;
|
|
537
|
+
inputOffset?: number;
|
|
500
538
|
},
|
|
501
539
|
) => Promise<MapStartResult>;
|
|
502
540
|
/**
|
|
@@ -528,6 +566,18 @@ export interface ContextOptions {
|
|
|
528
566
|
* context without a shared rate-state backend is a configuration error.
|
|
529
567
|
*/
|
|
530
568
|
requireSharedRateState?: boolean;
|
|
569
|
+
/**
|
|
570
|
+
* Node/Daytona map memory guard. Production adapters should normally omit
|
|
571
|
+
* this and use the runtime constants; tests and explicit resource profiles may
|
|
572
|
+
* pass a smaller/larger budget to make the OOM boundary deterministic.
|
|
573
|
+
*/
|
|
574
|
+
runtimeMapMemoryLimits?: Partial<RuntimeMapMemoryLimits>;
|
|
575
|
+
/**
|
|
576
|
+
* Return map PlayDataset handles backed by Runtime Sheet reads after a map
|
|
577
|
+
* has persisted. Runtime adapters with real sheet APIs should enable this;
|
|
578
|
+
* in-memory callback tests can omit it and keep local result resolvers.
|
|
579
|
+
*/
|
|
580
|
+
runtimeSheetBackedMapDatasets?: boolean;
|
|
531
581
|
resolvePlay?: (playRef: string) => Promise<ResolvedPlayExecution | null>;
|
|
532
582
|
getToolQueueHints?: (toolId: string) => Promise<readonly PlayQueueHint[]>;
|
|
533
583
|
getToolRetryPolicy?: (toolId: string) => Promise<{
|
|
@@ -583,6 +633,19 @@ export interface ContextOptions {
|
|
|
583
633
|
) =>
|
|
584
634
|
| Promise<Array<RuntimeStepReceipt | null>>
|
|
585
635
|
| Array<RuntimeStepReceipt | null>;
|
|
636
|
+
markRuntimeStepReceiptRunning?: (
|
|
637
|
+
input: MarkRuntimeStepReceiptRunningInput,
|
|
638
|
+
) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
|
|
639
|
+
markRuntimeStepReceiptsRunning?: (
|
|
640
|
+
input: MarkRuntimeStepReceiptsRunningInput,
|
|
641
|
+
) =>
|
|
642
|
+
| Promise<Array<RuntimeStepReceipt | null>>
|
|
643
|
+
| Array<RuntimeStepReceipt | null>;
|
|
644
|
+
markRuntimeStepReceiptsQueued?: (
|
|
645
|
+
input: MarkRuntimeStepReceiptsQueuedInput,
|
|
646
|
+
) =>
|
|
647
|
+
| Promise<Array<RuntimeStepReceipt | null>>
|
|
648
|
+
| Array<RuntimeStepReceipt | null>;
|
|
586
649
|
completeRuntimeStepReceipt?: (
|
|
587
650
|
input: CompleteRuntimeStepReceiptInput,
|
|
588
651
|
) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
|
|
@@ -15,8 +15,10 @@ import { getToolHttpErrorReceiptFailureKind } from './tool-http-errors';
|
|
|
15
15
|
import type { WorkReceiptFailureKind } from './work-receipts';
|
|
16
16
|
import {
|
|
17
17
|
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
18
|
+
runtimeLeaseHeartbeatIntervalFromExpiry,
|
|
18
19
|
runtimeLeaseHeartbeatIntervalMs,
|
|
19
20
|
} from './lease-policy';
|
|
21
|
+
import { createRuntimeReceiptHeartbeatSupervisor } from './receipt-heartbeat-supervisor';
|
|
20
22
|
|
|
21
23
|
const DURABLE_RECEIPT_WAIT_MAX_ATTEMPTS = 240;
|
|
22
24
|
const DURABLE_RECEIPT_WAIT_DELAY_MS = 250;
|
|
@@ -42,8 +44,14 @@ export class RuntimeReceiptLeaseLostError extends Error {
|
|
|
42
44
|
|
|
43
45
|
function isInFlightRuntimeReceipt(
|
|
44
46
|
receipt: RuntimeStepReceipt | null | undefined,
|
|
45
|
-
): receipt is RuntimeStepReceipt & {
|
|
46
|
-
|
|
47
|
+
): receipt is RuntimeStepReceipt & {
|
|
48
|
+
status: 'queued' | 'pending' | 'running';
|
|
49
|
+
} {
|
|
50
|
+
return (
|
|
51
|
+
receipt?.status === 'queued' ||
|
|
52
|
+
receipt?.status === 'pending' ||
|
|
53
|
+
receipt?.status === 'running'
|
|
54
|
+
);
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
export function runtimeReceiptFailureKindForError(
|
|
@@ -51,6 +59,7 @@ export function runtimeReceiptFailureKindForError(
|
|
|
51
59
|
): WorkReceiptFailureKind {
|
|
52
60
|
const toolFailureKind = getToolHttpErrorReceiptFailureKind(error);
|
|
53
61
|
if (toolFailureKind) return toolFailureKind;
|
|
62
|
+
|
|
54
63
|
return isPlayExecutionSuspendedError(error) ||
|
|
55
64
|
isPlayRowExecutionSuspendedError(error)
|
|
56
65
|
? 'repairable'
|
|
@@ -140,6 +149,11 @@ export type DurableReceiptExecutionStore = {
|
|
|
140
149
|
forceRefresh?: boolean,
|
|
141
150
|
forceFailedRefresh?: boolean,
|
|
142
151
|
): Promise<RuntimeStepReceipt | null>;
|
|
152
|
+
markRunning?(
|
|
153
|
+
receiptKey: string,
|
|
154
|
+
runId: string,
|
|
155
|
+
leaseId?: string | null,
|
|
156
|
+
): Promise<RuntimeStepReceipt | null>;
|
|
143
157
|
complete(
|
|
144
158
|
receiptKey: string,
|
|
145
159
|
runId: string,
|
|
@@ -173,6 +187,7 @@ async function executeWithDurableRuntimeReceiptHeartbeat<T>(input: {
|
|
|
173
187
|
leaseId: string;
|
|
174
188
|
heartbeatIntervalMs?: number;
|
|
175
189
|
store: DurableReceiptExecutionStore;
|
|
190
|
+
onTerminalHeartbeat?: (receipt: RuntimeStepReceipt) => void;
|
|
176
191
|
execute: () => Promise<T>;
|
|
177
192
|
}): Promise<T> {
|
|
178
193
|
if (!input.store.heartbeat) {
|
|
@@ -185,57 +200,56 @@ async function executeWithDurableRuntimeReceiptHeartbeat<T>(input: {
|
|
|
185
200
|
throw new Error('Runtime receipt heartbeat interval must be positive.');
|
|
186
201
|
}
|
|
187
202
|
|
|
188
|
-
let stopped = false;
|
|
189
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
190
203
|
let rejectLeaseLost!: (error: RuntimeReceiptLeaseLostError) => void;
|
|
191
204
|
const leaseLost = new Promise<never>((_, reject) => {
|
|
192
205
|
rejectLeaseLost = reject;
|
|
193
206
|
});
|
|
194
|
-
const
|
|
195
|
-
stopped = true;
|
|
196
|
-
if (timeout !== null) {
|
|
197
|
-
clearTimeout(timeout);
|
|
198
|
-
timeout = null;
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
const heartbeatOnce = async () => {
|
|
207
|
+
const heartbeatOnce = async (): Promise<'active' | 'terminal'> => {
|
|
202
208
|
const receipt = await input.store.heartbeat!(
|
|
203
209
|
input.receiptKey,
|
|
204
210
|
input.runId,
|
|
205
211
|
input.leaseId,
|
|
206
212
|
);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
const isTerminal =
|
|
214
|
+
receipt?.status === 'completed' || receipt?.status === 'skipped';
|
|
215
|
+
if (receipt && isTerminal) {
|
|
216
|
+
input.onTerminalHeartbeat?.(receipt);
|
|
217
|
+
return 'terminal';
|
|
218
|
+
}
|
|
219
|
+
const stillOwnsReceipt =
|
|
220
|
+
receipt &&
|
|
221
|
+
(receipt.status === 'running' || receipt.status === 'queued') &&
|
|
222
|
+
receipt.leaseId === input.leaseId;
|
|
223
|
+
if (!stillOwnsReceipt) {
|
|
224
|
+
const latest = await input.store.get(input.receiptKey);
|
|
225
|
+
if (
|
|
226
|
+
latest &&
|
|
227
|
+
(latest.status === 'completed' || latest.status === 'skipped')
|
|
228
|
+
) {
|
|
229
|
+
input.onTerminalHeartbeat?.(latest);
|
|
230
|
+
return 'terminal';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!stillOwnsReceipt) {
|
|
212
234
|
throw new RuntimeReceiptLeaseLostError(input);
|
|
213
235
|
}
|
|
236
|
+
return 'active';
|
|
214
237
|
};
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
stop();
|
|
223
|
-
rejectLeaseLost(
|
|
224
|
-
error instanceof RuntimeReceiptLeaseLostError
|
|
225
|
-
? error
|
|
226
|
-
: new RuntimeReceiptLeaseLostError(input),
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
})();
|
|
230
|
-
}, heartbeatIntervalMs);
|
|
231
|
-
};
|
|
238
|
+
const supervisor = createRuntimeReceiptHeartbeatSupervisor({
|
|
239
|
+
intervalMs: heartbeatIntervalMs,
|
|
240
|
+
heartbeat: heartbeatOnce,
|
|
241
|
+
isLeaseLost: (error) => error instanceof RuntimeReceiptLeaseLostError,
|
|
242
|
+
onLeaseLost: (error) =>
|
|
243
|
+
rejectLeaseLost(error as RuntimeReceiptLeaseLostError),
|
|
244
|
+
});
|
|
232
245
|
|
|
233
|
-
await heartbeatOnce()
|
|
234
|
-
|
|
246
|
+
if ((await heartbeatOnce()) === 'active') {
|
|
247
|
+
supervisor.start();
|
|
248
|
+
}
|
|
235
249
|
try {
|
|
236
250
|
return await Promise.race([input.execute(), leaseLost]);
|
|
237
251
|
} finally {
|
|
238
|
-
stop();
|
|
252
|
+
supervisor.stop();
|
|
239
253
|
}
|
|
240
254
|
}
|
|
241
255
|
|
|
@@ -274,6 +288,66 @@ export async function waitForCompletedRuntimeReceipt(input: {
|
|
|
274
288
|
throw new RuntimeReceiptWaitTimeoutError(input.receiptKey);
|
|
275
289
|
}
|
|
276
290
|
|
|
291
|
+
export async function waitForCompletedRuntimeReceipts(input: {
|
|
292
|
+
receiptKeys: string[];
|
|
293
|
+
store: Pick<DurableReceiptExecutionStore, 'getMany'>;
|
|
294
|
+
maxAttempts?: number;
|
|
295
|
+
delayMs?: number;
|
|
296
|
+
abortSignal?: AbortSignal;
|
|
297
|
+
}): Promise<{
|
|
298
|
+
completed: Map<string, RuntimeStepReceipt>;
|
|
299
|
+
failed: Map<string, Error>;
|
|
300
|
+
timedOut: Set<string>;
|
|
301
|
+
}> {
|
|
302
|
+
const uniqueKeys = [
|
|
303
|
+
...new Set(input.receiptKeys.map((key) => key.trim()).filter(Boolean)),
|
|
304
|
+
];
|
|
305
|
+
const pending = new Set(uniqueKeys);
|
|
306
|
+
const completed = new Map<string, RuntimeStepReceipt>();
|
|
307
|
+
const failed = new Map<string, Error>();
|
|
308
|
+
const maxAttempts = input.maxAttempts ?? DURABLE_RECEIPT_WAIT_MAX_ATTEMPTS;
|
|
309
|
+
const delayMs = input.delayMs ?? DURABLE_RECEIPT_WAIT_DELAY_MS;
|
|
310
|
+
|
|
311
|
+
for (
|
|
312
|
+
let attempt = 0;
|
|
313
|
+
attempt < maxAttempts && pending.size > 0;
|
|
314
|
+
attempt += 1
|
|
315
|
+
) {
|
|
316
|
+
throwIfReceiptWaitAborted(input.abortSignal);
|
|
317
|
+
if (attempt > 0) {
|
|
318
|
+
await sleepReceiptWait(delayMs, input.abortSignal);
|
|
319
|
+
}
|
|
320
|
+
let receipts: Map<string, RuntimeStepReceipt>;
|
|
321
|
+
try {
|
|
322
|
+
receipts = await input.store.getMany([...pending]);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (error instanceof RuntimeReceiptWaitTimeoutError) {
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
for (const key of [...pending]) {
|
|
330
|
+
const receipt = receipts.get(key);
|
|
331
|
+
if (receipt?.status === 'completed' || receipt?.status === 'skipped') {
|
|
332
|
+
completed.set(key, receipt);
|
|
333
|
+
pending.delete(key);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (receipt?.status === 'failed') {
|
|
337
|
+
failed.set(
|
|
338
|
+
key,
|
|
339
|
+
new Error(
|
|
340
|
+
`Durable tool call ${key} failed: ${receipt.error ?? 'unknown error'}`,
|
|
341
|
+
),
|
|
342
|
+
);
|
|
343
|
+
pending.delete(key);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return { completed, failed, timedOut: pending };
|
|
349
|
+
}
|
|
350
|
+
|
|
277
351
|
export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
278
352
|
operation: DurableReceiptOperation;
|
|
279
353
|
id: string;
|
|
@@ -295,6 +369,7 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
|
295
369
|
onClaimedResult?: (output: T, receiptKey: string) => T;
|
|
296
370
|
shouldPersistFailure?: (error: unknown) => boolean;
|
|
297
371
|
heartbeatIntervalMs?: number;
|
|
372
|
+
markRunningBeforeExecute?: boolean;
|
|
298
373
|
formatError: (error: unknown) => string;
|
|
299
374
|
log: (message: string) => void;
|
|
300
375
|
execute: (context: { leaseId: string | null }) => Promise<T>;
|
|
@@ -324,6 +399,7 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
|
324
399
|
};
|
|
325
400
|
|
|
326
401
|
let ownedLeaseId: string | null = null;
|
|
402
|
+
let ownedLeaseExpiresAt: string | null = null;
|
|
327
403
|
|
|
328
404
|
const reclaimReceipt = async (): Promise<
|
|
329
405
|
{ kind: 'recovered'; output: T } | { kind: 'claimed' }
|
|
@@ -351,6 +427,7 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
|
351
427
|
throw new RuntimeReceiptWaitTimeoutError(input.receiptKey);
|
|
352
428
|
}
|
|
353
429
|
ownedLeaseId = reclaimed?.leaseId ?? null;
|
|
430
|
+
ownedLeaseExpiresAt = reclaimed?.leaseExpiresAt ?? null;
|
|
354
431
|
return { kind: 'claimed' };
|
|
355
432
|
};
|
|
356
433
|
const shouldRepairSameRunRunningReceipt = (
|
|
@@ -439,6 +516,7 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
|
439
516
|
if (recovered.kind === 'recovered') return recovered.output;
|
|
440
517
|
} else {
|
|
441
518
|
ownedLeaseId = claimed.leaseId ?? null;
|
|
519
|
+
ownedLeaseExpiresAt = claimed.leaseExpiresAt ?? null;
|
|
442
520
|
}
|
|
443
521
|
} else if (claimed.status === 'failed') {
|
|
444
522
|
await repairFailedReceipt(claimed);
|
|
@@ -446,14 +524,40 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
|
446
524
|
|
|
447
525
|
let result: T;
|
|
448
526
|
try {
|
|
527
|
+
if (input.markRunningBeforeExecute !== false && input.store.markRunning) {
|
|
528
|
+
const running = await input.store.markRunning(
|
|
529
|
+
input.receiptKey,
|
|
530
|
+
input.runId,
|
|
531
|
+
ownedLeaseId,
|
|
532
|
+
);
|
|
533
|
+
if (!running || running.status !== 'running') {
|
|
534
|
+
throw new RuntimeReceiptLeaseLostError({
|
|
535
|
+
receiptKey: input.receiptKey,
|
|
536
|
+
runId: input.runId,
|
|
537
|
+
leaseId: ownedLeaseId ?? '',
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
ownedLeaseId = running.leaseId ?? ownedLeaseId;
|
|
541
|
+
ownedLeaseExpiresAt = running.leaseExpiresAt ?? ownedLeaseExpiresAt;
|
|
542
|
+
}
|
|
449
543
|
const executed =
|
|
450
544
|
ownedLeaseId && input.store.heartbeat
|
|
451
545
|
? await executeWithDurableRuntimeReceiptHeartbeat({
|
|
452
546
|
receiptKey: input.receiptKey,
|
|
453
547
|
runId: input.runId,
|
|
454
548
|
leaseId: ownedLeaseId,
|
|
455
|
-
heartbeatIntervalMs:
|
|
549
|
+
heartbeatIntervalMs:
|
|
550
|
+
input.heartbeatIntervalMs ??
|
|
551
|
+
runtimeLeaseHeartbeatIntervalFromExpiry({
|
|
552
|
+
leaseExpiresAt: ownedLeaseExpiresAt,
|
|
553
|
+
fallbackTtlMs: PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
554
|
+
}),
|
|
456
555
|
store: input.store,
|
|
556
|
+
onTerminalHeartbeat: (receipt) => {
|
|
557
|
+
input.log(
|
|
558
|
+
`ctx.${input.operation}(${input.id}): receipt heartbeat observed terminal ${receipt.status}; treating inner gateway completion as ownership handoff`,
|
|
559
|
+
);
|
|
560
|
+
},
|
|
457
561
|
execute: () => input.execute({ leaseId: ownedLeaseId }),
|
|
458
562
|
})
|
|
459
563
|
: await input.execute({ leaseId: ownedLeaseId });
|
|
@@ -474,7 +578,10 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
|
474
578
|
input.runId,
|
|
475
579
|
ownedLeaseId,
|
|
476
580
|
);
|
|
477
|
-
if (
|
|
581
|
+
if (
|
|
582
|
+
!released ||
|
|
583
|
+
(released.status !== 'queued' && released.status !== 'pending')
|
|
584
|
+
) {
|
|
478
585
|
throw new Error(
|
|
479
586
|
`ctx.${input.operation}(${input.id}): receipt suspended but ownership could not be released: ${input.formatError(error)}.`,
|
|
480
587
|
);
|
|
@@ -502,6 +609,21 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
|
502
609
|
isToolExecuteResult(result) ? serializeToolExecuteResult(result) : result,
|
|
503
610
|
ownedLeaseId,
|
|
504
611
|
);
|
|
612
|
+
if (
|
|
613
|
+
!completed ||
|
|
614
|
+
(completed.status !== 'completed' && completed.status !== 'skipped')
|
|
615
|
+
) {
|
|
616
|
+
const latest = await input.store.get(input.receiptKey);
|
|
617
|
+
if (
|
|
618
|
+
latest &&
|
|
619
|
+
(latest.status === 'completed' || latest.status === 'skipped')
|
|
620
|
+
) {
|
|
621
|
+
input.log(
|
|
622
|
+
`ctx.${input.operation}(${input.id}): receipt completion response reconciled from durable store read-back`,
|
|
623
|
+
);
|
|
624
|
+
return await recoverCompletedReceipt(latest, 'owner');
|
|
625
|
+
}
|
|
626
|
+
}
|
|
505
627
|
if (!completed && input.store.canPersistCompletion) {
|
|
506
628
|
throw new Error(
|
|
507
629
|
`ctx.${input.operation}(${input.id}): lost durable receipt ownership before completion.`,
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { PacingRule } from './rate-state-backend';
|
|
2
|
+
|
|
3
|
+
export type ResolvedPacing = {
|
|
4
|
+
provider: string;
|
|
5
|
+
rules: PacingRule[];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type AdaptiveAdmissionSnapshot = {
|
|
9
|
+
provider: string;
|
|
10
|
+
currentRequestsPerSecond: number;
|
|
11
|
+
latencyMsEwma: number | null;
|
|
12
|
+
successCount: number;
|
|
13
|
+
rateLimitCount: number;
|
|
14
|
+
retryAfterUntilMs: number | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export interface RuntimeAdaptiveAdmission {
|
|
18
|
+
resolvePacing(input: {
|
|
19
|
+
orgId: string;
|
|
20
|
+
toolId: string;
|
|
21
|
+
declaredPacing: ResolvedPacing | null;
|
|
22
|
+
fallbackPacing: ResolvedPacing;
|
|
23
|
+
}): ResolvedPacing;
|
|
24
|
+
observeProviderSuccess(input: {
|
|
25
|
+
orgId: string;
|
|
26
|
+
provider: string;
|
|
27
|
+
latencyMs?: number | null;
|
|
28
|
+
}): void;
|
|
29
|
+
observeProviderBackpressure(input: {
|
|
30
|
+
orgId: string;
|
|
31
|
+
provider: string;
|
|
32
|
+
retryAfterMs: number;
|
|
33
|
+
}): void;
|
|
34
|
+
snapshot(): Record<string, AdaptiveAdmissionSnapshot>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type InMemoryAdaptiveAdmissionOptions = {
|
|
38
|
+
initialRequestsPerSecond: number;
|
|
39
|
+
minRequestsPerSecond?: number;
|
|
40
|
+
maxRequestsPerSecond: number;
|
|
41
|
+
additiveIncreasePerWindow?: number;
|
|
42
|
+
cleanWindowMs?: number;
|
|
43
|
+
now?: () => number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type ProviderState = {
|
|
47
|
+
provider: string;
|
|
48
|
+
currentRequestsPerSecond: number;
|
|
49
|
+
successCount: number;
|
|
50
|
+
successCountAtLastIncrease: number;
|
|
51
|
+
rateLimitCount: number;
|
|
52
|
+
lastIncreaseAtMs: number;
|
|
53
|
+
retryAfterUntilMs: number | null;
|
|
54
|
+
latencyMsEwma: number | null;
|
|
55
|
+
fallbackRules: PacingRule[];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function finitePositive(value: number, fallback: number): number {
|
|
59
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function stateKey(orgId: string, provider: string): string {
|
|
63
|
+
return `${orgId}:${provider}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function cloneRulesWithRequestsPerSecond(
|
|
67
|
+
rules: readonly PacingRule[],
|
|
68
|
+
requestsPerSecond: number,
|
|
69
|
+
): PacingRule[] {
|
|
70
|
+
const rps = Math.max(1, Math.floor(requestsPerSecond));
|
|
71
|
+
return rules.map((rule) => ({
|
|
72
|
+
...rule,
|
|
73
|
+
requestsPerWindow:
|
|
74
|
+
rule.windowMs === 1_000
|
|
75
|
+
? rps
|
|
76
|
+
: Math.max(1, Math.floor((rps * rule.windowMs) / 1_000)),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createInMemoryAdaptiveAdmission(
|
|
81
|
+
options: InMemoryAdaptiveAdmissionOptions,
|
|
82
|
+
): RuntimeAdaptiveAdmission {
|
|
83
|
+
const now = options.now ?? Date.now;
|
|
84
|
+
const minRequestsPerSecond = finitePositive(
|
|
85
|
+
options.minRequestsPerSecond ?? 1,
|
|
86
|
+
1,
|
|
87
|
+
);
|
|
88
|
+
const initialRequestsPerSecond = Math.max(
|
|
89
|
+
minRequestsPerSecond,
|
|
90
|
+
finitePositive(options.initialRequestsPerSecond, minRequestsPerSecond),
|
|
91
|
+
);
|
|
92
|
+
const maxRequestsPerSecond = Math.max(
|
|
93
|
+
initialRequestsPerSecond,
|
|
94
|
+
finitePositive(options.maxRequestsPerSecond, initialRequestsPerSecond),
|
|
95
|
+
);
|
|
96
|
+
const additiveIncreasePerWindow = finitePositive(
|
|
97
|
+
options.additiveIncreasePerWindow ?? 5,
|
|
98
|
+
5,
|
|
99
|
+
);
|
|
100
|
+
const cleanWindowMs = finitePositive(options.cleanWindowMs ?? 1_000, 1_000);
|
|
101
|
+
const states = new Map<string, ProviderState>();
|
|
102
|
+
|
|
103
|
+
function getOrCreateState(input: {
|
|
104
|
+
orgId: string;
|
|
105
|
+
provider: string;
|
|
106
|
+
fallbackRules: readonly PacingRule[];
|
|
107
|
+
}): ProviderState {
|
|
108
|
+
const key = stateKey(input.orgId, input.provider);
|
|
109
|
+
let state = states.get(key);
|
|
110
|
+
if (!state) {
|
|
111
|
+
state = {
|
|
112
|
+
provider: input.provider,
|
|
113
|
+
currentRequestsPerSecond: initialRequestsPerSecond,
|
|
114
|
+
successCount: 0,
|
|
115
|
+
successCountAtLastIncrease: 0,
|
|
116
|
+
rateLimitCount: 0,
|
|
117
|
+
lastIncreaseAtMs: now(),
|
|
118
|
+
retryAfterUntilMs: null,
|
|
119
|
+
latencyMsEwma: null,
|
|
120
|
+
fallbackRules: [...input.fallbackRules],
|
|
121
|
+
};
|
|
122
|
+
states.set(key, state);
|
|
123
|
+
}
|
|
124
|
+
return state;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getExistingState(
|
|
128
|
+
orgId: string,
|
|
129
|
+
provider: string,
|
|
130
|
+
): ProviderState | null {
|
|
131
|
+
return states.get(stateKey(orgId, provider)) ?? null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
resolvePacing(input) {
|
|
136
|
+
if (input.declaredPacing && input.declaredPacing.rules.length > 0) {
|
|
137
|
+
return input.declaredPacing;
|
|
138
|
+
}
|
|
139
|
+
const state = getOrCreateState({
|
|
140
|
+
orgId: input.orgId,
|
|
141
|
+
provider: input.fallbackPacing.provider,
|
|
142
|
+
fallbackRules: input.fallbackPacing.rules,
|
|
143
|
+
});
|
|
144
|
+
return {
|
|
145
|
+
provider: input.fallbackPacing.provider,
|
|
146
|
+
rules: cloneRulesWithRequestsPerSecond(
|
|
147
|
+
state.fallbackRules,
|
|
148
|
+
state.currentRequestsPerSecond,
|
|
149
|
+
),
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
observeProviderSuccess(input) {
|
|
154
|
+
const state = getExistingState(input.orgId, input.provider);
|
|
155
|
+
if (!state) return;
|
|
156
|
+
const observedLatency = input.latencyMs;
|
|
157
|
+
if (observedLatency != null && Number.isFinite(observedLatency)) {
|
|
158
|
+
const latency = Math.max(0, observedLatency);
|
|
159
|
+
state.latencyMsEwma =
|
|
160
|
+
state.latencyMsEwma == null
|
|
161
|
+
? latency
|
|
162
|
+
: state.latencyMsEwma * 0.8 + latency * 0.2;
|
|
163
|
+
}
|
|
164
|
+
state.successCount += 1;
|
|
165
|
+
const currentTime = now();
|
|
166
|
+
if (
|
|
167
|
+
state.retryAfterUntilMs != null &&
|
|
168
|
+
state.retryAfterUntilMs > currentTime
|
|
169
|
+
) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
state.retryAfterUntilMs = null;
|
|
173
|
+
const successesSinceIncrease =
|
|
174
|
+
state.successCount - state.successCountAtLastIncrease;
|
|
175
|
+
if (
|
|
176
|
+
currentTime - state.lastIncreaseAtMs >= cleanWindowMs &&
|
|
177
|
+
successesSinceIncrease >= Math.max(1, state.currentRequestsPerSecond)
|
|
178
|
+
) {
|
|
179
|
+
state.currentRequestsPerSecond = Math.min(
|
|
180
|
+
maxRequestsPerSecond,
|
|
181
|
+
state.currentRequestsPerSecond + additiveIncreasePerWindow,
|
|
182
|
+
);
|
|
183
|
+
state.lastIncreaseAtMs = currentTime;
|
|
184
|
+
state.successCountAtLastIncrease = state.successCount;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
observeProviderBackpressure(input) {
|
|
189
|
+
const state = getExistingState(input.orgId, input.provider);
|
|
190
|
+
if (!state) return;
|
|
191
|
+
state.rateLimitCount += 1;
|
|
192
|
+
state.currentRequestsPerSecond = Math.max(
|
|
193
|
+
minRequestsPerSecond,
|
|
194
|
+
Math.floor(state.currentRequestsPerSecond / 2),
|
|
195
|
+
);
|
|
196
|
+
const currentTime = now();
|
|
197
|
+
state.retryAfterUntilMs = currentTime + Math.max(0, input.retryAfterMs);
|
|
198
|
+
state.lastIncreaseAtMs = currentTime;
|
|
199
|
+
state.successCountAtLastIncrease = state.successCount;
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
snapshot() {
|
|
203
|
+
return Object.fromEntries(
|
|
204
|
+
[...states.entries()].map(([key, state]) => [
|
|
205
|
+
key,
|
|
206
|
+
{
|
|
207
|
+
provider: state.provider,
|
|
208
|
+
currentRequestsPerSecond: state.currentRequestsPerSecond,
|
|
209
|
+
latencyMsEwma: state.latencyMsEwma,
|
|
210
|
+
successCount: state.successCount,
|
|
211
|
+
rateLimitCount: state.rateLimitCount,
|
|
212
|
+
retryAfterUntilMs: state.retryAfterUntilMs,
|
|
213
|
+
},
|
|
214
|
+
]),
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|