deepline 0.3.228 → 0.3.230
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 +18 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/contracts.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/sheet-column-storage.ts +252 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +29 -8
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +297 -69
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +38 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +8 -3
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +39 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +293 -4
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +29 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +56 -11
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/progress-sidecar.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +380 -183
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +187 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-work-counters.ts +52 -2
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +30 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +118 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +135 -21
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -0
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +16 -2
- package/dist/bundling-sources/shared_libs/plays/execution-pipeline.ts +72 -0
- package/dist/bundling-sources/shared_libs/plays/launch/prepare.ts +1 -3
- package/dist/bundling-sources/shared_libs/plays/sheet-filter.ts +9 -13
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +28 -31
- package/dist/cli/index.js +102 -2
- package/dist/cli/index.mjs +102 -2
- package/dist/{compiler-manifest-Dpd2hEet.d.mts → compiler-manifest-n8ZtPv2h.d.mts} +31 -21
- package/dist/{compiler-manifest-Dpd2hEet.d.ts → compiler-manifest-n8ZtPv2h.d.ts} +31 -21
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +98 -2
- package/dist/index.mjs +98 -2
- package/dist/install-integrity.json +4 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
serializePlayDatasetCell,
|
|
27
27
|
} from '../plays/dataset';
|
|
28
28
|
import type { PlayDataset, PlayDatasetInput } from '../plays/dataset';
|
|
29
|
+
import type { PlayDatasetColumnTypes } from '../play-data-plane/contracts';
|
|
29
30
|
import {
|
|
30
31
|
compileRequestsWithStrategy,
|
|
31
32
|
executeChunkedRequests,
|
|
@@ -201,8 +202,13 @@ import {
|
|
|
201
202
|
TOOL_EXECUTE_RUNNER_ACCEPTS_VALUE,
|
|
202
203
|
TOOL_EXECUTE_PROVIDER_RATE_SCOPE_HEADER,
|
|
203
204
|
TOOL_EXECUTE_PROVIDER_RATE_RETRY_SNAPSHOT_HEADER,
|
|
205
|
+
TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER,
|
|
204
206
|
classifyToolExecuteHttpFailure,
|
|
205
207
|
createToolExecuteHttpFailureAttemptTracker,
|
|
208
|
+
TOOL_EXECUTE_BARE_RATE_LIMIT_MAX_ATTEMPTS,
|
|
209
|
+
TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS,
|
|
210
|
+
TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS,
|
|
211
|
+
TOOL_EXECUTE_RATE_LIMIT_RETRY_DELAY_MAX_MS,
|
|
206
212
|
parseToolExecuteRetryAfterMs,
|
|
207
213
|
parseToolExecuteRetryAtMs,
|
|
208
214
|
parseToolExecuteAuthScopeChangedError,
|
|
@@ -680,7 +686,7 @@ async function runCtxFetchPhase<T>(input: {
|
|
|
680
686
|
callerSignal?: AbortSignal | null;
|
|
681
687
|
controller: AbortController;
|
|
682
688
|
deadline: CtxFetchDeadline;
|
|
683
|
-
phase: 'headers' | 'body';
|
|
689
|
+
phase: 'headers' | 'body' | 'total';
|
|
684
690
|
run: () => Promise<T>;
|
|
685
691
|
}): Promise<T> {
|
|
686
692
|
const remainingMs = remainingCtxFetchMs(input.deadline);
|
|
@@ -695,7 +701,9 @@ async function runCtxFetchPhase<T>(input: {
|
|
|
695
701
|
const phaseLimitMs =
|
|
696
702
|
input.phase === 'headers'
|
|
697
703
|
? input.deadline.headersMs
|
|
698
|
-
: input.
|
|
704
|
+
: input.phase === 'body'
|
|
705
|
+
? input.deadline.bodyMs
|
|
706
|
+
: remainingMs;
|
|
699
707
|
const timeoutMs = Math.min(phaseLimitMs, remainingMs);
|
|
700
708
|
const timeoutPhase = timeoutMs >= remainingMs ? 'total' : input.phase;
|
|
701
709
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -744,19 +752,42 @@ async function sleepWithinCtxFetchDeadline(input: {
|
|
|
744
752
|
delayMs: number;
|
|
745
753
|
}): Promise<void> {
|
|
746
754
|
const controller = new AbortController();
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
)
|
|
758
|
-
|
|
759
|
-
}
|
|
755
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
756
|
+
try {
|
|
757
|
+
await runCtxFetchPhase({
|
|
758
|
+
callerSignal: input.callerSignal,
|
|
759
|
+
controller,
|
|
760
|
+
deadline: input.deadline,
|
|
761
|
+
phase: 'total',
|
|
762
|
+
run: () =>
|
|
763
|
+
new Promise<void>((resolve) => {
|
|
764
|
+
timer = setTimeout(resolve, input.delayMs);
|
|
765
|
+
}),
|
|
766
|
+
});
|
|
767
|
+
} finally {
|
|
768
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function isCtxFetchBackpressure(response: Response, bodyText: string): boolean {
|
|
773
|
+
if (response.status === 429) return true;
|
|
774
|
+
if (response.status !== 503) return false;
|
|
775
|
+
// This Salesforce endpoint reports a rejected burst as 503. Match the final
|
|
776
|
+
// destination and exact response contract; arbitrary 503s remain failures.
|
|
777
|
+
try {
|
|
778
|
+
const destination = new URL(response.url);
|
|
779
|
+
const body: unknown = JSON.parse(bodyText);
|
|
780
|
+
return (
|
|
781
|
+
destination.protocol === 'https:' &&
|
|
782
|
+
destination.hostname.endsWith('.salesforce-sites.com') &&
|
|
783
|
+
body !== null &&
|
|
784
|
+
typeof body === 'object' &&
|
|
785
|
+
!Array.isArray(body) &&
|
|
786
|
+
(body as Record<string, unknown>).status === 'burst_circuit_open'
|
|
787
|
+
);
|
|
788
|
+
} catch {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
760
791
|
}
|
|
761
792
|
|
|
762
793
|
async function readCtxFetchBody(input: {
|
|
@@ -1673,6 +1704,69 @@ function rowsFromUnknown(value: unknown): Record<string, unknown>[] {
|
|
|
1673
1704
|
);
|
|
1674
1705
|
}
|
|
1675
1706
|
|
|
1707
|
+
function customerDbColumnTypes(
|
|
1708
|
+
columns: unknown,
|
|
1709
|
+
rows: readonly Record<string, unknown>[],
|
|
1710
|
+
): PlayDatasetColumnTypes | undefined {
|
|
1711
|
+
if (!Array.isArray(columns)) return undefined;
|
|
1712
|
+
// Core PostgreSQL OIDs only. Extension/domain identities are database-local,
|
|
1713
|
+
// and source SQL types never authorize coercing the values returned by a tool.
|
|
1714
|
+
const coreValueTypes: Record<
|
|
1715
|
+
number,
|
|
1716
|
+
PlayDatasetColumnTypes[string]['valueType']
|
|
1717
|
+
> = {
|
|
1718
|
+
16: 'boolean',
|
|
1719
|
+
20: 'string',
|
|
1720
|
+
21: 'number',
|
|
1721
|
+
23: 'number',
|
|
1722
|
+
25: 'string',
|
|
1723
|
+
114: 'json',
|
|
1724
|
+
700: 'number',
|
|
1725
|
+
701: 'number',
|
|
1726
|
+
1042: 'string',
|
|
1727
|
+
1043: 'string',
|
|
1728
|
+
1082: 'string',
|
|
1729
|
+
1083: 'string',
|
|
1730
|
+
1114: 'string',
|
|
1731
|
+
1184: 'string',
|
|
1732
|
+
1266: 'string',
|
|
1733
|
+
1700: 'string',
|
|
1734
|
+
2950: 'string',
|
|
1735
|
+
3802: 'json',
|
|
1736
|
+
};
|
|
1737
|
+
const entries: Array<[string, PlayDatasetColumnTypes[string]]> = [];
|
|
1738
|
+
for (const value of columns) {
|
|
1739
|
+
const column = recordOrNull(value);
|
|
1740
|
+
const postgresTypeId = column?.data_type_id;
|
|
1741
|
+
if (
|
|
1742
|
+
typeof column?.name !== 'string' ||
|
|
1743
|
+
typeof postgresTypeId !== 'number' ||
|
|
1744
|
+
!Object.hasOwn(coreValueTypes, postgresTypeId)
|
|
1745
|
+
)
|
|
1746
|
+
continue;
|
|
1747
|
+
const observed = new Set<PlayDatasetColumnTypes[string]['valueType']>();
|
|
1748
|
+
for (const row of rows) {
|
|
1749
|
+
const cell = row[column.name];
|
|
1750
|
+
if (cell === null || cell === undefined) continue;
|
|
1751
|
+
observed.add(
|
|
1752
|
+
typeof cell === 'boolean'
|
|
1753
|
+
? 'boolean'
|
|
1754
|
+
: typeof cell === 'string'
|
|
1755
|
+
? 'string'
|
|
1756
|
+
: typeof cell === 'number' && Number.isFinite(cell)
|
|
1757
|
+
? 'number'
|
|
1758
|
+
: 'json',
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
const valueType =
|
|
1762
|
+
postgresTypeId === 114 || postgresTypeId === 3802 || observed.size > 1
|
|
1763
|
+
? 'json'
|
|
1764
|
+
: ([...observed][0] ?? coreValueTypes[postgresTypeId]!);
|
|
1765
|
+
entries.push([column.name, { postgresTypeId, valueType }]);
|
|
1766
|
+
}
|
|
1767
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1676
1770
|
function finiteNonNegativeInteger(value: unknown): number | null {
|
|
1677
1771
|
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
1678
1772
|
return null;
|
|
@@ -2541,9 +2635,7 @@ const WAITING_ROW = Symbol('deepline.waiting_row');
|
|
|
2541
2635
|
const FAILED_ROW = Symbol('deepline.failed_row');
|
|
2542
2636
|
const COMPLETED_ROW = Symbol('deepline.completed_row');
|
|
2543
2637
|
type MapRowExecutionResult =
|
|
2544
|
-
| typeof
|
|
2545
|
-
| typeof WAITING_ROW
|
|
2546
|
-
| typeof FAILED_ROW;
|
|
2638
|
+
typeof COMPLETED_ROW | typeof WAITING_ROW | typeof FAILED_ROW;
|
|
2547
2639
|
|
|
2548
2640
|
function normalizeFetchHeaders(
|
|
2549
2641
|
headers: RequestInit['headers'],
|
|
@@ -4341,8 +4433,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4341
4433
|
artifactTableNamespace: string;
|
|
4342
4434
|
mapNodeId?: string | null;
|
|
4343
4435
|
explicitKey?:
|
|
4344
|
-
|
|
4345
|
-
| null;
|
|
4436
|
+
((row: Record<string, unknown>, index: number) => string) | null;
|
|
4346
4437
|
}): MapExecutionScope {
|
|
4347
4438
|
const mapInvocationId = `${input.logicalNamespace}:${this.mapInvocationIndex}`;
|
|
4348
4439
|
this.mapInvocationIndex += 1;
|
|
@@ -4592,12 +4683,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4592
4683
|
// run actually owns. The map/cohort publisher keeps the claim-free
|
|
4593
4684
|
// batched write, where the pointer genuinely shares the canonical
|
|
4594
4685
|
// result's single bounded request.
|
|
4595
|
-
if (
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
)
|
|
4600
|
-
) {
|
|
4686
|
+
if (!(
|
|
4687
|
+
this.#options.claimRuntimeStepReceipt ||
|
|
4688
|
+
this.#options.claimRuntimeStepReceipts
|
|
4689
|
+
)) {
|
|
4601
4690
|
return;
|
|
4602
4691
|
}
|
|
4603
4692
|
const existing = await this.getRuntimeStepReceipts(
|
|
@@ -7914,6 +8003,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
7914
8003
|
rows.length >= effectiveCount ? rows.slice(0, effectiveCount) : null,
|
|
7915
8004
|
sourceLabel: 'query result rows',
|
|
7916
8005
|
tableNamespace: null,
|
|
8006
|
+
columnTypes: isCustomerDbDatasetTool(toolId)
|
|
8007
|
+
? customerDbColumnTypes(raw?.columns, rows)
|
|
8008
|
+
: undefined,
|
|
7917
8009
|
resolvers: {
|
|
7918
8010
|
count: async () => Math.min(totalRows, datasetLimit),
|
|
7919
8011
|
at: async (index) => (await fetchPage(index, 1))[0],
|
|
@@ -8378,8 +8470,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8378
8470
|
_key: string,
|
|
8379
8471
|
_items: PlayDatasetInput<T>,
|
|
8380
8472
|
_input?:
|
|
8381
|
-
|
|
8382
|
-
| RuntimeStepProgram,
|
|
8473
|
+
MapFieldDefinition<T, Record<string, unknown>> | RuntimeStepProgram,
|
|
8383
8474
|
_options?: RuntimeDatasetOptions<T>,
|
|
8384
8475
|
): never {
|
|
8385
8476
|
void _key;
|
|
@@ -8472,6 +8563,18 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8472
8563
|
let itemsToProcess: Array<Record<string, unknown>> = [];
|
|
8473
8564
|
let itemOriginalIndexes: number[] = [];
|
|
8474
8565
|
const datasetColumnNames = Object.keys(input);
|
|
8566
|
+
// Generated fields no longer describe the source column, even when they
|
|
8567
|
+
// retain its name. Every writer still validates the actual incoming values.
|
|
8568
|
+
const inputColumnTypes = isPlayDataset(items)
|
|
8569
|
+
? items.columnTypes
|
|
8570
|
+
: undefined;
|
|
8571
|
+
const unchangedColumnTypes = inputColumnTypes
|
|
8572
|
+
? Object.fromEntries(
|
|
8573
|
+
Object.entries(inputColumnTypes).filter(
|
|
8574
|
+
([field]) => !datasetColumnNames.includes(field),
|
|
8575
|
+
),
|
|
8576
|
+
)
|
|
8577
|
+
: undefined;
|
|
8475
8578
|
const stripFieldOutputs = (row: Record<string, unknown>) => {
|
|
8476
8579
|
const stripped = cloneCsvAliasedRow(row);
|
|
8477
8580
|
for (const fieldName of datasetColumnNames) {
|
|
@@ -8491,8 +8594,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8491
8594
|
|
|
8492
8595
|
const userKeyOption = options?.key;
|
|
8493
8596
|
let explicitKeyResolver:
|
|
8494
|
-
|
|
8495
|
-
| null = null;
|
|
8597
|
+
((row: Record<string, unknown>, index: number) => string) | null = null;
|
|
8496
8598
|
if (userKeyOption !== undefined) {
|
|
8497
8599
|
explicitKeyResolver = (row, index) => {
|
|
8498
8600
|
const inputRow = stripFieldOutputs(row);
|
|
@@ -8863,6 +8965,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8863
8965
|
forceRefresh: this.#options.cachePolicy?.forceToolRefresh === true,
|
|
8864
8966
|
inputOffset: page.offset,
|
|
8865
8967
|
mode: options?.mode,
|
|
8968
|
+
columnTypes: unchangedColumnTypes,
|
|
8866
8969
|
},
|
|
8867
8970
|
);
|
|
8868
8971
|
resolvedTableNamespace = normalizeTableNamespace(
|
|
@@ -9424,6 +9527,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9424
9527
|
? immediateMaterializedRows
|
|
9425
9528
|
: null,
|
|
9426
9529
|
tableNamespace: resolvedTableNamespace,
|
|
9530
|
+
columnTypes: unchangedColumnTypes,
|
|
9427
9531
|
workProgress: {
|
|
9428
9532
|
total: totalInputCount,
|
|
9429
9533
|
executed: executedCount,
|
|
@@ -9666,6 +9770,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9666
9770
|
staticPipeline: this.currentStaticPipeline,
|
|
9667
9771
|
forceRefresh: this.#options.cachePolicy?.forceToolRefresh === true,
|
|
9668
9772
|
mode: options?.mode,
|
|
9773
|
+
columnTypes: unchangedColumnTypes,
|
|
9669
9774
|
},
|
|
9670
9775
|
);
|
|
9671
9776
|
resolvedTableNamespace = normalizeTableNamespace(
|
|
@@ -10175,6 +10280,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10175
10280
|
.map((row) => this.toMaterializedOutputRow(row)),
|
|
10176
10281
|
residentRows: terminalRows,
|
|
10177
10282
|
tableNamespace: resolvedTableNamespace,
|
|
10283
|
+
columnTypes: unchangedColumnTypes,
|
|
10178
10284
|
workProgress: {
|
|
10179
10285
|
total: totalInputCount,
|
|
10180
10286
|
executed: executedCount,
|
|
@@ -12895,6 +13001,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12895
13001
|
const callIndex = this.toolCallIndexByKey.get(callIndexKey) ?? 0;
|
|
12896
13002
|
this.toolCallIndexByKey.set(callIndexKey, callIndex + 1);
|
|
12897
13003
|
const logicalCallId = stableDigest(`${callIndexKey}:${callIndex}`);
|
|
13004
|
+
this.resourceGovernor.recordLogicalToolCall();
|
|
12898
13005
|
let executionAuthScopeDigest =
|
|
12899
13006
|
(await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null;
|
|
12900
13007
|
const eventWaitHandler =
|
|
@@ -14991,16 +15098,30 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14991
15098
|
let response: Response | null = null;
|
|
14992
15099
|
let bodyText: string | null = null;
|
|
14993
15100
|
try {
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
15101
|
+
// Only replay a body whose bytes are stable. In particular, a
|
|
15102
|
+
// consumed ReadableStream cannot be retried even with a write key.
|
|
15103
|
+
const replayableBody =
|
|
15104
|
+
init.body == null || typeof init.body === 'string';
|
|
15105
|
+
const canRetryTransport =
|
|
15106
|
+
replayableBody && ['GET', 'HEAD', 'OPTIONS'].includes(method);
|
|
14997
15107
|
const { safePublicFetch } = await loadSafeFetch();
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
attempt += 1
|
|
15002
|
-
) {
|
|
15108
|
+
let transportAttempt = 1;
|
|
15109
|
+
let rateLimitAttempt = 0;
|
|
15110
|
+
while (true) {
|
|
15003
15111
|
const attemptController = new AbortController();
|
|
15112
|
+
let permit: Awaited<
|
|
15113
|
+
ReturnType<RuntimeResourceGovernor['acquireProviderPermit']>
|
|
15114
|
+
> | null = null;
|
|
15115
|
+
let dispatched = false;
|
|
15116
|
+
const releasePhysicalPermit = async () => {
|
|
15117
|
+
const current = permit;
|
|
15118
|
+
permit = null;
|
|
15119
|
+
if (!current) return;
|
|
15120
|
+
if (dispatched) current.release();
|
|
15121
|
+
else await current.cancel();
|
|
15122
|
+
dispatched = false;
|
|
15123
|
+
};
|
|
15124
|
+
let transportRetry = false;
|
|
15004
15125
|
try {
|
|
15005
15126
|
const attemptInit = {
|
|
15006
15127
|
...fetchInit,
|
|
@@ -15012,10 +15133,53 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15012
15133
|
callerSignal: init.signal,
|
|
15013
15134
|
controller: attemptController,
|
|
15014
15135
|
deadline: fetchDeadline,
|
|
15015
|
-
phase: '
|
|
15136
|
+
phase: 'total',
|
|
15016
15137
|
run: () =>
|
|
15017
15138
|
safePublicFetch(url, attemptInit, {
|
|
15018
|
-
|
|
15139
|
+
// safePublicFetch validates each URL before invoking this
|
|
15140
|
+
// wrapper. Redirect hops are physical dispatches too.
|
|
15141
|
+
fetchImpl: async (physicalUrl, physicalInit) => {
|
|
15142
|
+
await releasePhysicalPermit();
|
|
15143
|
+
const signal = attemptInit.signal;
|
|
15144
|
+
permit = await runCtxFetchPhase({
|
|
15145
|
+
callerSignal: init.signal,
|
|
15146
|
+
controller: attemptController,
|
|
15147
|
+
deadline: fetchDeadline,
|
|
15148
|
+
phase: 'total',
|
|
15149
|
+
run: async () => {
|
|
15150
|
+
for (;;) {
|
|
15151
|
+
const acquired =
|
|
15152
|
+
await this.resourceGovernor.acquireProviderPermit(
|
|
15153
|
+
{ toolId: CTX_FETCH_EGRESS_TOOL_ID, signal },
|
|
15154
|
+
);
|
|
15155
|
+
try {
|
|
15156
|
+
signal.throwIfAborted();
|
|
15157
|
+
const valid =
|
|
15158
|
+
!acquired.validate ||
|
|
15159
|
+
(await acquired.validate());
|
|
15160
|
+
signal.throwIfAborted();
|
|
15161
|
+
if (valid) return acquired;
|
|
15162
|
+
} catch (error) {
|
|
15163
|
+
await acquired.cancel();
|
|
15164
|
+
throw error;
|
|
15165
|
+
}
|
|
15166
|
+
await acquired.cancel();
|
|
15167
|
+
}
|
|
15168
|
+
},
|
|
15169
|
+
});
|
|
15170
|
+
return await runCtxFetchPhase({
|
|
15171
|
+
callerSignal: init.signal,
|
|
15172
|
+
controller: attemptController,
|
|
15173
|
+
deadline: fetchDeadline,
|
|
15174
|
+
phase: 'headers',
|
|
15175
|
+
run: () => {
|
|
15176
|
+
dispatched = true;
|
|
15177
|
+
return (
|
|
15178
|
+
this.#options.fetchImpl ?? globalThis.fetch
|
|
15179
|
+
)(physicalUrl, physicalInit);
|
|
15180
|
+
},
|
|
15181
|
+
});
|
|
15182
|
+
},
|
|
15019
15183
|
sensitiveHeaders: Object.keys(secretHeaderMarkers),
|
|
15020
15184
|
stripHeadersOnCrossOriginRedirect: true,
|
|
15021
15185
|
headersOnCrossOriginRedirect: {
|
|
@@ -15029,7 +15193,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15029
15193
|
deadline: fetchDeadline,
|
|
15030
15194
|
response,
|
|
15031
15195
|
});
|
|
15032
|
-
break;
|
|
15033
15196
|
} catch (error) {
|
|
15034
15197
|
void response?.body?.cancel(error).catch(() => undefined);
|
|
15035
15198
|
response = null;
|
|
@@ -15045,22 +15208,67 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15045
15208
|
error instanceof Error ? error.message : String(error);
|
|
15046
15209
|
if (
|
|
15047
15210
|
canRetryTransport &&
|
|
15048
|
-
|
|
15211
|
+
transportAttempt < FETCH_TRANSPORT_MAX_ATTEMPTS
|
|
15049
15212
|
) {
|
|
15050
15213
|
this.runtimeLog(
|
|
15051
|
-
`ctx.fetch(${method} ${url}) transport failed on attempt ${
|
|
15214
|
+
`ctx.fetch(${method} ${url}) transport failed on attempt ${transportAttempt}/${FETCH_TRANSPORT_MAX_ATTEMPTS}; retrying: ${message}`,
|
|
15215
|
+
);
|
|
15216
|
+
transportRetry = true;
|
|
15217
|
+
} else {
|
|
15218
|
+
throw new Error(
|
|
15219
|
+
`ctx.fetch(${method} ${url}) failed on attempt ${transportAttempt}/${FETCH_TRANSPORT_MAX_ATTEMPTS}: ${message}`,
|
|
15052
15220
|
);
|
|
15053
|
-
await sleepWithinCtxFetchDeadline({
|
|
15054
|
-
callerSignal: init.signal,
|
|
15055
|
-
deadline: fetchDeadline,
|
|
15056
|
-
delayMs: FETCH_TRANSPORT_RETRY_DELAY_MS * attempt,
|
|
15057
|
-
});
|
|
15058
|
-
continue;
|
|
15059
15221
|
}
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15222
|
+
} finally {
|
|
15223
|
+
await releasePhysicalPermit();
|
|
15224
|
+
}
|
|
15225
|
+
if (transportRetry) {
|
|
15226
|
+
await sleepWithinCtxFetchDeadline({
|
|
15227
|
+
callerSignal: init.signal,
|
|
15228
|
+
deadline: fetchDeadline,
|
|
15229
|
+
delayMs: FETCH_TRANSPORT_RETRY_DELAY_MS * transportAttempt,
|
|
15230
|
+
});
|
|
15231
|
+
transportAttempt += 1;
|
|
15232
|
+
continue;
|
|
15063
15233
|
}
|
|
15234
|
+
if (
|
|
15235
|
+
!response ||
|
|
15236
|
+
bodyText === null ||
|
|
15237
|
+
!replayableBody ||
|
|
15238
|
+
!isCtxFetchBackpressure(response, bodyText)
|
|
15239
|
+
)
|
|
15240
|
+
break;
|
|
15241
|
+
rateLimitAttempt += 1;
|
|
15242
|
+
const retryAfter = response.headers.get('retry-after');
|
|
15243
|
+
const attemptCap = retryAfter?.trim()
|
|
15244
|
+
? TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS
|
|
15245
|
+
: TOOL_EXECUTE_BARE_RATE_LIMIT_MAX_ATTEMPTS;
|
|
15246
|
+
if (rateLimitAttempt >= attemptCap) break;
|
|
15247
|
+
const delayMs = Math.max(
|
|
15248
|
+
parseToolExecuteRetryAfterMs(retryAfter),
|
|
15249
|
+
Math.min(
|
|
15250
|
+
TOOL_EXECUTE_RATE_LIMIT_RETRY_DELAY_MAX_MS,
|
|
15251
|
+
TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS * rateLimitAttempt,
|
|
15252
|
+
),
|
|
15253
|
+
);
|
|
15254
|
+
this.runtimeLog(
|
|
15255
|
+
`[runtime.ctx_fetch_backpressure] ${JSON.stringify({
|
|
15256
|
+
key: normalizedKey,
|
|
15257
|
+
method,
|
|
15258
|
+
origin: new URL(response.url || url).origin,
|
|
15259
|
+
http_status: response.status,
|
|
15260
|
+
attempt: rateLimitAttempt,
|
|
15261
|
+
max_attempts: attemptCap,
|
|
15262
|
+
retry_delay_ms: delayMs,
|
|
15263
|
+
})}`,
|
|
15264
|
+
);
|
|
15265
|
+
// Destination feedback must not cool the shared generic_http
|
|
15266
|
+
// bucket used by unrelated websites. Pace this logical request.
|
|
15267
|
+
await sleepWithinCtxFetchDeadline({
|
|
15268
|
+
callerSignal: init.signal,
|
|
15269
|
+
deadline: fetchDeadline,
|
|
15270
|
+
delayMs,
|
|
15271
|
+
});
|
|
15064
15272
|
}
|
|
15065
15273
|
if (!response) {
|
|
15066
15274
|
throw new Error(
|
|
@@ -15283,6 +15491,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15283
15491
|
// Terminal logs are intentionally bounded; keep the small aggregate in
|
|
15284
15492
|
// stats so every retained terminal snapshot can explain queueing.
|
|
15285
15493
|
runtimeResources: this.getRuntimeResourceSnapshot(),
|
|
15494
|
+
runtimeWorkCounters: this.getRuntimeWorkCounters(),
|
|
15286
15495
|
};
|
|
15287
15496
|
}
|
|
15288
15497
|
|
|
@@ -15291,6 +15500,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15291
15500
|
return this.resourceGovernor.snapshot();
|
|
15292
15501
|
}
|
|
15293
15502
|
|
|
15503
|
+
/** Attempt-local counts without serializing the diagnostic resource map. */
|
|
15504
|
+
getRuntimeWorkCounters(): {
|
|
15505
|
+
logicalCalls: number;
|
|
15506
|
+
providerRequests: number;
|
|
15507
|
+
provider429s: number;
|
|
15508
|
+
} {
|
|
15509
|
+
return this.resourceGovernor.workCounters();
|
|
15510
|
+
}
|
|
15511
|
+
|
|
15294
15512
|
/**
|
|
15295
15513
|
* Record what one receipt gateway response said about itself.
|
|
15296
15514
|
*
|
|
@@ -16467,8 +16685,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
16467
16685
|
},
|
|
16468
16686
|
})),
|
|
16469
16687
|
buildPhysicalCall: async ({ members }) => {
|
|
16470
|
-
const owned = members.map(
|
|
16471
|
-
|
|
16688
|
+
const owned = members.map((member) =>
|
|
16689
|
+
batchRequestByCallId.get(member.memberId)!,
|
|
16472
16690
|
);
|
|
16473
16691
|
const compiled = compileOwnedPhysicalBatch(owned);
|
|
16474
16692
|
const providerActionVersion =
|
|
@@ -18794,9 +19012,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18794
19012
|
// running for.
|
|
18795
19013
|
const activeRowScope = rowContext.getStore();
|
|
18796
19014
|
const memoryLedgerRowScopes: readonly (
|
|
18797
|
-
|
|
|
18798
|
-
| null
|
|
18799
|
-
| undefined
|
|
19015
|
+
MemoryLedgerRowScope | null | undefined
|
|
18800
19016
|
)[] = options?.memoryLedgerRowScopes ?? [
|
|
18801
19017
|
{ rowKey: activeRowScope?.rowKey, fieldName: activeRowScope?.fieldName },
|
|
18802
19018
|
];
|
|
@@ -19639,6 +19855,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19639
19855
|
logProvider429RetryPhase('http_dispatch_started');
|
|
19640
19856
|
activeProviderRequest =
|
|
19641
19857
|
this.beginProviderRequest(provider);
|
|
19858
|
+
// Count at dispatch, including ambiguous transport
|
|
19859
|
+
// failures that never produce a residence sample.
|
|
19860
|
+
this.resourceGovernor.recordProviderDispatch();
|
|
19642
19861
|
const dispatchedResponse = await fetch(url, {
|
|
19643
19862
|
method: 'POST',
|
|
19644
19863
|
signal: abortController?.signal,
|
|
@@ -19694,21 +19913,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19694
19913
|
// it has been read and verified below.
|
|
19695
19914
|
response = dispatchedResponse;
|
|
19696
19915
|
if (dispatchedResponse.status === 429) {
|
|
19697
|
-
provider429RetryDiagnosticStartedAt =
|
|
19698
|
-
performance.now();
|
|
19699
|
-
provider429RetryDiagnosticNextInvocationAttempt =
|
|
19700
|
-
invocationAttempt + 1;
|
|
19701
|
-
logProvider429RetryPhase(
|
|
19702
|
-
'http_429_response_received',
|
|
19703
|
-
);
|
|
19704
|
-
providerRequestsInFlightAt429 =
|
|
19705
|
-
activeProviderRequest?.inFlightCount() ?? null;
|
|
19706
19916
|
const observedAt = Date.now();
|
|
19707
19917
|
const retryAfter =
|
|
19708
19918
|
dispatchedResponse.headers.get('retry-after');
|
|
19709
|
-
//
|
|
19710
|
-
//
|
|
19711
|
-
//
|
|
19919
|
+
// Preserve absolute provider deadlines even when the
|
|
19920
|
+
// response did not carry the gateway's counter
|
|
19921
|
+
// attestation. Classification below still decides
|
|
19922
|
+
// whether this 429 may update shared provider pacing.
|
|
19712
19923
|
providerRetryAtMs = retryAfter?.trim()
|
|
19713
19924
|
? parseToolExecuteRetryAtMs(
|
|
19714
19925
|
retryAfter,
|
|
@@ -19716,6 +19927,23 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19716
19927
|
)
|
|
19717
19928
|
: undefined;
|
|
19718
19929
|
}
|
|
19930
|
+
if (
|
|
19931
|
+
dispatchedResponse.status === 429 &&
|
|
19932
|
+
dispatchedResponse.headers.get(
|
|
19933
|
+
TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER,
|
|
19934
|
+
) === '1'
|
|
19935
|
+
) {
|
|
19936
|
+
this.resourceGovernor.recordProvider429();
|
|
19937
|
+
provider429RetryDiagnosticStartedAt =
|
|
19938
|
+
performance.now();
|
|
19939
|
+
provider429RetryDiagnosticNextInvocationAttempt =
|
|
19940
|
+
invocationAttempt + 1;
|
|
19941
|
+
logProvider429RetryPhase(
|
|
19942
|
+
'http_429_response_received',
|
|
19943
|
+
);
|
|
19944
|
+
providerRequestsInFlightAt429 =
|
|
19945
|
+
activeProviderRequest?.inFlightCount() ?? null;
|
|
19946
|
+
}
|
|
19719
19947
|
const dispatchedFetchToHeadersMs =
|
|
19720
19948
|
Date.now() - fetchedAt;
|
|
19721
19949
|
// The response writer owns these named wall times.
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from './cell-provenance';
|
|
8
8
|
import type { PlayBundleArtifact } from '../plays/artifact-types';
|
|
9
9
|
import type { PlayRunContractSnapshot } from '../plays/contracts';
|
|
10
|
+
import type { PlayDatasetColumnTypes } from '../play-data-plane/contracts';
|
|
10
11
|
import type { PlayStructuredDefinition } from '../plays/definition';
|
|
11
12
|
import type { PlayStaticPipeline } from '../plays/static-pipeline';
|
|
12
13
|
import type {
|
|
@@ -891,6 +892,7 @@ export interface ContextOptions {
|
|
|
891
892
|
forceRefresh?: boolean;
|
|
892
893
|
inputOffset?: number;
|
|
893
894
|
mode?: RuntimeDatasetRunOptions['mode'];
|
|
895
|
+
columnTypes?: PlayDatasetColumnTypes;
|
|
894
896
|
},
|
|
895
897
|
) => Promise<MapStartResult>;
|
|
896
898
|
/**
|
|
@@ -1169,6 +1171,9 @@ export interface ContextOptions {
|
|
|
1169
1171
|
}
|
|
1170
1172
|
|
|
1171
1173
|
export interface PlayCheckpoint {
|
|
1174
|
+
/** Last completed runner invocation within the current scheduler attempt. */
|
|
1175
|
+
workCounterInvocationAttempt?: number;
|
|
1176
|
+
workCounterInvocationSeq?: number;
|
|
1172
1177
|
/**
|
|
1173
1178
|
* Clock captured when this logical run first creates its context. All
|
|
1174
1179
|
* staleAfterSeconds receipt buckets use this value across durable resumes.
|
|
@@ -566,6 +566,7 @@ export class FlyMachinesClient {
|
|
|
566
566
|
method: string,
|
|
567
567
|
path: string,
|
|
568
568
|
body?: unknown,
|
|
569
|
+
leaseNonce?: string,
|
|
569
570
|
): Promise<unknown> {
|
|
570
571
|
const retryDelaysMs =
|
|
571
572
|
this.rateLimitOptions.retryDelaysMs ??
|
|
@@ -585,6 +586,7 @@ export class FlyMachinesClient {
|
|
|
585
586
|
headers: {
|
|
586
587
|
authorization: `Bearer ${this.token}`,
|
|
587
588
|
'content-type': 'application/json',
|
|
589
|
+
...(leaseNonce ? { 'fly-machine-lease-nonce': leaseNonce } : {}),
|
|
588
590
|
},
|
|
589
591
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
590
592
|
signal,
|
|
@@ -689,10 +691,43 @@ export class FlyMachinesClient {
|
|
|
689
691
|
);
|
|
690
692
|
}
|
|
691
693
|
|
|
692
|
-
|
|
694
|
+
/** Hold exclusive provider ownership while rechecking and reclaiming a
|
|
695
|
+
* stopped Machine. A concurrent rollback cannot update its hold or restart
|
|
696
|
+
* it until this lease is released or expires. */
|
|
697
|
+
async acquireMachineLease(
|
|
698
|
+
id: string,
|
|
699
|
+
options: { ttl: number; description: string },
|
|
700
|
+
): Promise<string> {
|
|
701
|
+
const result = await this.request('POST', `/machines/${id}/lease`, options);
|
|
702
|
+
const nonce =
|
|
703
|
+
result &&
|
|
704
|
+
typeof result === 'object' &&
|
|
705
|
+
'data' in result &&
|
|
706
|
+
result.data &&
|
|
707
|
+
typeof result.data === 'object' &&
|
|
708
|
+
'nonce' in result.data
|
|
709
|
+
? result.data.nonce
|
|
710
|
+
: undefined;
|
|
711
|
+
if (typeof nonce !== 'string' || nonce.trim() === '') {
|
|
712
|
+
throw new Error('Fly Machines API did not return a lease nonce.');
|
|
713
|
+
}
|
|
714
|
+
return nonce;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
async releaseMachineLease(id: string, nonce: string): Promise<void> {
|
|
718
|
+
await this.request('DELETE', `/machines/${id}/lease`, undefined, nonce);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async deleteMachine(
|
|
722
|
+
id: string,
|
|
723
|
+
force: boolean,
|
|
724
|
+
leaseNonce?: string,
|
|
725
|
+
): Promise<void> {
|
|
693
726
|
await this.request(
|
|
694
727
|
'DELETE',
|
|
695
728
|
`/machines/${id}?force=${force ? 'true' : 'false'}`,
|
|
729
|
+
undefined,
|
|
730
|
+
leaseNonce,
|
|
696
731
|
);
|
|
697
732
|
}
|
|
698
733
|
|
|
@@ -718,8 +753,9 @@ export class FlyMachinesClient {
|
|
|
718
753
|
async stopMachine(
|
|
719
754
|
id: string,
|
|
720
755
|
options?: { signal?: string; timeout?: string },
|
|
756
|
+
leaseNonce?: string,
|
|
721
757
|
): Promise<void> {
|
|
722
|
-
await this.request('POST', `/machines/${id}/stop`, options);
|
|
758
|
+
await this.request('POST', `/machines/${id}/stop`, options, leaseNonce);
|
|
723
759
|
}
|
|
724
760
|
|
|
725
761
|
async machinesInLane(releaseId: string): Promise<FlyMachine[]> {
|