deepline 0.1.146 → 0.1.147
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +205 -75
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/dataset-handles.ts +8 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/output-datasets.ts +363 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/result-dataset-persistence.ts +50 -0
- package/dist/bundling-sources/sdk/src/release.ts +54 -21
- package/dist/bundling-sources/sdk/src/tool-output.ts +18 -7
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result-types.ts +2 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +81 -5
- package/dist/cli/index.js +155 -41
- package/dist/cli/index.mjs +167 -53
- package/dist/index.d.mts +91 -89
- package/dist/index.d.ts +91 -89
- package/dist/index.js +545 -31
- package/dist/index.mjs +546 -32
- package/package.json +2 -2
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizePlayNameForSheet,
|
|
3
|
+
normalizeTableNamespace,
|
|
4
|
+
POSTGRES_IDENTIFIER_MAX_LENGTH,
|
|
5
|
+
sha256Hex,
|
|
6
|
+
} from '../../../../shared_libs/plays/row-identity';
|
|
7
|
+
import {
|
|
8
|
+
isSerializedPlayDataset,
|
|
9
|
+
type SerializedPlayDataset,
|
|
10
|
+
} from '../../../../shared_libs/plays/dataset';
|
|
11
|
+
|
|
12
|
+
export type OutputDatasetRow = Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
export type PromotedOutputDataset = {
|
|
15
|
+
path: string;
|
|
16
|
+
tableNamespace: string;
|
|
17
|
+
rows: OutputDatasetRow[];
|
|
18
|
+
handle: unknown;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ProjectedResultDatasetHandle = {
|
|
22
|
+
path: string;
|
|
23
|
+
tableNamespace: string;
|
|
24
|
+
datasetKind: 'csv' | 'map' | null;
|
|
25
|
+
handle: unknown;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ProjectTerminalResultOptions = {
|
|
29
|
+
playName?: string | null;
|
|
30
|
+
reservedTableNamespaces?: Iterable<string>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type DatasetFactory = (input: {
|
|
34
|
+
path: string;
|
|
35
|
+
tableNamespace: string;
|
|
36
|
+
rows: OutputDatasetRow[];
|
|
37
|
+
}) => unknown;
|
|
38
|
+
|
|
39
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
40
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isDatasetLike(value: unknown): boolean {
|
|
44
|
+
if (!isRecord(value)) return false;
|
|
45
|
+
return (
|
|
46
|
+
typeof value.datasetId === 'string' &&
|
|
47
|
+
typeof value.count === 'function' &&
|
|
48
|
+
typeof value.peek === 'function' &&
|
|
49
|
+
typeof value.toJSON === 'function'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function datasetMetadata(value: unknown): Record<string, unknown> | null {
|
|
54
|
+
if (!isDatasetLike(value)) return null;
|
|
55
|
+
try {
|
|
56
|
+
const metadata = (value as { toJSON(): unknown }).toJSON();
|
|
57
|
+
return isRecord(metadata) ? metadata : null;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isRowArray(
|
|
64
|
+
value: unknown,
|
|
65
|
+
path: readonly string[],
|
|
66
|
+
): value is OutputDatasetRow[] {
|
|
67
|
+
if (!Array.isArray(value)) return false;
|
|
68
|
+
if (value.length === 0) return isLikelyRowOutputPath(path);
|
|
69
|
+
return value.every(isRecord);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const NON_ROW_ARRAY_OUTPUT_KEYS = new Set([
|
|
73
|
+
'actions',
|
|
74
|
+
'columns',
|
|
75
|
+
'errors',
|
|
76
|
+
'logs',
|
|
77
|
+
'messages',
|
|
78
|
+
'next',
|
|
79
|
+
'preview',
|
|
80
|
+
'render',
|
|
81
|
+
'steps',
|
|
82
|
+
'warnings',
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
function isLikelyRowOutputPath(path: readonly string[]): boolean {
|
|
86
|
+
if (path.length === 0) return true;
|
|
87
|
+
let leaf = '';
|
|
88
|
+
try {
|
|
89
|
+
leaf = normalizeTableNamespace(path.at(-1) ?? '');
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (NON_ROW_ARRAY_OUTPUT_KEYS.has(leaf)) return false;
|
|
94
|
+
return /(^|_)(rows?|results?|records?|items?|leads?|people|contacts?|companies|accounts?)(_|$)/.test(
|
|
95
|
+
leaf,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function tableNamespaceForPath(path: readonly string[]): string {
|
|
100
|
+
const raw = path.length > 0 ? path.join('_') : 'rows';
|
|
101
|
+
try {
|
|
102
|
+
return normalizeTableNamespace(raw);
|
|
103
|
+
} catch {
|
|
104
|
+
const hash = sha256Hex(raw).slice(0, 10);
|
|
105
|
+
const leaf = path.at(-1) ?? 'rows';
|
|
106
|
+
let prefix = 'rows';
|
|
107
|
+
try {
|
|
108
|
+
prefix = normalizeTableNamespace(leaf).slice(0, 52) || 'rows';
|
|
109
|
+
} catch {
|
|
110
|
+
prefix = 'rows';
|
|
111
|
+
}
|
|
112
|
+
return normalizeTableNamespace(`${prefix}_${hash}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const MIN_GENERATED_RESULT_NAMESPACE_LENGTH = 9;
|
|
117
|
+
|
|
118
|
+
function maxTableNamespaceLengthForPlay(playName: string | null | undefined) {
|
|
119
|
+
if (!playName) {
|
|
120
|
+
return POSTGRES_IDENTIFIER_MAX_LENGTH;
|
|
121
|
+
}
|
|
122
|
+
let playNameLength: number;
|
|
123
|
+
try {
|
|
124
|
+
playNameLength = normalizePlayNameForSheet(playName).length;
|
|
125
|
+
} catch {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Play name ${JSON.stringify(
|
|
128
|
+
playName,
|
|
129
|
+
)} cannot be used to create generated result dataset table names. ` +
|
|
130
|
+
'Shorten the play name or use only identifier-safe characters.',
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const maxLength = POSTGRES_IDENTIFIER_MAX_LENGTH - playNameLength - 1;
|
|
134
|
+
if (maxLength < MIN_GENERATED_RESULT_NAMESPACE_LENGTH) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Play name ${JSON.stringify(
|
|
137
|
+
playName,
|
|
138
|
+
)} leaves only ${maxLength} characters for generated result dataset table names. ` +
|
|
139
|
+
`Shorten the play name so result datasets have at least ${MIN_GENERATED_RESULT_NAMESPACE_LENGTH} characters of namespace budget.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return maxLength;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function boundTableNamespace(
|
|
146
|
+
value: string,
|
|
147
|
+
hashInput: string,
|
|
148
|
+
maxLength: number,
|
|
149
|
+
): string {
|
|
150
|
+
const normalized = normalizeTableNamespace(value);
|
|
151
|
+
if (normalized.length <= maxLength) {
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const hash = sha256Hex(hashInput).slice(0, 10);
|
|
156
|
+
if (maxLength <= 12) {
|
|
157
|
+
return normalizeTableNamespace(`r${hash}`.slice(0, maxLength));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const suffix = `_${hash}`;
|
|
161
|
+
const prefix = normalized.slice(0, maxLength - suffix.length) || 'r';
|
|
162
|
+
return normalizeTableNamespace(`${prefix}${suffix}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function resultTableNamespaceForPath(
|
|
166
|
+
path: readonly string[],
|
|
167
|
+
maxLength: number,
|
|
168
|
+
): string {
|
|
169
|
+
const base = tableNamespaceForPath(path);
|
|
170
|
+
const raw = `result_${base}`;
|
|
171
|
+
const hashInput = path.join('.') || 'rows';
|
|
172
|
+
try {
|
|
173
|
+
return boundTableNamespace(raw, hashInput, maxLength);
|
|
174
|
+
} catch {
|
|
175
|
+
const hash = sha256Hex(path.join('.')).slice(0, 10);
|
|
176
|
+
return boundTableNamespace(
|
|
177
|
+
`result_${base.slice(0, 45)}_${hash}`,
|
|
178
|
+
hashInput,
|
|
179
|
+
maxLength,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function seedUsedNamespaces(
|
|
185
|
+
namespaces: Iterable<string> | undefined,
|
|
186
|
+
): Set<string> {
|
|
187
|
+
const usedNamespaces = new Set<string>();
|
|
188
|
+
for (const namespace of namespaces ?? []) {
|
|
189
|
+
try {
|
|
190
|
+
usedNamespaces.add(normalizeTableNamespace(namespace));
|
|
191
|
+
} catch {
|
|
192
|
+
// Ignore malformed historical metadata; result datasets still get a
|
|
193
|
+
// result-prefixed namespace and local collision disambiguation.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return usedNamespaces;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function disambiguateTableNamespace(
|
|
200
|
+
tableNamespace: string,
|
|
201
|
+
path: readonly string[],
|
|
202
|
+
usedNamespaces: Set<string>,
|
|
203
|
+
maxLength: number,
|
|
204
|
+
): string {
|
|
205
|
+
if (!usedNamespaces.has(tableNamespace)) {
|
|
206
|
+
usedNamespaces.add(tableNamespace);
|
|
207
|
+
return tableNamespace;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const hash = sha256Hex(path.join('.')).slice(0, 10);
|
|
211
|
+
let candidate = boundTableNamespace(
|
|
212
|
+
`${tableNamespace}_${hash}`,
|
|
213
|
+
`${path.join('.')}:collision`,
|
|
214
|
+
maxLength,
|
|
215
|
+
);
|
|
216
|
+
let suffix = 2;
|
|
217
|
+
while (usedNamespaces.has(candidate)) {
|
|
218
|
+
candidate = boundTableNamespace(
|
|
219
|
+
`${tableNamespace}_${hash}_${suffix}`,
|
|
220
|
+
`${path.join('.')}:collision:${suffix}`,
|
|
221
|
+
maxLength,
|
|
222
|
+
);
|
|
223
|
+
suffix += 1;
|
|
224
|
+
}
|
|
225
|
+
usedNamespaces.add(candidate);
|
|
226
|
+
return candidate;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function projectTerminalResultDatasets(
|
|
230
|
+
value: unknown,
|
|
231
|
+
createDataset: DatasetFactory,
|
|
232
|
+
options: ProjectTerminalResultOptions = {},
|
|
233
|
+
): {
|
|
234
|
+
result: unknown;
|
|
235
|
+
datasets: PromotedOutputDataset[];
|
|
236
|
+
handles: ProjectedResultDatasetHandle[];
|
|
237
|
+
} {
|
|
238
|
+
const datasets: PromotedOutputDataset[] = [];
|
|
239
|
+
const handles: ProjectedResultDatasetHandle[] = [];
|
|
240
|
+
const seen = new WeakMap<object, unknown>();
|
|
241
|
+
const seenHandles = new WeakSet<object>();
|
|
242
|
+
const usedNamespaces = seedUsedNamespaces(options.reservedTableNamespaces);
|
|
243
|
+
const maxTableNamespaceLength = maxTableNamespaceLengthForPlay(
|
|
244
|
+
options.playName,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const rememberHandle = (handle: unknown, path: readonly string[]): void => {
|
|
248
|
+
if (!isRecord(handle) || seenHandles.has(handle)) return;
|
|
249
|
+
const metadata = datasetMetadata(handle);
|
|
250
|
+
const tableNamespace =
|
|
251
|
+
typeof metadata?.tableNamespace === 'string'
|
|
252
|
+
? metadata.tableNamespace
|
|
253
|
+
: null;
|
|
254
|
+
if (!tableNamespace) return;
|
|
255
|
+
seenHandles.add(handle);
|
|
256
|
+
const datasetKind =
|
|
257
|
+
metadata?.datasetKind === 'csv' || metadata?.datasetKind === 'map'
|
|
258
|
+
? metadata.datasetKind
|
|
259
|
+
: null;
|
|
260
|
+
handles.push({
|
|
261
|
+
path: path.length > 0 ? path.join('.') : 'rows',
|
|
262
|
+
tableNamespace,
|
|
263
|
+
datasetKind,
|
|
264
|
+
handle,
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const promoteRows = (
|
|
269
|
+
rows: OutputDatasetRow[],
|
|
270
|
+
path: readonly string[],
|
|
271
|
+
): unknown => {
|
|
272
|
+
const resolvedPath = path.length > 0 ? path.join('.') : 'rows';
|
|
273
|
+
const tableNamespace = disambiguateTableNamespace(
|
|
274
|
+
resultTableNamespaceForPath(path, maxTableNamespaceLength),
|
|
275
|
+
path,
|
|
276
|
+
usedNamespaces,
|
|
277
|
+
maxTableNamespaceLength,
|
|
278
|
+
);
|
|
279
|
+
const handle = createDataset({
|
|
280
|
+
path: resolvedPath,
|
|
281
|
+
tableNamespace,
|
|
282
|
+
rows,
|
|
283
|
+
});
|
|
284
|
+
datasets.push({
|
|
285
|
+
path: resolvedPath,
|
|
286
|
+
tableNamespace,
|
|
287
|
+
rows,
|
|
288
|
+
handle,
|
|
289
|
+
});
|
|
290
|
+
rememberHandle(handle, path);
|
|
291
|
+
return handle;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const visit = (
|
|
295
|
+
candidate: unknown,
|
|
296
|
+
path: string[],
|
|
297
|
+
depth: number,
|
|
298
|
+
): unknown => {
|
|
299
|
+
if (depth > 20 || candidate == null) return candidate;
|
|
300
|
+
if (isDatasetLike(candidate)) {
|
|
301
|
+
rememberHandle(candidate, path);
|
|
302
|
+
return candidate;
|
|
303
|
+
}
|
|
304
|
+
if (isSerializedPlayDataset(candidate)) {
|
|
305
|
+
return candidate;
|
|
306
|
+
}
|
|
307
|
+
if (isLikelyRowOutputPath(path) && isRowArray(candidate, path)) {
|
|
308
|
+
return promoteRows(candidate, path.length > 0 ? path : ['rows']);
|
|
309
|
+
}
|
|
310
|
+
if (Array.isArray(candidate)) {
|
|
311
|
+
return candidate.map((entry, index) =>
|
|
312
|
+
visit(entry, [...path, String(index)], depth + 1),
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
if (!isRecord(candidate)) return candidate;
|
|
316
|
+
const cached = seen.get(candidate);
|
|
317
|
+
if (cached) return cached;
|
|
318
|
+
const out: Record<string, unknown> = {};
|
|
319
|
+
seen.set(candidate, out);
|
|
320
|
+
for (const [key, child] of Object.entries(candidate)) {
|
|
321
|
+
out[key] = visit(child, [...path, key], depth + 1);
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
result: visit(value, [], 0),
|
|
328
|
+
datasets,
|
|
329
|
+
handles,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
type SerializedDatasetHydrator = (
|
|
334
|
+
dataset: SerializedPlayDataset<Record<string, unknown>>,
|
|
335
|
+
) => unknown;
|
|
336
|
+
|
|
337
|
+
export function hydrateSerializedResultDatasets(
|
|
338
|
+
value: unknown,
|
|
339
|
+
hydrateDataset: SerializedDatasetHydrator,
|
|
340
|
+
): unknown {
|
|
341
|
+
const seen = new WeakMap<object, unknown>();
|
|
342
|
+
|
|
343
|
+
function visit(candidate: unknown, depth: number): unknown {
|
|
344
|
+
if (depth > 20 || candidate == null) return candidate;
|
|
345
|
+
if (isSerializedPlayDataset<Record<string, unknown>>(candidate)) {
|
|
346
|
+
return hydrateDataset(candidate);
|
|
347
|
+
}
|
|
348
|
+
if (Array.isArray(candidate)) {
|
|
349
|
+
return candidate.map((entry) => visit(entry, depth + 1));
|
|
350
|
+
}
|
|
351
|
+
if (!isRecord(candidate)) return candidate;
|
|
352
|
+
const cached = seen.get(candidate);
|
|
353
|
+
if (cached) return cached;
|
|
354
|
+
const out: Record<string, unknown> = {};
|
|
355
|
+
seen.set(candidate, out);
|
|
356
|
+
for (const [key, child] of Object.entries(candidate)) {
|
|
357
|
+
out[key] = visit(child, depth + 1);
|
|
358
|
+
}
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return visit(value, 0);
|
|
363
|
+
}
|
package/dist/bundling-sources/apps/play-runner-workers/src/runtime/result-dataset-persistence.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { augmentSheetContractWithDatasetFields } from '../../../../shared_libs/play-data-plane/sheet-contract';
|
|
2
|
+
import type { PlaySheetContract } from '../../../../shared_libs/plays/static-pipeline';
|
|
3
|
+
import type { OutputDatasetRow } from './output-datasets';
|
|
4
|
+
|
|
5
|
+
export type StableResultDatasetPersistenceContract = {
|
|
6
|
+
sheetContract: PlaySheetContract;
|
|
7
|
+
cachedChunks: OutputDatasetRow[][] | null;
|
|
8
|
+
rowCount: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export async function collectStableResultDatasetPersistenceContract(input: {
|
|
12
|
+
contract: PlaySheetContract;
|
|
13
|
+
chunks:
|
|
14
|
+
| AsyncIterable<readonly OutputDatasetRow[]>
|
|
15
|
+
| Iterable<readonly OutputDatasetRow[]>;
|
|
16
|
+
cacheChunks?: boolean;
|
|
17
|
+
}): Promise<StableResultDatasetPersistenceContract> {
|
|
18
|
+
const fields = new Set<string>();
|
|
19
|
+
const cachedChunks: OutputDatasetRow[][] | null = input.cacheChunks
|
|
20
|
+
? []
|
|
21
|
+
: null;
|
|
22
|
+
let rowCount = 0;
|
|
23
|
+
|
|
24
|
+
for await (const chunk of input.chunks) {
|
|
25
|
+
if (chunk.length === 0) continue;
|
|
26
|
+
rowCount += chunk.length;
|
|
27
|
+
for (const row of chunk) {
|
|
28
|
+
for (const field of Object.keys(row)) {
|
|
29
|
+
fields.add(field);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (cachedChunks) {
|
|
33
|
+
cachedChunks.push(chunk.map((row) => ({ ...row })));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const unionRow =
|
|
38
|
+
fields.size === 0
|
|
39
|
+
? []
|
|
40
|
+
: [Object.fromEntries([...fields].map((field) => [field, null]))];
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
sheetContract: augmentSheetContractWithDatasetFields({
|
|
44
|
+
contract: input.contract,
|
|
45
|
+
rows: unionRow,
|
|
46
|
+
}),
|
|
47
|
+
cachedChunks,
|
|
48
|
+
rowCount,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -101,10 +101,11 @@ export const SDK_RELEASE = {
|
|
|
101
101
|
// 0.1.108 ships explicit dataset column/tool recompute policy and removes
|
|
102
102
|
// the SDK enrich generator's one-second stale policy.
|
|
103
103
|
// 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
// 0.1.111 ships dataset-native tool list getters and result row datasets.
|
|
105
|
+
version: '0.1.147',
|
|
106
|
+
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
106
107
|
supportPolicy: {
|
|
107
|
-
latest: '0.1.
|
|
108
|
+
latest: '0.1.147',
|
|
108
109
|
minimumSupported: '0.1.53',
|
|
109
110
|
deprecatedBelow: '0.1.53',
|
|
110
111
|
commandMinimumSupported: [
|
|
@@ -116,61 +117,93 @@ export const SDK_RELEASE = {
|
|
|
116
117
|
},
|
|
117
118
|
{
|
|
118
119
|
command: 'plays',
|
|
119
|
-
minimumSupported: '0.1.
|
|
120
|
+
minimumSupported: '0.1.111',
|
|
120
121
|
reason:
|
|
121
|
-
'Play file commands now
|
|
122
|
+
'Play file commands now use dataset-native list getters and result row datasets.',
|
|
122
123
|
},
|
|
123
124
|
{
|
|
124
125
|
command: 'plays run',
|
|
125
|
-
minimumSupported: '0.1.
|
|
126
|
+
minimumSupported: '0.1.111',
|
|
126
127
|
reason:
|
|
127
|
-
'Play
|
|
128
|
+
'Play run results now promote row-shaped outputs into dataset handles for safe export.',
|
|
128
129
|
},
|
|
129
130
|
{
|
|
130
131
|
command: 'run',
|
|
131
132
|
displayCommand: 'plays run',
|
|
132
|
-
minimumSupported: '0.1.
|
|
133
|
+
minimumSupported: '0.1.111',
|
|
133
134
|
reason:
|
|
134
|
-
'Play
|
|
135
|
+
'Play run results now promote row-shaped outputs into dataset handles for safe export.',
|
|
135
136
|
},
|
|
136
137
|
{
|
|
137
138
|
command: 'plays check',
|
|
138
|
-
minimumSupported: '0.1.
|
|
139
|
+
minimumSupported: '0.1.111',
|
|
139
140
|
reason:
|
|
140
|
-
'Play file
|
|
141
|
+
'Play file checks now validate dataset-native list getter authoring.',
|
|
141
142
|
},
|
|
142
143
|
{
|
|
143
144
|
command: 'check',
|
|
144
145
|
displayCommand: 'plays check',
|
|
145
|
-
minimumSupported: '0.1.
|
|
146
|
+
minimumSupported: '0.1.111',
|
|
146
147
|
reason:
|
|
147
|
-
'Play file
|
|
148
|
+
'Play file checks now validate dataset-native list getter authoring.',
|
|
148
149
|
},
|
|
149
150
|
{
|
|
150
151
|
command: 'plays publish',
|
|
151
|
-
minimumSupported: '0.1.
|
|
152
|
+
minimumSupported: '0.1.111',
|
|
152
153
|
reason:
|
|
153
|
-
'
|
|
154
|
+
'Published play artifacts now target dataset-native list getters and result row datasets.',
|
|
154
155
|
},
|
|
155
156
|
{
|
|
156
157
|
command: 'publish',
|
|
157
158
|
displayCommand: 'plays publish',
|
|
158
|
-
minimumSupported: '0.1.
|
|
159
|
+
minimumSupported: '0.1.111',
|
|
159
160
|
reason:
|
|
160
|
-
'
|
|
161
|
+
'Published play artifacts now target dataset-native list getters and result row datasets.',
|
|
161
162
|
},
|
|
162
163
|
{
|
|
163
164
|
command: 'plays set-live',
|
|
164
|
-
minimumSupported: '0.1.
|
|
165
|
+
minimumSupported: '0.1.111',
|
|
165
166
|
reason:
|
|
166
|
-
'
|
|
167
|
+
'Published play artifacts now target dataset-native list getters and result row datasets.',
|
|
167
168
|
},
|
|
168
169
|
{
|
|
169
170
|
command: 'set-live',
|
|
170
171
|
displayCommand: 'plays set-live',
|
|
171
|
-
minimumSupported: '0.1.
|
|
172
|
+
minimumSupported: '0.1.111',
|
|
172
173
|
reason:
|
|
173
|
-
'
|
|
174
|
+
'Published play artifacts now target dataset-native list getters and result row datasets.',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
command: 'runs',
|
|
178
|
+
minimumSupported: '0.1.111',
|
|
179
|
+
reason:
|
|
180
|
+
'Run result rows now render as dataset handles with explicit export commands.',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
command: 'runs get',
|
|
184
|
+
minimumSupported: '0.1.111',
|
|
185
|
+
reason:
|
|
186
|
+
'Run result rows now render as dataset handles with explicit export commands.',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
command: 'get',
|
|
190
|
+
displayCommand: 'runs get',
|
|
191
|
+
minimumSupported: '0.1.111',
|
|
192
|
+
reason:
|
|
193
|
+
'Run result rows now render as dataset handles with explicit export commands.',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
command: 'runs export',
|
|
197
|
+
minimumSupported: '0.1.111',
|
|
198
|
+
reason:
|
|
199
|
+
'Run result row datasets now use the dataset-handle export contract.',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
command: 'export',
|
|
203
|
+
displayCommand: 'runs export',
|
|
204
|
+
minimumSupported: '0.1.111',
|
|
205
|
+
reason:
|
|
206
|
+
'Run result row datasets now use the dataset-handle export contract.',
|
|
174
207
|
},
|
|
175
208
|
],
|
|
176
209
|
autoUpdatePatchLag: 2,
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
27
27
|
import { homedir } from 'node:os';
|
|
28
|
-
import { join } from 'node:path';
|
|
28
|
+
import { dirname, join } from 'node:path';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Result of converting a tool response to a list of records.
|
|
@@ -236,18 +236,26 @@ export function tryConvertToList(
|
|
|
236
236
|
: [];
|
|
237
237
|
|
|
238
238
|
if (listExtractorPaths.length > 0) {
|
|
239
|
+
let emptyMatch: ListConversionResult | null = null;
|
|
239
240
|
for (const root of candidateRoots(payload)) {
|
|
240
241
|
for (const extractorPath of listExtractorPaths) {
|
|
241
242
|
const resolved = getByDottedPath(root.value, extractorPath);
|
|
242
243
|
const rows = normalizeRows(resolved);
|
|
243
|
-
if (rows
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
if (!rows) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const sourcePath = root.path
|
|
248
|
+
? `${root.path}.${extractorPath}`
|
|
249
|
+
: extractorPath;
|
|
250
|
+
if (rows.length > 0) {
|
|
247
251
|
return { rows, strategy: 'configured_paths', sourcePath };
|
|
248
252
|
}
|
|
253
|
+
emptyMatch ??= { rows, strategy: 'configured_paths', sourcePath };
|
|
249
254
|
}
|
|
250
255
|
}
|
|
256
|
+
if (emptyMatch) {
|
|
257
|
+
return emptyMatch;
|
|
258
|
+
}
|
|
251
259
|
}
|
|
252
260
|
|
|
253
261
|
for (const root of candidateRoots(payload)) {
|
|
@@ -321,9 +329,12 @@ export function writeJsonOutputFile(payload: unknown, stem: string): string {
|
|
|
321
329
|
export function writeCsvOutputFile(
|
|
322
330
|
rows: Array<Record<string, unknown>>,
|
|
323
331
|
stem: string,
|
|
332
|
+
options?: { outPath?: string },
|
|
324
333
|
): { path: string; rowCount: number; columns: string[]; preview: string } {
|
|
325
|
-
const
|
|
326
|
-
|
|
334
|
+
const outputPath = options?.outPath
|
|
335
|
+
? options.outPath
|
|
336
|
+
: join(ensureOutputDir(), `${stem}_${Date.now()}.csv`);
|
|
337
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
327
338
|
const seen = new Set<string>();
|
|
328
339
|
const columns: string[] = [];
|
|
329
340
|
for (const row of rows) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EmailStatusExtractorConfig } from './email-status';
|
|
2
2
|
import type { DeeplineGetterValueMap } from './extractor-targets';
|
|
3
|
+
import type { PlayDataset } from '../plays/dataset';
|
|
3
4
|
|
|
4
5
|
export type ToolResultExecutionMetadata = {
|
|
5
6
|
idempotent: true;
|
|
@@ -50,7 +51,7 @@ export type ToolResultListAccessor<
|
|
|
50
51
|
TKey extends string = string,
|
|
51
52
|
> = Omit<ToolResultListMetadata, 'keys'> & {
|
|
52
53
|
keys: Partial<Record<TKey, string>> & Record<string, string>;
|
|
53
|
-
get(): T
|
|
54
|
+
get(): PlayDataset<T>;
|
|
54
55
|
};
|
|
55
56
|
|
|
56
57
|
export type ToolResultMetadataInput = {
|