@pipelex/sdk 0.52.0 → 0.53.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.
Files changed (88) hide show
  1. package/README.md +17 -17
  2. package/dist/answer-reading.d.ts +70 -0
  3. package/dist/answer-reading.js +106 -0
  4. package/dist/answer-reading.js.map +1 -0
  5. package/dist/artifacts.js +45 -32
  6. package/dist/artifacts.js.map +1 -1
  7. package/dist/cli/present.d.ts +11 -14
  8. package/dist/cli/present.js +41 -37
  9. package/dist/cli/present.js.map +1 -1
  10. package/dist/cli/run.js +8 -7
  11. package/dist/cli/run.js.map +1 -1
  12. package/dist/cli/script.d.ts +1 -1
  13. package/dist/cli/source.js +2 -2
  14. package/dist/cli/source.js.map +1 -1
  15. package/dist/client.d.ts +248 -218
  16. package/dist/client.js +538 -477
  17. package/dist/client.js.map +1 -1
  18. package/dist/codegen-check.d.ts +21 -15
  19. package/dist/codegen-check.js +54 -27
  20. package/dist/codegen-check.js.map +1 -1
  21. package/dist/errors.d.ts +298 -455
  22. package/dist/errors.js +426 -534
  23. package/dist/errors.js.map +1 -1
  24. package/dist/exact-json.d.ts +8 -12
  25. package/dist/exact-json.js +19 -12
  26. package/dist/exact-json.js.map +1 -1
  27. package/dist/hooks/check-core.d.ts +12 -7
  28. package/dist/hooks/check-core.js +20 -14
  29. package/dist/hooks/check-core.js.map +1 -1
  30. package/dist/hooks/validate-stage.js +12 -3
  31. package/dist/hooks/validate-stage.js.map +1 -1
  32. package/dist/index.d.ts +4 -5
  33. package/dist/index.js +7 -6
  34. package/dist/index.js.map +1 -1
  35. package/dist/method-selector.js +7 -5
  36. package/dist/method-selector.js.map +1 -1
  37. package/dist/method-trees.d.ts +6 -7
  38. package/dist/method-trees.js +31 -37
  39. package/dist/method-trees.js.map +1 -1
  40. package/dist/models.d.ts +33 -210
  41. package/dist/models.js +2 -1
  42. package/dist/models.js.map +1 -1
  43. package/dist/prepare-inputs.d.ts +2 -2
  44. package/dist/prepare-inputs.js +51 -54
  45. package/dist/prepare-inputs.js.map +1 -1
  46. package/dist/problem-items.d.ts +19 -0
  47. package/dist/problem-items.js +52 -0
  48. package/dist/problem-items.js.map +1 -0
  49. package/dist/product-models.d.ts +23 -21
  50. package/dist/records.d.ts +12 -0
  51. package/dist/records.js +23 -0
  52. package/dist/records.js.map +1 -0
  53. package/dist/runs.d.ts +49 -38
  54. package/dist/runs.js +45 -19
  55. package/dist/runs.js.map +1 -1
  56. package/dist/transport-reading.d.ts +18 -0
  57. package/dist/{run-may-have-started.js → transport-reading.js} +15 -44
  58. package/dist/transport-reading.js.map +1 -0
  59. package/dist/upload-grant.d.ts +9 -6
  60. package/dist/upload-grant.js +42 -29
  61. package/dist/upload-grant.js.map +1 -1
  62. package/dist/upload.js +53 -33
  63. package/dist/upload.js.map +1 -1
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/docs/architecture.md +19 -19
  67. package/docs/artifact-download.md +6 -6
  68. package/docs/cli.md +1 -1
  69. package/docs/crate-routes.md +13 -14
  70. package/docs/errors.md +154 -267
  71. package/docs/exact-integers.md +1 -1
  72. package/docs/files.md +13 -13
  73. package/docs/hook-bundle.md +15 -1
  74. package/docs/input-preparation.md +14 -14
  75. package/docs/method-copy.md +24 -24
  76. package/docs/method-examples.md +13 -14
  77. package/docs/method-trees.md +27 -28
  78. package/docs/organization-branding.md +14 -16
  79. package/docs/run-results.md +8 -8
  80. package/package.json +1 -1
  81. package/dist/error-models.d.ts +0 -302
  82. package/dist/error-models.js +0 -203
  83. package/dist/error-models.js.map +0 -1
  84. package/dist/error-verdicts.d.ts +0 -46
  85. package/dist/error-verdicts.js +0 -120
  86. package/dist/error-verdicts.js.map +0 -1
  87. package/dist/run-may-have-started.d.ts +0 -31
  88. package/dist/run-may-have-started.js.map +0 -1
