deepline 0.1.10 → 0.1.12
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 +4 -4
- package/dist/cli/index.js +509 -353
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +513 -358
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +250 -305
- package/dist/index.d.ts +250 -305
- package/dist/index.js +174 -286
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +174 -285
- package/dist/index.mjs.map +1 -1
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +23 -13
- package/dist/repo/apps/play-runner-workers/src/entry.ts +581 -1220
- package/dist/repo/sdk/src/cli/commands/play.ts +381 -247
- package/dist/repo/sdk/src/cli/commands/tools.ts +1 -1
- package/dist/repo/sdk/src/cli/dataset-stats.ts +86 -12
- package/dist/repo/sdk/src/client.ts +54 -51
- package/dist/repo/sdk/src/index.ts +7 -16
- package/dist/repo/sdk/src/play.ts +122 -135
- package/dist/repo/sdk/src/plays/bundle-play-file.ts +6 -3
- package/dist/repo/sdk/src/tool-output.ts +0 -111
- package/dist/repo/sdk/src/types.ts +2 -0
- package/dist/repo/sdk/src/version.ts +1 -1
- package/dist/repo/sdk/src/worker-play-entry.ts +3 -0
- package/dist/repo/shared_libs/play-runtime/context.ts +510 -267
- package/dist/repo/shared_libs/play-runtime/csv-rename.ts +180 -0
- package/dist/repo/shared_libs/play-runtime/ctx-types.ts +13 -1
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +139 -114
- package/dist/repo/shared_libs/plays/bundling/index.ts +68 -5
- package/dist/repo/shared_libs/plays/compiler-manifest.ts +1 -1
- package/dist/repo/shared_libs/plays/dataset.ts +1 -1
- package/dist/repo/shared_libs/plays/runtime-validation.ts +8 -28
- package/package.json +1 -1
- package/dist/repo/apps/play-runner-workers/src/runtime/tool-result.ts +0 -184
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* PlayContextImpl — the cloud execution engine.
|
|
3
3
|
*
|
|
4
4
|
* Batching model:
|
|
5
|
-
* 1. ctx.map("table_key", rows, {
|
|
5
|
+
* 1. ctx.map("table_key", rows, { field: resolver }) starts all row field resolvers concurrently
|
|
6
6
|
* 2. ctx.waterfall() calls inside field resolvers QUEUE requests (don't execute)
|
|
7
|
-
* 3. ctx.tools.execute(
|
|
7
|
+
* 3. ctx.tools.execute() calls inside field resolvers also QUEUE
|
|
8
8
|
* 4. After all rows have queued, executeBatchedWaterfalls() runs provider-by-provider
|
|
9
9
|
* 5. Each provider batch = real HTTP call to /api/v2/integrations/{toolId}/execute
|
|
10
10
|
* 6. Results resolve suspended row promises, rows complete
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
resolveWaterfallToolId,
|
|
27
27
|
} from './batch-runtime';
|
|
28
28
|
import { PlayRateLimitScheduler } from '@shared_libs/plays/rate-limit-scheduler';
|
|
29
|
-
import { normalizePlayToolResult } from './result-normalization';
|
|
30
29
|
import {
|
|
31
30
|
cloneToolExecuteResultWithExecution,
|
|
32
31
|
createToolExecuteResult,
|
|
@@ -38,19 +37,12 @@ import { sqlSafePlayColumnName } from '@shared_libs/plays/static-pipeline';
|
|
|
38
37
|
import { createRuntimeDatasetId } from './dataset-id';
|
|
39
38
|
import {
|
|
40
39
|
derivePlayRowIdentity,
|
|
40
|
+
derivePlayRowIdentityFromKey,
|
|
41
41
|
MAP_KEY_NAMESPACE_MAX_LENGTH,
|
|
42
42
|
normalizeTableNamespace,
|
|
43
43
|
resolveStaleMapTableNamespace,
|
|
44
44
|
} from '@shared_libs/plays/row-identity';
|
|
45
|
-
import {
|
|
46
|
-
assertUniqueExplicitMapKeys,
|
|
47
|
-
createExplicitMapKeyResolver,
|
|
48
|
-
deriveMapRowIdentity,
|
|
49
|
-
MapRowIdentity,
|
|
50
|
-
} from './map-row-identity';
|
|
51
|
-
import { MapExecutionFrameStore } from './map-execution-frame';
|
|
52
|
-
import { PlayProgressEmitter } from './progress-emitter';
|
|
53
|
-
import { WaterfallReplayStore } from './waterfall-replay';
|
|
45
|
+
import { cloneCsvAliasedRow, stripCsvProjectedFields } from './csv-rename';
|
|
54
46
|
import { setSpanAttributes, withActiveSpan } from './tracing';
|
|
55
47
|
import { DISALLOWED_RUN_JAVASCRIPT_TOOL_MESSAGE } from './runtime-constraints';
|
|
56
48
|
import {
|
|
@@ -64,12 +56,9 @@ import type {
|
|
|
64
56
|
WaterfallRequest,
|
|
65
57
|
WaterfallOptions,
|
|
66
58
|
InlineWaterfallSpec,
|
|
67
|
-
MapDefinitionOptions,
|
|
68
59
|
MapOptions,
|
|
69
|
-
MapRunOptions,
|
|
70
60
|
ToolCallRequest,
|
|
71
61
|
ToolBatchResult,
|
|
72
|
-
ToolExecutionRequest,
|
|
73
62
|
ContextOptions,
|
|
74
63
|
PlayCallOptions,
|
|
75
64
|
PlayCheckpoint,
|
|
@@ -78,6 +67,7 @@ import type {
|
|
|
78
67
|
PlayRowUpdate,
|
|
79
68
|
MapFieldDefinition,
|
|
80
69
|
MapFieldResolver,
|
|
70
|
+
ToolCallOptions,
|
|
81
71
|
PlayExecutionGovernanceLimits,
|
|
82
72
|
PlayExecutionGovernanceState,
|
|
83
73
|
ResolvedPlayExecution,
|
|
@@ -111,10 +101,6 @@ const PURE_JS_HEARTBEAT_ROW_INTERVAL = 250;
|
|
|
111
101
|
const TOOL_RETRY_AFTER_FALLBACK_MS = 1_000;
|
|
112
102
|
const TOOL_RETRY_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
113
103
|
const TOOL_TRANSIENT_HTTP_MAX_ATTEMPTS = 3;
|
|
114
|
-
const TRANSIENT_HTTP_RETRY_SAFE_TOOL_IDS = new Set([
|
|
115
|
-
'test_company_search',
|
|
116
|
-
'test_transient_500',
|
|
117
|
-
]);
|
|
118
104
|
const EXECUTE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
|
|
119
105
|
const IN_MEMORY_STEP_RESULT_PREVIEW_LIMIT = 25;
|
|
120
106
|
const WATERFALL_ROW_MATCH_LOG_SAMPLE_LIMIT = 10;
|
|
@@ -169,16 +155,6 @@ function isRuntimeConditionalStepResolver(
|
|
|
169
155
|
);
|
|
170
156
|
}
|
|
171
157
|
|
|
172
|
-
function isMapDefinitionOptions(
|
|
173
|
-
value: unknown,
|
|
174
|
-
): value is Omit<MapOptions, 'description'> {
|
|
175
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
const keys = Object.keys(value);
|
|
179
|
-
return keys.every((key) => key === 'key' || key === 'staleAfterSeconds');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
158
|
class RuntimeMapBuilder<T extends Record<string, unknown>> {
|
|
183
159
|
private readonly program: RuntimeStepProgram = {
|
|
184
160
|
kind: 'steps',
|
|
@@ -189,7 +165,6 @@ class RuntimeMapBuilder<T extends Record<string, unknown>> {
|
|
|
189
165
|
private readonly ctx: PlayContextImpl,
|
|
190
166
|
private readonly key: string,
|
|
191
167
|
private readonly items: PlayDatasetInput<T>,
|
|
192
|
-
private readonly mapOptions?: MapDefinitionOptions<T>,
|
|
193
168
|
) {}
|
|
194
169
|
|
|
195
170
|
step(name: string, resolver: RuntimeStepProgramStep['resolver']): this {
|
|
@@ -202,17 +177,13 @@ class RuntimeMapBuilder<T extends Record<string, unknown>> {
|
|
|
202
177
|
return this;
|
|
203
178
|
}
|
|
204
179
|
|
|
205
|
-
run(options?:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return this.ctx.runStepProgramMap(this.key, this.items, this.program, {
|
|
213
|
-
...this.mapOptions,
|
|
214
|
-
...options,
|
|
215
|
-
} as MapOptions<T>);
|
|
180
|
+
run(options?: MapOptions<T>): Promise<PlayDataset<Record<string, unknown>>> {
|
|
181
|
+
return this.ctx.runStepProgramMap(
|
|
182
|
+
this.key,
|
|
183
|
+
this.items,
|
|
184
|
+
this.program,
|
|
185
|
+
options,
|
|
186
|
+
);
|
|
216
187
|
}
|
|
217
188
|
}
|
|
218
189
|
|
|
@@ -258,17 +229,6 @@ function parseRetryAfterMs(header: string | null): number {
|
|
|
258
229
|
return TOOL_RETRY_AFTER_FALLBACK_MS;
|
|
259
230
|
}
|
|
260
231
|
|
|
261
|
-
function isRetryableTransientToolHttpStatus(
|
|
262
|
-
toolId: string,
|
|
263
|
-
status: number,
|
|
264
|
-
): boolean {
|
|
265
|
-
return (
|
|
266
|
-
status >= 500 &&
|
|
267
|
-
status < 600 &&
|
|
268
|
-
TRANSIENT_HTTP_RETRY_SAFE_TOOL_IDS.has(toolId)
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
232
|
function parseJsonOrNull(bodyText: string): unknown | null {
|
|
273
233
|
if (!bodyText.trim()) return null;
|
|
274
234
|
try {
|
|
@@ -302,22 +262,18 @@ function parseExecuteToolMetadata(
|
|
|
302
262
|
const readGetters = (value: unknown): Record<string, readonly string[]> => {
|
|
303
263
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
304
264
|
return Object.fromEntries(
|
|
305
|
-
Object.entries(value as Record<string, unknown>).flatMap(
|
|
306
|
-
(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return normalized.length > 0 ? [[key, normalized]] : [];
|
|
313
|
-
},
|
|
314
|
-
),
|
|
265
|
+
Object.entries(value as Record<string, unknown>).flatMap(([key, paths]) => {
|
|
266
|
+
if (!Array.isArray(paths)) return [];
|
|
267
|
+
const normalized = paths.filter(
|
|
268
|
+
(path): path is string => typeof path === 'string' && path.trim().length > 0,
|
|
269
|
+
);
|
|
270
|
+
return normalized.length > 0 ? [[key, normalized]] : [];
|
|
271
|
+
}),
|
|
315
272
|
);
|
|
316
273
|
};
|
|
317
274
|
const listExtractorPaths = Array.isArray(record.listExtractorPaths)
|
|
318
275
|
? record.listExtractorPaths.filter(
|
|
319
|
-
(path): path is string =>
|
|
320
|
-
typeof path === 'string' && path.trim().length > 0,
|
|
276
|
+
(path): path is string => typeof path === 'string' && path.trim().length > 0,
|
|
321
277
|
)
|
|
322
278
|
: [];
|
|
323
279
|
return {
|
|
@@ -359,6 +315,14 @@ function emptyCheckpoint(): PlayCheckpoint {
|
|
|
359
315
|
};
|
|
360
316
|
}
|
|
361
317
|
|
|
318
|
+
function cloneMapFrame(frame: MapExecutionFrame): MapExecutionFrame {
|
|
319
|
+
return {
|
|
320
|
+
...frame,
|
|
321
|
+
completedRowKeys: [...frame.completedRowKeys],
|
|
322
|
+
pendingRowKeys: [...frame.pendingRowKeys],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
362
326
|
function createDefaultGovernanceState(
|
|
363
327
|
options: ContextOptions,
|
|
364
328
|
): PlayExecutionGovernanceState {
|
|
@@ -482,7 +446,7 @@ export class PlayContextImpl {
|
|
|
482
446
|
private resolvers = new Map<string, (value: unknown) => void>();
|
|
483
447
|
private waterfallQueue = new Map<string, WaterfallRequest[]>();
|
|
484
448
|
private toolCallQueue: ToolCallRequest[] = [];
|
|
485
|
-
private toolCallResolvers = new Map<string,
|
|
449
|
+
private toolCallResolvers = new Map<string, (value: unknown) => void>();
|
|
486
450
|
private options: ContextOptions;
|
|
487
451
|
private logBuffer: string[] = [];
|
|
488
452
|
private checkpoint: PlayCheckpoint;
|
|
@@ -503,22 +467,56 @@ export class PlayContextImpl {
|
|
|
503
467
|
private directToolCallIndex = 0;
|
|
504
468
|
private sleepBoundaryIndex = 0;
|
|
505
469
|
private fetchCallIndex = 0;
|
|
470
|
+
private mapInvocationIndex = 0;
|
|
506
471
|
private readonly stepCallIndexByKey = new Map<string, number>();
|
|
507
472
|
private readonly waterfallMatchLogCounts = new Map<string, number>();
|
|
508
473
|
private readonly rateLimitScheduler: PlayRateLimitScheduler;
|
|
509
474
|
private readonly governance: PlayExecutionGovernanceState;
|
|
510
|
-
private readonly mapRowIdentity = new MapRowIdentity();
|
|
511
|
-
private readonly mapFrames: MapExecutionFrameStore;
|
|
512
|
-
private readonly progress: PlayProgressEmitter;
|
|
513
|
-
private readonly waterfallReplay: WaterfallReplayStore;
|
|
514
475
|
readonly tools = {
|
|
515
476
|
execute: <TOutput = unknown>(
|
|
516
|
-
|
|
517
|
-
|
|
477
|
+
requestOrKey:
|
|
478
|
+
| {
|
|
479
|
+
id: string;
|
|
480
|
+
tool: string;
|
|
481
|
+
input: Record<string, unknown>;
|
|
482
|
+
description?: string;
|
|
483
|
+
}
|
|
484
|
+
| string,
|
|
485
|
+
toolId?: string,
|
|
486
|
+
input?: Record<string, unknown>,
|
|
487
|
+
options?: ToolCallOptions,
|
|
488
|
+
): Promise<TOutput> => {
|
|
489
|
+
if (typeof requestOrKey === 'object') {
|
|
490
|
+
return this.executeTool(
|
|
491
|
+
requestOrKey.id,
|
|
492
|
+
requestOrKey.tool,
|
|
493
|
+
requestOrKey.input,
|
|
494
|
+
requestOrKey.description
|
|
495
|
+
? { description: requestOrKey.description }
|
|
496
|
+
: undefined,
|
|
497
|
+
) as Promise<TOutput>;
|
|
498
|
+
}
|
|
499
|
+
if (!toolId || !input) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
'ctx.tools.execute(key, toolId, input) requires a tool ID and input.',
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
return this.executeTool(
|
|
505
|
+
requestOrKey,
|
|
506
|
+
toolId,
|
|
507
|
+
input,
|
|
508
|
+
options,
|
|
509
|
+
) as Promise<TOutput>;
|
|
510
|
+
},
|
|
518
511
|
};
|
|
519
512
|
|
|
520
|
-
tool<TOutput = unknown>(
|
|
521
|
-
|
|
513
|
+
tool<TOutput = unknown>(
|
|
514
|
+
key: string,
|
|
515
|
+
toolId: string,
|
|
516
|
+
input: Record<string, unknown>,
|
|
517
|
+
options?: ToolCallOptions,
|
|
518
|
+
): Promise<TOutput> {
|
|
519
|
+
return this.tools.execute<TOutput>(key, toolId, input, options);
|
|
522
520
|
}
|
|
523
521
|
|
|
524
522
|
constructor(options: ContextOptions) {
|
|
@@ -527,16 +525,6 @@ export class PlayContextImpl {
|
|
|
527
525
|
getQueueHints: options.getToolQueueHints,
|
|
528
526
|
});
|
|
529
527
|
this.checkpoint = options.checkpoint ?? emptyCheckpoint();
|
|
530
|
-
this.mapFrames = new MapExecutionFrameStore(this.checkpoint, (event) =>
|
|
531
|
-
this.emitExecutionEvent(event),
|
|
532
|
-
);
|
|
533
|
-
this.progress = new PlayProgressEmitter(
|
|
534
|
-
options.onRowUpdate,
|
|
535
|
-
options.onExecutionEvent,
|
|
536
|
-
() => rowContext.getStore() ?? null,
|
|
537
|
-
isInlineWaterfallToolStep,
|
|
538
|
-
);
|
|
539
|
-
this.waterfallReplay = new WaterfallReplayStore(this.checkpoint);
|
|
540
528
|
this.governance = {
|
|
541
529
|
...(options.governance ?? createDefaultGovernanceState(options)),
|
|
542
530
|
inFlightPlayCallsByPlayId:
|
|
@@ -557,14 +545,73 @@ export class PlayContextImpl {
|
|
|
557
545
|
tableNamespace: string | null,
|
|
558
546
|
update: Omit<PlayRowUpdate, 'key'>,
|
|
559
547
|
): void {
|
|
560
|
-
|
|
548
|
+
const rowScope = rowContext.getStore()?.mapScope;
|
|
549
|
+
if (rowScope && key) {
|
|
550
|
+
this.emitExecutionEvent({
|
|
551
|
+
type: 'map.row.updated',
|
|
552
|
+
mapInvocationId: rowScope.mapInvocationId,
|
|
553
|
+
mapNodeId: rowScope.mapNodeId ?? null,
|
|
554
|
+
logicalNamespace: rowScope.logicalNamespace,
|
|
555
|
+
artifactTableNamespace: rowScope.artifactTableNamespace,
|
|
556
|
+
rowKey: key,
|
|
557
|
+
rowStatus: update.status,
|
|
558
|
+
fieldName: rowContext.getStore()?.fieldName ?? null,
|
|
559
|
+
stage: update.stage ?? null,
|
|
560
|
+
provider: update.provider ?? null,
|
|
561
|
+
at: Date.now(),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
if (!key || !this.options.onRowUpdate) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
void this.options.onRowUpdate({
|
|
568
|
+
...update,
|
|
569
|
+
key,
|
|
570
|
+
tableNamespace,
|
|
571
|
+
});
|
|
561
572
|
}
|
|
562
573
|
|
|
563
574
|
private emitExecutionEvent(event: PlayExecutionEvent): void {
|
|
564
575
|
if (!this.options.onExecutionEvent) {
|
|
565
576
|
return;
|
|
566
577
|
}
|
|
567
|
-
this.
|
|
578
|
+
void this.options.onExecutionEvent(event);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private setMapFrame(frame: MapExecutionFrame): void {
|
|
582
|
+
this.checkpoint.mapFrames = {
|
|
583
|
+
...(this.checkpoint.mapFrames ?? {}),
|
|
584
|
+
[frame.mapInvocationId]: cloneMapFrame(frame),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private createMapExecutionScope(input: {
|
|
589
|
+
logicalNamespace: string;
|
|
590
|
+
artifactTableNamespace: string;
|
|
591
|
+
mapNodeId?: string | null;
|
|
592
|
+
explicitKey?:
|
|
593
|
+
| ((row: Record<string, unknown>, index: number) => string)
|
|
594
|
+
| null;
|
|
595
|
+
}): MapExecutionScope {
|
|
596
|
+
const mapInvocationId = `${input.logicalNamespace}:${this.mapInvocationIndex}`;
|
|
597
|
+
this.mapInvocationIndex += 1;
|
|
598
|
+
const explicitKey = input.explicitKey ?? null;
|
|
599
|
+
return {
|
|
600
|
+
mapInvocationId,
|
|
601
|
+
mapNodeId: input.mapNodeId ?? null,
|
|
602
|
+
logicalNamespace: input.logicalNamespace,
|
|
603
|
+
artifactTableNamespace: input.artifactTableNamespace,
|
|
604
|
+
rowIdentity: (row, index) =>
|
|
605
|
+
explicitKey
|
|
606
|
+
? derivePlayRowIdentityFromKey(
|
|
607
|
+
explicitKey(row, index ?? 0),
|
|
608
|
+
input.artifactTableNamespace,
|
|
609
|
+
)
|
|
610
|
+
: derivePlayRowIdentity(
|
|
611
|
+
stripCsvProjectedFields(row),
|
|
612
|
+
input.artifactTableNamespace,
|
|
613
|
+
),
|
|
614
|
+
};
|
|
568
615
|
}
|
|
569
616
|
|
|
570
617
|
private normalizeContextKey(key: string, operation: string): string {
|
|
@@ -743,7 +790,35 @@ export class PlayContextImpl {
|
|
|
743
790
|
reused?: boolean;
|
|
744
791
|
dataPatch?: Record<string, unknown>;
|
|
745
792
|
}): void {
|
|
746
|
-
|
|
793
|
+
if (!input.fieldName) {
|
|
794
|
+
this.emitScopedRowUpdate(input.key, input.tableNamespace, {
|
|
795
|
+
rowId: input.rowId,
|
|
796
|
+
status: input.rowStatus,
|
|
797
|
+
stage: input.stage ?? null,
|
|
798
|
+
provider: input.provider ?? null,
|
|
799
|
+
error: input.error ?? null,
|
|
800
|
+
dataPatch: input.dataPatch ?? {},
|
|
801
|
+
});
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
this.emitScopedRowUpdate(input.key, input.tableNamespace, {
|
|
806
|
+
rowId: input.rowId,
|
|
807
|
+
status: input.rowStatus,
|
|
808
|
+
stage: input.stage ?? null,
|
|
809
|
+
provider: input.provider ?? null,
|
|
810
|
+
error: input.error ?? null,
|
|
811
|
+
dataPatch: input.dataPatch ?? {},
|
|
812
|
+
cellMetaPatch: {
|
|
813
|
+
[input.fieldName]: {
|
|
814
|
+
status: input.status,
|
|
815
|
+
stage: input.stage ?? null,
|
|
816
|
+
provider: input.provider ?? null,
|
|
817
|
+
error: input.error ?? null,
|
|
818
|
+
...(input.reused !== undefined ? { reused: input.reused } : {}),
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
});
|
|
747
822
|
}
|
|
748
823
|
|
|
749
824
|
private emitCellUpdate(input: {
|
|
@@ -765,7 +840,23 @@ export class PlayContextImpl {
|
|
|
765
840
|
error?: string | null;
|
|
766
841
|
value?: unknown;
|
|
767
842
|
}): void {
|
|
768
|
-
this.
|
|
843
|
+
this.emitScopedRowUpdate(input.key, input.tableNamespace, {
|
|
844
|
+
rowId: input.rowId,
|
|
845
|
+
status: input.rowStatus,
|
|
846
|
+
stage: input.stage ?? null,
|
|
847
|
+
provider: input.provider ?? null,
|
|
848
|
+
error: input.error ?? null,
|
|
849
|
+
dataPatch:
|
|
850
|
+
input.value === undefined ? {} : { [input.columnName]: input.value },
|
|
851
|
+
cellMetaPatch: {
|
|
852
|
+
[input.columnName]: {
|
|
853
|
+
status: input.status,
|
|
854
|
+
stage: input.stage ?? null,
|
|
855
|
+
provider: input.provider ?? null,
|
|
856
|
+
error: input.error ?? null,
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
});
|
|
769
860
|
}
|
|
770
861
|
|
|
771
862
|
private emitQueuedInlineWaterfallSteps(
|
|
@@ -774,12 +865,18 @@ export class PlayContextImpl {
|
|
|
774
865
|
tableNamespace: string | null,
|
|
775
866
|
spec: InlineWaterfallSpec,
|
|
776
867
|
): void {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
868
|
+
for (const step of spec.steps) {
|
|
869
|
+
this.emitCellUpdate({
|
|
870
|
+
rowId,
|
|
871
|
+
key,
|
|
872
|
+
tableNamespace,
|
|
873
|
+
columnName: sqlSafePlayColumnName(`${spec.id}.${step.id}`),
|
|
874
|
+
status: 'queued',
|
|
875
|
+
stage: step.id,
|
|
876
|
+
provider: isInlineWaterfallToolStep(step) ? step.toolId : 'code',
|
|
877
|
+
value: null,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
783
880
|
}
|
|
784
881
|
|
|
785
882
|
private isCompletedFieldValue(value: unknown): boolean {
|
|
@@ -1038,8 +1135,7 @@ export class PlayContextImpl {
|
|
|
1038
1135
|
return createToolExecuteResult({
|
|
1039
1136
|
status: input.status,
|
|
1040
1137
|
result: input.result,
|
|
1041
|
-
metadata:
|
|
1042
|
-
input.metadata ?? (await this.resolveToolResultMetadata(input.toolId)),
|
|
1138
|
+
metadata: input.metadata ?? (await this.resolveToolResultMetadata(input.toolId)),
|
|
1043
1139
|
execution: {
|
|
1044
1140
|
idempotent: true,
|
|
1045
1141
|
cached: input.cached,
|
|
@@ -1072,11 +1168,9 @@ export class PlayContextImpl {
|
|
|
1072
1168
|
});
|
|
1073
1169
|
this.cacheToolResult(toolId, cacheKey, wrapped);
|
|
1074
1170
|
|
|
1075
|
-
const
|
|
1076
|
-
if (
|
|
1077
|
-
|
|
1078
|
-
resolver(wrapped);
|
|
1079
|
-
}
|
|
1171
|
+
const resolver = this.toolCallResolvers.get(request.callId);
|
|
1172
|
+
if (resolver) {
|
|
1173
|
+
resolver(wrapped);
|
|
1080
1174
|
this.toolCallResolvers.delete(request.callId);
|
|
1081
1175
|
}
|
|
1082
1176
|
|
|
@@ -1124,11 +1218,6 @@ export class PlayContextImpl {
|
|
|
1124
1218
|
);
|
|
1125
1219
|
}
|
|
1126
1220
|
|
|
1127
|
-
map<T extends Record<string, unknown>>(
|
|
1128
|
-
key: string,
|
|
1129
|
-
items: PlayDatasetInput<T>,
|
|
1130
|
-
options: MapDefinitionOptions<T>,
|
|
1131
|
-
): RuntimeMapBuilder<T>;
|
|
1132
1221
|
map<T extends Record<string, unknown>>(
|
|
1133
1222
|
key: string,
|
|
1134
1223
|
items: PlayDatasetInput<T>,
|
|
@@ -1145,10 +1234,7 @@ export class PlayContextImpl {
|
|
|
1145
1234
|
>(
|
|
1146
1235
|
key: string,
|
|
1147
1236
|
items: PlayDatasetInput<T>,
|
|
1148
|
-
input?:
|
|
1149
|
-
| MapFieldDefinition<T, TColumns>
|
|
1150
|
-
| RuntimeStepProgram
|
|
1151
|
-
| MapDefinitionOptions<T>,
|
|
1237
|
+
input?: MapFieldDefinition<T, TColumns> | RuntimeStepProgram,
|
|
1152
1238
|
options?: MapOptions<T>,
|
|
1153
1239
|
): RuntimeMapBuilder<T> | Promise<PlayDataset<Record<string, unknown>>> {
|
|
1154
1240
|
if (rowContext.getStore()) {
|
|
@@ -1158,20 +1244,17 @@ export class PlayContextImpl {
|
|
|
1158
1244
|
}
|
|
1159
1245
|
this.assertNoDuplicateConcurrentMapBackedPlay();
|
|
1160
1246
|
|
|
1161
|
-
if (input === undefined
|
|
1162
|
-
return new RuntimeMapBuilder(
|
|
1163
|
-
this,
|
|
1164
|
-
key,
|
|
1165
|
-
items,
|
|
1166
|
-
input as MapDefinitionOptions<T> | undefined,
|
|
1167
|
-
);
|
|
1247
|
+
if (input === undefined) {
|
|
1248
|
+
return new RuntimeMapBuilder(this, key, items);
|
|
1168
1249
|
}
|
|
1169
1250
|
|
|
1170
1251
|
if (isRuntimeStepProgram(input)) {
|
|
1171
1252
|
return this.runStepProgramMap(key, items, input, options);
|
|
1172
1253
|
}
|
|
1173
1254
|
|
|
1174
|
-
throw new Error(
|
|
1255
|
+
throw new Error(
|
|
1256
|
+
'ctx.map(key, rows, fields, options) was removed. Use ctx.map(key, rows).step(...).run(options).',
|
|
1257
|
+
);
|
|
1175
1258
|
}
|
|
1176
1259
|
|
|
1177
1260
|
async runStepProgramMap<T extends Record<string, unknown>>(
|
|
@@ -1213,7 +1296,7 @@ export class PlayContextImpl {
|
|
|
1213
1296
|
} catch (error) {
|
|
1214
1297
|
throw new Error(
|
|
1215
1298
|
error instanceof Error
|
|
1216
|
-
? `${error.message} Example: ctx.map(\"${normalizedMapNamespace}\", rows, {
|
|
1299
|
+
? `${error.message} Example: ctx.map(\"${normalizedMapNamespace}\", rows, { company: ... }, { staleAfterSeconds: 86400 }).`
|
|
1217
1300
|
: `ctx.map() key must normalize to <= ${MAP_KEY_NAMESPACE_MAX_LENGTH} characters.`,
|
|
1218
1301
|
);
|
|
1219
1302
|
}
|
|
@@ -1234,23 +1317,66 @@ export class PlayContextImpl {
|
|
|
1234
1317
|
let itemsToProcess: Array<Record<string, unknown>> = [];
|
|
1235
1318
|
let completedItemsByKey: Map<string, Record<string, unknown>> | null = null;
|
|
1236
1319
|
const mapFieldNames = Object.keys(input);
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1320
|
+
const stripFieldOutputs = (row: Record<string, unknown>) =>
|
|
1321
|
+
Object.fromEntries(
|
|
1322
|
+
Object.entries(row).filter(
|
|
1323
|
+
([fieldName]) => !mapFieldNames.includes(fieldName),
|
|
1324
|
+
),
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
const userKeyOption = options?.key;
|
|
1328
|
+
let explicitKeyResolver:
|
|
1329
|
+
| ((row: Record<string, unknown>, index: number) => string)
|
|
1330
|
+
| null = null;
|
|
1331
|
+
if (userKeyOption !== undefined) {
|
|
1332
|
+
explicitKeyResolver = (row, index) => {
|
|
1333
|
+
const inputRow = stripFieldOutputs(row);
|
|
1334
|
+
const raw =
|
|
1335
|
+
typeof userKeyOption === 'function'
|
|
1336
|
+
? (
|
|
1337
|
+
userKeyOption as (
|
|
1338
|
+
row: Record<string, unknown>,
|
|
1339
|
+
index: number,
|
|
1340
|
+
) => string | number | readonly unknown[]
|
|
1341
|
+
)(inputRow, index)
|
|
1342
|
+
: Array.isArray(userKeyOption)
|
|
1343
|
+
? userKeyOption.map((fieldName) => inputRow[fieldName])
|
|
1344
|
+
: inputRow[String(userKeyOption)];
|
|
1345
|
+
const parts = Array.isArray(raw) ? raw : [raw];
|
|
1346
|
+
if (parts.some((part) => part === null || part === undefined)) {
|
|
1347
|
+
throw new Error(
|
|
1348
|
+
`ctx.map("${normalizedMapNamespace}") key returned null or undefined for row ${index}. ` +
|
|
1349
|
+
'Return a non-empty string or number derived from a stable input column (e.g. row.email ?? row.domain).',
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
const normalizedParts = parts.map((part) => {
|
|
1353
|
+
if (typeof part === 'number') {
|
|
1354
|
+
return Number.isFinite(part) ? String(part) : '';
|
|
1355
|
+
}
|
|
1356
|
+
return String(part).trim();
|
|
1357
|
+
});
|
|
1358
|
+
if (normalizedParts.some((part) => !part)) {
|
|
1359
|
+
throw new Error(
|
|
1360
|
+
`ctx.map("${normalizedMapNamespace}") key returned an empty value for row ${index}. ` +
|
|
1361
|
+
'Return a non-empty string or finite number derived from a stable input column.',
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
return normalizedParts.length === 1
|
|
1365
|
+
? normalizedParts[0]!
|
|
1366
|
+
: JSON.stringify(normalizedParts);
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1245
1369
|
|
|
1246
1370
|
const rowIdentity = (row: Record<string, unknown>, index = 0) =>
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1371
|
+
explicitKeyResolver
|
|
1372
|
+
? derivePlayRowIdentityFromKey(
|
|
1373
|
+
explicitKeyResolver(row, index),
|
|
1374
|
+
resolvedTableNamespace,
|
|
1375
|
+
)
|
|
1376
|
+
: derivePlayRowIdentity(
|
|
1377
|
+
stripCsvProjectedFields(stripFieldOutputs(row)),
|
|
1378
|
+
resolvedTableNamespace,
|
|
1379
|
+
);
|
|
1254
1380
|
|
|
1255
1381
|
const materializedItems = await materializePlayDatasetInput(items);
|
|
1256
1382
|
totalInputCount = materializedItems.length;
|
|
@@ -1259,14 +1385,29 @@ export class PlayContextImpl {
|
|
|
1259
1385
|
this.toOutputRow(item as Record<string, unknown>),
|
|
1260
1386
|
);
|
|
1261
1387
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1388
|
+
if (explicitKeyResolver) {
|
|
1389
|
+
const seenKeys = new Map<string, number>();
|
|
1390
|
+
for (let index = 0; index < rawItems.length; index += 1) {
|
|
1391
|
+
const keyValue = explicitKeyResolver(rawItems[index]!, index);
|
|
1392
|
+
const previousIndex = seenKeys.get(keyValue);
|
|
1393
|
+
if (previousIndex !== undefined) {
|
|
1394
|
+
throw new Error(
|
|
1395
|
+
`ctx.map("${normalizedMapNamespace}") key function produced duplicate value "${keyValue}" for rows ${previousIndex} and ${index}. ` +
|
|
1396
|
+
'Each row must produce a unique key. Combine columns (e.g. `${row.email}|${row.company}`) or pick a column that is unique per row.',
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
seenKeys.set(keyValue, index);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1267
1402
|
if (this.options.onMapStart) {
|
|
1403
|
+
const mapStartRows = explicitKeyResolver
|
|
1404
|
+
? rawItems.map((row, index) => ({
|
|
1405
|
+
...row,
|
|
1406
|
+
__deeplineRowKey: rowIdentity(row, index),
|
|
1407
|
+
}))
|
|
1408
|
+
: rawItems;
|
|
1268
1409
|
const mapStartResult = await this.options.onMapStart(
|
|
1269
|
-
|
|
1410
|
+
mapStartRows,
|
|
1270
1411
|
resolvedTableNamespace,
|
|
1271
1412
|
{
|
|
1272
1413
|
playName: this.options.playName,
|
|
@@ -1278,16 +1419,30 @@ export class PlayContextImpl {
|
|
|
1278
1419
|
resolvedTableNamespace = normalizeTableNamespace(
|
|
1279
1420
|
mapStartResult.tableNamespace,
|
|
1280
1421
|
);
|
|
1422
|
+
const persistedRowIdentity = (
|
|
1423
|
+
row: Record<string, unknown>,
|
|
1424
|
+
index = 0,
|
|
1425
|
+
) =>
|
|
1426
|
+
typeof row.__deeplineRowKey === 'string'
|
|
1427
|
+
? row.__deeplineRowKey
|
|
1428
|
+
: rowIdentity(row, index);
|
|
1281
1429
|
const pendingKeys = new Set(
|
|
1282
|
-
mapStartResult.pendingRows.map((row) =>
|
|
1430
|
+
mapStartResult.pendingRows.map((row, index) =>
|
|
1431
|
+
persistedRowIdentity(row, index),
|
|
1432
|
+
),
|
|
1283
1433
|
);
|
|
1284
1434
|
itemsToProcess = materializedItems
|
|
1285
|
-
.map((item) =>
|
|
1286
|
-
|
|
1435
|
+
.map((item, index) => ({
|
|
1436
|
+
row: this.toOutputRow(item as Record<string, unknown>),
|
|
1437
|
+
index,
|
|
1438
|
+
}))
|
|
1439
|
+
.filter(({ row, index }) => pendingKeys.has(rowIdentity(row, index)))
|
|
1440
|
+
.map(({ row }) => row);
|
|
1287
1441
|
if (mapStartResult.completedRows.length > 0) {
|
|
1288
1442
|
completedItemsByKey = new Map();
|
|
1289
|
-
for (
|
|
1290
|
-
const
|
|
1443
|
+
for (let index = 0; index < mapStartResult.completedRows.length; index += 1) {
|
|
1444
|
+
const row = mapStartResult.completedRows[index]!;
|
|
1445
|
+
const rowKey = persistedRowIdentity(row, index);
|
|
1291
1446
|
if (rowKey) completedItemsByKey.set(rowKey, row);
|
|
1292
1447
|
}
|
|
1293
1448
|
this.log(
|
|
@@ -1296,22 +1451,38 @@ export class PlayContextImpl {
|
|
|
1296
1451
|
}
|
|
1297
1452
|
}
|
|
1298
1453
|
|
|
1299
|
-
const mapScope = this.
|
|
1454
|
+
const mapScope = this.createMapExecutionScope({
|
|
1300
1455
|
logicalNamespace: normalizedMapNamespace,
|
|
1301
1456
|
artifactTableNamespace: resolvedTableNamespace,
|
|
1302
1457
|
explicitKey: explicitKeyResolver,
|
|
1303
|
-
fieldNames: mapFieldNames,
|
|
1304
1458
|
});
|
|
1305
1459
|
const completedRowKeys =
|
|
1306
1460
|
completedItemsByKey != null ? [...completedItemsByKey.keys()] : [];
|
|
1307
|
-
const pendingRowKeys = itemsToProcess.map((item) =>
|
|
1308
|
-
mapScope.rowIdentity(this.toOutputRow(item)),
|
|
1461
|
+
const pendingRowKeys = itemsToProcess.map((item, index) =>
|
|
1462
|
+
mapScope.rowIdentity(this.toOutputRow(item), index),
|
|
1309
1463
|
);
|
|
1310
|
-
this.
|
|
1311
|
-
|
|
1464
|
+
this.setMapFrame({
|
|
1465
|
+
mapInvocationId: mapScope.mapInvocationId,
|
|
1466
|
+
mapNodeId: mapScope.mapNodeId ?? null,
|
|
1467
|
+
logicalNamespace: mapScope.logicalNamespace,
|
|
1468
|
+
artifactTableNamespace: mapScope.artifactTableNamespace,
|
|
1469
|
+
status: 'running',
|
|
1312
1470
|
totalRows: totalInputCount,
|
|
1313
1471
|
completedRowKeys,
|
|
1314
1472
|
pendingRowKeys,
|
|
1473
|
+
startedAt: Date.now(),
|
|
1474
|
+
updatedAt: Date.now(),
|
|
1475
|
+
});
|
|
1476
|
+
this.emitExecutionEvent({
|
|
1477
|
+
type: 'map.started',
|
|
1478
|
+
mapInvocationId: mapScope.mapInvocationId,
|
|
1479
|
+
mapNodeId: mapScope.mapNodeId ?? null,
|
|
1480
|
+
logicalNamespace: mapScope.logicalNamespace,
|
|
1481
|
+
artifactTableNamespace: mapScope.artifactTableNamespace,
|
|
1482
|
+
totalRows: totalInputCount,
|
|
1483
|
+
completedRows: completedRowKeys.length,
|
|
1484
|
+
pendingRows: pendingRowKeys.length,
|
|
1485
|
+
at: Date.now(),
|
|
1315
1486
|
});
|
|
1316
1487
|
|
|
1317
1488
|
const mapResults = await this.runFieldMap(
|
|
@@ -1334,19 +1505,19 @@ export class PlayContextImpl {
|
|
|
1334
1505
|
const rowIdentityRow = this.toOutputRow(
|
|
1335
1506
|
itemsToProcess[index] as Record<string, unknown>,
|
|
1336
1507
|
);
|
|
1337
|
-
const rowKey = rowIdentity(rowIdentityRow);
|
|
1508
|
+
const rowKey = rowIdentity(rowIdentityRow, index);
|
|
1338
1509
|
if (rowKey) resultsByKey.set(rowKey, row);
|
|
1339
1510
|
}
|
|
1340
|
-
return rawItems.map((rawItem) => {
|
|
1341
|
-
const rowKey = rowIdentity(rawItem);
|
|
1342
|
-
return (
|
|
1511
|
+
return rawItems.map((rawItem, index) => {
|
|
1512
|
+
const rowKey = rowIdentity(rawItem, index);
|
|
1513
|
+
return this.toPublicOutputRow(
|
|
1343
1514
|
resultsByKey.get(rowKey) ??
|
|
1344
|
-
|
|
1345
|
-
|
|
1515
|
+
completedItemsByKey!.get(rowKey) ??
|
|
1516
|
+
rawItem,
|
|
1346
1517
|
);
|
|
1347
1518
|
});
|
|
1348
1519
|
})()
|
|
1349
|
-
: mapResults;
|
|
1520
|
+
: mapResults.map((row) => this.toPublicOutputRow(row));
|
|
1350
1521
|
|
|
1351
1522
|
return createDeferredPlayDataset({
|
|
1352
1523
|
datasetKind: 'map',
|
|
@@ -1355,7 +1526,7 @@ export class PlayContextImpl {
|
|
|
1355
1526
|
resolvedTableNamespace,
|
|
1356
1527
|
),
|
|
1357
1528
|
count: results.length,
|
|
1358
|
-
previewRows: results.slice(0,
|
|
1529
|
+
previewRows: results.slice(0, 5),
|
|
1359
1530
|
tableNamespace: resolvedTableNamespace,
|
|
1360
1531
|
resolvers: {
|
|
1361
1532
|
count: async () => results.length,
|
|
@@ -1397,9 +1568,11 @@ export class PlayContextImpl {
|
|
|
1397
1568
|
const normalizedTableNamespace = mapScope.artifactTableNamespace;
|
|
1398
1569
|
const rowIdentity = (row: Record<string, unknown>, index = 0) =>
|
|
1399
1570
|
mapScope.rowIdentity(
|
|
1400
|
-
|
|
1401
|
-
Object.
|
|
1402
|
-
(
|
|
1571
|
+
stripCsvProjectedFields(
|
|
1572
|
+
Object.fromEntries(
|
|
1573
|
+
Object.entries(row).filter(
|
|
1574
|
+
([fieldName]) => !mapFieldNames.includes(fieldName),
|
|
1575
|
+
),
|
|
1403
1576
|
),
|
|
1404
1577
|
),
|
|
1405
1578
|
index,
|
|
@@ -1441,13 +1614,52 @@ export class PlayContextImpl {
|
|
|
1441
1614
|
completedRowKey?: string | null;
|
|
1442
1615
|
pendingRowKey?: string | null;
|
|
1443
1616
|
activeBoundaryId?: string | null;
|
|
1617
|
+
failedDelta?: number;
|
|
1444
1618
|
emitEventType?: PlayExecutionEvent['type'];
|
|
1445
1619
|
}) => {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
}
|
|
1620
|
+
const existing =
|
|
1621
|
+
this.checkpoint.mapFrames?.[mapScope.mapInvocationId] ?? null;
|
|
1622
|
+
if (!existing) {
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const completedRowKeys = new Set(existing.completedRowKeys);
|
|
1626
|
+
const pendingRowKeys = new Set(existing.pendingRowKeys);
|
|
1627
|
+
if (input.completedRowKey?.trim()) {
|
|
1628
|
+
completedRowKeys.add(input.completedRowKey.trim());
|
|
1629
|
+
pendingRowKeys.delete(input.completedRowKey.trim());
|
|
1630
|
+
}
|
|
1631
|
+
if (input.pendingRowKey?.trim()) {
|
|
1632
|
+
pendingRowKeys.add(input.pendingRowKey.trim());
|
|
1633
|
+
}
|
|
1634
|
+
const nextFrame: MapExecutionFrame = {
|
|
1635
|
+
...existing,
|
|
1636
|
+
status: input.status ?? existing.status,
|
|
1637
|
+
completedRowKeys: [...completedRowKeys],
|
|
1638
|
+
pendingRowKeys: [...pendingRowKeys],
|
|
1639
|
+
...(input.activeBoundaryId !== undefined
|
|
1640
|
+
? { activeBoundaryId: input.activeBoundaryId }
|
|
1641
|
+
: {}),
|
|
1642
|
+
updatedAt: Date.now(),
|
|
1643
|
+
};
|
|
1644
|
+
this.setMapFrame(nextFrame);
|
|
1645
|
+
if (input.emitEventType) {
|
|
1646
|
+
this.emitExecutionEvent({
|
|
1647
|
+
type: input.emitEventType,
|
|
1648
|
+
mapInvocationId: mapScope.mapInvocationId,
|
|
1649
|
+
mapNodeId: mapScope.mapNodeId ?? null,
|
|
1650
|
+
logicalNamespace: mapScope.logicalNamespace,
|
|
1651
|
+
artifactTableNamespace: mapScope.artifactTableNamespace,
|
|
1652
|
+
completedRows: nextFrame.completedRowKeys.length,
|
|
1653
|
+
failedRows: Math.max(
|
|
1654
|
+
0,
|
|
1655
|
+
totalRows -
|
|
1656
|
+
nextFrame.completedRowKeys.length -
|
|
1657
|
+
nextFrame.pendingRowKeys.length,
|
|
1658
|
+
),
|
|
1659
|
+
totalRows,
|
|
1660
|
+
at: Date.now(),
|
|
1661
|
+
} as PlayExecutionEvent);
|
|
1662
|
+
}
|
|
1451
1663
|
};
|
|
1452
1664
|
|
|
1453
1665
|
if (this.canUsePureJsMapFastPath(definition)) {
|
|
@@ -1456,10 +1668,12 @@ export class PlayContextImpl {
|
|
|
1456
1668
|
fieldEntries,
|
|
1457
1669
|
visibleFields,
|
|
1458
1670
|
normalizedTableNamespace,
|
|
1671
|
+
(row, index) => mapScope.rowIdentity(row, index),
|
|
1459
1672
|
);
|
|
1460
|
-
for (
|
|
1673
|
+
for (let index = 0; index < results.length; index += 1) {
|
|
1674
|
+
const row = results[index]!;
|
|
1461
1675
|
updateMapFrameProgress({
|
|
1462
|
-
completedRowKey: rowIdentity(row),
|
|
1676
|
+
completedRowKey: rowIdentity(row, index),
|
|
1463
1677
|
});
|
|
1464
1678
|
}
|
|
1465
1679
|
if (emitTerminalEvent) {
|
|
@@ -1527,7 +1741,7 @@ export class PlayContextImpl {
|
|
|
1527
1741
|
await this.resolveMapFieldValue(
|
|
1528
1742
|
resolver,
|
|
1529
1743
|
item,
|
|
1530
|
-
|
|
1744
|
+
cloneCsvAliasedRow(baseRow, computedFields),
|
|
1531
1745
|
idx,
|
|
1532
1746
|
),
|
|
1533
1747
|
);
|
|
@@ -1544,7 +1758,7 @@ export class PlayContextImpl {
|
|
|
1544
1758
|
});
|
|
1545
1759
|
}
|
|
1546
1760
|
|
|
1547
|
-
const merged =
|
|
1761
|
+
const merged = cloneCsvAliasedRow(baseRow, computedFields);
|
|
1548
1762
|
activeFieldName = null;
|
|
1549
1763
|
updateMapFrameProgress({
|
|
1550
1764
|
completedRowKey: rowKey,
|
|
@@ -1557,11 +1771,7 @@ export class PlayContextImpl {
|
|
|
1557
1771
|
error: null,
|
|
1558
1772
|
dataPatch: {},
|
|
1559
1773
|
});
|
|
1560
|
-
return
|
|
1561
|
-
Object.entries(merged).filter(
|
|
1562
|
-
([fieldName]) => !fieldName.startsWith('_'),
|
|
1563
|
-
),
|
|
1564
|
-
);
|
|
1774
|
+
return this.toPublicOutputRow(merged);
|
|
1565
1775
|
} catch (error) {
|
|
1566
1776
|
if (isPlayRowExecutionSuspendedError(error)) {
|
|
1567
1777
|
this.pendingRowEventBoundaries.push(error.boundary);
|
|
@@ -1620,17 +1830,17 @@ export class PlayContextImpl {
|
|
|
1620
1830
|
items[index] as Record<string, unknown>,
|
|
1621
1831
|
);
|
|
1622
1832
|
if (result === WAITING_ROW) {
|
|
1623
|
-
const key = rowIdentity(rawItem);
|
|
1833
|
+
const key = rowIdentity(rawItem, index);
|
|
1624
1834
|
if (key) {
|
|
1625
1835
|
completedRowKeys.add(key);
|
|
1626
1836
|
}
|
|
1627
1837
|
continue;
|
|
1628
1838
|
}
|
|
1629
1839
|
const row = result as Record<string, unknown>;
|
|
1630
|
-
const key = rowIdentity(row);
|
|
1840
|
+
const key = rowIdentity(row, index);
|
|
1631
1841
|
if (key) completedRowKeys.add(key);
|
|
1632
1842
|
}
|
|
1633
|
-
this.
|
|
1843
|
+
this.setMapFrame({
|
|
1634
1844
|
...(this.checkpoint.mapFrames?.[mapScope.mapInvocationId] ?? {
|
|
1635
1845
|
mapInvocationId: mapScope.mapInvocationId,
|
|
1636
1846
|
logicalNamespace: mapScope.logicalNamespace,
|
|
@@ -1638,8 +1848,8 @@ export class PlayContextImpl {
|
|
|
1638
1848
|
status: 'suspended' as const,
|
|
1639
1849
|
totalRows,
|
|
1640
1850
|
completedRowKeys: [...completedRowKeys],
|
|
1641
|
-
pendingRowKeys: items.map((item) =>
|
|
1642
|
-
rowIdentity(this.toOutputRow(item as Record<string, unknown>)),
|
|
1851
|
+
pendingRowKeys: items.map((item, index) =>
|
|
1852
|
+
rowIdentity(this.toOutputRow(item as Record<string, unknown>), index),
|
|
1643
1853
|
),
|
|
1644
1854
|
startedAt: Date.now(),
|
|
1645
1855
|
updatedAt: Date.now(),
|
|
@@ -1666,9 +1876,10 @@ export class PlayContextImpl {
|
|
|
1666
1876
|
const results = settledResults.filter(
|
|
1667
1877
|
(result): result is Record<string, unknown> => result !== WAITING_ROW,
|
|
1668
1878
|
);
|
|
1669
|
-
for (
|
|
1879
|
+
for (let index = 0; index < results.length; index += 1) {
|
|
1880
|
+
const row = results[index]!;
|
|
1670
1881
|
updateMapFrameProgress({
|
|
1671
|
-
completedRowKey: rowIdentity(row),
|
|
1882
|
+
completedRowKey: rowIdentity(row, index),
|
|
1672
1883
|
});
|
|
1673
1884
|
}
|
|
1674
1885
|
if (emitTerminalEvent) {
|
|
@@ -1714,7 +1925,7 @@ export class PlayContextImpl {
|
|
|
1714
1925
|
index: number,
|
|
1715
1926
|
path: string[],
|
|
1716
1927
|
): Promise<unknown> {
|
|
1717
|
-
let currentRow =
|
|
1928
|
+
let currentRow = cloneCsvAliasedRow(row);
|
|
1718
1929
|
const produced: Record<string, unknown> = {};
|
|
1719
1930
|
for (const step of program.steps) {
|
|
1720
1931
|
const value = await this.executeStepProgramStep(step, currentRow, index, [
|
|
@@ -1722,7 +1933,7 @@ export class PlayContextImpl {
|
|
|
1722
1933
|
step.name,
|
|
1723
1934
|
]);
|
|
1724
1935
|
produced[step.name] = value;
|
|
1725
|
-
currentRow =
|
|
1936
|
+
currentRow = cloneCsvAliasedRow(currentRow, { [step.name]: value });
|
|
1726
1937
|
}
|
|
1727
1938
|
if (typeof program.returnResolver === 'function') {
|
|
1728
1939
|
return await program.returnResolver(currentRow, this, index);
|
|
@@ -1804,7 +2015,6 @@ export class PlayContextImpl {
|
|
|
1804
2015
|
) {
|
|
1805
2016
|
return false;
|
|
1806
2017
|
}
|
|
1807
|
-
const toolExecuteMarker = ['.tools', 'execute('].join('.');
|
|
1808
2018
|
return Object.values(definition).every((resolver) => {
|
|
1809
2019
|
if (typeof resolver !== 'function') {
|
|
1810
2020
|
return true;
|
|
@@ -1812,7 +2022,7 @@ export class PlayContextImpl {
|
|
|
1812
2022
|
|
|
1813
2023
|
const source = Function.prototype.toString.call(resolver);
|
|
1814
2024
|
return (
|
|
1815
|
-
!source.includes(
|
|
2025
|
+
!source.includes('.tools.execute(') && !source.includes('.waterfall(')
|
|
1816
2026
|
);
|
|
1817
2027
|
});
|
|
1818
2028
|
}
|
|
@@ -1822,13 +2032,11 @@ export class PlayContextImpl {
|
|
|
1822
2032
|
fieldEntries: [string, MapFieldDefinition<T>[string]][],
|
|
1823
2033
|
visibleFields: string[],
|
|
1824
2034
|
tableNamespace: string,
|
|
2035
|
+
rowIdentity: (row: Record<string, unknown>, index: number) => string,
|
|
1825
2036
|
): Promise<Array<Record<string, unknown>>> {
|
|
1826
2037
|
const results: Array<Record<string, unknown>> = [];
|
|
1827
2038
|
this.pureMapExecutionActive = true;
|
|
1828
2039
|
|
|
1829
|
-
const rowIdentity = (row: Record<string, unknown>) =>
|
|
1830
|
-
derivePlayRowIdentity(row, tableNamespace);
|
|
1831
|
-
|
|
1832
2040
|
try {
|
|
1833
2041
|
for (let index = 0; index < items.length; index += 1) {
|
|
1834
2042
|
const item = items[index]!;
|
|
@@ -1841,7 +2049,7 @@ export class PlayContextImpl {
|
|
|
1841
2049
|
activeFieldName = fieldName;
|
|
1842
2050
|
if (this.shouldReuseExistingFieldValue(baseRow, fieldName)) {
|
|
1843
2051
|
computedFields[fieldName] = baseRow[fieldName];
|
|
1844
|
-
this.emitScopedRowUpdate(rowIdentity(baseRow), tableNamespace, {
|
|
2052
|
+
this.emitScopedRowUpdate(rowIdentity(baseRow, index), tableNamespace, {
|
|
1845
2053
|
rowId: index,
|
|
1846
2054
|
status: undefined,
|
|
1847
2055
|
stage: null,
|
|
@@ -1858,7 +2066,7 @@ export class PlayContextImpl {
|
|
|
1858
2066
|
continue;
|
|
1859
2067
|
}
|
|
1860
2068
|
|
|
1861
|
-
this.emitScopedRowUpdate(rowIdentity(baseRow), tableNamespace, {
|
|
2069
|
+
this.emitScopedRowUpdate(rowIdentity(baseRow, index), tableNamespace, {
|
|
1862
2070
|
rowId: index,
|
|
1863
2071
|
status: 'running',
|
|
1864
2072
|
stage: fieldName,
|
|
@@ -1876,10 +2084,10 @@ export class PlayContextImpl {
|
|
|
1876
2084
|
computedFields[fieldName] = await this.resolveMapFieldValue(
|
|
1877
2085
|
resolver,
|
|
1878
2086
|
item,
|
|
1879
|
-
|
|
2087
|
+
cloneCsvAliasedRow(baseRow, computedFields),
|
|
1880
2088
|
index,
|
|
1881
2089
|
);
|
|
1882
|
-
this.emitScopedRowUpdate(rowIdentity(baseRow), tableNamespace, {
|
|
2090
|
+
this.emitScopedRowUpdate(rowIdentity(baseRow, index), tableNamespace, {
|
|
1883
2091
|
rowId: index,
|
|
1884
2092
|
status: undefined,
|
|
1885
2093
|
stage: 'completed',
|
|
@@ -1897,16 +2105,13 @@ export class PlayContextImpl {
|
|
|
1897
2105
|
});
|
|
1898
2106
|
}
|
|
1899
2107
|
|
|
1900
|
-
const merged = { ...baseRow, ...computedFields };
|
|
1901
2108
|
results.push(
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
([fieldName]) => !fieldName.startsWith('_'),
|
|
1905
|
-
),
|
|
2109
|
+
this.toPublicOutputRow(
|
|
2110
|
+
cloneCsvAliasedRow(baseRow, computedFields),
|
|
1906
2111
|
),
|
|
1907
2112
|
);
|
|
1908
2113
|
|
|
1909
|
-
const rowKey = rowIdentity(baseRow);
|
|
2114
|
+
const rowKey = rowIdentity(baseRow, index);
|
|
1910
2115
|
activeFieldName = null;
|
|
1911
2116
|
this.emitScopedRowUpdate(rowKey, tableNamespace, {
|
|
1912
2117
|
rowId: index,
|
|
@@ -1917,7 +2122,7 @@ export class PlayContextImpl {
|
|
|
1917
2122
|
dataPatch: {},
|
|
1918
2123
|
});
|
|
1919
2124
|
} catch (error) {
|
|
1920
|
-
const rowKey = rowIdentity(baseRow);
|
|
2125
|
+
const rowKey = rowIdentity(baseRow, index);
|
|
1921
2126
|
this.emitScopedRowUpdate(rowKey, tableNamespace, {
|
|
1922
2127
|
rowId: index,
|
|
1923
2128
|
status: 'failed',
|
|
@@ -1964,7 +2169,7 @@ export class PlayContextImpl {
|
|
|
1964
2169
|
private async drainQueuedWork<T>(promises: Promise<T>[]): Promise<void> {
|
|
1965
2170
|
// Drain loop: each pass resolves queued waterfalls/tool calls.
|
|
1966
2171
|
// When a batch resolves, rows resume and may queue MORE calls
|
|
1967
|
-
// (e.g. row
|
|
2172
|
+
// (e.g. row does tools.execute('a') then tools.execute('b') sequentially).
|
|
1968
2173
|
// We keep looping until nothing new is queued and all rows finish.
|
|
1969
2174
|
//
|
|
1970
2175
|
// Important: an unresolved Promise by itself does not keep Node alive. The
|
|
@@ -2041,6 +2246,13 @@ export class PlayContextImpl {
|
|
|
2041
2246
|
return { value: item };
|
|
2042
2247
|
}
|
|
2043
2248
|
|
|
2249
|
+
private toPublicOutputRow(row: Record<string, unknown>): Record<string, unknown> {
|
|
2250
|
+
const stripped = stripCsvProjectedFields(row);
|
|
2251
|
+
return Object.fromEntries(
|
|
2252
|
+
Object.entries(stripped).filter(([fieldName]) => !fieldName.startsWith('_')),
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2044
2256
|
async waterfall(
|
|
2045
2257
|
toolNameOrSpec: string | InlineWaterfallSpec,
|
|
2046
2258
|
input: Record<string, unknown>,
|
|
@@ -2081,13 +2293,10 @@ export class PlayContextImpl {
|
|
|
2081
2293
|
}
|
|
2082
2294
|
|
|
2083
2295
|
// Check if this was already resolved in a previous attempt (checkpoint)
|
|
2084
|
-
const resolved = this.
|
|
2085
|
-
|
|
2086
|
-
rowKey: store.rowKey ?? null,
|
|
2087
|
-
});
|
|
2088
|
-
if (resolved.found) {
|
|
2296
|
+
const resolved = this.checkpoint.resolvedWaterfalls[queueKey];
|
|
2297
|
+
if (resolved && rowId in resolved) {
|
|
2089
2298
|
this.log(` Row ${rowId} ${toolName}: recovered from checkpoint`);
|
|
2090
|
-
return resolved
|
|
2299
|
+
return resolved[rowId];
|
|
2091
2300
|
}
|
|
2092
2301
|
|
|
2093
2302
|
return new Promise((resolve) => {
|
|
@@ -2137,9 +2346,13 @@ export class PlayContextImpl {
|
|
|
2137
2346
|
});
|
|
2138
2347
|
}
|
|
2139
2348
|
|
|
2140
|
-
private async executeTool(
|
|
2141
|
-
|
|
2142
|
-
|
|
2349
|
+
private async executeTool(
|
|
2350
|
+
key: string,
|
|
2351
|
+
toolId: string,
|
|
2352
|
+
input: Record<string, unknown>,
|
|
2353
|
+
options?: ToolCallOptions,
|
|
2354
|
+
): Promise<unknown> {
|
|
2355
|
+
const normalizedKey = this.normalizeContextKey(key, 'tool');
|
|
2143
2356
|
|
|
2144
2357
|
const eventWaitHandler =
|
|
2145
2358
|
(await this.options.getIntegrationEventWaitHandler?.(toolId)) ?? null;
|
|
@@ -2160,16 +2373,14 @@ export class PlayContextImpl {
|
|
|
2160
2373
|
|
|
2161
2374
|
if (this.pureMapExecutionActive && !store) {
|
|
2162
2375
|
throw new Error(
|
|
2163
|
-
'
|
|
2376
|
+
'ctx.tools.execute() cannot run inside the pure-JS fast path. Call it directly in the map definition so the batching runtime can stay enabled.',
|
|
2164
2377
|
);
|
|
2165
2378
|
}
|
|
2166
2379
|
|
|
2167
2380
|
if (!store) {
|
|
2168
2381
|
const directRowId = -(this.directToolCallIndex + 1);
|
|
2169
2382
|
this.directToolCallIndex += 1;
|
|
2170
|
-
const directCacheKey = this.buildToolResultCacheKey({
|
|
2171
|
-
rowId: directRowId,
|
|
2172
|
-
});
|
|
2383
|
+
const directCacheKey = this.buildToolResultCacheKey({ rowId: directRowId });
|
|
2173
2384
|
const cached = this.getCachedToolResult(toolId, directCacheKey);
|
|
2174
2385
|
if (cached?.done) {
|
|
2175
2386
|
this.log(`Calling tool: ${toolId} recovered from checkpoint`);
|
|
@@ -2209,9 +2420,15 @@ export class PlayContextImpl {
|
|
|
2209
2420
|
const callId = [
|
|
2210
2421
|
store.tableNamespace?.trim() || 'map',
|
|
2211
2422
|
store.rowKey?.trim() || String(rowId),
|
|
2212
|
-
fieldName?.trim() || 'field',
|
|
2213
2423
|
normalizedKey,
|
|
2424
|
+
toolId,
|
|
2214
2425
|
].join(':');
|
|
2426
|
+
if (this.toolCallResolvers.has(callId)) {
|
|
2427
|
+
throw new Error(
|
|
2428
|
+
`ctx.tools.execute("${normalizedKey}") was called more than once concurrently for the same row. ` +
|
|
2429
|
+
'Use a unique id for each row-scoped tool call.',
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2215
2432
|
|
|
2216
2433
|
const cached = this.getCachedToolResult(
|
|
2217
2434
|
toolId,
|
|
@@ -2240,12 +2457,7 @@ export class PlayContextImpl {
|
|
|
2240
2457
|
}
|
|
2241
2458
|
|
|
2242
2459
|
return new Promise((resolve) => {
|
|
2243
|
-
|
|
2244
|
-
if (existingResolvers) {
|
|
2245
|
-
existingResolvers.push(resolve);
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
this.toolCallResolvers.set(callId, [resolve]);
|
|
2460
|
+
this.toolCallResolvers.set(callId, resolve);
|
|
2249
2461
|
this.emitScopedFieldMetaUpdate({
|
|
2250
2462
|
rowId,
|
|
2251
2463
|
key: store.rowKey ?? null,
|
|
@@ -2266,7 +2478,7 @@ export class PlayContextImpl {
|
|
|
2266
2478
|
input,
|
|
2267
2479
|
tableNamespace: store.tableNamespace,
|
|
2268
2480
|
rowKey: store.rowKey ?? null,
|
|
2269
|
-
description: normalizeStepDescription(
|
|
2481
|
+
description: normalizeStepDescription(options?.description),
|
|
2270
2482
|
});
|
|
2271
2483
|
});
|
|
2272
2484
|
};
|
|
@@ -2282,7 +2494,7 @@ export class PlayContextImpl {
|
|
|
2282
2494
|
{
|
|
2283
2495
|
markSkipped: () => {
|
|
2284
2496
|
this.log(
|
|
2285
|
-
`ctx.tools.execute(
|
|
2497
|
+
`ctx.tools.execute(${toolId}): no-op due completed receipt ${normalizedKey}`,
|
|
2286
2498
|
);
|
|
2287
2499
|
},
|
|
2288
2500
|
execute: executeTool,
|
|
@@ -2602,7 +2814,7 @@ export class PlayContextImpl {
|
|
|
2602
2814
|
const normalizedKey = this.normalizeContextKey(key, 'fetch');
|
|
2603
2815
|
if (rowContext.getStore()) {
|
|
2604
2816
|
throw new Error(
|
|
2605
|
-
'ctx.fetch() must run outside ctx.map(); use ctx.tools.execute(
|
|
2817
|
+
'ctx.fetch() must run outside ctx.map(); use ctx.tools.execute(...) for row-level external requests so Deepline can batch and checkpoint them.',
|
|
2606
2818
|
);
|
|
2607
2819
|
}
|
|
2608
2820
|
|
|
@@ -2685,9 +2897,7 @@ export class PlayContextImpl {
|
|
|
2685
2897
|
}
|
|
2686
2898
|
|
|
2687
2899
|
const rowStore = rowContext.getStore();
|
|
2688
|
-
const scope = rowStore
|
|
2689
|
-
? `row-${rowStore.tableNamespace?.trim() || 'map'}:${rowStore.rowKey?.trim() || String(rowStore.rowId)}`
|
|
2690
|
-
: 'workflow';
|
|
2900
|
+
const scope = rowStore ? `row-${rowStore.rowId}` : 'workflow';
|
|
2691
2901
|
const callIndexKey = `${scope}:${normalizedKey}`;
|
|
2692
2902
|
const callIndex = this.stepCallIndexByKey.get(callIndexKey) ?? 0;
|
|
2693
2903
|
this.stepCallIndexByKey.set(callIndexKey, callIndex + 1);
|
|
@@ -2829,18 +3039,20 @@ export class PlayContextImpl {
|
|
|
2829
3039
|
}),
|
|
2830
3040
|
getCachedResults: (provider) => {
|
|
2831
3041
|
const batchKey = `${queueKey}:${provider}`;
|
|
2832
|
-
const
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
}
|
|
2839
|
-
return recovered;
|
|
3042
|
+
const cached = this.checkpoint.completedBatches[batchKey];
|
|
3043
|
+
if (!cached) return null;
|
|
3044
|
+
this.log(` ${provider}: skipping (recovered from checkpoint)`);
|
|
3045
|
+
return cached.map((entry) => ({
|
|
3046
|
+
request: requests.find((request) => request.rowId === entry.rowId)!,
|
|
3047
|
+
result: entry.result,
|
|
3048
|
+
}));
|
|
2840
3049
|
},
|
|
2841
3050
|
storeCachedResults: (provider, results) => {
|
|
2842
3051
|
const batchKey = `${queueKey}:${provider}`;
|
|
2843
|
-
this.
|
|
3052
|
+
this.checkpoint.completedBatches[batchKey] = results.map((entry) => ({
|
|
3053
|
+
rowId: entry.request.rowId,
|
|
3054
|
+
result: entry.result,
|
|
3055
|
+
}));
|
|
2844
3056
|
},
|
|
2845
3057
|
executeProviderRequests: async (provider, pending) => {
|
|
2846
3058
|
const providerToolId = resolveWaterfallToolId(provider, toolName);
|
|
@@ -2971,7 +3183,7 @@ export class PlayContextImpl {
|
|
|
2971
3183
|
if (wState?.status === 'pending') {
|
|
2972
3184
|
wState.status = 'failed';
|
|
2973
3185
|
wState.error = 'All providers exhausted';
|
|
2974
|
-
this.
|
|
3186
|
+
this.checkpoint.resolvedWaterfalls[queueKey]![req.rowId] = null;
|
|
2975
3187
|
|
|
2976
3188
|
const resolver = this.resolvers.get(`${req.rowId}-${queueKey}`);
|
|
2977
3189
|
if (resolver) {
|
|
@@ -3080,8 +3292,26 @@ export class PlayContextImpl {
|
|
|
3080
3292
|
execute: async (request) => {
|
|
3081
3293
|
const codeStepCtx = {
|
|
3082
3294
|
tools: {
|
|
3083
|
-
execute: async (
|
|
3084
|
-
|
|
3295
|
+
execute: async (
|
|
3296
|
+
requestOrKey:
|
|
3297
|
+
| { tool: string; input: Record<string, unknown> }
|
|
3298
|
+
| string,
|
|
3299
|
+
toolId?: string,
|
|
3300
|
+
payload?: Record<string, unknown>,
|
|
3301
|
+
) => {
|
|
3302
|
+
if (typeof requestOrKey === 'object') {
|
|
3303
|
+
return await this.callToolAPI(
|
|
3304
|
+
requestOrKey.tool,
|
|
3305
|
+
requestOrKey.input,
|
|
3306
|
+
);
|
|
3307
|
+
}
|
|
3308
|
+
if (!toolId || !payload) {
|
|
3309
|
+
throw new Error(
|
|
3310
|
+
'inline waterfall ctx.tools.execute requires a tool and input.',
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
3313
|
+
return await this.callToolAPI(toolId, payload);
|
|
3314
|
+
},
|
|
3085
3315
|
},
|
|
3086
3316
|
};
|
|
3087
3317
|
return await step.run(request.input, codeStepCtx);
|
|
@@ -3447,7 +3677,7 @@ export class PlayContextImpl {
|
|
|
3447
3677
|
if (wState?.status === 'pending') {
|
|
3448
3678
|
wState.status = 'failed';
|
|
3449
3679
|
wState.error = 'All waterfall steps exhausted';
|
|
3450
|
-
this.
|
|
3680
|
+
this.checkpoint.resolvedWaterfalls[queueKey]![req.rowId] = null;
|
|
3451
3681
|
const resolver = this.resolvers.get(`${req.rowId}-${queueKey}`);
|
|
3452
3682
|
if (resolver) {
|
|
3453
3683
|
resolver(null);
|
|
@@ -3499,14 +3729,7 @@ export class PlayContextImpl {
|
|
|
3499
3729
|
|
|
3500
3730
|
wState.status = 'complete';
|
|
3501
3731
|
wState.result = result;
|
|
3502
|
-
this.
|
|
3503
|
-
queueKey,
|
|
3504
|
-
{
|
|
3505
|
-
rowId,
|
|
3506
|
-
rowKey,
|
|
3507
|
-
},
|
|
3508
|
-
result,
|
|
3509
|
-
);
|
|
3732
|
+
this.checkpoint.resolvedWaterfalls[queueKey]![rowId] = result;
|
|
3510
3733
|
|
|
3511
3734
|
const resolver = this.resolvers.get(`${rowId}-${queueKey}`);
|
|
3512
3735
|
if (resolver) {
|
|
@@ -3558,11 +3781,9 @@ export class PlayContextImpl {
|
|
|
3558
3781
|
);
|
|
3559
3782
|
if (cached?.done) {
|
|
3560
3783
|
this.log(` Row ${req.rowId} ${toolId}: recovered from checkpoint`);
|
|
3561
|
-
const
|
|
3562
|
-
if (
|
|
3563
|
-
|
|
3564
|
-
resolver(cached.result);
|
|
3565
|
-
}
|
|
3784
|
+
const resolver = this.toolCallResolvers.get(req.callId);
|
|
3785
|
+
if (resolver) {
|
|
3786
|
+
resolver(cached.result);
|
|
3566
3787
|
this.toolCallResolvers.delete(req.callId);
|
|
3567
3788
|
}
|
|
3568
3789
|
} else {
|
|
@@ -3620,10 +3841,7 @@ export class PlayContextImpl {
|
|
|
3620
3841
|
},
|
|
3621
3842
|
});
|
|
3622
3843
|
} else {
|
|
3623
|
-
await executeChunkedRequests<
|
|
3624
|
-
ToolCallRequest,
|
|
3625
|
-
ToolExecutionResponse
|
|
3626
|
-
>({
|
|
3844
|
+
await executeChunkedRequests<ToolCallRequest, ToolExecutionResponse>({
|
|
3627
3845
|
requests: pendingRequests,
|
|
3628
3846
|
batchSize: await this.rateLimitScheduler.getSuggestedParallelism(
|
|
3629
3847
|
toolId,
|
|
@@ -3772,8 +3990,26 @@ export class PlayContextImpl {
|
|
|
3772
3990
|
toolNameOrSpec.output,
|
|
3773
3991
|
await step.run(input, {
|
|
3774
3992
|
tools: {
|
|
3775
|
-
execute: async (
|
|
3776
|
-
|
|
3993
|
+
execute: async (
|
|
3994
|
+
requestOrKey:
|
|
3995
|
+
| { tool: string; input: Record<string, unknown> }
|
|
3996
|
+
| string,
|
|
3997
|
+
toolId?: string,
|
|
3998
|
+
payload?: Record<string, unknown>,
|
|
3999
|
+
) => {
|
|
4000
|
+
if (typeof requestOrKey === 'object') {
|
|
4001
|
+
return await this.callToolAPI(
|
|
4002
|
+
requestOrKey.tool,
|
|
4003
|
+
requestOrKey.input,
|
|
4004
|
+
);
|
|
4005
|
+
}
|
|
4006
|
+
if (!toolId || !payload) {
|
|
4007
|
+
throw new Error(
|
|
4008
|
+
'inline waterfall ctx.tools.execute requires a tool and input.',
|
|
4009
|
+
);
|
|
4010
|
+
}
|
|
4011
|
+
return await this.callToolAPI(toolId, payload);
|
|
4012
|
+
},
|
|
3777
4013
|
},
|
|
3778
4014
|
}),
|
|
3779
4015
|
)
|
|
@@ -3830,7 +4066,13 @@ export class PlayContextImpl {
|
|
|
3830
4066
|
toolId: string,
|
|
3831
4067
|
input: Record<string, unknown>,
|
|
3832
4068
|
): Promise<unknown> {
|
|
3833
|
-
|
|
4069
|
+
const execution = await this.callToolExecutionAPI(toolId, input);
|
|
4070
|
+
return execution.result != null
|
|
4071
|
+
&& typeof execution.result === 'object'
|
|
4072
|
+
&& !Array.isArray(execution.result)
|
|
4073
|
+
&& 'data' in execution.result
|
|
4074
|
+
? execution.result.data
|
|
4075
|
+
: execution.result;
|
|
3834
4076
|
}
|
|
3835
4077
|
|
|
3836
4078
|
private async callToolExecutionAPI(
|
|
@@ -3895,7 +4137,8 @@ export class PlayContextImpl {
|
|
|
3895
4137
|
if (!response.ok) {
|
|
3896
4138
|
const text = await response.text();
|
|
3897
4139
|
if (
|
|
3898
|
-
|
|
4140
|
+
response.status >= 500 &&
|
|
4141
|
+
response.status < 600 &&
|
|
3899
4142
|
rateLimitAttempt + 1 < TOOL_TRANSIENT_HTTP_MAX_ATTEMPTS
|
|
3900
4143
|
) {
|
|
3901
4144
|
rateLimitAttempt += 1;
|
|
@@ -3947,24 +4190,24 @@ export class PlayContextImpl {
|
|
|
3947
4190
|
}
|
|
3948
4191
|
|
|
3949
4192
|
const data = (await response.json()) as Record<string, unknown>;
|
|
3950
|
-
const
|
|
4193
|
+
const result = data.result ?? data;
|
|
3951
4194
|
const status =
|
|
3952
4195
|
typeof data.status === 'string'
|
|
3953
4196
|
? data.status
|
|
3954
|
-
:
|
|
4197
|
+
: result == null
|
|
3955
4198
|
? 'no_result'
|
|
3956
4199
|
: 'completed';
|
|
3957
4200
|
setSpanAttributes(span, {
|
|
3958
4201
|
'plays.tool_result_kind':
|
|
3959
|
-
|
|
4202
|
+
result == null
|
|
3960
4203
|
? 'null'
|
|
3961
|
-
: Array.isArray(
|
|
4204
|
+
: Array.isArray(result)
|
|
3962
4205
|
? 'array'
|
|
3963
|
-
: typeof
|
|
4206
|
+
: typeof result,
|
|
3964
4207
|
});
|
|
3965
4208
|
return {
|
|
3966
4209
|
status,
|
|
3967
|
-
result
|
|
4210
|
+
result,
|
|
3968
4211
|
metadata: parseExecuteToolMetadata(toolId, data),
|
|
3969
4212
|
};
|
|
3970
4213
|
}
|