@pipelex/sdk 0.39.0 → 0.40.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.
@@ -0,0 +1,107 @@
1
+ # The files a bundle's steps read (`resources`)
2
+
3
+ A bundle sent to the API as content has no directory, so a file one of its steps reads from beside the bundle has to travel with it. Today that is a `PipeDocGen` step's `template_file`: the Word, Excel or PowerPoint template it fills, or the HTML template it prints as a PDF. The runner takes such files as the request's `resources`, a JSON object mapping each file's path, exactly as the step names it, to the file's bytes in standard padded base64. This SDK takes the bytes from you, keyed by that path, and encodes them.
4
+
5
+ ```ts
6
+ import { readFile } from "node:fs/promises";
7
+
8
+ const mthds = await readFile("invoicing/bundle.mthds", "utf8");
9
+ const resources = {
10
+ "templates/invoice.docx": await readFile("invoicing/templates/invoice.docx"),
11
+ };
12
+
13
+ const verdict = await client.validate([mthds], false, ["bundle.mthds"], undefined, undefined, {
14
+ resources,
15
+ });
16
+ const result = await client.startAndWaitForResult({ mthds_contents: [mthds], resources, inputs });
17
+ ```
18
+
19
+ ## Where they go
20
+
21
+ | Call | Where `resources` goes |
22
+ | --- | --- |
23
+ | `validate(contents, …, options)` | `options.resources`, beside inline contents or a hosted `{ method_id }` |
24
+ | `validateFiles(files, options)` | `options.resources` |
25
+ | `execute(options)`, `start(options)`, `startAndWaitForResult(options)` | `options.resources`, beside `mthds_contents`, a method bundle (`files` or `bundle_b64`) or a hosted `method_id`; the blocking fallback a bare runner takes sends them too |
26
+ | `pipeIo(request)`, `resolve(request)`, `codegen(request)` | `request.resources`, beside inline `files` |
27
+ | `prepareInputs(request)` | `request.resources`, beside inline `files`, sent with the one pipe I/O request that reads the signature |
28
+
29
+ An absent, `null` or empty map is not sent.
30
+
31
+ A request carrying `resources` can be large, so every route gives it the 20-minute execute ceiling: `validate` and `validateFiles` have it by default (a caller's `timeoutMs` still overrides it), and `start` and the crate routes raise their shorter default to it for a request that carries a file. On the hosted API the gateway caps a request at about 30 seconds whatever the client allows.
32
+
33
+ ## The key is the path the step names
34
+
35
+ Each key is the file's path relative to the bundle, written with forward slashes, exactly as the step's `template_file` writes it: `templates/invoice.docx`. The runner collapses `.` segments and repeated slashes, so `./templates/invoice.docx` names the same file, and the SDK sends the key as you gave it. Every bundle of a request is read as sitting in one directory, so a step in `steps/print.mthds` that names `templates/invoice.docx` reads the file sent under `templates/invoice.docx`, wherever it sat on your disk.
36
+
37
+ ## The value is the file's bytes
38
+
39
+ A value is a `Uint8Array` (a Node `Buffer` is one), an `ArrayBuffer` or a `Blob`, the byte forms `uploadFile` takes. The SDK sends each in standard base64 with padding. A string is refused: it would have to be a path the SDK reads or base64 it decodes, and it is neither. The SDK reads no file because a request named it, which keeps a server that sends a request on a caller's behalf from reading its own files; read the file yourself and pass its bytes.
40
+
41
+ ## What the SDK refuses before any request
42
+
43
+ Each of these is a `RequestArgumentError`, `input` and not retryable, thrown before anything is encoded or sent:
44
+
45
+ - **A key that cannot name a file inside the bundle**: an empty path or one of nothing but whitespace, as Python counts it, a backslash, a colon, a NUL character, an absolute path, a `..` segment, or a path that names no file (`./`). It is the runner's own rule, which it answers with a `422` `InvalidResourcePath`.
46
+ - **Two keys that name one file** once `.` segments and repeated slashes collapse, such as `a.docx` and `./a.docx`.
47
+ - **A value that is not bytes**, and a `resources` that is not a plain object mapping paths to bytes: a `Map`, an array or a class instance is refused rather than read as carrying no file (pass `Object.fromEntries(map)`).
48
+ - **`resources` in `extra`**: it is a named option, so the passthrough cannot carry it past these checks.
49
+ - **A pairing the runner refuses**, on the routes where the SDK already mirrors the runner's pairings. On `execute` and `start`, `resources` beside a `method_ref`, whose files are its package's own, or with no source at all for them to accompany. On `validate`, `resources` beside a `{ method_ref }`. A hosted `method_id` without inline source is left to the server, since the platform resolves it into the bundle the runner reads the files with. `prepareInputs` takes `resources` beside inline `files` only, and refuses them beside a `method_ref` or a `method_id` with an `InputPreparationError`.
50
+
51
+ The crate routes post their request as given, apart from encoding `resources`, and leave their pairings to the server, which answers `resources` beside a `method_ref` with a `422` `ValidationError`.
52
+
53
+ The sizes are left to the server, since a deployment configures them.
54
+
55
+ ## What the server refuses
56
+
57
+ Every refusal is an `ApiResponseError`, `input` and not retryable; branch on its `errorType`:
58
+
59
+ | Status | `errorType` | When |
60
+ | --- | --- | --- |
61
+ | `422` | `InvalidResourcePath` | A key that cannot name a file inside the bundle, two keys for one file, or, on a run route, two different files under one path, one sent in `resources` and one shipped in a method bundle. |
62
+ | `422` | `BundleResourceNotNamedError` | A file no step of the bundle names, on every route, the diagnostic ones included; its detail lists the unnamed paths. A misspelled key fails here rather than leaving its step without its file. |
63
+ | `422` | `ValidationError` | `resources` beside a `method_ref`, or on a run route with no bundle. |
64
+ | `413` | `PayloadTooLarge` | More files than the deployment allows (32 by default), a file over its per-file limit (5 MiB by default), or more bytes in all than its total (10 MiB by default), counting the files a method bundle ships for its steps. |
65
+ | `413` | `BundleResourcesTooLargeError` | The `.html` template files, which travel inline with the method wherever it runs, over 512 KiB in all. An office template file is not counted, since a run handed to a remote orchestrator carries it by reference to the run's storage. |
66
+ | `400` | `InvalidBase64` | A value that is not base64, which a request this SDK encoded never carries. |
67
+
68
+ ## A step whose file was not sent
69
+
70
+ A step that names a template file the request did not send cannot load. On `validate`, `pipeIo`, `resolve` and `codegen` that is an `is_valid: false` verdict whose validation error says the file was not supplied with its bundle and names the key it is expected under; `prepareInputs` throws it as a `MethodLoadError`. On `execute` and `start` it is a `422` `ValidateBundleError`, whose `validationErrors` say the same.
71
+
72
+ ## The template check's warnings
73
+
74
+ A dry run hands an office template file it was sent to the document engine's template check. An error finding fails the step's dry run, and so the verdict, as a `dry_run` validation error on the step. A warning finding, such as a field of the template that matches no input, rides the valid verdict's `warnings` as a `template_check_warning` item:
75
+
76
+ | Member | Value |
77
+ | --- | --- |
78
+ | `category` | `dry_run` |
79
+ | `error_type` | `template_check_warning` |
80
+ | `pipe_code`, `domain_code` | The `PipeDocGen` step |
81
+ | `template_file` | The template file as the step names it; `null` for a step of the server's own library, whose path is never shown |
82
+ | `template_location` | The place in the file the finding names, in the engine's words (a defined name, a table, a cell, a shape, a tag); `null` when it names none |
83
+ | `message` | The finding in words |
84
+
85
+ The warnings come in families, always in this order: `optional_force_redundant`, `input_presence_vacuous`, the `hint_*` lints, then `template_check_warning`. None of them flips `is_valid`.
86
+
87
+ ```ts
88
+ if (verdict.is_valid) {
89
+ for (const warning of verdict.warnings) {
90
+ if (warning.error_type === "template_check_warning") {
91
+ console.warn(`${warning.template_file ?? "?"} ${warning.template_location ?? ""}: ${warning.message}`);
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## Method bundles
98
+
99
+ On `execute` and `start`, a method bundle sent as a zip (`bundle_b64`) may ship a template file beside the `.mthds` file whose step names it, as it sits on disk, and the runner reads it as if it had come in `resources`, under the same limits. A file both shipped and sent must hold the same bytes. The `files` map of a method bundle carries text, so it can ship an `.html` template but not an office one, which goes in `bundle_b64` or in `resources`.
100
+
101
+ ## The `.mthds` check hook
102
+
103
+ The post-edit hook this package builds sends the template files of the bundle it validates: see [`hook-bundle.md`](./hook-bundle.md#what-the-hook-does-with-a-file).
104
+
105
+ ## Which servers read them
106
+
107
+ A `pipelex-api` runner reads `resources` from the first pipelex release after 0.84.0. The hosted API reads them once its platform forwards them to a runner on such a release. A runner that predates the field ignores it, so a step that names a template file fails to load there, the verdict saying the file was not supplied.
package/docs/cli.md CHANGED
@@ -75,6 +75,7 @@ Every error goes to stderr, its first line `Error: <sentence>` and the lines und
75
75
  - **An answer the command cannot read**, a 2xx whose body the SDK could not read into what the route answers: `Error: the API's answer could not be read (status <n>).`, then `Reason: <the SDK's message>`, which names the route and what could not be read (`API POST /v1/start answered 202 with a body that is not JSON`), since such an answer carries no problem document, and `Request id: <id>` when its `X-Request-ID` header carries one.
76
76
  - **A failed run**: `Error: run <id> ended with status <STATUS>.`, then `Reason: <the report's message>` (the problem's own sentence when the run has no report) and `Next step: <the report's advised action>`.
