@pipelex/sdk 0.25.1 → 0.26.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 +24 -2
- package/dist/artifacts.js +1 -1
- package/dist/artifacts.js.map +1 -1
- package/dist/client.d.ts +6 -3
- package/dist/client.js +106 -34
- package/dist/client.js.map +1 -1
- package/dist/error-models.d.ts +198 -0
- package/dist/error-models.js +26 -0
- package/dist/error-models.js.map +1 -0
- package/dist/errors.d.ts +132 -26
- package/dist/errors.js +134 -23
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +47 -8
- package/dist/product-models.d.ts +4 -15
- package/dist/runs.d.ts +22 -8
- package/dist/runs.js +4 -3
- package/dist/runs.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +5 -4
- package/docs/artifact-download.md +1 -1
- package/docs/errors.md +143 -0
- package/docs/run-results.md +3 -1
- package/package.json +2 -2
|
@@ -160,7 +160,7 @@ Per-item `error.code` is the fetch vocabulary above plus the download's own: `re
|
|
|
160
160
|
|
|
161
161
|
**What it throws.** Only conditions with no verdict, all typed:
|
|
162
162
|
|
|
163
|
-
- `RunStillRunningError` (with the retry hint) or `RunFailedError` — a `run_id` naming a run that has not completed;
|
|
163
|
+
- `RunStillRunningError` (with the retry hint) or `RunFailedError` (with the run's status and its stored error report as `error`) — a `run_id` naming a run that has not completed;
|
|
164
164
|
- `ScopeUnavailableError` — the requested scope's artifact is `null` or missing from the body (`scope` and `runId` on the error). Reading by `run_id`, a null `main_stuff` is already `MissingMainStuffError` from `getRunResult`;
|
|
165
165
|
- `ArtifactAuthenticationError` — the resolve route refused the credential (`401` / `403`), on the first resolve or on a re-resolve part-way through. It carries `verdict`, the result as it stood: the refusal stops the workers taking new items but lets the fetches already running finish, since they are on presigned links that do not carry the credential, so every file saved is real and listed and the rest are marked `aborted` with a detail naming the credential failure;
|
|
166
166
|
- `ArtifactOperationError` — outside Node, an unusable `dir`, both selectors or neither, an unknown `scope`, or nonsense bounds;
|
package/docs/errors.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Errors — a failed run's report and a refused request's problem
|
|
2
|
+
|
|
3
|
+
Two things can go wrong when you call the hosted API, and the SDK hands each one back whole. A **run can fail** after it started: the runner stores why, as its error report, and the SDK gives you that report on the failed run. A **request can be refused**: the server answers a non-2xx RFC 9457 problem document, and the SDK throws an `ApiResponseError` carrying every member of it. This page covers both: which fields each carries, which ones a program branches on, which ones a person reads, and which one support asks for. Either can carry validation items, the structured account of a method refused for what it says, which the [last section](#a-validation-item--validationerroritem) describes.
|
|
4
|
+
|
|
5
|
+
The shapes are not this SDK's invention. The report is the runner's `ErrorReport` (in `pipelex`), stored and served by the platform exactly as the runner wrote it, and the problem members are the ones the workspace's hosted-envelope spec names. The types live in `src/error-models.ts`; `pipelex-sdk` (Python) carries the same fields under snake_case names.
|
|
6
|
+
|
|
7
|
+
## A failed run — `RunErrorReport`
|
|
8
|
+
|
|
9
|
+
A run that ends without completing (`FAILED`, `CANCELLED`, `TERMINATED` or `TIMED_OUT`) has no result, and the results read answers `409`. The body of that `409` says why: its `detail` names the status and then the report's message, its `run_status` member holds the status, and its `error` member holds the run's stored error report, or `null` when the run has none. The SDK reads the status from `run_status`, falling back to the status word of the `detail` sentence on a platform that does not send the member yet, and types the report as `RunErrorReport`.
|
|
10
|
+
|
|
11
|
+
The report reaches you in four places, always the same object:
|
|
12
|
+
|
|
13
|
+
| Where | What you get |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `getRunResult(runId)` | the `failed` arm: `{ state: "failed", pipeline_run_id, status, message, error }` |
|
|
16
|
+
| `waitForResult`, `startAndWaitForResult` on the hosted API, `downloadArtifacts({ run_id })` | a thrown `RunFailedError` with `runId`, `status`, `message` and `error` |
|
|
17
|
+
| `getRunStatus(runId)` | `RunRead.error` on the run record |
|
|
18
|
+
| `listRuns`, `iterateRuns`, `getRunDetail` | `PipelineRun.error` on each run record |
|
|
19
|
+
|
|
20
|
+
**A bare runner has no durable run, so its failure is a refused request.** Against a bare `pipelex-api` runner, `startAndWaitForResult` runs the method with the blocking `execute`, and a run that fails there answers a non-2xx problem document: the call throws an `ApiResponseError`, not a `RunFailedError`, and the same classification rides its members (`errorDomain`, `type`, `retryable`, `userAction`, `errorType`, `model`, …) as described [below](#a-refused-request--apiresponseerror). A caller that must work against both catches both, as the example does.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { ApiResponseError, RunFailedError } from "@pipelex/sdk";
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const result = await client.startAndWaitForResult({ method_id: "mt_abc123", inputs });
|
|
27
|
+
console.log(result.main_stuff);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err instanceof RunFailedError) {
|
|
30
|
+
const report = err.error; // RunErrorReport | null
|
|
31
|
+
console.error(err.message); // "Run finished with status FAILED: <the report's message>"
|
|
32
|
+
if (report?.user_action) console.error(`Next step: ${report.user_action.detail}`);
|
|
33
|
+
if (report?.retryable) console.error("A retry can succeed.");
|
|
34
|
+
console.error(`Run id for support: ${err.runId}`);
|
|
35
|
+
} else if (err instanceof ApiResponseError) {
|
|
36
|
+
// A refused request — or, against a bare runner, a run that failed on the blocking path.
|
|
37
|
+
console.error(err.serverMessage, err.userAction?.detail, `request id ${err.requestId}`);
|
|
38
|
+
} else {
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The report's fields, all optional because the runner owns the shape:
|
|
45
|
+
|
|
46
|
+
| Field | Meaning |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `message` | What went wrong, as the runner wrote it. For a run failure it is where the failing pipe is named. |
|
|
49
|
+
| `error_domain` | Who can fix it: `input` (the caller — a malformed method, a bad input), `config` (a configuration change — a model the backend does not serve, a missing secret), `runtime` (nobody beforehand — a provider outage). **A branch field.** |
|
|
50
|
+
| `type_uri` | The stable URI naming the error class, the same on every occurrence. **A branch field.** |
|
|
51
|
+
| `retryable` | Whether running it again can succeed. Absent means unknown, which is not `false`. **A branch field.** |
|
|
52
|
+
| `user_action` | The next step, as `{ kind, detail }`: `detail` is the advice in words, `kind` its category (`wait_and_retry`, `check_billing`, `check_credentials`, `change_input`, `change_model`, `contact_support`, `unknown`). |
|
|
53
|
+
| `title` | The stable human label of the error class. |
|
|
54
|
+
| `error_type` | The runner's exception class name — an open set, for display and support, never matched against. |
|
|
55
|
+
| `error_category` | The finer class of an inference failure: `transient`, `configuration`, `content`, `capacity`, `ambiguous`, `unknown`. |
|
|
56
|
+
| `model`, `provider` | The model and provider an inference failure involved. |
|
|
57
|
+
| `provider_metadata` | What the provider's SDK said: `provider`, `sdk_exception_type`, `message`, `status_code`, `request_id` (the provider's own), `retry_after_seconds`, `provider_error_code`. |
|
|
58
|
+
| `validation_errors` | The structured diagnostics of a method that failed validation, the same [items](#a-validation-item--validationerroritem) the validate report carries. |
|
|
59
|
+
| `migration` | A pending configuration migration that explains the failure, present only when the runner's scan found one. |
|
|
60
|
+
| `caller_facing_message` | `true` when `message` was written as caller-facing copy. |
|
|
61
|
+
|
|
62
|
+
A field a newer runner adds is reachable through the interface's index signature before this SDK names it.
|
|
63
|
+
|
|
64
|
+
**The report is `null` when the run has none.** A run the platform finalized itself carries no report: one whose start failed, and a timeout, a termination or a vanished workflow resolved by the platform. A cancelled run usually has none either. The status and the `detail` sentence still say what happened, and the absence of a report says nothing about why.
|
|
65
|
+
|
|
66
|
+
**Nothing is stripped, so presentation is yours.** The platform serves the runner's VERBOSE report, so `message` and `provider_metadata.message` can hold a provider's raw text. The SDK types what the platform sends; deciding what a person sees is the consumer's decision. `RunFailedError.message`, being the `detail`, already contains the report's message.
|
|
67
|
+
|
|
68
|
+
**Numbers in `provider_metadata` may arrive as strings.** A report read back from the platform's run store carries `status_code` and `retry_after_seconds` as strings (`"404"`, `"1.5"`), because the store returns its numbers as decimals that the platform serializes as strings; a problem document a runner renders carries them as numbers. Both are in the type, so read them with `Number(...)`.
|
|
69
|
+
|
|
70
|
+
## A refused request — `ApiResponseError`
|
|
71
|
+
|
|
72
|
+
Every non-2xx answer from a `/v1` route is a problem document, and the SDK throws an `ApiResponseError` whose fields are its members. Each field is `undefined` when the document did not carry it, and a member of the wrong type (a numeric `type`, a `retryable` of `"no"`, a `user_action` without a `detail`) reads as absent rather than as a wrong value.
|
|
73
|
+
|
|
74
|
+
| Field | Wire member | Meaning |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `errorDomain` | `error_domain` | Who can fix it: `input`, `config` or `runtime`. **A branch field.** |
|
|
77
|
+
| `type` | `type` | The stable URI naming the error class. **A branch field.** |
|
|
78
|
+
| `retryable` | `retryable` | Whether retrying the same request can succeed; `undefined` means unknown. |
|
|
79
|
+
| `userAction` | `user_action` | The next step, `{ kind, detail }`. |
|
|
80
|
+
| `requestId` | `request_id`, else the `X-Request-ID` header | The id to hand to support: it finds the server's log lines for the request. |
|
|
81
|
+
| `title` | `title` | The stable human label of the error class. |
|
|
82
|
+
| `serverMessage` | `detail` (or `message`) | The per-occurrence message. |
|
|
83
|
+
| `instance` | `instance` | The occurrence: a request URN (`urn:pipelex:request:<id>`) or a request path. |
|
|
84
|
+
| `code` | `code` | The platform's native code, a closed set (`conflict`, `not_found`, `validation_failed`, `pipelex_api_key_limit_reached`, …), one-to-one with `type`. |
|
|
85
|
+
| `errorType` | `error_type` | The runner's native code, its open exception class name. |
|
|
86
|
+
| `errorCategory`, `model`, `provider`, `providerMetadata` | `error_category`, `model`, `provider`, `provider_metadata` | An inference failure's class and origin, as on the run report. |
|
|
87
|
+
| `migration` | `migration` | A pending configuration migration that explains the failure. |
|
|
88
|
+
| `validationErrors` | `validation_errors` | The structured diagnostics of a bundle that failed validation, as [validation items](#a-validation-item--validationerroritem). |
|
|
89
|
+
| `errors` | `errors` | The platform's field-level failures, `{ field, code, detail }` each. |
|
|
90
|
+
| `problemDocument` | the whole body | The decoded document, every member named or not; `undefined` when the body was not a JSON object. |
|
|
91
|
+
| `status`, `statusText`, `responseBody` | the transport | The HTTP status, its text and the raw body. |
|
|
92
|
+
|
|
93
|
+
**Branch on `errorDomain` and `type`, never on the HTTP status or the message.** That is the rule the hosted-envelope spec sets for every surface: `errorDomain` tells the caller's own mistake from a fault it cannot fix, and `type` names the error class with a URI that stays the same on every occurrence. `code` and `errorType` are each surface's finer native code, and stay available for logs and support. On the platform today `errorDomain` is not emitted yet, and `type` is `https://pipelex.com/errors/<code>`, so branching on `type` there is branching on the code:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { ApiResponseError } from "@pipelex/sdk";
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await client.createPipelexApiKey({ label: "ci" });
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (!(err instanceof ApiResponseError)) throw err;
|
|
102
|
+
if (err.type === "https://pipelex.com/errors/pipelex_api_key_limit_reached") {
|
|
103
|
+
// revoke a key first
|
|
104
|
+
} else if (err.errorDomain === "input") {
|
|
105
|
+
console.error(err.serverMessage, err.userAction?.detail, err.errors);
|
|
106
|
+
} else {
|
|
107
|
+
console.error(`${err.serverMessage} (request id ${err.requestId ?? "unknown"})`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The members `mthds`'s own `ApiResponseError` carries (`type`, `title`, `instance`, `requestId`, `errorDomain`, `retryable`, `userAction`) have the same names and types here, and a type-level test pins them to the standard client's declarations, so a consumer reads one vocabulary whichever client raised the error. The rest are the Pipelex members the standard's client leaves to this SDK.
|
|
113
|
+
|
|
114
|
+
## A validation item — `ValidationErrorItem`
|
|
115
|
+
|
|
116
|
+
A method refused for what it says, rather than for how its run went, comes back as a list of validation items. They ride the `/v1/validate` verdict (its invalid arm's `validation_errors[]`, and the advisory `warnings[]` of its valid arm) and the build and crate routes' invalid verdict (`CrateInvalidReport.validation_errors[]`), each a `200` because an invalid bundle is a produced verdict; the `422` a run route answers when the runner refuses the method for its validation errors, as `ApiResponseError.validationErrors`; and a failed run's report, as `validation_errors`. Each item has a `category` and a `message`; the other members locate the error and say what to do next, and each is present only when the runner knows it:
|
|
117
|
+
|
|
118
|
+
| Member | Meaning |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `category` | The stage that found the error: `blueprint_validation`, `pipe_factory`, `pipe_validation` or `dry_run`. |
|
|
121
|
+
| `message` | What is wrong, in words. |
|
|
122
|
+
| `error_type` | The error's code, such as `unknown_model`. |
|
|
123
|
+
| `pipe_code`, `concept_code`, `domain_code` | The pipe, concept and domain the error is in. |
|
|
124
|
+
| `source`, `field_path`, `field_name` | The file the error is in (the name passed in `mthds_sources`), and the field, as a path such as `pipe.summarize.model` and by name. |
|
|
125
|
+
| `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. |
|
|
126
|
+
| `variable_names` | The input variables an input error concerns. |
|
|
127
|
+
| `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. |
|
|
128
|
+
| `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. |
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const report = await client.validate(contents, false, sources);
|
|
132
|
+
if (!report.is_valid) {
|
|
133
|
+
for (const item of report.validation_errors) {
|
|
134
|
+
console.error(`${item.source ?? "?"} ${item.pipe_code ?? ""}: ${item.message}`);
|
|
135
|
+
if (item.suggestions?.length) console.error(`Did you mean: ${item.suggestions.join(", ")}`);
|
|
136
|
+
if (item.suggested_fix) console.error(`Fix: ${item.suggested_fix.description}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**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`.
|
|
142
|
+
|
|
143
|
+
**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`.
|
package/docs/run-results.md
CHANGED
|
@@ -65,12 +65,14 @@ switch (state.state) {
|
|
|
65
65
|
console.log(`not finished — poll again in ${state.retry_after_seconds ?? 2}s`);
|
|
66
66
|
break;
|
|
67
67
|
case "failed":
|
|
68
|
+
// `message` already names the reason; `error` is the run's stored report, or null.
|
|
68
69
|
console.error(`run ended as ${state.status}: ${state.message}`);
|
|
70
|
+
if (state.error?.user_action) console.error(`next step: ${state.error.user_action.detail}`);
|
|
69
71
|
break;
|
|
70
72
|
}
|
|
71
73
|
```
|
|
72
74
|
|
|
73
|
-
`getRunResult` is the single-shot lookup and returns that discriminated state. `waitForResult(runId)` drives the same lookup in a loop, honouring the server's `Retry-After`, and returns the `RunResults` directly — throwing `RunFailedError` on a terminal non-completed status and `RunTimeoutError` when the budget runs out.
|
|
75
|
+
`getRunResult` is the single-shot lookup and returns that discriminated state. `waitForResult(runId)` drives the same lookup in a loop, honouring the server's `Retry-After`, and returns the `RunResults` directly — throwing `RunFailedError` on a terminal non-completed status and `RunTimeoutError` when the budget runs out. The `failed` arm and `RunFailedError` carry the same facts: the run's `status`, the `message` naming the reason, and `error`, the run's stored error report, typed as `RunErrorReport` and `null` when the run ended without one. [`errors.md`](./errors.md#a-failed-run--runerrorreport) describes the report field by field.
|
|
74
76
|
|
|
75
77
|
## `working_memory` — every named stuff of the run
|
|
76
78
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipelex/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.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": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"vitest": "^4.0.18"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"mthds": "^0.
|
|
68
|
+
"mthds": "^0.28.0",
|
|
69
69
|
"smol-toml": "^1.6.0",
|
|
70
70
|
"undici": "^7.29.1"
|
|
71
71
|
}
|