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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
export type CsvRenameMap = Record<string, string | readonly string[]>;
|
|
2
|
+
|
|
3
|
+
export type CsvRenameOptions = {
|
|
4
|
+
columns?: CsvRenameMap;
|
|
5
|
+
rename?: CsvRenameMap;
|
|
6
|
+
required?: readonly string[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const CSV_PROJECTED_FIELDS = Symbol.for('deepline.play.csv.projected_fields');
|
|
10
|
+
const CSV_PROJECTED_FIELDS_KEY = '__deeplineCsvProjectedFields';
|
|
11
|
+
|
|
12
|
+
type ProjectedRow = Record<string, unknown> & {
|
|
13
|
+
[CSV_PROJECTED_FIELDS]?: ReadonlySet<string>;
|
|
14
|
+
[CSV_PROJECTED_FIELDS_KEY]?: readonly string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function normalizeCsvHeader(header: string): string {
|
|
18
|
+
return header
|
|
19
|
+
.trim()
|
|
20
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
23
|
+
.replace(/^_+|_+$/g, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readString(value: unknown): string {
|
|
27
|
+
if (typeof value === 'string') return value.trim();
|
|
28
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function buildRowLookup(row: Record<string, unknown>): Map<string, unknown> {
|
|
33
|
+
const lookup = new Map<string, unknown>();
|
|
34
|
+
for (const [field, value] of Object.entries(row)) {
|
|
35
|
+
lookup.set(field, value);
|
|
36
|
+
lookup.set(normalizeCsvHeader(field), value);
|
|
37
|
+
}
|
|
38
|
+
return lookup;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sourceAliases(target: string, aliases: string | readonly string[]) {
|
|
42
|
+
return [target, ...(Array.isArray(aliases) ? aliases : [aliases])];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function applyCsvRenameProjection<T extends Record<string, unknown>>(
|
|
46
|
+
rows: readonly T[],
|
|
47
|
+
options?: CsvRenameOptions,
|
|
48
|
+
): Array<T & Record<string, unknown>> {
|
|
49
|
+
const aliases = {
|
|
50
|
+
...(options?.rename ?? {}),
|
|
51
|
+
...(options?.columns ?? {}),
|
|
52
|
+
};
|
|
53
|
+
if (Object.keys(aliases).length === 0) {
|
|
54
|
+
return [...rows];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const required = new Set(options?.required ?? []);
|
|
58
|
+
return rows.map((row, index) => {
|
|
59
|
+
const lookup = buildRowLookup(row);
|
|
60
|
+
const projectedFields = new Set<string>();
|
|
61
|
+
const projected: Record<string, unknown> = { ...row };
|
|
62
|
+
|
|
63
|
+
for (const [target, sourceNames] of Object.entries(aliases)) {
|
|
64
|
+
let selected: unknown;
|
|
65
|
+
for (const alias of sourceAliases(target, sourceNames)) {
|
|
66
|
+
const exact = lookup.get(alias);
|
|
67
|
+
const normalized = lookup.get(normalizeCsvHeader(alias));
|
|
68
|
+
const candidate = exact ?? normalized;
|
|
69
|
+
if (readString(candidate)) {
|
|
70
|
+
selected = candidate;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (readString(selected)) {
|
|
76
|
+
if (!Object.prototype.hasOwnProperty.call(row, target)) {
|
|
77
|
+
Object.defineProperty(projected, target, {
|
|
78
|
+
value: selected,
|
|
79
|
+
enumerable: false,
|
|
80
|
+
configurable: true,
|
|
81
|
+
writable: true,
|
|
82
|
+
});
|
|
83
|
+
projectedFields.add(target);
|
|
84
|
+
} else {
|
|
85
|
+
projected[target] = selected;
|
|
86
|
+
}
|
|
87
|
+
} else if (required.has(target)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`ctx.csv(..., { rename }) row ${index + 1} is missing required column "${target}".`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Object.defineProperty(projected, CSV_PROJECTED_FIELDS, {
|
|
95
|
+
value: projectedFields,
|
|
96
|
+
enumerable: false,
|
|
97
|
+
configurable: true,
|
|
98
|
+
});
|
|
99
|
+
return projected as T & Record<string, unknown>;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function cloneCsvAliasedRow<T extends Record<string, unknown>>(
|
|
104
|
+
row: T,
|
|
105
|
+
extra?: Record<string, unknown>,
|
|
106
|
+
): Record<string, unknown> {
|
|
107
|
+
const cloned: Record<string, unknown> = { ...row, ...(extra ?? {}) };
|
|
108
|
+
const projectedFields = getCsvProjectedFields(row);
|
|
109
|
+
if (!projectedFields?.size) {
|
|
110
|
+
return cloned;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const clonedProjectedFields = new Set<string>();
|
|
114
|
+
for (const field of projectedFields) {
|
|
115
|
+
if (Object.prototype.hasOwnProperty.call(cloned, field)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const descriptor = Object.getOwnPropertyDescriptor(row, field);
|
|
119
|
+
if (descriptor) {
|
|
120
|
+
Object.defineProperty(cloned, field, {
|
|
121
|
+
...descriptor,
|
|
122
|
+
enumerable: false,
|
|
123
|
+
configurable: true,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
Object.defineProperty(cloned, field, {
|
|
127
|
+
value: row[field],
|
|
128
|
+
enumerable: false,
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
clonedProjectedFields.add(field);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (clonedProjectedFields.size > 0) {
|
|
137
|
+
Object.defineProperty(cloned, CSV_PROJECTED_FIELDS, {
|
|
138
|
+
value: clonedProjectedFields,
|
|
139
|
+
enumerable: false,
|
|
140
|
+
configurable: true,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return cloned;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function stripCsvProjectedFields<T extends Record<string, unknown>>(
|
|
147
|
+
row: T,
|
|
148
|
+
): T {
|
|
149
|
+
const projectedFields = getCsvProjectedFields(row);
|
|
150
|
+
if (!projectedFields?.size) return row;
|
|
151
|
+
const stripped = { ...row };
|
|
152
|
+
for (const field of projectedFields) {
|
|
153
|
+
delete stripped[field];
|
|
154
|
+
}
|
|
155
|
+
delete stripped[CSV_PROJECTED_FIELDS_KEY];
|
|
156
|
+
return stripped as T;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getCsvProjectedFields(
|
|
160
|
+
row: Record<string, unknown>,
|
|
161
|
+
): ReadonlySet<string> | null {
|
|
162
|
+
const symbolFields = (row as ProjectedRow)[CSV_PROJECTED_FIELDS];
|
|
163
|
+
if (symbolFields?.size) return symbolFields;
|
|
164
|
+
const serializedFields = (row as ProjectedRow)[CSV_PROJECTED_FIELDS_KEY];
|
|
165
|
+
if (!Array.isArray(serializedFields) || serializedFields.length === 0) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return new Set(serializedFields.filter((field) => typeof field === 'string'));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function stripCsvProjectionMetadata<T extends Record<string, unknown>>(
|
|
172
|
+
row: T,
|
|
173
|
+
): T {
|
|
174
|
+
if (!Object.prototype.hasOwnProperty.call(row, CSV_PROJECTED_FIELDS_KEY)) {
|
|
175
|
+
return row;
|
|
176
|
+
}
|
|
177
|
+
const stripped = { ...row };
|
|
178
|
+
delete stripped[CSV_PROJECTED_FIELDS_KEY];
|
|
179
|
+
return stripped as T;
|
|
180
|
+
}
|
|
@@ -103,6 +103,9 @@ export interface WaterfallOptions {
|
|
|
103
103
|
|
|
104
104
|
export interface CsvOptions {
|
|
105
105
|
description?: string;
|
|
106
|
+
columns?: Record<string, string | readonly string[]>;
|
|
107
|
+
rename?: Record<string, string | readonly string[]>;
|
|
108
|
+
required?: readonly string[];
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
export interface MapOptions<TItem = Record<string, unknown>> {
|
|
@@ -115,6 +118,12 @@ export interface MapOptions<TItem = Record<string, unknown>> {
|
|
|
115
118
|
* inside the same window still hit the cache. Daily refreshes use 86400.
|
|
116
119
|
*/
|
|
117
120
|
staleAfterSeconds?: number;
|
|
121
|
+
/**
|
|
122
|
+
* Maximum row resolvers to execute concurrently. Use 1 when a map calls a
|
|
123
|
+
* map-backed child play via ctx.runPlay, because those child tables share
|
|
124
|
+
* durable row identity.
|
|
125
|
+
*/
|
|
126
|
+
concurrency?: number;
|
|
118
127
|
/**
|
|
119
128
|
* Optional stable key per row. When provided, row identity is derived from
|
|
120
129
|
* the selected input field(s) or returned value instead of hashing the full
|
|
@@ -139,7 +148,10 @@ export type MapDefinitionOptions<TItem = Record<string, unknown>> = Omit<
|
|
|
139
148
|
'description'
|
|
140
149
|
>;
|
|
141
150
|
|
|
142
|
-
export type MapRunOptions =
|
|
151
|
+
export type MapRunOptions<TItem = Record<string, unknown>> = Pick<
|
|
152
|
+
MapOptions<TItem>,
|
|
153
|
+
'description' | 'staleAfterSeconds' | 'concurrency' | 'key'
|
|
154
|
+
>;
|
|
143
155
|
|
|
144
156
|
export interface ToolCallOptions {
|
|
145
157
|
description?: string;
|
|
@@ -6,7 +6,6 @@ export type ToolResultExecutionMetadata = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export type ToolResultTargetMetadata = {
|
|
9
|
-
value: unknown;
|
|
10
9
|
path: string;
|
|
11
10
|
};
|
|
12
11
|
|
|
@@ -16,6 +15,19 @@ export type ToolResultListMetadata = {
|
|
|
16
15
|
keys: Record<string, string>;
|
|
17
16
|
};
|
|
18
17
|
|
|
18
|
+
export type ToolExtractedValue<T = unknown> = {
|
|
19
|
+
path: string;
|
|
20
|
+
get(): T | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ToolResultEnvelope<
|
|
24
|
+
TData = unknown,
|
|
25
|
+
TMeta = Record<string, unknown>,
|
|
26
|
+
> = {
|
|
27
|
+
data: TData;
|
|
28
|
+
meta?: TMeta;
|
|
29
|
+
};
|
|
30
|
+
|
|
19
31
|
export type ToolResultMetadata = {
|
|
20
32
|
toolId: string;
|
|
21
33
|
execution: ToolResultExecutionMetadata;
|
|
@@ -30,30 +42,30 @@ export type ToolResultMetadataInput = {
|
|
|
30
42
|
listIdentityGetters?: Record<string, readonly string[]>;
|
|
31
43
|
};
|
|
32
44
|
|
|
33
|
-
export type ToolExecuteResult<
|
|
45
|
+
export type ToolExecuteResult<
|
|
46
|
+
TData = unknown,
|
|
47
|
+
TMeta = Record<string, unknown>,
|
|
48
|
+
> = {
|
|
34
49
|
status: string;
|
|
35
|
-
result:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
keys: TKeys,
|
|
44
|
-
name?: string,
|
|
45
|
-
): Array<Record<TKeys[number], unknown>> | null;
|
|
46
|
-
listKeys(name?: string): Record<string, string>;
|
|
50
|
+
result: ToolResultEnvelope<TData, TMeta>;
|
|
51
|
+
extracted: Record<string, ToolExtractedValue>;
|
|
52
|
+
lists: Record<
|
|
53
|
+
string,
|
|
54
|
+
ToolResultListMetadata & {
|
|
55
|
+
get(): Record<string, unknown>[];
|
|
56
|
+
}
|
|
57
|
+
>;
|
|
47
58
|
};
|
|
48
59
|
|
|
49
60
|
type PathSegment = string | number;
|
|
50
61
|
|
|
51
62
|
const TARGET_FALLBACK_KEYS: Record<string, readonly RegExp[]> = {
|
|
52
|
-
email: [/^email$/i, /email/i],
|
|
63
|
+
email: [/^email$/i, /^address$/i, /email/i],
|
|
53
64
|
phone: [/^phone$/i, /mobile/i, /phone/i, /telephone/i],
|
|
54
65
|
linkedin: [/^linkedin_url$/i, /^linkedin$/i, /linkedin/i],
|
|
55
66
|
domain: [/^domain$/i, /company_domain/i, /domain/i],
|
|
56
67
|
status: [/^email_status$/i, /^status$/i],
|
|
68
|
+
email_status: [/^email_status$/i, /^status$/i],
|
|
57
69
|
};
|
|
58
70
|
|
|
59
71
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -114,6 +126,22 @@ function getAtPath(root: unknown, path: string): unknown {
|
|
|
114
126
|
return current;
|
|
115
127
|
}
|
|
116
128
|
|
|
129
|
+
function asResultPath(path: string): string {
|
|
130
|
+
const normalized = path.trim().replace(/^result\./, '');
|
|
131
|
+
return normalized ? `result.${normalized}` : 'result';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function toResultEnvelope(value: unknown): ToolResultEnvelope {
|
|
135
|
+
if (isRecord(value) && 'data' in value) {
|
|
136
|
+
const envelope: ToolResultEnvelope = { data: value.data };
|
|
137
|
+
if ('meta' in value) {
|
|
138
|
+
envelope.meta = value.meta as Record<string, unknown>;
|
|
139
|
+
}
|
|
140
|
+
return envelope;
|
|
141
|
+
}
|
|
142
|
+
return { data: value };
|
|
143
|
+
}
|
|
144
|
+
|
|
117
145
|
function normalizeRows(value: unknown): Record<string, unknown>[] | null {
|
|
118
146
|
if (!Array.isArray(value)) return null;
|
|
119
147
|
return value.map((entry) => (isRecord(entry) ? entry : { value: entry }));
|
|
@@ -124,11 +152,13 @@ function findFirstTargetByPath(
|
|
|
124
152
|
paths: readonly string[] | undefined,
|
|
125
153
|
): ToolResultTargetMetadata | null {
|
|
126
154
|
for (const path of paths ?? []) {
|
|
127
|
-
const normalizedPath = String(path || '')
|
|
155
|
+
const normalizedPath = String(path || '')
|
|
156
|
+
.trim()
|
|
157
|
+
.replace(/^result\./, '');
|
|
128
158
|
if (!normalizedPath) continue;
|
|
129
159
|
const value = getAtPath(result, normalizedPath);
|
|
130
160
|
if (isMeaningfulValue(value)) {
|
|
131
|
-
return {
|
|
161
|
+
return { path: asResultPath(normalizedPath) };
|
|
132
162
|
}
|
|
133
163
|
}
|
|
134
164
|
return null;
|
|
@@ -157,12 +187,18 @@ function findFirstTargetByKey(
|
|
|
157
187
|
new RegExp(`^${target}$`, 'i'),
|
|
158
188
|
];
|
|
159
189
|
for (const [key, value] of Object.entries(result)) {
|
|
160
|
-
if (
|
|
161
|
-
|
|
190
|
+
if (
|
|
191
|
+
patterns.some((pattern) => pattern.test(key)) &&
|
|
192
|
+
isMeaningfulValue(value)
|
|
193
|
+
) {
|
|
194
|
+
return { path: asResultPath(pathToString([...path, key])) };
|
|
162
195
|
}
|
|
163
196
|
}
|
|
164
197
|
for (const [key, value] of Object.entries(result)) {
|
|
165
|
-
const found = findFirstTargetByKey(value, target, depth + 1, [
|
|
198
|
+
const found = findFirstTargetByKey(value, target, depth + 1, [
|
|
199
|
+
...path,
|
|
200
|
+
key,
|
|
201
|
+
]);
|
|
166
202
|
if (found) return found;
|
|
167
203
|
}
|
|
168
204
|
return null;
|
|
@@ -177,12 +213,23 @@ function resolveListRows(
|
|
|
177
213
|
{ path: string; rows: Record<string, unknown>[] }
|
|
178
214
|
> = {};
|
|
179
215
|
for (const rawPath of listExtractorPaths ?? []) {
|
|
180
|
-
const path = String(rawPath || '')
|
|
216
|
+
const path = String(rawPath || '')
|
|
217
|
+
.trim()
|
|
218
|
+
.replace(/^result\./, '');
|
|
181
219
|
if (!path) continue;
|
|
182
|
-
|
|
220
|
+
let resolvedPath = path;
|
|
221
|
+
let rows = normalizeRows(getAtPath(result, resolvedPath));
|
|
222
|
+
if (!rows && !path.startsWith('data.')) {
|
|
223
|
+
resolvedPath = `data.${path}`;
|
|
224
|
+
rows = normalizeRows(getAtPath(result, resolvedPath));
|
|
225
|
+
}
|
|
183
226
|
if (!rows) continue;
|
|
184
|
-
const name =
|
|
185
|
-
|
|
227
|
+
const name = resolvedPath
|
|
228
|
+
.split('.')
|
|
229
|
+
.filter(Boolean)
|
|
230
|
+
.at(-1)
|
|
231
|
+
?.replace(/\[\d+\]$/, '');
|
|
232
|
+
lists[name || resolvedPath] = { path: resolvedPath, rows };
|
|
186
233
|
}
|
|
187
234
|
return lists;
|
|
188
235
|
}
|
|
@@ -194,9 +241,15 @@ function deriveListKeys(input: {
|
|
|
194
241
|
listIdentityGetters?: Record<string, readonly string[]>;
|
|
195
242
|
}): Record<string, string> {
|
|
196
243
|
const keys: Record<string, string> = {};
|
|
197
|
-
for (const [target, paths] of Object.entries(
|
|
244
|
+
for (const [target, paths] of Object.entries(
|
|
245
|
+
input.listIdentityGetters ?? {},
|
|
246
|
+
)) {
|
|
198
247
|
const firstPath = paths
|
|
199
|
-
.map((rawPath) =>
|
|
248
|
+
.map((rawPath) =>
|
|
249
|
+
String(rawPath || '')
|
|
250
|
+
.trim()
|
|
251
|
+
.replace(/^result\./, ''),
|
|
252
|
+
)
|
|
200
253
|
.find(Boolean);
|
|
201
254
|
if (firstPath) {
|
|
202
255
|
keys[target] = firstPath;
|
|
@@ -207,13 +260,19 @@ function deriveListKeys(input: {
|
|
|
207
260
|
}
|
|
208
261
|
|
|
209
262
|
const listPrefix = input.listPath.replace(/\[\d+\]$/, '');
|
|
210
|
-
for (const [target, paths] of Object.entries(
|
|
263
|
+
for (const [target, paths] of Object.entries(
|
|
264
|
+
input.resultIdentityGetters ?? {},
|
|
265
|
+
)) {
|
|
211
266
|
for (const rawPath of paths) {
|
|
212
|
-
const path = String(rawPath || '')
|
|
267
|
+
const path = String(rawPath || '')
|
|
268
|
+
.trim()
|
|
269
|
+
.replace(/^result\./, '');
|
|
213
270
|
if (!path) continue;
|
|
214
271
|
const directPrefix = `${listPrefix}.`;
|
|
215
272
|
if (path.startsWith(directPrefix)) {
|
|
216
|
-
keys[target] = path
|
|
273
|
+
keys[target] = path
|
|
274
|
+
.slice(directPrefix.length)
|
|
275
|
+
.replace(/^\[\d+\]\.?/, '');
|
|
217
276
|
break;
|
|
218
277
|
}
|
|
219
278
|
const indexedPrefix = `${listPrefix}[0].`;
|
|
@@ -249,6 +308,11 @@ function buildTargets(
|
|
|
249
308
|
);
|
|
250
309
|
if (fromMetadata) {
|
|
251
310
|
targets[target] = fromMetadata;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const fallback = findFirstTargetByKey(result, target);
|
|
314
|
+
if (fallback) {
|
|
315
|
+
targets[target] = fallback;
|
|
252
316
|
}
|
|
253
317
|
}
|
|
254
318
|
if (metadataTargets.size > 0) return targets;
|
|
@@ -268,7 +332,7 @@ function buildLists(
|
|
|
268
332
|
const resolved = resolveListRows(result, metadata.listExtractorPaths);
|
|
269
333
|
for (const [name, list] of Object.entries(resolved)) {
|
|
270
334
|
lists[name] = {
|
|
271
|
-
path: list.path,
|
|
335
|
+
path: asResultPath(list.path),
|
|
272
336
|
count: list.rows.length,
|
|
273
337
|
keys: deriveListKeys({
|
|
274
338
|
listPath: list.path,
|
|
@@ -281,80 +345,61 @@ function buildLists(
|
|
|
281
345
|
return lists;
|
|
282
346
|
}
|
|
283
347
|
|
|
348
|
+
function attachDescriptorMethods<TResult>(
|
|
349
|
+
wrapper: ToolExecuteResult<TResult>,
|
|
350
|
+
): ToolExecuteResult<TResult> {
|
|
351
|
+
for (const descriptor of Object.values(wrapper.extracted)) {
|
|
352
|
+
Object.defineProperty(descriptor, 'get', {
|
|
353
|
+
configurable: true,
|
|
354
|
+
enumerable: false,
|
|
355
|
+
value: () => {
|
|
356
|
+
const value = getAtPath(wrapper, descriptor.path);
|
|
357
|
+
return isMeaningfulValue(value) ? value : null;
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const descriptor of Object.values(wrapper.lists)) {
|
|
363
|
+
Object.defineProperty(descriptor, 'get', {
|
|
364
|
+
configurable: true,
|
|
365
|
+
enumerable: false,
|
|
366
|
+
value: () => normalizeRows(getAtPath(wrapper, descriptor.path)) ?? [],
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return wrapper;
|
|
371
|
+
}
|
|
372
|
+
|
|
284
373
|
export function createToolExecuteResult<TResult = unknown>(input: {
|
|
285
374
|
status: string;
|
|
286
375
|
result: TResult;
|
|
287
376
|
metadata: ToolResultMetadataInput;
|
|
288
377
|
execution: ToolResultExecutionMetadata;
|
|
289
378
|
}): ToolExecuteResult<TResult> {
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
const
|
|
379
|
+
void input.execution;
|
|
380
|
+
const result = toResultEnvelope(input.result) as ToolResultEnvelope<TResult>;
|
|
381
|
+
const extracted = buildTargets(result, input.metadata.resultIdentityGetters);
|
|
382
|
+
const lists = buildLists(result, input.metadata);
|
|
383
|
+
const wrapper: ToolExecuteResult<TResult> = {
|
|
293
384
|
status: input.status,
|
|
294
|
-
result
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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;
|
|
385
|
+
result,
|
|
386
|
+
extracted: extracted as Record<string, ToolExtractedValue>,
|
|
387
|
+
lists: lists as ToolExecuteResult<TResult>['lists'],
|
|
388
|
+
};
|
|
389
|
+
return attachDescriptorMethods(wrapper);
|
|
349
390
|
}
|
|
350
391
|
|
|
351
|
-
export function isToolExecuteResult(
|
|
392
|
+
export function isToolExecuteResult(
|
|
393
|
+
value: unknown,
|
|
394
|
+
): value is ToolExecuteResult {
|
|
352
395
|
return (
|
|
353
396
|
isRecord(value) &&
|
|
354
397
|
typeof value.status === 'string' &&
|
|
355
|
-
'
|
|
356
|
-
isRecord(value.
|
|
357
|
-
'
|
|
398
|
+
'result' in value &&
|
|
399
|
+
isRecord(value.result) &&
|
|
400
|
+
'data' in value.result &&
|
|
401
|
+
'extracted' in value &&
|
|
402
|
+
isRecord(value.extracted)
|
|
358
403
|
);
|
|
359
404
|
}
|
|
360
405
|
|
|
@@ -362,26 +407,6 @@ export function cloneToolExecuteResultWithExecution<TResult>(
|
|
|
362
407
|
value: ToolExecuteResult<TResult>,
|
|
363
408
|
execution: ToolResultExecutionMetadata,
|
|
364
409
|
): ToolExecuteResult<TResult> {
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
});
|
|
410
|
+
void execution;
|
|
411
|
+
return attachDescriptorMethods(value);
|
|
387
412
|
}
|