@pipelex/sdk 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/artifacts.d.ts +284 -0
- package/dist/artifacts.js +841 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/client.d.ts +39 -0
- package/dist/client.js +62 -11
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +66 -2
- package/dist/errors.js +77 -2
- package/dist/errors.js.map +1 -1
- package/dist/execute-result.d.ts +3 -2
- package/dist/execute-result.js +3 -2
- package/dist/execute-result.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/runs.d.ts +40 -7
- package/dist/runs.js.map +1 -1
- package/dist/usage.d.ts +87 -0
- package/dist/usage.js +121 -0
- package/dist/usage.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Early. `PipelexApiClient` implements the MTHDS protocol-execution routes (`execu
|
|
|
10
10
|
|
|
11
11
|
Besides the client, the package exports `runCodegenCheck` — a **pure** offline check that verifies a committed `codegen()` tree still matches its `codegen.lock`. It needs no server, no key, and no client instance, so it fits a CI job. See [`docs/crate-routes.md`](./docs/crate-routes.md#the-offline-check--runcodegencheck).
|
|
12
12
|
|
|
13
|
+
It also exports `summarizeUsage`, which folds a completed run's usage records into one null-aware summary — total cost, input and output tokens, and a per-pipe rollup — without any I/O. See [`docs/run-usage.md`](./docs/run-usage.md#summarizing-a-run--summarizeusage).
|
|
14
|
+
|
|
13
15
|
## Install
|
|
14
16
|
|
|
15
17
|
```bash
|
|
@@ -32,8 +34,8 @@ const report = await client.validate(["domain = 'demo'"]);
|
|
|
32
34
|
if (report.is_valid) {
|
|
33
35
|
// Run it and wait for the result (durable start + poll on the hosted API).
|
|
34
36
|
const result = await client.startAndWaitForResult({ pipe_code: "demo.greet" });
|
|
35
|
-
// Every completed run delivers a resolved `main_stuff
|
|
36
|
-
//
|
|
37
|
+
// Every completed run delivers a resolved `main_stuff`, and every named stuff of
|
|
38
|
+
// the run in `working_memory`.
|
|
37
39
|
console.log(result.main_stuff);
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The artifact stack — the download twin of `prepareInputs`, in layers so each
|
|
3
|
+
* operation is usable without the next:
|
|
4
|
+
*
|
|
5
|
+
* - {@link collectArtifacts} — a pure walk of any JSON value for the strings that
|
|
6
|
+
* ARE `pipelex-storage://` references. No network, no key.
|
|
7
|
+
* - {@link resolveArtifacts} — the platform's bulk resolve route over a whole
|
|
8
|
+
* list, chunked at the route's bound, one verdict per reference.
|
|
9
|
+
* - {@link fetchArtifact} — a bounded `Response` for one reference: resolved
|
|
10
|
+
* fresh, timed out, redirects refused, the byte cap enforced mid-stream, no
|
|
11
|
+
* credentials forwarded, headers untouched.
|
|
12
|
+
* - {@link downloadArtifacts} — Node-only: a run's produced files saved under a
|
|
13
|
+
* directory by a bounded pool of workers, as a produced verdict.
|
|
14
|
+
*
|
|
15
|
+
* A produced file is never embedded in a run's results: the content carries its
|
|
16
|
+
* durable `pipelex-storage://` reference beside a signed `public_url` that expires
|
|
17
|
+
* on the provider's schedule. Nothing here reads that embedded link — every link
|
|
18
|
+
* is minted fresh by the platform, and re-minted when it has expired by the time a
|
|
19
|
+
* worker reaches it. See `docs/artifact-download.md`.
|
|
20
|
+
*
|
|
21
|
+
* `node:fs/promises`, `node:path` and `undici` are imported dynamically, behind
|
|
22
|
+
* `isNodeRuntime()`, so the module's static import graph stays free of Node
|
|
23
|
+
* builtins: `collectArtifacts` and `resolveArtifacts` run anywhere, `fetchArtifact`
|
|
24
|
+
* runs anywhere the global `fetch` does (the dispatcher is a Node refinement), and
|
|
25
|
+
* only `downloadArtifacts` touches a filesystem.
|
|
26
|
+
*/
|
|
27
|
+
import type { RunResults, RunResultState } from "./runs.js";
|
|
28
|
+
/** The scheme of a durable storage reference. */
|
|
29
|
+
export declare const PIPELEX_STORAGE_SCHEME = "pipelex-storage://";
|
|
30
|
+
/**
|
|
31
|
+
* How many references one bulk resolve request takes — the route's bound, fixed
|
|
32
|
+
* by the platform contract rather than configured per deployment. A longer list
|
|
33
|
+
* is a `422`, so {@link resolveArtifacts} chunks at this size.
|
|
34
|
+
*/
|
|
35
|
+
export declare const BULK_RESOLVE_MAX_URIS = 100;
|
|
36
|
+
/**
|
|
37
|
+
* The per-file byte cap. An accident guard against filling a disk from a
|
|
38
|
+
* runaway output, not a judgment about artifact size: a produced file is
|
|
39
|
+
* server-side, so a caller cannot shrink it the way they can shrink an upload.
|
|
40
|
+
*/
|
|
41
|
+
export declare const DEFAULT_ARTIFACT_MAX_BYTES: number;
|
|
42
|
+
/** The per-file budget for connecting, receiving the headers and reading the body. */
|
|
43
|
+
export declare const DEFAULT_ARTIFACT_TIMEOUT_MS = 120000;
|
|
44
|
+
/** The cap on the bytes one `downloadArtifacts` call writes in total. */
|
|
45
|
+
export declare const DEFAULT_DOWNLOAD_MAX_TOTAL_BYTES: number;
|
|
46
|
+
/** How many artifacts one `downloadArtifacts` call has in flight at once. */
|
|
47
|
+
export declare const DEFAULT_DOWNLOAD_CONCURRENCY = 4;
|
|
48
|
+
/**
|
|
49
|
+
* Why one reference failed — a value, never a thrown error. `code` is the
|
|
50
|
+
* resolve route's own per-reference code (`invalid_storage_uri`, `forbidden`)
|
|
51
|
+
* or one of the fetch boundary's (see `ArtifactFetchError`), plus the download's
|
|
52
|
+
* own `resolve_failed`, `total_limit_exceeded`, `write_failed` and `aborted`.
|
|
53
|
+
* `detail` is the sentence a person reads.
|
|
54
|
+
*/
|
|
55
|
+
export interface ArtifactItemError {
|
|
56
|
+
code: string;
|
|
57
|
+
detail: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* One reference's resolution — the bulk resolve route's item, verbatim. Either
|
|
61
|
+
* the link fields are set and `error` is null, or `error` is set and the three
|
|
62
|
+
* link fields are null. A consumer branches on `error`, never on an HTTP status:
|
|
63
|
+
* the request was a `200` whenever every reference got a verdict.
|
|
64
|
+
*/
|
|
65
|
+
export type ResolvedArtifact = {
|
|
66
|
+
/** The reference exactly as sent. */
|
|
67
|
+
uri: string;
|
|
68
|
+
/** A presigned link, fetchable now and for about fifteen minutes. */
|
|
69
|
+
url: string;
|
|
70
|
+
/** UTC expiry of the link, ISO 8601. */
|
|
71
|
+
expires_at: string;
|
|
72
|
+
/** The platform's content-type guess from the reference's extension; null when it has none. */
|
|
73
|
+
content_type: string | null;
|
|
74
|
+
error: null;
|
|
75
|
+
} | {
|
|
76
|
+
uri: string;
|
|
77
|
+
url: null;
|
|
78
|
+
expires_at: null;
|
|
79
|
+
content_type: null;
|
|
80
|
+
error: ArtifactItemError;
|
|
81
|
+
};
|
|
82
|
+
/** The wire request of `POST /v1/resolve-storage-url/bulk` — at most {@link BULK_RESOLVE_MAX_URIS} references. */
|
|
83
|
+
export interface BulkResolveStorageUrlsInput {
|
|
84
|
+
uris: string[];
|
|
85
|
+
}
|
|
86
|
+
/** The wire response of `POST /v1/resolve-storage-url/bulk` — one item per requested reference, in request order. */
|
|
87
|
+
export interface BulkResolvedStorageUrls {
|
|
88
|
+
items: ResolvedArtifact[];
|
|
89
|
+
}
|
|
90
|
+
/** The bounds `fetchArtifact` applies. Every one has a safe default. */
|
|
91
|
+
export interface FetchArtifactOptions {
|
|
92
|
+
/** Refuse (before a byte is written) and cut (mid-stream) a body over this many bytes. Default 1 GiB. */
|
|
93
|
+
maxBytes?: number;
|
|
94
|
+
/** Budget for the whole exchange — connect, headers and body. Default 120 s. */
|
|
95
|
+
timeoutMs?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Accept a plain `http:` link. Off by default: a general-purpose library does
|
|
98
|
+
* not fetch over plain http silently. The local compose stack's object store
|
|
99
|
+
* hands out such links, which is the case this opts into.
|
|
100
|
+
*/
|
|
101
|
+
allowHttp?: boolean;
|
|
102
|
+
/** Cancels the fetch; a body being read is cancelled too. */
|
|
103
|
+
signal?: AbortSignal;
|
|
104
|
+
}
|
|
105
|
+
/** Which of a run's artifacts `downloadArtifacts` walks for references. */
|
|
106
|
+
export type ArtifactScope = "main_stuff" | "working_memory";
|
|
107
|
+
/** The options shared by both forms of a `downloadArtifacts` request. */
|
|
108
|
+
export interface DownloadArtifactsOptions extends FetchArtifactOptions {
|
|
109
|
+
/** The directory to save under, created if missing. Files are never overwritten. */
|
|
110
|
+
dir: string;
|
|
111
|
+
/**
|
|
112
|
+
* `main_stuff` (the default) walks the run's main output. `working_memory` is
|
|
113
|
+
* the opt-in that also brings down the echoed inputs and every intermediate.
|
|
114
|
+
*/
|
|
115
|
+
scope?: ArtifactScope;
|
|
116
|
+
/** How many artifacts are in flight at once. Default 4. */
|
|
117
|
+
concurrency?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Cap on the bytes written by the whole call. Default 4 GiB. The item that would
|
|
120
|
+
* cross it is an item error, and the items not yet started are skipped with the
|
|
121
|
+
* same reason.
|
|
122
|
+
*/
|
|
123
|
+
maxTotalBytes?: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* What `downloadArtifacts` takes: the options plus exactly one of `run_id` (the
|
|
127
|
+
* results are re-read, so this works days after the run) or `results` (a
|
|
128
|
+
* `RunResults` already in hand, read as it is). The type pins the other to
|
|
129
|
+
* `never`, so naming both is a compile error.
|
|
130
|
+
*/
|
|
131
|
+
export type DownloadArtifactsRequest = DownloadArtifactsOptions & ({
|
|
132
|
+
run_id: string;
|
|
133
|
+
results?: never;
|
|
134
|
+
} | {
|
|
135
|
+
results: RunResults;
|
|
136
|
+
run_id?: never;
|
|
137
|
+
});
|
|
138
|
+
/**
|
|
139
|
+
* One reference's outcome in a download verdict — one shape with nullable fields,
|
|
140
|
+
* like {@link ResolvedArtifact}: either `path` and `size` are set and `error` is
|
|
141
|
+
* null, or `error` is set and both are null. `content_type` is the platform's
|
|
142
|
+
* guess from the reference's extension, known before the fetch, on both arms.
|
|
143
|
+
*/
|
|
144
|
+
export type DownloadedArtifact = {
|
|
145
|
+
uri: string;
|
|
146
|
+
/** Absolute path of the written file. */
|
|
147
|
+
path: string;
|
|
148
|
+
content_type: string | null;
|
|
149
|
+
/** Bytes written. */
|
|
150
|
+
size: number;
|
|
151
|
+
error: null;
|
|
152
|
+
} | {
|
|
153
|
+
uri: string;
|
|
154
|
+
path: null;
|
|
155
|
+
content_type: string | null;
|
|
156
|
+
size: null;
|
|
157
|
+
error: ArtifactItemError;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* The produced verdict of `downloadArtifacts`. `artifacts.length` is the count
|
|
161
|
+
* of references walked, errors included; an empty list over a present scope is
|
|
162
|
+
* a verdict ("this output references no stored file"), not an error.
|
|
163
|
+
*/
|
|
164
|
+
export interface DownloadArtifactsResult {
|
|
165
|
+
scope: ArtifactScope;
|
|
166
|
+
/** One entry per reference, in discovery order. */
|
|
167
|
+
artifacts: DownloadedArtifact[];
|
|
168
|
+
/** The absolute paths of the files saved, in the same order. */
|
|
169
|
+
saved_paths: string[];
|
|
170
|
+
/** True when every walked reference was saved — vacuously true for an empty walk. */
|
|
171
|
+
all_saved: boolean;
|
|
172
|
+
/** Present, and true, only when the caller's `signal` aborted the call. */
|
|
173
|
+
aborted?: true;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The client surface the artifact operations need: the raw bulk resolve call and
|
|
177
|
+
* the single-shot result lookup. Typed as the client's own signatures so
|
|
178
|
+
* `PipelexApiClient` satisfies it structurally, and so tests inject a fake.
|
|
179
|
+
*/
|
|
180
|
+
export interface ArtifactCapableClient {
|
|
181
|
+
resolveStorageUrls(input: BulkResolveStorageUrlsInput, options?: {
|
|
182
|
+
signal?: AbortSignal;
|
|
183
|
+
}): Promise<BulkResolvedStorageUrls>;
|
|
184
|
+
getRunResult(runId: string, options?: {
|
|
185
|
+
signal?: AbortSignal;
|
|
186
|
+
}): Promise<RunResultState>;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Every `pipelex-storage://` reference inside a JSON-shaped value, deduplicated,
|
|
190
|
+
* in discovery order. A string counts only when it IS a reference — the whole
|
|
191
|
+
* string, scheme first, with something after the scheme; text that merely
|
|
192
|
+
* contains one does not. The scheme is unambiguous, so this walk is a contract
|
|
193
|
+
* rather than a heuristic: the runtime serializes a produced image or document
|
|
194
|
+
* as content carrying its reference in `url`, beside an expiring `public_url`
|
|
195
|
+
* this walk ignores. Pure — no network, no key — so a consumer can count or
|
|
196
|
+
* list a run's produced files without resolving any of them.
|
|
197
|
+
*/
|
|
198
|
+
export declare function collectArtifacts(value: unknown): string[];
|
|
199
|
+
/** A string that is a storage reference: the scheme, then at least one character. */
|
|
200
|
+
export declare function isStorageReference(value: string): boolean;
|
|
201
|
+
/**
|
|
202
|
+
* The bare filename a storage reference is saved under: the last segment of the
|
|
203
|
+
* storage key, reduced to a conservative character set so it can never name
|
|
204
|
+
* anything but a regular file directly inside the target directory. Path
|
|
205
|
+
* separators are the split point, so no traversal survives; leading dots are
|
|
206
|
+
* stripped, so no hidden file and no `..`; everything outside `[A-Za-z0-9._-]`
|
|
207
|
+
* becomes `_`; an empty result falls back to a numbered `artifact-N`. Length is
|
|
208
|
+
* capped with the extension preserved, and an extension is added from the
|
|
209
|
+
* content type when the key carries none. A collision on disk is not this
|
|
210
|
+
* function's concern: `downloadArtifacts` suffixes the stem (`name-1.ext`) on
|
|
211
|
+
* exclusive creation, so a file is never overwritten.
|
|
212
|
+
*/
|
|
213
|
+
export declare function artifactFilename(uri: string, contentType: string | null | undefined, index: number): string;
|
|
214
|
+
/**
|
|
215
|
+
* Resolve a list of references through the bulk route, chunked at
|
|
216
|
+
* {@link BULK_RESOLVE_MAX_URIS} per request, and answer one
|
|
217
|
+
* {@link ResolvedArtifact} per reference in request order, duplicates included.
|
|
218
|
+
* Per-reference failure is a value on the item; only what is not about a
|
|
219
|
+
* reference throws — the route's whole-request refusals as `ApiResponseError`
|
|
220
|
+
* (a caller with no organization, a malformed request, a signing failure, or a
|
|
221
|
+
* `404` from a deployment that does not serve the route) and an unreachable
|
|
222
|
+
* host as `ApiUnreachableError`. An empty list resolves to an empty list with
|
|
223
|
+
* no request made.
|
|
224
|
+
*/
|
|
225
|
+
export declare function resolveArtifacts(client: Pick<ArtifactCapableClient, "resolveStorageUrls">, uris: string[], options?: {
|
|
226
|
+
signal?: AbortSignal;
|
|
227
|
+
}): Promise<ResolvedArtifact[]>;
|
|
228
|
+
/**
|
|
229
|
+
* A bounded `Response` for one reference: the link is minted fresh through the
|
|
230
|
+
* bulk route, then fetched with `redirect: "manual"` (a presigned link has no
|
|
231
|
+
* reason to redirect, and one that does is refused rather than followed), no
|
|
232
|
+
* headers of ours (the link carries its own authorization in the query string,
|
|
233
|
+
* and nothing must ride along to the object store), a budget covering the
|
|
234
|
+
* headers and the body, and a byte cap checked against `Content-Length` before
|
|
235
|
+
* a byte is read and again on every chunk. The status, status text and headers
|
|
236
|
+
* are the store's own, except that a `Content-Encoding` the fetch already decoded
|
|
237
|
+
* is dropped with the encoded `Content-Length`: a proxy relaying this response sets its own
|
|
238
|
+
* `Content-Disposition`, `X-Content-Type-Options`, CSP and caching headers,
|
|
239
|
+
* because this function does not.
|
|
240
|
+
*
|
|
241
|
+
* Only a `2xx` is returned. Anything else is thrown as `ArtifactFetchError`
|
|
242
|
+
* with a `code` (a redirect, a refused or vanished object, a store fault, a
|
|
243
|
+
* declared oversize, a timeout, a network fault, an unusable link, or the
|
|
244
|
+
* route's own per-reference refusal); a body that crosses the cap mid-stream
|
|
245
|
+
* errors the returned stream with the same error type (`too_large`). The
|
|
246
|
+
* caller's abort propagates as-is. Whole-request failures of the resolve step
|
|
247
|
+
* propagate unchanged (`ApiResponseError`, `ApiUnreachableError`).
|
|
248
|
+
*/
|
|
249
|
+
export declare function fetchArtifact(client: Pick<ArtifactCapableClient, "resolveStorageUrls">, uri: string, options?: FetchArtifactOptions): Promise<Response>;
|
|
250
|
+
/** The write side of a streamed download: what {@link writeFully} needs of a `FileHandle`. */
|
|
251
|
+
export interface ChunkWriter {
|
|
252
|
+
write(buffer: Uint8Array, offset: number, length: number): Promise<{
|
|
253
|
+
bytesWritten: number;
|
|
254
|
+
}>;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Write the whole chunk, looping on short writes. `FileHandle.write()` resolves
|
|
258
|
+
* with the count it managed, which can fall short of the chunk on a filesystem
|
|
259
|
+
* under pressure; taking one call as "written" would report a truncated file
|
|
260
|
+
* as saved.
|
|
261
|
+
*/
|
|
262
|
+
export declare function writeFully(writer: ChunkWriter, chunk: Uint8Array): Promise<void>;
|
|
263
|
+
/**
|
|
264
|
+
* Save a run's produced files under `dir` — Node-only, like the path-string arm
|
|
265
|
+
* of `uploadFile`. Takes a `run_id` (the results are re-read, so a run is
|
|
266
|
+
* downloadable days later) or a `RunResults` in hand; walks the requested scope
|
|
267
|
+
* with {@link collectArtifacts}; resolves the whole set through the bulk route
|
|
268
|
+
* ahead of the workers; then a bounded pool of workers each fetch → open with
|
|
269
|
+
* `wx` → write, re-resolving any link that has expired by the time a worker
|
|
270
|
+
* reaches it. The embedded `public_url` is never used. Files are named by
|
|
271
|
+
* {@link artifactFilename} and never overwritten; a failed or aborted download
|
|
272
|
+
* unlinks its partial file.
|
|
273
|
+
*
|
|
274
|
+
* Returns a produced verdict: one entry per reference, errors as values. It
|
|
275
|
+
* throws only when no verdict can be produced — `RunStillRunningError` or
|
|
276
|
+
* `RunFailedError` for a run that has not completed, `ScopeUnavailableError`
|
|
277
|
+
* when the scope's artifact is null or missing, `ArtifactAuthenticationError`
|
|
278
|
+
* (carrying the verdict so far) when the resolve route refuses the credential,
|
|
279
|
+
* `ArtifactOperationError` outside Node or for an unusable `dir`, and the
|
|
280
|
+
* transport and lifecycle errors of the reads it makes (`ApiResponseError` for
|
|
281
|
+
* a deployment without the bulk route, `RunLifecycleUnavailableError` for a
|
|
282
|
+
* bare runner asked by id, `ApiUnreachableError`).
|
|
283
|
+
*/
|
|
284
|
+
export declare function downloadArtifacts(client: ArtifactCapableClient, request: DownloadArtifactsRequest): Promise<DownloadArtifactsResult>;
|