deepline 0.3.322 → 0.3.324
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/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +0 -18
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +454 -129
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +407 -37
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +18 -4
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +16 -1
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +6 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-http-request.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +15 -5
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +7 -2
- package/dist/bundling-sources/shared_libs/play-runtime/provider-row-retry-queue.ts +65 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +34 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +116 -15
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-scope.ts +59 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +294 -37
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +73 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +41 -3
- package/dist/cli/{auth-VSXORUWX.mjs → auth-6KWNR6AI.mjs} +4 -4
- package/dist/cli/auth-LKSTC3YV.js +31 -0
- package/dist/cli/{billing-Q6WO5E2R.js → billing-CLLWHIAT.js} +70 -70
- package/dist/cli/{billing-EH74RDBF.mjs → billing-VQ2NUJLB.mjs} +4 -4
- package/dist/cli/{chunk-M4TZIO5U.js → chunk-2UQUOUAW.js} +2 -2
- package/dist/cli/{chunk-JWEYKLOV.mjs → chunk-3ZKAKQYC.mjs} +1 -1
- package/dist/cli/{chunk-6JV2VSQN.mjs → chunk-5CQ554BM.mjs} +1 -1
- package/dist/cli/{chunk-Z5TQC2ID.mjs → chunk-5QZFWY7C.mjs} +2 -2
- package/dist/cli/{chunk-A3THNBGB.mjs → chunk-AKM5JVPG.mjs} +2 -2
- package/dist/cli/{chunk-U6N6MS6N.js → chunk-D5YJHGST.js} +4 -4
- package/dist/cli/{chunk-SISYT2DM.mjs → chunk-DO2VA5UP.mjs} +2 -2
- package/dist/cli/{chunk-H52C7AHC.mjs → chunk-EHSI2DMO.mjs} +4 -4
- package/dist/cli/{chunk-CNO37AI3.js → chunk-EXKRHZRG.js} +220 -220
- package/dist/cli/{chunk-JCZX5ZQW.js → chunk-G4F2AIQ5.js} +6 -6
- package/dist/cli/{chunk-GAFGAIVW.js → chunk-GMO3NBE2.js} +60 -60
- package/dist/cli/{chunk-4F4XU2Z5.js → chunk-GROXVGGW.js} +6 -6
- package/dist/cli/{chunk-I7UUOAZN.mjs → chunk-HHZDWBSQ.mjs} +1 -1
- package/dist/cli/{chunk-ZHIPP7YD.mjs → chunk-I7RLL7LN.mjs} +1 -1
- package/dist/cli/{chunk-PTOQKD2Y.js → chunk-IXYR65PW.js} +1 -1
- package/dist/cli/{chunk-LUAF3MIC.mjs → chunk-LHJKYMTS.mjs} +1 -1
- package/dist/cli/{chunk-5RPFAX7O.js → chunk-N5E66DP3.js} +6 -6
- package/dist/cli/{chunk-CMRWPMZS.js → chunk-NC2ZXG2Y.js} +26 -26
- package/dist/cli/{chunk-LUTXK4ON.mjs → chunk-NIOFMRYW.mjs} +5 -5
- package/dist/cli/{chunk-PMTJYMDJ.mjs → chunk-NQJQ3CLX.mjs} +3 -3
- package/dist/cli/{chunk-KAUABQOG.js → chunk-PAA4FYCF.js} +15 -15
- package/dist/cli/{chunk-QXIPQYZZ.js → chunk-QM2Z4FQF.js} +6 -6
- package/dist/cli/{chunk-WARD6DBW.mjs → chunk-TJGJTB5T.mjs} +1 -1
- package/dist/cli/{chunk-KHRWZU6B.js → chunk-TZZNI2A4.js} +50 -50
- package/dist/cli/{chunk-6PE2VHSG.mjs → chunk-U2RPP53P.mjs} +6 -6
- package/dist/cli/{chunk-Y3LPZJFE.js → chunk-V5PA6TCT.js} +48 -48
- package/dist/cli/{client-XRO4X5XF.mjs → client-VHUKTDAZ.mjs} +3 -3
- package/dist/cli/client-WMSTRUGP.js +20 -0
- package/dist/cli/{csv-ALZ44QHM.js → csv-5MZY7HU5.js} +12 -12
- package/dist/cli/{csv-5B6JDXCN.mjs → csv-7DA4APB6.mjs} +3 -3
- package/dist/cli/{db-HJECAUNU.mjs → db-7UPW3N5U.mjs} +4 -4
- package/dist/cli/{db-D6UHGVOU.js → db-ICG3FUVE.js} +20 -20
- package/dist/cli/{describe-YAN46NBC.js → describe-VDRY2JCE.js} +15 -15
- package/dist/cli/{describe-FBAWTMDS.mjs → describe-ZAPGM5ND.mjs} +8 -8
- package/dist/cli/{executions-XSV7ZE4N.js → executions-D5GYCY3A.js} +6 -6
- package/dist/cli/{executions-LWHMAUHH.mjs → executions-TK3C2FAQ.mjs} +4 -4
- package/dist/cli/{feedback-RKT6F6W6.js → feedback-PZAZZUXF.js} +6 -6
- package/dist/cli/{feedback-EPXMI55U.mjs → feedback-T4EIP5QR.mjs} +3 -3
- package/dist/cli/git-73N6EUZ3.js +23 -0
- package/dist/cli/{git-BOXANJBI.mjs → git-WBFWIVO5.mjs} +4 -4
- package/dist/cli/index.js +75 -75
- package/dist/cli/index.mjs +31 -31
- package/dist/cli/{legacy-noops-VV7DRGA7.mjs → legacy-noops-DUCK7OKN.mjs} +4 -4
- package/dist/cli/{legacy-noops-7EBEYUQZ.js → legacy-noops-LRBJWXRK.js} +7 -7
- package/dist/cli/{monitors-RDRG3E4Y.js → monitors-7S2SL62G.js} +85 -85
- package/dist/cli/{monitors-NJPHRFYT.mjs → monitors-WYH5AQIO.mjs} +4 -4
- package/dist/cli/{org-ZFWDBQ7R.js → org-EQNGGHPV.js} +60 -60
- package/dist/cli/{org-USE3QRY6.mjs → org-XCSMLTUW.mjs} +4 -4
- package/dist/cli/{play-3VEIZZJ5.mjs → play-A5ISFBJM.mjs} +7 -7
- package/dist/cli/play-CLC2Z5SO.js +152 -0
- package/dist/cli/{providers-SXWPIPKO.js → providers-DR2CLD3N.js} +7 -7
- package/dist/cli/{providers-PP4L5HLM.mjs → providers-KHTZFQ5I.mjs} +4 -4
- package/dist/cli/{quickstart-QCSPS2D5.mjs → quickstart-KT4LKORA.mjs} +3 -3
- package/dist/cli/{quickstart-WVLEQQXO.js → quickstart-NIRWI6YU.js} +5 -5
- package/dist/cli/search-DM7IQS5F.js +26 -0
- package/dist/cli/{search-LTC6R6IS.mjs → search-WCEXPBAT.mjs} +5 -5
- package/dist/cli/{secrets-R5FGK6UC.js → secrets-QSFEZGVE.js} +13 -13
- package/dist/cli/{secrets-LV4FJNAL.mjs → secrets-TN7DW3QR.mjs} +4 -4
- package/dist/cli/{sessions-N6CMP6XZ.js → sessions-LJS6W67T.js} +5 -5
- package/dist/cli/{sessions-CFNRSRYX.mjs → sessions-WSE5FN4H.mjs} +4 -4
- package/dist/cli/{settings-RNEJQGBL.js → settings-FGS3VYJN.js} +74 -74
- package/dist/cli/{settings-ADEYLHV7.mjs → settings-Z3V53A6J.mjs} +7 -7
- package/dist/cli/{setup-YLEOL7E7.mjs → setup-GMH4VJS3.mjs} +6 -6
- package/dist/cli/{setup-NJCUXN4H.js → setup-ZA234HEY.js} +41 -41
- package/dist/cli/skills-6APAQV6W.js +28 -0
- package/dist/cli/{skills-WABXOGDA.mjs → skills-7G2RZMYH.mjs} +4 -4
- package/dist/cli/{tools-XQCGEX5J.mjs → tools-7EE4BGXO.mjs} +6 -6
- package/dist/cli/tools-EPYUFWPB.js +38 -0
- package/dist/cli/update-FPVX3XJC.js +28 -0
- package/dist/cli/{update-3W2NBTST.mjs → update-O5QEWS7K.mjs} +6 -6
- package/dist/cli/{workflow-GKFGFQ4D.mjs → workflow-KE54RUTR.mjs} +5 -5
- package/dist/cli/{workflow-NUVM7LQR.js → workflow-M5PAAW3O.js} +15 -15
- package/dist/index.js +6 -6
- package/dist/index.mjs +3 -3
- package/dist/install-integrity.json +75 -74
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli/auth-MKFIE6KN.js +0 -31
- package/dist/cli/client-NITVYMZK.js +0 -20
- package/dist/cli/git-B3L74RFF.js +0 -23
- package/dist/cli/play-EONETVYE.js +0 -152
- package/dist/cli/search-XK2KJXQG.js +0 -26
- package/dist/cli/skills-VNAPDBFA.js +0 -28
- package/dist/cli/tools-XF5EQO4O.js +0 -38
- package/dist/cli/update-3TQMEJO5.js +0 -28
|
@@ -211,6 +211,11 @@ import {
|
|
|
211
211
|
TOOL_EXECUTE_RUNNER_ACCEPTS_VALUE,
|
|
212
212
|
TOOL_EXECUTE_PROVIDER_RATE_SCOPE_HEADER,
|
|
213
213
|
TOOL_EXECUTE_PROVIDER_RATE_RETRY_SNAPSHOT_HEADER,
|
|
214
|
+
TOOL_EXECUTE_PROVIDER_PERMIT_PATH_SUFFIX,
|
|
215
|
+
TOOL_EXECUTE_PROVIDER_PERMIT_RECEIVER_HEADER,
|
|
216
|
+
isProviderPermitExecuteReceiverUnavailable,
|
|
217
|
+
TOOL_EXECUTE_APP_PACING_REFUSAL_HEADER,
|
|
218
|
+
TOOL_EXECUTE_APP_PACING_REFUSAL_VALUE,
|
|
214
219
|
isAttestedProvider429Response,
|
|
215
220
|
classifyToolExecuteHttpFailure,
|
|
216
221
|
createToolExecuteHttpFailureAttemptTracker,
|
|
@@ -362,6 +367,8 @@ import {
|
|
|
362
367
|
type RuntimeMapHeapAdmissionSnapshot,
|
|
363
368
|
} from './map-memory-limits';
|
|
364
369
|
import { RuntimeMapRowFeed } from './runtime-map-row-feed';
|
|
370
|
+
import { ProviderRowRetryQueue } from './provider-row-retry-queue';
|
|
371
|
+
import type { ProviderRowRetry } from './provider-row-retry-queue';
|
|
365
372
|
import type { RuntimeMapRowFeedSnapshot } from './runtime-map-row-feed';
|
|
366
373
|
import { createRuntimeDispatchFlow, createRuntimeFlow } from './runtime-flow';
|
|
367
374
|
import {
|
|
@@ -523,8 +530,62 @@ const rowContext = new AsyncLocalStorage<{
|
|
|
523
530
|
resolverGap?: RowResolverGapTracker;
|
|
524
531
|
/** Shared through nested fields, reset for each row execution attempt. */
|
|
525
532
|
providerBackpressure?: { pending: PlayRowProviderBackpressureError | null };
|
|
533
|
+
replayCallIndexes?: RowReplayCallIndexes;
|
|
534
|
+
replayStepCaptures?: Map<string, number>[];
|
|
526
535
|
}>();
|
|
527
536
|
|
|
537
|
+
/** Re-enter a held row with the call positions from its original attempt. */
|
|
538
|
+
class RowReplayCallIndexes {
|
|
539
|
+
private readonly recorded = new Map<string, number[]>();
|
|
540
|
+
private readonly positions = new Map<string, number>();
|
|
541
|
+
private readonly completedSteps = new Map<string, Map<string, number>>();
|
|
542
|
+
|
|
543
|
+
resetAttempt(): void {
|
|
544
|
+
this.positions.clear();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
next(key: string, freshIndex: number): number {
|
|
548
|
+
const position = this.positions.get(key) ?? 0;
|
|
549
|
+
this.positions.set(key, position + 1);
|
|
550
|
+
let recorded = this.recorded.get(key);
|
|
551
|
+
if (!recorded) {
|
|
552
|
+
recorded = [];
|
|
553
|
+
this.recorded.set(key, recorded);
|
|
554
|
+
}
|
|
555
|
+
if (recorded[position] === undefined) recorded[position] = freshIndex;
|
|
556
|
+
return recorded[position]!;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
completeStep(boundaryId: string, advances: Map<string, number>): void {
|
|
560
|
+
this.completedSteps.set(boundaryId, advances);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
recoverStep(boundaryId: string): boolean {
|
|
564
|
+
// A cached step does not run its body. Consume its recorded nested calls
|
|
565
|
+
// so the next sibling cannot inherit one of their invocation positions.
|
|
566
|
+
const advances = this.completedSteps.get(boundaryId);
|
|
567
|
+
if (!advances) return false;
|
|
568
|
+
for (const [key, advance] of advances) {
|
|
569
|
+
this.positions.set(key, (this.positions.get(key) ?? 0) + advance);
|
|
570
|
+
recordReplayCallAdvance(key, advance);
|
|
571
|
+
}
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function recordReplayCallAdvance(key: string, advance: number): void {
|
|
577
|
+
for (const capture of rowContext.getStore()?.replayStepCaptures ?? []) {
|
|
578
|
+
capture.set(key, (capture.get(key) ?? 0) + advance);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function nextReplayableCallIndex(key: string, freshIndex: number): number {
|
|
583
|
+
const indexes = rowContext.getStore()?.replayCallIndexes;
|
|
584
|
+
if (!indexes) return freshIndex;
|
|
585
|
+
recordReplayCallAdvance(key, 1);
|
|
586
|
+
return indexes.next(key, freshIndex);
|
|
587
|
+
}
|
|
588
|
+
|
|
528
589
|
/**
|
|
529
590
|
* The open end of one `rowResolverGap` segment.
|
|
530
591
|
*
|
|
@@ -2714,6 +2775,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2714
2775
|
private toolDispatcherFailure: unknown | null = null;
|
|
2715
2776
|
/** Active provider HTTP attempts in this Play execution, grouped by provider. */
|
|
2716
2777
|
private readonly inFlightProviderRequests = new Map<string, number>();
|
|
2778
|
+
/** A row replay must not restart this call's spent live-sandbox wait budget. */
|
|
2779
|
+
private readonly exhaustedProviderResidentWaitCalls = new Set<string>();
|
|
2717
2780
|
/**
|
|
2718
2781
|
* Deepline's own zero-credit denial is run-fatal. Keep it independently of
|
|
2719
2782
|
* customer control flow so a broad `try/catch` cannot turn it into a
|
|
@@ -9260,12 +9323,18 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9260
9323
|
// aliases ride on non-enumerable props after stripCsvProjectionMetadata,
|
|
9261
9324
|
// so a spread would silently drop them before the sheet write and the
|
|
9262
9325
|
// aliases would never persist as visible input cells.
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9326
|
+
// A resumed checkpoint contains only pending rows. Preserve their
|
|
9327
|
+
// original indexes through admission instead of renumbering the subset.
|
|
9328
|
+
const mapStartRows = rawItems.map((row, index) => {
|
|
9329
|
+
const inputIndex = itemOriginalIndexes[index] ?? index;
|
|
9330
|
+
return {
|
|
9331
|
+
...toSerializableCsvAliasedRow(row),
|
|
9332
|
+
...mapRowOutcomeRuntimeFields({
|
|
9333
|
+
key: seedRowIdentity(row, inputIndex),
|
|
9334
|
+
inputIndex,
|
|
9335
|
+
}),
|
|
9336
|
+
};
|
|
9337
|
+
});
|
|
9269
9338
|
const mapStartResult = await this.#options.onMapStart(
|
|
9270
9339
|
mapStartRows,
|
|
9271
9340
|
resolvedTableNamespace,
|
|
@@ -9303,7 +9372,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9303
9372
|
const seededItems = materializedItems
|
|
9304
9373
|
.map((item, index) => ({
|
|
9305
9374
|
row: this.toOutputRow(item as Record<string, unknown>),
|
|
9306
|
-
index,
|
|
9375
|
+
index: originalInputIndexByItem.get(item) ?? index,
|
|
9307
9376
|
}))
|
|
9308
9377
|
.map(({ row, index }) => {
|
|
9309
9378
|
// Look up by the SAME seed identity the stamp above used — the
|
|
@@ -9332,7 +9401,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9332
9401
|
)
|
|
9333
9402
|
: row,
|
|
9334
9403
|
index,
|
|
9335
|
-
|
|
9404
|
+
// `index` already names the authored input, not a position in
|
|
9405
|
+
// the pending subset. Looking it up again aliases resumed rows.
|
|
9406
|
+
originalIndex: index,
|
|
9336
9407
|
};
|
|
9337
9408
|
});
|
|
9338
9409
|
const admittedItems =
|
|
@@ -10268,16 +10339,31 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10268
10339
|
);
|
|
10269
10340
|
let nextRowIndex = 0;
|
|
10270
10341
|
let firstRowError: unknown = null;
|
|
10271
|
-
const
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10342
|
+
const providerRowRetries = new ProviderRowRetryQueue();
|
|
10343
|
+
const parkedProviderRows = new ProviderRowRetryQueue();
|
|
10344
|
+
const nextProviderRetry = (includeParked: boolean) => {
|
|
10345
|
+
const resident = providerRowRetries.peek();
|
|
10346
|
+
const parked = includeParked ? parkedProviderRows.peek() : undefined;
|
|
10347
|
+
return !resident || (parked && parked.readyAtMs < resident.readyAtMs)
|
|
10348
|
+
? parked
|
|
10349
|
+
: resident;
|
|
10350
|
+
};
|
|
10351
|
+
const retryWakeFeed = rowFeed ?? new RuntimeMapRowFeed();
|
|
10352
|
+
const waitingBaseRows = new Map<
|
|
10353
|
+
number,
|
|
10354
|
+
{
|
|
10355
|
+
baseRow: Record<string, unknown>;
|
|
10356
|
+
callIndexes: RowReplayCallIndexes;
|
|
10357
|
+
}
|
|
10358
|
+
>();
|
|
10277
10359
|
|
|
10278
10360
|
const runRow = async (
|
|
10279
10361
|
idx: number,
|
|
10280
10362
|
baseRow: Record<string, unknown>,
|
|
10363
|
+
retryAfterRelease: {
|
|
10364
|
+
pending: ProviderRowRetry | null;
|
|
10365
|
+
callIndexes: RowReplayCallIndexes;
|
|
10366
|
+
},
|
|
10281
10367
|
): Promise<MapRowExecutionResult> => {
|
|
10282
10368
|
const item = items[idx];
|
|
10283
10369
|
const rowKey = executionRowKey(baseRow, idx);
|
|
@@ -10350,6 +10436,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10350
10436
|
mapStallResolverToken: resolverObservation?.token,
|
|
10351
10437
|
resolverGap,
|
|
10352
10438
|
providerBackpressure,
|
|
10439
|
+
replayCallIndexes: retryAfterRelease.callIndexes,
|
|
10353
10440
|
},
|
|
10354
10441
|
async () => {
|
|
10355
10442
|
try {
|
|
@@ -10559,7 +10646,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10559
10646
|
return COMPLETED_ROW;
|
|
10560
10647
|
} catch (error) {
|
|
10561
10648
|
if (isPlayRowExecutionSuspendedError(error)) {
|
|
10562
|
-
waitingBaseRows.set(idx,
|
|
10649
|
+
waitingBaseRows.set(idx, {
|
|
10650
|
+
baseRow,
|
|
10651
|
+
callIndexes: retryAfterRelease.callIndexes,
|
|
10652
|
+
});
|
|
10563
10653
|
this.pendingRowEventBoundaries.push(error.boundary);
|
|
10564
10654
|
mapProgress.update({
|
|
10565
10655
|
pendingRowKey: rowKey,
|
|
@@ -10582,14 +10672,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10582
10672
|
return WAITING_ROW;
|
|
10583
10673
|
}
|
|
10584
10674
|
if (error instanceof PlayRowProviderBackpressureError) {
|
|
10585
|
-
waitingBaseRows.set(idx,
|
|
10675
|
+
waitingBaseRows.set(idx, {
|
|
10676
|
+
baseRow,
|
|
10677
|
+
callIndexes: retryAfterRelease.callIndexes,
|
|
10678
|
+
});
|
|
10586
10679
|
// Each row retains its own deadline. Provider names are not rate
|
|
10587
10680
|
// scopes: two credentials for one provider can recover separately.
|
|
10588
|
-
|
|
10681
|
+
retryAfterRelease.pending = {
|
|
10682
|
+
rowIndex: idx,
|
|
10589
10683
|
provider: error.provider,
|
|
10590
10684
|
retryAtMs: error.retryAtMs,
|
|
10591
|
-
|
|
10592
|
-
|
|
10685
|
+
readyAtMs: Math.max(Date.now() + 1_000, error.retryAtMs),
|
|
10686
|
+
...(error.residentWaitBudgetExhausted
|
|
10687
|
+
? { residentWaitBudgetExhausted: true }
|
|
10688
|
+
: {}),
|
|
10689
|
+
};
|
|
10593
10690
|
mapProgress.update({ pendingRowKey: rowKey });
|
|
10594
10691
|
const fieldName = activeFieldName;
|
|
10595
10692
|
if (!fieldName) {
|
|
@@ -10703,7 +10800,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10703
10800
|
this.#options.rowAdmissionSignal?.reason ??
|
|
10704
10801
|
new Error('Row admission stopped.');
|
|
10705
10802
|
heapAdmission.close(firstRowError);
|
|
10706
|
-
|
|
10803
|
+
retryWakeFeed.fail(firstRowError);
|
|
10707
10804
|
};
|
|
10708
10805
|
this.#options.rowAdmissionSignal?.addEventListener('abort', stopAdmission, {
|
|
10709
10806
|
once: true,
|
|
@@ -10718,7 +10815,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10718
10815
|
if (firstRowError == null) firstRowError = cause;
|
|
10719
10816
|
this.wakeToolDispatcher();
|
|
10720
10817
|
heapAdmission.close(firstRowError);
|
|
10721
|
-
|
|
10818
|
+
retryWakeFeed.fail(firstRowError);
|
|
10722
10819
|
};
|
|
10723
10820
|
const unsubscribePersistenceLatch = onRuntimePersistenceLatchTripped(
|
|
10724
10821
|
this.persistenceLatch,
|
|
@@ -10736,29 +10833,109 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10736
10833
|
PROGRESS_HEARTBEAT_INTERVAL_MS,
|
|
10737
10834
|
);
|
|
10738
10835
|
mapActivityTimer.unref();
|
|
10836
|
+
// Short holds should reuse the live sandbox after every row returns its
|
|
10837
|
+
// leases. Bound total idle residency from the first all-waiting transition,
|
|
10838
|
+
// rather than resetting it for each successive provider response.
|
|
10839
|
+
let providerIdleWaitStartedAt: number | null = null;
|
|
10840
|
+
const canWaitForProviderRetry = (retry: ProviderRowRetry) => {
|
|
10841
|
+
const elapsedMs =
|
|
10842
|
+
providerIdleWaitStartedAt === null
|
|
10843
|
+
? 0
|
|
10844
|
+
: performance.now() - providerIdleWaitStartedAt;
|
|
10845
|
+
return (
|
|
10846
|
+
Math.max(1, retry.readyAtMs - Date.now()) <=
|
|
10847
|
+
Math.max(0, TOOL_PROVIDER_RESIDENT_WAIT_BUDGET_MS - elapsedMs)
|
|
10848
|
+
);
|
|
10849
|
+
};
|
|
10739
10850
|
const workers = Array.from({ length: workerCount }, async () => {
|
|
10740
10851
|
while (firstRowError == null) {
|
|
10741
|
-
|
|
10852
|
+
// A short provider hold must not leave this row behind the entire
|
|
10853
|
+
// fresh backlog. Its previous flow released both admission leases;
|
|
10854
|
+
// take a new one after its own deadline while other rows make progress.
|
|
10855
|
+
const nextReadyRetry = nextProviderRetry(true);
|
|
10856
|
+
const providerRetry = nextReadyRetry?.residentWaitBudgetExhausted
|
|
10857
|
+
? parkedProviderRows.takeReady(Date.now())
|
|
10858
|
+
: providerRowRetries.takeReady(Date.now());
|
|
10859
|
+
if (!providerRetry && nextRowIndex >= items.length) {
|
|
10860
|
+
// Spent calls may join productive work, but only an unspent call can
|
|
10861
|
+
// keep an otherwise idle sandbox waiting for its next deadline.
|
|
10862
|
+
const nextRetry = nextProviderRetry(mapProgress.activeResolvers > 0);
|
|
10863
|
+
if (
|
|
10864
|
+
nextRetry &&
|
|
10865
|
+
(mapProgress.activeResolvers > 0 ||
|
|
10866
|
+
canWaitForProviderRetry(nextRetry)) &&
|
|
10867
|
+
(!rowFeed || rowFeed.closed)
|
|
10868
|
+
) {
|
|
10869
|
+
if (
|
|
10870
|
+
mapProgress.activeResolvers === 0 &&
|
|
10871
|
+
providerIdleWaitStartedAt === null
|
|
10872
|
+
)
|
|
10873
|
+
providerIdleWaitStartedAt = performance.now();
|
|
10874
|
+
await retryWakeFeed.waitForWorkerWake(nextRetry.readyAtMs);
|
|
10875
|
+
continue;
|
|
10876
|
+
}
|
|
10742
10877
|
if (!rowFeed) return;
|
|
10878
|
+
if (nextRetry) rowFeed.wakeWorkersAt(nextRetry.readyAtMs);
|
|
10743
10879
|
const more = await rowFeed.waitForRows(nextRowIndex);
|
|
10744
|
-
|
|
10880
|
+
const pendingRetry = nextProviderRetry(
|
|
10881
|
+
mapProgress.activeResolvers > 0,
|
|
10882
|
+
);
|
|
10883
|
+
if (
|
|
10884
|
+
!more &&
|
|
10885
|
+
!(
|
|
10886
|
+
pendingRetry &&
|
|
10887
|
+
(mapProgress.activeResolvers > 0 ||
|
|
10888
|
+
canWaitForProviderRetry(pendingRetry))
|
|
10889
|
+
)
|
|
10890
|
+
)
|
|
10891
|
+
return;
|
|
10745
10892
|
continue;
|
|
10746
10893
|
}
|
|
10747
|
-
const idx = nextRowIndex
|
|
10748
|
-
|
|
10749
|
-
rowFeed?.markDispatched();
|
|
10894
|
+
const idx = providerRetry?.rowIndex ?? nextRowIndex++;
|
|
10895
|
+
if (!providerRetry) rowFeed?.markDispatched();
|
|
10750
10896
|
try {
|
|
10751
|
-
const
|
|
10752
|
-
|
|
10897
|
+
const waitingRow = providerRetry
|
|
10898
|
+
? waitingBaseRows.get(idx)
|
|
10899
|
+
: undefined;
|
|
10900
|
+
const baseRow = providerRetry
|
|
10901
|
+
? waitingRow?.baseRow
|
|
10902
|
+
: this.toOutputRow(items[idx]);
|
|
10903
|
+
if (!baseRow) {
|
|
10904
|
+
throw new Error(
|
|
10905
|
+
`Provider retry row ${idx} lost its resumable input.`,
|
|
10906
|
+
);
|
|
10907
|
+
}
|
|
10908
|
+
if (providerRetry) waitingBaseRows.delete(idx);
|
|
10909
|
+
else releaseDispatchedInputRow?.(idx);
|
|
10910
|
+
const callIndexes =
|
|
10911
|
+
waitingRow?.callIndexes ?? new RowReplayCallIndexes();
|
|
10912
|
+
callIndexes.resetAttempt();
|
|
10913
|
+
const retryAfterRelease = {
|
|
10914
|
+
pending: null as ProviderRowRetry | null,
|
|
10915
|
+
callIndexes,
|
|
10916
|
+
};
|
|
10753
10917
|
settledResults[idx] = await mapRowFlow.run({ baseRow }, async () => {
|
|
10754
10918
|
this.assertRowAdmissionOpen();
|
|
10755
|
-
mapProgress.resolverStarted();
|
|
10919
|
+
mapProgress.resolverStarted({ retry: Boolean(providerRetry) });
|
|
10756
10920
|
try {
|
|
10757
|
-
return await runRow(idx, baseRow);
|
|
10921
|
+
return await runRow(idx, baseRow, retryAfterRelease);
|
|
10758
10922
|
} finally {
|
|
10759
10923
|
mapProgress.resolverFinished();
|
|
10760
10924
|
}
|
|
10761
10925
|
});
|
|
10926
|
+
// Publish only after both flow leases have returned. Another worker
|
|
10927
|
+
// must never re-admit this index while the old attempt is unwinding.
|
|
10928
|
+
if (retryAfterRelease.pending) {
|
|
10929
|
+
if (retryAfterRelease.pending.residentWaitBudgetExhausted) {
|
|
10930
|
+
parkedProviderRows.add(retryAfterRelease.pending);
|
|
10931
|
+
} else {
|
|
10932
|
+
providerRowRetries.add(retryAfterRelease.pending);
|
|
10933
|
+
}
|
|
10934
|
+
rowFeed?.wakeWorkersAt(nextProviderRetry(true)!.readyAtMs);
|
|
10935
|
+
} else if (settledResults[idx] !== WAITING_ROW) {
|
|
10936
|
+
providerIdleWaitStartedAt = null;
|
|
10937
|
+
}
|
|
10938
|
+
if (mapProgress.activeResolvers === 0) retryWakeFeed.wakeWorkers();
|
|
10762
10939
|
} catch (error) {
|
|
10763
10940
|
// RuntimeFlow has already returned the row slot and memory lease.
|
|
10764
10941
|
firstRowError = this.pendingDurableSuspension
|
|
@@ -10766,7 +10943,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10766
10943
|
: (firstRowError ?? error);
|
|
10767
10944
|
this.wakeToolDispatcher();
|
|
10768
10945
|
heapAdmission.close(firstRowError);
|
|
10769
|
-
|
|
10946
|
+
retryWakeFeed.fail(firstRowError);
|
|
10770
10947
|
throw error;
|
|
10771
10948
|
}
|
|
10772
10949
|
}
|
|
@@ -10852,6 +11029,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10852
11029
|
}
|
|
10853
11030
|
} finally {
|
|
10854
11031
|
clearInterval(mapActivityTimer);
|
|
11032
|
+
retryWakeFeed.wakeWorkers();
|
|
10855
11033
|
publishMapActivity();
|
|
10856
11034
|
datasetStep.items = currentTotalRows();
|
|
10857
11035
|
this.processedRowCount = currentTotalRows();
|
|
@@ -10932,19 +11110,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10932
11110
|
}
|
|
10933
11111
|
if (
|
|
10934
11112
|
this.pendingRowEventBoundaries.length > 0 ||
|
|
10935
|
-
|
|
11113
|
+
providerRowRetries.size > 0 ||
|
|
11114
|
+
parkedProviderRows.size > 0
|
|
10936
11115
|
) {
|
|
11116
|
+
const waitingProviderRows =
|
|
11117
|
+
providerRowRetries.size + parkedProviderRows.size;
|
|
10937
11118
|
const waitingRowKeys = new Set<string>();
|
|
10938
11119
|
const previousSuspendedInputs =
|
|
10939
11120
|
this.checkpoint.mapFrames?.[mapScope.mapInvocationId]
|
|
10940
11121
|
?.suspendedInputRows;
|
|
10941
11122
|
const suspendedInputRows =
|
|
10942
|
-
rowFeed ||
|
|
10943
|
-
(providerRetryDeadlines.length === 0 && !previousSuspendedInputs)
|
|
11123
|
+
rowFeed || (waitingProviderRows === 0 && !previousSuspendedInputs)
|
|
10944
11124
|
? undefined
|
|
10945
11125
|
: rowResults.flatMap((result, index) => {
|
|
10946
11126
|
if (result !== WAITING_ROW) return [];
|
|
10947
|
-
const baseRow = waitingBaseRows.get(index);
|
|
11127
|
+
const baseRow = waitingBaseRows.get(index)?.baseRow;
|
|
10948
11128
|
if (!baseRow) {
|
|
10949
11129
|
throw new Error(
|
|
10950
11130
|
`Waiting row ${index} lost its resumable input before map suspension.`,
|
|
@@ -10976,13 +11156,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10976
11156
|
]),
|
|
10977
11157
|
).values(),
|
|
10978
11158
|
];
|
|
10979
|
-
const
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
11159
|
+
const residentHold = providerRowRetries.peek();
|
|
11160
|
+
const parkedHold = parkedProviderRows.peek();
|
|
11161
|
+
const earliestHold =
|
|
11162
|
+
!residentHold ||
|
|
11163
|
+
(parkedHold && parkedHold.retryAtMs < residentHold.retryAtMs)
|
|
11164
|
+
? parkedHold
|
|
11165
|
+
: residentHold;
|
|
10986
11166
|
const providerWakeAtMs = earliestHold
|
|
10987
11167
|
? Math.max(Date.now() + 1_000, earliestHold.retryAtMs)
|
|
10988
11168
|
: null;
|
|
@@ -11005,7 +11185,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11005
11185
|
provider: earliestHold.provider,
|
|
11006
11186
|
retry_at: new Date(providerWakeAtMs).toISOString(),
|
|
11007
11187
|
wait_ms: Math.max(0, providerWakeAtMs - Date.now()),
|
|
11008
|
-
waiting_rows:
|
|
11188
|
+
waiting_rows: waitingProviderRows,
|
|
11009
11189
|
table_namespace: normalizedTableNamespace,
|
|
11010
11190
|
})}`,
|
|
11011
11191
|
{ level: 'warn' },
|
|
@@ -12534,8 +12714,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12534
12714
|
].join(':')
|
|
12535
12715
|
: 'workflow';
|
|
12536
12716
|
const callIndexKey = `${this.currentExecutionScope.receipt.namespace}:${logicalCallScope}:${normalizedKey}:${toolRequestIdentity}`;
|
|
12537
|
-
const
|
|
12538
|
-
|
|
12717
|
+
const freshCallIndex = this.toolCallIndexByKey.get(callIndexKey) ?? 0;
|
|
12718
|
+
const callIndex = nextReplayableCallIndex(
|
|
12719
|
+
`tool:${callIndexKey}`,
|
|
12720
|
+
freshCallIndex,
|
|
12721
|
+
);
|
|
12722
|
+
this.toolCallIndexByKey.set(
|
|
12723
|
+
callIndexKey,
|
|
12724
|
+
Math.max(freshCallIndex, callIndex + 1),
|
|
12725
|
+
);
|
|
12539
12726
|
const logicalCallId = stableDigest(`${callIndexKey}:${callIndex}`);
|
|
12540
12727
|
this.resourceGovernor.recordLogicalToolCall();
|
|
12541
12728
|
let executionAuthScopeDigest =
|
|
@@ -14399,8 +14586,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14399
14586
|
: Math.max(0, Math.round(ms));
|
|
14400
14587
|
if (this.#options.durableBoundaries) {
|
|
14401
14588
|
const rowStore = rowContext.getStore();
|
|
14402
|
-
const legacyIndex =
|
|
14403
|
-
|
|
14589
|
+
const legacyIndex = nextReplayableCallIndex(
|
|
14590
|
+
'sleep:legacy',
|
|
14591
|
+
this.sleepBoundaryIndex,
|
|
14592
|
+
);
|
|
14593
|
+
this.sleepBoundaryIndex = Math.max(
|
|
14594
|
+
this.sleepBoundaryIndex,
|
|
14595
|
+
legacyIndex + 1,
|
|
14596
|
+
);
|
|
14404
14597
|
const legacyBoundaryId = this.durableBoundaryId(
|
|
14405
14598
|
`sleep-${legacyIndex}-${delayMs}`,
|
|
14406
14599
|
);
|
|
@@ -14414,9 +14607,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14414
14607
|
? `${rowStore.mapScope.mapInvocationId}:${rowStore.rowKey ?? rowStore.rowId}`
|
|
14415
14608
|
: `row:${rowStore.rowKey ?? rowStore.rowId}`;
|
|
14416
14609
|
const scopedIndexKey = `${this.currentExecutionScope.receipt.namespace}:${rowScope}`;
|
|
14417
|
-
const
|
|
14610
|
+
const freshScopedIndex =
|
|
14418
14611
|
this.sleepBoundaryIndexByScope.get(scopedIndexKey) ?? 0;
|
|
14419
|
-
|
|
14612
|
+
const scopedIndex = nextReplayableCallIndex(
|
|
14613
|
+
`sleep:${scopedIndexKey}`,
|
|
14614
|
+
freshScopedIndex,
|
|
14615
|
+
);
|
|
14616
|
+
this.sleepBoundaryIndexByScope.set(
|
|
14617
|
+
scopedIndexKey,
|
|
14618
|
+
Math.max(freshScopedIndex, scopedIndex + 1),
|
|
14619
|
+
);
|
|
14420
14620
|
const scopedBoundaryId = this.durableBoundaryId(
|
|
14421
14621
|
`sleep-${stableDigest(`${rowScope}:${scopedIndex}`)}-${delayMs}`,
|
|
14422
14622
|
);
|
|
@@ -14771,8 +14971,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14771
14971
|
const rowStore = rowContext.getStore();
|
|
14772
14972
|
const scope = rowStore ? `row-${rowStore.rowId}` : 'workflow';
|
|
14773
14973
|
const callIndexKey = `${this.currentExecutionScope.receipt.namespace}:${scope}:${normalizedKey}`;
|
|
14774
|
-
const
|
|
14775
|
-
|
|
14974
|
+
const freshCallIndex = this.stepCallIndexByKey.get(callIndexKey) ?? 0;
|
|
14975
|
+
const callIndex = nextReplayableCallIndex(
|
|
14976
|
+
`step:${callIndexKey}`,
|
|
14977
|
+
freshCallIndex,
|
|
14978
|
+
);
|
|
14979
|
+
this.stepCallIndexByKey.set(
|
|
14980
|
+
callIndexKey,
|
|
14981
|
+
Math.max(freshCallIndex, callIndex + 1),
|
|
14982
|
+
);
|
|
14776
14983
|
const boundarySuffix = callIndex === 0 ? '' : `:${callIndex}`;
|
|
14777
14984
|
const boundaryId = this.durableBoundaryId(
|
|
14778
14985
|
`step-${scope}:${normalizedKey}${boundarySuffix}`,
|
|
@@ -14783,6 +14990,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14783
14990
|
this.checkpoint.resolvedBoundaries?.[boundaryId],
|
|
14784
14991
|
);
|
|
14785
14992
|
if (existing?.kind === 'step' && 'output' in existing) {
|
|
14993
|
+
const replayedCalls =
|
|
14994
|
+
rowStore?.replayCallIndexes?.recoverStep(boundaryId) ?? false;
|
|
14786
14995
|
this.runtimeLog(
|
|
14787
14996
|
`ctx.step(${normalizedKey}): recovered result from checkpoint`,
|
|
14788
14997
|
);
|
|
@@ -14792,6 +15001,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14792
15001
|
// the next sibling operation. This is deliberately run-local state;
|
|
14793
15002
|
// it is not a cross-run sleep cache or an input-derived identity.
|
|
14794
15003
|
if (
|
|
15004
|
+
!replayedCalls &&
|
|
14795
15005
|
Number.isSafeInteger(existing.sleepCount) &&
|
|
14796
15006
|
existing.sleepCount !== undefined &&
|
|
14797
15007
|
existing.sleepCount > 0
|
|
@@ -14802,7 +15012,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14802
15012
|
}
|
|
14803
15013
|
|
|
14804
15014
|
const sleepBoundaryIndexBefore = this.sleepBoundaryIndex;
|
|
14805
|
-
const
|
|
15015
|
+
const callAdvances = new Map<string, number>();
|
|
15016
|
+
const output = rowStore?.replayCallIndexes
|
|
15017
|
+
? await rowContext.run(
|
|
15018
|
+
{
|
|
15019
|
+
...rowStore,
|
|
15020
|
+
replayStepCaptures: [
|
|
15021
|
+
...(rowStore.replayStepCaptures ?? []),
|
|
15022
|
+
callAdvances,
|
|
15023
|
+
],
|
|
15024
|
+
},
|
|
15025
|
+
run,
|
|
15026
|
+
)
|
|
15027
|
+
: await run();
|
|
14806
15028
|
assertJsonSerializableStepOutput(normalizedKey, output);
|
|
14807
15029
|
// A caught durable suspension is still the invocation's outcome. Never
|
|
14808
15030
|
// publish an enclosing step fallback that would make a later resume skip
|
|
@@ -14814,11 +15036,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14814
15036
|
kind: 'step',
|
|
14815
15037
|
stepId: normalizedKey,
|
|
14816
15038
|
output,
|
|
14817
|
-
sleepCount:
|
|
15039
|
+
sleepCount: rowStore?.replayCallIndexes
|
|
15040
|
+
? (callAdvances.get('sleep:legacy') ?? 0)
|
|
15041
|
+
: this.sleepBoundaryIndex - sleepBoundaryIndexBefore,
|
|
14818
15042
|
completedAt: Date.now(),
|
|
14819
15043
|
},
|
|
14820
15044
|
};
|
|
14821
15045
|
this.#options.onBatchComplete?.(this.checkpoint);
|
|
15046
|
+
rowStore?.replayCallIndexes?.completeStep(boundaryId, callAdvances);
|
|
14822
15047
|
return output;
|
|
14823
15048
|
};
|
|
14824
15049
|
|
|
@@ -17607,9 +17832,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17607
17832
|
: options?.providerOperationForceRefreshIdentities;
|
|
17608
17833
|
const executionRequestNonce = crypto.randomUUID();
|
|
17609
17834
|
// Transport ambiguity must retain the billing identity. An ordinary
|
|
17610
|
-
// (unfenced) call advances it
|
|
17611
|
-
//
|
|
17612
|
-
//
|
|
17835
|
+
// (unfenced) call advances it after a definite rejection released
|
|
17836
|
+
// that attempt's reservation, including app pacing refusals. Fenced
|
|
17837
|
+
// calls retain it: gateway-qualified retries own their distinct charge lifecycle.
|
|
17613
17838
|
let deeplineRequestId = `ctx-tool-${stableDigest(executionRequestNonce).slice(0, 32)}`;
|
|
17614
17839
|
// The provider operation has a semantic identity independent of
|
|
17615
17840
|
// the response receipt. Its key is supplied by the caller that owns
|
|
@@ -17769,10 +17994,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17769
17994
|
? { integration_mode: this.#options.integrationMode }
|
|
17770
17995
|
: {}),
|
|
17771
17996
|
});
|
|
17997
|
+
// Legacy retry migration retains this transport identity even when
|
|
17998
|
+
// the current checkpoint cursor index belongs to a newer batch.
|
|
17999
|
+
const providerResidentWaitKey = durableCallReceiptKey;
|
|
17772
18000
|
let transportAttempt = 0;
|
|
17773
18001
|
let invocationAttempt =
|
|
17774
18002
|
savedProviderRetryCursor?.invocationAttempt ?? 0;
|
|
17775
18003
|
clearProviderRetryCursor = () => {
|
|
18004
|
+
if (providerResidentWaitKey)
|
|
18005
|
+
this.exhaustedProviderResidentWaitCalls.delete(
|
|
18006
|
+
providerResidentWaitKey,
|
|
18007
|
+
);
|
|
17776
18008
|
if (
|
|
17777
18009
|
!providerRetryCursorKey ||
|
|
17778
18010
|
!this.checkpoint.providerRetryCursors
|
|
@@ -17838,6 +18070,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17838
18070
|
// work. Repeated zero or expired holds must also yield durably.
|
|
17839
18071
|
let residentProviderWaitStartedAt: number | null = null;
|
|
17840
18072
|
const canWaitForProviderInSandbox = (waitMs: number) => {
|
|
18073
|
+
if (
|
|
18074
|
+
providerResidentWaitKey &&
|
|
18075
|
+
this.exhaustedProviderResidentWaitCalls.has(
|
|
18076
|
+
providerResidentWaitKey,
|
|
18077
|
+
)
|
|
18078
|
+
)
|
|
18079
|
+
return false;
|
|
17841
18080
|
const elapsedMs =
|
|
17842
18081
|
residentProviderWaitStartedAt === null
|
|
17843
18082
|
? 0
|
|
@@ -17847,6 +18086,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17847
18086
|
Math.max(0, TOOL_PROVIDER_RESIDENT_WAIT_BUDGET_MS - elapsedMs)
|
|
17848
18087
|
);
|
|
17849
18088
|
};
|
|
18089
|
+
const markProviderResidentWaitBudgetExhausted = () => {
|
|
18090
|
+
const exhausted =
|
|
18091
|
+
residentProviderWaitStartedAt !== null ||
|
|
18092
|
+
Boolean(
|
|
18093
|
+
providerResidentWaitKey &&
|
|
18094
|
+
this.exhaustedProviderResidentWaitCalls.has(
|
|
18095
|
+
providerResidentWaitKey,
|
|
18096
|
+
),
|
|
18097
|
+
);
|
|
18098
|
+
if (exhausted && providerResidentWaitKey)
|
|
18099
|
+
this.exhaustedProviderResidentWaitCalls.add(
|
|
18100
|
+
providerResidentWaitKey,
|
|
18101
|
+
);
|
|
18102
|
+
return exhausted;
|
|
18103
|
+
};
|
|
17850
18104
|
const waitForToolRetry = async (
|
|
17851
18105
|
waitMs: number,
|
|
17852
18106
|
providerHold: boolean,
|
|
@@ -17947,6 +18201,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17947
18201
|
// the tool-keyed dispatch identity so terminal attribution can join
|
|
17948
18202
|
// provider admission with the response residence it admitted.
|
|
17949
18203
|
let providerTelemetryKey: string | null = null;
|
|
18204
|
+
let providerPermitReceiverUnavailable = false;
|
|
17950
18205
|
let providerRunInFlightAtDispatch: number | null = null;
|
|
17951
18206
|
const acquireProviderPermit = async (signal?: AbortSignal) => {
|
|
17952
18207
|
options?.onProviderAdmissionPending?.();
|
|
@@ -17955,6 +18210,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17955
18210
|
toolId,
|
|
17956
18211
|
signal,
|
|
17957
18212
|
yieldProviderHolds: this.#options.durableBoundaries === true,
|
|
18213
|
+
...(invocationAttempt > 0 ||
|
|
18214
|
+
residentProviderWaitStartedAt !== null
|
|
18215
|
+
? { retryPriority: true }
|
|
18216
|
+
: {}),
|
|
17958
18217
|
});
|
|
17959
18218
|
providerConcurrencyGrant = permit.concurrencyGrant ?? null;
|
|
17960
18219
|
providerPacingGrant = permit.pacingGrant ?? null;
|
|
@@ -18000,6 +18259,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18000
18259
|
release: () => void;
|
|
18001
18260
|
} | null = null;
|
|
18002
18261
|
let fetchDispatched = false;
|
|
18262
|
+
let dispatchedWithProviderPermit = false;
|
|
18263
|
+
let providerPermitReceiverRejected = false;
|
|
18264
|
+
let providerDispatchCounted = false;
|
|
18265
|
+
const recordProviderDispatch = () => {
|
|
18266
|
+
if (providerDispatchCounted) return;
|
|
18267
|
+
providerDispatchCounted = true;
|
|
18268
|
+
this.resourceGovernor.recordProviderDispatch();
|
|
18269
|
+
};
|
|
18003
18270
|
// A provider 429 must be reported while its server-minted
|
|
18004
18271
|
// admission lease is still live. Keep its parsed outcome here so
|
|
18005
18272
|
// the ordinary retry boundary can reuse it after Flow releases the
|
|
@@ -18142,6 +18409,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18142
18409
|
signal: abortController?.signal,
|
|
18143
18410
|
requiresTransportPermit: !fixtureExecution,
|
|
18144
18411
|
deferProviderPermitValidation: true,
|
|
18412
|
+
providerDispatchAtGateway:
|
|
18413
|
+
!providerPermitReceiverUnavailable,
|
|
18145
18414
|
onTransportAdmitted: (waitMs) => {
|
|
18146
18415
|
transportSlotWaitMs = waitMs;
|
|
18147
18416
|
},
|
|
@@ -18327,63 +18596,90 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18327
18596
|
logProvider429RetryPhase('http_dispatch_started');
|
|
18328
18597
|
activeProviderRequest =
|
|
18329
18598
|
this.beginProviderRequest(provider);
|
|
18330
|
-
|
|
18331
|
-
//
|
|
18332
|
-
this
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18349
|
-
|
|
18350
|
-
|
|
18351
|
-
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
18356
|
-
|
|
18357
|
-
|
|
18358
|
-
|
|
18359
|
-
|
|
18360
|
-
|
|
18361
|
-
|
|
18362
|
-
|
|
18363
|
-
:
|
|
18364
|
-
|
|
18365
|
-
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
|
|
18374
|
-
|
|
18375
|
-
|
|
18376
|
-
|
|
18377
|
-
|
|
18599
|
+
|
|
18600
|
+
// The new action binds delegated validation to the
|
|
18601
|
+
// actual receiver. Older gateways refuse this path
|
|
18602
|
+
// before admission; direct app routes do not serve it.
|
|
18603
|
+
dispatchedWithProviderPermit =
|
|
18604
|
+
!providerPermitReceiverUnavailable &&
|
|
18605
|
+
providerGatewayDispatchProof?.dispatchValidationSupported ===
|
|
18606
|
+
true;
|
|
18607
|
+
if (!dispatchedWithProviderPermit)
|
|
18608
|
+
recordProviderDispatch();
|
|
18609
|
+
const dispatchedResponse = await fetch(
|
|
18610
|
+
dispatchedWithProviderPermit
|
|
18611
|
+
? `${url}${TOOL_EXECUTE_PROVIDER_PERMIT_PATH_SUFFIX}`
|
|
18612
|
+
: url,
|
|
18613
|
+
{
|
|
18614
|
+
method: 'POST',
|
|
18615
|
+
signal: abortController?.signal,
|
|
18616
|
+
headers: {
|
|
18617
|
+
'Content-Type': 'application/json',
|
|
18618
|
+
Authorization: `Bearer ${this.#options.executorToken}`,
|
|
18619
|
+
[EXECUTE_RESPONSE_CONTRACT_HEADER]:
|
|
18620
|
+
RAW_V2_TOOL_RESPONSE_CONTRACT,
|
|
18621
|
+
[EXECUTE_RESPONSE_INTENT_HEADER]: 'dataset',
|
|
18622
|
+
[EXECUTE_TOOL_METADATA_HEADER]: 'true',
|
|
18623
|
+
[TOOL_EXECUTION_ERROR_SCHEMA_HEADER]: String(
|
|
18624
|
+
toolErrorSchemaVersion,
|
|
18625
|
+
),
|
|
18626
|
+
// Tell the gateway which protective outcomes this
|
|
18627
|
+
// runner can absorb. The relay may only shed a
|
|
18628
|
+
// provider execute with the Deepline concurrency
|
|
18629
|
+
// shape when this token is present; without it the
|
|
18630
|
+
// relay must queue, because an older artifact would
|
|
18631
|
+
// settle the row as failed instead of pacing.
|
|
18632
|
+
[TOOL_EXECUTE_RUNNER_ACCEPTS_HEADER]:
|
|
18633
|
+
TOOL_EXECUTE_RUNNER_ACCEPTS_VALUE,
|
|
18634
|
+
...(!usesDurableInvocationFence &&
|
|
18635
|
+
!providerExecutionFenceKey
|
|
18636
|
+
? {
|
|
18637
|
+
[TOOL_EXECUTE_APP_PACING_REFUSAL_HEADER]:
|
|
18638
|
+
TOOL_EXECUTE_APP_PACING_REFUSAL_VALUE,
|
|
18639
|
+
}
|
|
18640
|
+
: {}),
|
|
18641
|
+
...(providerGatewayDispatchProof
|
|
18642
|
+
? {
|
|
18643
|
+
[TOOL_EXECUTE_PROVIDER_RATE_SCOPE_HEADER]:
|
|
18644
|
+
providerGatewayDispatchProof.rateScopeToken,
|
|
18645
|
+
[TOOL_EXECUTE_PROVIDER_RATE_RETRY_SNAPSHOT_HEADER]:
|
|
18646
|
+
String(
|
|
18647
|
+
providerGatewayDispatchProof.retryAtMsAtGrant,
|
|
18648
|
+
),
|
|
18649
|
+
}
|
|
18650
|
+
: {}),
|
|
18651
|
+
'x-deepline-request-id': deeplineRequestId,
|
|
18652
|
+
...(providerExecutionFenceKey
|
|
18653
|
+
? {
|
|
18654
|
+
'x-deepline-idempotency-key':
|
|
18655
|
+
providerExecutionFenceKey,
|
|
18656
|
+
}
|
|
18657
|
+
: {}),
|
|
18658
|
+
...protectionHeaders,
|
|
18659
|
+
...(this.#options.runtimeTestFaultHeader
|
|
18660
|
+
? {
|
|
18661
|
+
'x-deepline-test-fault':
|
|
18662
|
+
this.#options.runtimeTestFaultHeader,
|
|
18663
|
+
}
|
|
18664
|
+
: {}),
|
|
18665
|
+
},
|
|
18666
|
+
body: serializedRequestBody(invocationAttempt),
|
|
18378
18667
|
},
|
|
18379
|
-
|
|
18380
|
-
});
|
|
18668
|
+
);
|
|
18381
18669
|
// Error classification needs the status and headers even
|
|
18382
18670
|
// when the body is truncated. Keep the response visible
|
|
18383
18671
|
// to the enclosing retry/billing boundary as soon as
|
|
18384
18672
|
// headers arrive; decoded body data stays scoped until
|
|
18385
18673
|
// it has been read and verified below.
|
|
18386
18674
|
response = dispatchedResponse;
|
|
18675
|
+
if (
|
|
18676
|
+
dispatchedResponse.headers.has(
|
|
18677
|
+
TOOL_EXECUTE_PROVIDER_PERMIT_RECEIVER_HEADER,
|
|
18678
|
+
) ||
|
|
18679
|
+
(dispatchedResponse.status !== 403 &&
|
|
18680
|
+
dispatchedResponse.status !== 404)
|
|
18681
|
+
)
|
|
18682
|
+
recordProviderDispatch();
|
|
18387
18683
|
if (dispatchedResponse.status === 429) {
|
|
18388
18684
|
const observedAt = Date.now();
|
|
18389
18685
|
const retryAfter =
|
|
@@ -18539,6 +18835,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18539
18835
|
);
|
|
18540
18836
|
providerBodyAdmissionWaitMs += bodyRead.waitMs;
|
|
18541
18837
|
}
|
|
18838
|
+
providerPermitReceiverRejected =
|
|
18839
|
+
dispatchedWithProviderPermit &&
|
|
18840
|
+
isProviderPermitExecuteReceiverUnavailable(
|
|
18841
|
+
dispatchedResponse,
|
|
18842
|
+
dispatchedResponseErrorText ?? '',
|
|
18843
|
+
);
|
|
18844
|
+
if (!providerPermitReceiverRejected)
|
|
18845
|
+
recordProviderDispatch();
|
|
18542
18846
|
if (dispatchedResponse.status === 429) {
|
|
18543
18847
|
const bodyText = dispatchedResponseErrorText ?? '';
|
|
18544
18848
|
let attempt = httpFailureAttempts.next({
|
|
@@ -18616,6 +18920,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18616
18920
|
// diagnostic counter now; the flow still holds the
|
|
18617
18921
|
// real provider lease until this callback returns.
|
|
18618
18922
|
releaseProviderExecution();
|
|
18923
|
+
if (providerPermitReceiverRejected)
|
|
18924
|
+
return 'not_dispatched' as const;
|
|
18619
18925
|
const failure =
|
|
18620
18926
|
preclassifiedHttpFailure.current?.failure;
|
|
18621
18927
|
if (
|
|
@@ -18658,6 +18964,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18658
18964
|
},
|
|
18659
18965
|
);
|
|
18660
18966
|
} catch (error) {
|
|
18967
|
+
// A failed exchange whose dispatch is unknown remains a
|
|
18968
|
+
// physical attempt, including failure before headers.
|
|
18969
|
+
if (fetchDispatched && !providerPermitReceiverRejected)
|
|
18970
|
+
recordProviderDispatch();
|
|
18661
18971
|
// `request()` can fail while waiting for its transport
|
|
18662
18972
|
// lease, before its beforeRelease callback runs.
|
|
18663
18973
|
activeProviderRequest?.release();
|
|
@@ -18693,24 +19003,25 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18693
19003
|
providerCallElapsedMs =
|
|
18694
19004
|
providerBodyCompletedAtMs - providerCallStartedAt;
|
|
18695
19005
|
options?.onProviderBodyCompleted?.(providerBodyCompletedAtMs);
|
|
18696
|
-
|
|
18697
|
-
|
|
18698
|
-
|
|
18699
|
-
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
|
|
18704
|
-
|
|
18705
|
-
|
|
18706
|
-
|
|
18707
|
-
|
|
18708
|
-
|
|
18709
|
-
|
|
18710
|
-
|
|
18711
|
-
|
|
18712
|
-
|
|
18713
|
-
|
|
19006
|
+
if (!providerPermitReceiverRejected)
|
|
19007
|
+
this.resourceGovernor.observe({
|
|
19008
|
+
toolId,
|
|
19009
|
+
providerResourceKey: `tool:${toolId}`,
|
|
19010
|
+
providerTelemetryKey,
|
|
19011
|
+
providerConcurrencyGrant,
|
|
19012
|
+
providerPacingGrant,
|
|
19013
|
+
providerRunInFlightAtDispatch,
|
|
19014
|
+
providerLatencyMs: providerCallElapsedMs,
|
|
19015
|
+
fixtureDelayMs,
|
|
19016
|
+
transportSlotWaitMs,
|
|
19017
|
+
fetchToHeadersMs,
|
|
19018
|
+
executeServerTiming,
|
|
19019
|
+
bodyReadMs,
|
|
19020
|
+
providerBodyAdmissionWaitMs:
|
|
19021
|
+
dispatched.providerBodyAdmissionWaitMs,
|
|
19022
|
+
providerSuccess: response.ok,
|
|
19023
|
+
provider429: response.status === 429,
|
|
19024
|
+
});
|
|
18714
19025
|
} finally {
|
|
18715
19026
|
receiptHeartbeatSupervisor?.stop();
|
|
18716
19027
|
}
|
|
@@ -18747,6 +19058,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18747
19058
|
throw new PlayRowProviderBackpressureError(
|
|
18748
19059
|
error.provider,
|
|
18749
19060
|
Number.isFinite(retryAtMs) ? retryAtMs : Date.now(),
|
|
19061
|
+
markProviderResidentWaitBudgetExhausted(),
|
|
18750
19062
|
);
|
|
18751
19063
|
}
|
|
18752
19064
|
// This is a provider admission hold, not a row outcome.
|
|
@@ -18904,6 +19216,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18904
19216
|
}
|
|
18905
19217
|
span.setAttribute('plays.http_status_code', response.status);
|
|
18906
19218
|
|
|
19219
|
+
if (providerPermitReceiverRejected) {
|
|
19220
|
+
// This named action was rejected before provider work. Retry the
|
|
19221
|
+
// same invocation once through the legacy worker validation path.
|
|
19222
|
+
providerPermitReceiverUnavailable = true;
|
|
19223
|
+
continue;
|
|
19224
|
+
}
|
|
19225
|
+
|
|
18907
19226
|
if (!response.ok) {
|
|
18908
19227
|
const text = responseErrorText ?? '';
|
|
18909
19228
|
const developerTunnelOrigin502 =
|
|
@@ -19075,6 +19394,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19075
19394
|
throw new PlayRowProviderBackpressureError(
|
|
19076
19395
|
provider,
|
|
19077
19396
|
retryAtMs,
|
|
19397
|
+
markProviderResidentWaitBudgetExhausted(),
|
|
19078
19398
|
);
|
|
19079
19399
|
}
|
|
19080
19400
|
await this.waitForProviderBackpressure(provider, retryAtMs);
|
|
@@ -19185,14 +19505,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19185
19505
|
if (
|
|
19186
19506
|
!usesDurableInvocationFence &&
|
|
19187
19507
|
(failure.backpressureDelayMs !== null ||
|
|
19508
|
+
failure.reason === 'provider_pacing_wait' ||
|
|
19188
19509
|
(failure.reason === 'rate_limit' &&
|
|
19189
19510
|
failure.knownNotDispatched))
|
|
19190
19511
|
) {
|
|
19191
|
-
//
|
|
19192
|
-
//
|
|
19193
|
-
//
|
|
19194
|
-
//
|
|
19195
|
-
|
|
19512
|
+
// These refusals release the prior reservation. App pacing
|
|
19513
|
+
// does not advance invocationAttempt, so use its own HTTP
|
|
19514
|
+
// failure count to avoid reusing a released retry reservation.
|
|
19515
|
+
// Durable result and provider-operation keys stay unchanged.
|
|
19516
|
+
const billingRetryIdentity =
|
|
19517
|
+
failure.reason === 'provider_pacing_wait'
|
|
19518
|
+
? `app-pacing-refusal:${httpFailureAttempt}`
|
|
19519
|
+
: `provider-rate-limit-retry:${invocationAttempt}`;
|
|
19520
|
+
deeplineRequestId = `ctx-tool-${stableDigest(`${executionRequestNonce}:${billingRetryIdentity}`).slice(0, 32)}`;
|
|
19196
19521
|
}
|
|
19197
19522
|
this.emitExecutionEvent({
|
|
19198
19523
|
type: RUN_EVENT_KIND.activityObserved,
|