@pipelex/sdk 0.23.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.
@@ -32,7 +32,7 @@ The package is ESM-only (`"type": "module"`, `dist/index.js` built by `tsc` with
32
32
 
33
33
  Condition order in `exports` matters: `types` is listed first (TS/Node resolution convention — the type-checker looks at the first matching condition), then `import`, then `default` as the CJS fallback.
34
34
 
35
- The map has a second entry, **`@pipelex/sdk/upload`** (`dist/upload-grant.js`), the browser-safe one. The main entry is Node-first — `upload.ts` names `node:fs/promises` and `artifacts.ts` names `undici`, so a browser-targeting bundler must mark them external — because the client holds an API key and belongs on a server. The subpath carries only what a page needs to store a file with an upload grant: `uploadWithGrant`, the grant types and the three error classes it raises. Its runtime import graph is `errors.ts` and `mthds/protocol`, nothing else, and the unit suite bundles it for the browser with esbuild, nothing marked external, so a Node-only module becoming reachable fails a test rather than a consumer's build. Both entries load the same `dist/errors.js` and `dist/upload-grant.js`, so the function and the error classes are identical whichever entry a consumer imports them through. Whether the main entry should become browser-safe as a whole is a separate question, because it touches how path strings are read.
35
+ The map has a second entry, **`@pipelex/sdk/upload`** (`dist/upload-grant.js`), the browser-safe one. The main entry is Node-first — `upload.ts` names `node:fs/promises` and `artifacts.ts` names `undici`, so a browser-targeting bundler must mark them external — because the client holds an API key and belongs on a server. The subpath carries only what a page needs to store a file with an upload grant: `uploadWithGrant`, the grant types and the three error classes it raises. Its runtime import graph is `errors.ts`, `timers.ts` and `mthds/protocol`, nothing else, and the unit suite bundles it for the browser with esbuild, nothing marked external, so a Node-only module becoming reachable fails a test rather than a consumer's build. Both entries load the same `dist/errors.js` and `dist/upload-grant.js`, so the function and the error classes are identical whichever entry a consumer imports them through. Whether the main entry should become browser-safe as a whole is a separate question, because it touches how path strings are read.
36
36
 
37
37
  ## Wire conventions
38
38
 
@@ -86,13 +86,14 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
86
86
  - `product-models.ts` — the snake_case wire shapes for the hosted-product management routes (profile, methods catalog, organizations, billing, API keys, gateway key, onboarding, storage, runs list/detail/update). Each interface models only the fields the product actually consumes — no speculative mirrors.
87
87
  - `method-source.ts` — `methodSourceToContents`, the canonical parser turning a stored method's polymorphic `mthds` source (raw single-bundle text XOR a JSON `{name, content}[]` file array) into a flat list of file contents. A verbatim port of the platform's `_method_source_to_contents` so the SDK and runtime read a stored source identically; exported from the barrel and consumed by `getMethodClosure`. See [input-preparation.md](./input-preparation.md#getmethodclosure--the-explicit-expansion-utility).
88
88
  - `codegen-check.ts` — `runCodegenCheck`, the **pure** offline drift check over a committed codegen tree: the stamp grammar, the `codegen.lock` parser, the hash, and the drift taxonomy, ported from pipelex's `codegen/check.py` so a verdict computed here equals the one `pipelex codegen check` computes over the same bytes (the drift `detail` sentences included, verbatim). It touches neither the client nor `mthds/protocol` — its only import is `smol-toml` — and it hashes through WebCrypto rather than `node:crypto` so it adds no Node builtin to the barrel's import graph (`upload.ts` still names `node:fs/promises`, so a browser-targeting bundler must mark `node:*` external). It raises `CodegenLockError`, which extends `Error` rather than the protocol base. No filesystem, no fetch, no API key — the caller walks its own tree and hands in the text. See [crate-routes.md](./crate-routes.md#the-offline-check--runcodegencheck).
89
- - `upload-grant.ts` — `uploadWithGrant`, the sender of an upload grant (`requestUploadGrant`, `POST /v1/upload/grant`): the file as the raw body of a `PUT` to the grant's URL with its signed headers unchanged, redirects refused, and storage's refusals mapped onto the input-preparation errors (`RejectedAssetError` with a `code` for a `4xx` such as the `412` of a used grant or the `403` of a signature mismatch, `UploadTransportError` for a `5xx`, storage's `RequestTimeout`, a redirect or no response). It is the browser-safe `@pipelex/sdk/upload` entry, so it imports the error classes and the wire types only, never the client. See [input-preparation.md](./input-preparation.md#uploading-with-a-grant--requestuploadgrant--uploadwithgrant).
89
+ - `upload-grant.ts` — `uploadWithGrant`, the sender of an upload grant (`requestUploadGrant`, `POST /v1/upload/grant`): the file as the raw body of a `PUT` to the grant's URL with its signed headers unchanged, redirects refused, the whole exchange bounded by a time limit scaled by the file's size unless the caller passes `timeoutMs`, only the first 16 KiB of storage's error body read, and storage's refusals mapped onto the input-preparation errors (`RejectedAssetError` with a `code` for a `4xx` such as the `412` of a used grant or the `403` of a signature mismatch, `UploadTransportError` with a `code` for the time limit running out, a `5xx`, storage's `RequestTimeout`, a `409` conflict, a redirect or no response). It is the browser-safe `@pipelex/sdk/upload` entry, so it imports the error classes, the timer bound in `timers.ts` and the wire types only, never the client. See [input-preparation.md](./input-preparation.md#uploading-with-a-grant--requestuploadgrant--uploadwithgrant).
90
90
  - `artifacts.ts` — the artifact stack, the download twin of `prepareInputs`, in layers: `locateArtifacts` (a pure, exported walk for `pipelex-storage://` references, each with every `$`-rooted path it sits at) and `collectArtifacts` (the same walk's bare references), `resolveArtifacts` (the bulk resolve route over a whole list, chunked at its bound), `fetchArtifact` (one reference as a bounded `Response`) and `downloadArtifacts` (Node-only, a run's produced files saved under a directory as a produced verdict), plus `artifactFilename`, the rule that names a saved file after the field it fills. The three network operations are client methods over a small structural seam (`ArtifactCapableClient`: the raw `resolveStorageUrls` call and `getRunResult`); the module's static import graph stays free of Node builtins, because `node:fs/promises`, `node:path` and `undici` are loaded by dynamic import behind `isNodeRuntime()` — a browser-targeting bundler still has to mark `node:*` and `undici` external, as it already must for `upload.ts`. `undici` is a runtime dependency for one reason: the fetch runs on an `Agent` carrying `headersTimeout` and `bodyTimeout`, the per-stall bounds an `AbortSignal` budget does not give, and Node exposes its bundled undici's `fetch` but not its `Agent`. See [artifact-download.md](./artifact-download.md).
91
91
  - `runs.ts` — the run-lifecycle types (`RunStatus`, `RunRead`, `RunResults`, `RunResultState`) and the single poll loop (`pollUntilResult`). `RunResults.main_stuff` (the resolved main output content) is always present for a completed run: on the hosted path it is the `main_stuff.json` S3 artifact; on the bare-runner blocking path the SDK resolves it from the returned working memory via the response's `main_stuff_name`, so both paths deliver the same shape. Consumers read `main_stuff` directly. `working_memory` — every named stuff of the run, `{ root, aliases }`, typed `DictWorkingMemory` from `models.ts` — reads the same on both paths: the hosted path relays the `working_memory.json` artifact, and the blocking path lifts it off `pipe_output`, which still rides whole (blocking path only). `graph_spec` — the executed graph — also reaches the client on both paths: the hosted path relays the `graphspec.json` artifact, and the blocking path lifts it off `pipe_output` beside `graph_assembly_error`, the graph's twin of `usage_assembly_error`. That error field is declared ahead of the wire on the hosted side, where the platform's results body relays no such key and the field is therefore absent, reading `undefined` rather than `null`. A completed run that cannot deliver a main stuff throws `MissingMainStuffError`. `runs.ts` also owns `TokensUsageRecord` — one client-facing usage record per inference call, carried by `RunResults.tokens_usages` on both paths. That is a mirror of a wire contract this SDK does not own (inference accounting is a Pipelex runtime extension rather than part of the MTHDS standard): every field is optional and the interface carries an index signature so pre-contract artifacts (relayed verbatim, never migrated) still type-check. See [`run-usage.md`](./run-usage.md) for the field reference, the cost/null semantics, and the old-artifact rules, and [`run-results.md`](./run-results.md) for the field-by-field account of `RunResults` — the durable handle, the output and how to read it, the run's working memory, the executed graph and how to render or keep it, and the produced files with their short-lived links.
92
92
  - `usage.ts` — `summarizeUsage`, the **pure** fold of a run's usage pair (`tokens_usages` / `usage_assembly_error`) into one `UsageSummary`: a `state` (`records`, `no_inference` or `unavailable`), the null-aware total cost with its `cost_partial` flag, the `input` and `output` token totals, the call count, the relayed assembly error, and a `by_pipe` rollup sorted by cost with the unattributed calls grouped under a `null` `pipe_code`. It applies the rules [`run-usage.md`](./run-usage.md#summarizing-a-run--summarizeusage) states, so that every consumer reads one computation rather than re-deriving those rules by hand. It imports types from `runs.ts` and nothing else: no client, no fetch, no `mthds/protocol`.
93
93
  - `execute-result.ts` — `PipelexExecuteResult`, a `DictRunResultExecute` subtype that adds a resolved `.main_stuff` accessor (dug out of `pipe_output`'s working memory via the response's `main_stuff_name`, throwing `MissingMainStuffError` if unlocatable). `execute()` returns it, giving blocking and durable results the **same output accessor** — `result.main_stuff` — so callers never branch on which path ran. The neutral `mthds` wire model leaves `main_stuff_name` in its extension index signature; this Pipelex-branded subtype declares it as a typed field (Pipelex owns that concept). `resultsFromExecute` (the public lift from that result onto `RunResults`, in the same module) reads that accessor too, keeping the resolution single-sourced; the `executeBlocking` fallback is its only caller inside the SDK, and a consumer driving `execute()` directly calls it by name.
94
94
  - `errors.ts` — the transport and run-lifecycle typed errors, the input-preparation family under `InputPreparationError`, and the artifact family under `ArtifactOperationError` (`ScopeUnavailableError`, `ArtifactFetchError`, `ArtifactAuthenticationError`, the last carrying the download verdict so far); all derive from the protocol-base `PipelineRequestError` (re-exported from `mthds/protocol`) except `ClientAuthenticationError`. It is not the whole error surface: `codegen-check.ts` exports `CodegenLockError`, which extends plain `Error` because nothing was requested over the wire.
95
95
  - `user-agent.ts` — client identification per the workspace spec `docs/specs/client-identification.md`: the exported `AppInfo` option, its RFC 9110 token validation (a `TypeError` on a bad field, a `RangeError` past the 512-character ceiling), its rendering, the runtime detection (Node, Bun, Deno, or a browser, where no header is set) and `buildUserAgent`, which the client calls once at construction. The client's private `requestHeaders` is the one place API request headers are built, so every route carries the value; the artifact fetch of a presigned link never goes through it. See [client-identification.md](./client-identification.md).
96
+ - `timers.ts` — `MAX_TIMER_DELAY_MS`, the longest delay `setTimeout` honours, and `isTimerDelay`, which every caller-supplied `timeoutMs` is checked against before it reaches a timer: a longer delay overflows and fires at once. It imports nothing, so the browser-safe upload entry uses it too.
96
97
  - `version.ts` — `SDK_VERSION`, in its own module so the client stamps it on the `User-Agent` without importing the barrel, which re-exports it.
97
98
  - `index.ts` — the public barrel: `export * from "mthds/protocol"` (single import source for the standard surface) plus the client, models, run types, the standalone pure helpers (`runCodegenCheck`, `summarizeUsage`, `locateArtifacts`, `collectArtifacts`, `artifactFilename`), and errors.
98
99
 
@@ -125,7 +126,7 @@ The hosted catalog/account routes the webapp drives. All ride the same `{base}/v
125
126
 
126
127
  ## Hook bundle (`dist-hooks/check.mjs`)
127
128
 
128
- `src/hooks/` holds the post-edit `.mthds` hook pipeline (local lint/format via `@pipelex/tools-wasm`, then a `POST /v1/validate` verdict through this SDK), bundled into a single dependency-free ESM file by `npm run build:hook`. Its validate client is built by `src/hooks/validate-client.ts` with `appInfo: { name: "pipelex-mthds-check", version: SDK_VERSION }`, the `hook` surface's registry token, so the platform attributes its calls to the hook rather than to a plain SDK caller. The one specifier left external is `undici`, reached only by the artifact fetch path through a dynamic import the hook never executes, so the bundle does not inline an HTTP client it cannot use. **Distribution is source-checkout vendoring, not npm:** `pipelex-plugins` runs `make vendor-hook`, which builds `dist-hooks/check.mjs` in a sibling checkout of this repo and copies it in as a static hook asset — the published `@pipelex/sdk` tarball deliberately does not ship the bundle (it inlines the ~4 MB WASM engine, and no npm consumer of the SDK's library API uses it). `dist-hooks/` is gitignored; the consumer repo's own checks gate a stale vendored copy.
129
+ `src/hooks/` holds the post-edit `.mthds` hook pipeline (local lint/format via `@pipelex/tools-wasm`, then a `POST /v1/validate` verdict through this SDK), bundled into a single dependency-free ESM file by `npm run build:hook`. Its validate client is built by `src/hooks/validate-client.ts` with `appInfo: { name: "pipelex-mthds-check", version: SDK_VERSION }`, the `hook` surface's registry token, so the platform attributes its calls to the hook rather than to a plain SDK caller. The one specifier left external is `undici`, reached only by the artifact fetch path through a dynamic import the hook never executes, so the bundle does not inline an HTTP client it cannot use. **Distribution is source-checkout vendoring, not npm:** `pipelex-plugins` runs `make vendor-hook`, which builds `dist-hooks/check.mjs` in a sibling checkout of this repo and copies it in as a static hook asset — the published `@pipelex/sdk` tarball deliberately does not ship the bundle (it inlines the ~4 MB WASM engine, and no npm consumer of the SDK's library API uses it). `dist-hooks/` is gitignored; the consumer repo's own checks gate a stale vendored copy. `@pipelex/tools-wasm` is pinned to an exact version in `package.json`, because the engine carries the MTHDS schema the hook lints against: a newer schema reaches the hook only by a deliberate bump followed by a rebuild and a re-vendor, and the bundle's provenance line (`@pipelex/sdk <version> (<commit>) + @pipelex/tools-wasm <version> (npm)`) names the engine it embeds.
129
130
 
130
131
  ## Testing
131
132
 
@@ -74,7 +74,7 @@ export async function GET(request: Request, { params }: { params: { uri: string
74
74
 
75
75
  Resolves the reference fresh and returns the object store's response as a bounded `Response`. The bounds:
76
76
 
77
- - **A timeout** covering the connection, the headers and the whole body (`timeoutMs`, default 120 s). On Node the fetch also runs on an `undici` dispatcher carrying `headersTimeout` and `bodyTimeout` at the same value, the per-stall bounds an `AbortSignal` alone does not give.
77
+ - **A timeout** covering the connection, the headers and the whole body (`timeoutMs`, default 120 s). On Node the fetch also runs on an `undici` dispatcher carrying `headersTimeout` and `bodyTimeout` at the same value, the per-stall bounds an `AbortSignal` alone does not give. It must be a positive number no larger than 2147483647, the longest delay a timer honours, or the call throws an `ArtifactOperationError` before anything is resolved.
78
78
  - **Redirects refused** (`redirect: "manual"`): a presigned link has no reason to redirect, and one that does is refused rather than followed.
79
79
  - **The byte cap enforced mid-stream** (`maxBytes`, default 1 GiB): a declared `Content-Length` over the cap is refused before a byte is read, and a body that crosses the cap while streaming errors the returned stream — never buffered.
80
80
  - **No credentials forwarded**: the request carries no headers of ours. The link's authorization is in its query string, and nothing else may ride along to the store.
@@ -54,7 +54,7 @@ The file is described, never sent. The grant (`UploadGrant`) is a presigned, cre
54
54
 
55
55
  **The grant is a bearer capability.** Until it expires, whoever holds it can write that one object, so the SDK never logs it, and its caller should not either. The route never replays a grant for a repeated request: a caller that lost one asks for a new one.
56
56
 
57
- **Sending the file** is the standalone `uploadWithGrant(grant, file, { signal? })`, which takes a `Blob` or a `File` and returns `{ uri }`:
57
+ **Sending the file** is the standalone `uploadWithGrant(grant, file, { signal?, timeoutMs? })`, which takes a `Blob` or a `File` and returns `{ uri }`:
58
58
 
59
59
  ```ts
60
60
  import { uploadWithGrant, RejectedAssetError } from "@pipelex/sdk/upload";
@@ -64,6 +64,8 @@ const { uri } = await uploadWithGrant(grant, file);
64
64
 
65
65
  It sends the file as the raw body of a `PUT` to `grant.url`, with `grant.headers` exactly as received. The grant signed `Content-Length` too, and the body sets it, so the file must be exactly the declared size. Redirects are refused rather than followed, because a presigned URL is valid only at the address it was signed for. **The `uri` names nothing until the `PUT` has succeeded**, so fill a form field or a run input with it only after `uploadWithGrant` resolves.
66
66
 
67
+ **The time limit.** The whole exchange with storage runs under a time limit — the request, the file going out, the answer coming back and the read of an error body — so a stalled upload settles even when the caller passes no signal. By default the limit is 60 s plus 1 s for every started 128 KiB of the file, which assumes about 1 Mbit/s and comes to 460 s for a 50 MiB file. `timeoutMs` replaces the default, for a slower link or a caller that wants a tighter bound; it must be a positive number no larger than 2147483647, the longest delay a timer honours, or the call throws an `InputPreparationError` before anything is sent. A `signal` ends the upload whenever it fires, so it can shorten the limit but never lengthen it, and a caller's abort still propagates as the signal's own reason even when the limit ran out too. When the limit runs out before storage answers, the call throws an `UploadTransportError` whose `code` is `timeout` and whose message names the seconds allowed. When it runs out while storage's error body is still arriving, storage has already answered, so the call settles as that answer instead, classified from its status and whatever part of the body arrived. After a timeout, whether storage stored the file is unknown: retrying with the same grant before it expires either stores the file or answers the `412` of a used grant, and then the grant's `uri` already names the file. The grant's expiry does not bound the upload, because storage checks the signature when the request starts, not when it ends.
68
+
67
69
  **The browser-safe entry.** `uploadWithGrant`, the grant types and the three error classes it raises (`InputPreparationError`, `RejectedAssetError`, `UploadTransportError`) are exported from `@pipelex/sdk/upload`, whose import graph reaches no Node builtin, no `undici` and not the client, so a page bundles it with nothing marked external. They are the same function and the same classes the main entry exports, so an `instanceof` check agrees whichever entry the error came through. The unit suite bundles the entry for the browser with esbuild and fails if anything Node-only becomes reachable.
68
70
 
69
71
  **Storage's refusals** map onto the input-preparation errors, so a caller that already handles `uploadFile` needs no new case:
@@ -75,14 +77,16 @@ It sends the file as the raw body of a `PUT` to `grant.url`, with `grant.headers
75
77
  | `403 SignatureDoesNotMatch` | `RejectedAssetError` | `signature_mismatch` | The file's size, content type or metadata differ from what the grant signed. |
76
78
  | `403 AccessDenied`, "Request has expired" | `RejectedAssetError` | `grant_expired` | The grant expired. |
77
79
  | `403 AccessDenied`, headers "not signed" | `RejectedAssetError` | `unsigned_header` | The request carried a storage header the grant did not sign. |
78
- | `400 RequestTimeout` | `UploadTransportError` | | Storage stopped waiting for the body and wrote nothing, so the same grant can be retried while it is valid. |
80
+ | `400 RequestTimeout` | `UploadTransportError` | `storage_timeout` | Storage stopped waiting for the body and wrote nothing, so the same grant can be retried while it is valid. |
81
+ | `409 ConditionalRequestConflict` | `UploadTransportError` | `conflict` | Another upload with the same grant was still in progress. Whether the object was written is unknown, so the same-grant retry applies. A grant is meant to be sent once at a time. |
79
82
  | any other `4xx` | `RejectedAssetError` | `store_refused` | Storage's own message is relayed. |
80
- | `5xx` | `UploadTransportError` | | Whether the object was written is unknown: retrying with the same grant either stores it or answers the `412` of a used grant. |
81
- | a redirect | `UploadTransportError` | | Refused, not followed. |
82
- | no response | `UploadTransportError` | | Storage unreachable. In a browser a refused cross-origin request looks exactly like this, so the message also points at the page's CSP `connect-src`. |
83
- | nothing sent | `UploadTransportError` | | The grant's `url` is not an absolute `http(s)` URL free of user info, so it is refused before any request. A browser would resolve a relative URL against the page and send the credential there. |
83
+ | `5xx` | `UploadTransportError` | `server_error` | Whether the object was written is unknown: retrying with the same grant either stores it or answers the `412` of a used grant. |
84
+ | a redirect | `UploadTransportError` | `redirected` | Refused, not followed. |
85
+ | no answer within the time limit | `UploadTransportError` | `timeout` | The limit above ran out before storage answered. Whether the object was written is unknown, so the same-grant retry applies. |
86
+ | no response | `UploadTransportError` | `unreachable` | Storage unreachable. In a browser a refused cross-origin request looks exactly like this, so the message also points at the page's CSP `connect-src`. If the connection failed after the file went out, the object may have been written, so the message gives the same-grant retry too. |
87
+ | nothing sent | `UploadTransportError` | `invalid_grant_url` | The grant's `url` is not an absolute `http(s)` URL free of user info, so it is refused before any request. A browser would resolve a relative URL against the page and send the credential there. |
84
88
 
85
- `RejectedAssetError.code` is what a caller branches on — to request a new grant on `grant_used` or `grant_expired`, say — rather than the message or the status, since an expired grant and an unsigned header both answer `403 AccessDenied` and only S3's message tells them apart. Its `status` is storage's, and its `filename` is the `File`'s name, or the object name the grant's `uri` ends in for a nameless `Blob`. `UploadTransportError.status` is storage's status when storage answered, and undefined when it did not. Neither error wraps storage's response, and only S3's error code and message reach a message: its error body can echo the signed request, credential included, so it is never kept. **No error carries the grant's URL.** The runtime's network error can: Node and Chrome name the whole URL when they cannot build a request from it, and Bun puts it on the error's `path`. So an unreachable storage is described by its origin and by the error names and codes along the cause chain (`TypeError, caused by Error ENOTFOUND`), and the runtime's error is neither relayed nor kept as `cause`. A caller's abort propagates as the signal's own reason, unwrapped, including one that lands while storage's error body is still arriving.
89
+ `RejectedAssetError.code` and `UploadTransportError.code` are what a caller branches on — to request a new grant on `grant_used` or `grant_expired`, to retry with the same grant on `timeout` or `server_error`, or to tell a timeout from an unreachable host — rather than the message, the status or the error's `name`, since an expired grant and an unsigned header both answer `403 AccessDenied` and only S3's message tells them apart. The SDK sets `code` on every error it raises. `RejectedAssetError.status` is storage's, and its `filename` is the `File`'s name, or the object name the grant's `uri` ends in for a nameless `Blob`. `UploadTransportError.status` is storage's status when storage answered, and undefined when it did not. Neither error wraps storage's response, and only S3's error code and message reach a message: its error body can echo the signed request, credential included, so it is never kept, and only its first 16 KiB are read, which is where S3 writes its code and message. **No error carries the grant's URL.** The runtime's network error can: Node and Chrome name the whole URL when they cannot build a request from it, and Bun puts it on the error's `path`. So an unreachable storage is described by its origin and by the error names and codes along the cause chain (`TypeError, caused by Error ENOTFOUND`), and the runtime's error is neither relayed nor kept as `cause`. A caller's abort propagates as the signal's own reason, unwrapped, including one that lands while storage's error body is still arriving.
86
90
 
87
91
  ### `prepareInputs` — signature-driven input preparation
88
92
 
@@ -225,7 +229,9 @@ The contract distinguishes these semantic outcomes, each a typed subclass of `In
225
229
  - **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`);
226
230
  - **unsupported server capability** (`UnsupportedUploadCapabilityError`) — the configured deployment has no upload route;
227
231
  - **authentication / authorization failure** (`UploadAuthenticationError`) — `401` / `403`;
228
- - **transport failure** (`UploadTransportError`) — a network or server fault, a malformed data URL payload, or any other unexpected upload failure; its `status` is the HTTP status when a response produced it.
232
+ - **transport failure** (`UploadTransportError`) — a network or server fault, or any other unexpected upload failure; its `status` is the HTTP status when a response produced it, and its `code` says which failure it was. From `uploadFile` the code is `server_error` for a `5xx`, `timeout` when the client's own request timeout ran out, `unreachable` when no response came back, and `unexpected` for any other status or failure; `uploadWithGrant`'s codes are in the table above.
233
+
234
+ A malformed data URL — no comma, bad base64, bad percent-encoding — is a plain `InputPreparationError`, raised while the asset is read and before anything is uploaded.
229
235
 
230
236
  All preparation failures are raised **before any run is created**.
231
237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/sdk",
3
- "version": "0.23.0",
3
+ "version": "0.24.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": {
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "devDependencies": {
54
54
  "@eslint/js": "^9.39.4",
55
- "@pipelex/tools-wasm": "^0.1.0",
55
+ "@pipelex/tools-wasm": "0.3.0",
56
56
  "@types/node": "^22.12.0",
57
57
  "@vitest/coverage-v8": "^4.1.9",
58
58
  "dependency-cruiser": "^17.4.3",