deepline 0.1.210 → 0.1.212
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 +203 -12
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +23 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +15 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-latency-profile.ts +90 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +162 -100
- package/dist/bundling-sources/sdk/src/client.ts +118 -3
- package/dist/bundling-sources/sdk/src/play.ts +2 -0
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +253 -30
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +365 -103
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +10 -4
- package/dist/bundling-sources/shared_libs/play-runtime/db-session.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +45 -8
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +14 -2
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +4 -2
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +21 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +128 -6
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +5 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +37 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +19 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +63 -7
- package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +18 -4
- package/dist/bundling-sources/shared_libs/play-runtime/single-flight.ts +31 -0
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +12 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +29 -9
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +22 -1
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +79 -1
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +41 -5
- package/dist/cli/index.js +728 -239
- package/dist/cli/index.mjs +728 -239
- package/dist/index.d.mts +93 -7
- package/dist/index.d.ts +93 -7
- package/dist/index.js +245 -46
- package/dist/index.mjs +245 -46
- package/dist/plays/bundle-play-file.mjs +65 -4
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isPlayDataset,
|
|
19
19
|
iteratePlayDatasetInputPages,
|
|
20
20
|
materializePlayDatasetInput,
|
|
21
|
+
resolveMaterializeLimitCap,
|
|
21
22
|
} from '@shared_libs/plays/dataset';
|
|
22
23
|
import type { PlayDataset, PlayDatasetInput } from '@shared_libs/plays/dataset';
|
|
23
24
|
import {
|
|
@@ -105,6 +106,7 @@ import {
|
|
|
105
106
|
parseToolExecuteAuthScopeChangedError,
|
|
106
107
|
ToolExecuteAuthScopeChangedError,
|
|
107
108
|
} from './tool-execute-retry-policy';
|
|
109
|
+
import { ToolHttpError } from './tool-http-errors';
|
|
108
110
|
import {
|
|
109
111
|
buildDurableCtxCallCacheKey,
|
|
110
112
|
buildDurableRunPlayInvocationScope,
|
|
@@ -261,6 +263,7 @@ const rowContext = new AsyncLocalStorage<{
|
|
|
261
263
|
rowKey?: string;
|
|
262
264
|
mapScope?: MapExecutionScope;
|
|
263
265
|
}>();
|
|
266
|
+
const toolExecutionOverrides = new AsyncLocalStorage<{ force: boolean }>();
|
|
264
267
|
const PROGRESS_HEARTBEAT_INTERVAL_MS = 1_000;
|
|
265
268
|
const PURE_JS_HEARTBEAT_ROW_INTERVAL = 250;
|
|
266
269
|
/**
|
|
@@ -819,6 +822,111 @@ function stableDigest(value: string): string {
|
|
|
819
822
|
return sha256Hex(value);
|
|
820
823
|
}
|
|
821
824
|
|
|
825
|
+
// Receipt keys contain canonicalized tool inputs. Runtime diagnostics must never
|
|
826
|
+
// emit them directly; a short digest is enough to correlate gateway, runner,
|
|
827
|
+
// and waiter observations for one failing run.
|
|
828
|
+
function runtimeReceiptKeyDigest(key: string): string {
|
|
829
|
+
return stableDigest(key).slice(0, 12);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function runtimeReceiptReadSummary(input: {
|
|
833
|
+
requested: readonly string[];
|
|
834
|
+
receipts: readonly (RuntimeStepReceipt | null | undefined)[];
|
|
835
|
+
resolved: ReadonlyMap<string, RuntimeStepReceipt>;
|
|
836
|
+
}): string {
|
|
837
|
+
const statuses = input.receipts.reduce<Record<string, number>>(
|
|
838
|
+
(counts, receipt) => {
|
|
839
|
+
const status = receipt?.status ?? 'missing';
|
|
840
|
+
counts[status] = (counts[status] ?? 0) + 1;
|
|
841
|
+
return counts;
|
|
842
|
+
},
|
|
843
|
+
{},
|
|
844
|
+
);
|
|
845
|
+
const positionalMismatches = input.receipts.reduce(
|
|
846
|
+
(count, receipt, index) =>
|
|
847
|
+
receipt && receipt.key.trim() !== (input.requested[index] ?? '').trim()
|
|
848
|
+
? count + 1
|
|
849
|
+
: count,
|
|
850
|
+
0,
|
|
851
|
+
);
|
|
852
|
+
const missingRequested = input.requested.filter(
|
|
853
|
+
(key) => !input.resolved.has(key),
|
|
854
|
+
);
|
|
855
|
+
return (
|
|
856
|
+
`requested=${input.requested.length} returned=${input.receipts.length} ` +
|
|
857
|
+
`resolved=${input.resolved.size} positional_mismatches=${positionalMismatches} ` +
|
|
858
|
+
`request_digests=${input.requested.map(runtimeReceiptKeyDigest).join(',')} ` +
|
|
859
|
+
`returned_digests=${input.receipts
|
|
860
|
+
.map((receipt) =>
|
|
861
|
+
receipt ? runtimeReceiptKeyDigest(receipt.key) : 'null',
|
|
862
|
+
)
|
|
863
|
+
.join(',')} ` +
|
|
864
|
+
`missing_digests=${missingRequested
|
|
865
|
+
.map(runtimeReceiptKeyDigest)
|
|
866
|
+
.join(',')} ` +
|
|
867
|
+
`statuses=${Object.entries(statuses)
|
|
868
|
+
.map(([status, count]) => `${status}:${count}`)
|
|
869
|
+
.join(',')}`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const runtimeReceiptReadTraceEnabled =
|
|
874
|
+
process.env.DEEPLINE_RUNTIME_RECEIPT_TRACE === '1';
|
|
875
|
+
|
|
876
|
+
function runtimeReceiptReadTrace(input: {
|
|
877
|
+
keys: readonly string[];
|
|
878
|
+
receipts: Array<RuntimeStepReceipt | null>;
|
|
879
|
+
byKey: ReadonlyMap<string, RuntimeStepReceipt>;
|
|
880
|
+
}): string | null {
|
|
881
|
+
if (!runtimeReceiptReadTraceEnabled) return null;
|
|
882
|
+
const digest = (value: string) => stableDigest(value).slice(0, 16);
|
|
883
|
+
const statusCounts = input.receipts.reduce<Record<string, number>>(
|
|
884
|
+
(counts, receipt) => {
|
|
885
|
+
if (receipt) {
|
|
886
|
+
counts[receipt.status] = (counts[receipt.status] ?? 0) + 1;
|
|
887
|
+
}
|
|
888
|
+
return counts;
|
|
889
|
+
},
|
|
890
|
+
{},
|
|
891
|
+
);
|
|
892
|
+
const expiredInFlightCount = input.receipts.filter((receipt) => {
|
|
893
|
+
if (
|
|
894
|
+
receipt?.status !== 'queued' &&
|
|
895
|
+
receipt?.status !== 'pending' &&
|
|
896
|
+
receipt?.status !== 'running'
|
|
897
|
+
) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
const expiresAt = receipt.leaseExpiresAt
|
|
901
|
+
? Date.parse(receipt.leaseExpiresAt)
|
|
902
|
+
: Number.NaN;
|
|
903
|
+
return Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
904
|
+
}).length;
|
|
905
|
+
return (
|
|
906
|
+
'[runtime-receipt-context.read-trace] ' +
|
|
907
|
+
JSON.stringify({
|
|
908
|
+
requestedCount: input.keys.length,
|
|
909
|
+
returnedCount: input.receipts.filter(Boolean).length,
|
|
910
|
+
mapSize: input.byKey.size,
|
|
911
|
+
statusCounts,
|
|
912
|
+
expiredInFlightCount,
|
|
913
|
+
requestedOrderDigest: digest(input.keys.join('\u0000')),
|
|
914
|
+
returnedOrderDigest: digest(
|
|
915
|
+
input.receipts
|
|
916
|
+
.map((receipt) =>
|
|
917
|
+
receipt ? `${digest(receipt.key)}:${receipt.status}` : '-',
|
|
918
|
+
)
|
|
919
|
+
.join('\u0000'),
|
|
920
|
+
),
|
|
921
|
+
mapOrderDigest: digest(
|
|
922
|
+
[...input.byKey.entries()]
|
|
923
|
+
.map(([key, receipt]) => `${digest(key)}:${receipt.status}`)
|
|
924
|
+
.join('\u0000'),
|
|
925
|
+
),
|
|
926
|
+
})
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
822
930
|
type DurableCtxOperation = 'step' | 'tool' | 'fetch';
|
|
823
931
|
|
|
824
932
|
function durableCtxKey(input: {
|
|
@@ -1176,12 +1284,15 @@ export class PlayContextImpl {
|
|
|
1176
1284
|
);
|
|
1177
1285
|
}
|
|
1178
1286
|
assertNoSecretTaint(request.input, 'ctx.tools.execute input');
|
|
1287
|
+
const force =
|
|
1288
|
+
request.force === true ||
|
|
1289
|
+
toolExecutionOverrides.getStore()?.force === true;
|
|
1179
1290
|
return this.executeTool(
|
|
1180
1291
|
request.id.trim(),
|
|
1181
1292
|
request.tool,
|
|
1182
1293
|
request.input,
|
|
1183
1294
|
request.description ||
|
|
1184
|
-
|
|
1295
|
+
force ||
|
|
1185
1296
|
request.staleAfterSeconds !== undefined ||
|
|
1186
1297
|
request.timeoutMs !== undefined ||
|
|
1187
1298
|
request.receiptWaitMs !== undefined
|
|
@@ -1189,7 +1300,7 @@ export class PlayContextImpl {
|
|
|
1189
1300
|
...(request.description
|
|
1190
1301
|
? { description: request.description }
|
|
1191
1302
|
: {}),
|
|
1192
|
-
...(
|
|
1303
|
+
...(force ? { force: true } : {}),
|
|
1193
1304
|
...(request.staleAfterSeconds !== undefined
|
|
1194
1305
|
? { staleAfterSeconds: request.staleAfterSeconds }
|
|
1195
1306
|
: {}),
|
|
@@ -1204,6 +1315,10 @@ export class PlayContextImpl {
|
|
|
1204
1315
|
) as Promise<TOutput>;
|
|
1205
1316
|
},
|
|
1206
1317
|
};
|
|
1318
|
+
|
|
1319
|
+
async __deeplineRunWithForcedTools<T>(run: () => Promise<T>): Promise<T> {
|
|
1320
|
+
return await toolExecutionOverrides.run({ force: true }, run);
|
|
1321
|
+
}
|
|
1207
1322
|
readonly customerDb = {
|
|
1208
1323
|
query: async <TRow extends object = Record<string, unknown>>(
|
|
1209
1324
|
statement:
|
|
@@ -2161,6 +2276,21 @@ export class PlayContextImpl {
|
|
|
2161
2276
|
);
|
|
2162
2277
|
if (normalized) byKey.set(normalized.key, normalized);
|
|
2163
2278
|
}
|
|
2279
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
2280
|
+
this.log(
|
|
2281
|
+
`[runtime-receipt-normalize] ${runtimeReceiptReadSummary({
|
|
2282
|
+
requested: uniqueKeys,
|
|
2283
|
+
receipts,
|
|
2284
|
+
resolved: byKey,
|
|
2285
|
+
})}`,
|
|
2286
|
+
);
|
|
2287
|
+
const trace = runtimeReceiptReadTrace({
|
|
2288
|
+
keys: uniqueKeys,
|
|
2289
|
+
receipts,
|
|
2290
|
+
byKey,
|
|
2291
|
+
});
|
|
2292
|
+
if (trace) this.log(trace);
|
|
2293
|
+
}
|
|
2164
2294
|
return byKey;
|
|
2165
2295
|
}
|
|
2166
2296
|
|
|
@@ -2207,6 +2337,16 @@ export class PlayContextImpl {
|
|
|
2207
2337
|
);
|
|
2208
2338
|
if (normalized) byKey.set(normalized.key, normalized);
|
|
2209
2339
|
}
|
|
2340
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
2341
|
+
this.log(
|
|
2342
|
+
`[runtime-receipt-claim-normalize] reclaim_running=${reclaimRunning} force_refresh=${forceRefresh} force_failed_refresh=${forceFailedRefresh} ` +
|
|
2343
|
+
runtimeReceiptReadSummary({
|
|
2344
|
+
requested: uniqueKeys,
|
|
2345
|
+
receipts,
|
|
2346
|
+
resolved: byKey,
|
|
2347
|
+
}),
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2210
2350
|
return byKey;
|
|
2211
2351
|
}
|
|
2212
2352
|
|
|
@@ -2667,6 +2807,7 @@ export class PlayContextImpl {
|
|
|
2667
2807
|
receiptKeys: keys,
|
|
2668
2808
|
store: this.durableReceiptExecutionStore(),
|
|
2669
2809
|
maxAttempts,
|
|
2810
|
+
log: (message) => this.log(message),
|
|
2670
2811
|
});
|
|
2671
2812
|
}
|
|
2672
2813
|
|
|
@@ -3346,7 +3487,8 @@ export class PlayContextImpl {
|
|
|
3346
3487
|
);
|
|
3347
3488
|
}
|
|
3348
3489
|
const finalWrapped =
|
|
3349
|
-
completed?.status === 'completed' || completed?.status === 'skipped'
|
|
3490
|
+
(completed?.status === 'completed' || completed?.status === 'skipped') &&
|
|
3491
|
+
completed.output !== undefined
|
|
3350
3492
|
? await this.wrapToolExecutionResult({
|
|
3351
3493
|
toolId,
|
|
3352
3494
|
status:
|
|
@@ -3755,6 +3897,13 @@ export class PlayContextImpl {
|
|
|
3755
3897
|
let duplicateReuseCount = 0;
|
|
3756
3898
|
let executedCount = 0;
|
|
3757
3899
|
const previewRows: Record<string, unknown>[] = [];
|
|
3900
|
+
// A bounded dataset may be materialized by authored code immediately
|
|
3901
|
+
// after run(). Keep the rows produced by this exact awaited execution so
|
|
3902
|
+
// that read-after-write does not depend on a separate sheet read becoming
|
|
3903
|
+
// visible. Datasets above the public materialize cap remain fully
|
|
3904
|
+
// streaming and sheet-backed.
|
|
3905
|
+
const materializedResultRowLimit = resolveMaterializeLimitCap();
|
|
3906
|
+
const immediateMaterializedRows: Record<string, unknown>[] = [];
|
|
3758
3907
|
|
|
3759
3908
|
const persistMapRows = async (rows: PersistableMapRow[]) => {
|
|
3760
3909
|
if (!this.#options.onMapRowsCompleted || rows.length === 0) {
|
|
@@ -4038,8 +4187,11 @@ export class PlayContextImpl {
|
|
|
4038
4187
|
this.activeMapCellMeta = null;
|
|
4039
4188
|
|
|
4040
4189
|
for (const row of mapResult.completedRows) {
|
|
4041
|
-
|
|
4042
|
-
previewRows.push(
|
|
4190
|
+
const materializedRow = this.toMaterializedOutputRow(row.data);
|
|
4191
|
+
if (previewRows.length < 5) previewRows.push(materializedRow);
|
|
4192
|
+
if (immediateMaterializedRows.length < materializedResultRowLimit) {
|
|
4193
|
+
immediateMaterializedRows.push(materializedRow);
|
|
4194
|
+
}
|
|
4043
4195
|
}
|
|
4044
4196
|
executedCount += rowsToExecute.length;
|
|
4045
4197
|
successfulCount += mapResult.completedRows.length;
|
|
@@ -4100,6 +4252,42 @@ export class PlayContextImpl {
|
|
|
4100
4252
|
return result.rows.map((row) => this.toMaterializedOutputRow(row));
|
|
4101
4253
|
};
|
|
4102
4254
|
const materializeRuntimeBackedMapRows = async (limit?: number) => {
|
|
4255
|
+
if (limit !== undefined && limit <= 0) return [];
|
|
4256
|
+
const availableResultRows = Math.min(
|
|
4257
|
+
successfulCount,
|
|
4258
|
+
materializedResultRowLimit,
|
|
4259
|
+
);
|
|
4260
|
+
if (
|
|
4261
|
+
immediateMaterializedRows.length === availableResultRows &&
|
|
4262
|
+
(limit !== undefined || successfulCount <= materializedResultRowLimit)
|
|
4263
|
+
) {
|
|
4264
|
+
return limit === undefined
|
|
4265
|
+
? immediateMaterializedRows.slice()
|
|
4266
|
+
: immediateMaterializedRows.slice(0, limit);
|
|
4267
|
+
}
|
|
4268
|
+
const pageSize = 1000;
|
|
4269
|
+
const materialized: Record<string, unknown>[] = [];
|
|
4270
|
+
let offset = 0;
|
|
4271
|
+
while (true) {
|
|
4272
|
+
const remaining =
|
|
4273
|
+
limit === undefined
|
|
4274
|
+
? pageSize
|
|
4275
|
+
: Math.max(0, limit - materialized.length);
|
|
4276
|
+
if (remaining === 0) break;
|
|
4277
|
+
const rows = await readRuntimeBackedMapRows({
|
|
4278
|
+
limit: Math.min(pageSize, remaining),
|
|
4279
|
+
offset,
|
|
4280
|
+
});
|
|
4281
|
+
if (rows.length === 0) break;
|
|
4282
|
+
materialized.push(...rows);
|
|
4283
|
+
if (limit !== undefined && materialized.length >= limit) {
|
|
4284
|
+
return materialized.slice(0, limit);
|
|
4285
|
+
}
|
|
4286
|
+
offset += rows.length;
|
|
4287
|
+
}
|
|
4288
|
+
return materialized;
|
|
4289
|
+
};
|
|
4290
|
+
const materializeFullPersistedMapRows = async (limit?: number) => {
|
|
4103
4291
|
if (limit !== undefined && limit <= 0) return [];
|
|
4104
4292
|
const pageSize = 1000;
|
|
4105
4293
|
const materialized: Record<string, unknown>[] = [];
|
|
@@ -4156,14 +4344,22 @@ export class PlayContextImpl {
|
|
|
4156
4344
|
peek: async (limit) =>
|
|
4157
4345
|
limit <= 0
|
|
4158
4346
|
? []
|
|
4159
|
-
:
|
|
4160
|
-
limit,
|
|
4161
|
-
|
|
4162
|
-
|
|
4347
|
+
: immediateMaterializedRows.length >=
|
|
4348
|
+
Math.min(limit, successfulCount)
|
|
4349
|
+
? immediateMaterializedRows.slice(0, limit)
|
|
4350
|
+
: await readRuntimeBackedMapRows({
|
|
4351
|
+
limit,
|
|
4352
|
+
offset: 0,
|
|
4353
|
+
}),
|
|
4163
4354
|
materialize: materializeRuntimeBackedMapRows,
|
|
4355
|
+
materializeFullPersistedDataset: materializeFullPersistedMapRows,
|
|
4164
4356
|
iterate: () =>
|
|
4165
4357
|
({
|
|
4166
4358
|
async *[Symbol.asyncIterator]() {
|
|
4359
|
+
if (immediateMaterializedRows.length === successfulCount) {
|
|
4360
|
+
for (const row of immediateMaterializedRows) yield row;
|
|
4361
|
+
return;
|
|
4362
|
+
}
|
|
4167
4363
|
const pageSize = 1000;
|
|
4168
4364
|
let offset = 0;
|
|
4169
4365
|
while (true) {
|
|
@@ -4598,39 +4794,33 @@ export class PlayContextImpl {
|
|
|
4598
4794
|
});
|
|
4599
4795
|
}
|
|
4600
4796
|
|
|
4601
|
-
|
|
4797
|
+
// This map's terminal rows have already crossed the persistence barrier
|
|
4798
|
+
// above. Keep that verified snapshot as the authored return value instead
|
|
4799
|
+
// of immediately re-reading the Runtime Sheet projection. A second read
|
|
4800
|
+
// can observe a lagging/dynamically incomplete physical projection even
|
|
4801
|
+
// though the terminal write succeeded (notably for inline-child object
|
|
4802
|
+
// outputs on Absurd), making `materialize()` disagree with durable truth.
|
|
4803
|
+
// Neon remains the backing for later API/export reads of this handle.
|
|
4804
|
+
const terminalRows = results.map((row) =>
|
|
4805
|
+
this.toMaterializedOutputRow(row),
|
|
4806
|
+
);
|
|
4807
|
+
const materializeTerminalRows = async (limit?: number) =>
|
|
4808
|
+
limit === undefined
|
|
4809
|
+
? terminalRows.slice()
|
|
4810
|
+
: terminalRows.slice(0, Math.max(0, limit));
|
|
4811
|
+
const runtimeSheetBacked =
|
|
4602
4812
|
this.#options.runtimeSheetBackedMapDatasets === true &&
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
userEmail: this.#options.userEmail,
|
|
4615
|
-
runId: this.#options.runId!,
|
|
4616
|
-
},
|
|
4617
|
-
{
|
|
4618
|
-
tableNamespace: resolvedTableNamespace,
|
|
4619
|
-
runId: this.#options.runId!,
|
|
4620
|
-
limit: input.limit,
|
|
4621
|
-
offset: input.offset,
|
|
4622
|
-
},
|
|
4623
|
-
);
|
|
4624
|
-
return result.rows.map((row) => this.toMaterializedOutputRow(row));
|
|
4625
|
-
}
|
|
4626
|
-
: null;
|
|
4627
|
-
const materializeRuntimeBackedMapRows = async (limit?: number) => {
|
|
4628
|
-
if (!readRuntimeBackedMapRows) {
|
|
4629
|
-
return limit === undefined
|
|
4630
|
-
? results.map((row) => this.toMaterializedOutputRow(row))
|
|
4631
|
-
: results
|
|
4632
|
-
.slice(0, Math.max(0, limit))
|
|
4633
|
-
.map((row) => this.toMaterializedOutputRow(row));
|
|
4813
|
+
Boolean(
|
|
4814
|
+
this.#options.baseUrl &&
|
|
4815
|
+
this.#options.executorToken &&
|
|
4816
|
+
this.#options.playName &&
|
|
4817
|
+
this.#options.runId,
|
|
4818
|
+
);
|
|
4819
|
+
const materializeFullPersistedMapRows = async (limit?: number) => {
|
|
4820
|
+
if (!runtimeSheetBacked) {
|
|
4821
|
+
throw new Error(
|
|
4822
|
+
'The full persisted dataset is unavailable because this run result has no Runtime Sheet backing.',
|
|
4823
|
+
);
|
|
4634
4824
|
}
|
|
4635
4825
|
if (limit !== undefined && limit <= 0) return [];
|
|
4636
4826
|
const pageSize = 1000;
|
|
@@ -4642,10 +4832,25 @@ export class PlayContextImpl {
|
|
|
4642
4832
|
? pageSize
|
|
4643
4833
|
: Math.max(0, limit - materialized.length);
|
|
4644
4834
|
if (remaining === 0) break;
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4835
|
+
const result = await readRuntimeSheetDatasetRows(
|
|
4836
|
+
{
|
|
4837
|
+
baseUrl: this.#options.baseUrl!,
|
|
4838
|
+
executorToken: this.#options.executorToken!,
|
|
4839
|
+
dbSessionStrategy: this.#options.dbSessionStrategy,
|
|
4840
|
+
playName: this.#options.playName!,
|
|
4841
|
+
userEmail: this.#options.userEmail,
|
|
4842
|
+
runId: this.#options.runId!,
|
|
4843
|
+
},
|
|
4844
|
+
{
|
|
4845
|
+
tableNamespace: resolvedTableNamespace,
|
|
4846
|
+
runId: this.#options.runId!,
|
|
4847
|
+
limit: Math.min(pageSize, remaining),
|
|
4848
|
+
offset,
|
|
4849
|
+
},
|
|
4850
|
+
);
|
|
4851
|
+
const rows = result.rows.map((row) =>
|
|
4852
|
+
this.toMaterializedOutputRow(row),
|
|
4853
|
+
);
|
|
4649
4854
|
if (rows.length === 0) break;
|
|
4650
4855
|
materialized.push(...rows);
|
|
4651
4856
|
if (limit !== undefined && materialized.length >= limit) {
|
|
@@ -4663,7 +4868,7 @@ export class PlayContextImpl {
|
|
|
4663
4868
|
resolvedTableNamespace,
|
|
4664
4869
|
),
|
|
4665
4870
|
count: results.length,
|
|
4666
|
-
backing:
|
|
4871
|
+
backing: runtimeSheetBacked
|
|
4667
4872
|
? {
|
|
4668
4873
|
storage: 'neon_sheet',
|
|
4669
4874
|
sheet: {
|
|
@@ -4689,37 +4894,17 @@ export class PlayContextImpl {
|
|
|
4689
4894
|
},
|
|
4690
4895
|
resolvers: {
|
|
4691
4896
|
count: async () => results.length,
|
|
4692
|
-
peek: async (limit) =>
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
:
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
})
|
|
4700
|
-
: results
|
|
4701
|
-
.slice(0, Math.max(0, limit))
|
|
4702
|
-
.map((row) => this.toMaterializedOutputRow(row)),
|
|
4703
|
-
materialize: materializeRuntimeBackedMapRows,
|
|
4897
|
+
peek: async (limit) => terminalRows.slice(0, Math.max(0, limit)),
|
|
4898
|
+
materialize: materializeTerminalRows,
|
|
4899
|
+
...(runtimeSheetBacked
|
|
4900
|
+
? {
|
|
4901
|
+
materializeFullPersistedDataset: materializeFullPersistedMapRows,
|
|
4902
|
+
}
|
|
4903
|
+
: {}),
|
|
4704
4904
|
iterate: () =>
|
|
4705
4905
|
({
|
|
4706
4906
|
async *[Symbol.asyncIterator]() {
|
|
4707
|
-
|
|
4708
|
-
const pageSize = 1000;
|
|
4709
|
-
let offset = 0;
|
|
4710
|
-
while (true) {
|
|
4711
|
-
const rows = await readRuntimeBackedMapRows({
|
|
4712
|
-
limit: pageSize,
|
|
4713
|
-
offset,
|
|
4714
|
-
});
|
|
4715
|
-
if (rows.length === 0) return;
|
|
4716
|
-
for (const row of rows) {
|
|
4717
|
-
yield row;
|
|
4718
|
-
}
|
|
4719
|
-
offset += rows.length;
|
|
4720
|
-
}
|
|
4721
|
-
}
|
|
4722
|
-
for (const row of results) {
|
|
4907
|
+
for (const row of terminalRows) {
|
|
4723
4908
|
yield row;
|
|
4724
4909
|
}
|
|
4725
4910
|
},
|
|
@@ -6136,11 +6321,6 @@ export class PlayContextImpl {
|
|
|
6136
6321
|
semanticKey: toolRequestIdentity,
|
|
6137
6322
|
force: toolCachePolicy.force,
|
|
6138
6323
|
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
6139
|
-
markSkipped: () => {
|
|
6140
|
-
this.log(
|
|
6141
|
-
`ctx.tools.execute(${toolId}): no-op due completed receipt ${toolRequestIdentity} (label: ${normalizedKey})`,
|
|
6142
|
-
);
|
|
6143
|
-
},
|
|
6144
6324
|
onClaimedResult: (output, receiptKey) =>
|
|
6145
6325
|
markToolExecuteResultExecutionOutcome(output, {
|
|
6146
6326
|
kind: 'live',
|
|
@@ -7094,11 +7274,6 @@ export class PlayContextImpl {
|
|
|
7094
7274
|
egressSlot.release();
|
|
7095
7275
|
}
|
|
7096
7276
|
},
|
|
7097
|
-
markSkipped: (output) => {
|
|
7098
|
-
this.log(
|
|
7099
|
-
`ctx.fetch(${output?.url ?? ''}): no-op due completed receipt`,
|
|
7100
|
-
);
|
|
7101
|
-
},
|
|
7102
7277
|
},
|
|
7103
7278
|
);
|
|
7104
7279
|
}
|
|
@@ -7165,7 +7340,6 @@ export class PlayContextImpl {
|
|
|
7165
7340
|
: options?.semanticKey,
|
|
7166
7341
|
staleAfterSeconds: options?.staleAfterSeconds,
|
|
7167
7342
|
markSkipped: (output) => {
|
|
7168
|
-
this.log(`ctx.step(${normalizedKey}): no-op due completed receipt`);
|
|
7169
7343
|
assertJsonSerializableStepOutput(normalizedKey, output);
|
|
7170
7344
|
},
|
|
7171
7345
|
execute: executeStep,
|
|
@@ -7431,18 +7605,32 @@ export class PlayContextImpl {
|
|
|
7431
7605
|
const forcedExisting = forcedReceiptKeys.length
|
|
7432
7606
|
? await this.getRuntimeStepReceipts(forcedReceiptKeys)
|
|
7433
7607
|
: new Map<string, RuntimeStepReceipt>();
|
|
7434
|
-
|
|
7608
|
+
// A forced refresh must never duplicate a provider call owned by a
|
|
7609
|
+
// live lease. It can, however, immediately claim pending/unleased
|
|
7610
|
+
// work and any expired lease. Waiting first for those receipts turns
|
|
7611
|
+
// an already-recoverable interruption into a five-minute stall.
|
|
7612
|
+
const forcedLiveReceiptKeys = new Set(
|
|
7435
7613
|
[...forcedExisting]
|
|
7436
|
-
.filter(
|
|
7437
|
-
(
|
|
7438
|
-
receipt.status
|
|
7439
|
-
receipt.status
|
|
7440
|
-
receipt.status
|
|
7441
|
-
|
|
7614
|
+
.filter(([, receipt]) => {
|
|
7615
|
+
if (
|
|
7616
|
+
receipt.status !== 'queued' &&
|
|
7617
|
+
receipt.status !== 'pending' &&
|
|
7618
|
+
receipt.status !== 'running'
|
|
7619
|
+
) {
|
|
7620
|
+
return false;
|
|
7621
|
+
}
|
|
7622
|
+
if (!receipt.leaseId?.trim()) return false;
|
|
7623
|
+
const expiresAt = receipt.leaseExpiresAt
|
|
7624
|
+
? Date.parse(receipt.leaseExpiresAt)
|
|
7625
|
+
: Number.NaN;
|
|
7626
|
+
// Keep an unparseable leased receipt conservative. The
|
|
7627
|
+
// fenced claim remains the recovery path after the wait.
|
|
7628
|
+
return !Number.isFinite(expiresAt) || expiresAt > Date.now();
|
|
7629
|
+
})
|
|
7442
7630
|
.map(([receiptKey]) => receiptKey),
|
|
7443
7631
|
);
|
|
7444
7632
|
const claimableForcedReceiptKeys = forcedReceiptKeys.filter(
|
|
7445
|
-
(receiptKey) => !
|
|
7633
|
+
(receiptKey) => !forcedLiveReceiptKeys.has(receiptKey),
|
|
7446
7634
|
);
|
|
7447
7635
|
const claims =
|
|
7448
7636
|
normalReceiptKeys.length > 0
|
|
@@ -7706,16 +7894,39 @@ export class PlayContextImpl {
|
|
|
7706
7894
|
const processExistingRunningReceiptWaits =
|
|
7707
7895
|
async (): Promise<void> => {
|
|
7708
7896
|
if (durableExistingRunningWaits.length === 0) return;
|
|
7897
|
+
const waitingReceiptKeys = durableExistingRunningWaits.map(
|
|
7898
|
+
(wait) => wait.receiptKey,
|
|
7899
|
+
);
|
|
7709
7900
|
const waitMaxAttempts = Math.max(
|
|
7710
7901
|
...durableExistingRunningWaits.map(
|
|
7711
7902
|
(wait) => wait.waitMaxAttempts ?? 0,
|
|
7712
7903
|
),
|
|
7713
7904
|
);
|
|
7905
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
7906
|
+
this.log(
|
|
7907
|
+
`[runtime-receipt-wait] phase=start requested=${waitingReceiptKeys.length} ` +
|
|
7908
|
+
`request_digests=${waitingReceiptKeys
|
|
7909
|
+
.map(runtimeReceiptKeyDigest)
|
|
7910
|
+
.join(',')} max_attempts=${waitMaxAttempts || 'default'}`,
|
|
7911
|
+
);
|
|
7912
|
+
}
|
|
7714
7913
|
const waited = await this.waitForCompletedRuntimeToolReceipts(
|
|
7715
|
-
|
|
7914
|
+
waitingReceiptKeys,
|
|
7716
7915
|
waitMaxAttempts > 0 ? waitMaxAttempts : undefined,
|
|
7717
7916
|
);
|
|
7718
|
-
|
|
7917
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
7918
|
+
this.log(
|
|
7919
|
+
`[runtime-receipt-wait] phase=settled completed=${waited.completed.size} ` +
|
|
7920
|
+
`failed=${waited.failed.size} timed_out=${waited.timedOut.size} ` +
|
|
7921
|
+
`completed_digests=${[...waited.completed.keys()]
|
|
7922
|
+
.map(runtimeReceiptKeyDigest)
|
|
7923
|
+
.join(',')} ` +
|
|
7924
|
+
`timed_out_digests=${[...waited.timedOut]
|
|
7925
|
+
.map(runtimeReceiptKeyDigest)
|
|
7926
|
+
.join(',')}`,
|
|
7927
|
+
);
|
|
7928
|
+
}
|
|
7929
|
+
const deliverySettlements = await Promise.allSettled(
|
|
7719
7930
|
durableExistingRunningWaits.map(async (wait) => {
|
|
7720
7931
|
const failed = waited.failed.get(wait.receiptKey);
|
|
7721
7932
|
if (failed) {
|
|
@@ -7737,6 +7948,28 @@ export class PlayContextImpl {
|
|
|
7737
7948
|
);
|
|
7738
7949
|
}),
|
|
7739
7950
|
);
|
|
7951
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
7952
|
+
const rejected = deliverySettlements.flatMap(
|
|
7953
|
+
(settlement, index) => {
|
|
7954
|
+
if (settlement.status !== 'rejected') return [];
|
|
7955
|
+
const receiptKey =
|
|
7956
|
+
durableExistingRunningWaits[index]?.receiptKey;
|
|
7957
|
+
return receiptKey
|
|
7958
|
+
? [
|
|
7959
|
+
`${runtimeReceiptKeyDigest(receiptKey)}:${
|
|
7960
|
+
settlement.reason instanceof Error
|
|
7961
|
+
? settlement.reason.name
|
|
7962
|
+
: 'non_error'
|
|
7963
|
+
}`,
|
|
7964
|
+
]
|
|
7965
|
+
: ['unknown'];
|
|
7966
|
+
},
|
|
7967
|
+
);
|
|
7968
|
+
this.log(
|
|
7969
|
+
`[runtime-receipt-wait] phase=delivery settled=${deliverySettlements.length} ` +
|
|
7970
|
+
`rejected=${rejected.length} rejected_digests=${rejected.join(',')}`,
|
|
7971
|
+
);
|
|
7972
|
+
}
|
|
7740
7973
|
};
|
|
7741
7974
|
|
|
7742
7975
|
for (const [receiptKey, group] of requestsByReceiptKey) {
|
|
@@ -8222,11 +8455,17 @@ export class PlayContextImpl {
|
|
|
8222
8455
|
// The Governor's tool slot is the single seam for tool-call budget + the
|
|
8223
8456
|
// global tool-concurrency backstop + per-(org, provider) pacing. It blocks
|
|
8224
8457
|
// until all three are satisfied and is held (across retries) until release.
|
|
8458
|
+
const admissionStartedAt = Date.now();
|
|
8225
8459
|
const toolSlot = await this.resourceGovernor.acquireTool({
|
|
8226
8460
|
orgId: this.#options.orgId ?? null,
|
|
8227
8461
|
providerResourceKey: `tool:${toolId}`,
|
|
8228
8462
|
toolId,
|
|
8229
8463
|
});
|
|
8464
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
8465
|
+
this.log(
|
|
8466
|
+
`[perf] tool call id=${toolId} phase=governor_admission elapsed_ms=${Date.now() - admissionStartedAt}`,
|
|
8467
|
+
);
|
|
8468
|
+
}
|
|
8230
8469
|
try {
|
|
8231
8470
|
return await withActiveSpan(
|
|
8232
8471
|
'plays.tool.execute',
|
|
@@ -8252,6 +8491,8 @@ export class PlayContextImpl {
|
|
|
8252
8491
|
|
|
8253
8492
|
while (true) {
|
|
8254
8493
|
let response: Response;
|
|
8494
|
+
let providerCallStartedAt: number | null = null;
|
|
8495
|
+
let providerCallElapsedMs: number | null = null;
|
|
8255
8496
|
const durableCallReceiptKey =
|
|
8256
8497
|
options?.durableCallReceiptKey?.trim() || null;
|
|
8257
8498
|
const executionAuthScopeDigest =
|
|
@@ -8285,8 +8526,14 @@ export class PlayContextImpl {
|
|
|
8285
8526
|
}, timeoutMs)
|
|
8286
8527
|
: null;
|
|
8287
8528
|
try {
|
|
8288
|
-
const
|
|
8529
|
+
const ownershipStartedAt = Date.now();
|
|
8289
8530
|
await options?.beforeProviderCall?.();
|
|
8531
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
8532
|
+
this.log(
|
|
8533
|
+
`[perf] tool call id=${toolId} phase=before_provider_ownership elapsed_ms=${Date.now() - ownershipStartedAt}`,
|
|
8534
|
+
);
|
|
8535
|
+
}
|
|
8536
|
+
providerCallStartedAt = Date.now();
|
|
8290
8537
|
const heartbeatIntervalMs = hasReceiptHeartbeat
|
|
8291
8538
|
? runtimeLeaseHeartbeatIntervalFromExpiry({
|
|
8292
8539
|
leaseExpiresAt: options?.receiptLeaseExpiresAt,
|
|
@@ -8316,6 +8563,7 @@ export class PlayContextImpl {
|
|
|
8316
8563
|
: null;
|
|
8317
8564
|
receiptHeartbeatSupervisor?.start();
|
|
8318
8565
|
try {
|
|
8566
|
+
const integrationFetchStartedAt = Date.now();
|
|
8319
8567
|
response = await fetch(url, {
|
|
8320
8568
|
method: 'POST',
|
|
8321
8569
|
signal: abortController?.signal,
|
|
@@ -8380,10 +8628,16 @@ export class PlayContextImpl {
|
|
|
8380
8628
|
: {}),
|
|
8381
8629
|
}),
|
|
8382
8630
|
});
|
|
8631
|
+
if (runtimeReceiptReadTraceEnabled) {
|
|
8632
|
+
this.log(
|
|
8633
|
+
`[perf] tool call id=${toolId} phase=integration_fetch_headers elapsed_ms=${Date.now() - integrationFetchStartedAt} status=${response.status}`,
|
|
8634
|
+
);
|
|
8635
|
+
}
|
|
8636
|
+
providerCallElapsedMs = Date.now() - providerCallStartedAt;
|
|
8383
8637
|
this.resourceGovernor.observe({
|
|
8384
8638
|
toolId,
|
|
8385
8639
|
providerResourceKey: `tool:${toolId}`,
|
|
8386
|
-
providerLatencyMs:
|
|
8640
|
+
providerLatencyMs: providerCallElapsedMs,
|
|
8387
8641
|
providerSuccess: response.ok,
|
|
8388
8642
|
provider429: response.status === 429,
|
|
8389
8643
|
});
|
|
@@ -8394,16 +8648,18 @@ export class PlayContextImpl {
|
|
|
8394
8648
|
if (heartbeatFailure) {
|
|
8395
8649
|
throw heartbeatFailure;
|
|
8396
8650
|
}
|
|
8397
|
-
|
|
8398
|
-
|
|
8651
|
+
const transportError = abortController?.signal.aborted
|
|
8652
|
+
? abortController.signal.reason instanceof Error
|
|
8399
8653
|
? abortController.signal.reason
|
|
8400
8654
|
: new Error(
|
|
8401
8655
|
`Tool ${toolId} runtime API call timed out after ${timeoutMs}ms.`,
|
|
8402
|
-
)
|
|
8403
|
-
|
|
8656
|
+
)
|
|
8657
|
+
: error;
|
|
8404
8658
|
transportAttempt += 1;
|
|
8405
8659
|
const message =
|
|
8406
|
-
|
|
8660
|
+
transportError instanceof Error
|
|
8661
|
+
? transportError.message
|
|
8662
|
+
: String(transportError);
|
|
8407
8663
|
if (transportAttempt < TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS) {
|
|
8408
8664
|
await this.governor.chargeBudget('retry');
|
|
8409
8665
|
const retryAfterMs =
|
|
@@ -8419,8 +8675,11 @@ export class PlayContextImpl {
|
|
|
8419
8675
|
await this.sleepWithCheckpointHeartbeat(retryAfterMs);
|
|
8420
8676
|
continue;
|
|
8421
8677
|
}
|
|
8422
|
-
throw new
|
|
8678
|
+
throw new ToolHttpError(
|
|
8423
8679
|
`Tool ${toolId} transport failed calling ${url} after ${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS} attempts: ${message}`,
|
|
8680
|
+
null,
|
|
8681
|
+
0,
|
|
8682
|
+
'repairable',
|
|
8424
8683
|
);
|
|
8425
8684
|
} finally {
|
|
8426
8685
|
if (timeoutHandle) {
|
|
@@ -8451,6 +8710,9 @@ export class PlayContextImpl {
|
|
|
8451
8710
|
bodyText: text,
|
|
8452
8711
|
retryAfterHeader: response.headers.get('retry-after'),
|
|
8453
8712
|
transientHttpRetrySafe: retrySafeTransientHttp,
|
|
8713
|
+
...(providerCallElapsedMs !== null
|
|
8714
|
+
? { providerLatencyMs: providerCallElapsedMs }
|
|
8715
|
+
: {}),
|
|
8454
8716
|
});
|
|
8455
8717
|
if (failure.backpressureDelayMs !== null) {
|
|
8456
8718
|
// Feed the server-observed Retry-After back into the shared
|