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,21 @@
1
+ # Per-play Worker Runtime
2
+
3
+ These modules are bundled into every `esm_workers` play artifact. Keep them:
4
+
5
+ - dependency-free, except for type-only imports
6
+ - small enough to understand in one sitting
7
+ - specific to the Cloudflare Worker execution path
8
+
9
+ Do not import broad `shared_libs/play-runtime/*` modules here. Shared runtime
10
+ modules are optimized for the Node/Temporal runner and often bring along code
11
+ that is correct but expensive in a per-graphHash Worker isolate.
12
+
13
+ If a helper needs zod, Neon, runtime API transport, validation registries, or
14
+ other heavy leaves, put it behind the `env.HARNESS` service binding in
15
+ `apps/play-harness-worker` and call it through `sdk/src/plays/harness-stub.ts`.
16
+
17
+ The intended split is:
18
+
19
+ - `entry.ts`: request/workflow orchestration and ctx construction
20
+ - `runtime/*`: tiny per-play primitives that must execute in the isolate
21
+ - `apps/play-harness-worker`: heavy shared leaves kept warm behind RPC
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Tiny batching helpers for the per-play Worker runtime.
3
+ *
4
+ * This mirrors the small subset of shared_libs/play-runtime batching that the
5
+ * Workers harness needs without importing the broader shared runtime graph.
6
+ */
7
+
8
+ import type { AnyBatchOperationStrategy } from '../../../../shared_libs/play-runtime/batching-types';
9
+
10
+ export type ChunkExecutionResult<TRequest, TResult> = {
11
+ request: TRequest;
12
+ result: TResult | null;
13
+ };
14
+
15
+ export async function executeChunkedRequests<TRequest, TResult>(input: {
16
+ requests: TRequest[];
17
+ batchSize: number;
18
+ execute: (request: TRequest) => Promise<TResult>;
19
+ onChunkComplete?: (
20
+ results: Array<ChunkExecutionResult<TRequest, TResult>>,
21
+ ) => void | Promise<void>;
22
+ }): Promise<Array<ChunkExecutionResult<TRequest, TResult>>> {
23
+ const results: Array<ChunkExecutionResult<TRequest, TResult>> = [];
24
+ for (let start = 0; start < input.requests.length; start += input.batchSize) {
25
+ const chunk = input.requests.slice(start, start + input.batchSize);
26
+ const settled = await Promise.allSettled(
27
+ chunk.map((request) => input.execute(request)),
28
+ );
29
+ const rejected = settled.find(
30
+ (outcome): outcome is PromiseRejectedResult =>
31
+ outcome.status === 'rejected',
32
+ );
33
+ if (rejected) {
34
+ throw new Error(
35
+ `Play batch request failed: ${
36
+ rejected.reason instanceof Error
37
+ ? rejected.reason.message
38
+ : String(rejected.reason)
39
+ }`,
40
+ { cause: rejected.reason },
41
+ );
42
+ }
43
+ for (let index = 0; index < chunk.length; index += 1) {
44
+ const outcome = settled[index] as PromiseFulfilledResult<TResult>;
45
+ results.push({ request: chunk[index]!, result: outcome.value });
46
+ }
47
+ await input.onChunkComplete?.(results.slice(results.length - chunk.length));
48
+ }
49
+ return results;
50
+ }
51
+
52
+ export function compileRequestsWithStrategy<TRequest>(input: {
53
+ requests: TRequest[];
54
+ strategy: AnyBatchOperationStrategy;
55
+ getPayload: (request: TRequest) => Record<string, unknown>;
56
+ }): Array<{
57
+ batchOperation: string;
58
+ memberRequests: TRequest[];
59
+ batchPayload: Record<string, unknown>;
60
+ splitResults: (value: unknown) => Array<unknown | null>;
61
+ }> {
62
+ const compiledBatches: Array<{
63
+ batchOperation: string;
64
+ memberRequests: TRequest[];
65
+ batchPayload: Record<string, unknown>;
66
+ splitResults: (value: unknown) => Array<unknown | null>;
67
+ }> = [];
68
+ const bucketedRequests = new Map<string, TRequest[]>();
69
+ for (const request of input.requests) {
70
+ const payload = input.getPayload(request);
71
+ const bucketKey = String(input.strategy.toBucketKey(payload));
72
+ const bucket = bucketedRequests.get(bucketKey);
73
+ if (bucket) bucket.push(request);
74
+ else bucketedRequests.set(bucketKey, [request]);
75
+ }
76
+ for (const bucketRequests of bucketedRequests.values()) {
77
+ let currentBatch: TRequest[] = [];
78
+ const flushBatch = () => {
79
+ if (currentBatch.length === 0) return;
80
+ const memberRequests = [...currentBatch];
81
+ const compiled = input.strategy.compile(
82
+ memberRequests.map((request) => input.getPayload(request)),
83
+ );
84
+ compiledBatches.push({
85
+ batchOperation: compiled.batchOperation,
86
+ memberRequests,
87
+ batchPayload: compiled.batchPayload,
88
+ splitResults: (value: unknown) => {
89
+ const splitResults = input.strategy.splitResult(value, compiled);
90
+ return memberRequests.map((_, index) => splitResults[index]?.result ?? null);
91
+ },
92
+ });
93
+ currentBatch = [];
94
+ };
95
+ for (const request of bucketRequests) {
96
+ const payload = input.getPayload(request);
97
+ const canAppend =
98
+ currentBatch.length > 0 &&
99
+ currentBatch.length < input.strategy.maxBatchSize &&
100
+ currentBatch.every((existing) =>
101
+ input.strategy.canBatchWith(input.getPayload(existing), payload),
102
+ );
103
+ if (!canAppend && currentBatch.length > 0) flushBatch();
104
+ currentBatch.push(request);
105
+ }
106
+ flushBatch();
107
+ }
108
+ return compiledBatches;
109
+ }
110
+
111
+ export function getDefaultPlayRuntimeBatchStrategy(
112
+ operation: string | null | undefined,
113
+ ): AnyBatchOperationStrategy | null {
114
+ if (operation !== 'test_rate_limit') return null;
115
+ return {
116
+ sourceOperation: 'test_rate_limit',
117
+ batchOperation: 'test_batch_rate_limit',
118
+ kind: 'identifier_batch',
119
+ maxBatchSize: 200,
120
+ canBatchWith(left, right) {
121
+ return String(left.stage || '') === String(right.stage || '');
122
+ },
123
+ toBucketKey(payload) {
124
+ return `test_batch_rate_limit:${String(payload.stage || '')}`;
125
+ },
126
+ toItemKey(payload) {
127
+ return String(payload.lead_id || payload.row_number || payload.key || '');
128
+ },
129
+ compile(payloads) {
130
+ const stage = String(payloads[0]?.stage || '');
131
+ const simulatedDelayMs =
132
+ typeof payloads[0]?.simulated_delay_ms === 'number'
133
+ ? payloads[0].simulated_delay_ms
134
+ : undefined;
135
+ const items = payloads.map((payload, index) => ({
136
+ itemKey: String(payload.lead_id || payload.row_number || `row_${index}`),
137
+ payload,
138
+ }));
139
+ return {
140
+ batchOperation: 'test_batch_rate_limit',
141
+ batchPayload: {
142
+ key: 'batch',
143
+ ...(stage ? { stage } : {}),
144
+ ...(simulatedDelayMs !== undefined
145
+ ? { simulated_delay_ms: simulatedDelayMs }
146
+ : {}),
147
+ items,
148
+ },
149
+ items,
150
+ };
151
+ },
152
+ splitResult(fullResult, compiled) {
153
+ const container =
154
+ fullResult != null && typeof fullResult === 'object' && !Array.isArray(fullResult)
155
+ ? (fullResult as Record<string, unknown>)
156
+ : {};
157
+ const nestedData =
158
+ container.data != null &&
159
+ typeof container.data === 'object' &&
160
+ !Array.isArray(container.data)
161
+ ? (container.data as Record<string, unknown>)
162
+ : {};
163
+ const resultItems = Array.isArray(container.items)
164
+ ? (container.items as Array<{ itemKey?: string; result?: unknown }>)
165
+ : Array.isArray(nestedData.items)
166
+ ? (nestedData.items as Array<{ itemKey?: string; result?: unknown }>)
167
+ : [];
168
+ return compiled.items.map((item, index) => ({
169
+ itemKey: item.itemKey,
170
+ result:
171
+ index < resultItems.length ? (resultItems[index]?.result ?? null) : null,
172
+ rawResult:
173
+ index < resultItems.length ? (resultItems[index]?.result ?? null) : null,
174
+ }));
175
+ },
176
+ };
177
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Per-play Worker execution-plan helpers.
3
+ *
4
+ * Keep this file dependency-free. It is bundled into every esm_workers play
5
+ * artifact, so shared-runtime imports here directly increase cold compile cost.
6
+ */
7
+
8
+ const INLINE_ROWS_LIMIT = 1_000;
9
+ const LARGE_MAP_CHUNK_SIZE = 5_000;
10
+ const SOFT_STEP_BUDGET = 20_000;
11
+ const INGEST_STEP_COUNT = 1;
12
+ const FINALIZATION_STEP_COUNT = 2;
13
+
14
+ export function chooseMapChunkSize(input: {
15
+ totalRows: number;
16
+ mapCount: number;
17
+ stepsPerChunk: number;
18
+ preferredChunkSize?: number | null;
19
+ softWorkflowStepBudget?: number | null;
20
+ }): number {
21
+ const totalRows = Math.max(0, Math.floor(input.totalRows));
22
+ if (totalRows <= INLINE_ROWS_LIMIT) {
23
+ return Math.max(1, totalRows || 1);
24
+ }
25
+ const mapCount = Math.max(1, Math.floor(input.mapCount));
26
+ const stepsPerChunk = Math.max(1, Math.floor(input.stepsPerChunk));
27
+ const softBudget = input.softWorkflowStepBudget ?? SOFT_STEP_BUDGET;
28
+ const nonChunkSteps = INGEST_STEP_COUNT + FINALIZATION_STEP_COUNT;
29
+ const maxChunksAcrossMaps = Math.max(
30
+ mapCount,
31
+ Math.floor((softBudget - nonChunkSteps) / stepsPerChunk),
32
+ );
33
+ const maxChunksPerMap = Math.max(1, Math.floor(maxChunksAcrossMaps / mapCount));
34
+ const minimumSurvivalChunkSize = Math.max(
35
+ 1,
36
+ Math.ceil(totalRows / maxChunksPerMap),
37
+ );
38
+ const preferred = Math.max(
39
+ 1,
40
+ Math.floor(input.preferredChunkSize ?? LARGE_MAP_CHUNK_SIZE),
41
+ );
42
+ return Math.max(preferred, minimumSurvivalChunkSize);
43
+ }
44
+
45
+ export function deterministicMapChunkStepName(input: {
46
+ mapName: string;
47
+ chunkIndex: number;
48
+ phase?: 'prepare' | 'execute' | 'persist' | string;
49
+ }): string {
50
+ const phase = input.phase?.trim() || 'execute';
51
+ return `map:${input.mapName}:chunk:${String(input.chunkIndex).padStart(4, '0')}:${phase}`;
52
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Per-play Worker tool batch executor.
3
+ *
4
+ * This is intentionally local to the Worker bundle. Pulling the shared
5
+ * executor module back in would widen the bundle graph for a tiny helper.
6
+ */
7
+
8
+ export type ToolBatchRequest = {
9
+ runId: string;
10
+ orgId: string;
11
+ toolId: string;
12
+ operation: string;
13
+ provider: string;
14
+ items: Array<{
15
+ itemKey: string;
16
+ payload: Record<string, unknown>;
17
+ inputHash?: string | null;
18
+ }>;
19
+ waterfallId?: string | null;
20
+ stageId?: string | null;
21
+ fieldName?: string | null;
22
+ mapName?: string | null;
23
+ chunkIndex?: number | null;
24
+ userProvidedRateLimitKey?: string | null;
25
+ providerBatchSize: number;
26
+ };
27
+
28
+ type ToolBatchItemResult = {
29
+ itemKey: string;
30
+ result: unknown;
31
+ cached?: boolean;
32
+ };
33
+
34
+ export function createToolBatchExecutor(input: {
35
+ executeProviderBatch(batch: {
36
+ request: ToolBatchRequest;
37
+ batchIndex: number;
38
+ idempotencyKeys: string[];
39
+ rateLimitKey: string;
40
+ items: ToolBatchRequest['items'];
41
+ }): Promise<ToolBatchItemResult[]>;
42
+ }) {
43
+ return {
44
+ async executeToolBatch(request: ToolBatchRequest) {
45
+ const providerBatchSize = Math.max(1, Math.floor(request.providerBatchSize));
46
+ const batches: Array<ToolBatchRequest['items']> = [];
47
+ for (let index = 0; index < request.items.length; index += providerBatchSize) {
48
+ batches.push(request.items.slice(index, index + providerBatchSize));
49
+ }
50
+ const results: ToolBatchItemResult[] = [];
51
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex += 1) {
52
+ const items = batches[batchIndex]!;
53
+ results.push(
54
+ ...(await input.executeProviderBatch({
55
+ request,
56
+ batchIndex,
57
+ items,
58
+ rateLimitKey: [
59
+ request.orgId,
60
+ request.provider,
61
+ request.operation,
62
+ request.userProvidedRateLimitKey ?? '',
63
+ ].join(':'),
64
+ idempotencyKeys: items.map((item) =>
65
+ [
66
+ request.runId,
67
+ request.mapName ?? '',
68
+ request.chunkIndex ?? '',
69
+ item.itemKey,
70
+ request.fieldName ?? '',
71
+ request.waterfallId ?? '',
72
+ request.stageId ?? '',
73
+ item.inputHash ?? stableStringify(item.payload),
74
+ ].join(':'),
75
+ ),
76
+ })),
77
+ );
78
+ }
79
+ return {
80
+ runId: request.runId,
81
+ toolId: request.toolId,
82
+ operation: request.operation,
83
+ provider: request.provider,
84
+ batchCount: batches.length,
85
+ itemCount: request.items.length,
86
+ results,
87
+ };
88
+ },
89
+ };
90
+ }
91
+
92
+ function stableStringify(value: unknown): string {
93
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
94
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
95
+ const record = value as Record<string, unknown>;
96
+ return `{${Object.keys(record)
97
+ .sort()
98
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
99
+ .join(',')}}`;
100
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Per-play Worker tool result wrapper.
3
+ *
4
+ * User play code expects ergonomic getters like `result.getEmail()`. Keep this
5
+ * local and dependency-free so those getters do not pull the shared runtime
6
+ * module graph into every dynamic Worker.
7
+ */
8
+
9
+ export type ToolResultMetadataInput = {
10
+ toolId: string;
11
+ resultIdentityGetters?: Record<string, readonly string[]>;
12
+ listExtractorPaths?: readonly string[];
13
+ listIdentityGetters?: Record<string, readonly string[]>;
14
+ };
15
+
16
+ type ToolResultExecutionMetadata = {
17
+ idempotent: true;
18
+ cached: boolean;
19
+ source: 'live' | 'checkpoint' | 'cache';
20
+ cacheKey?: string;
21
+ };
22
+
23
+ export type ToolExecuteResult<TResult = unknown> = {
24
+ status: string;
25
+ result: TResult;
26
+ _metadata: {
27
+ toolId: string;
28
+ execution: ToolResultExecutionMetadata;
29
+ targets: Record<string, { value: unknown; path: string }>;
30
+ lists: Record<string, { path: string; count: number | null; keys: Record<string, string> }>;
31
+ };
32
+ get<T = unknown>(target: string): T | null;
33
+ getEmail(): string | null;
34
+ getPhone(): string | null;
35
+ getLinkedin(): string | null;
36
+ list<T = Record<string, unknown>>(name?: string): T[] | null;
37
+ listPick<const TKeys extends readonly string[]>(
38
+ keys: TKeys,
39
+ name?: string,
40
+ ): Array<Record<TKeys[number], unknown>> | null;
41
+ listKeys(name?: string): Record<string, string>;
42
+ };
43
+
44
+ type PathSegment = string | number;
45
+
46
+ export function createToolExecuteResult<TResult = unknown>(input: {
47
+ status: string;
48
+ result: TResult;
49
+ metadata: ToolResultMetadataInput;
50
+ execution: ToolResultExecutionMetadata;
51
+ }): ToolExecuteResult<TResult> {
52
+ const targets: ToolExecuteResult<TResult>['_metadata']['targets'] = {};
53
+ for (const [target, paths] of Object.entries(
54
+ input.metadata.resultIdentityGetters ?? {},
55
+ )) {
56
+ const path = paths
57
+ .map((rawPath) => String(rawPath || '').trim().replace(/^result\./, ''))
58
+ .find((candidate) => candidate && getAtPath(input.result, candidate) != null);
59
+ if (path) {
60
+ targets[target] = { value: getAtPath(input.result, path), path };
61
+ }
62
+ }
63
+ if (Object.keys(targets).length === 0 && isRecordLike(input.result)) {
64
+ for (const target of ['email', 'phone', 'linkedin', 'domain', 'status']) {
65
+ if (target in input.result && input.result[target] != null) {
66
+ targets[target] = { value: input.result[target], path: target };
67
+ }
68
+ }
69
+ }
70
+
71
+ const lists: ToolExecuteResult<TResult>['_metadata']['lists'] = {};
72
+ for (const rawPath of input.metadata.listExtractorPaths ?? []) {
73
+ const path = String(rawPath || '').trim().replace(/^result\./, '');
74
+ const rows = normalizeRows(getAtPath(input.result, path));
75
+ if (!path || !rows) continue;
76
+ const name = path.split('.').filter(Boolean).at(-1)?.replace(/\[\d+\]$/, '') || path;
77
+ const keys = Object.fromEntries(
78
+ Object.keys(rows[0] ?? {}).map((key) => [key, key]),
79
+ );
80
+ lists[name] = { path, count: rows.length, keys };
81
+ }
82
+
83
+ return {
84
+ status: input.status,
85
+ result: input.result,
86
+ _metadata: {
87
+ toolId: input.metadata.toolId,
88
+ execution: input.execution,
89
+ targets,
90
+ lists,
91
+ },
92
+ get<T = unknown>(target: string): T | null {
93
+ return (this._metadata.targets[target]?.value as T | undefined) ?? null;
94
+ },
95
+ getEmail(): string | null {
96
+ const value = this.get('email');
97
+ return typeof value === 'string' && value.trim() ? value : null;
98
+ },
99
+ getPhone(): string | null {
100
+ const value = this.get('phone');
101
+ return typeof value === 'string' && value.trim() ? value : null;
102
+ },
103
+ getLinkedin(): string | null {
104
+ const value = this.get('linkedin');
105
+ return typeof value === 'string' && value.trim() ? value : null;
106
+ },
107
+ list<T = Record<string, unknown>>(name?: string): T[] | null {
108
+ const entryName = name ?? Object.keys(this._metadata.lists)[0];
109
+ if (!entryName) return null;
110
+ const list = this._metadata.lists[entryName];
111
+ return list ? (normalizeRows(getAtPath(this.result, list.path)) as T[] | null) : null;
112
+ },
113
+ listPick<const TKeys extends readonly string[]>(
114
+ keys: TKeys,
115
+ name?: string,
116
+ ): Array<Record<TKeys[number], unknown>> | null {
117
+ const entryName = name ?? Object.keys(this._metadata.lists)[0];
118
+ if (!entryName) return null;
119
+ const listMetadata = this._metadata.lists[entryName];
120
+ if (!listMetadata) return null;
121
+ const rows = normalizeRows(getAtPath(this.result, listMetadata.path));
122
+ if (!rows) return null;
123
+ return rows.map((row) => {
124
+ const picked: Record<string, unknown> = {};
125
+ for (const key of keys) {
126
+ const path = listMetadata.keys[key];
127
+ picked[key] = path ? getAtPath(row, path) ?? null : null;
128
+ }
129
+ return picked as Record<TKeys[number], unknown>;
130
+ });
131
+ },
132
+ listKeys(name?: string): Record<string, string> {
133
+ const entryName = name ?? Object.keys(this._metadata.lists)[0];
134
+ return entryName ? (this._metadata.lists[entryName]?.keys ?? {}) : {};
135
+ },
136
+ };
137
+ }
138
+
139
+ export function isToolExecuteResult(value: unknown): value is ToolExecuteResult {
140
+ return (
141
+ isRecordLike(value) &&
142
+ typeof value.status === 'string' &&
143
+ isRecordLike(value._metadata) &&
144
+ 'result' in value
145
+ );
146
+ }
147
+
148
+ function parseToolPath(path: string): PathSegment[] {
149
+ const segments: PathSegment[] = [];
150
+ for (const rawPart of path.split('.').filter(Boolean)) {
151
+ const bracketPattern = /([^\[\]]+)|\[(\d+)\]/g;
152
+ let matched = false;
153
+ for (const match of rawPart.matchAll(bracketPattern)) {
154
+ matched = true;
155
+ if (match[1]) segments.push(match[1]);
156
+ else if (match[2]) segments.push(Number(match[2]));
157
+ }
158
+ if (!matched) segments.push(rawPart);
159
+ }
160
+ return segments;
161
+ }
162
+
163
+ function getAtPath(root: unknown, path: string): unknown {
164
+ let current = root;
165
+ for (const segment of parseToolPath(path)) {
166
+ if (typeof segment === 'number') {
167
+ if (!Array.isArray(current)) return undefined;
168
+ current = current[segment];
169
+ } else {
170
+ if (!isRecordLike(current)) return undefined;
171
+ current = current[segment];
172
+ }
173
+ }
174
+ return current;
175
+ }
176
+
177
+ function normalizeRows(value: unknown): Record<string, unknown>[] | null {
178
+ if (!Array.isArray(value)) return null;
179
+ return value.map((entry) => (isRecordLike(entry) ? entry : { value: entry }));
180
+ }
181
+
182
+ function isRecordLike(value: unknown): value is Record<string, unknown> {
183
+ return value != null && typeof value === 'object' && !Array.isArray(value);
184
+ }