77
77
  - **A method that does not load**: `Error: the method does not load.`, then one line per validation error that carries a string category and message, as ` - <file>: <message>`, or, when there is none, ` - <the answer's own message>`. It reads the same whichever request found it: the pipe I/O call of `--inputs-template` and `script`, or the input preparation of a run with inputs.
78
+ - **A pipe I/O answer the command cannot use** for `--inputs-template` and `script`: `Error: the API's pipe I/O answer says neither that the method loads nor why it does not.` when it carries no verdict, `Error: the API's pipe I/O answer does not describe the pipe it selected.` when it names no pipe, carries no input form or describes another pipe, and `Error: the API's pipe I/O answer describes the pipe it selected with a malformed input form.` when the selected pipe's entry is malformed where input preparation or the template reads it, such as an enum with no list of `choices`, a `concept_ref` that is not a string or a fixed list counting fewer than two elements, or names a kind the SDK does not know, rather than crashing or rendering a template under made-up keys. Neither command says where the input form is malformed, since the Python client refuses it whole when it parses the answer, and the Python command refuses more, such as a member the standard does not define.
78
79
  - **A value at a file input that is no file**: `Error: the inputs hold a value at a file input that cannot be read as a file.`, then `Reason: <the SDK's message>` and the forms a file input takes. An unreadable local path reads `Error: Local file cannot be read: "<path>" (<reason>).`
