deepline 0.1.80 → 0.1.81
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/README.md +2 -1
- package/dist/cli/index.js +10 -13
- package/dist/cli/index.mjs +10 -13
- package/dist/index.js +8 -8
- package/dist/index.mjs +8 -8
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +1 -1
- package/dist/repo/apps/play-runner-workers/src/entry.ts +258 -250
- package/dist/repo/apps/play-runner-workers/src/runtime/tool-http-errors.ts +43 -1
- package/dist/repo/sdk/src/client.ts +6 -6
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +10 -10
- package/package.json +1 -1
- package/dist/repo/shared_libs/play-runtime/tool-batch-executor.ts +0 -149
|
@@ -61,10 +61,6 @@ import {
|
|
|
61
61
|
type WorkflowStepLike,
|
|
62
62
|
} from './child-play-await';
|
|
63
63
|
import type { AnyBatchOperationStrategy } from '../../../shared_libs/play-runtime/batching-types';
|
|
64
|
-
import {
|
|
65
|
-
createToolBatchExecutor,
|
|
66
|
-
type ToolBatchRequest,
|
|
67
|
-
} from '../../../shared_libs/play-runtime/tool-batch-executor';
|
|
68
64
|
import {
|
|
69
65
|
adaptV2ExecuteResponseToToolResult,
|
|
70
66
|
createToolExecuteResult,
|
|
@@ -137,7 +133,6 @@ import {
|
|
|
137
133
|
import { createHarnessWorkerReceiptStore } from './runtime/harness-receipt-store';
|
|
138
134
|
import {
|
|
139
135
|
applyCsvRenameProjection,
|
|
140
|
-
stripCsvProjectedFields,
|
|
141
136
|
stripCsvProjectionMetadata,
|
|
142
137
|
cloneCsvAliasedRow,
|
|
143
138
|
type CsvRenameOptions,
|
|
@@ -162,7 +157,6 @@ import type {
|
|
|
162
157
|
LiveNodeProgressSnapshot,
|
|
163
158
|
} from './runtime/live-progress';
|
|
164
159
|
import {
|
|
165
|
-
ToolHttpError,
|
|
166
160
|
extractErrorBilling,
|
|
167
161
|
isHardBillingToolHttpError,
|
|
168
162
|
normalizeToolHttpErrorMessage,
|
|
@@ -597,6 +591,7 @@ type WorkerCtxCallbacks = {
|
|
|
597
591
|
onNodeProgress?: (input: {
|
|
598
592
|
nodeId: string;
|
|
599
593
|
progress: LiveNodeProgressSnapshot;
|
|
594
|
+
forceFlush?: boolean;
|
|
600
595
|
}) => void;
|
|
601
596
|
onMapStarted?: (nodeId: string, at?: number) => void;
|
|
602
597
|
onMapCompleted?: (nodeId: string, at?: number) => void;
|
|
@@ -685,12 +680,17 @@ function makeRequestId(): string {
|
|
|
685
680
|
}
|
|
686
681
|
|
|
687
682
|
function publicCsvInputRow<T extends Record<string, unknown>>(row: T): T {
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
683
|
+
const restored = stripCsvProjectionMetadata(row) as Record<string, unknown>;
|
|
684
|
+
const publicRow: Record<string, unknown> = {};
|
|
685
|
+
for (const fieldName of Reflect.ownKeys(restored)) {
|
|
686
|
+
if (typeof fieldName === 'string' && fieldName.startsWith('__deepline')) {
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
const descriptor = Object.getOwnPropertyDescriptor(restored, fieldName);
|
|
690
|
+
if (!descriptor) continue;
|
|
691
|
+
Object.defineProperty(publicRow, fieldName, descriptor);
|
|
692
|
+
}
|
|
693
|
+
return publicRow as T;
|
|
694
694
|
}
|
|
695
695
|
|
|
696
696
|
function publicCsvOutputRow<T extends Record<string, unknown>>(row: T): T {
|
|
@@ -707,6 +707,27 @@ function publicCsvOutputRow<T extends Record<string, unknown>>(row: T): T {
|
|
|
707
707
|
return publicRow as T;
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
+
function publicCsvStorageRow<T extends Record<string, unknown>>(row: T): T {
|
|
711
|
+
const publicRow = publicCsvInputRow(row) as Record<string, unknown>;
|
|
712
|
+
const storageRow: Record<string, unknown> = {};
|
|
713
|
+
for (const fieldName of Reflect.ownKeys(publicRow)) {
|
|
714
|
+
if (typeof fieldName !== 'string') continue;
|
|
715
|
+
const descriptor = Object.getOwnPropertyDescriptor(publicRow, fieldName);
|
|
716
|
+
if (!descriptor) continue;
|
|
717
|
+
storageRow[fieldName] =
|
|
718
|
+
'value' in descriptor ? descriptor.value : publicRow[fieldName];
|
|
719
|
+
}
|
|
720
|
+
for (const runtimeField of [
|
|
721
|
+
'__deeplineRowKey',
|
|
722
|
+
'__deeplineCellMetaPatch',
|
|
723
|
+
]) {
|
|
724
|
+
if (Object.prototype.hasOwnProperty.call(row, runtimeField)) {
|
|
725
|
+
storageRow[runtimeField] = row[runtimeField];
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return storageRow as T;
|
|
729
|
+
}
|
|
730
|
+
|
|
710
731
|
/**
|
|
711
732
|
* Strip credentials and JWT-shaped tokens from any string before it lands in
|
|
712
733
|
* a log buffer or upstream error message. The harness routinely echoes
|
|
@@ -1245,50 +1266,11 @@ async function callToolDirect(
|
|
|
1245
1266
|
onRetryAttempt?: () => void,
|
|
1246
1267
|
): Promise<ToolExecuteResult> {
|
|
1247
1268
|
const { id, toolId, input } = args;
|
|
1248
|
-
if (toolId === 'test_rate_limit') {
|
|
1249
|
-
return wrapWorkerToolResult(
|
|
1250
|
-
toolId,
|
|
1251
|
-
executeSyntheticTestRateLimit(input),
|
|
1252
|
-
syntheticToolMetadata(toolId),
|
|
1253
|
-
);
|
|
1254
|
-
}
|
|
1255
|
-
if (toolId === 'test_batch_rate_limit') {
|
|
1256
|
-
return wrapWorkerToolResult(
|
|
1257
|
-
toolId,
|
|
1258
|
-
await executeSyntheticTestRateLimitBatch(req, input),
|
|
1259
|
-
syntheticToolMetadata(toolId),
|
|
1260
|
-
);
|
|
1261
|
-
}
|
|
1262
1269
|
const path = `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
|
|
1263
1270
|
const maxAttempts = 3;
|
|
1264
1271
|
let lastError: Error | null = null;
|
|
1265
1272
|
|
|
1266
1273
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
1267
|
-
if (toolId === 'test_transient_500' || toolId === 'test_transient_429') {
|
|
1268
|
-
const syntheticResult = executeSyntheticTransientRetry(
|
|
1269
|
-
toolId,
|
|
1270
|
-
input,
|
|
1271
|
-
attempt,
|
|
1272
|
-
);
|
|
1273
|
-
if (syntheticResult.ok) {
|
|
1274
|
-
return wrapWorkerToolResult(
|
|
1275
|
-
toolId,
|
|
1276
|
-
syntheticResult.result,
|
|
1277
|
-
syntheticToolMetadata(toolId),
|
|
1278
|
-
);
|
|
1279
|
-
}
|
|
1280
|
-
lastError = new Error(
|
|
1281
|
-
`tool ${toolId} ${syntheticResult.status} attempt ${attempt}/${maxAttempts}: ${syntheticResult.message}`,
|
|
1282
|
-
);
|
|
1283
|
-
if (attempt >= maxAttempts) {
|
|
1284
|
-
throw lastError;
|
|
1285
|
-
}
|
|
1286
|
-
// Charge the retry budget per attempt, matching the cjs runner.
|
|
1287
|
-
onRetryAttempt?.();
|
|
1288
|
-
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
|
1289
|
-
continue;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
1274
|
const res = await fetchRuntimeApi(req.baseUrl, path, {
|
|
1293
1275
|
method: 'POST',
|
|
1294
1276
|
headers: {
|
|
@@ -1466,7 +1448,7 @@ function parseStringArray(value: unknown): string[] {
|
|
|
1466
1448
|
.filter(Boolean);
|
|
1467
1449
|
}
|
|
1468
1450
|
|
|
1469
|
-
function
|
|
1451
|
+
function toolMetadataFallback(toolId: string): ToolResultMetadataInput {
|
|
1470
1452
|
if (toolId === 'test_rate_limit') {
|
|
1471
1453
|
return {
|
|
1472
1454
|
toolId,
|
|
@@ -1511,193 +1493,6 @@ function wrapWorkerToolResult(
|
|
|
1511
1493
|
});
|
|
1512
1494
|
}
|
|
1513
1495
|
|
|
1514
|
-
async function executeSyntheticTestRateLimitBatch(
|
|
1515
|
-
req: RunRequest,
|
|
1516
|
-
input: Record<string, unknown>,
|
|
1517
|
-
): Promise<Record<string, unknown>> {
|
|
1518
|
-
const delayMs =
|
|
1519
|
-
typeof input.simulated_delay_ms === 'number' &&
|
|
1520
|
-
Number.isInteger(input.simulated_delay_ms) &&
|
|
1521
|
-
input.simulated_delay_ms > 0
|
|
1522
|
-
? input.simulated_delay_ms
|
|
1523
|
-
: 0;
|
|
1524
|
-
if (delayMs > 0) {
|
|
1525
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1526
|
-
}
|
|
1527
|
-
const rawItems = Array.isArray(input.items) ? input.items : [];
|
|
1528
|
-
const items = rawItems
|
|
1529
|
-
.filter((item): item is Record<string, unknown> =>
|
|
1530
|
-
Boolean(item && typeof item === 'object' && !Array.isArray(item)),
|
|
1531
|
-
)
|
|
1532
|
-
.map((item, index) => {
|
|
1533
|
-
const itemKey =
|
|
1534
|
-
typeof item.itemKey === 'string' && item.itemKey.trim()
|
|
1535
|
-
? item.itemKey.trim()
|
|
1536
|
-
: `item-${index}`;
|
|
1537
|
-
const payload =
|
|
1538
|
-
item.payload &&
|
|
1539
|
-
typeof item.payload === 'object' &&
|
|
1540
|
-
!Array.isArray(item.payload)
|
|
1541
|
-
? (item.payload as Record<string, unknown>)
|
|
1542
|
-
: {};
|
|
1543
|
-
return { itemKey, payload };
|
|
1544
|
-
});
|
|
1545
|
-
const batchRequest: ToolBatchRequest = {
|
|
1546
|
-
runId: req.runId,
|
|
1547
|
-
orgId: req.orgId,
|
|
1548
|
-
toolId: 'test_rate_limit',
|
|
1549
|
-
operation: 'test_batch_rate_limit',
|
|
1550
|
-
provider: 'test',
|
|
1551
|
-
items,
|
|
1552
|
-
waterfallId:
|
|
1553
|
-
typeof input.waterfall_id === 'string' ? input.waterfall_id : null,
|
|
1554
|
-
stageId: typeof input.stage === 'string' ? input.stage : null,
|
|
1555
|
-
fieldName: typeof input.field_name === 'string' ? input.field_name : null,
|
|
1556
|
-
mapName: typeof input.map_name === 'string' ? input.map_name : null,
|
|
1557
|
-
chunkIndex:
|
|
1558
|
-
typeof input.chunk_index === 'number' ? input.chunk_index : null,
|
|
1559
|
-
userProvidedRateLimitKey:
|
|
1560
|
-
typeof input.rate_limit_key === 'string' ? input.rate_limit_key : null,
|
|
1561
|
-
providerBatchSize: 200,
|
|
1562
|
-
};
|
|
1563
|
-
const executor = createToolBatchExecutor({
|
|
1564
|
-
async executeProviderBatch({ items: providerItems }) {
|
|
1565
|
-
return providerItems.map((item) => ({
|
|
1566
|
-
itemKey: item.itemKey,
|
|
1567
|
-
result: executeSyntheticTestRateLimit(item.payload),
|
|
1568
|
-
}));
|
|
1569
|
-
},
|
|
1570
|
-
});
|
|
1571
|
-
const result = await executor.executeToolBatch(batchRequest);
|
|
1572
|
-
return {
|
|
1573
|
-
status: 'completed',
|
|
1574
|
-
key: String(input.key ?? 'batch'),
|
|
1575
|
-
provider: 'test',
|
|
1576
|
-
batch: true,
|
|
1577
|
-
batch_size: result.itemCount,
|
|
1578
|
-
provider_batch_count: result.batchCount,
|
|
1579
|
-
items: result.results.map((item) => ({
|
|
1580
|
-
itemKey: item.itemKey,
|
|
1581
|
-
result: item.result,
|
|
1582
|
-
})),
|
|
1583
|
-
};
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
type SyntheticTransientRetryResult =
|
|
1587
|
-
| { ok: true; result: Record<string, unknown> }
|
|
1588
|
-
| { ok: false; status: number; message: string };
|
|
1589
|
-
|
|
1590
|
-
function executeSyntheticTransientRetry(
|
|
1591
|
-
toolId: string,
|
|
1592
|
-
input: Record<string, unknown>,
|
|
1593
|
-
attempt: number,
|
|
1594
|
-
): SyntheticTransientRetryResult {
|
|
1595
|
-
const failuresBeforeSuccess =
|
|
1596
|
-
typeof input.failures_before_success === 'number' &&
|
|
1597
|
-
Number.isInteger(input.failures_before_success) &&
|
|
1598
|
-
input.failures_before_success >= 0
|
|
1599
|
-
? input.failures_before_success
|
|
1600
|
-
: 1;
|
|
1601
|
-
if (attempt <= failuresBeforeSuccess) {
|
|
1602
|
-
const status = toolId === 'test_transient_429' ? 429 : 502;
|
|
1603
|
-
return {
|
|
1604
|
-
ok: false,
|
|
1605
|
-
status,
|
|
1606
|
-
message: `Synthetic transient ${status} for attempt ${attempt}`,
|
|
1607
|
-
};
|
|
1608
|
-
}
|
|
1609
|
-
return {
|
|
1610
|
-
ok: true,
|
|
1611
|
-
result: {
|
|
1612
|
-
status: 'completed',
|
|
1613
|
-
provider: 'test',
|
|
1614
|
-
key: String(input.key ?? 'transient'),
|
|
1615
|
-
attempts: attempt,
|
|
1616
|
-
recovered: attempt > 1,
|
|
1617
|
-
},
|
|
1618
|
-
};
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
function executeSyntheticTestRateLimit(
|
|
1622
|
-
input: Record<string, unknown>,
|
|
1623
|
-
): Record<string, unknown> {
|
|
1624
|
-
if (
|
|
1625
|
-
typeof input.key === 'string' &&
|
|
1626
|
-
input.key.startsWith('public-error-message-regression')
|
|
1627
|
-
) {
|
|
1628
|
-
throw new ToolHttpError(
|
|
1629
|
-
[
|
|
1630
|
-
'tool test_rate_limit 422 attempt 1/1:',
|
|
1631
|
-
'Synthetic public test error with a redacted token=[REDACTED].',
|
|
1632
|
-
'code=TEST_PUBLIC_ERROR.',
|
|
1633
|
-
'failure_description=The fake test provider intentionally raised a typed public error so V2 runner output preserves actionable details.',
|
|
1634
|
-
'operator_hint=Use this no-bill test provider fixture when verifying play runner error rendering.',
|
|
1635
|
-
].join(' '),
|
|
1636
|
-
null,
|
|
1637
|
-
);
|
|
1638
|
-
}
|
|
1639
|
-
const rowNumber =
|
|
1640
|
-
typeof input.row_number === 'number' && Number.isInteger(input.row_number)
|
|
1641
|
-
? input.row_number
|
|
1642
|
-
: null;
|
|
1643
|
-
const leadId = typeof input.lead_id === 'string' ? input.lead_id : null;
|
|
1644
|
-
const matchedDomain =
|
|
1645
|
-
typeof input.matched_domain === 'string' && input.matched_domain.trim()
|
|
1646
|
-
? input.matched_domain.trim()
|
|
1647
|
-
: 'example.com';
|
|
1648
|
-
const matchedPrefix =
|
|
1649
|
-
typeof input.matched_prefix === 'string' && input.matched_prefix.trim()
|
|
1650
|
-
? input.matched_prefix.trim()
|
|
1651
|
-
: (leadId ??
|
|
1652
|
-
(rowNumber !== null
|
|
1653
|
-
? `row${String(rowNumber).padStart(3, '0')}`
|
|
1654
|
-
: 'match'));
|
|
1655
|
-
const matched = syntheticMatchWindow(input, rowNumber);
|
|
1656
|
-
const matchedEmail = matched ? `${matchedPrefix}@${matchedDomain}` : null;
|
|
1657
|
-
const securityGateway =
|
|
1658
|
-
input.emit_security_gateway === true
|
|
1659
|
-
? { email_status: 'valid', mx_security_gateway: true }
|
|
1660
|
-
: {};
|
|
1661
|
-
return {
|
|
1662
|
-
status: 'completed',
|
|
1663
|
-
key: String(input.key || ''),
|
|
1664
|
-
provider: 'test',
|
|
1665
|
-
lead_id: leadId,
|
|
1666
|
-
row_number: rowNumber,
|
|
1667
|
-
matched_result: matchedEmail,
|
|
1668
|
-
email: matchedEmail,
|
|
1669
|
-
value: matchedEmail,
|
|
1670
|
-
batch: false,
|
|
1671
|
-
...securityGateway,
|
|
1672
|
-
};
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
function syntheticMatchWindow(
|
|
1676
|
-
input: Record<string, unknown>,
|
|
1677
|
-
rowNumber: number | null,
|
|
1678
|
-
): boolean {
|
|
1679
|
-
const min =
|
|
1680
|
-
typeof input.match_rows_min === 'number' ? input.match_rows_min : null;
|
|
1681
|
-
const max =
|
|
1682
|
-
typeof input.match_rows_max === 'number' ? input.match_rows_max : null;
|
|
1683
|
-
if (rowNumber === null) return min === null && max === null;
|
|
1684
|
-
if (min !== null && rowNumber < min) return false;
|
|
1685
|
-
if (max !== null && rowNumber > max) return false;
|
|
1686
|
-
const moduloBase =
|
|
1687
|
-
typeof input.match_modulo_base === 'number' && input.match_modulo_base > 0
|
|
1688
|
-
? input.match_modulo_base
|
|
1689
|
-
: null;
|
|
1690
|
-
if (moduloBase !== null) {
|
|
1691
|
-
const equals = Array.isArray(input.match_modulo_equals)
|
|
1692
|
-
? input.match_modulo_equals
|
|
1693
|
-
.filter((entry): entry is number => typeof entry === 'number')
|
|
1694
|
-
.map((entry) => entry % moduloBase)
|
|
1695
|
-
: [];
|
|
1696
|
-
return equals.length > 0 && equals.includes(rowNumber % moduloBase);
|
|
1697
|
-
}
|
|
1698
|
-
return true;
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
1496
|
function isRecordLike(value: unknown): value is Record<string, unknown> {
|
|
1702
1497
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
1703
1498
|
}
|
|
@@ -1791,7 +1586,7 @@ type WorkerToolBatchRequest = {
|
|
|
1791
1586
|
reject: (error: unknown) => void;
|
|
1792
1587
|
};
|
|
1793
1588
|
|
|
1794
|
-
const WORKER_TOOL_BATCH_GRACE_MS =
|
|
1589
|
+
const WORKER_TOOL_BATCH_GRACE_MS = 250;
|
|
1795
1590
|
// Fallback batch-chunk parallelism when a tool declares no provider rate hints.
|
|
1796
1591
|
// Matches the prior hardcoded `Math.min(4, ...)` so undeclared providers keep
|
|
1797
1592
|
// their previous batching behavior; declared providers tighten via the
|
|
@@ -1812,6 +1607,7 @@ class WorkerToolBatchScheduler {
|
|
|
1812
1607
|
private readonly governor: PlayExecutionGovernor,
|
|
1813
1608
|
private readonly resolvePacing: WorkerPacingResolver,
|
|
1814
1609
|
private readonly abortSignal?: AbortSignal,
|
|
1610
|
+
private readonly onRequestsSettled?: (count: number) => void,
|
|
1815
1611
|
) {}
|
|
1816
1612
|
|
|
1817
1613
|
/**
|
|
@@ -1933,6 +1729,7 @@ class WorkerToolBatchScheduler {
|
|
|
1933
1729
|
} catch (error) {
|
|
1934
1730
|
request.reject(error);
|
|
1935
1731
|
} finally {
|
|
1732
|
+
this.onRequestsSettled?.(1);
|
|
1936
1733
|
slot.release();
|
|
1937
1734
|
}
|
|
1938
1735
|
}),
|
|
@@ -1959,6 +1756,7 @@ class WorkerToolBatchScheduler {
|
|
|
1959
1756
|
abortSignal: this.abortSignal,
|
|
1960
1757
|
reportBackpressure: (retryAfterMs) =>
|
|
1961
1758
|
this.reportBackpressure(toolId, retryAfterMs),
|
|
1759
|
+
onRequestsSettled: this.onRequestsSettled,
|
|
1962
1760
|
});
|
|
1963
1761
|
recordRunnerPerfTrace({
|
|
1964
1762
|
req: this.req,
|
|
@@ -1992,12 +1790,25 @@ async function executeBatchedWorkerToolGroup(input: {
|
|
|
1992
1790
|
suggestedParallelism: number;
|
|
1993
1791
|
abortSignal?: AbortSignal;
|
|
1994
1792
|
reportBackpressure: (retryAfterMs: number) => void;
|
|
1793
|
+
onRequestsSettled?: (count: number) => void;
|
|
1995
1794
|
}): Promise<void> {
|
|
1996
1795
|
const compiledBatches = compileRequestsWithStrategy({
|
|
1997
1796
|
requests: input.requests,
|
|
1998
1797
|
strategy: input.strategy,
|
|
1999
1798
|
getPayload: (request) => request.input,
|
|
2000
1799
|
});
|
|
1800
|
+
recordRunnerPerfTrace({
|
|
1801
|
+
req: input.req,
|
|
1802
|
+
phase: 'runner.tool.batch.compile',
|
|
1803
|
+
ms: 0,
|
|
1804
|
+
extra: {
|
|
1805
|
+
sourceOperation: input.strategy.sourceOperation,
|
|
1806
|
+
batchOperation: input.strategy.batchOperation,
|
|
1807
|
+
requests: input.requests.length,
|
|
1808
|
+
batches: compiledBatches.length,
|
|
1809
|
+
batchSizes: compiledBatches.map((batch) => batch.memberRequests.length),
|
|
1810
|
+
},
|
|
1811
|
+
});
|
|
2001
1812
|
|
|
2002
1813
|
await executeChunkedRequests({
|
|
2003
1814
|
requests: compiledBatches,
|
|
@@ -2052,11 +1863,18 @@ async function executeBatchedWorkerToolGroup(input: {
|
|
|
2052
1863
|
wrapWorkerToolResult(
|
|
2053
1864
|
request.toolId,
|
|
2054
1865
|
splitResults[index] ?? null,
|
|
2055
|
-
|
|
1866
|
+
toolMetadataFallback(request.toolId),
|
|
2056
1867
|
),
|
|
2057
1868
|
);
|
|
2058
1869
|
}
|
|
2059
1870
|
}
|
|
1871
|
+
const settledMembers = chunkResults.reduce(
|
|
1872
|
+
(total, entry) => total + entry.request.memberRequests.length,
|
|
1873
|
+
0,
|
|
1874
|
+
);
|
|
1875
|
+
if (settledMembers > 0) {
|
|
1876
|
+
input.onRequestsSettled?.(settledMembers);
|
|
1877
|
+
}
|
|
2060
1878
|
},
|
|
2061
1879
|
}).catch((error) => {
|
|
2062
1880
|
for (const request of input.requests) {
|
|
@@ -3172,10 +2990,10 @@ async function persistCompletedMapRows(input: {
|
|
|
3172
2990
|
tableNamespace: input.tableNamespace,
|
|
3173
2991
|
sheetContract: augmentSheetContractWithDatasetFields({
|
|
3174
2992
|
contract: requireSheetContract(input.req, input.tableNamespace),
|
|
3175
|
-
rows: input.rows,
|
|
2993
|
+
rows: input.rows.map((row) => publicCsvStorageRow(row)),
|
|
3176
2994
|
outputFields,
|
|
3177
2995
|
}),
|
|
3178
|
-
rows: input.rows,
|
|
2996
|
+
rows: input.rows.map((row) => publicCsvStorageRow(row)),
|
|
3179
2997
|
outputFields,
|
|
3180
2998
|
runId: input.req.runId,
|
|
3181
2999
|
userEmail: input.req.userEmail,
|
|
@@ -3206,10 +3024,10 @@ async function prepareMapRows(input: {
|
|
|
3206
3024
|
tableNamespace: input.tableNamespace,
|
|
3207
3025
|
sheetContract: augmentSheetContractWithDatasetFields({
|
|
3208
3026
|
contract: requireSheetContract(input.req, input.tableNamespace),
|
|
3209
|
-
rows: input.rows,
|
|
3027
|
+
rows: input.rows.map((row) => publicCsvStorageRow(row)),
|
|
3210
3028
|
outputFields: input.outputFields,
|
|
3211
3029
|
}),
|
|
3212
|
-
rows: input.rows.map((row) => (
|
|
3030
|
+
rows: input.rows.map((row) => publicCsvStorageRow(row)),
|
|
3213
3031
|
runId: input.req.runId,
|
|
3214
3032
|
userEmail: input.req.userEmail,
|
|
3215
3033
|
cellPolicies: input.cellPolicies,
|
|
@@ -3334,18 +3152,65 @@ function childPipelineNeedsWorkflowScheduler(
|
|
|
3334
3152
|
* fail OPEN — grant immediately — matching customer-rate-limiter semantics so a
|
|
3335
3153
|
* miswired binding degrades pacing without stalling the run.
|
|
3336
3154
|
*/
|
|
3337
|
-
function createCoordinatorRatePort(): CoordinatorRatePort {
|
|
3155
|
+
function createCoordinatorRatePort(req: RunRequest): CoordinatorRatePort {
|
|
3338
3156
|
return {
|
|
3339
3157
|
async rateAcquire(input) {
|
|
3340
3158
|
const binding = cachedCoordinatorBinding;
|
|
3341
3159
|
if (!binding?.rateAcquire) {
|
|
3342
|
-
|
|
3160
|
+
const coordinatorUrl = req.coordinatorUrl?.trim();
|
|
3161
|
+
if (!coordinatorUrl) {
|
|
3162
|
+
throw new Error('Coordinator rate acquire is unavailable.');
|
|
3163
|
+
}
|
|
3164
|
+
const res = await fetch(`${coordinatorUrl.replace(/\/$/, '')}/rate-acquire`, {
|
|
3165
|
+
method: 'POST',
|
|
3166
|
+
headers: {
|
|
3167
|
+
'x-deepline-request-id': makeRequestId(),
|
|
3168
|
+
...coordinatorRequestHeaders({
|
|
3169
|
+
runId: req.runId,
|
|
3170
|
+
contentType: 'application/json',
|
|
3171
|
+
internalToken: req.coordinatorInternalToken,
|
|
3172
|
+
}),
|
|
3173
|
+
},
|
|
3174
|
+
body: JSON.stringify(input),
|
|
3175
|
+
});
|
|
3176
|
+
if (!res.ok) {
|
|
3177
|
+
const text = await res.text().catch(() => '');
|
|
3178
|
+
throw new Error(
|
|
3179
|
+
`Coordinator rate acquire failed (${res.status}): ${text}`,
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
return (await res.json()) as { granted: number; waitMs: number };
|
|
3343
3183
|
}
|
|
3344
3184
|
return await binding.rateAcquire(input);
|
|
3345
3185
|
},
|
|
3346
3186
|
async ratePenalize(input) {
|
|
3347
3187
|
const binding = cachedCoordinatorBinding;
|
|
3348
|
-
if (!binding?.ratePenalize)
|
|
3188
|
+
if (!binding?.ratePenalize) {
|
|
3189
|
+
const coordinatorUrl = req.coordinatorUrl?.trim();
|
|
3190
|
+
if (!coordinatorUrl) return;
|
|
3191
|
+
const res = await fetch(
|
|
3192
|
+
`${coordinatorUrl.replace(/\/$/, '')}/rate-penalize`,
|
|
3193
|
+
{
|
|
3194
|
+
method: 'POST',
|
|
3195
|
+
headers: {
|
|
3196
|
+
'x-deepline-request-id': makeRequestId(),
|
|
3197
|
+
...coordinatorRequestHeaders({
|
|
3198
|
+
runId: req.runId,
|
|
3199
|
+
contentType: 'application/json',
|
|
3200
|
+
internalToken: req.coordinatorInternalToken,
|
|
3201
|
+
}),
|
|
3202
|
+
},
|
|
3203
|
+
body: JSON.stringify(input),
|
|
3204
|
+
},
|
|
3205
|
+
);
|
|
3206
|
+
if (!res.ok) {
|
|
3207
|
+
const text = await res.text().catch(() => '');
|
|
3208
|
+
throw new Error(
|
|
3209
|
+
`Coordinator rate penalize failed (${res.status}): ${text}`,
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3349
3214
|
await binding.ratePenalize(input);
|
|
3350
3215
|
},
|
|
3351
3216
|
};
|
|
@@ -3477,7 +3342,7 @@ function createGovernorForRun(req: RunRequest): {
|
|
|
3477
3342
|
orgId: req.orgId,
|
|
3478
3343
|
rootRunId: req.playCallGovernance?.rootRunId ?? req.runId,
|
|
3479
3344
|
},
|
|
3480
|
-
rateState: new CoordinatorRateStateBackend(createCoordinatorRatePort()),
|
|
3345
|
+
rateState: new CoordinatorRateStateBackend(createCoordinatorRatePort(req)),
|
|
3481
3346
|
resolvePacing,
|
|
3482
3347
|
resume: resumeGovernanceFromRequest(req),
|
|
3483
3348
|
});
|
|
@@ -3660,6 +3525,7 @@ function createMinimalWorkerCtx(
|
|
|
3660
3525
|
...progress,
|
|
3661
3526
|
updatedAt: progress.updatedAt ?? nowMs(),
|
|
3662
3527
|
},
|
|
3528
|
+
forceFlush: true,
|
|
3663
3529
|
});
|
|
3664
3530
|
};
|
|
3665
3531
|
const formatMapProgressMessage = (completed: number, total?: number) =>
|
|
@@ -3789,6 +3655,18 @@ function createMinimalWorkerCtx(
|
|
|
3789
3655
|
completedRows: prepared.completedRows.length,
|
|
3790
3656
|
},
|
|
3791
3657
|
});
|
|
3658
|
+
updateMapProgress({
|
|
3659
|
+
completed: prepared.completedRows.length,
|
|
3660
|
+
total: chunkRows.length,
|
|
3661
|
+
startedAt: mapStartedAt,
|
|
3662
|
+
message:
|
|
3663
|
+
prepared.pendingRows.length > 0
|
|
3664
|
+
? `${prepared.pendingRows.length.toLocaleString()} rows queued`
|
|
3665
|
+
: formatMapProgressMessage(
|
|
3666
|
+
prepared.completedRows.length,
|
|
3667
|
+
chunkRows.length,
|
|
3668
|
+
),
|
|
3669
|
+
});
|
|
3792
3670
|
const pendingKeys = new Set<string>();
|
|
3793
3671
|
const pendingRowsByKey = new Map<string, Record<string, unknown>>();
|
|
3794
3672
|
const completedKeys = new Set<string>();
|
|
@@ -3836,6 +3714,34 @@ function createMinimalWorkerCtx(
|
|
|
3836
3714
|
0,
|
|
3837
3715
|
prepared.skipped - missingPreparedRows.length,
|
|
3838
3716
|
);
|
|
3717
|
+
let settledToolRequests = 0;
|
|
3718
|
+
let lastToolProgressAt = 0;
|
|
3719
|
+
const reportSettledToolRequests = (count: number) => {
|
|
3720
|
+
if (count <= 0) return;
|
|
3721
|
+
settledToolRequests += count;
|
|
3722
|
+
const now = nowMs();
|
|
3723
|
+
const estimatedCompleted = Math.min(
|
|
3724
|
+
chunkRows.length,
|
|
3725
|
+
prepared.completedRows.length + settledToolRequests,
|
|
3726
|
+
);
|
|
3727
|
+
const isTerminalEstimate = estimatedCompleted >= chunkRows.length;
|
|
3728
|
+
if (
|
|
3729
|
+
!isTerminalEstimate &&
|
|
3730
|
+
now - lastToolProgressAt < RUN_LEDGER_FLUSH_INTERVAL_MS
|
|
3731
|
+
) {
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
lastToolProgressAt = now;
|
|
3735
|
+
updateMapProgress({
|
|
3736
|
+
completed: estimatedCompleted,
|
|
3737
|
+
total: chunkRows.length,
|
|
3738
|
+
startedAt: mapStartedAt,
|
|
3739
|
+
message: formatMapProgressMessage(
|
|
3740
|
+
estimatedCompleted,
|
|
3741
|
+
chunkRows.length,
|
|
3742
|
+
),
|
|
3743
|
+
});
|
|
3744
|
+
};
|
|
3839
3745
|
// Row concurrency comes from the Governor: an explicit map concurrency is
|
|
3840
3746
|
// clamped to the policy row-max, otherwise the policy default. Each row
|
|
3841
3747
|
// body additionally acquires a global row slot (the Governor's rowMax
|
|
@@ -3863,6 +3769,7 @@ function createMinimalWorkerCtx(
|
|
|
3863
3769
|
governor,
|
|
3864
3770
|
resolveToolPacing,
|
|
3865
3771
|
abortSignal,
|
|
3772
|
+
reportSettledToolRequests,
|
|
3866
3773
|
);
|
|
3867
3774
|
const generatedOutputFields = new Set<string>();
|
|
3868
3775
|
let idx = 0;
|
|
@@ -5398,6 +5305,100 @@ async function executeRunRequest(
|
|
|
5398
5305
|
|
|
5399
5306
|
const stepProgressSnapshot = () => ({ ...stepProgressByNodeId });
|
|
5400
5307
|
|
|
5308
|
+
const publishCoordinatorProgressEvent = async (
|
|
5309
|
+
occurredAt: number,
|
|
5310
|
+
): Promise<void> => {
|
|
5311
|
+
const coordinatorUrl = req.coordinatorUrl?.trim();
|
|
5312
|
+
if (!coordinatorUrl) {
|
|
5313
|
+
recordRunnerPerfTrace({
|
|
5314
|
+
req,
|
|
5315
|
+
phase: 'runner.coordinator_progress_publish',
|
|
5316
|
+
ms: 0,
|
|
5317
|
+
extra: { status: 'skipped_no_url' },
|
|
5318
|
+
});
|
|
5319
|
+
return;
|
|
5320
|
+
}
|
|
5321
|
+
const publishStartedAt = nowMs();
|
|
5322
|
+
const liveNodeProgress = stepProgressSnapshot();
|
|
5323
|
+
const activeEntry =
|
|
5324
|
+
Object.entries(liveNodeProgress).find(
|
|
5325
|
+
([, progress]) => typeof progress.completedAt !== 'number',
|
|
5326
|
+
) ?? Object.entries(liveNodeProgress).at(-1);
|
|
5327
|
+
const activeNodeId = activeEntry?.[0] ?? null;
|
|
5328
|
+
const activeProgress = activeEntry?.[1] ?? null;
|
|
5329
|
+
const activeArtifactTableNamespace =
|
|
5330
|
+
typeof activeProgress?.artifactTableNamespace === 'string'
|
|
5331
|
+
? activeProgress.artifactTableNamespace
|
|
5332
|
+
: null;
|
|
5333
|
+
const activeCompleted =
|
|
5334
|
+
typeof activeProgress?.completed === 'number'
|
|
5335
|
+
? activeProgress.completed
|
|
5336
|
+
: null;
|
|
5337
|
+
const activeTotal =
|
|
5338
|
+
typeof activeProgress?.total === 'number' ? activeProgress.total : null;
|
|
5339
|
+
const activeMessage =
|
|
5340
|
+
typeof activeProgress?.message === 'string'
|
|
5341
|
+
? activeProgress.message
|
|
5342
|
+
: null;
|
|
5343
|
+
const response = await fetch(
|
|
5344
|
+
`${coordinatorUrl.replace(/\/$/, '')}/dedup/${encodeURIComponent(
|
|
5345
|
+
req.runId,
|
|
5346
|
+
)}/event-add`,
|
|
5347
|
+
{
|
|
5348
|
+
method: 'POST',
|
|
5349
|
+
headers: {
|
|
5350
|
+
'x-deepline-request-id': makeRequestId(),
|
|
5351
|
+
...coordinatorRequestHeaders({
|
|
5352
|
+
runId: req.runId,
|
|
5353
|
+
contentType: 'application/json',
|
|
5354
|
+
internalToken: req.coordinatorInternalToken,
|
|
5355
|
+
}),
|
|
5356
|
+
},
|
|
5357
|
+
body: JSON.stringify({
|
|
5358
|
+
runId: req.runId,
|
|
5359
|
+
type: 'progress',
|
|
5360
|
+
status: 'running',
|
|
5361
|
+
ts: occurredAt,
|
|
5362
|
+
logs: runLogBuffer,
|
|
5363
|
+
activeNodeId,
|
|
5364
|
+
activeArtifactTableNamespace,
|
|
5365
|
+
updatedAt: occurredAt,
|
|
5366
|
+
liveNodeProgress,
|
|
5367
|
+
}),
|
|
5368
|
+
},
|
|
5369
|
+
);
|
|
5370
|
+
if (!response.ok) {
|
|
5371
|
+
recordRunnerPerfTrace({
|
|
5372
|
+
req,
|
|
5373
|
+
phase: 'runner.coordinator_progress_publish',
|
|
5374
|
+
ms: nowMs() - publishStartedAt,
|
|
5375
|
+
extra: {
|
|
5376
|
+
status: 'failed',
|
|
5377
|
+
httpStatus: response.status,
|
|
5378
|
+
activeNodeId,
|
|
5379
|
+
activeArtifactTableNamespace,
|
|
5380
|
+
activeCompleted,
|
|
5381
|
+
activeTotal,
|
|
5382
|
+
activeMessage,
|
|
5383
|
+
},
|
|
5384
|
+
});
|
|
5385
|
+
throw new Error(`coordinator progress event failed ${response.status}`);
|
|
5386
|
+
}
|
|
5387
|
+
recordRunnerPerfTrace({
|
|
5388
|
+
req,
|
|
5389
|
+
phase: 'runner.coordinator_progress_publish',
|
|
5390
|
+
ms: nowMs() - publishStartedAt,
|
|
5391
|
+
extra: {
|
|
5392
|
+
status: 'ok',
|
|
5393
|
+
activeNodeId,
|
|
5394
|
+
activeArtifactTableNamespace,
|
|
5395
|
+
activeCompleted,
|
|
5396
|
+
activeTotal,
|
|
5397
|
+
activeMessage,
|
|
5398
|
+
},
|
|
5399
|
+
});
|
|
5400
|
+
};
|
|
5401
|
+
|
|
5401
5402
|
const appendStepLifecycleEvent = (event: PlayStepLifecycleEvent) => {
|
|
5402
5403
|
updateStepProgress({
|
|
5403
5404
|
nodeId: event.nodeId,
|
|
@@ -5465,6 +5466,12 @@ async function executeRunRequest(
|
|
|
5465
5466
|
progress.artifactTableNamespace === null
|
|
5466
5467
|
? { artifactTableNamespace: progress.artifactTableNamespace }
|
|
5467
5468
|
: {}),
|
|
5469
|
+
...(typeof progress.startedAt === 'number'
|
|
5470
|
+
? { startedAt: progress.startedAt }
|
|
5471
|
+
: {}),
|
|
5472
|
+
...(typeof progress.completedAt === 'number'
|
|
5473
|
+
? { completedAt: progress.completedAt }
|
|
5474
|
+
: {}),
|
|
5468
5475
|
updatedAt:
|
|
5469
5476
|
typeof progress.updatedAt === 'number'
|
|
5470
5477
|
? progress.updatedAt
|
|
@@ -5513,6 +5520,7 @@ async function executeRunRequest(
|
|
|
5513
5520
|
pendingLedgerEvents = [...events, ...pendingLedgerEvents];
|
|
5514
5521
|
throw new Error('runtime run-ledger append failed');
|
|
5515
5522
|
}
|
|
5523
|
+
await publishCoordinatorProgressEvent(now).catch(() => undefined);
|
|
5516
5524
|
})
|
|
5517
5525
|
.catch(() => undefined);
|
|
5518
5526
|
};
|
|
@@ -5556,7 +5564,7 @@ async function executeRunRequest(
|
|
|
5556
5564
|
const workerCallbacks: WorkerCtxCallbacks = {
|
|
5557
5565
|
onNodeProgress: (input) => {
|
|
5558
5566
|
updateStepProgress(input);
|
|
5559
|
-
flushLedgerEvents(
|
|
5567
|
+
flushLedgerEvents(Boolean(input.forceFlush));
|
|
5560
5568
|
},
|
|
5561
5569
|
onMapStarted: (nodeId, at) => stepLifecycle?.onMapStarted(nodeId, at),
|
|
5562
5570
|
onMapCompleted: (nodeId, at) => stepLifecycle?.onMapCompleted(nodeId, at),
|