@pipelex/sdk 0.22.0 → 0.24.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 CHANGED
@@ -112,7 +112,7 @@ These pages ship inside the published package, so a reader who has only installe
112
112
  | [`docs/architecture.md`](./docs/architecture.md) | The whole client surface: the request pipeline, every route, the typed errors |
113
113
  | [`docs/run-results.md`](./docs/run-results.md) | Every field of `RunResults` — the run id as a durable handle, `main_stuff`, `working_memory`, `graph_spec`, the usage pair, produced files |
114
114
  | [`docs/run-usage.md`](./docs/run-usage.md) | What a run consumed, record by record, and `summarizeUsage` which folds them into one reading |
115
- | [`docs/artifact-download.md`](./docs/artifact-download.md) | Turning the `pipelex-storage://` references a run produced back into bytes: `collectArtifacts`, `resolveArtifacts`, `fetchArtifact`, `downloadArtifacts` |
115
+ | [`docs/artifact-download.md`](./docs/artifact-download.md) | Turning the `pipelex-storage://` references a run produced back into bytes: `locateArtifacts`, `collectArtifacts`, `resolveArtifacts`, `fetchArtifact`, `downloadArtifacts`, and how a saved file is named after the field it fills |
116
116
  | [`docs/input-preparation.md`](./docs/input-preparation.md) | The other direction — `uploadFile` and `prepareInputs`, which turn local files into references a run can take, and the upload grant a browser page sends a file with |
117
117
  | [`docs/crate-routes.md`](./docs/crate-routes.md) | `resolve` and `codegen`, and the offline `runCodegenCheck` that guards a committed tree |
118
118
  | [`docs/build-routes.md`](./docs/build-routes.md) | The `/v1/build/*` projections: `buildInputs`, `buildOutput`, `buildRunner` |
@@ -2,8 +2,9 @@
2
2
  * The artifact stack — the download twin of `prepareInputs`, in layers so each
3
3
  * operation is usable without the next:
4
4
  *
5
- * - {@link collectArtifacts} — a pure walk of any JSON value for the strings that
6
- * ARE `pipelex-storage://` references. No network, no key.
5
+ * - {@link locateArtifacts} — a pure walk of any JSON value for the strings that
6
+ * ARE `pipelex-storage://` references, each with every path it sits at.
7
+ * {@link collectArtifacts} is the same walk's bare references. No network, no key.
7
8
  * - {@link resolveArtifacts} — the platform's bulk resolve route over a whole
8
9
  * list, chunked at the route's bound, one verdict per reference.
9
10
  * - {@link fetchArtifact} — a bounded `Response` for one reference: resolved
@@ -20,7 +21,7 @@
20
21
  *
21
22
  * `node:fs/promises`, `node:path` and `undici` are imported dynamically, behind
22
23
  * `isNodeRuntime()`, so the module's static import graph stays free of Node
23
- * builtins: `collectArtifacts` and `resolveArtifacts` run anywhere, `fetchArtifact`
24
+ * builtins: the walk and `resolveArtifacts` run anywhere, `fetchArtifact`
24
25
  * runs anywhere the global `fetch` does (the dispatcher is a Node refinement), and
25
26
  * only `downloadArtifacts` touches a filesystem.
26
27
  */
