@tiny-fish/cli 0.51.2-next.417 → 0.52.0
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 +8 -0
- package/dist/commands/dataset.d.ts +2 -0
- package/dist/commands/dataset.js +268 -0
- package/dist/lib/client.d.ts +195 -0
- package/dist/lib/client.js +104 -4
- package/dist/lib/datasets.d.ts +246 -0
- package/dist/lib/datasets.js +119 -0
- package/dist/program.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -442,6 +442,14 @@ checks npm once a day for a newer CLI. Set `TINYFISH_NO_TELEMETRY` to any value
|
|
|
442
442
|
TinyFish-owned origins (`*.tinyfish.ai`, `*.tinyfish.io`) or a local development endpoint
|
|
443
443
|
(`localhost`, `127.0.0.1`, `[::1]`).
|
|
444
444
|
|
|
445
|
+
### Datasets
|
|
446
|
+
|
|
447
|
+
`tinyfish dataset create "prompt"` plans a schema, creates the dataset and follows the run: rows print as they land (`--pretty` prints them tab-separated in column order, status and log lines go to stderr; JSON mode prints one `{event, data}` line per frame and the final dataset last). `--rows N` sets the target (1-250, default 100); `--async` returns the 202 dataset at once. To review the schema first, `tinyfish dataset plan "prompt" > spec.json`, edit it, then `tinyfish dataset create --from-spec spec.json`.
|
|
448
|
+
|
|
449
|
+
Manage datasets with `tinyfish dataset list`, `get <id>`, `rows <id> [--limit] [--after] [--all]`, `populate <id> [--rows N] [--reset] [--async]`, `watch <id> <run_id>`, `cancel <id> <run_id>` and `delete <id>`. `rows --all` follows `next_cursor` to the last page. `populate` and `delete` are refused with a 409 while a run is live.
|
|
450
|
+
|
|
451
|
+
Ctrl+C during `create` or `populate` cancels the run it started; during `watch` it only detaches. A failed or cancelled run exits unsuccessfully with the run's error. A stream that closes while the run is still live is picked up again, up to three times; after that the recovery hint names `watch`.
|
|
452
|
+
|
|
445
453
|
## CI/CD
|
|
446
454
|
|
|
447
455
|
```yaml
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { withResolvedKey } from '../lib/auth.js';
|
|
3
|
+
import * as api from '../lib/client.js';
|
|
4
|
+
import * as datasets from '../lib/datasets.js';
|
|
5
|
+
import { integerInRange } from '../lib/list-options.js';
|
|
6
|
+
import { ApiError, errLine, handleApiError, out, outLine, parseJson } from '../lib/output.js';
|
|
7
|
+
import { installSignalGuard, onBrokenPipe } from '../lib/signals.js';
|
|
8
|
+
const datasetLine = (d) => `${d.id} ${d.status} ${d.row_count}/${d.target_rows} ${d.name}`;
|
|
9
|
+
function printDataset(d, pretty, columns = false) {
|
|
10
|
+
if (!pretty)
|
|
11
|
+
return out(d);
|
|
12
|
+
outLine(datasetLine(d));
|
|
13
|
+
if (d.run)
|
|
14
|
+
outLine(`Run: ${d.run.id} ${d.run.status}${d.run.error ? `: ${d.run.error}` : ''}`);
|
|
15
|
+
if (columns) {
|
|
16
|
+
const names = d.spec.columns.map((c) => `${c.name}(${c.type})${c.is_primary_key ? '*' : ''}`);
|
|
17
|
+
outLine(`Columns: ${names.join(', ')}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const cell = (value) => value === null || value === undefined
|
|
21
|
+
? ''
|
|
22
|
+
: typeof value === 'object'
|
|
23
|
+
? JSON.stringify(value)
|
|
24
|
+
: String(value);
|
|
25
|
+
function printRows(page, pretty, header = true) {
|
|
26
|
+
if (!pretty)
|
|
27
|
+
return out(page);
|
|
28
|
+
const names = page.columns.map((c) => c.name);
|
|
29
|
+
if (header)
|
|
30
|
+
outLine(names.join('\t'));
|
|
31
|
+
for (const row of page.rows)
|
|
32
|
+
outLine(names.map((name) => cell(row.data[name])).join('\t'));
|
|
33
|
+
}
|
|
34
|
+
function renderFrame(frame, columns, pretty) {
|
|
35
|
+
if (!pretty)
|
|
36
|
+
return out(frame);
|
|
37
|
+
const { event, data } = frame;
|
|
38
|
+
if (event !== 'row')
|
|
39
|
+
return noteFrame(event, data);
|
|
40
|
+
const row = datasets.rowFrameSchema.safeParse(data).data;
|
|
41
|
+
if (!row)
|
|
42
|
+
return;
|
|
43
|
+
if (row.deleted)
|
|
44
|
+
return errLine(`row removed ${row.row_id}`);
|
|
45
|
+
outLine(columns.map((name) => cell(row.data[name])).join('\t'));
|
|
46
|
+
}
|
|
47
|
+
// Everything but rows goes to stderr, so stdout stays a table.
|
|
48
|
+
function noteFrame(event, data) {
|
|
49
|
+
if (event === 'status') {
|
|
50
|
+
const status = datasets.statusFrameSchema.safeParse(data).data;
|
|
51
|
+
return errLine(`status: ${status?.status ?? '?'}${status?.error ? `: ${status.error}` : ''}`);
|
|
52
|
+
}
|
|
53
|
+
if (event === 'log')
|
|
54
|
+
return errLine(`log: ${datasets.logFrameSchema.safeParse(data).data?.text ?? ''}`);
|
|
55
|
+
if (event === 'error')
|
|
56
|
+
return errLine(`error: ${datasets.errorFrameSchema.safeParse(data).data?.message ?? ''}`);
|
|
57
|
+
if (event !== 'heartbeat' && event !== 'done')
|
|
58
|
+
errLine(event);
|
|
59
|
+
}
|
|
60
|
+
// True once a status frame says the run is over. A bare `done` is not: a task
|
|
61
|
+
// that does not hold the run answers `running` then `done`, so it reattaches.
|
|
62
|
+
function isTerminal(frame) {
|
|
63
|
+
const status = datasets.statusFrameSchema.safeParse(frame.data).data;
|
|
64
|
+
return (frame.event === 'status' && status !== undefined && !datasets.RUN_IN_PROGRESS.has(status.status));
|
|
65
|
+
}
|
|
66
|
+
// The route caps one stream; a run still going is picked up again.
|
|
67
|
+
async function followStream(id, runId, apiKey, opts) {
|
|
68
|
+
for (let attempt = 0;; attempt++) {
|
|
69
|
+
let terminal = false;
|
|
70
|
+
for await (const frame of api.datasetEvents(id, runId, apiKey, opts.signal)) {
|
|
71
|
+
renderFrame(frame, opts.columns, opts.pretty);
|
|
72
|
+
if (isTerminal(frame))
|
|
73
|
+
terminal = true;
|
|
74
|
+
}
|
|
75
|
+
if (terminal)
|
|
76
|
+
return;
|
|
77
|
+
const live = (await api.getDataset(id, apiKey)).run;
|
|
78
|
+
if (live?.id !== runId)
|
|
79
|
+
throw new Error('Run stream ended before completion');
|
|
80
|
+
// Finished in the gap; the caller prints the dataset.
|
|
81
|
+
if (!datasets.RUN_IN_PROGRESS.has(live.status))
|
|
82
|
+
return;
|
|
83
|
+
if (attempt >= datasets.MAX_REATTACHES)
|
|
84
|
+
throw new Error('Run stream ended before completion');
|
|
85
|
+
await datasets.pause(datasets.reattachDelayMs(attempt), opts.signal);
|
|
86
|
+
if (opts.pretty)
|
|
87
|
+
errLine('reconnected');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function follow(id, runId, apiKey, opts) {
|
|
91
|
+
const { started, pretty, timeout = datasets.RUN_TIMEOUT_MS } = opts;
|
|
92
|
+
// Guard exits 130 once the handler settles.
|
|
93
|
+
const uninstall = installSignalGuard(() => interrupted(started, id, runId, apiKey));
|
|
94
|
+
const removePipeHandler = onBrokenPipe(() => interrupted(started, id, runId, apiKey));
|
|
95
|
+
try {
|
|
96
|
+
await followStream(id, runId, apiKey, { ...opts, signal: AbortSignal.timeout(timeout) });
|
|
97
|
+
const result = await api.getDataset(id, apiKey);
|
|
98
|
+
printDataset(result, pretty);
|
|
99
|
+
if (result.status === 'failed' || result.status === 'cancelled')
|
|
100
|
+
throw new Error(`Dataset ${result.status}: ${result.run?.error ?? runId}`);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
recoveryHint(error, started, id, runId);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
uninstall();
|
|
108
|
+
removePipeHandler();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function recoveryHint(error, started, id, runId) {
|
|
112
|
+
// A 4xx before the run existed means nothing to recover.
|
|
113
|
+
if (error instanceof ApiError && error.status < 500 && !started)
|
|
114
|
+
return;
|
|
115
|
+
errLine(`Check tinyfish dataset get ${id} or tinyfish dataset watch ${id} ${runId}.`);
|
|
116
|
+
}
|
|
117
|
+
async function interrupted(started, id, runId, apiKey) {
|
|
118
|
+
if (!started)
|
|
119
|
+
return;
|
|
120
|
+
try {
|
|
121
|
+
errLine(`Run ${(await api.cancelDatasetRun(id, runId, apiKey)).status}.`);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
errLine(`Cancellation unconfirmed. Use tinyfish dataset cancel ${id} ${runId}.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// The planner's output, bare or as `{spec}`, so `plan > f.json` feeds back in.
|
|
128
|
+
function readSpecFile(path) {
|
|
129
|
+
let text;
|
|
130
|
+
try {
|
|
131
|
+
text = readFileSync(path, 'utf8');
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return handleApiError(new Error(`Cannot read ${path}`));
|
|
135
|
+
}
|
|
136
|
+
const parsed = parseJson(text);
|
|
137
|
+
const wrapped = parsed?.spec;
|
|
138
|
+
return api.parseWithSchema(datasets.specSchema, wrapped ?? parsed, 'Invalid spec file');
|
|
139
|
+
}
|
|
140
|
+
async function printOrFollow(dataset, apiKey, opts) {
|
|
141
|
+
if (opts.async || !dataset.run)
|
|
142
|
+
return printDataset(dataset, opts.pretty);
|
|
143
|
+
errLine(`Dataset ID: ${dataset.id}`);
|
|
144
|
+
errLine(`Run ID: ${dataset.run.id}`);
|
|
145
|
+
const columns = dataset.spec.columns.map((c) => c.name);
|
|
146
|
+
return follow(dataset.id, dataset.run.id, apiKey, {
|
|
147
|
+
started: true,
|
|
148
|
+
columns,
|
|
149
|
+
pretty: opts.pretty,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function create(prompt, opts) {
|
|
153
|
+
const hasPrompt = Boolean(prompt?.trim());
|
|
154
|
+
if (hasPrompt === Boolean(opts.fromSpec))
|
|
155
|
+
handleApiError(new Error('prompt or --from-spec required, not both'));
|
|
156
|
+
return withResolvedKey(async (apiKey) => {
|
|
157
|
+
const request = api.parseWithSchema(datasets.createRequestSchema, {
|
|
158
|
+
...(opts.fromSpec ? { spec: readSpecFile(opts.fromSpec) } : { prompt }),
|
|
159
|
+
target_rows: opts.rows,
|
|
160
|
+
}, 'Invalid dataset request');
|
|
161
|
+
return printOrFollow(await api.createDataset(request, apiKey), apiKey, opts);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async function* walkRows(id, opts, apiKey) {
|
|
165
|
+
let after = opts.after;
|
|
166
|
+
do {
|
|
167
|
+
const page = await api.getDatasetRows(id, { ...opts, after }, apiKey);
|
|
168
|
+
yield page;
|
|
169
|
+
after = page.next_cursor ?? undefined;
|
|
170
|
+
} while (after);
|
|
171
|
+
}
|
|
172
|
+
async function rows(id, opts, apiKey) {
|
|
173
|
+
const query = api.parseWithSchema(datasets.rowsQuerySchema, { limit: opts.limit, after: opts.after }, 'Invalid rows query');
|
|
174
|
+
if (!opts.all) {
|
|
175
|
+
const page = await api.getDatasetRows(id, query, apiKey);
|
|
176
|
+
printRows(page, opts.pretty);
|
|
177
|
+
if (opts.pretty && page.next_cursor)
|
|
178
|
+
outLine(`Next: --after ${page.next_cursor}`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const merged = { columns: [], rows: [], truncated: false, next_cursor: null };
|
|
182
|
+
let first = true;
|
|
183
|
+
for await (const page of walkRows(id, query, apiKey)) {
|
|
184
|
+
if (opts.pretty)
|
|
185
|
+
printRows(page, true, first);
|
|
186
|
+
merged.columns = page.columns;
|
|
187
|
+
merged.rows.push(...page.rows);
|
|
188
|
+
merged.truncated = page.truncated;
|
|
189
|
+
first = false;
|
|
190
|
+
}
|
|
191
|
+
if (!opts.pretty)
|
|
192
|
+
out(merged);
|
|
193
|
+
}
|
|
194
|
+
async function list(opts, apiKey) {
|
|
195
|
+
const query = api.parseWithSchema(datasets.listQuerySchema, { limit: opts.limit, cursor: opts.cursor }, 'Invalid list query');
|
|
196
|
+
const result = await api.listDatasets(query, apiKey);
|
|
197
|
+
if (!opts.pretty)
|
|
198
|
+
return out(result);
|
|
199
|
+
if (result.data.length === 0)
|
|
200
|
+
outLine('No datasets.');
|
|
201
|
+
result.data.forEach((item) => outLine(datasetLine(item)));
|
|
202
|
+
if (result.pagination.next_cursor) {
|
|
203
|
+
const limit = opts.limit === undefined ? '' : `--limit ${opts.limit} `;
|
|
204
|
+
outLine(`Next: ${limit}--cursor ${result.pagination.next_cursor}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function watch(id, runId, opts, apiKey) {
|
|
208
|
+
const run = api.parseRunId(runId);
|
|
209
|
+
const dataset = await api.getDataset(api.parseDatasetId(id), apiKey);
|
|
210
|
+
const columns = dataset.spec.columns.map((c) => c.name);
|
|
211
|
+
const follows = { started: false, columns, pretty: opts.pretty };
|
|
212
|
+
if (opts.timeout !== undefined)
|
|
213
|
+
follows.timeout = opts.timeout;
|
|
214
|
+
return follow(dataset.id, run, apiKey, follows);
|
|
215
|
+
}
|
|
216
|
+
export function registerDataset(program) {
|
|
217
|
+
const group = program
|
|
218
|
+
.command('dataset')
|
|
219
|
+
.description('Web-populated datasets')
|
|
220
|
+
.enablePositionalOptions()
|
|
221
|
+
.addHelpText('after', '\nCtrl+C cancels a run this command started; watch only detaches.');
|
|
222
|
+
const command = (name, description) => group.command(name).description(description).option('--pretty', 'Human-readable output');
|
|
223
|
+
const rowsOption = (cmd) => cmd.option('--rows <n>', `Rows to fill to (1-${datasets.MAX_TARGET_ROWS}; default: 100)`, integerInRange(1, datasets.MAX_TARGET_ROWS));
|
|
224
|
+
rowsOption(command('create [prompt]', 'Plan a dataset from a prompt or a spec file, then fill it')
|
|
225
|
+
.option('--from-spec <file>', 'A spec JSON file, as printed by `plan`')
|
|
226
|
+
.option('--async', 'Submit without waiting for the rows')).action(create);
|
|
227
|
+
command('plan <prompt>', 'Print the schema the planner designs, to edit and pass to create').action((prompt, opts) => withResolvedKey(async (apiKey) => {
|
|
228
|
+
const { spec } = await api.planDataset(prompt, apiKey);
|
|
229
|
+
if (opts.pretty)
|
|
230
|
+
outLine(JSON.stringify(spec, null, 2));
|
|
231
|
+
else
|
|
232
|
+
out(spec);
|
|
233
|
+
}));
|
|
234
|
+
command('list', 'List datasets, newest first')
|
|
235
|
+
.option('--limit <n>', `Maximum datasets (1-${datasets.LIST_LIMIT_MAX}; default: 20)`, integerInRange(1, datasets.LIST_LIMIT_MAX))
|
|
236
|
+
.option('--cursor <cursor>', 'Pagination cursor from a previous response')
|
|
237
|
+
.action((opts) => withResolvedKey((apiKey) => list(opts, apiKey)));
|
|
238
|
+
command('get <id>', 'Get a dataset: status, row count and latest run').action((id, opts) => withResolvedKey(async (apiKey) => printDataset(await api.getDataset(api.parseDatasetId(id), apiKey), opts.pretty, true)));
|
|
239
|
+
command('rows <id>', 'Print rows; --all walks every page')
|
|
240
|
+
.option('--limit <n>', `Rows per page (1-${datasets.ROWS_LIMIT_MAX}; default: 500)`, integerInRange(1, datasets.ROWS_LIMIT_MAX))
|
|
241
|
+
.option('--after <cursor>', 'Row cursor from a previous response')
|
|
242
|
+
.option('--all', 'Follow next_cursor until the last page')
|
|
243
|
+
.action((id, opts) => withResolvedKey((apiKey) => rows(api.parseDatasetId(id), opts, apiKey)));
|
|
244
|
+
rowsOption(command('populate <id>', 'Fill the dataset again; refused while a run is live')
|
|
245
|
+
.option('--reset', 'Empty the dataset first')
|
|
246
|
+
.option('--async', 'Submit without waiting for the rows')).action((id, opts) => withResolvedKey(async (apiKey) => {
|
|
247
|
+
const request = api.parseWithSchema(datasets.populateRequestSchema, { reset: opts.reset, target_rows: opts.rows }, 'Invalid populate request');
|
|
248
|
+
const dataset = await api.populateDataset(api.parseDatasetId(id), request, apiKey);
|
|
249
|
+
return printOrFollow(dataset, apiKey, opts);
|
|
250
|
+
}));
|
|
251
|
+
command('watch <id> <run_id>', 'Follow a run; Ctrl+C detaches')
|
|
252
|
+
.option('--timeout <ms>', 'Maximum watch duration in milliseconds', integerInRange(1, 2147483647), datasets.RUN_TIMEOUT_MS)
|
|
253
|
+
.action((id, runId, opts) => withResolvedKey((apiKey) => watch(id, runId, opts, apiKey)));
|
|
254
|
+
command('cancel <id> <run_id>', 'Stop a run; the final status arrives on watch').action((id, runId, opts) => withResolvedKey(async (apiKey) => {
|
|
255
|
+
const result = await api.cancelDatasetRun(api.parseDatasetId(id), api.parseRunId(runId), apiKey);
|
|
256
|
+
if (opts.pretty)
|
|
257
|
+
outLine(`${result.run_id} ${result.status}`);
|
|
258
|
+
else
|
|
259
|
+
out(result);
|
|
260
|
+
}));
|
|
261
|
+
command('delete <id>', 'Delete a dataset and its rows; refused while a run is live').action((id, opts) => withResolvedKey(async (apiKey) => {
|
|
262
|
+
const result = await api.deleteDataset(api.parseDatasetId(id), apiKey);
|
|
263
|
+
if (opts.pretty)
|
|
264
|
+
outLine(`Dataset ${result.id} deleted`);
|
|
265
|
+
else
|
|
266
|
+
out(result);
|
|
267
|
+
}));
|
|
268
|
+
}
|
package/dist/lib/client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type AgentRunAsyncResponse, type AgentRunResponse, type AgentRunWithStr
|
|
|
2
2
|
import type { PageParams } from './list-options.js';
|
|
3
3
|
import type { BatchCancelResponse, BatchGetResponse, BatchRunRequest, BatchRunResponse, CancelRunResponse, CliAgentRunParams, CliBrowserSessionCreateParams, CookieRecord, ProfileCreateResponse, ProfileSummary, ProfileUploadResponse, RunStepsResponse, VaultConnectRequest, VaultConnectResponse, VaultConnectionsListResponse, VaultDisconnectResponse, VaultItemsListResponse, VaultItemsSyncResponse } from './types.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import * as datasets from './datasets.js';
|
|
5
6
|
import * as research from './research.js';
|
|
6
7
|
type ConnectMap = Record<string, {
|
|
7
8
|
attempt_id: string;
|
|
@@ -211,4 +212,198 @@ export declare function cancelResearch(id: string, apiKey: string): Promise<{
|
|
|
211
212
|
cancelled: boolean;
|
|
212
213
|
}>;
|
|
213
214
|
export declare function researchStream(input: research.Request | string, apiKey: string, signal: AbortSignal): AsyncGenerator<research.Event>;
|
|
215
|
+
export declare const parseDatasetId: (id: string) => string;
|
|
216
|
+
export declare const parseRunId: (id: string) => string;
|
|
217
|
+
export declare function createDataset(req: datasets.CreateRequest, apiKey: string): Promise<{
|
|
218
|
+
[x: string]: unknown;
|
|
219
|
+
id: string;
|
|
220
|
+
name: string;
|
|
221
|
+
spec: {
|
|
222
|
+
[x: string]: unknown;
|
|
223
|
+
name: string;
|
|
224
|
+
description: string;
|
|
225
|
+
columns: {
|
|
226
|
+
[x: string]: unknown;
|
|
227
|
+
name: string;
|
|
228
|
+
type: string;
|
|
229
|
+
is_primary_key: boolean;
|
|
230
|
+
label?: string | undefined;
|
|
231
|
+
retrieval_hint?: string | undefined;
|
|
232
|
+
}[];
|
|
233
|
+
keep_when?: {
|
|
234
|
+
[x: string]: unknown;
|
|
235
|
+
column: string;
|
|
236
|
+
} | undefined;
|
|
237
|
+
};
|
|
238
|
+
status: "ready" | "empty" | "populating" | "failed" | "cancelled";
|
|
239
|
+
target_rows: number;
|
|
240
|
+
row_count: number;
|
|
241
|
+
run: {
|
|
242
|
+
[x: string]: unknown;
|
|
243
|
+
id: string;
|
|
244
|
+
status: "failed" | "cancelled" | "queued" | "running" | "done";
|
|
245
|
+
started_at: string;
|
|
246
|
+
finished_at?: string | undefined;
|
|
247
|
+
error?: string | undefined;
|
|
248
|
+
} | null;
|
|
249
|
+
created_at: string;
|
|
250
|
+
}>;
|
|
251
|
+
export declare function planDataset(prompt: string, apiKey: string): Promise<{
|
|
252
|
+
[x: string]: unknown;
|
|
253
|
+
spec: {
|
|
254
|
+
[x: string]: unknown;
|
|
255
|
+
name: string;
|
|
256
|
+
description: string;
|
|
257
|
+
columns: {
|
|
258
|
+
[x: string]: unknown;
|
|
259
|
+
name: string;
|
|
260
|
+
type: string;
|
|
261
|
+
is_primary_key: boolean;
|
|
262
|
+
label?: string | undefined;
|
|
263
|
+
retrieval_hint?: string | undefined;
|
|
264
|
+
}[];
|
|
265
|
+
keep_when?: {
|
|
266
|
+
[x: string]: unknown;
|
|
267
|
+
column: string;
|
|
268
|
+
} | undefined;
|
|
269
|
+
};
|
|
270
|
+
}>;
|
|
271
|
+
export declare function listDatasets(opts: datasets.ListQuery, apiKey: string): Promise<{
|
|
272
|
+
[x: string]: unknown;
|
|
273
|
+
data: {
|
|
274
|
+
[x: string]: unknown;
|
|
275
|
+
id: string;
|
|
276
|
+
name: string;
|
|
277
|
+
spec: {
|
|
278
|
+
[x: string]: unknown;
|
|
279
|
+
name: string;
|
|
280
|
+
description: string;
|
|
281
|
+
columns: {
|
|
282
|
+
[x: string]: unknown;
|
|
283
|
+
name: string;
|
|
284
|
+
type: string;
|
|
285
|
+
is_primary_key: boolean;
|
|
286
|
+
label?: string | undefined;
|
|
287
|
+
retrieval_hint?: string | undefined;
|
|
288
|
+
}[];
|
|
289
|
+
keep_when?: {
|
|
290
|
+
[x: string]: unknown;
|
|
291
|
+
column: string;
|
|
292
|
+
} | undefined;
|
|
293
|
+
};
|
|
294
|
+
status: "ready" | "empty" | "populating" | "failed" | "cancelled";
|
|
295
|
+
target_rows: number;
|
|
296
|
+
row_count: number;
|
|
297
|
+
run: {
|
|
298
|
+
[x: string]: unknown;
|
|
299
|
+
id: string;
|
|
300
|
+
status: "failed" | "cancelled" | "queued" | "running" | "done";
|
|
301
|
+
started_at: string;
|
|
302
|
+
finished_at?: string | undefined;
|
|
303
|
+
error?: string | undefined;
|
|
304
|
+
} | null;
|
|
305
|
+
created_at: string;
|
|
306
|
+
}[];
|
|
307
|
+
pagination: {
|
|
308
|
+
[x: string]: unknown;
|
|
309
|
+
next_cursor: string | null;
|
|
310
|
+
has_more: boolean;
|
|
311
|
+
};
|
|
312
|
+
}>;
|
|
313
|
+
export declare function getDataset(id: string, apiKey: string): Promise<{
|
|
314
|
+
[x: string]: unknown;
|
|
315
|
+
id: string;
|
|
316
|
+
name: string;
|
|
317
|
+
spec: {
|
|
318
|
+
[x: string]: unknown;
|
|
319
|
+
name: string;
|
|
320
|
+
description: string;
|
|
321
|
+
columns: {
|
|
322
|
+
[x: string]: unknown;
|
|
323
|
+
name: string;
|
|
324
|
+
type: string;
|
|
325
|
+
is_primary_key: boolean;
|
|
326
|
+
label?: string | undefined;
|
|
327
|
+
retrieval_hint?: string | undefined;
|
|
328
|
+
}[];
|
|
329
|
+
keep_when?: {
|
|
330
|
+
[x: string]: unknown;
|
|
331
|
+
column: string;
|
|
332
|
+
} | undefined;
|
|
333
|
+
};
|
|
334
|
+
status: "ready" | "empty" | "populating" | "failed" | "cancelled";
|
|
335
|
+
target_rows: number;
|
|
336
|
+
row_count: number;
|
|
337
|
+
run: {
|
|
338
|
+
[x: string]: unknown;
|
|
339
|
+
id: string;
|
|
340
|
+
status: "failed" | "cancelled" | "queued" | "running" | "done";
|
|
341
|
+
started_at: string;
|
|
342
|
+
finished_at?: string | undefined;
|
|
343
|
+
error?: string | undefined;
|
|
344
|
+
} | null;
|
|
345
|
+
created_at: string;
|
|
346
|
+
}>;
|
|
347
|
+
export declare function getDatasetRows(id: string, opts: datasets.RowsQuery, apiKey: string): Promise<{
|
|
348
|
+
[x: string]: unknown;
|
|
349
|
+
columns: {
|
|
350
|
+
[x: string]: unknown;
|
|
351
|
+
name: string;
|
|
352
|
+
label: string;
|
|
353
|
+
type: string;
|
|
354
|
+
is_primary_key: boolean;
|
|
355
|
+
}[];
|
|
356
|
+
rows: {
|
|
357
|
+
[x: string]: unknown;
|
|
358
|
+
_id: string;
|
|
359
|
+
data: Record<string, unknown>;
|
|
360
|
+
}[];
|
|
361
|
+
truncated: boolean;
|
|
362
|
+
next_cursor: string | null;
|
|
363
|
+
}>;
|
|
364
|
+
export declare function populateDataset(id: string, req: datasets.PopulateRequest, apiKey: string): Promise<{
|
|
365
|
+
[x: string]: unknown;
|
|
366
|
+
id: string;
|
|
367
|
+
name: string;
|
|
368
|
+
spec: {
|
|
369
|
+
[x: string]: unknown;
|
|
370
|
+
name: string;
|
|
371
|
+
description: string;
|
|
372
|
+
columns: {
|
|
373
|
+
[x: string]: unknown;
|
|
374
|
+
name: string;
|
|
375
|
+
type: string;
|
|
376
|
+
is_primary_key: boolean;
|
|
377
|
+
label?: string | undefined;
|
|
378
|
+
retrieval_hint?: string | undefined;
|
|
379
|
+
}[];
|
|
380
|
+
keep_when?: {
|
|
381
|
+
[x: string]: unknown;
|
|
382
|
+
column: string;
|
|
383
|
+
} | undefined;
|
|
384
|
+
};
|
|
385
|
+
status: "ready" | "empty" | "populating" | "failed" | "cancelled";
|
|
386
|
+
target_rows: number;
|
|
387
|
+
row_count: number;
|
|
388
|
+
run: {
|
|
389
|
+
[x: string]: unknown;
|
|
390
|
+
id: string;
|
|
391
|
+
status: "failed" | "cancelled" | "queued" | "running" | "done";
|
|
392
|
+
started_at: string;
|
|
393
|
+
finished_at?: string | undefined;
|
|
394
|
+
error?: string | undefined;
|
|
395
|
+
} | null;
|
|
396
|
+
created_at: string;
|
|
397
|
+
}>;
|
|
398
|
+
export declare function cancelDatasetRun(id: string, runId: string, apiKey: string): Promise<{
|
|
399
|
+
[x: string]: unknown;
|
|
400
|
+
run_id: string;
|
|
401
|
+
status: string;
|
|
402
|
+
}>;
|
|
403
|
+
export declare function deleteDataset(id: string, apiKey: string): Promise<{
|
|
404
|
+
[x: string]: unknown;
|
|
405
|
+
id: string;
|
|
406
|
+
deleted: true;
|
|
407
|
+
}>;
|
|
408
|
+
export declare function datasetEvents(id: string, runId: string, apiKey: string, signal: AbortSignal): AsyncGenerator<datasets.Frame>;
|
|
214
409
|
export {};
|
package/dist/lib/client.js
CHANGED
|
@@ -5,6 +5,7 @@ import { detectHarness, detectHumanInitiated } from './harness.js';
|
|
|
5
5
|
import { captureNotice } from './notice.js';
|
|
6
6
|
import { ApiError, parseJson } from './output.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
+
import * as datasets from './datasets.js';
|
|
8
9
|
import * as research from './research.js';
|
|
9
10
|
// Install-level connect context (PF-3169). A known caller is credited only to its own
|
|
10
11
|
// install; under an unknown caller we credit a lone install, else send source only.
|
|
@@ -86,7 +87,7 @@ class TinyFishCliClient extends TinyFish {
|
|
|
86
87
|
return headers;
|
|
87
88
|
}
|
|
88
89
|
// The SDK lacks GET streaming; reuse its authentication headers.
|
|
89
|
-
async
|
|
90
|
+
async getEventStream(path, signal) {
|
|
90
91
|
const response = await fetch(`${this.baseURL}${path}`, {
|
|
91
92
|
headers: { ...this._buildHeaders(), Accept: 'text/event-stream' },
|
|
92
93
|
signal,
|
|
@@ -95,10 +96,10 @@ class TinyFishCliClient extends TinyFish {
|
|
|
95
96
|
if (!response.ok)
|
|
96
97
|
throw await apiErrorFrom(response);
|
|
97
98
|
if (!response.body)
|
|
98
|
-
throw new Error('
|
|
99
|
+
throw new Error('Event stream response has no body');
|
|
99
100
|
return response.body.pipeThrough(new TextDecoderStream());
|
|
100
101
|
}
|
|
101
|
-
// SDK
|
|
102
|
+
// The SDK's delete() and patch() parse a JSON body; 204 routes come through here.
|
|
102
103
|
async send(method, path, json) {
|
|
103
104
|
const response = await fetch(`${this.baseURL}${path}`, {
|
|
104
105
|
method,
|
|
@@ -264,6 +265,50 @@ async function* parseSseStream(stream, onUnparseableLine) {
|
|
|
264
265
|
reader.releaseLock();
|
|
265
266
|
}
|
|
266
267
|
}
|
|
268
|
+
// Frames named on the event line; `data:` only streams keep using parseSseStream.
|
|
269
|
+
async function* parseSseFrames(stream) {
|
|
270
|
+
const reader = stream.getReader();
|
|
271
|
+
let buffer = '';
|
|
272
|
+
const parseFrame = (raw) => {
|
|
273
|
+
let event = 'message';
|
|
274
|
+
const data = [];
|
|
275
|
+
for (const line of raw.split('\n')) {
|
|
276
|
+
if (line.startsWith('event:'))
|
|
277
|
+
event = line.slice('event:'.length).trim();
|
|
278
|
+
else if (line.startsWith('data:'))
|
|
279
|
+
data.push(line.slice('data:'.length).trim());
|
|
280
|
+
}
|
|
281
|
+
if (data.length === 0)
|
|
282
|
+
return undefined;
|
|
283
|
+
try {
|
|
284
|
+
return { event, data: JSON.parse(data.join('\n')) };
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
throw new Error('Invalid dataset event JSON');
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
try {
|
|
291
|
+
while (true) {
|
|
292
|
+
const { done, value } = await reader.read();
|
|
293
|
+
if (done)
|
|
294
|
+
break;
|
|
295
|
+
buffer += value.replaceAll('\r\n', '\n');
|
|
296
|
+
const frames = buffer.split('\n\n');
|
|
297
|
+
buffer = frames.pop() ?? '';
|
|
298
|
+
for (const raw of frames) {
|
|
299
|
+
const frame = parseFrame(raw);
|
|
300
|
+
if (frame)
|
|
301
|
+
yield frame;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const last = parseFrame(buffer);
|
|
305
|
+
if (last)
|
|
306
|
+
yield last;
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
reader.releaseLock();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
267
312
|
export function runSync(req, apiKey) {
|
|
268
313
|
return sdk(apiKey, async (client) => {
|
|
269
314
|
const response = await client.post('/v1/automation/run', {
|
|
@@ -643,7 +688,7 @@ export async function* researchStream(input, apiKey, signal) {
|
|
|
643
688
|
try {
|
|
644
689
|
stream =
|
|
645
690
|
typeof input === 'string'
|
|
646
|
-
? await client.
|
|
691
|
+
? await client.getEventStream(`${runPath(input)}/events`, signal)
|
|
647
692
|
: await client.postStream('/v1/automation/run-research', {
|
|
648
693
|
json: research.requestSchema.parse(input),
|
|
649
694
|
signal,
|
|
@@ -660,3 +705,58 @@ export async function* researchStream(input, apiKey, signal) {
|
|
|
660
705
|
await stream?.cancel().catch(() => { });
|
|
661
706
|
}
|
|
662
707
|
}
|
|
708
|
+
// A dataset may exist; never lose its ID.
|
|
709
|
+
function parseDataset(schema, body) {
|
|
710
|
+
const id = z.looseObject({ id: datasets.idSchema }).safeParse(body).data;
|
|
711
|
+
const suffix = id ? ` for ${id.id}` : '';
|
|
712
|
+
return parseWithSchema(schema, body, `Invalid dataset response${suffix}`);
|
|
713
|
+
}
|
|
714
|
+
export const parseDatasetId = (id) => parseWithSchema(datasets.idSchema, id, 'Invalid dataset ID');
|
|
715
|
+
export const parseRunId = (id) => parseWithSchema(datasets.idSchema, id, 'Invalid run ID');
|
|
716
|
+
const datasetPath = (id) => `/v1/datasets/${parseDatasetId(id)}`;
|
|
717
|
+
const datasetRunPath = (id, runId) => `${datasetPath(id)}/runs/${parseRunId(runId)}`;
|
|
718
|
+
export function createDataset(req, apiKey) {
|
|
719
|
+
return sdk(apiKey, async (client) => parseDataset(datasets.datasetSchema, await client.post('/v1/datasets', { json: datasets.createRequestSchema.parse(req) })), datasets.START_TIMEOUT_MS);
|
|
720
|
+
}
|
|
721
|
+
export function planDataset(prompt, apiKey) {
|
|
722
|
+
return sdk(apiKey, async (client) => parseDataset(datasets.planResponseSchema, await client.post('/v1/datasets/plan', {
|
|
723
|
+
json: datasets.planRequestSchema.parse({ prompt }),
|
|
724
|
+
})), datasets.START_TIMEOUT_MS);
|
|
725
|
+
}
|
|
726
|
+
export function listDatasets(opts, apiKey) {
|
|
727
|
+
return sdk(apiKey, async (client) => parseDataset(datasets.listSchema, await client.get('/v1/datasets', { params: datasets.listQuerySchema.parse(opts) })), datasets.READ_TIMEOUT_MS);
|
|
728
|
+
}
|
|
729
|
+
export function getDataset(id, apiKey) {
|
|
730
|
+
return sdk(apiKey, async (client) => parseDataset(datasets.datasetSchema, await client.get(datasetPath(id))), datasets.READ_TIMEOUT_MS);
|
|
731
|
+
}
|
|
732
|
+
export function getDatasetRows(id, opts, apiKey) {
|
|
733
|
+
return sdk(apiKey, async (client) => parseDataset(datasets.rowsSchema, await client.get(`${datasetPath(id)}/rows`, {
|
|
734
|
+
params: datasets.rowsQuerySchema.parse(opts),
|
|
735
|
+
})), datasets.READ_TIMEOUT_MS);
|
|
736
|
+
}
|
|
737
|
+
export function populateDataset(id, req, apiKey) {
|
|
738
|
+
return sdk(apiKey, async (client) => parseDataset(datasets.datasetSchema, await client.post(`${datasetPath(id)}/populate`, {
|
|
739
|
+
json: datasets.populateRequestSchema.parse(req),
|
|
740
|
+
})), datasets.START_TIMEOUT_MS);
|
|
741
|
+
}
|
|
742
|
+
export function cancelDatasetRun(id, runId, apiKey) {
|
|
743
|
+
return sdk(apiKey, async (client) => parseDataset(datasets.cancelSchema, await client.post(`${datasetRunPath(id, runId)}/cancel`)), datasets.CANCEL_TIMEOUT_MS);
|
|
744
|
+
}
|
|
745
|
+
export function deleteDataset(id, apiKey) {
|
|
746
|
+
return sdk(apiKey, async (client) => parseDataset(datasets.deleteSchema, await client.delete(datasetPath(id))), datasets.READ_TIMEOUT_MS);
|
|
747
|
+
}
|
|
748
|
+
export async function* datasetEvents(id, runId, apiKey, signal) {
|
|
749
|
+
let stream;
|
|
750
|
+
const client = createSdkClient(apiKey, datasets.RUN_TIMEOUT_MS);
|
|
751
|
+
try {
|
|
752
|
+
stream = await client.getEventStream(`${datasetRunPath(id, runId)}/events`, signal);
|
|
753
|
+
for await (const frame of parseSseFrames(stream))
|
|
754
|
+
yield parseWithSchema(datasets.frameSchema, frame, 'Invalid dataset event');
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
rethrowSdkError(error, client.baseURL);
|
|
758
|
+
}
|
|
759
|
+
finally {
|
|
760
|
+
await stream?.cancel().catch(() => { });
|
|
761
|
+
}
|
|
762
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const RUN_TIMEOUT_MS = 1800000;
|
|
3
|
+
export declare const START_TIMEOUT_MS = 90000;
|
|
4
|
+
export declare const READ_TIMEOUT_MS = 15000;
|
|
5
|
+
export declare const CANCEL_TIMEOUT_MS = 5000;
|
|
6
|
+
export declare const MAX_TARGET_ROWS = 250;
|
|
7
|
+
export declare const LIST_LIMIT_MAX = 100;
|
|
8
|
+
export declare const ROWS_LIMIT_MAX = 2000;
|
|
9
|
+
export declare const MAX_REATTACHES = 3;
|
|
10
|
+
export declare const reattachDelayMs: (attempt: number) => number;
|
|
11
|
+
export declare function pause(ms: number, signal: AbortSignal): Promise<void>;
|
|
12
|
+
export declare const idSchema: z.ZodUUID;
|
|
13
|
+
export declare const statusSchema: z.ZodEnum<{
|
|
14
|
+
ready: "ready";
|
|
15
|
+
empty: "empty";
|
|
16
|
+
populating: "populating";
|
|
17
|
+
failed: "failed";
|
|
18
|
+
cancelled: "cancelled";
|
|
19
|
+
}>;
|
|
20
|
+
export declare const runStatusSchema: z.ZodEnum<{
|
|
21
|
+
failed: "failed";
|
|
22
|
+
cancelled: "cancelled";
|
|
23
|
+
queued: "queued";
|
|
24
|
+
running: "running";
|
|
25
|
+
done: "done";
|
|
26
|
+
}>;
|
|
27
|
+
export declare const RUN_IN_PROGRESS: ReadonlySet<string>;
|
|
28
|
+
export declare const specSchema: z.ZodObject<{
|
|
29
|
+
name: z.ZodString;
|
|
30
|
+
description: z.ZodString;
|
|
31
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
32
|
+
name: z.ZodString;
|
|
33
|
+
label: z.ZodOptional<z.ZodString>;
|
|
34
|
+
type: z.ZodString;
|
|
35
|
+
is_primary_key: z.ZodBoolean;
|
|
36
|
+
retrieval_hint: z.ZodOptional<z.ZodString>;
|
|
37
|
+
}, z.core.$loose>>;
|
|
38
|
+
keep_when: z.ZodOptional<z.ZodObject<{
|
|
39
|
+
column: z.ZodString;
|
|
40
|
+
}, z.core.$loose>>;
|
|
41
|
+
}, z.core.$loose>;
|
|
42
|
+
export type Spec = z.infer<typeof specSchema>;
|
|
43
|
+
export declare const runSchema: z.ZodNullable<z.ZodObject<{
|
|
44
|
+
id: z.ZodUUID;
|
|
45
|
+
status: z.ZodEnum<{
|
|
46
|
+
failed: "failed";
|
|
47
|
+
cancelled: "cancelled";
|
|
48
|
+
queued: "queued";
|
|
49
|
+
running: "running";
|
|
50
|
+
done: "done";
|
|
51
|
+
}>;
|
|
52
|
+
started_at: z.ZodString;
|
|
53
|
+
finished_at: z.ZodOptional<z.ZodString>;
|
|
54
|
+
error: z.ZodOptional<z.ZodString>;
|
|
55
|
+
}, z.core.$loose>>;
|
|
56
|
+
export declare const datasetSchema: z.ZodObject<{
|
|
57
|
+
id: z.ZodUUID;
|
|
58
|
+
name: z.ZodString;
|
|
59
|
+
spec: z.ZodObject<{
|
|
60
|
+
name: z.ZodString;
|
|
61
|
+
description: z.ZodString;
|
|
62
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
63
|
+
name: z.ZodString;
|
|
64
|
+
label: z.ZodOptional<z.ZodString>;
|
|
65
|
+
type: z.ZodString;
|
|
66
|
+
is_primary_key: z.ZodBoolean;
|
|
67
|
+
retrieval_hint: z.ZodOptional<z.ZodString>;
|
|
68
|
+
}, z.core.$loose>>;
|
|
69
|
+
keep_when: z.ZodOptional<z.ZodObject<{
|
|
70
|
+
column: z.ZodString;
|
|
71
|
+
}, z.core.$loose>>;
|
|
72
|
+
}, z.core.$loose>;
|
|
73
|
+
status: z.ZodEnum<{
|
|
74
|
+
ready: "ready";
|
|
75
|
+
empty: "empty";
|
|
76
|
+
populating: "populating";
|
|
77
|
+
failed: "failed";
|
|
78
|
+
cancelled: "cancelled";
|
|
79
|
+
}>;
|
|
80
|
+
target_rows: z.ZodNumber;
|
|
81
|
+
row_count: z.ZodNumber;
|
|
82
|
+
run: z.ZodNullable<z.ZodObject<{
|
|
83
|
+
id: z.ZodUUID;
|
|
84
|
+
status: z.ZodEnum<{
|
|
85
|
+
failed: "failed";
|
|
86
|
+
cancelled: "cancelled";
|
|
87
|
+
queued: "queued";
|
|
88
|
+
running: "running";
|
|
89
|
+
done: "done";
|
|
90
|
+
}>;
|
|
91
|
+
started_at: z.ZodString;
|
|
92
|
+
finished_at: z.ZodOptional<z.ZodString>;
|
|
93
|
+
error: z.ZodOptional<z.ZodString>;
|
|
94
|
+
}, z.core.$loose>>;
|
|
95
|
+
created_at: z.ZodString;
|
|
96
|
+
}, z.core.$loose>;
|
|
97
|
+
export type Dataset = z.infer<typeof datasetSchema>;
|
|
98
|
+
export declare const createRequestSchema: z.ZodObject<{
|
|
99
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
100
|
+
spec: z.ZodOptional<z.ZodObject<{
|
|
101
|
+
name: z.ZodString;
|
|
102
|
+
description: z.ZodString;
|
|
103
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
104
|
+
name: z.ZodString;
|
|
105
|
+
label: z.ZodOptional<z.ZodString>;
|
|
106
|
+
type: z.ZodString;
|
|
107
|
+
is_primary_key: z.ZodBoolean;
|
|
108
|
+
retrieval_hint: z.ZodOptional<z.ZodString>;
|
|
109
|
+
}, z.core.$loose>>;
|
|
110
|
+
keep_when: z.ZodOptional<z.ZodObject<{
|
|
111
|
+
column: z.ZodString;
|
|
112
|
+
}, z.core.$loose>>;
|
|
113
|
+
}, z.core.$loose>>;
|
|
114
|
+
target_rows: z.ZodOptional<z.ZodNumber>;
|
|
115
|
+
}, z.core.$strict>;
|
|
116
|
+
export type CreateRequest = z.infer<typeof createRequestSchema>;
|
|
117
|
+
export declare const planRequestSchema: z.ZodObject<{
|
|
118
|
+
prompt: z.ZodString;
|
|
119
|
+
}, z.core.$strict>;
|
|
120
|
+
export declare const planResponseSchema: z.ZodObject<{
|
|
121
|
+
spec: z.ZodObject<{
|
|
122
|
+
name: z.ZodString;
|
|
123
|
+
description: z.ZodString;
|
|
124
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
125
|
+
name: z.ZodString;
|
|
126
|
+
label: z.ZodOptional<z.ZodString>;
|
|
127
|
+
type: z.ZodString;
|
|
128
|
+
is_primary_key: z.ZodBoolean;
|
|
129
|
+
retrieval_hint: z.ZodOptional<z.ZodString>;
|
|
130
|
+
}, z.core.$loose>>;
|
|
131
|
+
keep_when: z.ZodOptional<z.ZodObject<{
|
|
132
|
+
column: z.ZodString;
|
|
133
|
+
}, z.core.$loose>>;
|
|
134
|
+
}, z.core.$loose>;
|
|
135
|
+
}, z.core.$loose>;
|
|
136
|
+
export declare const listQuerySchema: z.ZodObject<{
|
|
137
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
138
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
139
|
+
}, z.core.$strict>;
|
|
140
|
+
export type ListQuery = z.infer<typeof listQuerySchema>;
|
|
141
|
+
export declare const listSchema: z.ZodObject<{
|
|
142
|
+
data: z.ZodArray<z.ZodObject<{
|
|
143
|
+
id: z.ZodUUID;
|
|
144
|
+
name: z.ZodString;
|
|
145
|
+
spec: z.ZodObject<{
|
|
146
|
+
name: z.ZodString;
|
|
147
|
+
description: z.ZodString;
|
|
148
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
149
|
+
name: z.ZodString;
|
|
150
|
+
label: z.ZodOptional<z.ZodString>;
|
|
151
|
+
type: z.ZodString;
|
|
152
|
+
is_primary_key: z.ZodBoolean;
|
|
153
|
+
retrieval_hint: z.ZodOptional<z.ZodString>;
|
|
154
|
+
}, z.core.$loose>>;
|
|
155
|
+
keep_when: z.ZodOptional<z.ZodObject<{
|
|
156
|
+
column: z.ZodString;
|
|
157
|
+
}, z.core.$loose>>;
|
|
158
|
+
}, z.core.$loose>;
|
|
159
|
+
status: z.ZodEnum<{
|
|
160
|
+
ready: "ready";
|
|
161
|
+
empty: "empty";
|
|
162
|
+
populating: "populating";
|
|
163
|
+
failed: "failed";
|
|
164
|
+
cancelled: "cancelled";
|
|
165
|
+
}>;
|
|
166
|
+
target_rows: z.ZodNumber;
|
|
167
|
+
row_count: z.ZodNumber;
|
|
168
|
+
run: z.ZodNullable<z.ZodObject<{
|
|
169
|
+
id: z.ZodUUID;
|
|
170
|
+
status: z.ZodEnum<{
|
|
171
|
+
failed: "failed";
|
|
172
|
+
cancelled: "cancelled";
|
|
173
|
+
queued: "queued";
|
|
174
|
+
running: "running";
|
|
175
|
+
done: "done";
|
|
176
|
+
}>;
|
|
177
|
+
started_at: z.ZodString;
|
|
178
|
+
finished_at: z.ZodOptional<z.ZodString>;
|
|
179
|
+
error: z.ZodOptional<z.ZodString>;
|
|
180
|
+
}, z.core.$loose>>;
|
|
181
|
+
created_at: z.ZodString;
|
|
182
|
+
}, z.core.$loose>>;
|
|
183
|
+
pagination: z.ZodObject<{
|
|
184
|
+
next_cursor: z.ZodNullable<z.ZodString>;
|
|
185
|
+
has_more: z.ZodBoolean;
|
|
186
|
+
}, z.core.$loose>;
|
|
187
|
+
}, z.core.$loose>;
|
|
188
|
+
export declare const rowsQuerySchema: z.ZodObject<{
|
|
189
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
190
|
+
after: z.ZodOptional<z.ZodString>;
|
|
191
|
+
}, z.core.$strict>;
|
|
192
|
+
export type RowsQuery = z.infer<typeof rowsQuerySchema>;
|
|
193
|
+
export declare const rowsSchema: z.ZodObject<{
|
|
194
|
+
columns: z.ZodArray<z.ZodObject<{
|
|
195
|
+
name: z.ZodString;
|
|
196
|
+
label: z.ZodString;
|
|
197
|
+
type: z.ZodString;
|
|
198
|
+
is_primary_key: z.ZodBoolean;
|
|
199
|
+
}, z.core.$loose>>;
|
|
200
|
+
rows: z.ZodArray<z.ZodObject<{
|
|
201
|
+
_id: z.ZodString;
|
|
202
|
+
data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
203
|
+
}, z.core.$loose>>;
|
|
204
|
+
truncated: z.ZodBoolean;
|
|
205
|
+
next_cursor: z.ZodNullable<z.ZodString>;
|
|
206
|
+
}, z.core.$loose>;
|
|
207
|
+
export type Rows = z.infer<typeof rowsSchema>;
|
|
208
|
+
export declare const populateRequestSchema: z.ZodObject<{
|
|
209
|
+
reset: z.ZodOptional<z.ZodBoolean>;
|
|
210
|
+
target_rows: z.ZodOptional<z.ZodNumber>;
|
|
211
|
+
}, z.core.$strict>;
|
|
212
|
+
export type PopulateRequest = z.infer<typeof populateRequestSchema>;
|
|
213
|
+
export declare const cancelSchema: z.ZodObject<{
|
|
214
|
+
run_id: z.ZodUUID;
|
|
215
|
+
status: z.ZodString;
|
|
216
|
+
}, z.core.$loose>;
|
|
217
|
+
export declare const deleteSchema: z.ZodObject<{
|
|
218
|
+
id: z.ZodUUID;
|
|
219
|
+
deleted: z.ZodLiteral<true>;
|
|
220
|
+
}, z.core.$loose>;
|
|
221
|
+
export declare const frameSchema: z.ZodObject<{
|
|
222
|
+
event: z.ZodString;
|
|
223
|
+
data: z.ZodUnknown;
|
|
224
|
+
}, z.core.$strip>;
|
|
225
|
+
export type Frame = z.infer<typeof frameSchema>;
|
|
226
|
+
export declare const statusFrameSchema: z.ZodObject<{
|
|
227
|
+
status: z.ZodEnum<{
|
|
228
|
+
failed: "failed";
|
|
229
|
+
cancelled: "cancelled";
|
|
230
|
+
queued: "queued";
|
|
231
|
+
running: "running";
|
|
232
|
+
done: "done";
|
|
233
|
+
}>;
|
|
234
|
+
error: z.ZodOptional<z.ZodString>;
|
|
235
|
+
}, z.core.$loose>;
|
|
236
|
+
export declare const rowFrameSchema: z.ZodObject<{
|
|
237
|
+
row_id: z.ZodString;
|
|
238
|
+
data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
239
|
+
deleted: z.ZodOptional<z.ZodLiteral<true>>;
|
|
240
|
+
}, z.core.$loose>;
|
|
241
|
+
export declare const logFrameSchema: z.ZodObject<{
|
|
242
|
+
text: z.ZodString;
|
|
243
|
+
}, z.core.$loose>;
|
|
244
|
+
export declare const errorFrameSchema: z.ZodObject<{
|
|
245
|
+
message: z.ZodString;
|
|
246
|
+
}, z.core.$loose>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// A populate can outlive the SDK's ten-minute timeout; watch reattaches within this.
|
|
3
|
+
export const RUN_TIMEOUT_MS = 1_800_000;
|
|
4
|
+
// Create plans in-request, under the route's 90 s budget.
|
|
5
|
+
export const START_TIMEOUT_MS = 90_000;
|
|
6
|
+
export const READ_TIMEOUT_MS = 15_000;
|
|
7
|
+
export const CANCEL_TIMEOUT_MS = 5_000;
|
|
8
|
+
export const MAX_TARGET_ROWS = 250;
|
|
9
|
+
export const LIST_LIMIT_MAX = 100;
|
|
10
|
+
export const ROWS_LIMIT_MAX = 2000;
|
|
11
|
+
// One stream lasts at most ~10 minutes server-side; a live run is picked up again.
|
|
12
|
+
export const MAX_REATTACHES = 3;
|
|
13
|
+
// Before each reattach: 1 s doubling, capped at 16 s, like the SDK's reconnectDelay.
|
|
14
|
+
export const reattachDelayMs = (attempt) => Math.min(1_000 * 2 ** attempt, 16_000);
|
|
15
|
+
// Resolves after `ms`, or at once when the signal aborts; the next request reports the abort.
|
|
16
|
+
export function pause(ms, signal) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const done = () => {
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
signal.removeEventListener('abort', done);
|
|
21
|
+
resolve();
|
|
22
|
+
};
|
|
23
|
+
const timer = setTimeout(done, ms);
|
|
24
|
+
signal.addEventListener('abort', done, { once: true });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export const idSchema = z.uuid();
|
|
28
|
+
export const statusSchema = z.enum(['empty', 'populating', 'ready', 'failed', 'cancelled']);
|
|
29
|
+
export const runStatusSchema = z.enum(['queued', 'running', 'done', 'cancelled', 'failed']);
|
|
30
|
+
// Queued and running runs are in progress; the rest have ended.
|
|
31
|
+
export const RUN_IN_PROGRESS = new Set(['queued', 'running']);
|
|
32
|
+
const columnSchema = z.looseObject({
|
|
33
|
+
name: z.string(),
|
|
34
|
+
label: z.string().optional(),
|
|
35
|
+
type: z.string(),
|
|
36
|
+
is_primary_key: z.boolean(),
|
|
37
|
+
retrieval_hint: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
export const specSchema = z.looseObject({
|
|
40
|
+
name: z.string().min(1),
|
|
41
|
+
description: z.string(),
|
|
42
|
+
columns: z.array(columnSchema).min(1),
|
|
43
|
+
keep_when: z.looseObject({ column: z.string() }).optional(),
|
|
44
|
+
});
|
|
45
|
+
export const runSchema = z
|
|
46
|
+
.looseObject({
|
|
47
|
+
id: idSchema,
|
|
48
|
+
status: runStatusSchema,
|
|
49
|
+
started_at: z.string(),
|
|
50
|
+
finished_at: z.string().optional(),
|
|
51
|
+
error: z.string().optional(),
|
|
52
|
+
})
|
|
53
|
+
.nullable();
|
|
54
|
+
export const datasetSchema = z.looseObject({
|
|
55
|
+
id: idSchema,
|
|
56
|
+
name: z.string(),
|
|
57
|
+
spec: specSchema,
|
|
58
|
+
status: statusSchema,
|
|
59
|
+
target_rows: z.number().int(),
|
|
60
|
+
row_count: z.number().int(),
|
|
61
|
+
run: runSchema,
|
|
62
|
+
created_at: z.string(),
|
|
63
|
+
});
|
|
64
|
+
const targetRowsSchema = z.number().int().min(1).max(MAX_TARGET_ROWS);
|
|
65
|
+
export const createRequestSchema = z
|
|
66
|
+
.strictObject({
|
|
67
|
+
prompt: z.string().trim().min(3).max(2000).optional(),
|
|
68
|
+
spec: specSchema.optional(),
|
|
69
|
+
target_rows: targetRowsSchema.optional(),
|
|
70
|
+
})
|
|
71
|
+
.refine((body) => (body.prompt === undefined) !== (body.spec === undefined), {
|
|
72
|
+
message: 'Send exactly one of prompt or spec',
|
|
73
|
+
path: ['prompt'],
|
|
74
|
+
});
|
|
75
|
+
export const planRequestSchema = z.strictObject({ prompt: z.string().trim().min(3).max(2000) });
|
|
76
|
+
export const planResponseSchema = z.looseObject({ spec: specSchema });
|
|
77
|
+
export const listQuerySchema = z.strictObject({
|
|
78
|
+
limit: z.number().int().min(1).max(LIST_LIMIT_MAX).optional(),
|
|
79
|
+
cursor: z.string().optional(),
|
|
80
|
+
});
|
|
81
|
+
export const listSchema = z.looseObject({
|
|
82
|
+
data: z.array(datasetSchema),
|
|
83
|
+
pagination: z.looseObject({ next_cursor: z.string().nullable(), has_more: z.boolean() }),
|
|
84
|
+
});
|
|
85
|
+
export const rowsQuerySchema = z.strictObject({
|
|
86
|
+
limit: z.number().int().min(1).max(ROWS_LIMIT_MAX).optional(),
|
|
87
|
+
after: z.string().max(32).optional(),
|
|
88
|
+
});
|
|
89
|
+
export const rowsSchema = z.looseObject({
|
|
90
|
+
columns: z.array(z.looseObject({
|
|
91
|
+
name: z.string(),
|
|
92
|
+
label: z.string(),
|
|
93
|
+
type: z.string(),
|
|
94
|
+
is_primary_key: z.boolean(),
|
|
95
|
+
})),
|
|
96
|
+
rows: z.array(z.looseObject({ _id: z.string(), data: z.record(z.string(), z.unknown()) })),
|
|
97
|
+
truncated: z.boolean(),
|
|
98
|
+
next_cursor: z.string().nullable(),
|
|
99
|
+
});
|
|
100
|
+
export const populateRequestSchema = z.strictObject({
|
|
101
|
+
reset: z.boolean().optional(),
|
|
102
|
+
target_rows: targetRowsSchema.optional(),
|
|
103
|
+
});
|
|
104
|
+
export const cancelSchema = z.looseObject({ run_id: idSchema, status: z.string() });
|
|
105
|
+
export const deleteSchema = z.looseObject({ id: idSchema, deleted: z.literal(true) });
|
|
106
|
+
// The SSE envelope: the event line names the frame, the payload varies by name.
|
|
107
|
+
export const frameSchema = z.object({ event: z.string().min(1), data: z.unknown() });
|
|
108
|
+
export const statusFrameSchema = z.looseObject({
|
|
109
|
+
status: runStatusSchema,
|
|
110
|
+
error: z.string().optional(),
|
|
111
|
+
});
|
|
112
|
+
export const rowFrameSchema = z.looseObject({
|
|
113
|
+
row_id: z.string(),
|
|
114
|
+
data: z.record(z.string(), z.unknown()),
|
|
115
|
+
deleted: z.literal(true).optional(),
|
|
116
|
+
});
|
|
117
|
+
export const logFrameSchema = z.looseObject({ text: z.string() });
|
|
118
|
+
// The proxy's own frame: the service went away mid-stream.
|
|
119
|
+
export const errorFrameSchema = z.looseObject({ message: z.string() });
|
package/dist/program.js
CHANGED
|
@@ -25,6 +25,7 @@ const GROUPS = {
|
|
|
25
25
|
(await import('./commands/batch.js')).registerBatch(agentCmd);
|
|
26
26
|
},
|
|
27
27
|
research: async (p) => (await import('./commands/research.js')).registerResearch(p),
|
|
28
|
+
dataset: async (p) => (await import('./commands/dataset.js')).registerDataset(p),
|
|
28
29
|
search: async (p) => (await import('./commands/search.js')).registerSearch(p),
|
|
29
30
|
fetch: async (p) => (await import('./commands/fetch.js')).registerFetch(p),
|
|
30
31
|
monitor: async (p) => (await import('./commands/monitor.js')).registerMonitor(p),
|