deepline 0.1.150 → 0.1.151
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 +157 -140
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/csv-rows.ts +2 -19
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/row-isolation.ts +5 -53
- package/dist/bundling-sources/sdk/src/config.ts +2 -2
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +100 -158
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-row-outcome.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/pacing.ts +79 -0
- package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +39 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +19 -86
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-transition.ts +90 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-session.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +142 -11
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +3 -2
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +20 -23
- package/dist/cli/index.js +35 -3
- package/dist/cli/index.mjs +35 -3
- package/dist/index.js +3 -3
- package/dist/index.mjs +3 -3
- package/dist/plays/bundle-play-file.mjs +22 -19
- package/package.json +1 -1
|
@@ -55,13 +55,17 @@ import {
|
|
|
55
55
|
import {
|
|
56
56
|
CTX_FETCH_EGRESS_PROVIDER,
|
|
57
57
|
CTX_FETCH_EGRESS_TOOL_ID,
|
|
58
|
-
resolveBuiltinPacing,
|
|
59
58
|
} from '../../../shared_libs/play-runtime/builtin-pacing';
|
|
60
59
|
import {
|
|
61
60
|
CoordinatorRateStateBackend,
|
|
62
61
|
type CoordinatorRatePort,
|
|
63
62
|
} from '../../../shared_libs/play-runtime/governor/coordinator-rate-state-backend';
|
|
64
63
|
import type { PacingRule } from '../../../shared_libs/play-runtime/governor/rate-state-backend';
|
|
64
|
+
import {
|
|
65
|
+
pacingPolicyForTool,
|
|
66
|
+
pacingPolicyFromUnknownQueueHints,
|
|
67
|
+
type ResolvedPacingPolicy,
|
|
68
|
+
} from '../../../shared_libs/play-runtime/pacing';
|
|
65
69
|
import {
|
|
66
70
|
awaitChildTerminal,
|
|
67
71
|
type ChildPlayTerminalWaitResult,
|
|
@@ -83,7 +87,8 @@ import {
|
|
|
83
87
|
TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS,
|
|
84
88
|
TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS,
|
|
85
89
|
TOOL_EXECUTE_TRANSPORT_RETRY_DELAY_MS,
|
|
86
|
-
|
|
90
|
+
classifyToolExecuteHttpFailure,
|
|
91
|
+
createToolExecuteHttpFailureAttemptTracker,
|
|
87
92
|
} from '../../../shared_libs/play-runtime/tool-execute-retry-policy';
|
|
88
93
|
import type { PlayCallGovernanceSnapshot } from '../../../shared_libs/play-runtime/scheduler-backend';
|
|
89
94
|
import type { PreloadedRuntimeDbSession } from '../../../shared_libs/play-runtime/db-session';
|
|
@@ -161,6 +166,15 @@ import {
|
|
|
161
166
|
publicCsvStorageRow,
|
|
162
167
|
runtimeCsvStorageRow,
|
|
163
168
|
} from './runtime/csv-rows';
|
|
169
|
+
import {
|
|
170
|
+
completedMapRowOutcome,
|
|
171
|
+
failedMapRowOutcome,
|
|
172
|
+
mapRowOutcomeRuntimeRow,
|
|
173
|
+
mapRowOutcomeRuntimeFields,
|
|
174
|
+
resolveMapRowOutcomeKey,
|
|
175
|
+
stripMapRowOutcomeRuntimeFields,
|
|
176
|
+
} from '../../../shared_libs/play-runtime/map-row-outcome';
|
|
177
|
+
import { runtimeSheetSessionScope } from '../../../shared_libs/play-runtime/runtime-sheet-session';
|
|
164
178
|
import { chooseWorkerMapRowsPerChunk } from './runtime/map-chunk-plan';
|
|
165
179
|
import {
|
|
166
180
|
applyCsvRenameProjection,
|
|
@@ -194,11 +208,7 @@ import type {
|
|
|
194
208
|
LiveNodeProgressMap,
|
|
195
209
|
LiveNodeProgressSnapshot,
|
|
196
210
|
} from './runtime/live-progress';
|
|
197
|
-
import {
|
|
198
|
-
extractErrorBilling,
|
|
199
|
-
isHardBillingToolHttpError,
|
|
200
|
-
normalizeToolHttpErrorMessage,
|
|
201
|
-
} from './runtime/tool-http-errors';
|
|
211
|
+
import { extractErrorBilling } from './runtime/tool-http-errors';
|
|
202
212
|
import {
|
|
203
213
|
WorkflowAbortError,
|
|
204
214
|
isAbortLikeError,
|
|
@@ -326,6 +336,7 @@ type WorkerEnv = {
|
|
|
326
336
|
runtimeApiCall(input: {
|
|
327
337
|
executorToken: string;
|
|
328
338
|
path: string;
|
|
339
|
+
method?: string;
|
|
329
340
|
body: unknown;
|
|
330
341
|
headers?: Record<string, string>;
|
|
331
342
|
timeoutMs?: number;
|
|
@@ -581,6 +592,7 @@ async function callRuntimeApiRpcBinding(
|
|
|
581
592
|
const result = await binding.runtimeApiCall({
|
|
582
593
|
executorToken: authorization.replace(/^Bearer\s+/i, '').trim(),
|
|
583
594
|
path: input.path,
|
|
595
|
+
method: init.method ?? 'POST',
|
|
584
596
|
body: rawBody ? JSON.parse(rawBody) : {},
|
|
585
597
|
headers,
|
|
586
598
|
timeoutMs: input.timeoutMs,
|
|
@@ -1156,6 +1168,7 @@ async function executeTool(
|
|
|
1156
1168
|
workflowStep?: WorkflowStep,
|
|
1157
1169
|
onProviderBackpressure?: (retryAfterMs: number) => void,
|
|
1158
1170
|
onRetryAttempt?: () => void,
|
|
1171
|
+
transientHttpRetrySafe = false,
|
|
1159
1172
|
): Promise<ToolExecuteResult> {
|
|
1160
1173
|
if (args.toolId === 'test_wait_for_event' && workflowStep) {
|
|
1161
1174
|
const result = await waitForSyntheticIntegrationEvent(
|
|
@@ -1170,7 +1183,13 @@ async function executeTool(
|
|
|
1170
1183
|
// service bindings, NOT through HTTP from this worker. Removing the
|
|
1171
1184
|
// dispatcher-side coordinatorUrl plumbing intentionally turns the old
|
|
1172
1185
|
// HTTP-based dedup helpers into dead code.
|
|
1173
|
-
return callToolDirect(
|
|
1186
|
+
return callToolDirect(
|
|
1187
|
+
req,
|
|
1188
|
+
args,
|
|
1189
|
+
onProviderBackpressure,
|
|
1190
|
+
onRetryAttempt,
|
|
1191
|
+
transientHttpRetrySafe,
|
|
1192
|
+
);
|
|
1174
1193
|
}
|
|
1175
1194
|
|
|
1176
1195
|
async function executeToolWithLifecycle(
|
|
@@ -1180,6 +1199,7 @@ async function executeToolWithLifecycle(
|
|
|
1180
1199
|
callbacks: WorkerCtxCallbacks | undefined,
|
|
1181
1200
|
onProviderBackpressure?: (retryAfterMs: number) => void,
|
|
1182
1201
|
onRetryAttempt?: () => void,
|
|
1202
|
+
transientHttpRetrySafe = false,
|
|
1183
1203
|
): Promise<ToolExecuteResult> {
|
|
1184
1204
|
callbacks?.onToolCalled?.(args.toolId, nowMs());
|
|
1185
1205
|
try {
|
|
@@ -1189,6 +1209,7 @@ async function executeToolWithLifecycle(
|
|
|
1189
1209
|
workflowStep,
|
|
1190
1210
|
onProviderBackpressure,
|
|
1191
1211
|
onRetryAttempt,
|
|
1212
|
+
transientHttpRetrySafe,
|
|
1192
1213
|
);
|
|
1193
1214
|
} catch (error) {
|
|
1194
1215
|
callbacks?.onToolFailed?.(args.toolId, nowMs());
|
|
@@ -1322,16 +1343,17 @@ async function callToolDirect(
|
|
|
1322
1343
|
// 429 / transient-5xx retry). Without this the worker substrate would leave
|
|
1323
1344
|
// policy.budgets.maxRetryCount effectively unenforced.
|
|
1324
1345
|
onRetryAttempt?: () => void,
|
|
1346
|
+
transientHttpRetrySafe = false,
|
|
1325
1347
|
): Promise<ToolExecuteResult> {
|
|
1326
1348
|
const { id, toolId, input } = args;
|
|
1327
1349
|
const path = `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
|
|
1328
1350
|
let lastError: Error | null = null;
|
|
1351
|
+
const httpFailureAttempts = createToolExecuteHttpFailureAttemptTracker();
|
|
1352
|
+
let requestAttempt = 0;
|
|
1353
|
+
let transportAttempt = 0;
|
|
1329
1354
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
attempt <= TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS;
|
|
1333
|
-
attempt += 1
|
|
1334
|
-
) {
|
|
1355
|
+
while (true) {
|
|
1356
|
+
requestAttempt += 1;
|
|
1335
1357
|
let res: Response;
|
|
1336
1358
|
try {
|
|
1337
1359
|
res = await fetchRuntimeApi(req.baseUrl, path, {
|
|
@@ -1339,7 +1361,7 @@ async function callToolDirect(
|
|
|
1339
1361
|
headers: {
|
|
1340
1362
|
'content-type': 'application/json',
|
|
1341
1363
|
authorization: `Bearer ${req.executorToken}`,
|
|
1342
|
-
'x-deepline-request-id': `${req.runId}:${toolId}:${id}:attempt:${
|
|
1364
|
+
'x-deepline-request-id': `${req.runId}:${toolId}:${id}:attempt:${requestAttempt}`,
|
|
1343
1365
|
[EXECUTE_RESPONSE_CONTRACT_HEADER]: V2_EXECUTE_RESPONSE_CONTRACT,
|
|
1344
1366
|
[EXECUTE_TOOL_METADATA_HEADER]: 'true',
|
|
1345
1367
|
},
|
|
@@ -1349,20 +1371,21 @@ async function callToolDirect(
|
|
|
1349
1371
|
}),
|
|
1350
1372
|
});
|
|
1351
1373
|
} catch (error) {
|
|
1374
|
+
transportAttempt += 1;
|
|
1352
1375
|
const message = error instanceof Error ? error.message : String(error);
|
|
1353
1376
|
lastError = new Error(
|
|
1354
|
-
`Tool ${toolId} transport failed calling ${path} for run ${req.runId} on attempt ${
|
|
1377
|
+
`Tool ${toolId} transport failed calling ${path} for run ${req.runId} on attempt ${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS}: ${message}`,
|
|
1355
1378
|
);
|
|
1356
1379
|
if (
|
|
1357
|
-
|
|
1380
|
+
transportAttempt >= TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS ||
|
|
1358
1381
|
!isRetryableRuntimeApiError(error)
|
|
1359
1382
|
) {
|
|
1360
1383
|
throw lastError;
|
|
1361
1384
|
}
|
|
1362
1385
|
onRetryAttempt?.();
|
|
1363
|
-
const delayMs = TOOL_EXECUTE_TRANSPORT_RETRY_DELAY_MS *
|
|
1386
|
+
const delayMs = TOOL_EXECUTE_TRANSPORT_RETRY_DELAY_MS * transportAttempt;
|
|
1364
1387
|
console.warn(
|
|
1365
|
-
`[deepline-run:${req.runId}] tool transport retry tool=${toolId} path=${path} attempt=${
|
|
1388
|
+
`[deepline-run:${req.runId}] tool transport retry tool=${toolId} path=${path} attempt=${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS} retryAfterMs=${delayMs} error=${redactSecretsFromLogString(message)}`,
|
|
1366
1389
|
);
|
|
1367
1390
|
await sleepWorkerMs(delayMs);
|
|
1368
1391
|
continue;
|
|
@@ -1379,51 +1402,34 @@ async function callToolDirect(
|
|
|
1379
1402
|
}
|
|
1380
1403
|
|
|
1381
1404
|
const text = await res.text().catch(() => '');
|
|
1382
|
-
const
|
|
1383
|
-
const initialRetryDecision = decideToolExecuteHttpRetry({
|
|
1405
|
+
const httpFailureAttempt = httpFailureAttempts.next({
|
|
1384
1406
|
toolId,
|
|
1385
1407
|
status: res.status,
|
|
1408
|
+
transientHttpRetrySafe,
|
|
1386
1409
|
});
|
|
1387
|
-
|
|
1410
|
+
const failure = classifyToolExecuteHttpFailure({
|
|
1388
1411
|
toolId,
|
|
1389
1412
|
status: res.status,
|
|
1390
|
-
attempt,
|
|
1391
|
-
maxAttempts: initialRetryDecision.attemptCap,
|
|
1413
|
+
attempt: httpFailureAttempt,
|
|
1392
1414
|
bodyText: text,
|
|
1415
|
+
retryAfterHeader: res.headers.get('retry-after'),
|
|
1416
|
+
transientHttpRetrySafe,
|
|
1393
1417
|
});
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const retryDecision = decideToolExecuteHttpRetry({
|
|
1397
|
-
toolId,
|
|
1398
|
-
status: res.status,
|
|
1399
|
-
hardBillingFailure: isHardBillingToolHttpError(lastError),
|
|
1400
|
-
});
|
|
1401
|
-
const attemptCap = retryDecision.attemptCap;
|
|
1402
|
-
const retryAfterSeconds = Number(res.headers.get('retry-after'));
|
|
1403
|
-
const retryAfterMs =
|
|
1404
|
-
Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
|
|
1405
|
-
? Math.ceil(retryAfterSeconds * 1000)
|
|
1406
|
-
: 0;
|
|
1407
|
-
if (isRateLimited) {
|
|
1418
|
+
lastError = failure.error;
|
|
1419
|
+
if (failure.backpressureDelayMs !== null) {
|
|
1408
1420
|
// Feed the provider's backpressure into the shared pacer even on the
|
|
1409
1421
|
// final attempt so the (org, provider) bucket backs off across isolates.
|
|
1410
|
-
onProviderBackpressure?.(
|
|
1422
|
+
onProviderBackpressure?.(failure.backpressureDelayMs);
|
|
1411
1423
|
}
|
|
1412
|
-
if (!
|
|
1424
|
+
if (!failure.shouldRetry) {
|
|
1413
1425
|
throw lastError;
|
|
1414
1426
|
}
|
|
1415
1427
|
// Charge the retry budget per attempt, matching the cjs runner's
|
|
1416
1428
|
// chargeBudget('retry') on every 429 / retryable-5xx retry.
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
const delayMs = isRateLimited
|
|
1422
|
-
? Math.min(5_000, Math.max(retryAfterMs, 1_000 * attempt))
|
|
1423
|
-
: retryAfterMs > 0
|
|
1424
|
-
? Math.min(5_000, retryAfterMs)
|
|
1425
|
-
: 1_000;
|
|
1426
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1429
|
+
if (failure.chargeRetryBudget) {
|
|
1430
|
+
onRetryAttempt?.();
|
|
1431
|
+
}
|
|
1432
|
+
await sleepWorkerMs(failure.retryDelayMs);
|
|
1427
1433
|
}
|
|
1428
1434
|
|
|
1429
1435
|
throw lastError ?? new Error(`tool ${toolId} failed before execution.`);
|
|
@@ -1728,6 +1734,9 @@ class WorkerToolBatchScheduler {
|
|
|
1728
1734
|
const groupStartedAt = nowMs();
|
|
1729
1735
|
await Promise.all(
|
|
1730
1736
|
requests.map(async (request) => {
|
|
1737
|
+
const toolContract = await this.resolvePacing(toolId).catch(
|
|
1738
|
+
() => null,
|
|
1739
|
+
);
|
|
1731
1740
|
// Each unbatched provider call takes its own tool slot: the Governor
|
|
1732
1741
|
// charges tool budget, holds a global tool-concurrency slot, and
|
|
1733
1742
|
// applies per-(org,provider) pacing before the call runs.
|
|
@@ -1743,6 +1752,7 @@ class WorkerToolBatchScheduler {
|
|
|
1743
1752
|
this.callbacks,
|
|
1744
1753
|
(retryAfterMs) => this.reportBackpressure(toolId, retryAfterMs),
|
|
1745
1754
|
() => this.governor.chargeBudget('retry'),
|
|
1755
|
+
toolContract?.retrySafeTransientHttp === true,
|
|
1746
1756
|
),
|
|
1747
1757
|
);
|
|
1748
1758
|
} catch (error) {
|
|
@@ -1775,6 +1785,7 @@ class WorkerToolBatchScheduler {
|
|
|
1775
1785
|
abortSignal: this.abortSignal,
|
|
1776
1786
|
reportBackpressure: (retryAfterMs) =>
|
|
1777
1787
|
this.reportBackpressure(toolId, retryAfterMs),
|
|
1788
|
+
resolveToolContract: this.resolvePacing,
|
|
1778
1789
|
onRequestsSettled: this.onRequestsSettled,
|
|
1779
1790
|
callbacks: this.callbacks,
|
|
1780
1791
|
});
|
|
@@ -1810,6 +1821,7 @@ async function executeBatchedWorkerToolGroup(input: {
|
|
|
1810
1821
|
suggestedParallelism: number;
|
|
1811
1822
|
abortSignal?: AbortSignal;
|
|
1812
1823
|
reportBackpressure: (retryAfterMs: number) => void;
|
|
1824
|
+
resolveToolContract: WorkerPacingResolver;
|
|
1813
1825
|
onRequestsSettled?: (count: number) => void;
|
|
1814
1826
|
callbacks?: WorkerCtxCallbacks;
|
|
1815
1827
|
}): Promise<void> {
|
|
@@ -1840,6 +1852,9 @@ async function executeBatchedWorkerToolGroup(input: {
|
|
|
1840
1852
|
Math.min(input.suggestedParallelism, compiledBatches.length || 1),
|
|
1841
1853
|
),
|
|
1842
1854
|
execute: async (batch) => {
|
|
1855
|
+
const toolContract = await input
|
|
1856
|
+
.resolveToolContract(batch.batchOperation)
|
|
1857
|
+
.catch(() => null);
|
|
1843
1858
|
// One provider call per batch → one tool slot (budget + global
|
|
1844
1859
|
// concurrency + per-(org,provider) pacing) around the whole batch.
|
|
1845
1860
|
const slot = await input.governor.acquireToolSlot(batch.batchOperation, {
|
|
@@ -1857,6 +1872,7 @@ async function executeBatchedWorkerToolGroup(input: {
|
|
|
1857
1872
|
undefined,
|
|
1858
1873
|
input.reportBackpressure,
|
|
1859
1874
|
() => input.governor.chargeBudget('retry'),
|
|
1875
|
+
toolContract?.retrySafeTransientHttp === true,
|
|
1860
1876
|
);
|
|
1861
1877
|
} catch (error) {
|
|
1862
1878
|
input.callbacks?.onToolFailed?.(batch.batchOperation, nowMs());
|
|
@@ -2246,7 +2262,11 @@ async function executeWorkerWaterfall(
|
|
|
2246
2262
|
opts?: WorkerWaterfallOptions,
|
|
2247
2263
|
callbacks?: WorkerCtxCallbacks,
|
|
2248
2264
|
workflowStep?: WorkflowStep,
|
|
2265
|
+
resolveToolContract?: WorkerPacingResolver,
|
|
2249
2266
|
): Promise<unknown | null> {
|
|
2267
|
+
const retrySafeTransientHttp = async (toolId: string): Promise<boolean> =>
|
|
2268
|
+
(await resolveToolContract?.(toolId).catch(() => null))
|
|
2269
|
+
?.retrySafeTransientHttp === true;
|
|
2250
2270
|
// Inline-spec form
|
|
2251
2271
|
if (typeof toolNameOrSpec === 'object' && toolNameOrSpec) {
|
|
2252
2272
|
const spec = toolNameOrSpec;
|
|
@@ -2257,13 +2277,18 @@ async function executeWorkerWaterfall(
|
|
|
2257
2277
|
if (isWorkerInlineCodeStep(step)) {
|
|
2258
2278
|
result = await step.run(input, {
|
|
2259
2279
|
tools: {
|
|
2260
|
-
execute: async (request: unknown) =>
|
|
2261
|
-
|
|
2280
|
+
execute: async (request: unknown) => {
|
|
2281
|
+
const args = normalizeToolExecuteArgs(request);
|
|
2282
|
+
return await executeToolWithLifecycle(
|
|
2262
2283
|
req,
|
|
2263
|
-
|
|
2284
|
+
args,
|
|
2264
2285
|
workflowStep,
|
|
2265
2286
|
callbacks,
|
|
2266
|
-
|
|
2287
|
+
undefined,
|
|
2288
|
+
undefined,
|
|
2289
|
+
await retrySafeTransientHttp(args.toolId),
|
|
2290
|
+
);
|
|
2291
|
+
},
|
|
2267
2292
|
},
|
|
2268
2293
|
});
|
|
2269
2294
|
} else {
|
|
@@ -2276,6 +2301,9 @@ async function executeWorkerWaterfall(
|
|
|
2276
2301
|
},
|
|
2277
2302
|
workflowStep,
|
|
2278
2303
|
callbacks,
|
|
2304
|
+
undefined,
|
|
2305
|
+
undefined,
|
|
2306
|
+
await retrySafeTransientHttp(step.toolId),
|
|
2279
2307
|
);
|
|
2280
2308
|
}
|
|
2281
2309
|
} catch {
|
|
@@ -2367,6 +2395,9 @@ async function executeWorkerWaterfall(
|
|
|
2367
2395
|
{ id: toolName, toolId: toolName, input },
|
|
2368
2396
|
workflowStep,
|
|
2369
2397
|
callbacks,
|
|
2398
|
+
undefined,
|
|
2399
|
+
undefined,
|
|
2400
|
+
await retrySafeTransientHttp(toolName),
|
|
2370
2401
|
);
|
|
2371
2402
|
} catch {
|
|
2372
2403
|
return null;
|
|
@@ -2384,6 +2415,9 @@ async function executeWorkerWaterfall(
|
|
|
2384
2415
|
},
|
|
2385
2416
|
workflowStep,
|
|
2386
2417
|
callbacks,
|
|
2418
|
+
undefined,
|
|
2419
|
+
undefined,
|
|
2420
|
+
await retrySafeTransientHttp(toolName),
|
|
2387
2421
|
);
|
|
2388
2422
|
if (resultHasContent(result)) {
|
|
2389
2423
|
recorder.push({
|
|
@@ -3196,22 +3230,18 @@ async function persistCompletedMapRows(input: {
|
|
|
3196
3230
|
(field) => !input.outputFields.includes(field),
|
|
3197
3231
|
),
|
|
3198
3232
|
];
|
|
3233
|
+
const sessionScope = runtimeSheetSessionScope(input.req);
|
|
3234
|
+
const rows = input.rows.map((row) => publicCsvStorageRow(row));
|
|
3199
3235
|
await harnessPersistCompletedSheetRows({
|
|
3200
|
-
|
|
3201
|
-
executorToken: input.req.executorToken,
|
|
3202
|
-
orgId: input.req.orgId,
|
|
3203
|
-
preloadedDbSessions: input.req.preloadedDbSessions ?? null,
|
|
3204
|
-
playName: input.req.playName,
|
|
3236
|
+
...sessionScope,
|
|
3205
3237
|
tableNamespace: input.tableNamespace,
|
|
3206
3238
|
sheetContract: augmentSheetContractWithDatasetFields({
|
|
3207
3239
|
contract: requireSheetContract(input.req, input.tableNamespace),
|
|
3208
|
-
rows
|
|
3240
|
+
rows,
|
|
3209
3241
|
outputFields,
|
|
3210
3242
|
}),
|
|
3211
|
-
rows
|
|
3243
|
+
rows,
|
|
3212
3244
|
outputFields,
|
|
3213
|
-
runId: input.req.runId,
|
|
3214
|
-
userEmail: input.req.userEmail,
|
|
3215
3245
|
});
|
|
3216
3246
|
}
|
|
3217
3247
|
|
|
@@ -3231,22 +3261,18 @@ async function prepareMapRows(input: {
|
|
|
3231
3261
|
if (input.rows.length === 0) {
|
|
3232
3262
|
return { inserted: 0, skipped: 0, pendingRows: [], completedRows: [] };
|
|
3233
3263
|
}
|
|
3264
|
+
const sessionScope = runtimeSheetSessionScope(input.req);
|
|
3265
|
+
const rows = input.rows.map((row) => runtimeCsvStorageRow(row));
|
|
3234
3266
|
const result = await harnessStartSheetDataset({
|
|
3235
|
-
|
|
3236
|
-
executorToken: input.req.executorToken,
|
|
3237
|
-
orgId: input.req.orgId,
|
|
3238
|
-
preloadedDbSessions: input.req.preloadedDbSessions ?? null,
|
|
3239
|
-
playName: input.req.playName,
|
|
3267
|
+
...sessionScope,
|
|
3240
3268
|
tableNamespace: input.tableNamespace,
|
|
3241
3269
|
sheetContract: augmentSheetContractWithDatasetFields({
|
|
3242
3270
|
contract: requireSheetContract(input.req, input.tableNamespace),
|
|
3243
|
-
rows
|
|
3271
|
+
rows,
|
|
3244
3272
|
outputFields: input.outputFields,
|
|
3245
3273
|
}),
|
|
3246
|
-
rows
|
|
3247
|
-
runId: input.req.runId,
|
|
3274
|
+
rows,
|
|
3248
3275
|
inputOffset: input.inputOffset,
|
|
3249
|
-
userEmail: input.req.userEmail,
|
|
3250
3276
|
cellPolicies: input.cellPolicies,
|
|
3251
3277
|
});
|
|
3252
3278
|
for (const timing of result.timings ?? []) {
|
|
@@ -3418,18 +3444,25 @@ function createCoordinatorRatePort(req: RunRequest): CoordinatorRatePort {
|
|
|
3418
3444
|
*/
|
|
3419
3445
|
type WorkerPacingResolver = (
|
|
3420
3446
|
toolId: string,
|
|
3421
|
-
) => Promise<
|
|
3447
|
+
) => Promise<
|
|
3448
|
+
(ResolvedPacingPolicy & { retrySafeTransientHttp: boolean }) | null
|
|
3449
|
+
>;
|
|
3422
3450
|
|
|
3423
3451
|
function createWorkerPacingResolver(req: RunRequest): WorkerPacingResolver {
|
|
3424
3452
|
const cache = new Map<
|
|
3425
3453
|
string,
|
|
3426
|
-
Promise<
|
|
3454
|
+
Promise<(ResolvedPacingPolicy & { retrySafeTransientHttp: boolean }) | null>
|
|
3427
3455
|
>();
|
|
3428
3456
|
return (toolId: string) => {
|
|
3429
3457
|
const normalized = String(toolId || '').trim();
|
|
3430
3458
|
if (!normalized) return Promise.resolve(null);
|
|
3431
|
-
const builtin =
|
|
3432
|
-
if (builtin)
|
|
3459
|
+
const builtin = pacingPolicyForTool(normalized, []);
|
|
3460
|
+
if (builtin) {
|
|
3461
|
+
return Promise.resolve({
|
|
3462
|
+
...builtin,
|
|
3463
|
+
retrySafeTransientHttp: false,
|
|
3464
|
+
});
|
|
3465
|
+
}
|
|
3433
3466
|
const cached = cache.get(normalized);
|
|
3434
3467
|
if (cached) return cached;
|
|
3435
3468
|
const promise = (async () => {
|
|
@@ -3445,37 +3478,26 @@ function createWorkerPacingResolver(req: RunRequest): WorkerPacingResolver {
|
|
|
3445
3478
|
const body = (await res.json().catch(() => null)) as {
|
|
3446
3479
|
provider?: unknown;
|
|
3447
3480
|
queueHints?: unknown;
|
|
3481
|
+
retry?: unknown;
|
|
3448
3482
|
} | null;
|
|
3449
3483
|
if (!body) return null;
|
|
3450
|
-
const
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
ruleId: record.ruleId,
|
|
3468
|
-
requestsPerWindow: record.requestsPerWindow,
|
|
3469
|
-
windowMs: record.windowMs,
|
|
3470
|
-
maxConcurrency:
|
|
3471
|
-
typeof record.maxConcurrency === 'number'
|
|
3472
|
-
? record.maxConcurrency
|
|
3473
|
-
: null,
|
|
3474
|
-
} satisfies PacingRule,
|
|
3475
|
-
];
|
|
3476
|
-
});
|
|
3477
|
-
if (rules.length === 0) return null;
|
|
3478
|
-
return { provider, rules };
|
|
3484
|
+
const pacing = pacingPolicyFromUnknownQueueHints(body.queueHints);
|
|
3485
|
+
const retry =
|
|
3486
|
+
body.retry &&
|
|
3487
|
+
typeof body.retry === 'object' &&
|
|
3488
|
+
!Array.isArray(body.retry)
|
|
3489
|
+
? (body.retry as Record<string, unknown>)
|
|
3490
|
+
: {};
|
|
3491
|
+
return {
|
|
3492
|
+
...(pacing ?? {
|
|
3493
|
+
provider:
|
|
3494
|
+
typeof body.provider === 'string' && body.provider.trim()
|
|
3495
|
+
? body.provider.trim()
|
|
3496
|
+
: '',
|
|
3497
|
+
rules: [],
|
|
3498
|
+
}),
|
|
3499
|
+
retrySafeTransientHttp: retry.retrySafeTransientHttp === true,
|
|
3500
|
+
};
|
|
3479
3501
|
})();
|
|
3480
3502
|
cache.set(normalized, promise);
|
|
3481
3503
|
return promise;
|
|
@@ -3930,7 +3952,7 @@ function createMinimalWorkerCtx(
|
|
|
3930
3952
|
cellPolicies,
|
|
3931
3953
|
rows: chunkEntries.map(({ row, rowKey }) => ({
|
|
3932
3954
|
...row,
|
|
3933
|
-
|
|
3955
|
+
...mapRowOutcomeRuntimeFields({ key: rowKey }),
|
|
3934
3956
|
})),
|
|
3935
3957
|
inputOffset: baseOffset + chunkStart,
|
|
3936
3958
|
});
|
|
@@ -3969,9 +3991,8 @@ function createMinimalWorkerCtx(
|
|
|
3969
3991
|
const preparedKeys = new Set<string>();
|
|
3970
3992
|
for (const row of prepared.pendingRows) {
|
|
3971
3993
|
const key =
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
: derivePlayRowIdentity(publicCsvInputRow(row), name);
|
|
3994
|
+
resolveMapRowOutcomeKey(row) ??
|
|
3995
|
+
derivePlayRowIdentity(publicCsvInputRow(row), name);
|
|
3975
3996
|
if (key) {
|
|
3976
3997
|
pendingKeys.add(key);
|
|
3977
3998
|
pendingRowsByKey.set(key, row);
|
|
@@ -3980,9 +4001,8 @@ function createMinimalWorkerCtx(
|
|
|
3980
4001
|
}
|
|
3981
4002
|
for (const row of prepared.completedRows) {
|
|
3982
4003
|
const key =
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
: derivePlayRowIdentity(publicCsvInputRow(row), name);
|
|
4004
|
+
resolveMapRowOutcomeKey(row) ??
|
|
4005
|
+
derivePlayRowIdentity(publicCsvInputRow(row), name);
|
|
3986
4006
|
if (key) {
|
|
3987
4007
|
completedKeys.add(key);
|
|
3988
4008
|
preparedKeys.add(key);
|
|
@@ -4193,32 +4213,27 @@ function createMinimalWorkerCtx(
|
|
|
4193
4213
|
outputFields,
|
|
4194
4214
|
extraOutputFields: Array.from(generatedOutputFields),
|
|
4195
4215
|
rows: [
|
|
4196
|
-
...rowsToPersist.map(({ row, executedIndex }) =>
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
uniqueRowsToExecuteEntries[executedIndex]!.rowKey,
|
|
4206
|
-
})),
|
|
4216
|
+
...rowsToPersist.map(({ row, executedIndex }) =>
|
|
4217
|
+
mapRowOutcomeRuntimeRow(
|
|
4218
|
+
completedMapRowOutcome({
|
|
4219
|
+
key: uniqueRowsToExecuteEntries[executedIndex]!.rowKey,
|
|
4220
|
+
data: row,
|
|
4221
|
+
cellMetaPatch: executedCellMetaPatches[executedIndex],
|
|
4222
|
+
}),
|
|
4223
|
+
),
|
|
4224
|
+
),
|
|
4207
4225
|
// Failed rows persist as recoverable `_status='failed'` sheet
|
|
4208
4226
|
// rows: partial data + per-cell failure meta + the row error.
|
|
4209
|
-
...failedRowsToPersist.map(({ failure, executedIndex }) =>
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
__deeplineRowStatus: 'failed',
|
|
4220
|
-
__deeplineRowError: failure.error,
|
|
4221
|
-
})),
|
|
4227
|
+
...failedRowsToPersist.map(({ failure, executedIndex }) =>
|
|
4228
|
+
mapRowOutcomeRuntimeRow(
|
|
4229
|
+
failedMapRowOutcome({
|
|
4230
|
+
key: uniqueRowsToExecuteEntries[executedIndex]!.rowKey,
|
|
4231
|
+
data: failure.row,
|
|
4232
|
+
cellMetaPatch: executedCellMetaPatches[executedIndex],
|
|
4233
|
+
error: failure.error,
|
|
4234
|
+
}),
|
|
4235
|
+
),
|
|
4236
|
+
),
|
|
4222
4237
|
],
|
|
4223
4238
|
});
|
|
4224
4239
|
for (const { executedIndex } of rowsToPersist) {
|
|
@@ -4324,6 +4339,7 @@ function createMinimalWorkerCtx(
|
|
|
4324
4339
|
waterfallOpts,
|
|
4325
4340
|
callbacks,
|
|
4326
4341
|
workflowStep,
|
|
4342
|
+
resolveToolPacing,
|
|
4327
4343
|
),
|
|
4328
4344
|
};
|
|
4329
4345
|
let activeField: string | null = null;
|
|
@@ -4596,12 +4612,12 @@ function createMinimalWorkerCtx(
|
|
|
4596
4612
|
const resultByKey = new Map<string, T & Record<string, unknown>>();
|
|
4597
4613
|
for (const completedRow of prepared.completedRows) {
|
|
4598
4614
|
const key =
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
: derivePlayRowIdentity(publicCsvInputRow(completedRow), name);
|
|
4615
|
+
resolveMapRowOutcomeKey(completedRow) ??
|
|
4616
|
+
derivePlayRowIdentity(publicCsvInputRow(completedRow), name);
|
|
4602
4617
|
if (key) {
|
|
4603
|
-
const cleanedRow =
|
|
4604
|
-
|
|
4618
|
+
const cleanedRow = stripMapRowOutcomeRuntimeFields(
|
|
4619
|
+
publicCsvOutputRow(completedRow),
|
|
4620
|
+
);
|
|
4605
4621
|
resultByKey.set(key, cleanedRow as T & Record<string, unknown>);
|
|
4606
4622
|
}
|
|
4607
4623
|
}
|
|
@@ -5247,6 +5263,7 @@ function createMinimalWorkerCtx(
|
|
|
5247
5263
|
opts,
|
|
5248
5264
|
callbacks,
|
|
5249
5265
|
workflowStep,
|
|
5266
|
+
resolveToolPacing,
|
|
5250
5267
|
);
|
|
5251
5268
|
},
|
|
5252
5269
|
async sleep(ms: number): Promise<void> {
|
|
@@ -2,14 +2,9 @@ import {
|
|
|
2
2
|
cloneCsvAliasedRow,
|
|
3
3
|
stripCsvProjectionMetadata,
|
|
4
4
|
} from '../../../../shared_libs/play-runtime/csv-rename';
|
|
5
|
+
import { copyMapRowOutcomeRuntimeFields } from '../../../../shared_libs/play-runtime/map-row-outcome';
|
|
5
6
|
|
|
6
7
|
const CSV_TEMPLATE_CONTEXT = '__deeplineCsvProjectedValues';
|
|
7
|
-
const RUNTIME_STORAGE_FIELDS = [
|
|
8
|
-
'__deeplineRowKey',
|
|
9
|
-
'__deeplineCellMetaPatch',
|
|
10
|
-
'__deeplineRowStatus',
|
|
11
|
-
'__deeplineRowError',
|
|
12
|
-
] as const;
|
|
13
8
|
|
|
14
9
|
export type CsvRuntimeRow = Record<string, unknown>;
|
|
15
10
|
|
|
@@ -74,18 +69,6 @@ export function publicCsvOutputRow<T extends CsvRuntimeRow>(row: T): T {
|
|
|
74
69
|
return publicCsvInputRow(row);
|
|
75
70
|
}
|
|
76
71
|
|
|
77
|
-
function copyRuntimeStorageFields(
|
|
78
|
-
target: CsvRuntimeRow,
|
|
79
|
-
row: CsvRuntimeRow,
|
|
80
|
-
): CsvRuntimeRow {
|
|
81
|
-
for (const runtimeField of RUNTIME_STORAGE_FIELDS) {
|
|
82
|
-
if (runtimeField in row) {
|
|
83
|
-
target[runtimeField] = row[runtimeField];
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return target;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
72
|
export function publicCsvStorageRow<T extends CsvRuntimeRow>(row: T): T {
|
|
90
73
|
const publicRow = publicCsvInputRow(row) as CsvRuntimeRow;
|
|
91
74
|
const storageRow: CsvRuntimeRow = {};
|
|
@@ -93,7 +76,7 @@ export function publicCsvStorageRow<T extends CsvRuntimeRow>(row: T): T {
|
|
|
93
76
|
if (typeof fieldName !== 'string') continue;
|
|
94
77
|
storageRow[fieldName] = publicRow[fieldName];
|
|
95
78
|
}
|
|
96
|
-
return
|
|
79
|
+
return copyMapRowOutcomeRuntimeFields(storageRow, row) as T;
|
|
97
80
|
}
|
|
98
81
|
|
|
99
82
|
export function runtimeCsvStorageRow<T extends CsvRuntimeRow>(row: T): T {
|