79
80
  - **An unreachable API**: `Error: could not reach the Pipelex API at <base URL> (<code>).`
80
81
  - **A failed upload** of a file the inputs name: `Error: the upload of "<file>" failed (status <n>).`, the status left out when no answer came back, then `Reason: <the SDK's message>`, which says what the status means for an upload (a file past the size limit, a deployment without file upload, a credential refused), and `Next step:` and `Request id:` when the API's answer carries them. It covers the SDK's `RejectedAssetError`, `UploadAuthenticationError`, `UnsupportedUploadCapabilityError` and `UploadTransportError`, each of which names the file it was sending. It exits `1`, and no run starts.
@@ -18,6 +18,7 @@ All three take the same closure selector — inline `files`, a `method_ref`, or
18
18
  interface CrateRequestBase {
19
19
  files?: MthdsFileItem[]; // [{ content, source? }] — `source` is the provenance label
20
20
  method_ref?: string; // address form server-resolved (pipelex-api >= 0.21.0); registry form reserved → 501
21
+ resources?: BundleResources | null; // beside `files`: the files the steps read, keyed by the path a step names
21
22
  }
22
23
 
23
24
  interface PipelexHostedToolingExtensions {
@@ -39,7 +40,9 @@ Supplying **no** selector or **more than one** is a request-shape `422` — the
39
40
 
40
41
  The old advice to expand a stored method client-side (`resolve({ files: await client.getMethodClosure(methodId) })`) remains valid — `getMethodClosure` stays public as the local expansion utility, and it is what a caller uses against a bare runner, which has no catalog to resolve an id against.
41
42
 
42
- A `method_ref` makes the server fetch the repository before it answers, so the client gives a call carrying one a fetch-sized budget (three minutes) instead of the 30-second default. On the hosted API the gateway caps every request at about 30 seconds whatever the client allows, so a cold `method_ref` clone can answer a `502`; retrying clears it once the runner has cached the clone.
43
+ Beside inline `files`, `resources` carries the files the closure's steps read, such as a `PipeDocGen` step's `template_file`, keyed by the path the step names, each as its bytes. It is the one member the client does not post as given: it encodes the bytes into the wire's base64 map, after refusing a key the runner's path rule refuses. A step whose template file was not sent makes the closure invalid, a `200` verdict saying so; `resources` beside a `method_ref` is the server's `422`. The rules, the limits and the refusals are in [`bundle-resources.md`](./bundle-resources.md).
44
+
45
+ A `method_ref` makes the server fetch the repository before it answers, so the client gives a call carrying one a fetch-sized budget (three minutes) instead of the 30-second default. A call carrying `resources` gets the 20-minute execute ceiling that `start` and `validate` give the same files, because uploading them can take longer than the default on its own. On the hosted API the gateway caps every request at about 30 seconds whatever the client allows, so a cold `method_ref` clone can answer a `502`; retrying clears it once the runner has cached the clone.
43
46
 
44
47
  ## `resolve` — the normalized crate
45
48
 
@@ -135,6 +138,7 @@ Only a **no-verdict** condition, as the typed `ApiResponseError` — branch on i
135
138
  | Status | Cause |
136
139
  | ------------- | --------------------------------------------------------------------------------------------------------- |
137
140
  | `422` | Request shape: no closure selector or more than one, an over-limit file, an unknown `kind`/`target`, a `pipe_ref` on `kind: "types"`, a fetched package with no `.mthds` file, a stored method with no MTHDS source, a `method_id` whose version suffix is malformed. |
141
+ | `422` / `413` / `400` | On `resources`: `InvalidResourcePath`, `BundleResourceNotNamedError` (a file no step names), `ValidationError` (beside a `method_ref`), `PayloadTooLarge`, `BundleResourcesTooLargeError`, `InvalidBase64` — see [`bundle-resources.md`](./bundle-resources.md#what-the-server-refuses). |
138
142
  | `422` | On `pipeIo`, a pipe selection the route refuses: `errorType` `EntryPipeNotFoundError` for a `pipe_ref` that names no pipe or a method with no entry pipe, `EntryPipeAmbiguousError` for a code that matches pipes in several domains or several `main_pipe` declarations. The `serverMessage` names the candidates where there are some. |
139
143
  | `404` | Unknown or foreign-org `method_id` (indistinguishable by design); no package at a `method_ref` address. |
140
144
  | `404` | `code` `method_version_not_found`: a `mt_…@<n>` naming a version the method never published. |
package/docs/errors.md CHANGED
@@ -51,7 +51,7 @@ On an error from a request that may have started a run, `retryable` is `false` w
51
51
  | `RunLifecycleUnavailableError` | `config` | no | The base URL points at a bare runner without the run lifecycle. |
52
52
  | `MissingMainStuffError` | `runtime` | no | The API broke its own contract on a completed run. |
53
53
  | `PagingNotTerminatingError` | `runtime` | no | A server minting cursors forever; `pageLimit` is the ceiling `iterateMethods` stopped at. |
54
- | `InputPreparationError` and `EmptyMethodSourceError`, `InvalidLocalSourceError`, `InvalidInputValueError`, `UploadNotAllowedError`, `MethodLoadError`, `RejectedAssetError` | `input` | no | The asset, the inputs or the method selector must change. The family's base takes an optional `verdict` in its options, so a subclass of your own can declare another. |
54
+ | `InputPreparationError` and `EmptyMethodSourceError`, `InvalidLocalSourceError`, `InvalidInputValueError`, `UploadNotAllowedError`, `MethodLoadError`, `RejectedAssetError` | `input` | no | The asset, the inputs or the method selector must change. The family's base takes an optional `verdict` in its options, so a subclass of your own can declare another; `prepareInputs` declares `runtime` for a pipe I/O answer that breaks the route's contract: no boolean `is_valid`, an `input_form` that does not describe the pipe it selected, or a descriptor that is malformed or names a kind this SDK does not know. |
55
55
  | `UnsupportedUploadCapabilityError`, `UploadAuthenticationError` | `config` | no | The deployment or the credential must change. |
56
56
  | `UploadTransportError` | the cause's, else by `code` | the cause's, else by `code` | `uploadFile` wraps the `ApiResponseError` or `ApiUnreachableError` the client threw, and takes its verdict, so a wrapped `402` is `config` and not retryable. Without such a cause, which is always the case from `uploadWithGrant`: `timeout`, `storage_timeout` and `conflict` are `runtime` and retryable, `conflict` because storage documents its `409 ConditionalRequestConflict` as retryable and the grant is not spent by it (a spent grant is the `412` of a `RejectedAssetError` with code `grant_used`); `server_error` is `runtime`, and retryable when [the fallback table](#a-refused-requests-verdict) would call its `status` retryable, any `5xx` but the `501` storage answers for a request it does not implement, or when it carries no status; `unexpected` is `runtime`, and retryable when that table would call its `status` retryable, which is the `408` or `429` storage answers when it times out or throttles the request rather than refusing the file, and not retryable for any other status or none; `unreachable` is `config` and retryable; `redirected` is `config` and not retryable; `invalid_grant_url` is `runtime` and not retryable. |
57
57
  | `ArtifactOperationError`, `ScopeUnavailableError` | `runtime` | no | The family's base also takes an optional `verdict`: the SDK declares `input` where it refuses an argument (a `scope`, a bound, a location) and `config` where the environment refuses the download (a runtime with no filesystem, a directory that cannot be created). |
@@ -293,6 +293,7 @@ A method refused for what it says, rather than for how its run went, comes back
293
293
  | `missing_concept_code`, `missing_pipe_code`, `declared_concepts` | The concept or pipe a reference names that the bundle does not declare, and the concepts the domain does declare. |
294
294
  | `variable_names` | The input variables an input error concerns. |
295
295
  | `model_reference`, `model_type`, `suggestions` | On an `unknown_model` item: the model reference exactly as the author wrote it, the kind of model the field takes (`llm`, `img_gen`, …), and the close matches, each spelled as a reference the field accepts. |
296
+ | `template_file`, `template_location` | On a `template_check_warning` item, which rides the valid verdict's `warnings[]`: the template file as the `PipeDocGen` step names it, `null` for a step of the server's own library, and the place in it the template check's finding names, in the engine's own words, `null` when it names none ([`bundle-resources.md`](./bundle-resources.md#the-template-checks-warnings)). |
296
297
  | `suggested_fix` | The runner's deterministic correction, when it has one: a `description` in words, `ops` that patch the `.mthds` document by table and key rather than by text, `safety` (`safe` to apply without asking, or `unsafe`) and the `source` file the ops apply to. An unknown model with exactly one close match carries a `rename-model` fix that remaps the reference to it. |
297
298
 
298
299
  ```ts
@@ -306,6 +307,6 @@ if (!report.is_valid) {
306
307
  }
307
308
  ```
308
309
 
309
- **The shape is `mthds`'s.** The standard's client declares the same item and the same fix vocabulary (`SuggestedFix`, `FixOp` and its arms, `FixSafety`, `FixOpKind`, `FixValue`), member for member under the same names, and this SDK exports every one of those names, so a consumer reads one vocabulary whichever client handed it the item. `@pipelex/sdk` imports `mthds` only through `mthds/protocol`, and `mthds` exports these declarations from its package root, so they are restated in `src/models.ts` and a type-level test pins them to the standard's: a member added on one side and not the other fails `make check`.
310
+ **The shape is `mthds`'s.** The standard's client declares the same item and the same fix vocabulary (`SuggestedFix`, `FixOp` and its arms, `FixSafety`, `FixOpKind`, `FixValue`), member for member under the same names, and this SDK exports every one of those names, so a consumer reads one vocabulary whichever client handed it the item. `@pipelex/sdk` imports `mthds` only through `mthds/protocol`, and `mthds` exports these declarations from its package root, so they are restated in `src/models.ts` and a type-level test pins them to the standard's: a member added on one side and not the other fails `make check`. The one difference the test allows is `template_file` and `template_location`, which the runner's item carries and the `mthds` this SDK depends on does not declare yet; the test fails the day `mthds` declares them, so the allowance goes then.
310
311
 
311
312
  **An unset member can be `null`.** That is the one way this declaration differs from the standard's. The verdict's valid arm, which carries the advisory `warnings[]`, sends an unset member as an explicit `null`, where every other channel leaves the key out, so every optional member is typed `| null`. Read a member with a truthiness check or `??`, never with `=== undefined`.
@@ -6,6 +6,8 @@
6
6
 
7
7
  Each file goes through three stages: a local lint that blocks on any diagnostic, a local format that rewrites the file in place when its layout changes, and a validation by the hosted API (`POST /v1/validate`) that blocks on an invalid verdict and adds a non-blocking note about pending pipe signatures. The hook fails open: when the engine cannot load, when `PIPELEX_API_KEY` is unset or the API cannot be reached, the stage that could not run passes silently and the others stand. When several files are checked, one block among them wins and the notes are joined, and each file is read when its turn comes, so an edit made to it while an earlier file was being validated is checked rather than overwritten. The hook always exits 0, since its verdict is on stdout.
8
8
 
9
+ The validation sends the bundle around the file: every `.mthds` file under the edited file's directory, skipping hidden and excluded directories, within a cap on their number and size. It sends with them the template files their `PipeDocGen` steps name in `template_file`, each read beside the `.mthds` file whose step names it and sent under the path the step names, as the request's `resources` ([`bundle-resources.md`](./bundle-resources.md)), so a bundle that fills a template validates as it would load from disk, its template checked by the dry run. A template file that is not there beside any `.mthds` file naming it is not sent, and the verdict then says the step's file was not supplied, which blocks until it is written. The request keys each file by the path a step names alone, so the hook sends a bundle only when that one file per path is what loading every `.mthds` file from disk would find. Validation passes silently, as when the API cannot be reached, when a template file leads out of the edited file's directory through a link, which the hook never reads, when one path names two different files beside two `.mthds` files, or a file beside one and nothing beside the other, and past the server's default limits: 32 template files, 5 MiB for one of them or 10 MiB in all. Every size is checked before a file is read, so the hook never holds more than those limits.
10
+
9
11
  ## Which file it checks
10
12
 
11
13
  | Platform | Where the edited file comes from |
@@ -117,6 +117,8 @@ Exactly one per call. The type pins the other two to `never`, so a second select
117
117
  | `method_ref` | a published method's address, `github.com/<owner>/<repo>[/<selector>][@<tag>]` | the runner, server-side (pipelex-api >= 0.21.0 fetches the repository at the tag) |
118
118
  | `method_id` | a stored method's catalog id (`mt_…`, `mt_…@<n>` or `mt_…@draft`) | the hosted platform, which injects the `.mthds` files of the version it names before the runner sees the request |
119
119
 
120
+ Inline `files` may bring `resources`, the files the closure's steps read, such as a `PipeDocGen` step's `template_file`, keyed by the path the step names, each as its bytes ([`bundle-resources.md`](./bundle-resources.md)). They ride the one pipe I/O request, which loads the closure and fails to load it, as a `MethodLoadError`, when a step's template file was not sent. A published or stored method's files are its own, so `resources` beside `method_ref` or `method_id` is an `InputPreparationError` before any request.
121
+
120
122
  Nothing is expanded client-side: `method_id` here is a **pass-through**, the same rule every other id-taking operation follows. `getMethodClosure` stays available for callers that want the files in hand, but it is no longer a step on the way to preparing inputs.
121
123
 
122
124
  #### Where the signature comes from
@@ -124,12 +126,12 @@ Nothing is expanded client-side: `method_id` here is a **pass-through**, the sam
124
126
  One `POST /v1/pipe-io` per call, whatever the selector, through the client's own `pipeIo` (see [crate-routes.md](./crate-routes.md#pipeio--a-methods-io-artifacts-without-a-validation)):
125
127
 
126
128
  ```
127
- pipeIo({ <files | method_ref | method_id>, pipe_ref? })
129
+ pipeIo({ <files [+ resources] | method_ref | method_id>, pipe_ref? })
128
130
  ```
129
131
 
130
132
  The route resolves the closure, **selects the pipe**, and answers with that pipe's **input-form descriptor** under the qualified ref it resolved. It runs no dry run, so preparing inputs costs one static load rather than a validation of the whole method. It also refuses nothing for a pending signature elsewhere in the method: preparation needs a pipe's *declared* inputs, and whether the method runs is the run's verdict, not preparation's. An `is_valid: false` verdict still means the closure does not load, which is a preparation failure.
131
133
 
132
- A `method_ref` makes the server clone a repository first, so the call gets the crate routes' fetch-sized budget. On the hosted API the gateway caps a request at about 30 seconds whatever the client allows, so a cold clone can answer a `502` `ApiResponseError`; a retry clears it once the runner has cached the clone.
134
+ A `method_ref` makes the server clone a repository first, so the call gets the crate routes' fetch-sized budget, and `resources` beside inline `files` get the 20-minute execute ceiling, since uploading them can take longer than the default on its own. On the hosted API the gateway caps a request at about 30 seconds whatever the client allows, so a cold clone can answer a `502` `ApiResponseError`; a retry clears it once the runner has cached the clone.
133
135
 
134
136
  `prepareInputs` needs an API that serves `POST /v1/pipe-io`. Against one that does not, the route's `404` (a runner) or `403` (a hosted origin whose gateway does not list it yet) propagates as an `ApiResponseError`. It also needs the route's pipe-selection refusals typed, as step 3 of "Pipe selection" below describes, which `pipelex-api` does from v0.33.1: v0.33.0, the release that added the route, typed them as the generic `ValidationError`, so against it a refused selection reaches the caller as that `ApiResponseError` rather than an `InputPreparationError`.
135
137
 
@@ -153,7 +155,7 @@ client.prepareInputs({ method_id, inputs, allowUploads: false }) → PreparedInp
153
155
 
154
156
  - a value at a file position that would need an upload — a local path, a `data:` URL, bytes (`Blob`, `File`, `ArrayBuffer`, `Uint8Array`), whether bare, inside canonical `{ url }` content or inside an explicit envelope — throws `UploadNotAllowedError` **before** anything is read from disk, decoded or sent to the upload route. A `data:` URL is refused without being decoded, so a malformed one is refused the same way;
155
157
  - `http(s)` URLs and `pipelex-storage://` references pass through and are rewritten exactly as in the default mode, and `uploads` is always empty;
156
- - a value no file input takes (a number, an object that is neither bytes nor `{ url }` content) is still an `InvalidInputValueError`, as in the default mode, since it would need no upload, only a correction;
158
+ - a value no file input takes (a number, an object that is neither bytes nor `{ url }` content) is still an `InvalidInputValueError`, as in the default mode, since it would need no upload, only a correction. It names the input the same way, and its message offers only the two forms this mode takes, an `http(s)` URL or a `pipelex-storage://` reference;
157
159
  - everything that is not a file position is untouched, as always: the walk is the same descriptor walk.
158
160
 
159
161
  The error names the input as `inputName` and where inside it the value sits as `inputPath`: the input's name for a top-level file, then `.field` for each declared field and `[index]` for each list element (`dossier.attachments[1]`). An explicit envelope adds nothing to the path, since its `content` is the input's value. `valueKind` says what the value is, `local_path`, `data_url` or `bytes`, so a consumer can tell its caller what to send instead; the value itself is never in the message or on the error, since it is the caller's data. The option is `true` when omitted, must be a boolean when given — anything else is refused with an `InputPreparationError` before any request, since reading a stray `"false"` as permission would upload exactly what the caller meant to keep — and is not sent to the server. The option covers `prepareInputs` alone: `uploadFile` is an upload by definition.
@@ -212,6 +214,8 @@ The walk is discriminated on the node's `kind`:
212
214
  | `list` | Walks `item` against each element of an array value. |
213
215
  | everything else (`text`, `prose`, `date`, `number`, `boolean`, `enum`, `unknown`) | Passes through at any depth. |
214
216
 
217
+ The table covers the standard's whole `kind` vocabulary at the `mthds` the SDK pins, and is the walk's own: a newer `mthds` installed beside the SDK, defining a kind this table lacks, does not widen it, and moving the pin onto one fails the SDK's build until the walk says what the new kind is. **A descriptor the walk cannot read is refused, never walked around**: an entry that is not an object, a `fields` that is not a list, a field with no string `name`, a node with no string `kind`, an `object` node with no list of `fields`, a `list` node whose `item` is not a node, or a `kind` outside the vocabulary is an `InputPreparationError` with a `runtime` verdict (see "[Error and capability behavior](#error-and-capability-behavior)"). Passing such a node through would be safe only if nothing uploaded: under the default mode a node the SDK cannot read may be a file position, and a newer standard's kind may be a file kind, so the caller's local path would travel to the runner verbatim. Members the walk does not read, such as `concept_ref`, `presence` or `hints`, are not checked. `pipelex-sdk` refuses every one of these answers too, and more, since its client parses the whole answer into the standard's closed models, which also refuse a malformed member the walk never reads; the case file both suites run, `tests/fixtures/malformed-input-forms.json`, holds the two to the answers both refuse. The check is exported as `inputFormFault(entry)`, which returns the first fault of one pipe's entry (`input_form[pipe_ref]`) as `{ path, problem, message }`, or `undefined` when the walk can read it, for a caller that reads an entry itself, to render a form or a template, as the `pipelex-sdk` command's `--inputs-template` does before it checks the members its template reads beyond the walk.
218
+
215
219
  Two consequences worth stating outright, because they are what the descriptor buys:
216
220
 
217
221
  - **Whether a concept is native or refines `Document` / `Image` is the descriptor's statement** (`refines`), never a shape test. A concept refining `native.Document` arrives as `kind: "document"` and is prepared as one.
@@ -247,7 +251,7 @@ The contract distinguishes these semantic outcomes, each a typed subclass of `In
247
251
  - **empty method source** (`EmptyMethodSourceError`, carries `methodId`) — `getMethodClosure` found the stored method but its `mthds` source parses to nothing (the row exists, no runnable source yet). Distinct from the `getMethod` `404` for an unknown/foreign id, which stays an `ApiResponseError`. `prepareInputs` never raises it: it hands the id to the server, which answers a sourceless method with a `422`;
248
252
  - **a method that does not load** (`MethodLoadError`) — the pipe I/O answer's `is_valid: false` arm. Its message names the first validation item's message, else the answer's own `message`; `validationErrors` holds the answer's items, each kept when it carries a string `category` and `message`, and `serverMessage` the answer's own `message` when it is a string, so a consumer lists every item rather than reading the first from a sentence. The method must change, not the inputs;
249
253
  - **invalid local source** (`InvalidLocalSourceError`) — missing, unreadable, or a path string outside Node;
250
- - **invalid input value** (`InvalidInputValueError`) — a value at a file input that cannot be turned into a file to upload: a `data:` URL that does not decode (no comma, bad base64, bad percent-encoding), or a value of a type no file input takes, such as a number or an object that is neither bytes nor `{url}` content. It is raised while the inputs are read, before anything is uploaded. With `InvalidLocalSourceError`, it is how a consumer tells the inputs' own mistakes from a method that does not load, though all three are `InputPreparationError`s with the same verdict;
254
+ - **invalid input value** (`InvalidInputValueError`, carries `inputName` and `inputPath`) — a value at a file input that cannot be turned into a file to upload: a `data:` URL that does not decode (no comma, bad base64, bad percent-encoding), or a value of a type no file input takes, such as a number or an object that is neither bytes nor `{url}` content. It is raised while the inputs are read, before anything is uploaded. It names the input and the path inside it as `UploadNotAllowedError` does (`Input "dossier.attachments[1]" holds a number, which no file input takes: …`), describes the value by its kind and never quotes it, and lists the forms a file input takes in the mode it ran in. With `InvalidLocalSourceError`, it is how a consumer tells the inputs' own mistakes from a method that does not load, though all three are `InputPreparationError`s with the same verdict;
251
255
  - **upload not allowed** (`UploadNotAllowedError`, carries `inputName`, `inputPath` and `valueKind`) — under `allowUploads: false`, a value at a file input that would need an upload: a local path, a `data:` URL or bytes. It is raised before anything is read, decoded or uploaded (see "[Preparing without uploads](#preparing-without-uploads)");
252
256
  - **rejected asset** (`RejectedAssetError`) — the server refused it (e.g. a `413` past the service-defined size cap — see "Storage policy" — surfaced as a clear rejection with `code` `too_large`, not a raw transport error), or storage refused an upload with a grant (a used or expired grant, or a file that differs from what the grant signed — see the table above for each `code`);
253
257
  - **unsupported server capability** (`UnsupportedUploadCapabilityError`) — the configured deployment has no upload route;
@@ -256,7 +260,7 @@ The contract distinguishes these semantic outcomes, each a typed subclass of `In
256
260
 
257
261
  **Which file.** Every upload failure `uploadFile` raises names the file it was sending, as it sent it: `RejectedAssetError`, `UploadAuthenticationError`, `UnsupportedUploadCapabilityError` and `UploadTransportError` each carry `filename`, so a consumer that prepared several files says which one failed without parsing a message. `uploadWithGrant` sends a grant's file, not a named one, and leaves `filename` undefined on the `UploadTransportError` it raises.
258
262
 
259
- Each of these carries the verdict every SDK error does, `retryable` and `errorDomain` ([errors.md](./errors.md#each-classs-verdict)): the family's own is `input` and not retryable, an unsupported deployment and a refused credential are `config`, and a transport failure takes the verdict of the error it wraps, so a `402` plan refusal wrapped by `uploadFile` is `config` and not retryable, or, wrapping none, the verdict its `code` gives (`timeout`, `storage_timeout` and `conflict` retryable, `server_error` retryable unless storage answered a `501`, `unreachable` and `redirected` the environment's). A pipe I/O answer whose `is_valid` is not a boolean, or whose `input_form` does not describe the pipe it selected, is a plain `InputPreparationError` with a `runtime` verdict, since the route broke its own contract.
263
+ Each of these carries the verdict every SDK error does, `retryable` and `errorDomain` ([errors.md](./errors.md#each-classs-verdict)): the family's own is `input` and not retryable, an unsupported deployment and a refused credential are `config`, and a transport failure takes the verdict of the error it wraps, so a `402` plan refusal wrapped by `uploadFile` is `config` and not retryable, or, wrapping none, the verdict its `code` gives (`timeout`, `storage_timeout` and `conflict` retryable, `server_error` retryable unless storage answered a `501`, `unreachable` and `redirected` the environment's). A pipe I/O answer whose `is_valid` is not a boolean, whose `input_form` does not describe the pipe it selected, or whose descriptor for that pipe is malformed where the walk reads it or names a kind this SDK does not know, is a plain `InputPreparationError` with a `runtime` verdict, not retryable, since the route broke its own contract. The descriptor is checked whole, once, before any caller value is read or anything uploaded, so the refusal does not depend on which inputs the caller supplied, and its message names the pipe, where in the descriptor the fault sits (`fields[3].item`) and what was found there.
260
264
 
261
265
  All preparation failures are raised **before any run is created**.
262
266
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/sdk",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "TypeScript SDK for the Pipelex hosted API — execute methods, manage runs, and call the product surface.",
5
5
  "license": "MIT",
6
6
  "repository": {