@@ -91,7 +92,10 @@ export interface BulkResolvedStorageUrls {
91
92
  export interface FetchArtifactOptions {
92
93
  /** Refuse (before a byte is written) and cut (mid-stream) a body over this many bytes. Default 1 GiB. */
93
94
  maxBytes?: number;
94
- /** Budget for the whole exchange — connect, headers and body. Default 120 s. */
95
+ /**
96
+ * Budget for the whole exchange — connect, headers and body. Default 120 s. At most
97
+ * 2147483647, the longest delay a timer honours.
98
+ */
95
99
  timeoutMs?: number;
96
100
  /**
97
101
  * Accept a plain `http:` link. Off by default: a general-purpose library does
@@ -135,14 +139,36 @@ export type DownloadArtifactsRequest = DownloadArtifactsOptions & ({
135
139
  results: RunResults;
136
140
  run_id?: never;
137
141
  });
142
+ /**
143
+ * Where one `pipelex-storage://` reference sits in a walked value — what
144
+ * {@link locateArtifacts} answers per reference. `found_at` lists every path at
145
+ * which the reference occurs, in walk order, and is never empty: `found_at[0]` is
146
+ * where it was first seen, and the path a saved file is named after.
147
+ *
148
+ * A path is `$`-rooted: `$` is the walked value itself, an object key matching
149
+ * `^[A-Za-z_][A-Za-z0-9_]*$` is `.key`, any other key is `["…"]` in JSON string
150
+ * escaping, and an array index is `[n]` — `$.rooms[3].staged_photo.url`,
151
+ * `$.items[0].url`, `$["a key"].url`. It is the exact path of the string, the
152
+ * final `url` of a content object included.
153
+ */
154
+ export interface ArtifactLocation {
155
+ /** The reference, exactly as it appears in the walked value. */
156
+ uri: string;
157
+ /** Every `$`-rooted path at which the reference occurs, in walk order. */
158
+ found_at: string[];
159
+ }
138
160
  /**
139
161
  * One reference's outcome in a download verdict — one shape with nullable fields,
140
162
  * 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.
163
+ * null, or `error` is set and both are null. `found_at` says where the reference
164
+ * sits in the walked scope, and `content_type` is the platform's guess from the
165
+ * reference's extension, known before the fetch; both are on both arms, so an
166
+ * item that was not saved still says which field it would have filled.
143
167
  */
144
168
  export type DownloadedArtifact = {
145
169
  uri: string;
170
+ /** Every `$`-rooted path in the walked scope where the reference sits; the first one named the file. */
171
+ found_at: string[];
146
172
  /** Absolute path of the written file. */
147
173
  path: string;
148
174
  content_type: string | null;
@@ -151,6 +177,8 @@ export type DownloadedArtifact = {
151
177
  error: null;
152
178
  } | {
153
179
  uri: string;
180
+ /** Every `$`-rooted path in the walked scope where the reference sits. */
181
+ found_at: string[];
154
182
  path: null;
155
183
  content_type: string | null;
156
184
  size: null;
@@ -185,32 +213,68 @@ export interface ArtifactCapableClient {
185
213
  signal?: AbortSignal;
186
214
  }): Promise<RunResultState>;
187
215
  }
216
+ /**
217
+ * Every `pipelex-storage://` reference inside a JSON-shaped value, each with
218
+ * every path at which it occurs. The references are deduplicated and kept in
219
+ * discovery order (the order of their first sighting), and each one's `found_at`
220
+ * lists its paths in walk order, so `found_at[0]` is where it was first seen.
221
+ *
222
+ * The string test is {@link collectArtifacts}'s: a string counts only when it IS
223
+ * a reference. A path is rooted at `$`, the walked value itself; an object key
224
+ * matching `^[A-Za-z_][A-Za-z0-9_]*$` is written `.key`, any other key `["…"]` in
225
+ * JSON string escaping, and an array index `[n]`. The runtime serializes a
226
+ * produced image or document as content carrying its reference in `url`, so a
227
+ * typical path ends there: `$.rooms[3].staged_photo.url`, `$.items[0].url`, or
228
+ * `$.url` for an output that is one image. Pure — no network, no key.
229
+ */
230
+ export declare function locateArtifacts(value: unknown): ArtifactLocation[];
188
231
  /**
189
232
  * 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.
233
+ * in discovery order — the references of {@link locateArtifacts}, without their
234
+ * paths. A string counts only when it IS a reference — the whole string, scheme
235
+ * first, with something after the scheme; text that merely contains one does
236
+ * not. The scheme is unambiguous, so this walk is a contract rather than a
237
+ * heuristic: the runtime serializes a produced image or document as content
238
+ * carrying its reference in `url`, beside an expiring `public_url` this walk
239
+ * ignores. Pure — no network, no key — so a consumer can count or list a run's
240
+ * produced files without resolving any of them.
197
241
  */
198
242
  export declare function collectArtifacts(value: unknown): string[];
199
243
  /** A string that is a storage reference: the scheme, then at least one character. */
200
244
  export declare function isStorageReference(value: string): boolean;
201
245
  /**
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
246
+ * The bare filename a reference is saved under, named after the field it fills:
247
+ * the path in `location.found_at[0]`, where the reference was first seen.
248
+ *
249
+ * 1. A final object key `url` is dropped, since the runtime's image and
250
+ * document contents carry their reference there; a reference under any other
251
+ * key keeps that key, and a `url` key that is not final is kept.
252
+ * 2. Each key is reduced to `[A-Za-z0-9_]`, every other character (`-` and `.`
253
+ * included) becoming `_`; an index stays its decimal digits.
254
+ * 3. The segments are joined with `-`. An empty result — the reference is the
255
+ * walked value itself, or its `url` — becomes the scope's name.
256
+ * 4. Over the filename length cap (`MAX_FILENAME_LENGTH`, extension included),
257
+ * the tail is kept: whole leading segments are dropped first, since the last
258
+ * ones are the specific ones, and a single segment still too long is cut to
259
+ * fit.
260
+ * 5. A stem Windows reserves for a device (`con`, `prn`, `aux`, `nul`,
261
+ * `com0`–`com9`, `lpt0`–`lpt9`, in any case) gets a trailing `_`, so
262
+ * `$.aux.url` is saved as `aux_.png`.
263
+ * 6. The extension is the storage key's own, reduced to `[A-Za-z0-9]`, when it
264
+ * has a short one; otherwise the content type's, for the types a run
265
+ * produces; otherwise there is none.
266
+ *
267
+ * So `$.rooms[3].staged_photo.url` is saved as `rooms-3-staged_photo.png`, and
268
+ * `$.url` in `main_stuff` as `main_stuff.png`. The stem is ASCII letters, digits,
269
+ * `_` and the `-` joins, never empty and never a device name, so the name can
270
+ * only ever be a regular file directly inside the target directory. A collision on disk is not this
210
271
  * function's concern: `downloadArtifacts` suffixes the stem (`name-1.ext`) on
211
272
  * exclusive creation, so a file is never overwritten.
273
+ *
274
+ * Throws `ArtifactOperationError` for a location whose `found_at[0]` is not a path
275
+ * in the notation {@link locateArtifacts} writes, or for an unknown scope.
212
276
  */
213
- export declare function artifactFilename(uri: string, contentType: string | null | undefined, index: number): string;
277
+ export declare function artifactFilename(location: ArtifactLocation, contentType: string | null | undefined, scope: ArtifactScope): string;
214
278
  /**
215
279
  * Resolve a list of references through the bulk route, chunked at
216
280
  * {@link BULK_RESOLVE_MAX_URIS} per request, and answer one
@@ -264,19 +328,20 @@ export declare function writeFully(writer: ChunkWriter, chunk: Uint8Array): Prom
264
328
  * Save a run's produced files under `dir` — Node-only, like the path-string arm
265
329
  * of `uploadFile`. Takes a `run_id` (the results are re-read, so a run is
266
330
  * downloadable days later) or a `RunResults` in hand; walks the requested scope
267
- * with {@link collectArtifacts}; resolves the whole set through the bulk route
331
+ * with {@link locateArtifacts}; resolves the whole set through the bulk route
268
332
  * ahead of the workers; then a bounded pool of workers each fetch → open with
269
333
  * `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.
334
+ * reaches it. The embedded `public_url` is never used. Each file is named after
335
+ * the field it fills, by {@link artifactFilename}'s rule, and never overwritten;
336
+ * a failed or aborted download unlinks its partial file.
273
337
  *
274
338
  * Returns a produced verdict: one entry per reference, errors as values. It
275
339
  * throws only when no verdict can be produced — `RunStillRunningError` or
276
340
  * `RunFailedError` for a run that has not completed, `ScopeUnavailableError`
277
341
  * when the scope's artifact is null or missing, `ArtifactAuthenticationError`
278
342
  * (carrying the verdict so far) when the resolve route refuses the credential,
279
- * `ArtifactOperationError` outside Node or for an unusable `dir`, and the
343
+ * `ArtifactOperationError` outside Node, for an unusable `dir` or for an
344
+ * unknown `scope`, and the
280
345
  * transport and lifecycle errors of the reads it makes (`ApiResponseError` for
281
346
  * a deployment without the bulk route, `RunLifecycleUnavailableError` for a
282
347
  * bare runner asked by id, `ApiUnreachableError`).