deepline 0.1.0 → 0.1.1

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.
Files changed (97) hide show
  1. package/dist/cli/index.js +212 -54
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/cli/index.mjs +198 -40
  4. package/dist/cli/index.mjs.map +1 -1
  5. package/dist/index.d.mts +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.mjs +1 -1
  9. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +3256 -0
  10. package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +710 -0
  11. package/dist/repo/apps/play-runner-workers/src/entry.ts +5070 -0
  12. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +21 -0
  13. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +177 -0
  14. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +52 -0
  15. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +100 -0
  16. package/dist/repo/apps/play-runner-workers/src/runtime/tool-result.ts +184 -0
  17. package/dist/repo/sdk/src/cli/commands/auth.ts +482 -0
  18. package/dist/repo/sdk/src/cli/commands/billing.ts +188 -0
  19. package/dist/repo/sdk/src/cli/commands/csv.ts +123 -0
  20. package/dist/repo/sdk/src/cli/commands/db.ts +119 -0
  21. package/dist/repo/sdk/src/cli/commands/feedback.ts +40 -0
  22. package/dist/repo/sdk/src/cli/commands/org.ts +117 -0
  23. package/dist/repo/sdk/src/cli/commands/play.ts +3200 -0
  24. package/dist/repo/sdk/src/cli/commands/tools.ts +687 -0
  25. package/dist/repo/sdk/src/cli/dataset-stats.ts +341 -0
  26. package/dist/repo/sdk/src/cli/index.ts +138 -0
  27. package/dist/repo/sdk/src/cli/progress.ts +135 -0
  28. package/dist/repo/sdk/src/cli/trace.ts +61 -0
  29. package/dist/repo/sdk/src/cli/utils.ts +145 -0
  30. package/dist/repo/sdk/src/client.ts +1188 -0
  31. package/dist/repo/sdk/src/compat.ts +77 -0
  32. package/dist/repo/sdk/src/config.ts +285 -0
  33. package/dist/repo/sdk/src/errors.ts +125 -0
  34. package/dist/repo/sdk/src/http.ts +391 -0
  35. package/dist/repo/sdk/src/index.ts +139 -0
  36. package/dist/repo/sdk/src/play.ts +1330 -0
  37. package/dist/repo/sdk/src/plays/bundle-play-file.ts +133 -0
  38. package/dist/repo/sdk/src/plays/harness-stub.ts +210 -0
  39. package/dist/repo/sdk/src/plays/local-file-discovery.ts +326 -0
  40. package/dist/repo/sdk/src/tool-output.ts +489 -0
  41. package/dist/repo/sdk/src/types.ts +669 -0
  42. package/dist/repo/sdk/src/version.ts +2 -0
  43. package/dist/repo/sdk/src/worker-play-entry.ts +286 -0
  44. package/dist/repo/shared_libs/observability/node-tracing.ts +129 -0
  45. package/dist/repo/shared_libs/observability/tracing.ts +98 -0
  46. package/dist/repo/shared_libs/play-runtime/backend.ts +139 -0
  47. package/dist/repo/shared_libs/play-runtime/batch-runtime.ts +182 -0
  48. package/dist/repo/shared_libs/play-runtime/batching-types.ts +91 -0
  49. package/dist/repo/shared_libs/play-runtime/context.ts +3999 -0
  50. package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +78 -0
  51. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +250 -0
  52. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +713 -0
  53. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +10 -0
  54. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +304 -0
  55. package/dist/repo/shared_libs/play-runtime/db-session.ts +462 -0
  56. package/dist/repo/shared_libs/play-runtime/dedup-backend.ts +0 -0
  57. package/dist/repo/shared_libs/play-runtime/default-batch-strategies.ts +124 -0
  58. package/dist/repo/shared_libs/play-runtime/execution-plan.ts +262 -0
  59. package/dist/repo/shared_libs/play-runtime/live-events.ts +214 -0
  60. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +50 -0
  61. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +114 -0
  62. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +158 -0
  63. package/dist/repo/shared_libs/play-runtime/profiles.ts +90 -0
  64. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +172 -0
  65. package/dist/repo/shared_libs/play-runtime/protocol.ts +121 -0
  66. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +42 -0
  67. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +33 -0
  68. package/dist/repo/shared_libs/play-runtime/runtime-actions.ts +208 -0
  69. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +1873 -0
  70. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +2 -0
  71. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +201 -0
  72. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +48 -0
  73. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +84 -0
  74. package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +174 -0
  75. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +147 -0
  76. package/dist/repo/shared_libs/play-runtime/suspension.ts +68 -0
  77. package/dist/repo/shared_libs/play-runtime/tool-batch-executor.ts +146 -0
  78. package/dist/repo/shared_libs/play-runtime/tool-result.ts +387 -0
  79. package/dist/repo/shared_libs/play-runtime/tracing.ts +31 -0
  80. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +75 -0
  81. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +140 -0
  82. package/dist/repo/shared_libs/plays/artifact-transport.ts +14 -0
  83. package/dist/repo/shared_libs/plays/artifact-types.ts +49 -0
  84. package/dist/repo/shared_libs/plays/bundling/index.ts +1346 -0
  85. package/dist/repo/shared_libs/plays/compiler-manifest.ts +186 -0
  86. package/dist/repo/shared_libs/plays/contracts.ts +51 -0
  87. package/dist/repo/shared_libs/plays/dataset.ts +308 -0
  88. package/dist/repo/shared_libs/plays/definition.ts +264 -0
  89. package/dist/repo/shared_libs/plays/file-refs.ts +11 -0
  90. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +206 -0
  91. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +164 -0
  92. package/dist/repo/shared_libs/plays/row-identity.ts +302 -0
  93. package/dist/repo/shared_libs/plays/runtime-validation.ts +415 -0
  94. package/dist/repo/shared_libs/plays/static-pipeline.ts +560 -0
  95. package/dist/repo/shared_libs/temporal/constants.ts +39 -0
  96. package/dist/repo/shared_libs/temporal/preview-config.ts +153 -0
  97. package/package.json +4 -4
