deepline 0.1.201 → 0.1.203
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/entry.ts +30 -9
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +25 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +139 -124
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +17 -2
- package/dist/bundling-sources/shared_libs/play-runtime/bounded-dispatch.ts +52 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +154 -23
- package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +121 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-budget-state-backend.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +86 -19
- package/dist/bundling-sources/shared_libs/play-runtime/governor/block-reserving-budget-state-backend.ts +141 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/budget-state-backend.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +129 -41
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +40 -2
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +92 -33
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +13 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +122 -120
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +10 -8
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +140 -32
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-paths.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +225 -29
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +21 -0
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +32 -10
- package/dist/cli/index.js +335 -77
- package/dist/cli/index.mjs +341 -83
- package/dist/index.js +3 -2
- package/dist/index.mjs +3 -2
- package/package.json +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +0 -355
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
CoordinatorRateStateBackend,
|
|
54
54
|
type CoordinatorRatePort,
|
|
55
55
|
} from '../../../shared_libs/play-runtime/governor/coordinator-rate-state-backend';
|
|
56
|
+
import { BlockReservingBudgetStateBackend } from '../../../shared_libs/play-runtime/governor/block-reserving-budget-state-backend';
|
|
56
57
|
import type { PacingRule } from '../../../shared_libs/play-runtime/governor/rate-state-backend';
|
|
57
58
|
import {
|
|
58
59
|
pacingPolicyForTool,
|
|
@@ -250,6 +251,7 @@ import { runtimeSheetSessionScope } from '../../../shared_libs/play-runtime/runt
|
|
|
250
251
|
import {
|
|
251
252
|
chooseWorkerMapDispatchPlan,
|
|
252
253
|
estimateBatchedToolChunkSubrequests,
|
|
254
|
+
workflowSafeMapRowsPerChunk,
|
|
253
255
|
} from './runtime/map-chunk-plan';
|
|
254
256
|
import {
|
|
255
257
|
applyCsvRenameProjection,
|
|
@@ -337,6 +339,8 @@ type RunRequest = {
|
|
|
337
339
|
forceToolRefresh?: boolean;
|
|
338
340
|
/** Monotonic coordinator redispatch epoch for receipt and sheet fencing. */
|
|
339
341
|
runAttempt?: number;
|
|
342
|
+
/** Tenant/worktree scheduler schema for shared durable governor state. */
|
|
343
|
+
runtimeSchedulerSchema?: string | null;
|
|
340
344
|
/** Optional inline CSV rows (for plays where ctx.csv was passed inline data). */
|
|
341
345
|
inlineCsv?: { name: string; rows: Record<string, unknown>[] } | null;
|
|
342
346
|
/** Staged input files keyed by logical filename (used by ctx.csv). */
|
|
@@ -1823,7 +1827,7 @@ async function executeTool(
|
|
|
1823
1827
|
},
|
|
1824
1828
|
workflowStep?: WorkflowStep,
|
|
1825
1829
|
onProviderBackpressure?: (retryAfterMs: number) => void,
|
|
1826
|
-
onRetryAttempt?: () => void
|
|
1830
|
+
onRetryAttempt?: () => void | Promise<void>,
|
|
1827
1831
|
transientHttpRetrySafe = false,
|
|
1828
1832
|
abortSignal?: AbortSignal,
|
|
1829
1833
|
runtimeDeadlineMs?: number,
|
|
@@ -1869,7 +1873,7 @@ async function executeToolWithLifecycle(
|
|
|
1869
1873
|
workflowStep: WorkflowStep | undefined,
|
|
1870
1874
|
callbacks: WorkerCtxCallbacks | undefined,
|
|
1871
1875
|
onProviderBackpressure?: (retryAfterMs: number) => void,
|
|
1872
|
-
onRetryAttempt?: () => void
|
|
1876
|
+
onRetryAttempt?: () => void | Promise<void>,
|
|
1873
1877
|
transientHttpRetrySafe = false,
|
|
1874
1878
|
abortSignal?: AbortSignal,
|
|
1875
1879
|
runtimeDeadlineMs?: number,
|
|
@@ -2169,7 +2173,7 @@ async function callToolDirect(
|
|
|
2169
2173
|
) {
|
|
2170
2174
|
throw lastError;
|
|
2171
2175
|
}
|
|
2172
|
-
onRetryAttempt?.();
|
|
2176
|
+
await onRetryAttempt?.();
|
|
2173
2177
|
const delayMs = TOOL_EXECUTE_TRANSPORT_RETRY_DELAY_MS * transportAttempt;
|
|
2174
2178
|
console.warn(
|
|
2175
2179
|
`[deepline-run:${req.runId}] tool transport retry tool=${toolId} path=${path} attempt=${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS} retryAfterMs=${delayMs} error=${redactSecretsFromLogString(message)}`,
|
|
@@ -2224,7 +2228,7 @@ async function callToolDirect(
|
|
|
2224
2228
|
// Charge the retry budget per attempt, matching the cjs runner's
|
|
2225
2229
|
// chargeBudget('retry') on every 429 / retryable-5xx retry.
|
|
2226
2230
|
if (failure.chargeRetryBudget) {
|
|
2227
|
-
onRetryAttempt?.();
|
|
2231
|
+
await onRetryAttempt?.();
|
|
2228
2232
|
}
|
|
2229
2233
|
await sleepWorkerMs(failure.retryDelayMs);
|
|
2230
2234
|
}
|
|
@@ -2954,7 +2958,7 @@ async function postRuntimeEgressFetch(
|
|
|
2954
2958
|
req: RunRequest,
|
|
2955
2959
|
payload: RuntimeEgressFetchPayload,
|
|
2956
2960
|
onProviderBackpressure?: (retryAfterMs: number) => void,
|
|
2957
|
-
onRetryAttempt?: () => void
|
|
2961
|
+
onRetryAttempt?: () => void | Promise<void>,
|
|
2958
2962
|
): Promise<WorkerFetchResponse> {
|
|
2959
2963
|
let lastError: Error | null = null;
|
|
2960
2964
|
for (
|
|
@@ -3008,7 +3012,7 @@ async function postRuntimeEgressFetch(
|
|
|
3008
3012
|
if (attempt >= TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS) {
|
|
3009
3013
|
throw lastError;
|
|
3010
3014
|
}
|
|
3011
|
-
onRetryAttempt?.();
|
|
3015
|
+
await onRetryAttempt?.();
|
|
3012
3016
|
await sleepWorkerMs(
|
|
3013
3017
|
Math.min(5_000, Math.max(retryAfterMs, attempt * 1000)),
|
|
3014
3018
|
);
|
|
@@ -4309,6 +4313,16 @@ function createGovernorForRun(req: RunRequest): {
|
|
|
4309
4313
|
rootRunId: req.playCallGovernance?.rootRunId ?? req.runId,
|
|
4310
4314
|
},
|
|
4311
4315
|
rateState: new CoordinatorRateStateBackend(createCoordinatorRatePort(req)),
|
|
4316
|
+
budgetState: new BlockReservingBudgetStateBackend(
|
|
4317
|
+
async (input) =>
|
|
4318
|
+
await postRuntimeApi(req.baseUrl, req.executorToken, {
|
|
4319
|
+
action: 'governor_budget_charge',
|
|
4320
|
+
rootRunId: input.rootRunId,
|
|
4321
|
+
reservationId: input.reservationId,
|
|
4322
|
+
charges: input.charges,
|
|
4323
|
+
schedulerSchema: req.runtimeSchedulerSchema ?? null,
|
|
4324
|
+
}),
|
|
4325
|
+
),
|
|
4312
4326
|
resolvePacing,
|
|
4313
4327
|
resume: resumeGovernanceFromRequest(req),
|
|
4314
4328
|
});
|
|
@@ -4415,7 +4429,7 @@ function createMinimalWorkerCtx(
|
|
|
4415
4429
|
workflowStep?: unknown;
|
|
4416
4430
|
callbacks?: WorkerCtxCallbacks;
|
|
4417
4431
|
onProviderBackpressure?: (retryAfterMs: number) => void;
|
|
4418
|
-
onRetryAttempt?: () => void
|
|
4432
|
+
onRetryAttempt?: () => void | Promise<void>;
|
|
4419
4433
|
transientHttpRetrySafe?: boolean;
|
|
4420
4434
|
durableCallReceiptKey?: string | null;
|
|
4421
4435
|
executionAuthScopeDigest?: string | null;
|
|
@@ -4604,7 +4618,13 @@ function createMinimalWorkerCtx(
|
|
|
4604
4618
|
mapDispatchPlan.workEstimate.providerToolCallsPerRow > 0 ||
|
|
4605
4619
|
mapDispatchPlan.workEstimate.childPlaySubmitsPerRow > 0 ||
|
|
4606
4620
|
mapDispatchPlan.workEstimate.eventWaitsPerRow > 0;
|
|
4607
|
-
const
|
|
4621
|
+
const plannedRowsPerChunk = mapDispatchPlan.rowsPerChunk;
|
|
4622
|
+
const rowsPerChunk = workflowSafeMapRowsPerChunk({
|
|
4623
|
+
plannedRowsPerChunk,
|
|
4624
|
+
rowAdmissionWindow: governor.policy.concurrency.rowDefault,
|
|
4625
|
+
hasWorkflowStep: Boolean(workflowStep),
|
|
4626
|
+
hasExternalSideEffects: mapHasExternalSideEffects,
|
|
4627
|
+
});
|
|
4608
4628
|
recordRunnerPerfTrace({
|
|
4609
4629
|
req,
|
|
4610
4630
|
phase: 'runner.map_dispatch_plan',
|
|
@@ -4612,6 +4632,7 @@ function createMinimalWorkerCtx(
|
|
|
4612
4632
|
extra: {
|
|
4613
4633
|
mapName: name,
|
|
4614
4634
|
rowsPerChunk,
|
|
4635
|
+
plannedRowsPerChunk,
|
|
4615
4636
|
rowCountHint,
|
|
4616
4637
|
estimatedExternalStepsPerRow:
|
|
4617
4638
|
mapDispatchPlan.estimatedExternalStepsPerRow,
|
|
@@ -6949,7 +6970,7 @@ function createMinimalWorkerCtx(
|
|
|
6949
6970
|
// across isolates. Charged inside the receipt boundary so a replay
|
|
6950
6971
|
// (cache hit) never double-charges.
|
|
6951
6972
|
const childRunId = `${req.runId}:child:${normalizedKey}`;
|
|
6952
|
-
const childGovernance = governor.forkChild({
|
|
6973
|
+
const childGovernance = await governor.forkChild({
|
|
6953
6974
|
childPlayName: resolvedName,
|
|
6954
6975
|
childRunId,
|
|
6955
6976
|
});
|
|
@@ -146,6 +146,31 @@ export type WorkerMapWorkEstimate = {
|
|
|
146
146
|
eventWaitsPerRow: number;
|
|
147
147
|
};
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Keep one externally-effectful Workflow chunk inside the same bounded row
|
|
151
|
+
* window used by runtime admission. The static planner may choose a larger
|
|
152
|
+
* chunk when provider calls coalesce, but a chunk is still the smallest point
|
|
153
|
+
* where the dispatcher can cross a durable sleep boundary. Capping only the
|
|
154
|
+
* native Workflow execution chunk prevents a slow 5k provider chunk from
|
|
155
|
+
* outliving one invocation and being retried before the dispatcher can yield.
|
|
156
|
+
*/
|
|
157
|
+
export function workflowSafeMapRowsPerChunk(input: {
|
|
158
|
+
plannedRowsPerChunk: number;
|
|
159
|
+
rowAdmissionWindow: number;
|
|
160
|
+
hasWorkflowStep: boolean;
|
|
161
|
+
hasExternalSideEffects: boolean;
|
|
162
|
+
}): number {
|
|
163
|
+
const plannedRowsPerChunk = Math.max(
|
|
164
|
+
1,
|
|
165
|
+
Math.floor(input.plannedRowsPerChunk),
|
|
166
|
+
);
|
|
167
|
+
if (!input.hasWorkflowStep || !input.hasExternalSideEffects) {
|
|
168
|
+
return plannedRowsPerChunk;
|
|
169
|
+
}
|
|
170
|
+
const rowAdmissionWindow = Math.max(1, Math.floor(input.rowAdmissionWindow));
|
|
171
|
+
return Math.min(plannedRowsPerChunk, rowAdmissionWindow);
|
|
172
|
+
}
|
|
173
|
+
|
|
149
174
|
function fieldRoot(field: string | null | undefined): string {
|
|
150
175
|
return String(field ?? '').split('.', 1)[0] ?? '';
|
|
151
176
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
executeChunkedRequests,
|
|
4
4
|
type ChunkExecutionResult,
|
|
5
5
|
} from '../../../../shared_libs/play-runtime/batch-runtime';
|
|
6
|
+
import { dispatchBounded } from '../../../../shared_libs/play-runtime/bounded-dispatch';
|
|
6
7
|
import type { AnyBatchOperationStrategy } from '../../../../shared_libs/play-runtime/batching-types';
|
|
7
8
|
import {
|
|
8
9
|
buildDurableToolCallAuthScopeDigest,
|
|
@@ -110,7 +111,7 @@ export type WorkerToolDispatchExecuteTool = (input: {
|
|
|
110
111
|
workflowStep?: WorkflowStepLike;
|
|
111
112
|
callbacks?: WorkerToolDispatchCallbacks;
|
|
112
113
|
onProviderBackpressure?: (retryAfterMs: number) => void;
|
|
113
|
-
onRetryAttempt?: () => void
|
|
114
|
+
onRetryAttempt?: () => void | Promise<void>;
|
|
114
115
|
transientHttpRetrySafe?: boolean;
|
|
115
116
|
durableCallReceiptKey?: string | null;
|
|
116
117
|
executionAuthScopeDigest?: string | null;
|
|
@@ -2026,32 +2027,137 @@ export class WorkerToolBatchScheduler {
|
|
|
2026
2027
|
claimedRequests.length < 2
|
|
2027
2028
|
) {
|
|
2028
2029
|
const groupStartedAt = this.options.nowMs();
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2030
|
+
// Seed the dispatch width from the governor's provider-shaped parallelism
|
|
2031
|
+
// instead of the flat policy.concurrency.toolCalls ceiling. A flat width
|
|
2032
|
+
// launched every claimed row at once into the pacer, so unhinted providers
|
|
2033
|
+
// 429'd on the first burst before AIMD could halve the rate. The shaped
|
|
2034
|
+
// estimate is derived from the provider's RPS/maxConcurrency pacing rules
|
|
2035
|
+
// (see suggestedParallelism in governor.ts), floored at 1 and capped by the
|
|
2036
|
+
// global tool-call concurrency ceiling. Per-call pacing still gates each
|
|
2037
|
+
// in-flight request, so this only trims the launch burst.
|
|
2038
|
+
const toolCallConcurrencyCeiling =
|
|
2039
|
+
this.options.governor.policy.concurrency.toolCalls;
|
|
2040
|
+
const shapedToolParallelism =
|
|
2041
|
+
await this.options.governor.suggestedParallelism(
|
|
2042
|
+
toolId,
|
|
2043
|
+
toolCallConcurrencyCeiling,
|
|
2044
|
+
);
|
|
2045
|
+
const dispatchWidth = Math.min(
|
|
2046
|
+
toolCallConcurrencyCeiling,
|
|
2047
|
+
Math.max(1, shapedToolParallelism),
|
|
2048
|
+
);
|
|
2049
|
+
await dispatchBounded(claimedRequests, dispatchWidth, async (claimed) => {
|
|
2050
|
+
const { request } = claimed;
|
|
2051
|
+
const toolContract = await this.options
|
|
2052
|
+
.resolvePacing(toolId)
|
|
2053
|
+
.catch(() => null);
|
|
2054
|
+
const circuitOpenError = this.runtimePersistenceCircuitOpenError(1);
|
|
2055
|
+
if (circuitOpenError) {
|
|
2056
|
+
this.rejectRequests(
|
|
2057
|
+
claimed,
|
|
2058
|
+
await this.failureForRejectedToolRequest(claimed, circuitOpenError),
|
|
2059
|
+
);
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
// Each unbatched provider call takes its own tool slot: the Governor
|
|
2063
|
+
// charges tool budget, holds a global tool-concurrency slot, and
|
|
2064
|
+
// applies per-(org,provider) pacing before the call runs.
|
|
2065
|
+
let slot: { release: () => void };
|
|
2066
|
+
try {
|
|
2067
|
+
slot = await this.options.governor.acquireToolSlot(toolId, {
|
|
2068
|
+
signal: this.options.abortSignal,
|
|
2069
|
+
});
|
|
2070
|
+
} catch (error) {
|
|
2071
|
+
this.rejectRequests(
|
|
2072
|
+
claimed,
|
|
2073
|
+
await this.failureForRejectedToolRequest(claimed, error),
|
|
2074
|
+
);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
try {
|
|
2078
|
+
let result: unknown;
|
|
2079
|
+
try {
|
|
2080
|
+
await this.markAdmittedToolRequestsRunning([claimed]);
|
|
2081
|
+
this.options.budgetMeter?.count('provider');
|
|
2082
|
+
this.options.budgetMeter?.count(
|
|
2083
|
+
'egress',
|
|
2084
|
+
Math.max(
|
|
2085
|
+
0,
|
|
2086
|
+
WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL - 1,
|
|
2042
2087
|
),
|
|
2043
2088
|
);
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2089
|
+
const durableReceiptKey = claimed.receiptKey
|
|
2090
|
+
? durableCallReceiptKeyForClaimed({
|
|
2091
|
+
req: this.options.req,
|
|
2092
|
+
claimed,
|
|
2093
|
+
})
|
|
2094
|
+
: null;
|
|
2095
|
+
result = await executeWithWorkerReceiptHeartbeats({
|
|
2096
|
+
playName: this.options.req.playName,
|
|
2097
|
+
runId: this.options.req.runId,
|
|
2098
|
+
runAttempt: this.options.req.runAttempt ?? 0,
|
|
2099
|
+
...(this.options.receiptStore
|
|
2100
|
+
? { receiptStore: this.options.receiptStore }
|
|
2101
|
+
: {}),
|
|
2102
|
+
budgetMeter: this.options.budgetMeter,
|
|
2103
|
+
heartbeatIntervalMs:
|
|
2104
|
+
this.options.runtimeReceiptHeartbeatIntervalMs,
|
|
2105
|
+
targets: claimed.receiptKey
|
|
2106
|
+
? [
|
|
2107
|
+
{
|
|
2108
|
+
key: claimed.receiptKey,
|
|
2109
|
+
leaseId: claimed.receiptLeaseId,
|
|
2110
|
+
},
|
|
2111
|
+
]
|
|
2112
|
+
: [],
|
|
2113
|
+
execute: () =>
|
|
2114
|
+
this.options.executeTool({
|
|
2115
|
+
id: request.id,
|
|
2116
|
+
toolId,
|
|
2117
|
+
input: request.input,
|
|
2118
|
+
workflowStep: request.workflowStep,
|
|
2119
|
+
callbacks: this.options.callbacks,
|
|
2120
|
+
onProviderBackpressure: (retryAfterMs) =>
|
|
2121
|
+
this.reportBackpressure(toolId, retryAfterMs),
|
|
2122
|
+
onRetryAttempt: () =>
|
|
2123
|
+
this.options.governor.chargeBudget('retry'),
|
|
2124
|
+
transientHttpRetrySafe:
|
|
2125
|
+
toolContract?.retrySafeTransientHttp === true,
|
|
2126
|
+
...(durableReceiptKey
|
|
2127
|
+
? {
|
|
2128
|
+
durableCallReceiptKey: durableReceiptKey,
|
|
2129
|
+
executionAuthScopeDigest:
|
|
2130
|
+
request.executionAuthScopeDigest,
|
|
2131
|
+
providerIdempotencyKey:
|
|
2132
|
+
providerIdempotencyKeyForClaimed({
|
|
2133
|
+
req: this.options.req,
|
|
2134
|
+
claimed,
|
|
2135
|
+
}),
|
|
2136
|
+
}
|
|
2137
|
+
: {}),
|
|
2138
|
+
runtimeTimeoutMs: resolveWorkerToolRuntimeTimeoutMs(
|
|
2139
|
+
[claimed],
|
|
2140
|
+
{
|
|
2141
|
+
resolveOwnerTimeoutMs: (request) =>
|
|
2142
|
+
request.runtimeTimeoutMs,
|
|
2143
|
+
},
|
|
2144
|
+
),
|
|
2145
|
+
directOptions: request.directOptions,
|
|
2146
|
+
}),
|
|
2053
2147
|
});
|
|
2054
2148
|
} catch (error) {
|
|
2149
|
+
if (error instanceof RuntimeLeaseLostError) {
|
|
2150
|
+
this.rejectRequests(claimed, error);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (
|
|
2154
|
+
await this.requeueAfterAuthScopeChanged(
|
|
2155
|
+
[request, ...claimed.followers],
|
|
2156
|
+
error,
|
|
2157
|
+
)
|
|
2158
|
+
) {
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2055
2161
|
this.rejectRequests(
|
|
2056
2162
|
claimed,
|
|
2057
2163
|
await this.failureForRejectedToolRequest(claimed, error),
|
|
@@ -2059,108 +2165,17 @@ export class WorkerToolBatchScheduler {
|
|
|
2059
2165
|
return;
|
|
2060
2166
|
}
|
|
2061
2167
|
try {
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
await this.
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
Math.max(
|
|
2069
|
-
0,
|
|
2070
|
-
WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL - 1,
|
|
2071
|
-
),
|
|
2072
|
-
);
|
|
2073
|
-
const durableReceiptKey = claimed.receiptKey
|
|
2074
|
-
? durableCallReceiptKeyForClaimed({
|
|
2075
|
-
req: this.options.req,
|
|
2076
|
-
claimed,
|
|
2077
|
-
})
|
|
2078
|
-
: null;
|
|
2079
|
-
result = await executeWithWorkerReceiptHeartbeats({
|
|
2080
|
-
playName: this.options.req.playName,
|
|
2081
|
-
runId: this.options.req.runId,
|
|
2082
|
-
runAttempt: this.options.req.runAttempt ?? 0,
|
|
2083
|
-
...(this.options.receiptStore
|
|
2084
|
-
? { receiptStore: this.options.receiptStore }
|
|
2085
|
-
: {}),
|
|
2086
|
-
budgetMeter: this.options.budgetMeter,
|
|
2087
|
-
heartbeatIntervalMs:
|
|
2088
|
-
this.options.runtimeReceiptHeartbeatIntervalMs,
|
|
2089
|
-
targets: claimed.receiptKey
|
|
2090
|
-
? [
|
|
2091
|
-
{
|
|
2092
|
-
key: claimed.receiptKey,
|
|
2093
|
-
leaseId: claimed.receiptLeaseId,
|
|
2094
|
-
},
|
|
2095
|
-
]
|
|
2096
|
-
: [],
|
|
2097
|
-
execute: () =>
|
|
2098
|
-
this.options.executeTool({
|
|
2099
|
-
id: request.id,
|
|
2100
|
-
toolId,
|
|
2101
|
-
input: request.input,
|
|
2102
|
-
workflowStep: request.workflowStep,
|
|
2103
|
-
callbacks: this.options.callbacks,
|
|
2104
|
-
onProviderBackpressure: (retryAfterMs) =>
|
|
2105
|
-
this.reportBackpressure(toolId, retryAfterMs),
|
|
2106
|
-
onRetryAttempt: () =>
|
|
2107
|
-
this.options.governor.chargeBudget('retry'),
|
|
2108
|
-
transientHttpRetrySafe:
|
|
2109
|
-
toolContract?.retrySafeTransientHttp === true,
|
|
2110
|
-
...(durableReceiptKey
|
|
2111
|
-
? {
|
|
2112
|
-
durableCallReceiptKey: durableReceiptKey,
|
|
2113
|
-
executionAuthScopeDigest:
|
|
2114
|
-
request.executionAuthScopeDigest,
|
|
2115
|
-
providerIdempotencyKey:
|
|
2116
|
-
providerIdempotencyKeyForClaimed({
|
|
2117
|
-
req: this.options.req,
|
|
2118
|
-
claimed,
|
|
2119
|
-
}),
|
|
2120
|
-
}
|
|
2121
|
-
: {}),
|
|
2122
|
-
runtimeTimeoutMs: resolveWorkerToolRuntimeTimeoutMs(
|
|
2123
|
-
[claimed],
|
|
2124
|
-
{
|
|
2125
|
-
resolveOwnerTimeoutMs: (request) =>
|
|
2126
|
-
request.runtimeTimeoutMs,
|
|
2127
|
-
},
|
|
2128
|
-
),
|
|
2129
|
-
directOptions: request.directOptions,
|
|
2130
|
-
}),
|
|
2131
|
-
});
|
|
2132
|
-
} catch (error) {
|
|
2133
|
-
if (error instanceof RuntimeLeaseLostError) {
|
|
2134
|
-
this.rejectRequests(claimed, error);
|
|
2135
|
-
return;
|
|
2136
|
-
}
|
|
2137
|
-
if (
|
|
2138
|
-
await this.requeueAfterAuthScopeChanged(
|
|
2139
|
-
[request, ...claimed.followers],
|
|
2140
|
-
error,
|
|
2141
|
-
)
|
|
2142
|
-
) {
|
|
2143
|
-
return;
|
|
2144
|
-
}
|
|
2145
|
-
this.rejectRequests(
|
|
2146
|
-
claimed,
|
|
2147
|
-
await this.failureForRejectedToolRequest(claimed, error),
|
|
2148
|
-
);
|
|
2149
|
-
return;
|
|
2150
|
-
}
|
|
2151
|
-
try {
|
|
2152
|
-
this.settleRequests(
|
|
2153
|
-
claimed,
|
|
2154
|
-
await this.completeDurableToolRequest(claimed, result),
|
|
2155
|
-
);
|
|
2156
|
-
} catch (receiptError) {
|
|
2157
|
-
this.rejectRequests(claimed, receiptError);
|
|
2158
|
-
}
|
|
2159
|
-
} finally {
|
|
2160
|
-
slot.release();
|
|
2168
|
+
this.settleRequests(
|
|
2169
|
+
claimed,
|
|
2170
|
+
await this.completeDurableToolRequest(claimed, result),
|
|
2171
|
+
);
|
|
2172
|
+
} catch (receiptError) {
|
|
2173
|
+
this.rejectRequests(claimed, receiptError);
|
|
2161
2174
|
}
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2175
|
+
} finally {
|
|
2176
|
+
slot.release();
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2164
2179
|
this.options.recordPerfTrace?.({
|
|
2165
2180
|
phase: 'runner.tool.group',
|
|
2166
2181
|
ms: this.options.nowMs() - groupStartedAt,
|
|
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
|
|
|
106
106
|
// 0.1.154 removes the short-lived generated enrich StepOptions recompute
|
|
107
107
|
// fields shipped in 0.1.153.
|
|
108
108
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
109
|
-
version: '0.1.
|
|
109
|
+
version: '0.1.203',
|
|
110
110
|
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
111
111
|
supportPolicy: {
|
|
112
|
-
latest: '0.1.
|
|
112
|
+
latest: '0.1.203',
|
|
113
113
|
minimumSupported: '0.1.53',
|
|
114
114
|
deprecatedBelow: '0.1.53',
|
|
115
115
|
commandMinimumSupported: [
|
|
@@ -29,6 +29,8 @@ import type { PlayBundleArtifact } from '@shared_libs/plays/artifact-types';
|
|
|
29
29
|
import type {
|
|
30
30
|
PlayRunnerRateStateAcquireInput,
|
|
31
31
|
PlayRunnerRateStateAcquireResult,
|
|
32
|
+
PlayRunnerBudgetChargeInput,
|
|
33
|
+
PlayRunnerBudgetChargeResult,
|
|
32
34
|
PlayRunnerRateStatePenalizeInput,
|
|
33
35
|
PlayRunnerRateStateReleaseInput,
|
|
34
36
|
} from '@shared_libs/play-runtime/protocol';
|
|
@@ -176,6 +178,9 @@ type RuntimeApiRequest =
|
|
|
176
178
|
| ({
|
|
177
179
|
action: 'rate_state_penalize';
|
|
178
180
|
} & PlayRunnerRateStatePenalizeInput)
|
|
181
|
+
| ({
|
|
182
|
+
action: 'governor_budget_charge';
|
|
183
|
+
} & PlayRunnerBudgetChargeInput)
|
|
179
184
|
| {
|
|
180
185
|
action: 'get_runtime_step_receipt';
|
|
181
186
|
playName: string;
|
|
@@ -426,8 +431,8 @@ function summarizeAppRuntimeErrorBody(body: string): string {
|
|
|
426
431
|
return trimmed.length > 500 ? `${trimmed.slice(0, 500)}...` : trimmed;
|
|
427
432
|
}
|
|
428
433
|
|
|
429
|
-
function isTransientAppRuntimeFailureBody(body: string): boolean {
|
|
430
|
-
return /WorkerOverloaded|"\s*code\s*"\s*:\s*"\s*InternalServerError\s*"|Your request couldn't be completed\. Try again later\.|timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|tuple concurrently updated/i.test(
|
|
434
|
+
export function isTransientAppRuntimeFailureBody(body: string): boolean {
|
|
435
|
+
return /WorkerOverloaded|"\s*code\s*"\s*:\s*"\s*InternalServerError\s*"|Your request couldn't be completed\. Try again later\.|timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|tuple concurrently updated|RUNTIME_SCHEDULER_SATURATED|Runtime scheduler DB pool is saturated|Runtime scheduler DB circuit breaker is open/i.test(
|
|
431
436
|
body,
|
|
432
437
|
);
|
|
433
438
|
}
|
|
@@ -751,6 +756,16 @@ export async function penalizeRateStateViaAppRuntime(
|
|
|
751
756
|
});
|
|
752
757
|
}
|
|
753
758
|
|
|
759
|
+
export async function chargeGovernorBudgetViaAppRuntime(
|
|
760
|
+
context: WorkerRuntimeApiContext,
|
|
761
|
+
input: PlayRunnerBudgetChargeInput,
|
|
762
|
+
): Promise<PlayRunnerBudgetChargeResult> {
|
|
763
|
+
return await postAppRuntimeApi<PlayRunnerBudgetChargeResult>(context, {
|
|
764
|
+
action: 'governor_budget_charge',
|
|
765
|
+
...input,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
754
769
|
export async function writeStagedFileFromAppRuntime(
|
|
755
770
|
context: WorkerRuntimeApiContext,
|
|
756
771
|
file: Pick<PlayStagedFileRef, 'storageKey'>,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type BoundedDispatchResult<TJob, TResult> = {
|
|
2
|
+
job: TJob;
|
|
3
|
+
index: number;
|
|
4
|
+
result: TResult | null;
|
|
5
|
+
error?: unknown;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/** Drain a finite queue with a fixed number of workers. Pacing and adaptive
|
|
9
|
+
* admission belong inside `execute`, at the RuntimeResourceGovernor seam. */
|
|
10
|
+
export async function dispatchBoundedSettled<TJob, TResult>(
|
|
11
|
+
jobs: readonly TJob[],
|
|
12
|
+
concurrency: number,
|
|
13
|
+
execute: (job: TJob, index: number) => Promise<TResult>,
|
|
14
|
+
): Promise<Array<BoundedDispatchResult<TJob, TResult>>> {
|
|
15
|
+
if (jobs.length === 0) return [];
|
|
16
|
+
const width = Math.min(
|
|
17
|
+
jobs.length,
|
|
18
|
+
Math.max(1, Math.floor(Number.isFinite(concurrency) ? concurrency : 1)),
|
|
19
|
+
);
|
|
20
|
+
const results = new Array<BoundedDispatchResult<TJob, TResult>>(jobs.length);
|
|
21
|
+
let cursor = 0;
|
|
22
|
+
const workers = Array.from({ length: width }, async () => {
|
|
23
|
+
while (true) {
|
|
24
|
+
const index = cursor;
|
|
25
|
+
cursor += 1;
|
|
26
|
+
if (index >= jobs.length) return;
|
|
27
|
+
const job = jobs[index]!;
|
|
28
|
+
try {
|
|
29
|
+
results[index] = {
|
|
30
|
+
job,
|
|
31
|
+
index,
|
|
32
|
+
result: await execute(job, index),
|
|
33
|
+
};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
results[index] = { job, index, result: null, error };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
await Promise.all(workers);
|
|
40
|
+
return results;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function dispatchBounded<TJob, TResult>(
|
|
44
|
+
jobs: readonly TJob[],
|
|
45
|
+
concurrency: number,
|
|
46
|
+
execute: (job: TJob, index: number) => Promise<TResult>,
|
|
47
|
+
): Promise<TResult[]> {
|
|
48
|
+
const settled = await dispatchBoundedSettled(jobs, concurrency, execute);
|
|
49
|
+
const failed = settled.find((entry) => entry.error !== undefined);
|
|
50
|
+
if (failed) throw failed.error;
|
|
51
|
+
return settled.map((entry) => entry.result as TResult);
|
|
52
|
+
}
|