@@ -33,7 +33,7 @@ const verdict = await client.validate({
33
33
 
34
34
  **A directory is listed by the standard's lister.** The client loads the `mthds` package's `listMethodTree` when a call gives a `directory`, and lists it under the MTHDS standard's membership rule and Manifest Discovery: the tree is rooted at the nearest `METHODS.toml` at or above the directory, else at the directory itself, and holds every file the rule keeps. Hidden entries, `node_modules`, `__pycache__`, what a `.gitignore` or the manifest's `exclude` keeps out, and **a run's inputs file** (`inputs.json`, `inputs.toml`) never travel with the method. A tree holding a symbolic link or a special file is refused rather than followed. The directory is listed under the limits the deployment announces (below), so a tree the deployment would refuse for its size is refused while it is listed, before any file is hashed. Every refusal is a `RequestArgumentError` naming the call and the directory, whose `cause` is `mthds`'s `MethodTreeError`, with its `code` (`not_found`, `symbolic_link`, `limit_exceeded`, `empty_tree` and the rest). Listing needs Node: the lister comes from the Node host ([architecture.md](./architecture.md#the-node-host)), so a bundle built for the browser holds none of it, and a `directory` given outside Node is refused with `RequestArgumentError` before the version handshake, so before any request, and a browser gives `contents`, `tree` or `files`.
35
35
 
36
- **A map in memory.** A text in `contents` is encoded in UTF-8, and a text a file cannot hold, one carrying a lone surrogate, is refused, as is a value that is neither a string nor a `Uint8Array`. The keys are sent as they are, and the platform refuses one that is not a tree path with the `422` `invalid_method_files` naming it.
36
+ **A map in memory.** A text in `contents` is encoded in UTF-8, and a text a file cannot hold, one carrying a lone surrogate, is refused, as is a value that is neither a string nor a `Uint8Array`. The keys are sent as they are, and the platform refuses one that is not a tree path with a `422` `method.package_invalid` whose `params` name its `path`.
37
37
 
38
38
  **One form, and never an empty tree.** Two forms at once are refused with a `RequestArgumentError` before anything is read or sent. A `files` map, `contents` or a `tree` holding no file is refused too on the run routes and on `validate`, as the standard refuses it: a tree holds at least one file. On the crate routes and in `prepareInputs`, an empty map is absent instead, so it never reaches the wire as a second selector, and in a save `files: null` empties the draft. An empty `directory` string is absent everywhere.
39
39
 
@@ -46,42 +46,41 @@ A tree given as a `directory`, `contents` or `tree` is sent by the rule the `mth
46
46
 
47
47
  A `files` map is sent as given, with no handshake. A run's `start` carrying a tree, a run that names a `method_ref`, and a start whose `extra` carries a method bundle (`mthds_contents` or `bundle_b64`, as the `pipelex-sdk` command sends one) get the client's request ceiling rather than the 30-second start budget, since the body can be large. On the crate routes, a request whose `files` carry more than `INLINE_TREE_MAX_BYTES` inline, as a tree sent to an API keeping no blob store or a `files` map given as is may, gets the request ceiling too rather than the 30-second budget of a static route ([crate-routes.md](./crate-routes.md)). A save carrying a tree (`createMethod`, `writeDraft`), inline or by hash, gets the request ceiling as well, rather than the 30-second budget of a save sending none, since a tree sent inline to an API keeping no blob store can be large and its whole upload is charged against the budget.
48
48
 
49
- **A refusal of the tree's size is sent once more.** When the API refuses a tree the client prepared, a `directory`, `contents` or `tree`, as past one of its limits (a `422` coded `method_tree_too_large`, or `method.package_invalid` naming a limit, or of type `MethodTreeTooLarge`), the handshake that announced the limits may be stale: the client forgets it, prepares the tree again on a fresh one and sends the request once more, as `mthds`'s client does. A `files` map given as is was not prepared from the handshake, so it is not sent again, and neither is a `start` under an idempotency key, since the platform binds the key to the refusal it stores.
49
+ **A refusal of the tree's size is sent once more.** When the API refuses a tree the client prepared, a `directory`, `contents` or `tree`, as past one of its limits (a `422` `method.package_invalid` whose `params` carry `reason` `too_large` and an integer `limit`), the handshake that announced the limits may be stale: the client forgets it, prepares the tree again on a fresh one and sends the request once more, as `mthds`'s client does. A `files` map given as is was not prepared from the handshake, so it is not sent again, and neither is a `start` under an idempotency key, since the platform binds the key to the refusal it stores.
50
50
 
51
51
  **Stopping a call.** A call that takes a `signal` (`validate`, `startAndWaitForResult`, the crate routes, `prepareInputs`) reads it before each step a `directory`, `contents` or `tree` costs and hands it to the step's request, so an abort stops the version handshake, the directory's listing, the blob route, a file's read and an upload already under way, and nothing further starts.
52
52
 
53
53
  A `MethodTree` reads a file again whenever its bytes are sent, inline or as an upload, and checks it against the hash its listing recorded, so a file changed since the listing is refused by `mthds` rather than sent as another tree than the one listed. Apart from listing a directory, sending a tree reads no Node module: hashing is Web Crypto's, and Base64 is written by the SDK.
54
54
 
55
- **What is uploaded is what the listing names.** Before each upload the client hashes the bytes a tree's `readFile` gives and refuses them, with a `RequestArgumentError`, unless they have the size and the SHA-256 the tree lists for them: a blob is stored under its hash for the whole organization, so a hand-built `MethodTreeSource` whose `readFile` disagrees with its listing never stores other bytes under that hash. An answer of `POST /v1/blobs/missing` asking for a blob the tree does not hold is the platform's fault, not the caller's: it is an `ApiResponseError` of an answer the SDK cannot read, verdict `runtime` and not retryable, thrown before any upload. An upload that fails throws `MethodFileTransferError` with `operation` `upload`, and the request is not sent or the method not saved ([below](#when-a-transfer-fails)).
55
+ **What is uploaded is what the listing names.** Before each upload the client hashes the bytes a tree's `readFile` gives and refuses them, with a `RequestArgumentError`, unless they have the size and the SHA-256 the tree lists for them: a blob is stored under its hash for the whole organization, so a hand-built `MethodTreeSource` whose `readFile` disagrees with its listing never stores other bytes under that hash. An answer of `POST /v1/blobs/missing` asking for a blob the tree does not hold is the platform's fault, not the caller's: it is an `ApiResponseError` of an answer the SDK cannot read, `client.response_unreadable`, thrown before any upload. An upload that fails throws `MethodFileTransferError` with `operation` `upload`, and the request is not sent or the method not saved ([below](#when-a-transfer-fails)).
56
56
 
57
- **A blob that went missing.** A request whose hash entries name a blob the API does not hold is refused with a `422` `missing_blobs` listing them all, which also releases a start's idempotency key. A request built from a `directory`, `contents` or `tree` uploads every blob it names first, so it meets this only if a blob was removed between the upload and the request. The client answers it as `mthds`'s client does: a `422` problem coded `request.blobs_missing`, whose `params.missing_blobs` names blobs the tree holds, has those blobs asked of the blob route again, uploaded and the request sent once more, while the platform's `missing_blobs` code is not read as one and is thrown, so sending the same call again asks the blob route afresh and uploads what is missing.
57
+ **A blob that went missing.** A request whose hash entries name a blob the API does not hold is refused with a `422` `request.blobs_missing` whose `params.missing_blobs` lists them all, which also releases a start's idempotency key. A request built from a `directory`, `contents` or `tree` uploads every blob it names first, so it meets this only if a blob was removed between the upload and the request. The client answers it as `mthds`'s client does: when the listed blobs are ones the tree holds, it asks the blob route for them again, uploads them and sends the request once more; any other answer is thrown, so sending the same call again asks the blob route afresh and uploads what is missing.
58
58
 
59
59
  On the crate routes, `read_scope` is the scope the blob route is asked under, so the blobs are uploaded into the store the request's hash entries are read from; the hosted platform reads the caller's organization whatever the request says ([crate-routes.md](./crate-routes.md#the-shared-envelope)).
60
60
 
61
61
  ## The files a step reads
62
62
 
63
- A file a step reads, such as a `PipeDocGen` step's `template_file` (the Word, Excel or PowerPoint template it fills, or the HTML template it prints as a PDF), is a file of the tree like any other, at the tree path the step names from the root. A `directory` sends it with the bundles; a `contents` or `files` map carries it under its tree path. A step that names a file the tree does not hold cannot load: on `validate`, `pipeIo`, `resolve` and `codegen` that is an `is_valid: false` verdict whose validation error says the file is missing, `prepareInputs` throws it as a `MethodLoadError`, and on `execute` and `start` it is a `422` `ValidateBundleError`, whose `validationErrors` say the same.
63
+ A file a step reads, such as a `PipeDocGen` step's `template_file` (the Word, Excel or PowerPoint template it fills, or the HTML template it prints as a PDF), is a file of the tree like any other, at the tree path the step names from the root. A `directory` sends it with the bundles; a `contents` or `files` map carries it under its tree path. A step that names a file the tree does not hold cannot load: on `validate`, `pipeIo`, `resolve` and `codegen` that is an `is_valid: false` verdict whose problem item says the file is missing (`method.resource_missing`), `prepareInputs` throws it as a `MethodLoadError`, and on `execute` and `start` it is a `422` problem whose `errors` say the same.
64
64
 
65
65
  ### The template check's warnings
66
66
 
67
- A dry run hands an office template file of the tree 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:
67
+ A dry run hands an office template file of the tree to the document engine's template check. An error finding fails the step's dry run, and so the verdict, as a problem item 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 problem item coded `pipelex.docgen.template_warning`:
68
68
 
69
69
  | Member | Value |
70
70
  | --- | --- |
71
- | `category` | `dry_run` |
72
- | `error_type` | `template_check_warning` |
73
- | `pipe_code`, `domain_code` | The `PipeDocGen` step |
74
- | `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 |
75
- | `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 |
76
- | `message` | The finding in words |
71
+ | `code` | `pipelex.docgen.template_warning` |
72
+ | `location.pipe` | The `PipeDocGen` step |
73
+ | `params.template` | The template file as the step names it |
74
+ | `params.finding` | The finding in the engine's words |
75
+ | `detail` | The sentence a person reads, naming the template and the finding |
77
76
 
78
- 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`.
77
+ The warnings come in families, always in this order: `method.optional_force_redundant`, `method.input_presence_vacuous`, the hint lints (`method.hint_invalid`), then `pipelex.docgen.template_warning`. None of them flips `is_valid`.
79
78
 
80
79
  ```ts
81
80
  if (verdict.is_valid) {
82
81
  for (const warning of verdict.warnings) {
83
- if (warning.error_type === "template_check_warning") {
84
- console.warn(`${warning.template_file ?? "?"} ${warning.template_location ?? ""}: ${warning.message}`);
82
+ if (warning.code === "pipelex.docgen.template_warning") {
83
+ console.warn(`${warning.location?.pipe ?? "?"}: ${warning.detail}`);
85
84
  }
86
85
  }
87
86
  }
@@ -127,11 +126,11 @@ The save is refused, with the `ApiResponseError` of its problem, when the platfo
127
126
 
128
127
  | Status and `code` | When |
129
128
  |---|---|
130
- | `422` `invalid_method_files` | The map breaks the standard's rules, a path that is not a tree path or two paths that collide among them; the problem's `tree_refusal` names the rule. |
131
- | `422` `method_tree_too_large` | The tree passes one of the deployment's limits, which the problem's `limit` names. |
132
- | `422` `blob_mismatch` | A hash entry states another size than the blob the organization holds under that hash. |
133
- | `422` `missing_blobs` | A hash entry names bytes the organization does not hold (above). |
134
- | `413` `payload_too_large` | The method would leave no room for the summary a publish adds. |
129
+ | `422` `method.package_invalid` | The map breaks the standard's rules, a path that is not a tree path or two paths that collide among them; the problem names the path. |
130
+ | `422` `method.package_invalid` | The tree passes one of the deployment's limits, which the problem's `params` name (`reason` `too_large` and the `limit`). |
131
+ | `422` `request.invalid` | A hash entry states another size than the blob the organization holds under that hash. |
132
+ | `422` `request.blobs_missing` | A hash entry names bytes the organization does not hold (above). |
133
+ | `413` `request.too_large` | The method would leave no room for the summary a publish adds. |
135
134
 
136
135
  ## The deployment's limits
137
136
 
@@ -162,7 +161,7 @@ An empty draft lists no file and has a `draft_digest` of `null`. The listing pag
162
161
 
163
162
  **A link is a bearer credential.** Anyone holding it reads the file until it expires, so it belongs in no log, message or stored record; this SDK puts it in none of its errors, not even when the answer that carried it cannot be read. A read that fails because a link expired succeeds once the method is read again.
164
163
 
165
- A method whose draft names a tree the platform holds no record of answers `getMethod` with a `409` whose verdict is `runtime`, and a version stored before methods were trees and never rewritten as one names no tree to serve, so `getMethodVersion` answers it with `409` `conflict`, whose verdict is `runtime` too. An answer whose `files` is not a listing, each entry with its string `path`, `url` and `expires_at`, a `sha256` of 64 lower-case hex digits and a non-negative integer `size`, is an `ApiResponseError` naming the entry at fault. Such an answer, like any other the SDK cannot read from these two routes or from `POST /v1/blobs/missing`, reaches its error redacted: `responseBody` has every `url` member and every link's query replaced by `[redacted]`, and the error keeps no `cause`, since a parse failure can quote the body.
164
+ A method whose draft names a tree the platform holds no record of answers `getMethod` with a `500` `runtime.unexpected`, and a version stored before methods were trees and never rewritten as one names no tree to serve, so `getMethodVersion` answers it with `500` `runtime.unexpected` too. An answer whose `files` is not a listing, each entry with its string `path`, `url` and `expires_at`, a `sha256` of 64 lower-case hex digits and a non-negative integer `size`, is an `ApiResponseError` naming the entry at fault. Such an answer, like any other the SDK cannot read from these two routes or from `POST /v1/blobs/missing`, reaches its error redacted: `responseBody` has every `url` member and every link's query replaced by `[redacted]`, and the error keeps no `cause`, since a parse failure can quote the body.
166
165
 
167
166
  ## Writing a tree to disk
168
167
 
@@ -179,16 +178,16 @@ Each file is downloaded through its link, at most eight at a time, with no crede
179
178
 
180
179
  ## When a transfer fails
181
180
 
182
- `MethodFileTransferError` is thrown when a file could not be moved between this machine and storage. `operation` says which way (`upload` for a request or a save that sends a tree by hash, `download` for `writeMethodTree`), `code` why, `sha256` which bytes, `path` the tree path when one is known, and `status` storage's HTTP status when it answered. Neither its message nor any member carries the link, nor storage's error body, which can echo the signed request, and it keeps no `cause`: the failure `fetch` throws for a request that got no answer can quote the URL, so only its network code (`ECONNREFUSED`, `ABORT_TIMEOUT`) reaches the message.
181
+ `MethodFileTransferError` is thrown when a file could not be moved between this machine and storage. `operation` says which way (`upload` for a request or a save that sends a tree by hash, `download` for `writeMethodTree`), `reason` why, `sha256` which bytes, `path` the tree path when one is known, and `status` storage's HTTP status when it answered. Neither its message nor any member carries the link, nor storage's error body, which can echo the signed request, and it keeps no `cause`: the failure `fetch` throws for a request that got no answer can quote the URL, so only its network code (`ECONNREFUSED`, `ABORT_TIMEOUT`) reaches the message.
183
182
 
184
- | `code` | When | Retryable |
183
+ | `reason` | When | Its problem |
185
184
  |---|---|---|
186
- | `unreachable` | Storage gave no answer: the connection failed or the time limit ran out. | yes |
187
- | `storage_error` | Storage timed out or throttled the request (`408`, `429`) or failed (a `5xx` other than `501`). | yes |
188
- | `refused` | Storage refused the request with any other status, an expired link among them. Read the method again for fresh links. | no |
189
- | `mismatch` | A download's bytes are not the file the listing names: another size, or another SHA-256. | yes |
185
+ | `unreachable` | Storage gave no answer: the connection failed or the time limit ran out. | `runtime.unavailable` |
186
+ | `storage_error` | Storage timed out or throttled the request (`408`, `429`) or failed (a `5xx` other than `501`). | `runtime.unavailable` |
187
+ | `refused` | Storage refused the request with any other status, an expired link among them. Read the method again for fresh links. | `client.response_unreadable` naming the status, `runtime.unsupported` for a `501` |
188
+ | `mismatch` | A download's bytes are not the file the listing names: another size, or another SHA-256. | `client.response_unreadable` |
190
189
 
191
- Its `errorDomain` is always `runtime`. An upload to a target that asks for a create-only write (`If-None-Match`) and answers `412` is not a failure: the bytes are already stored.
190
+ An upload to a target that asks for a create-only write (`If-None-Match`) and answers `412` is not a failure: the bytes are already stored.
192
191
 
193
192
  ## What changed from the source fields
194
193
 
@@ -56,21 +56,19 @@ The write sends no `Idempotency-Key`, as `renameOrganization` sends none. A late
56
56
 
57
57
  ## When a write is refused
58
58
 
59
- A refused write changes nothing, whichever part of it was refused, and throws an `ApiResponseError` whose `code` is the platform's; only a `503` leaves it unknown whether the write landed:
59
+ A refused write changes nothing, whichever part of it was refused, and throws an `ApiResponseError` whose `problem` is the platform's problem document; only a `503` leaves it unknown whether the write landed:
60
60
 
61
61
  | Status | `code` | When |
62
62
  |---|---|---|
63
- | `403` | `forbidden` | `orgId` is not the caller's active organization. |
64
- | `403` | `role_not_permitted` | The caller is not the organization's admin. |
65
- | `404` | `not_found` | The organization does not exist. |
66
- | `409` | `conflict` | The organization is a personal workspace, which has no branding. |
67
- | `422` | `validation_failed` | A colour is not `#rrggbb`, the body holds a key the route does not take, or a logo was refused. |
68
- | `400` | `invalid_storage_uri` | A logo's URI is not a storage URI. |
69
- | `403` | `forbidden` | A logo's URI is a file of another organization. |
70
- | `500` | `storage_backend_failed` | The platform's storage failed while reading or copying a logo. Its verdict is `runtime` and retryable, and nothing was written. |
71
- | `503` | `service_unavailable` | The platform's database failed or could not be reached, so the write may have landed or not. Its verdict is `runtime` and retryable: the same `input` sent again is safe, and `getBranding` reads what the branding holds. |
72
-
73
- **A refusal about a logo or a colour names it in `errors`**, the platform's field-level list, so the settings screen shows each sentence under the control it concerns rather than in a toast. Each entry's `field` is the slot, `square_logo` or `wide_logo`, or the colour's dotted key, such as `colors.action` or `dark_colors.header`; its `detail` is the sentence to show. A logo the platform read and refused carries a `LogoRefusalCode`, one entry per broken rule, both slots answered in one refusal:
63
+ | `403` | `auth.forbidden` | The caller is not the organization's admin. |
64
+ | `403` | `pipelex.org.mismatch` | `orgId` is not the caller's active organization, or a logo's URI is a file of another organization. |
65
+ | `404` | `pipelex.org.not_found` | The organization does not exist. |
66
+ | `409` | `pipelex.org.not_manageable` | The organization is a personal workspace, which has no branding. |
67
+ | `422` | `request.invalid` | A colour is not `#rrggbb`, the body holds a key the route does not take, or a logo was refused. |
68
+ | `422` | `pipelex.storage.uri_invalid` | A logo's URI is not a storage URI. |
69
+ | `503` | `runtime.unavailable` | The platform's storage failed while reading or copying a logo, or its database failed or could not be reached, so the write may have landed or not. Its `retry` is `yes`: the same `input` sent again is safe, and `getBranding` reads what the branding holds. |
70
+
71
+ **A refusal about a logo or a colour names it in its problem's `errors`**, one problem item per refused logo or colour, so the settings screen shows each sentence under the control it concerns rather than in a toast. Each item's `location.field` is the slot, `square_logo` or `wide_logo`, or the colour's dotted key, such as `colors.action` or `dark_colors.header`; its `detail` is the sentence to show. A logo the platform read and refused carries one item per broken rule, both slots answered in one refusal, each item's `code` one `LogoRefusalCode` names:
74
72
 
75
73
  | `code` | Example `detail` |
76
74
  |---|---|
@@ -85,16 +83,16 @@ A refused write changes nothing, whichever part of it was refused, and throws an
85
83
  | `logo_not_wide` | The wide logo is 600 × 500 px, 1.2 times as wide as it is tall; a wide logo must be at least 1.5 times as wide as it is tall. |
86
84
  | `logo_too_short` | The wide logo is 240 × 48 px; a wide logo must be at least 64 px tall. |
87
85
 
88
- The two refusals of a logo URI that come before any file is read, `invalid_storage_uri` and another organization's `forbidden`, name the slot the same way, with the platform's code in the entry. A colour's entry carries pydantic's code, such as `string_pattern_mismatch`. Reading one slot's sentences is the same filter in every case:
86
+ The two refusals of a logo URI that come before any file is read, `pipelex.storage.uri_invalid` and another organization's `pipelex.org.mismatch`, name the slot the same way, with the refusal's code on the item. Reading one slot's sentences is the same filter in every case:
89
87
 
90
88
  ```ts
91
89
  try {
92
90
  await client.updateOrganizationBranding(orgId, { square_logo: uri });
93
91
  } catch (err) {
94
92
  if (!(err instanceof ApiResponseError)) throw err;
95
- const underSquare = (err.errors ?? [])
96
- .filter((entry) => entry.field === "square_logo")
97
- .map((entry) => entry.detail);
93
+ const underSquare = (err.problem.errors ?? [])
94
+ .filter((item) => item.location?.field === "square_logo")
95
+ .map((item) => item.detail);
98
96
  // Show `underSquare` under the square logo's slot; an error naming no field goes in a toast.
99
97
  }
100
98
  ```
@@ -43,7 +43,7 @@ const results = await client.waitForResult(runId, { artifacts: ["working_memory"
43
43
 
44
44
  **Absent versus null.** With a selection, an artifact the caller did not name is absent from the result, reading `undefined`, while one it named that the run never wrote is `null`. So `undefined` means "not asked for" and `null` means "asked for, not there". That is also why `main_stuff` is declared optional: it is never null on a completed run, but it is absent from a read that left it out, and `MissingMainStuffError` is thrown only by a read that asked for it — one with no selection, or one naming `main_stuff`.
45
45
 
46
- An empty selection or a name outside the list is a `RequestArgumentError` thrown before any request, `input` and not retryable, the same refusal the platform would answer with a `400`. `waitForResult` and `startAndWaitForResult` take the same `artifacts` in their poll options and send it on every poll; `startAndWaitForResult` refuses a bad selection before it starts the run. Against a bare runner the selection has nothing to narrow: the blocking fallback returns every artifact the runner sent. `downloadArtifacts` given a `run_id` asks for its scope's artifact alone.
46
+ An empty selection or a name outside the list is a `RequestArgumentError` thrown before any request, `client.argument_invalid`, the same refusal the platform would answer with a `400`. `waitForResult` and `startAndWaitForResult` take the same `artifacts` in their poll options and send it on every poll; `startAndWaitForResult` refuses a bad selection before it starts the run. Against a bare runner the selection has nothing to narrow: the blocking fallback returns every artifact the runner sent. `downloadArtifacts` given a `run_id` asks for its scope's artifact alone.
47
47
 
48
48
  ## `pipeline_run_id` — the durable handle
49
49
 
@@ -79,7 +79,7 @@ It is never called on the blocking path, a bare runner's `POST /v1/execute` or t
79
79
 
80
80
  **Knowing whether a run may exist.** `onStarting`, the other callback `StartAndWaitForResultOptions` takes, is called right before each request that may create a run is sent: `POST /v1/start`, and the blocking `POST /v1/execute` of a bare runner or of the fallback to it, so it is called twice when a runner that looked hosted refuses the start. Until it is called no run exists, and once the `signal` has aborted it is not called and no such request is sent, so a caller that stops waiting before then can say that no run was started; from then until `onStarted`, a run may exist that the API has not yet named. It runs synchronously and its return value is ignored; an exception it throws propagates before the request is sent. The `pipelex-sdk` command words its interrupt message by it ([`cli.md`](cli.md)).
81
81
 
82
- **A failed start says whether a run may exist.** An error from the start, or from the blocking execute, carries `runMayHaveStarted`: `true` when the request may have created a run although the call failed, so a person looks the run up in the run history before starting it again, and then its `retryable` is `false` ([`errors.md`](errors.md#a-failed-run-start--runmayhavestarted)). Once `onStarted` has been called the run exists and the caller holds its id, so an error while the composite waits started nothing: its `runMayHaveStarted` is `false`, and its `retryable` is about waiting again on that id with `waitForResult`, never about calling `startAndWaitForResult` again, which would start a second run.
82
+ **A failed start says whether a run may exist.** An error from the start, or from the blocking execute, whose request may have created a run although the call failed is `client.outcome_unknown`, its `retry` being `outcome_unknown`, so a person looks the run up in the run history before starting it again ([`errors.md`](errors.md#a-run-that-may-have-started--outcome_unknown)). Once `onStarted` has been called the run exists and the caller holds its id, so an error while the composite waits started nothing: its `retry` is about waiting again on that id with `waitForResult`, never about calling `startAndWaitForResult` again, which would start a second run.
83
83
 
84
84
  ## A degraded status — `degraded` and `degradation`
85
85
 
@@ -90,7 +90,7 @@ It is never called on the blocking path, a bare runner's `POST /v1/execute` or t
90
90
  | `reason` | What holds | `status` | What to tell the person |
91
91
  |---|---|---|---|
92
92
  | `status_unavailable` | The platform could not reach the workflow engine. | The last one recorded. | Nothing about the run, which may be fine: the status is not fresh, and the next read comes after `retry_after_seconds`. |
93
- | `workflow_task_failing` | The workflow task that drives the run has failed `failed_attempts` times in a row since `failing_since`, so the run is not making progress. | Live. | The run is stuck. The workflow engine keeps retrying, and if the task is still failing at `ends_at` the platform ends the run `FAILED`, with a `WorkflowTaskKeptFailingError` report ([errors.md](./errors.md#a-failed-run--runerrorreport)). |
93
+ | `workflow_task_failing` | The workflow task that drives the run has failed `failed_attempts` times in a row since `failing_since`, so the run is not making progress. | Live. | The run is stuck. The workflow engine keeps retrying, and if the task is still failing at `ends_at` the platform ends the run `FAILED`, with a problem of its own ([errors.md](./errors.md#a-failed-run--runfailederror)). |
94
94
 
95
95
  ```ts
96
96
  const run = await client.getRunStatus(runId);
@@ -138,7 +138,7 @@ const results = await client.waitForResult(runId, {
138
138
  - **`startAndWaitForResult` honours it on the durable path only.** Its blocking fallback, a bare runner's `POST /v1/execute`, has no run to read, so `onProgress` is never called there.
139
139
  - **The callback runs synchronously** and its return value is ignored. An exception it throws propagates out of the wait, as one thrown by `onPoll` does, and the run keeps going on the server.
140
140
 
141
- **Reading it yourself: `getRunProgress(runId)`.** It returns the snapshot as the platform answered it, the `RunProgress`. A pending run, and a run that has written no progress event yet, answer with no invocation. When the workflow engine cannot be reached the read still answers `200`: its `degradation` reads `status_unavailable`, its `status` is the last one recorded and no invocation carries a health annotation. A run that does not exist, or belongs to another organization, is the platform's `404`, an `ApiResponseError`, and a server without the route, a bare runner or a hosted API older than it, is a `RunLifecycleUnavailableError`. The members the freshness rule and a timeline lean on are checked: an answer whose `version` is not a non-negative integer, whose `pipeline_run_id`, `mode`, `status`, `assembled_at` or `summary` is not a string, whose `invocations` is not a list, or one of whose invocations misses its path, pipe identity, status or times, is an `ApiResponseError` for an answer the SDK cannot read. The snapshot's `degradation` and each invocation's are checked field by field as the status read's are, and one whose `reason` is not a string is dropped, so neither fails the read. Members the SDK does not name are relayed as sent.
141
+ **Reading it yourself: `getRunProgress(runId)`.** It returns the snapshot as the platform answered it, the `RunProgress`. A pending run, and a run that has written no progress event yet, answer with no invocation. When the workflow engine cannot be reached the read still answers `200`: its `degradation` reads `status_unavailable`, its `status` is the last one recorded and no invocation carries a health annotation. A run that does not exist, or belongs to another organization, is the platform's `404`, an `ApiResponseError`, and a server without the route, a bare runner or a hosted API older than it, is a `RunLifecycleUnavailableError`. The members the freshness rule and a timeline lean on are checked: an answer whose `version` is not a non-negative integer, whose `pipeline_run_id`, `mode`, `status`, `assembled_at` or `summary` is not a string, whose `invocations` is not a list, or one of whose invocations misses its path, pipe identity, status or times, is an `ApiResponseError` for an answer the SDK cannot read. The snapshot's `degradation` and each invocation's are checked field by field as the status read's are, and one whose `reason` is not a string is dropped; an invocation's `last_failure` is kept only when its `problem` is a problem document (`isProblem` in `mthds/protocol`), and its `at` only when it is a string. So no health annotation fails the read. Members the SDK does not name are relayed as sent.
142
142
 
143
143
  ### The snapshot
144
144
 
@@ -169,7 +169,7 @@ An **invocation** is one run of one pipe inside the run, named by its `path`: th
169
169
  | `batch` | `ProgressBatch`, optional | On a `PipeBatch` that has fanned out, `{ total, done, running, failed }`: `done` counts the items succeeded or skipped, and an item scheduled or canceled is in none of the three. |
170
170
  | `condition` | `ProgressCondition`, optional | On a `PipeCondition` that has resolved, `{ selected_outcome }`, the key its value matched, or `null` for its default outcome. |
171
171
  | `attempts` | `number`, optional | Health: how many times its work has been tried, present only when more than once. |
172
- | `last_failure` | `ProgressFailure`, optional | Health: `{ message, at? }`, the latest failure of its work. `message` keeps the disclosure of a run's error report: the failure's own text only when it was written for the caller, `An internal error occurred.` otherwise. |
172
+ | `last_failure` | `ProgressFailure`, optional | Health: `{ problem, at? }`, the latest failure of its work. `problem` is the standard's `Problem`, in the caller's view as the run reads serve a failed run's problem: the problem that ended the invocation when it is `failed`, and otherwise the problem of the latest failure of an attempt its engine is trying again, `runtime.unexpected` for a failure only the engine observed. Branch on its `code`, never on the wording of its `detail`. `at` is when it failed, absent when not recorded. |
173
173
  | `degradation` | `RunDegradation`, optional | Health: the run is stuck here, in the run status's own words — the status read's `degradation`, `workflow_task_failing`, placed on the run's root or on the batch item whose execution keeps failing to advance. |
174
174
 
175
175
  The health annotations are what the workflow engine reports, so an absent one means that nothing was observed, never that the engine looked and found all well. They reuse the run status's `RunDegradation` rather than a shape of their own, so the code that shows a degraded status shows a stuck invocation too.
@@ -221,14 +221,14 @@ switch (state.state) {
221
221
  console.log(`not finished — poll again in ${state.retry_after_seconds ?? 2}s`);
222
222
  break;
223
223
  case "failed":
224
- // `message` already names the reason; `error` is the run's stored report, or null.
224
+ // `problem` is `run.failed`; `error` is the run's own problem, or null.
225
225
  console.error(`run ended as ${state.status}: ${state.message}`);
226
- if (state.error?.user_action) console.error(`next step: ${state.error.user_action.detail}`);
226
+ if (state.error) console.error(`${state.error.code}: ${state.error.next_step.detail}`);
227
227
  break;
228
228
  }
229
229
  ```
230
230
 
231
- `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.
231
+ `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, `problem`, the platform's `run.failed` document, and the run's own problem, which `run.failed` nests as `run_error`: the arm's `error`, `null` when the run ended without one, and the error's `runError`. [`errors.md`](./errors.md#a-failed-run--runfailederror) describes both.
232
232
 
233
233
  ## `working_memory` — every named stuff of the run
234
234
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/sdk",
3
- "version": "0.52.0",
3
+ "version": "0.53.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": {
@@ -1,302 +0,0 @@
1
- /**
2
- * Error-report wire models — a failed run's stored report, and the typed members of a
3
- * problem document.
4
- *
5
- * **The runner owns these shapes; this SDK follows them.** A run that fails reports why as
6
- * the runner's `ErrorReport` (`pipelex.base_exceptions`), which the hosted platform stores
7
- * whole on the run row as `error` and serves through the runner's STRICT disclosure: on the
8
- * status read (`RunRead.error`), on the run records (`PipelineRun.error`,
9
- * `RunHistoryItem.error`), and inside the problem document of the results read's `409`, where
10
- * the client lifts it onto the failed arm of
11
- * `RunResultState` and onto `RunFailedError.error`. The same classification fields
12
- * (`error_domain`, `user_action`, …) ride a runner-rendered problem document as extension
13
- * members, which is why `ApiResponseError` types its members with the models declared here.
14
- *
15
- * Every field is optional and every interface keeps an index signature, for the reason
16
- * `TokensUsageRecord` gives: the runner adds fields without asking this SDK, and a field this
17
- * version does not name still reaches the caller. The enum-ish fields (`error_domain`,
18
- * `error_category`, `user_action.kind`) are open sets on the wire and stay `string`, never
19
- * frozen unions, so a value the runner adds is not an SDK break; their known values are listed
20
- * where they are declared.
21
- *
22
- * **A stored report is checked, not cast.** Every read that hands one back runs it through
23
- * `readRunErrorReport`, which keeps each named field only when it has the type declared here and
24
- * reads it as absent otherwise, as the Python twin does: a report written by an older runner, or
25
- * stored by hand, cannot hand a consumer a number where a string is typed. The client checks the
26
- * nested members of a problem document with the same readers, so `ApiResponseError`'s
27
- * `providerMetadata`, `migration`, `validationErrors` and `errors` hold to their types as a stored
28
- * report's do.
29
- *
30
- * **The platform serves the STRICT projection; this SDK strips nothing further.** The run row
31
- * keeps the runner's VERBOSE report as the operators' record, and every read that hands it to a
32
- * caller serves the rule the hosted runner applies to an error on `execute`: `error_type`,
33
- * `title`, `type_uri`, `error_domain`, `error_category`, `retryable` and `validation_errors`
34
- * pass through; `message` and `user_action` pass through only when `caller_facing_message` is
35
- * `true`, which the served report then keeps, and otherwise `message` is the placeholder
36
- * `An internal error occurred.` while `user_action` and the flag are absent; `provider_metadata`
37
- * keeps only `status_code` and `retry_after_seconds`; `model`, `provider` and `migration` are
38
- * absent. The reports the platform writes itself are caller-facing copy, so they keep their
39
- * message and their next step. A platform run locally with `ERROR_DISCLOSURE=verbose` serves the
40
- * report whole, which is why the types still name every member the runner writes. Deciding what
41
- * a person should see is each consumer's presentation; the report arrives here as served,
42
- * members this SDK does not name included.
43
- */
44
- import type { ValidationErrorItem } from "./models.js";
45
- /**
46
- * The next step an error advises — the runner's `UserAction`, carried as `user_action` on a
47
- * stored report and on a runner's problem document. `kind` names the category of advice, so a
48
- * consumer can render consistent guidance; `detail` is the advice itself, in words (a billing
49
- * URL, a retry hint, the model to change).
50
- *
51
- * Known `kind` values: `wait_and_retry`, `check_billing`, `check_credentials`, `change_input`,
52
- * `change_model`, `contact_support`, `unknown`. The set is the runner's, so it is typed open.
53
- * It is the hosted plane's own member, which predates the standard's problem document: there the
54
- * next step is the problem's `next_step`, whose `kind` is a closed list of its own.
55
- */
56
- export interface UserAction {
57
- kind: string;
58
- detail: string;
59
- }
60
- /**
61
- * What the inference provider's SDK said about a failed call — the runner's
62
- * `ProviderErrorMetadata`. Present on a report whose failure came back from a model provider.
63
- * `message` is the provider SDK's own text, relayed raw; the provider's response body never
64
- * crosses the wire, because the runner excludes it from every serialization. A hosted run read
65
- * keeps only `status_code` and `retry_after_seconds`, the client hints; the other members reach a
66
- * caller only from a server that discloses errors VERBOSE, such as a platform run locally with
67
- * `ERROR_DISCLOSURE=verbose`.
68
- *
69
- * **The hosted store turns its numbers into strings.** A report read back from the platform's
70
- * run store carries `status_code` and `retry_after_seconds` as JSON strings (`"404"`,
71
- * `"1.5"`), because the store hands its numbers back as decimals that the platform serializes
72
- * as strings. A problem document rendered by a runner carries them as numbers. Both are typed,
73
- * so read them with `Number(...)` when you need the value.
74
- */
75
- export interface ProviderErrorMetadata {
76
- provider?: string | null;
77
- sdk_exception_type?: string | null;
78
- message?: string | null;
79
- /** The provider's HTTP status, when it answered one. */
80
- status_code?: number | string | null;
81
- /** The provider's own request id — what its support desk asks for. */
82
- request_id?: string | null;
83
- retry_after_seconds?: number | string | null;
84
- provider_error_code?: string | null;
85
- [extension: string]: unknown;
86
- }
87
- /**
88
- * A pending configuration migration that explains the failure — the runner's
89
- * `MigrationErrorBlock`. Present only on a configuration failure whose raiser scanned the
90
- * host's configuration directories; a consumer branches on its presence. `plans` is carried
91
- * opaquely: it is the shape `pipelex-agent migrate --dry-run --format json` emits, which no
92
- * published package declares.
93
- */
94
- export interface MigrationErrorBlock {
95
- /** The command that applies whatever can be applied without a decision. */
96
- remedy?: string | null;
97
- /** Whether running `remedy` would rewrite any file. */
98
- would_write?: boolean | null;
99
- /** Whether something here is a person's to resolve rather than the tool's. */
100
- needs_attention?: boolean | null;
101
- plans?: Record<string, unknown>[] | null;
102
- [extension: string]: unknown;
103
- }
104
- /**
105
- * Why a run failed — the runner's `ErrorReport`, typed with every field it carries.
106
- *
107
- * The one type for a failed run's report wherever the SDK hands it back: `RunPublic.error`
108
- * (and so `RunRead.error` on the status read), `PipelineRun.error` on the run records, `error`
109
- * on the failed arm of `RunResultState` (the results read's `409`), and `RunFailedError.error`
110
- * when `waitForResult`, `startAndWaitForResult` or an artifact download throws for a run that
111
- * ended without a result.
112
- *
113
- * **Branch on `error_domain`, `type_uri` and `retryable`**, never on the wording of `message`.
114
- * `error_type` is the runner's open-ended exception class name: finer than `error_domain`,
115
- * useful in a support line, but not a closed set to match against. Which fault's identity a run
116
- * failure's report carries, and how its `message` names the failing pipe, is the runner's to
117
- * decide; read them as given.
118
- *
119
- * The runner writes the report of a run that fails while it runs. A run the platform ends or
120
- * settles itself carries one the platform wrote in the same shape, as caller-facing copy:
121
- * `WorkflowTaskKeptFailingError` for a workflow task that kept failing (the run ends `FAILED`,
122
- * not `TERMINATED`), `RunTimedOutError` for a timeout and `RunWorkflowNotFoundError` for a
123
- * workflow that vanished. A `CANCELLED`, `TERMINATED` or `TIMED_OUT` run holds a report when a
124
- * completion callback carried one, and a `TIMED_OUT` run otherwise holds the platform's timeout
125
- * report. A report is `null` on a completed run and where a run has none — one whose start
126
- * failed, a blocking `execute` run, whose answer carried its reason, one cancelled, terminated or
127
- * given its status by hand with no completion callback carrying a report, one that ended before
128
- * the platform wrote reports of its own — so the absence of a report says nothing about why.
129
- *
130
- * What the platform serves of a report is the runner's STRICT projection (see the module
131
- * comment), so the members the projection drops are absent on a hosted read.
132
- */
133
- export interface RunErrorReport {
134
- /**
135
- * The runner's exception class name (`LLMCompletionError`, `PipeRunError`, …) — an open
136
- * set, for display and support, not for branching.
137
- */
138
- error_type?: string | null;
139
- /**
140
- * What went wrong. The platform serves the author's copy only when `caller_facing_message` is
141
- * `true`, and the placeholder `An internal error occurred.` otherwise.
142
- */
143
- message?: string | null;
144
- /** A stable human label for the error class (`LLM completion`). */
145
- title?: string | null;
146
- /** The stable URI naming the error class — a branch field, and where its documentation lives. */
147
- type_uri?: string | null;
148
- /**
149
- * Who can fix it, the coarse branch field. Known values: `input` (the caller — a malformed
150
- * bundle, a bad input), `config` (a configuration change — a model not served, a missing
151
- * secret), `runtime` (nobody beforehand — a provider outage during execution).
152
- */
153
- error_domain?: string | null;
154
- /**
155
- * A finer classification of an inference failure, when the runner has one. Known values:
156
- * `transient`, `configuration`, `content`, `capacity`, `ambiguous`, `unknown`.
157
- */
158
- error_category?: string | null;
159
- /** Whether retrying the same run can succeed. Absent or `null` means unknown, never "no". */
160
- retryable?: boolean | null;
161
- /** The next step the runner advises. The platform serves it only beside caller-facing copy. */
162
- user_action?: UserAction | null;
163
- /**
164
- * The model the failing call used, when the failure is an inference failure. Absent on a
165
- * hosted read, whose STRICT projection drops it.
166
- */
167
- model?: string | null;
168
- /**
169
- * The provider the failing call reached, when the failure is an inference failure. Absent on
170
- * a hosted read, whose STRICT projection drops it.
171
- */
172
- provider?: string | null;
173
- /**
174
- * What the provider's SDK said, when the failure came back from a provider. A hosted read
175
- * keeps only its `status_code` and `retry_after_seconds`.
176
- */
177
- provider_metadata?: ProviderErrorMetadata | null;
178
- /**
179
- * True when `message` was written as caller-facing copy. The runner emits it only when true,
180
- * and the platform keeps it on a caller-facing report and leaves it out otherwise, so it is how
181
- * a consumer tells the author's copy from the placeholder.
182
- */
183
- caller_facing_message?: boolean | null;
184
- /**
185
- * The structured diagnostics of a bundle that failed validation — the same items the
186
- * validate report and a `422`'s `ApiResponseError.validationErrors` carry.
187
- */
188
- validation_errors?: ValidationErrorItem[] | null;
189
- /**
190
- * A pending configuration migration that explains the failure. Absent on a hosted read, whose
191
- * STRICT projection drops it.
192
- */
193
- migration?: MigrationErrorBlock | null;
194
- /** Fields a newer runner adds, reachable before this SDK names them. */
195
- [extension: string]: unknown;
196
- }
197
- /**
198
- * One field-level failure of a request — an item of the platform problem document's
199
- * `errors[]`. `field` is the dotted path to the offending attribute, `code` a stable sub-code
200
- * (`invalid_format`, `out_of_range`, …), `detail` optional human text.
201
- */
202
- export interface FieldError {
203
- field?: string | null;
204
- code?: string | null;
205
- detail?: string | null;
206
- [extension: string]: unknown;
207
- }
208
- /**
209
- * The members of an RFC 9457 problem document that `ApiResponseError` exposes beyond its
210
- * message, as the client parsed them: the standard's own `type`, `title` and `instance`, the
211
- * request id, and the extension members the runner and the platform add to classify the
212
- * failure. Every member is optional — a server sends what it knows, and a body that is not a
213
- * problem document carries none of them.
214
- *
215
- * These are the hosted plane's members, which predate the standard's problem document: the
216
- * standard's reading of the same answer is `ApiResponseError.problem`, and the two never share a
217
- * slot, so the platform's field-level `errors` here and the problem items of `problem.errors`
218
- * stay apart.
219
- */
220
- export interface ProblemDetails {
221
- /** RFC 9457 `type` — the stable URI of the error class. */
222
- type?: string;
223
- /** RFC 9457 `title` — the short human label of the error class. */
224
- title?: string;
225
- /** RFC 9457 `instance` — the occurrence (a request path or a request URN). */
226
- instance?: string;
227
- /** The request's correlation id: the body's `request_id`, else the `X-Request-ID` response header. */
228
- requestId?: string;
229
- /** The body's `error_domain` — `input`, `config` or `runtime`. */
230
- errorDomain?: string;
231
- /** The body's `error_category` — the finer classification of an inference failure. */
232
- errorCategory?: string;
233
- /** The body's `retryable` — whether retrying the same request can plausibly succeed. */
234
- retryable?: boolean;
235
- /** The body's `user_action` — what the caller should do next. */
236
- userAction?: UserAction;
237
- /** The body's `model` — the model an inference failure used. */
238
- model?: string;
239
- /** The body's `provider` — the provider an inference failure reached. */
240
- provider?: string;
241
- /** The body's `provider_metadata` — what the provider's SDK said. */
242
- providerMetadata?: ProviderErrorMetadata;
243
- /** The body's `migration` — a pending configuration migration that explains the failure. */
244
- migration?: MigrationErrorBlock;
245
- /** The platform's field-level `errors[]`. */
246
- errors?: FieldError[];
247
- }
248
- /** Whether a value is a plain object: an object that is neither `null` nor an array. */
249
- export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
250
- /**
251
- * Drop each of `fields` from `record` when it is present and fails `fits`. `null` always fits,
252
- * since every field of these shapes allows it. Internal: the run status read's degradation
253
- * (`readRunDegradation` in `runs.ts`) is checked with it too.
254
- */
255
- export declare function dropUnfit(record: Record<string, unknown>, fields: readonly string[], fits: (value: unknown) => boolean): void;
256
- /**
257
- * `provider_metadata`, its named string fields kept as strings and `status_code` and
258
- * `retry_after_seconds` as a number or a string; its other members are relayed as sent.
259
- */
260
- export declare function readProviderMetadata(value: Record<string, unknown>): ProviderErrorMetadata;
261
- /**
262
- * `migration`, its `remedy` kept as a string, `would_write` and `needs_attention` as booleans, and
263
- * `plans` as an array keeping only its objects; its other members are relayed as sent.
264
- */
265
- export declare function readMigration(value: Record<string, unknown>): MigrationErrorBlock;
266
- /**
267
- * A validation item is kept when it is an object with a string `category` and `message`, its other
268
- * members relayed as sent. It checks a problem document's items and a stored report's only:
269
- * `validate()` and the crate routes hand their reports' items back unchecked.
270
- */
271
- export declare function isValidationItem(value: unknown): value is ValidationErrorItem;
272
- /**
273
- * An item of a platform problem document's `errors[]`, its `field`, `code` and `detail` kept when
274
- * they are strings and its other members relayed as sent.
275
- */
276
- export declare function readFieldError(value: Record<string, unknown>): FieldError;
277
- /**
278
- * A failed run's stored report, checked field by field — the one way every read in this SDK
279
- * hands a report back.
280
- *
281
- * - A value that is not a plain object reads as `null`, no report.
282
- * - Each string field (`error_type`, `message`, `title`, `type_uri`, `error_domain`,
283
- * `error_category`, `model`, `provider`) is kept when it is a string, an empty one included, and
284
- * is absent otherwise. Deciding that a blank field says nothing is presentation, left to each
285
- * consumer.
286
- * - `retryable` and `caller_facing_message` are kept when they are booleans.
287
- * - `user_action` is kept whole or not at all: an object whose `kind` and `detail` are both
288
- * strings, which is what `UserAction` promises. (The Python twin keeps a partial action, its
289
- * stored-report action being a lenient type of its own; this is the one place the two differ.)
290
- * - `provider_metadata` is kept when it is an object, with each named field checked: the strings
291
- * as strings, `status_code` and `retry_after_seconds` as a number or a string, since the hosted
292
- * store serves its numbers as strings.
293
- * - `migration` is kept when it is an object: `remedy` as a string, `would_write` and
294
- * `needs_attention` as booleans, and `plans` as an array keeping only its objects.
295
- * - `validation_errors` is kept when it is an array, keeping each item that is an object with a
296
- * string `category` and a string `message`.
297
- * - `null` is kept wherever the type allows it, which is every named field; and members this SDK
298
- * does not name, in the report and in each nested object, are relayed as sent.
299
- *
300
- * Internal: every report a consumer meets comes through one of the reads that apply it.
301
- */
302
- export declare function readRunErrorReport(value: unknown): RunErrorReport | null;