@@ -0,0 +1,68 @@
1
+ export type PlayExecutionSuspension =
2
+ | {
3
+ kind: 'sleep';
4
+ boundaryId: string;
5
+ delayMs: number;
6
+ }
7
+ | {
8
+ kind: 'integration_event';
9
+ boundaryId: string;
10
+ eventKey: string;
11
+ timeoutMs: number;
12
+ }
13
+ | {
14
+ kind: 'integration_event_batch';
15
+ boundaries: Array<{
16
+ boundaryId: string;
17
+ eventKey: string;
18
+ timeoutMs: number;
19
+ }>;
20
+ };
21
+
22
+ export class PlayExecutionSuspendedError extends Error {
23
+ readonly suspension: PlayExecutionSuspension;
24
+
25
+ constructor(suspension: PlayExecutionSuspension) {
26
+ super(
27
+ suspension.kind === 'sleep'
28
+ ? `Play execution suspended for durable sleep (${suspension.delayMs}ms).`
29
+ : suspension.kind === 'integration_event_batch'
30
+ ? `Play execution suspended waiting for ${suspension.boundaries.length} integration events.`
31
+ : `Play execution suspended waiting for integration event ${JSON.stringify(suspension.eventKey)}.`,
32
+ );
33
+ this.name = 'PlayExecutionSuspendedError';
34
+ this.suspension = suspension;
35
+ }
36
+ }
37
+
38
+ export class PlayRowExecutionSuspendedError extends Error {
39
+ readonly boundary: {
40
+ boundaryId: string;
41
+ eventKey: string;
42
+ timeoutMs: number;
43
+ };
44
+
45
+ constructor(boundary: {
46
+ boundaryId: string;
47
+ eventKey: string;
48
+ timeoutMs: number;
49
+ }) {
50
+ super(
51
+ `Play row execution suspended waiting for integration event ${JSON.stringify(boundary.eventKey)}.`,
52
+ );
53
+ this.name = 'PlayRowExecutionSuspendedError';
54
+ this.boundary = boundary;
55
+ }
56
+ }
57
+
58
+ export function isPlayRowExecutionSuspendedError(
59
+ value: unknown,
60
+ ): value is PlayRowExecutionSuspendedError {
61
+ return value instanceof PlayRowExecutionSuspendedError;
62
+ }
63
+
64
+ export function isPlayExecutionSuspendedError(
65
+ value: unknown,
66
+ ): value is PlayExecutionSuspendedError {
67
+ return value instanceof PlayExecutionSuspendedError;
68
+ }
@@ -0,0 +1,146 @@
1
+ export type ToolBatchItem = {
2
+ itemKey: string;
3
+ payload: Record<string, unknown>;
4
+ inputHash?: string | null;
5
+ };
6
+
7
+ export type ToolBatchRequest = {
8
+ runId: string;
9
+ orgId: string;
10
+ toolId: string;
11
+ operation: string;
12
+ provider: string;
13
+ items: ToolBatchItem[];
14
+ waterfallId?: string | null;
15
+ stageId?: string | null;
16
+ fieldName?: string | null;
17
+ mapName?: string | null;
18
+ chunkIndex?: number | null;
19
+ userProvidedRateLimitKey?: string | null;
20
+ providerBatchSize: number;
21
+ };
22
+
23
+ export type ToolBatchItemResult = {
24
+ itemKey: string;
25
+ result: unknown;
26
+ cached?: boolean;
27
+ };
28
+
29
+ export type ToolBatchResult = {
30
+ runId: string;
31
+ toolId: string;
32
+ operation: string;
33
+ provider: string;
34
+ batchCount: number;
35
+ itemCount: number;
36
+ results: ToolBatchItemResult[];
37
+ };
38
+
39
+ export type ToolBatchExecutorTransport = {
40
+ executeProviderBatch(input: {
41
+ request: ToolBatchRequest;
42
+ batchIndex: number;
43
+ idempotencyKeys: string[];
44
+ rateLimitKey: string;
45
+ items: ToolBatchItem[];
46
+ }): Promise<ToolBatchItemResult[]>;
47
+ };
48
+
49
+ export type ToolBatchExecutor = {
50
+ executeToolBatch(request: ToolBatchRequest): Promise<ToolBatchResult>;
51
+ };
52
+
53
+ export function createToolBatchExecutor(
54
+ transport: ToolBatchExecutorTransport,
55
+ ): ToolBatchExecutor {
56
+ return {
57
+ async executeToolBatch(request) {
58
+ const providerBatchSize = Math.max(1, Math.floor(request.providerBatchSize));
59
+ const batches = chunkToolBatchItems(request.items, providerBatchSize);
60
+ const results: ToolBatchItemResult[] = [];
61
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex += 1) {
62
+ const items = batches[batchIndex]!;
63
+ results.push(
64
+ ...(await transport.executeProviderBatch({
65
+ request,
66
+ batchIndex,
67
+ items,
68
+ rateLimitKey: buildToolBatchRateLimitKey(request),
69
+ idempotencyKeys: items.map((item) =>
70
+ buildToolBatchIdempotencyKey(request, item),
71
+ ),
72
+ })),
73
+ );
74
+ }
75
+ return {
76
+ runId: request.runId,
77
+ toolId: request.toolId,
78
+ operation: request.operation,
79
+ provider: request.provider,
80
+ batchCount: batches.length,
81
+ itemCount: request.items.length,
82
+ results,
83
+ };
84
+ },
85
+ };
86
+ }
87
+
88
+ export function buildToolBatchIdempotencyKey(
89
+ request: ToolBatchRequest,
90
+ item: ToolBatchItem,
91
+ ): string {
92
+ return [
93
+ request.runId,
94
+ request.mapName ?? '',
95
+ request.chunkIndex ?? '',
96
+ item.itemKey,
97
+ request.fieldName ?? '',
98
+ request.waterfallId ?? '',
99
+ request.stageId ?? '',
100
+ item.inputHash ?? stableToolBatchHash(item.payload),
101
+ ].join(':');
102
+ }
103
+
104
+ export function buildToolBatchRateLimitKey(request: ToolBatchRequest): string {
105
+ return [
106
+ request.orgId,
107
+ request.provider,
108
+ request.operation,
109
+ request.userProvidedRateLimitKey ?? '',
110
+ ].join(':');
111
+ }
112
+
113
+ function chunkToolBatchItems(
114
+ items: readonly ToolBatchItem[],
115
+ size: number,
116
+ ): ToolBatchItem[][] {
117
+ const chunks: ToolBatchItem[][] = [];
118
+ for (let index = 0; index < items.length; index += size) {
119
+ chunks.push(items.slice(index, index + size));
120
+ }
121
+ return chunks;
122
+ }
123
+
124
+ function stableToolBatchHash(value: unknown): string {
125
+ const text = stableStringify(value);
126
+ let hash = 2166136261;
127
+ for (let index = 0; index < text.length; index += 1) {
128
+ hash ^= text.charCodeAt(index);
129
+ hash = Math.imul(hash, 16777619);
130
+ }
131
+ return (hash >>> 0).toString(36);
132
+ }
133
+
134
+ function stableStringify(value: unknown): string {
135
+ if (value === null || typeof value !== 'object') {
136
+ return JSON.stringify(value);
137
+ }
138
+ if (Array.isArray(value)) {
139
+ return `[${value.map(stableStringify).join(',')}]`;
140
+ }
141
+ const record = value as Record<string, unknown>;
142
+ return `{${Object.keys(record)
143
+ .sort()
144
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
145
+ .join(',')}}`;
146
+ }
@@ -0,0 +1,387 @@
1
+ export type ToolResultExecutionMetadata = {
2
+ idempotent: true;
3
+ cached: boolean;
4
+ source: 'live' | 'checkpoint' | 'cache';
5
+ cacheKey?: string;
6
+ };
7
+
8
+ export type ToolResultTargetMetadata = {
9
+ value: unknown;
10
+ path: string;
11
+ };
12
+
13
+ export type ToolResultListMetadata = {
14
+ path: string;
15
+ count: number | null;
16
+ keys: Record<string, string>;
17
+ };
18
+
19
+ export type ToolResultMetadata = {
20
+ toolId: string;
21
+ execution: ToolResultExecutionMetadata;
22
+ targets: Record<string, ToolResultTargetMetadata>;
23
+ lists: Record<string, ToolResultListMetadata>;
24
+ };
25
+
26
+ export type ToolResultMetadataInput = {
27
+ toolId: string;
28
+ resultIdentityGetters?: Record<string, readonly string[]>;
29
+ listExtractorPaths?: readonly string[];
30
+ listIdentityGetters?: Record<string, readonly string[]>;
31
+ };
32
+
33
+ export type ToolExecuteResult<TResult = unknown> = {
34
+ status: string;
35
+ result: TResult;
36
+ _metadata: ToolResultMetadata;
37
+ get<T = unknown>(target: string): T | null;
38
+ getEmail(): string | null;
39
+ getPhone(): string | null;
40
+ getLinkedin(): string | null;
41
+ list<T = Record<string, unknown>>(name?: string): T[] | null;
42
+ listPick<const TKeys extends readonly string[]>(
43
+ keys: TKeys,
44
+ name?: string,
45
+ ): Array<Record<TKeys[number], unknown>> | null;
46
+ listKeys(name?: string): Record<string, string>;
47
+ };
48
+
49
+ type PathSegment = string | number;
50
+
51
+ const TARGET_FALLBACK_KEYS: Record<string, readonly RegExp[]> = {
52
+ email: [/^email$/i, /email/i],
53
+ phone: [/^phone$/i, /mobile/i, /phone/i, /telephone/i],
54
+ linkedin: [/^linkedin_url$/i, /^linkedin$/i, /linkedin/i],
55
+ domain: [/^domain$/i, /company_domain/i, /domain/i],
56
+ status: [/^email_status$/i, /^status$/i],
57
+ };
58
+
59
+ function isRecord(value: unknown): value is Record<string, unknown> {
60
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
61
+ }
62
+
63
+ function isMeaningfulValue(value: unknown): boolean {
64
+ if (value == null) return false;
65
+ if (typeof value === 'string') return value.trim().length > 0;
66
+ if (Array.isArray(value)) return value.length > 0;
67
+ if (typeof value === 'object') return Object.keys(value).length > 0;
68
+ return true;
69
+ }
70
+
71
+ function parsePath(path: string): PathSegment[] {
72
+ const segments: PathSegment[] = [];
73
+ for (const rawPart of path.split('.').filter(Boolean)) {
74
+ const bracketPattern = /([^\[\]]+)|\[(\d+)\]/g;
75
+ let matched = false;
76
+ for (const match of rawPart.matchAll(bracketPattern)) {
77
+ matched = true;
78
+ if (match[1]) {
79
+ segments.push(match[1]);
80
+ } else if (match[2]) {
81
+ segments.push(Number(match[2]));
82
+ }
83
+ }
84
+ if (!matched) {
85
+ segments.push(rawPart);
86
+ }
87
+ }
88
+ return segments;
89
+ }
90
+
91
+ function pathToString(segments: readonly PathSegment[]): string {
92
+ return segments
93
+ .map((segment, index) =>
94
+ typeof segment === 'number'
95
+ ? `[${segment}]`
96
+ : index === 0
97
+ ? segment
98
+ : `.${segment}`,
99
+ )
100
+ .join('');
101
+ }
102
+
103
+ function getAtPath(root: unknown, path: string): unknown {
104
+ let current = root;
105
+ for (const segment of parsePath(path)) {
106
+ if (typeof segment === 'number') {
107
+ if (!Array.isArray(current)) return undefined;
108
+ current = current[segment];
109
+ continue;
110
+ }
111
+ if (!isRecord(current)) return undefined;
112
+ current = current[segment];
113
+ }
114
+ return current;
115
+ }
116
+
117
+ function normalizeRows(value: unknown): Record<string, unknown>[] | null {
118
+ if (!Array.isArray(value)) return null;
119
+ return value.map((entry) => (isRecord(entry) ? entry : { value: entry }));
120
+ }
121
+
122
+ function findFirstTargetByPath(
123
+ result: unknown,
124
+ paths: readonly string[] | undefined,
125
+ ): ToolResultTargetMetadata | null {
126
+ for (const path of paths ?? []) {
127
+ const normalizedPath = String(path || '').trim().replace(/^result\./, '');
128
+ if (!normalizedPath) continue;
129
+ const value = getAtPath(result, normalizedPath);
130
+ if (isMeaningfulValue(value)) {
131
+ return { value, path: normalizedPath };
132
+ }
133
+ }
134
+ return null;
135
+ }
136
+
137
+ function findFirstTargetByKey(
138
+ result: unknown,
139
+ target: string,
140
+ depth = 0,
141
+ path: PathSegment[] = [],
142
+ ): ToolResultTargetMetadata | null {
143
+ if (depth > 6) return null;
144
+ if (Array.isArray(result)) {
145
+ for (let index = 0; index < result.length; index += 1) {
146
+ const found = findFirstTargetByKey(result[index], target, depth + 1, [
147
+ ...path,
148
+ index,
149
+ ]);
150
+ if (found) return found;
151
+ }
152
+ return null;
153
+ }
154
+ if (!isRecord(result)) return null;
155
+
156
+ const patterns = TARGET_FALLBACK_KEYS[target] ?? [
157
+ new RegExp(`^${target}$`, 'i'),
158
+ ];
159
+ for (const [key, value] of Object.entries(result)) {
160
+ if (patterns.some((pattern) => pattern.test(key)) && isMeaningfulValue(value)) {
161
+ return { value, path: pathToString([...path, key]) };
162
+ }
163
+ }
164
+ for (const [key, value] of Object.entries(result)) {
165
+ const found = findFirstTargetByKey(value, target, depth + 1, [...path, key]);
166
+ if (found) return found;
167
+ }
168
+ return null;
169
+ }
170
+
171
+ function resolveListRows(
172
+ result: unknown,
173
+ listExtractorPaths: readonly string[] | undefined,
174
+ ): Record<string, { path: string; rows: Record<string, unknown>[] }> {
175
+ const lists: Record<
176
+ string,
177
+ { path: string; rows: Record<string, unknown>[] }
178
+ > = {};
179
+ for (const rawPath of listExtractorPaths ?? []) {
180
+ const path = String(rawPath || '').trim().replace(/^result\./, '');
181
+ if (!path) continue;
182
+ const rows = normalizeRows(getAtPath(result, path));
183
+ if (!rows) continue;
184
+ const name = path.split('.').filter(Boolean).at(-1)?.replace(/\[\d+\]$/, '');
185
+ lists[name || path] = { path, rows };
186
+ }
187
+ return lists;
188
+ }
189
+
190
+ function deriveListKeys(input: {
191
+ listPath: string;
192
+ rows: readonly Record<string, unknown>[];
193
+ resultIdentityGetters?: Record<string, readonly string[]>;
194
+ listIdentityGetters?: Record<string, readonly string[]>;
195
+ }): Record<string, string> {
196
+ const keys: Record<string, string> = {};
197
+ for (const [target, paths] of Object.entries(input.listIdentityGetters ?? {})) {
198
+ const firstPath = paths
199
+ .map((rawPath) => String(rawPath || '').trim().replace(/^result\./, ''))
200
+ .find(Boolean);
201
+ if (firstPath) {
202
+ keys[target] = firstPath;
203
+ }
204
+ }
205
+ if (Object.keys(keys).length > 0) {
206
+ return keys;
207
+ }
208
+
209
+ const listPrefix = input.listPath.replace(/\[\d+\]$/, '');
210
+ for (const [target, paths] of Object.entries(input.resultIdentityGetters ?? {})) {
211
+ for (const rawPath of paths) {
212
+ const path = String(rawPath || '').trim().replace(/^result\./, '');
213
+ if (!path) continue;
214
+ const directPrefix = `${listPrefix}.`;
215
+ if (path.startsWith(directPrefix)) {
216
+ keys[target] = path.slice(directPrefix.length).replace(/^\[\d+\]\.?/, '');
217
+ break;
218
+ }
219
+ const indexedPrefix = `${listPrefix}[0].`;
220
+ if (path.startsWith(indexedPrefix)) {
221
+ keys[target] = path.slice(indexedPrefix.length);
222
+ break;
223
+ }
224
+ const dottedIndexPrefix = `${listPrefix}.0.`;
225
+ if (path.startsWith(dottedIndexPrefix)) {
226
+ keys[target] = path.slice(dottedIndexPrefix.length);
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ if (Object.keys(keys).length === 0 && input.rows[0]) {
232
+ for (const key of Object.keys(input.rows[0])) {
233
+ keys[key] = key;
234
+ }
235
+ }
236
+ return keys;
237
+ }
238
+
239
+ function buildTargets(
240
+ result: unknown,
241
+ resultIdentityGetters?: Record<string, readonly string[]>,
242
+ ): Record<string, ToolResultTargetMetadata> {
243
+ const targets: Record<string, ToolResultTargetMetadata> = {};
244
+ const metadataTargets = new Set(Object.keys(resultIdentityGetters ?? {}));
245
+ for (const target of metadataTargets) {
246
+ const fromMetadata = findFirstTargetByPath(
247
+ result,
248
+ resultIdentityGetters?.[target],
249
+ );
250
+ if (fromMetadata) {
251
+ targets[target] = fromMetadata;
252
+ }
253
+ }
254
+ if (metadataTargets.size > 0) return targets;
255
+
256
+ for (const target of ['email', 'phone', 'linkedin', 'domain', 'status']) {
257
+ const found = findFirstTargetByKey(result, target);
258
+ if (found) targets[target] = found;
259
+ }
260
+ return targets;
261
+ }
262
+
263
+ function buildLists(
264
+ result: unknown,
265
+ metadata: ToolResultMetadataInput,
266
+ ): Record<string, ToolResultListMetadata> {
267
+ const lists: Record<string, ToolResultListMetadata> = {};
268
+ const resolved = resolveListRows(result, metadata.listExtractorPaths);
269
+ for (const [name, list] of Object.entries(resolved)) {
270
+ lists[name] = {
271
+ path: list.path,
272
+ count: list.rows.length,
273
+ keys: deriveListKeys({
274
+ listPath: list.path,
275
+ rows: list.rows,
276
+ resultIdentityGetters: metadata.resultIdentityGetters,
277
+ listIdentityGetters: metadata.listIdentityGetters,
278
+ }),
279
+ };
280
+ }
281
+ return lists;
282
+ }
283
+
284
+ export function createToolExecuteResult<TResult = unknown>(input: {
285
+ status: string;
286
+ result: TResult;
287
+ metadata: ToolResultMetadataInput;
288
+ execution: ToolResultExecutionMetadata;
289
+ }): ToolExecuteResult<TResult> {
290
+ const targets = buildTargets(input.result, input.metadata.resultIdentityGetters);
291
+ const lists = buildLists(input.result, input.metadata);
292
+ const wrapper = {
293
+ status: input.status,
294
+ result: input.result,
295
+ _metadata: {
296
+ toolId: input.metadata.toolId,
297
+ execution: input.execution,
298
+ targets,
299
+ lists,
300
+ },
301
+ get<T = unknown>(target: string): T | null {
302
+ return (this._metadata.targets[target]?.value as T | undefined) ?? null;
303
+ },
304
+ getEmail(): string | null {
305
+ const value = this.get('email');
306
+ return typeof value === 'string' && value.trim() ? value : null;
307
+ },
308
+ getPhone(): string | null {
309
+ const value = this.get('phone');
310
+ return typeof value === 'string' && value.trim() ? value : null;
311
+ },
312
+ getLinkedin(): string | null {
313
+ const value = this.get('linkedin');
314
+ return typeof value === 'string' && value.trim() ? value : null;
315
+ },
316
+ list<T = Record<string, unknown>>(name?: string): T[] | null {
317
+ const entryName = name ?? Object.keys(this._metadata.lists)[0];
318
+ if (!entryName) return null;
319
+ const list = this._metadata.lists[entryName];
320
+ if (!list) return null;
321
+ return (normalizeRows(getAtPath(this.result, list.path)) as T[] | null) ?? null;
322
+ },
323
+ listPick<const TKeys extends readonly string[]>(
324
+ keys: TKeys,
325
+ name?: string,
326
+ ): Array<Record<TKeys[number], unknown>> | null {
327
+ const entryName = name ?? Object.keys(this._metadata.lists)[0];
328
+ if (!entryName) return null;
329
+ const listMetadata = this._metadata.lists[entryName];
330
+ if (!listMetadata) return null;
331
+ const rows = normalizeRows(getAtPath(this.result, listMetadata.path));
332
+ if (!rows) return null;
333
+ return rows.map((row) => {
334
+ const picked: Record<string, unknown> = {};
335
+ for (const key of keys) {
336
+ const path = listMetadata.keys[key];
337
+ picked[key] = path ? getAtPath(row, path) ?? null : null;
338
+ }
339
+ return picked as Record<TKeys[number], unknown>;
340
+ });
341
+ },
342
+ listKeys(name?: string): Record<string, string> {
343
+ const entryName = name ?? Object.keys(this._metadata.lists)[0];
344
+ if (!entryName) return {};
345
+ return this._metadata.lists[entryName]?.keys ?? {};
346
+ },
347
+ } satisfies ToolExecuteResult<TResult>;
348
+ return wrapper;
349
+ }
350
+
351
+ export function isToolExecuteResult(value: unknown): value is ToolExecuteResult {
352
+ return (
353
+ isRecord(value) &&
354
+ typeof value.status === 'string' &&
355
+ '_metadata' in value &&
356
+ isRecord(value._metadata) &&
357
+ 'result' in value
358
+ );
359
+ }
360
+
361
+ export function cloneToolExecuteResultWithExecution<TResult>(
362
+ value: ToolExecuteResult<TResult>,
363
+ execution: ToolResultExecutionMetadata,
364
+ ): ToolExecuteResult<TResult> {
365
+ return createToolExecuteResult({
366
+ status: value.status,
367
+ result: value.result,
368
+ metadata: {
369
+ toolId: value._metadata.toolId,
370
+ resultIdentityGetters: Object.fromEntries(
371
+ Object.entries(value._metadata.targets).map(([target, info]) => [
372
+ target,
373
+ [info.path],
374
+ ]),
375
+ ),
376
+ listExtractorPaths: Object.values(value._metadata.lists).map(
377
+ (list) => list.path,
378
+ ),
379
+ listIdentityGetters: Object.fromEntries(
380
+ Object.values(value._metadata.lists)
381
+ .flatMap((list) => Object.entries(list.keys))
382
+ .map(([target, path]) => [target, [path]]),
383
+ ),
384
+ },
385
+ execution,
386
+ });
387
+ }
@@ -0,0 +1,31 @@
1
+ type SpanLike = {
2
+ setAttribute: (key: string, value: unknown) => void;
3
+ };
4
+
5
+ type SpanOptions = {
6
+ tracer?: string;
7
+ attributes?: Record<string, unknown>;
8
+ };
9
+
10
+ export function setSpanAttributes(
11
+ span: SpanLike | null | undefined,
12
+ attributes: Record<string, unknown>,
13
+ ): void {
14
+ for (const [key, value] of Object.entries(attributes)) {
15
+ span?.setAttribute?.(key, value);
16
+ }
17
+ }
18
+
19
+ export async function withActiveSpan<T>(
20
+ _name: string,
21
+ options: SpanOptions,
22
+ fn: (span: SpanLike) => Promise<T>,
23
+ ): Promise<T> {
24
+ const attributes = new Map<string, unknown>(Object.entries(options.attributes ?? {}));
25
+ const span: SpanLike = {
26
+ setAttribute(key, value) {
27
+ attributes.set(key, value);
28
+ },
29
+ };
30
+ return await fn(span);
31
+ }
@@ -0,0 +1,75 @@
1
+ import type { BatchResult, PlayCheckpoint, WaterfallRequest } from './ctx-types';
2
+
3
+ export class WaterfallReplayStore {
4
+ constructor(private readonly checkpoint: PlayCheckpoint) {}
5
+
6
+ checkpointKey(input: { rowId: number; rowKey?: string | null }): string {
7
+ return input.rowKey?.trim() || String(input.rowId);
8
+ }
9
+
10
+ getResolved(
11
+ queueKey: string,
12
+ input: {
13
+ rowId: number;
14
+ rowKey?: string | null;
15
+ },
16
+ ): { found: boolean; value: unknown } {
17
+ const resolved = this.checkpoint.resolvedWaterfalls[queueKey];
18
+ if (!resolved) {
19
+ return { found: false, value: undefined };
20
+ }
21
+ const durableKey = this.checkpointKey(input);
22
+ if (Object.prototype.hasOwnProperty.call(resolved, durableKey)) {
23
+ return { found: true, value: resolved[durableKey] };
24
+ }
25
+ return { found: false, value: undefined };
26
+ }
27
+
28
+ setResolved(
29
+ queueKey: string,
30
+ input: {
31
+ rowId: number;
32
+ rowKey?: string | null;
33
+ },
34
+ value: unknown,
35
+ ): void {
36
+ this.checkpoint.resolvedWaterfalls[queueKey] ??= {};
37
+ this.checkpoint.resolvedWaterfalls[queueKey]![this.checkpointKey(input)] =
38
+ value;
39
+ }
40
+
41
+ readProviderBatch(input: {
42
+ batchKey: string;
43
+ requests: readonly WaterfallRequest[];
44
+ }): Array<{ request: WaterfallRequest; result: unknown | null }> | null {
45
+ const cached = this.checkpoint.completedBatches[input.batchKey];
46
+ if (!cached) {
47
+ return null;
48
+ }
49
+ const recovered = cached.flatMap((entry) => {
50
+ const request = input.requests.find((candidate) =>
51
+ entry.rowKey
52
+ ? candidate.rowKey === entry.rowKey
53
+ : candidate.rowId === entry.rowId,
54
+ );
55
+ return request ? [{ request, result: entry.result }] : [];
56
+ });
57
+ return recovered.length > 0 ? recovered : null;
58
+ }
59
+
60
+ writeProviderBatch(
61
+ batchKey: string,
62
+ results: ReadonlyArray<{
63
+ request: WaterfallRequest;
64
+ result: unknown | null;
65
+ }>,
66
+ ): void {
67
+ this.checkpoint.completedBatches[batchKey] = results.map(
68
+ (entry): BatchResult => ({
69
+ rowId: entry.request.rowId,
70
+ rowKey: entry.request.rowKey ?? null,
71
+ result: entry.result,
72
+ }),
73
+ );
74
+ }
75
+ }