@pipelex/sdk 0.48.1 → 0.49.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 +10 -0
- package/dist/cli/script.d.ts +1 -1
- package/dist/client.d.ts +89 -5
- package/dist/client.js +131 -7
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +91 -6
- package/dist/errors.js +48 -6
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/prepare-inputs.d.ts +12 -1
- package/dist/prepare-inputs.js +26 -3
- package/dist/prepare-inputs.js.map +1 -1
- package/dist/product-models.d.ts +219 -2
- package/dist/product-models.js +2 -2
- package/dist/upload.d.ts +39 -11
- package/dist/upload.js +115 -8
- package/dist/upload.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +3 -1
- package/docs/errors.md +6 -1
- package/docs/files.md +88 -0
- package/docs/input-preparation.md +14 -4
- package/docs/organization-branding.md +102 -0
- package/package.json +1 -1
package/dist/upload.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* The MIME type and size are known client-side at upload time, so the record is
|
|
8
8
|
* built without extending the `/v1/upload` response.
|
|
9
9
|
*/
|
|
10
|
+
import type { UploadInput, UploadKind } from "./product-models.js";
|
|
10
11
|
/** A local asset `uploadFile` accepts. A path string is Node-only (see {@link readLocalPath}). */
|
|
11
12
|
export type UploadableAsset = Blob | ArrayBuffer | Uint8Array | string;
|
|
12
13
|
/**
|
|
@@ -24,8 +25,27 @@ export interface UploadRecord {
|
|
|
24
25
|
/** Size of the uploaded bytes. */
|
|
25
26
|
size: number;
|
|
26
27
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* What an upload is recorded as, sent with it to `POST /v1/upload`: its `kind` and, for a test
|
|
30
|
+
* file, the method it is for. `uploadFile` takes these among its options and `prepareInputs` as
|
|
31
|
+
* its `upload` member, for every file it uploads. Both are checked as the upload route checks
|
|
32
|
+
* them, before any file is read: `kind: "test"` needs `method_id`, a bare catalog id, and only a
|
|
33
|
+
* test file names a method.
|
|
34
|
+
*/
|
|
35
|
+
export interface UploadKindOptions {
|
|
36
|
+
/** What the upload is recorded as; `uploaded` when omitted. */
|
|
37
|
+
kind?: UploadKind;
|
|
38
|
+
/**
|
|
39
|
+
* The bare catalog id of the method a `test` file is for, required with `kind: "test"`
|
|
40
|
+
* and refused without it.
|
|
41
|
+
*/
|
|
42
|
+
method_id?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Per-call options for {@link uploadFile}. Fill in a filename/MIME for nameless byte sources, and
|
|
46
|
+
* a `kind` with its `method_id` for a test file.
|
|
47
|
+
*/
|
|
48
|
+
export interface UploadFileOptions extends UploadKindOptions {
|
|
29
49
|
filename?: string;
|
|
30
50
|
contentType?: string;
|
|
31
51
|
/**
|
|
@@ -37,15 +57,21 @@ export interface UploadFileOptions {
|
|
|
37
57
|
}
|
|
38
58
|
/** The subset of the client `uploadFile` needs — the raw base64 `upload` wire call. */
|
|
39
59
|
export interface UploadCapableClient {
|
|
40
|
-
upload(input: {
|
|
41
|
-
filename: string;
|
|
42
|
-
data: string;
|
|
43
|
-
content_type: string;
|
|
44
|
-
}): Promise<{
|
|
60
|
+
upload(input: UploadInput): Promise<{
|
|
45
61
|
uri: string;
|
|
46
62
|
filename: string;
|
|
47
63
|
}>;
|
|
48
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The `kind` and `method_id` an upload is sent with, checked as the upload route checks them, so
|
|
67
|
+
* a request the route would refuse with a `422` is refused before any file is read: a `kind` is
|
|
68
|
+
* `uploaded` or `test`, a `method_id` is a bare catalog id, `kind: "test"` needs one and only a
|
|
69
|
+
* test file names one. Returns the members to send, each only when given; `null` reads as
|
|
70
|
+
* absent, as it does on the route. A refusal is an `InputPreparationError` whose message opens
|
|
71
|
+
* with `refusal` and names each member as `field` prefixes it, so `prepareInputs` names its
|
|
72
|
+
* `upload.kind`.
|
|
73
|
+
*/
|
|
74
|
+
export declare function uploadKindFields(given: UploadKindOptions, refusal: string, field?: string): UploadKindOptions;
|
|
49
75
|
/** MIME guess from a filename extension; `application/octet-stream` when unknown. */
|
|
50
76
|
export declare function guessContentType(filename: string): string;
|
|
51
77
|
/**
|
|
@@ -60,9 +86,11 @@ export declare function readLocalPath(path: string): Promise<Uint8Array>;
|
|
|
60
86
|
/** Base64-encode bytes, cross-runtime (Buffer when present, else chunked btoa). */
|
|
61
87
|
export declare function bytesToBase64(bytes: Uint8Array): string;
|
|
62
88
|
/**
|
|
63
|
-
* Upload one local asset and return its {@link UploadRecord}.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
89
|
+
* Upload one local asset and return its {@link UploadRecord}. A `kind` and its `method_id` in
|
|
90
|
+
* the options are checked before the asset is read and sent with it (see
|
|
91
|
+
* {@link UploadKindOptions}). Maps the raw `upload()` transport errors onto the semantic
|
|
92
|
+
* input-preparation errors: a `413` is a rejected asset, a test file refused for its role or its
|
|
93
|
+
* method a {@link TestFileRefusedError}, any other `401`/`403` an auth failure, any other `404`
|
|
94
|
+
* an unsupported upload capability, an unreachable host a transport failure.
|
|
67
95
|
*/
|
|
68
96
|
export declare function uploadFile(client: UploadCapableClient, asset: UploadableAsset, options?: UploadFileOptions): Promise<UploadRecord>;
|
package/dist/upload.js
CHANGED
|
@@ -7,10 +7,68 @@
|
|
|
7
7
|
* The MIME type and size are known client-side at upload time, so the record is
|
|
8
8
|
* built without extending the `/v1/upload` response.
|
|
9
9
|
*/
|
|
10
|
-
import { InvalidLocalSourceError, RejectedAssetError, UploadAuthenticationError, UploadTransportError, UnsupportedUploadCapabilityError, } from "./errors.js";
|
|
10
|
+
import { InputPreparationError, InvalidLocalSourceError, RejectedAssetError, TestFileRefusedError, UploadAuthenticationError, UploadTransportError, UnsupportedUploadCapabilityError, } from "./errors.js";
|
|
11
11
|
import { ApiResponseError, ApiUnreachableError } from "./errors.js";
|
|
12
|
+
import { parseMethodSelector } from "./method-selector.js";
|
|
12
13
|
import { absenceCause, loadNodeHost } from "./node-host.js";
|
|
13
14
|
import { throwIfAborted } from "./runs.js";
|
|
15
|
+
/** The kinds an upload is recorded as, in the order the refusal of another one lists them. */
|
|
16
|
+
const UPLOAD_KINDS = ["uploaded", "test"];
|
|
17
|
+
/** A value an upload's kind or method was given as, described for a refusal without quoting more than an id. */
|
|
18
|
+
function describedArgument(value) {
|
|
19
|
+
if (value === null)
|
|
20
|
+
return "null";
|
|
21
|
+
if (typeof value === "string")
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
return typeof value;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The `kind` and `method_id` an upload is sent with, checked as the upload route checks them, so
|
|
27
|
+
* a request the route would refuse with a `422` is refused before any file is read: a `kind` is
|
|
28
|
+
* `uploaded` or `test`, a `method_id` is a bare catalog id, `kind: "test"` needs one and only a
|
|
29
|
+
* test file names one. Returns the members to send, each only when given; `null` reads as
|
|
30
|
+
* absent, as it does on the route. A refusal is an `InputPreparationError` whose message opens
|
|
31
|
+
* with `refusal` and names each member as `field` prefixes it, so `prepareInputs` names its
|
|
32
|
+
* `upload.kind`.
|
|
33
|
+
*/
|
|
34
|
+
export function uploadKindFields(given, refusal, field = "") {
|
|
35
|
+
const raw = given;
|
|
36
|
+
const kind = raw.kind ?? undefined;
|
|
37
|
+
const methodId = raw.method_id ?? undefined;
|
|
38
|
+
if (kind !== undefined && !UPLOAD_KINDS.includes(kind)) {
|
|
39
|
+
throw new InputPreparationError(`${refusal}: \`${field}kind\` is "uploaded" or "test", got ${describedArgument(kind)}.`);
|
|
40
|
+
}
|
|
41
|
+
if (methodId !== undefined) {
|
|
42
|
+
if (typeof methodId !== "string") {
|
|
43
|
+
throw new InputPreparationError(`${refusal}: \`${field}method_id\` names a method by its bare catalog id, got ` +
|
|
44
|
+
`${describedArgument(methodId)}.`);
|
|
45
|
+
}
|
|
46
|
+
let bare;
|
|
47
|
+
try {
|
|
48
|
+
bare = parseMethodSelector(methodId).version === null;
|
|
49
|
+
}
|
|
50
|
+
catch (cause) {
|
|
51
|
+
throw new InputPreparationError(`${refusal}: \`${field}method_id\` ${JSON.stringify(methodId)} is not a catalog id, ` +
|
|
52
|
+
"which is mt_ followed by letters, digits, _ or -.", { cause });
|
|
53
|
+
}
|
|
54
|
+
if (!bare) {
|
|
55
|
+
throw new InputPreparationError(`${refusal}: \`${field}method_id\` names the method a test file is for by its bare ` +
|
|
56
|
+
`catalog id, with no version suffix, got ${JSON.stringify(methodId)}.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (kind === "test" && methodId === undefined) {
|
|
60
|
+
throw new InputPreparationError(`${refusal}: a test file names the method it is for; give \`${field}method_id\` with ` +
|
|
61
|
+
`\`${field}kind: "test"\`.`);
|
|
62
|
+
}
|
|
63
|
+
if (kind !== "test" && methodId !== undefined) {
|
|
64
|
+
throw new InputPreparationError(`${refusal}: only a test file names a method; give \`${field}method_id\` with ` +
|
|
65
|
+
`\`${field}kind: "test"\` alone.`);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...(kind === undefined ? {} : { kind: kind }),
|
|
69
|
+
...(methodId === undefined ? {} : { method_id: methodId }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
14
72
|
const DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
15
73
|
const DEFAULT_FILENAME = "upload.bin";
|
|
16
74
|
const EXTENSION_MIME = {
|
|
@@ -103,22 +161,25 @@ function assetBytesFromBuffer(bytes, options) {
|
|
|
103
161
|
return { bytes, filename, contentType: options.contentType ?? guessContentType(filename) };
|
|
104
162
|
}
|
|
105
163
|
/**
|
|
106
|
-
* Upload one local asset and return its {@link UploadRecord}.
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
164
|
+
* Upload one local asset and return its {@link UploadRecord}. A `kind` and its `method_id` in
|
|
165
|
+
* the options are checked before the asset is read and sent with it (see
|
|
166
|
+
* {@link UploadKindOptions}). Maps the raw `upload()` transport errors onto the semantic
|
|
167
|
+
* input-preparation errors: a `413` is a rejected asset, a test file refused for its role or its
|
|
168
|
+
* method a {@link TestFileRefusedError}, any other `401`/`403` an auth failure, any other `404`
|
|
169
|
+
* an unsupported upload capability, an unreachable host a transport failure.
|
|
110
170
|
*/
|
|
111
171
|
export async function uploadFile(client, asset, options = {}) {
|
|
172
|
+
const claimed = uploadKindFields(options, "Cannot upload");
|
|
112
173
|
const { bytes, filename, contentType } = await toAssetBytes(asset, options);
|
|
113
174
|
// A slow read, or a Blob's `arrayBuffer()`, may outlast the caller's patience.
|
|
114
175
|
throwIfAborted(options.signal);
|
|
115
176
|
const data = bytesToBase64(bytes);
|
|
116
177
|
let uploaded;
|
|
117
178
|
try {
|
|
118
|
-
uploaded = await client.upload({ filename, data, content_type: contentType });
|
|
179
|
+
uploaded = await client.upload({ filename, data, content_type: contentType, ...claimed });
|
|
119
180
|
}
|
|
120
181
|
catch (error) {
|
|
121
|
-
throw mapUploadError(error, filename);
|
|
182
|
+
throw mapUploadError(error, filename, claimed.method_id);
|
|
122
183
|
}
|
|
123
184
|
// The record guarantees a filename: an answer that names none keeps the one sent.
|
|
124
185
|
const stored = uploaded.filename;
|
|
@@ -129,8 +190,42 @@ export async function uploadFile(client, asset, options = {}) {
|
|
|
129
190
|
size: bytes.length,
|
|
130
191
|
};
|
|
131
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Which refusal of a test file an upload's error is, or `undefined` for any other. Each is read
|
|
195
|
+
* off the platform's code, never the status alone: a `403` without `role_not_permitted` is a
|
|
196
|
+
* refused credential, and a `404` is an unknown method only when the upload named one and the
|
|
197
|
+
* answer is the platform's domain miss (`not_found`, `input`), not the `404` of a deployment
|
|
198
|
+
* without the route, which the platform answers as `config` and a bare runner without a code.
|
|
199
|
+
*/
|
|
200
|
+
function testFileRefusalOf(error, methodId) {
|
|
201
|
+
if (error.code === "role_not_permitted")
|
|
202
|
+
return "role_not_permitted";
|
|
203
|
+
if (error.code === "method_being_deleted")
|
|
204
|
+
return "method_being_deleted";
|
|
205
|
+
if (error.status === 404 &&
|
|
206
|
+
error.code === "not_found" &&
|
|
207
|
+
error.errorDomain === "input" &&
|
|
208
|
+
methodId !== undefined) {
|
|
209
|
+
return "method_not_found";
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
/** The message of a test file's refusal, naming the file, the method and what to do. */
|
|
214
|
+
function testFileRefusalMessage(code, error, filename, methodId) {
|
|
215
|
+
const refused = `Upload of "${filename}" as a test file was refused (${error.status})`;
|
|
216
|
+
const method = methodId === undefined ? "the method it names" : `the method "${methodId}"`;
|
|
217
|
+
switch (code) {
|
|
218
|
+
case "role_not_permitted":
|
|
219
|
+
return (`${refused}: ${error.serverMessage ?? "the caller's role may not change methods"}. ` +
|
|
220
|
+
'Upload it as "uploaded", or with the credential of a member who may change methods.');
|
|
221
|
+
case "method_not_found":
|
|
222
|
+
return `${refused}: ${method} is not one of the organization's methods.`;
|
|
223
|
+
case "method_being_deleted":
|
|
224
|
+
return `${refused}: ${method} is being deleted.`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
132
227
|
/** Translate a raw `upload()` transport error into the matching preparation error. */
|
|
133
|
-
function mapUploadError(error, filename) {
|
|
228
|
+
function mapUploadError(error, filename, methodId) {
|
|
134
229
|
if (error instanceof ApiResponseError) {
|
|
135
230
|
// A 2xx the client could not read: storage may hold the file, but under no reference the SDK
|
|
136
231
|
// can return.
|
|
@@ -138,6 +233,18 @@ function mapUploadError(error, filename) {
|
|
|
138
233
|
return new UploadTransportError(`Upload of "${filename}" was answered (${error.status}) with a body the SDK could not ` +
|
|
139
234
|
"read, so whether and where the file was stored is unknown.", { cause: error, status: error.status, code: "unexpected", filename });
|
|
140
235
|
}
|
|
236
|
+
// A test file refused for what it claims, its role or its method, before the status alone is
|
|
237
|
+
// read as a refused credential or a deployment without upload.
|
|
238
|
+
const refusal = testFileRefusalOf(error, methodId);
|
|
239
|
+
if (refusal !== undefined) {
|
|
240
|
+
return new TestFileRefusedError(testFileRefusalMessage(refusal, error, filename, methodId), {
|
|
241
|
+
cause: error,
|
|
242
|
+
status: error.status,
|
|
243
|
+
code: refusal,
|
|
244
|
+
filename,
|
|
245
|
+
...(methodId === undefined ? {} : { methodId }),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
141
248
|
switch (error.status) {
|
|
142
249
|
case 413:
|
|
143
250
|
return new RejectedAssetError(`The server rejected "${filename}": ${error.serverMessage ?? "asset exceeds the service size limit"}.`, filename, error.status, { cause: error, code: "too_large" });
|
package/dist/upload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,oBAAoB,EACpB,gCAAgC,GACjC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/docs/architecture.md
CHANGED
|
@@ -112,7 +112,7 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
|
|
|
112
112
|
|
|
113
113
|
- `client.ts` — `PipelexApiClient`: the `request()` pipeline (auth, base URL, timeouts/abort, problem-details parsing) and every route. It implements the standard's `MTHDSProtocol<DictPipeOutput>` for every route but `validate`, so the protocol-execution methods stay shaped like the standard: `validate` answers Pipelex's own verdict, `PipelexValidationResult`, because the hosted plane still sends the verdict that predates the protocol's problem items, where the standard's `ValidationResult` lists them.
|
|
114
114
|
- `models.ts` — Dict-serialized concretes (`DictStuff` / `DictWorkingMemory` / `DictPipeOutput` / `DictRunResultExecute`), the `/v1/validate` surface (`PipelexValidationResult`, whose two arms are declared whole rather than as narrowings of the standard's `ValidationResult`, `ValidationErrorItem`, and the `SuggestedFix` / `FixOp` repair vocabulary its items carry — the hosted plane's shapes, which predate the standard's problem items, their operation and safety vocabularies pinned to the standard's by a type-level test, see [errors.md](./errors.md#a-validation-item--validationerroritem)), the tools surface (`Diagnostic` / `LintResponse` / `FormatResponse`), and the crate-route request/response models — the shared `CrateRequestBase` / `CrateInvalidReport` vocabulary plus the `/v1/resolve`, `/v1/codegen` and `/v1/pipe-io` shapes built on it, `GeneratedArtifact` among them.
|
|
115
|
-
- `product-models.ts` — the snake_case wire shapes for the hosted-product management routes (profile, methods catalog, organizations, billing, API keys, onboarding, storage, runs list/detail/update). Each interface models only the fields the product actually consumes — no speculative mirrors.
|
|
115
|
+
- `product-models.ts` — the snake_case wire shapes for the hosted-product management routes (profile, methods catalog, organizations, billing, API keys, onboarding, storage, an organization's files, runs list/detail/update). Each interface models only the fields the product actually consumes — no speculative mirrors.
|
|
116
116
|
- `error-models.ts` — the error-report wire models: `RunErrorReport`, the runner's `ErrorReport` typed with every field it carries (with `UserAction`, `ProviderErrorMetadata` and `MigrationErrorBlock`), which is the one type for a failed run's report on `RunPublic.error`, `PipelineRun.error`, `RunHistoryItem.error`, the failed arm of `RunResultState` and `RunFailedError.error`; and `ProblemDetails` / `FieldError`, the typed members of a problem document that `ApiResponseError` exposes. Every field is optional and every interface keeps an index signature, because the runner owns the shape and adds fields without asking. It also holds the one function that checks a stored report, the internal `readRunErrorReport`, which every read handing a report back applies: each named field is kept only when it has its declared type, and members the SDK does not name are relayed as sent. It imports the `ValidationErrorItem` type and nothing else. See [errors.md](./errors.md#a-failed-run--runerrorreport).
|
|
117
117
|
- `method-trees.ts` — a saved method's tree on the client side: `MethodTreeSource`, the shape a save reads a tree from, which the `mthds` package's `MethodTree` satisfies; the choice between sending a tree inline and by hash (`INLINE_TREE_MAX_BYTES`), the wire types of `POST /v1/blobs/missing` and the upload of a blob the platform lacks; `redactSignedLinks`, which every error built from an answer holding signed links reads its body through; the tree of an in-memory map; the tree limits a version handshake announces; the listing of a `directory`, by the `mthds` package's lister, which it gets from the Node host only when a directory is given; and the Node-only write of a saved listing to disk, with the file system it gets from the Node host when called. Outside Node, both are refused with `RequestArgumentError`. Its tree-sending half mirrors the `mthds` package's own helpers, which the `mthds/protocol` subpath does not export yet, so it holds the same rules with Web Crypto's SHA-256 and a Base64 writer of its own. See [method-trees.md](./method-trees.md).
|
|
118
118
|
- `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 runtime imports are `smol-toml` and the plain-object guard of the pure `error-models.ts` — and it hashes through WebCrypto rather than `node:crypto` so it adds no Node builtin to the barrel's import graph. It raises `CodegenLockError`, which extends `Error` rather than the protocol base and declares the error verdict itself (`input`, not retryable). 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).
|
|
@@ -155,11 +155,13 @@ The hosted catalog/account routes the webapp drives. All ride the same `{base}/v
|
|
|
155
155
|
- **Methods catalog:** `listMethods`, `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod`, `listMethodVersions`, `getMethodVersion`, `deleteMethod`. **A saved method has a draft and immutable published versions.** Its draft is a method tree with its `input_data`, which `writeDraft` (`PUT /v1/methods/{id}/draft`) replaces whole, without validating it; `updated_at` is the draft's token, which moves on every draft write and nothing else, and `draft_digest` the draft's tree digest (`sha256:` and 64 hex digits), `null` for an empty draft. A save takes the tree as `files`, sent as given, or as `directory`, `tree` or `contents`, sent inline or by hash after uploading the blobs the organization lacks, and a read lists the tree's files, each with a short-lived link to its bytes, which `writeMethodTree` writes to disk ([method-trees.md](./method-trees.md)). A draft write may carry `expected_updated_at`, the token the caller last saw, and is then a compare-and-swap: a draft that moved since is a `409` `method_update_conflict` and nothing is written. `renameMethod` (`PATCH /v1/methods/{id}`) changes the name alone and moves no token. `publishMethod` (`POST /v1/methods/{id}/publish`) takes the required `expected_draft_updated_at` and answers a `MethodPublishResult` discriminated on `outcome` — `published` with the new version, `unchanged` with the existing latest version when the draft's digest equals it, `refused` with a `reason` (`invalid` or `not_runnable`), a `message` and the runner's `validation` verdict — every arm carrying the `method`; the SDK reads the nested method as `getMethod` does, and an answer whose `outcome` is none of these is an answer it cannot read. A publish that produced no verdict is thrown rather than answered: only a draft that differs from the latest version reaches the runner, and a runner that cannot be reached, answers unusably or does not finish within the platform's deadline is a `502` or `503` with nothing written, so a retry is safe, and a retry of a publish that landed while its answer was lost answers `unchanged` with the version it wrote. A method is stored as one item that keeps room for the summary a publish adds, so a draft that saves can always be published: a creation or a draft write is a `413` `payload_too_large` when it would grow the method past that room, below the store's item limit, and a rename is the same `413` for a name so long it would leave the method too large to publish. A version is numbered from 1, never reused, and holds the draft's tree as it stood, its `tree_digest` (`null` for a version stored before methods were trees), its `crate_fingerprint` (`null` when the runner could not resolve the bundle in memory), the runner's version, and when and by whom it was published; `listMethodVersions` pages the summaries newest first (`MethodVersionPage`, `{items, nextCursor}`, a `limit` from 1 to 100, 20 by default, and a `422` outside that range), and `getMethodVersion` reads one with its `files` listed as a method's are, or answers a `409` `conflict`, whose verdict is `runtime`, for a version stored before methods were trees and never rewritten as one. A method read carries `latest_version` and `latest_published`, both `null` until the first publish, so the publish state is derived: never published, draft unchanged (`draft_digest` not `null` and equal to `latest_published.tree_digest`), or draft ahead. The method routes take a bare id and address the method itself, never one of its versions; the platform does not parse a suffix there, so `mt_x@3` would answer a `404` that reads as a method that does not exist, and each route refuses a suffixed id with `RequestArgumentError` before anything is sent, naming `parseMethodSelector` and `getMethodVersion`. The whole-method `updateMethod` is gone, replaced by the draft write and the rename. **`deleteMethod` is asynchronous and its return type says so:** `DELETE /v1/methods/{id}` answers `202` with a `MethodDeletionAccepted` (`method_id`, `deletion_state`, `deletion_job_id`) the moment the platform has claimed the method and terminated its in-flight workflows — the rest of the cascade (runs, events, S3 objects) is enqueued. A resolved promise means "accepted", never "gone"; completion is the row disappearing from `listMethods`, not any field of that body. The claim is a conditional write, so a double-clicked delete is a `409 method_being_deleted` rather than a second cascade over the same runs, and an unknown or foreign-org id is a `404`. The erasure deletes the method's versions with the rest. (This doc previously stated the SDK had no `delete` because the platform had no such route — the route has always existed; the claim was simply false.) `listMethods` returns **one page**, not the whole catalog — a `MethodPage` (`{items, nextCursor}`) served from a narrow index projection, so its cost is the page size rather than the size of the catalog. It used to return every method with its full `.mthds` bundle attached, which hit DynamoDB's 1 MB page cap at a couple hundred methods and came back truncated with no error and no flag, so methods silently vanished from the UI. Rows are therefore `MethodSummary`, **not** `MethodDetail`: no `files`, no `updated_at`, because neither is in the projection and putting the source back is what restored the truncation. Reach for `getMethod(id)` when you need a method's files. `query.q` is applied server-side across the whole catalog, not over the page — filtering one page client-side would be searching 50 rows of 10,000 and calling it a search. `query.published` filters the same way, sent as `published=true` (the methods with a published version) or `published=false` (those never published), and combines with `q`. Each row carries the method's publish state, so a list tells its methods apart without reading each one: `latest_version` and `latest_published` (a `MethodVersionSummary`), both `null` when the method was never published, and `draft_digest`, `null` for an empty draft; all three are optional, absent from a platform that does not serve them. Never published is `latest_version === null`, the draft unchanged is a `draft_digest` that is not `null` and equals `latest_published.tree_digest`, and the draft ahead is two digests that differ. Both filters run after the index read, so **a filtered page can come back empty and still carry a `nextCursor`**: an empty page is not the end of the list, and only a `null` cursor is. `iterateMethods` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const m of client.iterateMethods())`); it keeps going **past empty pages**, since `{items: [], nextCursor: "…"}` means "nothing matched in the slice I just read, keep going". A server that keeps minting fresh cursors forever stops it at a page ceiling far past any real catalog with a `PagingNotTerminatingError` carrying `pageLimit`, never a silently truncated stream; `iterateRuns` shares the same loop. It gives up only when the server stops advancing its cursor, and **throws** rather than returning a partial answer; like `iterateRuns` it is deliberately not a `listAllMethods(): Promise<MethodSummary[]>`, because an all-at-once helper needs a page cap and a cap means silently returning a truncated list. `MethodSummary.deletion_state` rides the list rows: a method mid-erasure stays listed so the UI can render it as "Deleting…", while `getMethod` refuses it with a 409. Full read responses (`MethodDetail`, from `getMethod`) carry `org_id`, `created_by_user_id`, a server-derived `description` (recomputed from the draft's top-level `description` on every draft write), the draft token and digest, the latest version's summary and the draft's `files` on top of the write contract's `name` / `input_data`; the writes and the rename answer `MethodData`, the same method without `files`.
|
|
156
156
|
- **Method examples:** `createMethodExample`, `listMethodExamples`, `iterateMethodExamples`, `getMethodExample`, `updateMethodExample`, `recordMethodExampleLatestRun`, `deleteMethodExample`, on `/v1/methods/{id}/examples`. An example is a named set of inputs saved on a method, shared across the organization, with an `input_revision` that rises each time its inputs change and the `latest_run_id` of those inputs, which an inputs edit clears. `recordMethodExampleLatestRun` records a run against the revision whose inputs it used and answers a `MethodExampleLatestRunResult` discriminated on `outcome`, `recorded` or `stale`, each arm carrying the `example`: a run that outlasted an edit is a verdict, never thrown, and an answer off the union is one the SDK cannot read; one helper, `readVerdict`, reads this verdict and `publishMethod`'s, the example checked once, by the reader every example goes through. Every answer carrying an example is read with `parseExactJson` and every request body written with `stringifyExactJson`, so an integer past `Number.MAX_SAFE_INTEGER` in the inputs reads as a `bigint` and goes back digit for digit, to an edit or as a run's inputs; `3.0` still reads as `3`, and inputs holding one sent back unchanged are an edit for the platform, which compares inputs as canonical JSON. Every example the client hands back, the page rows and the verdict's `example` included, is checked by `readMethodExample` against the fields the Python SDK's model requires, with the strict types it holds them to, an example breaking one being an answer the SDK cannot read. The list is paged oldest first by an opaque cursor, and a page may be short while `nextCursor` is set; `iterateMethodExamples` follows it through the loop `iterateMethods` and `iterateRuns` share, and refuses a method id at the call rather than at the first pull. Every method takes a bare catalog id, and the client refuses an example id that is empty, `.` or `..` or holds a lone surrogate, a create without its name or inputs or whose `run_id` is given but empty, an edit naming neither `name` nor `inputs`, and a latest-run write without a run id or a positive integer revision before sending. See [method-examples.md](./method-examples.md).
|
|
157
157
|
- **Organizations:** `listMemberships`, `createOrganization`, `renameOrganization` (`PATCH`). Each membership carries the caller's `role_in_org`, an `OrgRole` (`admin`, `member` or `runner`) read open: a role a later platform adds arrives as its raw string, so a `switch` over it says what an unknown role does. The role is for display; what it may do is the platform's to decide, and a call refused to the caller's role is an `ApiResponseError`. The org "switch" is a WorkOS-session op (`/api/active-org`), not a `/v1` route — out of SDK scope.
|
|
158
|
+
- **Organization branding:** `getBranding` (`GET /v1/branding`, any role, web session or API key) and `updateOrganizationBranding` (`PATCH /v1/organizations/{org_id}/branding`, an admin's write) answer the same `OrganizationBranding`: the organization's name, its light and dark colour sets (`BrandingColors`) and its square and wide logos (`LogoDescriptor`). The write's `OrganizationBrandingUpdate` sets a key given a value, clears a key given `null` and keeps a key left out; a refused logo or colour is named in `ApiResponseError.errors`, by slot or by dotted key, a logo the platform measured with a `LogoRefusalCode`. See [organization-branding.md](./organization-branding.md).
|
|
158
159
|
- **Billing:** `getSubscription`, `listPlans`, `listInvoices`, `createCheckout`, `changePlan` (409 `conflict` ⇒ no subscription), `getBillingPortal` (409 `conflict` ⇒ no subscription).
|
|
159
160
|
- **Pipelex API keys (`plx_sk_…`):** `listPipelexApiKeys`, `createPipelexApiKey` (409 `pipelex_api_key_limit_reached`), `revokePipelexApiKey`, `rotatePipelexApiKey`. The plaintext `api_key` is returned once, on create/rotate.
|
|
160
161
|
- **Onboarding:** `submitOnboarding`.
|
|
161
162
|
- **Storage:** `resolveStorageUrl`, `resolveStorageUrls` (`POST /v1/resolve-storage-url/bulk` — the single route applied to a list of at most 100 references, one verdict per reference in request order, a refused reference a value on its item; a deployment without the route answers a `404` `ApiResponseError`), `upload` (JSON base64 — the multipart hop is browser→BFF only), `requestUploadGrant` (`POST /v1/upload/grant` — a presigned, create-only `PUT` for one new object, which the holder of the bytes sends with the standalone `uploadWithGrant`, also exported from the browser-safe `@pipelex/sdk/upload` entry; a declared size over the cap is a `413` `ApiResponseError`). The higher-level `uploadFile` / `prepareInputs` preparation surface is built on top of `upload`; `prepareInputs` additionally composes one `pipeIo` call, which selects the pipe and answers with the input-form descriptor it walks, and takes the method as its tree, a `method_ref` or a `method_id` like every other method-taking operation — see [input-preparation.md](./input-preparation.md). The download twin — `locateArtifacts`, `collectArtifacts`, `resolveArtifacts`, `fetchArtifact`, `downloadArtifacts` — is built on top of `resolveStorageUrls` and `getRunResult`, never on the embedded `public_url`, and is documented on [artifact-download.md](./artifact-download.md); downloading is a separate explicit gesture, and no run call takes a `download` option.
|
|
162
163
|
- **Runs list/detail/update:** `listRuns` (by `method_id`) returns **one page**, not the whole history — a `RunPage` (`{items, nextCursor}`) served from a time-ordered index, so its cost is the page size rather than the size of the history. Its rows are `RunHistoryItem`, **not** `PipelineRun`: the id, `status`, `created_at`, `finished_at`, `pipe_code`, for a failed run `error`, and `method_version` and `source_digest`, which version of the method the run ran (a number, `"draft"`, or `null` for an inline source) and the SHA-256 of the sources it sent as text, 64 hex digits with no prefix and `null` for a run whose `files` held bytes as Base64 or by hash — what a history row shows. `getRunDetail` also carries the run's `tree_digest` and `crate_fingerprint`, each `null` for a run that recorded none. The history lists the runs of every version and of the draft together, by the bare id. `method_version` and `source_digest` are optional on every run record (`RunPublic`, `RunRead`, `PipelineRun`, `RunDetail`, `RunHistoryItem`), since a run recorded before the platform recorded them carries neither. The run's organization, creator, method, workflow id and result location are not on the list; `getRunDetail` returns them. `iterateRuns` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const run of client.iterateRuns(id))`); it is deliberately not a `listAllRuns(): Promise<RunHistoryItem[]>`, because an all-at-once helper needs a page cap and a cap means silently returning a truncated list. Both accept `createdFrom` / `createdTo`, applied server-side as index key conditions — these are **instants, not days** (ISO-8601 with a UTC offset; a naive timestamp is a 400), because only the caller knows which timezone's day it means. Both also accept `createdBy: "me"`, sent as `created_by=me`, which keeps the runs the caller started (the platform resolves "me" from the authenticated identity and takes no other value), and `status`, a list of `RunStatus` sent as one repeated `status=` parameter per status, deduplicated into the platform's order, which keeps the runs in any of them: the caller's own latest unfinished run is `{ createdBy: "me", status: ["PENDING", "STARTED", "RUNNING"], limit: 1 }`. An empty `status` list would be sent as no parameter at all, which lists every status, so it is a `RequestArgumentError` before any request, as is a name that is no `RunStatus`. These two are filters applied after the index read, so **a filtered page can hold fewer runs than `limit`, or none, and still carry a `nextCursor`**: an empty page is not the end of the history, a `null` cursor is, and `iterateRuns` keeps following the cursor through one. It runs the same loop as `iterateMethods`, so it also stops on a cursor the server failed to advance and throws `PagingNotTerminatingError` past the same page ceiling. `getRunDetail` (`GET /v1/runs/{id}`) is the only call returning `mthds_contents` — what the run actually executed — and `inputs`; it is kept off the status read that pollers hit every few seconds and off the list, where the bundle would be multiplied by the page size. `updateRun` is the admin/manual status patch. The status/results/start lifecycle routes live in the run-lifecycle section above.
|
|
164
|
+
- **An organization's files:** `listFiles` (`GET /v1/files`) returns **one page** of the organization's listed files, newest first, a `FilePage` (`{items, nextCursor}`) of `FileSummary` rows, each with the `uri` a run's inputs take as any address, its name, type, size, `kind` (`uploaded`, `generated` or `test`) and `origin`. Its `ListFilesQuery` follows `ListRunsQuery`'s camelCase and is sent under the API's names: `methodId`, `runId` and `createdBy` choose the method's, the run's (in the order of its addresses), one member's or the organization's list, and `q`, `kind`, `format`, `mediaType`, `createdFrom` and `createdTo` filter it, `format` and `mediaType` as one repeated parameter per value, an empty list of either refused with `RequestArgumentError` before any request since it would be sent as no filter at all. The filters apply after the index read, so a filtered page can be short or empty and still carry a `nextCursor`, which is bound to the list and filters that issued it; `iterateFiles` follows it through the loop the other iterators share. An upload is recorded as a method's test file by `kind: "test"` with its `method_id`, the pair `UploadInput`, `uploadFile`'s options and `prepareInputs`' nested `upload` all take, checked before any file is read; the upload route's refusals of a test file, for the caller's role or for its method, are a `TestFileRefusedError`. See [files.md](./files.md).
|
|
163
165
|
|
|
164
166
|
## Hook bundle (`dist-hooks/check.mjs`)
|
|
165
167
|
|
package/docs/errors.md
CHANGED
|
@@ -50,8 +50,9 @@ On an error from a request that may have started a run, `retryable` is `false` w
|
|
|
50
50
|
| `RunTimeoutError`, `RunStillRunningError` | `runtime` | yes | The run is still going; waiting again, by its id, can succeed. |
|
|
51
51
|
| `RunLifecycleUnavailableError` | `config` | no | The base URL points at a bare runner without the run lifecycle. |
|
|
52
52
|
| `MissingMainStuffError` | `runtime` | no | The API broke its own contract on a completed run. |
|
|
53
|
-
| `PagingNotTerminatingError` | `runtime` | no | A server minting cursors forever; `pageLimit` is the ceiling `iterateMethods`, `iterateRuns` or `
|
|
53
|
+
| `PagingNotTerminatingError` | `runtime` | no | A server minting cursors forever; `pageLimit` is the ceiling `iterateMethods`, `iterateRuns`, `iterateMethodExamples` or `iterateFiles` stopped at. |
|
|
54
54
|
| `InputPreparationError` and `InvalidLocalSourceError`, `InvalidInputValueError`, `UploadNotAllowedError`, `MethodLoadError`, `RejectedAssetError` | `input` | no | The asset, the inputs or the method selector must change. The family's base takes an optional `verdict` in its options, so a subclass of your own can declare another; `prepareInputs` declares `runtime` for a pipe I/O answer that breaks the route's contract: no boolean `is_valid`, an `input_form` that does not describe the pipe it selected, or a descriptor that is malformed or names a kind this SDK does not know. |
|
|
55
|
+
| `TestFileRefusedError` | the cause's, else by `code` | no | The upload route refused a test file (`kind: "test"`) for what it claims, never for its bytes. `uploadFile` and `prepareInputs` wrap the platform's `ApiResponseError` and take its verdict; without such a cause, `code` decides: `role_not_permitted`, the `403` of a role that may not change methods, is `config`, since an admin must change the role, and `method_not_found` (`404`) and `method_being_deleted` (`409`) are `input`, since the upload must name another method ([files.md](./files.md#uploading-a-test-file)). |
|
|
55
56
|
| `UnsupportedUploadCapabilityError`, `UploadAuthenticationError` | `config` | no | The deployment or the credential must change. |
|
|
56
57
|
| `UploadTransportError` | the cause's, else by `code` | the cause's, else by `code` | `uploadFile` wraps the `ApiResponseError` or `ApiUnreachableError` the client threw, and takes its verdict, so a wrapped `402` is `config` and not retryable. Without such a cause, which is always the case from `uploadWithGrant`: `timeout`, `storage_timeout` and `conflict` are `runtime` and retryable, `conflict` because storage documents its `409 ConditionalRequestConflict` as retryable and the grant is not spent by it (a spent grant is the `412` of a `RejectedAssetError` with code `grant_used`); `server_error` is `runtime`, and retryable when [the fallback table](#a-refused-requests-verdict) would call its `status` retryable, any `5xx` but the `501` storage answers for a request it does not implement, or when it carries no status; `unexpected` is `runtime`, and retryable when that table would call its `status` retryable, which is the `408` or `429` storage answers when it times out or throttles the request rather than refusing the file, and not retryable for any other status or none; `unreachable` is `config` and retryable; `redirected` is `config` and not retryable; `invalid_grant_url` is `runtime` and not retryable. |
|
|
57
58
|
| `MethodFileTransferError` | `runtime` | by `code` | A method's file could not be moved between this machine and storage: uploaded by `createMethod` or `writeDraft`, so the method was not saved, or by a run, `validate`, `resolve`, `codegen`, `pipeIo` or `prepareInputs` sending a `directory`, `tree` or `contents` by hash, so the request was not sent, or downloaded by `writeMethodTree`. `unreachable` (no answer), `storage_error` (`408`, `429` or a `5xx` other than `501`) and `mismatch` (downloaded bytes that are not the file listed) are retryable; `refused`, any other status, an expired link among them, is not. It carries `operation`, `code`, `sha256`, `path` and `status`, and neither the link, nor storage's error body, nor a cause ([method-trees.md](./method-trees.md#when-a-transfer-fails)). |
|
|
@@ -317,6 +318,10 @@ A refusal about a saved method or one of its versions carries one of the platfor
|
|
|
317
318
|
|
|
318
319
|
A malformed version suffix (`@0`, `@03`, `@Draft`, two suffixes) is a `422` `validation_failed` whose `errors` name the field `method_id`; `parseMethodSelector` refuses the same selectors locally with a `RequestArgumentError`, before anything is sent. So is any suffix on the run routes' linkage form, a `method_id` beside an inline source, which `execute` and `start` refuse locally the same way.
|
|
319
320
|
|
|
321
|
+
### The branding codes
|
|
322
|
+
|
|
323
|
+
A refused branding write (`updateOrganizationBranding`) names each logo or colour it refused in `errors`: the entry's `field` is the logo's slot, `square_logo` or `wide_logo`, or the colour's dotted key, such as `colors.action`, and its `detail` the sentence a settings screen shows under that control. A logo the platform read and refused is a `422` `validation_failed` with one entry per broken rule, both slots at once, each `code` one the exported type `LogoRefusalCode` names (`logo_too_large`, `logo_not_square`, `logo_too_small`, …); a logo URI refused before any read names its slot the same way, under `400` `invalid_storage_uri` or `403` `forbidden`. [organization-branding.md](./organization-branding.md#when-a-write-is-refused) lists every code with a sentence of each.
|
|
324
|
+
|
|
320
325
|
## A validation item — `ValidationErrorItem`
|
|
321
326
|
|
|
322
327
|
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 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:
|
package/docs/files.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# An organization's files
|
|
2
|
+
|
|
3
|
+
A **file** is the record the hosted platform keeps of a stored object: a member's upload, a test file made for a method, or a file a run made that reached its result. The bytes stay where they were written, and a run, a form or an example still points at them by their `pipelex-storage://` address; the record adds what the bytes lack, the name a person sees, a kind, a type, a size and where the file came from. A file belongs to the organization: every role of it reads every file of it, as every role reads every run. The client reaches the list on the hosted API's `GET /v1/files`, and the types are in `src/product-models.ts`. The platform's own contract is `platform/docs/files.md` in `pipelex-server`.
|
|
4
|
+
|
|
5
|
+
The point of the list for a program is the `uri` on every row: a run's inputs take it as they take any address, so an agent or an app can find "the invoice uploaded this morning" and run a method on it without uploading it again.
|
|
6
|
+
|
|
7
|
+
## A row — `FileSummary`
|
|
8
|
+
|
|
9
|
+
| Field | Meaning |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `file_id` | The record's id, `fl_…`. |
|
|
12
|
+
| `uri` | The `pipelex-storage://` address of the bytes. |
|
|
13
|
+
| `name` | The name shown and used for downloads. It starts as the name the file arrived with and changes with a rename. |
|
|
14
|
+
| `media_type` | The MIME type the object is stored under. |
|
|
15
|
+
| `format` | The format key the list's `format` filter matches: `pdf`, `docx`, `image`, `html` and the rest of the runtime's table. |
|
|
16
|
+
| `size` | The byte size. |
|
|
17
|
+
| `kind` | A `FileKind`: `uploaded`, a person's upload or a run's inline input; `generated`, a file a run made whose result reaches it; `test`, a test input made for one method. |
|
|
18
|
+
| `origin` | A `FileOrigin`, who or what made the file, each member `null` where the kind fills no value: `surface`, the surface an upload came through as its `User-Agent` declared it (`app`, `mcp`, `cli`, `n8n`, `hook`, `sdk`, `raw_http`), which is provenance to show and never authority; `run_id`, the run that made it; `method_id`, the method whose run made it or the method a test file was made for; `method_version`, the version number or `"draft"` that ran. |
|
|
19
|
+
| `created_at` | The ISO-8601 instant the record was made. |
|
|
20
|
+
| `created_by` | The member the file is attributed to: the uploader, or the member who started the run that made it. |
|
|
21
|
+
|
|
22
|
+
**A list never holds a file a run made along the way** (an intermediate file, which the run's result does not reach), **nor an upload whose bytes have not been seen stored**, nor anything of another organization.
|
|
23
|
+
|
|
24
|
+
## The methods
|
|
25
|
+
|
|
26
|
+
| Method | Route | Answer |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `listFiles(query?)` | `GET /v1/files` | One `FilePage`, `{ items, nextCursor }`, newest first. |
|
|
29
|
+
| `iterateFiles(query?)` | the same, page after page | Every file the query selects, as an async iterator. |
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const page = await client.listFiles({ q: "invoice", kind: "uploaded", format: ["pdf"] });
|
|
33
|
+
for (const file of page.items) console.log(file.name, file.uri);
|
|
34
|
+
|
|
35
|
+
// A method's files: what its runs and examples took or made.
|
|
36
|
+
for await (const file of client.iterateFiles({ methodId: "mt_abc123" })) {
|
|
37
|
+
if (file.kind === "test") console.log("test input", file.name);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## The query — `ListFilesQuery`
|
|
42
|
+
|
|
43
|
+
Every member is optional, and each is sent only when given, so an empty `q`, cursor or date is sent and refused by the API rather than dropped into an unfiltered list. The members follow the SDK's camelCase, as `ListRunsQuery`'s do, and each is sent under the API's own name.
|
|
44
|
+
|
|
45
|
+
| Member | Sent as | Meaning |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `methodId` | `method_id` | The method's list, by its bare catalog id: the files its runs and examples took or made. |
|
|
48
|
+
| `runId` | `run_id` | The run's own list: its final outputs and its inline inputs, **in the order of their addresses** rather than newest first. Beside `methodId`, the run's list when the method is the run's own, and an empty page otherwise. The files a run took from the organization as inputs are not in it. |
|
|
49
|
+
| `createdBy` | `created_by` | One member's files by user id, or `"me"` for the caller's own: their uploads and their runs' outputs. |
|
|
50
|
+
| `kind` | `kind` | `uploaded`, `generated` or `test`. |
|
|
51
|
+
| `format` | `format`, repeated | Only the files of one of these format keys, one parameter per value (`format=pdf&format=image`), at most 100. |
|
|
52
|
+
| `mediaType` | `media_type`, repeated | Only the files of one of these MIME types, one parameter per value, at most 100. |
|
|
53
|
+
| `createdFrom`, `createdTo` | `created_from`, `created_to` | ISO-8601 **instants** with an offset, both inclusive. A bare date or a naive timestamp is a `400`, since only the caller knows which time zone's day it means. |
|
|
54
|
+
| `q` | `q` | A case-insensitive search in the name and the name the file arrived with, at most 200 characters. |
|
|
55
|
+
| `limit` | `limit` | Page size, 1 to 200, 50 by default. |
|
|
56
|
+
| `cursor` | `cursor` | The previous page's `nextCursor`. `iterateFiles` takes no cursor: it follows its own. |
|
|
57
|
+
|
|
58
|
+
`methodId`, `runId` and `createdBy` choose which list serves the page, and with none of them it is the organization's. The other members filter that list.
|
|
59
|
+
|
|
60
|
+
**An empty `format` or `mediaType` is refused before any request** with a `RequestArgumentError`, since a list with no value would be sent as no parameter at all and list every format; a value that is not a list of strings is a `TypeError`, a bug in the calling code. Everything else the API checks itself: a `limit` out of range, an unknown `kind`, more than 100 values of `format` or `mediaType`, or an id that cannot be one is a `422`; a date bound that is not an instant with an offset, a range whose start is after its end, or a cursor of another list or other filters is a `400`; and a run that is not the organization's is a `404` `run_not_found`. Each is an `ApiResponseError` ([errors.md](./errors.md)).
|
|
61
|
+
|
|
62
|
+
## Paging
|
|
63
|
+
|
|
64
|
+
**Only a `null` `nextCursor` ends a list.** The platform applies the filters to the rows a page reads and never refills the page, so a filtered page can hold fewer rows than `limit`, or none, and still carry a cursor; a client keeps asking until the cursor is `null`. **A cursor is bound to the list and the filters that issued it**, so a client passes it back with the same query, and passing it to another is a `400`.
|
|
65
|
+
|
|
66
|
+
`iterateFiles` does both: it sends the same query with every page, keeps going through an empty page that carries a cursor, stops on a `null` cursor or on a cursor the server failed to advance, and throws `PagingNotTerminatingError` past a page ceiling no real list reaches, through the loop `iterateMethods`, `iterateRuns` and `iterateMethodExamples` share. A page whose `items` is not an array, or whose `next_cursor` is neither a string nor `null`, is an `ApiResponseError` of an answer the SDK cannot read (`runtime`, not retryable), never a page the iterator walks.
|
|
67
|
+
|
|
68
|
+
## Uploading a test file
|
|
69
|
+
|
|
70
|
+
What an upload is recorded as travels with it to `POST /v1/upload`: its `kind`, `uploaded` when omitted, and for a `test` file the bare catalog id of the method it is for, `method_id`. A test file is a statement about a method, so the platform holds it to what changing that method takes. The pair is spelled the same on every surface that sends it: `UploadInput` (the raw `upload`), `UploadFileOptions` (`uploadFile`) and `prepareInputs`' nested `upload` member, which applies to every file the preparation uploads. `UploadKind` names the two kinds, and `UploadKindOptions` the pair.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// One file the build assistant made to try a method:
|
|
74
|
+
await client.uploadFile("cases/invoice.pdf", { kind: "test", method_id: "mt_abc123" });
|
|
75
|
+
|
|
76
|
+
// Every file a preparation uploads, recorded as test files of the method it prepares:
|
|
77
|
+
const prepared = await client.prepareInputs({
|
|
78
|
+
method_id: "mt_abc123",
|
|
79
|
+
inputs: { invoice: "cases/invoice.pdf" },
|
|
80
|
+
upload: { kind: "test", method_id: "mt_abc123" },
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`upload` is nested so that its `method_id`, the method a test file is for, never reads as the request's own `method_id` selector, which names the method whose inputs are prepared and excludes `method_ref` and the method's tree.
|
|
85
|
+
|
|
86
|
+
**The pair is checked before any file is read**, as the upload route checks it, so a request the route would refuse with a `422` is an `InputPreparationError` instead: a `kind` that is neither `uploaded` nor `test`, a `method_id` that is not a catalog id or carries a version suffix, `kind: "test"` without a `method_id`, and a `method_id` without `kind: "test"`. `null` reads as absent. `prepareInputs` names the members it refuses `upload.kind` and `upload.method_id`.
|
|
87
|
+
|
|
88
|
+
**A test file the route refuses for what it claims is a `TestFileRefusedError`**, whose `code` says why: `role_not_permitted`, the `403` of a caller whose role may not change methods, such as a runner, which an admin of the organization must change (`config`), or which the caller avoids by uploading the file as `uploaded`; `method_not_found`, the `404` of a method that is not one of the organization's (`input`); and `method_being_deleted`, the `409` of a method being erased (`input`). It carries the `status`, the `filename` it was sending and the `methodId` it named, and wraps the `ApiResponseError` as `cause`, whose verdict it takes. These refusals are read off the platform's code, never the status alone: a `403` without `role_not_permitted` is still a refused credential (`UploadAuthenticationError`), and a `404` is still a deployment without upload (`UnsupportedUploadCapabilityError`) unless the upload named a method and the platform answered its domain miss, `not_found` with the `input` domain, rather than the `config` `404` of a route it does not serve ([input-preparation.md](./input-preparation.md#error-and-capability-behavior)).
|
|
@@ -20,6 +20,7 @@ Uploads one asset and returns its upload record. It is the language-native conve
|
|
|
20
20
|
- **Path strings are Node-only.** A filesystem path string is read from disk, which only a process that owns a filesystem can do. In a non-Node runtime a path string fails instructively rather than being misread — the SDK does not silently treat it as text or as a URL.
|
|
21
21
|
- A string that is an **HTTP(S) URL** or an existing **`pipelex-storage://` URI** is not a local asset and passes through in any runtime (see pass-through rules below).
|
|
22
22
|
- Open file objects and streams are **deferred** — they can be added later without removing anything.
|
|
23
|
+
- **A test file.** `kind: "test"` with the `method_id` of the method it is for, among the options, records the upload as that method's test file rather than a person's file, sent with it to `POST /v1/upload`; the pair is checked before the asset is read ([files.md](./files.md#uploading-a-test-file)).
|
|
23
24
|
|
|
24
25
|
> **Runtime acceptance vs. bundling.** These source types are accepted at *runtime* in every environment, and the main `@pipelex/sdk` entry bundles for a browser or edge target with nothing marked external: such a bundle resolves the Node host to its empty variant, a module holding no Node built-in ([architecture.md](./architecture.md#the-node-host)), so path strings stay Node-only, and where the host is absent a path string is refused with `InvalidLocalSourceError`, never read. The client still belongs on a server, since it holds an API key (Next.js Server Actions, the MCP server, the plugins hook), so a page imports the main entry for its error classes and types, and a page that must store a file uses the upload grant below instead, whose sender ships from the `@pipelex/sdk/upload` entry.
|
|
25
26
|
|
|
@@ -160,6 +161,14 @@ client.prepareInputs({ method_id, inputs, allowUploads: false }) → PreparedInp
|
|
|
160
161
|
|
|
161
162
|
The error names the input as `inputName` and where inside it the value sits as `inputPath`: the input's name for a top-level file, then `.field` for each declared field and `[index]` for each list element (`dossier.attachments[1]`). An explicit envelope adds nothing to the path, since its `content` is the input's value. `valueKind` says what the value is, `local_path`, `data_url` or `bytes`, so a consumer can tell its caller what to send instead; the value itself is never in the message or on the error, since it is the caller's data. The option is `true` when omitted, must be a boolean when given — anything else is refused with an `InputPreparationError` before any request, since reading a stray `"false"` as permission would upload exactly what the caller meant to keep — and is not sent to the server. The option covers `prepareInputs` alone: `uploadFile` is an upload by definition.
|
|
162
163
|
|
|
164
|
+
#### Recording the uploads as test files
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
client.prepareInputs({ method_id, inputs, upload: { kind: "test", method_id } }) → PreparedInputs
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`upload` says what every file the preparation uploads is recorded as, sent with each upload: its `kind` and, for a test file, the bare catalog id of the method it is for, such as the inputs a build assistant makes to try a method. Omitted, every upload is a person's file. It is a member of its own so that its `method_id` never reads as the request's own `method_id` selector, and it is checked once, before the pipe I/O request, as `uploadFile` checks its options: anything the upload route would refuse with a `422` is an `InputPreparationError` naming `upload.kind` or `upload.method_id`, and an `upload` that is not an object is refused the same way. A file the route refuses for what a test file claims is a `TestFileRefusedError` ([files.md](./files.md#uploading-a-test-file)). It is not sent to the pipe I/O route, and under `allowUploads: false` it records nothing, since nothing is uploaded.
|
|
171
|
+
|
|
163
172
|
## A method's files in hand
|
|
164
173
|
|
|
165
174
|
Every method-taking operation — `execute` / `start`, `validate` / `resolve` / `codegen` / `pipeIo`, and `prepareInputs` — takes a `method_id` **natively** on the hosted API, as a server pass-through, its version suffix included (`mt_…`, `mt_…@<n>`, `mt_…@draft`): the platform resolves it and injects the tree of the version it names before the runner sees the request (see [architecture.md → hosted run extensions](./architecture.md#hosted-run-extensions-method_id)). That is the uniform rule — *an id option is always a server pass-through, and any client-side expansion is the caller's own explicit call*.
|
|
@@ -229,13 +238,14 @@ The contract distinguishes these semantic outcomes, each a typed subclass of `In
|
|
|
229
238
|
- **invalid input value** (`InvalidInputValueError`, carries `inputName` and `inputPath`) — a value at a file input that cannot be turned into a file to upload: a `data:` URL that does not decode (no comma, bad base64, bad percent-encoding), or a value of a type no file input takes, such as a number or an object that is neither bytes nor `{url}` content. It is raised while the inputs are read, before anything is uploaded. It names the input and the path inside it as `UploadNotAllowedError` does (`Input "dossier.attachments[1]" holds a number, which no file input takes: …`), describes the value by its kind and never quotes it, and lists the forms a file input takes in the mode it ran in. With `InvalidLocalSourceError`, it is how a consumer tells the inputs' own mistakes from a method that does not load, though all three are `InputPreparationError`s with the same verdict;
|
|
230
239
|
- **upload not allowed** (`UploadNotAllowedError`, carries `inputName`, `inputPath` and `valueKind`) — under `allowUploads: false`, a value at a file input that would need an upload: a local path, a `data:` URL or bytes. It is raised before anything is read, decoded or uploaded (see "[Preparing without uploads](#preparing-without-uploads)");
|
|
231
240
|
- **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`);
|
|
232
|
-
- **
|
|
233
|
-
- **
|
|
241
|
+
- **refused test file** (`TestFileRefusedError`, carries `code`, `status`, `filename` and `methodId`) — the upload route refused a test file for what it claims, never for its bytes: `role_not_permitted`, the `403` of a caller whose role may not change methods; `method_not_found`, the `404` the platform answers as `not_found` in the `input` domain for a method that is not the organization's; `method_being_deleted`, the `409` of a method being erased. Each is read off the platform's code, never the status alone;
|
|
242
|
+
- **unsupported server capability** (`UnsupportedUploadCapabilityError`) — the configured deployment has no upload route: any other `404`, the platform's `config` one for a route it does not serve or a bare runner's, which carries no code;
|
|
243
|
+
- **authentication / authorization failure** (`UploadAuthenticationError`) — a `401`, or a `403` other than a test file's `role_not_permitted`;
|
|
234
244
|
- **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, a `2xx` answer the SDK cannot read included, such as one with no string `uri`; `uploadWithGrant`'s codes are in the table above.
|
|
235
245
|
|
|
236
|
-
**Which file.** Every upload failure `uploadFile` raises names the file it was sending, as it sent it: `RejectedAssetError`, `UploadAuthenticationError`, `UnsupportedUploadCapabilityError` and `UploadTransportError` each carry `filename`, so a consumer that prepared several files says which one failed without parsing a message. `uploadWithGrant` sends a grant's file, not a named one, and leaves `filename` undefined on the `UploadTransportError` it raises.
|
|
246
|
+
**Which file.** Every upload failure `uploadFile` raises names the file it was sending, as it sent it: `RejectedAssetError`, `TestFileRefusedError`, `UploadAuthenticationError`, `UnsupportedUploadCapabilityError` and `UploadTransportError` each carry `filename`, so a consumer that prepared several files says which one failed without parsing a message. `uploadWithGrant` sends a grant's file, not a named one, and leaves `filename` undefined on the `UploadTransportError` it raises.
|
|
237
247
|
|
|
238
|
-
Each of these carries the verdict every SDK error does, `retryable` and `errorDomain` ([errors.md](./errors.md#each-classs-verdict)): the family's own is `input` and not retryable, an unsupported deployment and a refused credential are `config`, and a transport failure takes the verdict of the error it wraps, so a `402` plan refusal wrapped by `uploadFile` is `config` and not retryable, or, wrapping none, the verdict its `code` gives (`timeout`, `storage_timeout` and `conflict` retryable, `server_error` retryable unless storage answered a `501`, `unreachable` and `redirected` the environment's). A pipe I/O answer whose `is_valid` is not a boolean, whose `input_form` does not describe the pipe it selected, or whose descriptor for that pipe is malformed where the walk reads it or names a kind this SDK does not know, is a plain `InputPreparationError` with a `runtime` verdict, not retryable, since the route broke its own contract. The descriptor is checked whole, once, before any caller value is read or anything uploaded, so the refusal does not depend on which inputs the caller supplied, and its message names the pipe, where in the descriptor the fault sits (`fields[3].item`) and what was found there.
|
|
248
|
+
Each of these carries the verdict every SDK error does, `retryable` and `errorDomain` ([errors.md](./errors.md#each-classs-verdict)): the family's own is `input` and not retryable, an unsupported deployment and a refused credential are `config`, a refused test file takes the platform's verdict, `config` for a role and `input` for a method, and a transport failure takes the verdict of the error it wraps, so a `402` plan refusal wrapped by `uploadFile` is `config` and not retryable, or, wrapping none, the verdict its `code` gives (`timeout`, `storage_timeout` and `conflict` retryable, `server_error` retryable unless storage answered a `501`, `unreachable` and `redirected` the environment's). A pipe I/O answer whose `is_valid` is not a boolean, whose `input_form` does not describe the pipe it selected, or whose descriptor for that pipe is malformed where the walk reads it or names a kind this SDK does not know, is a plain `InputPreparationError` with a `runtime` verdict, not retryable, since the route broke its own contract. The descriptor is checked whole, once, before any caller value is read or anything uploaded, so the refusal does not depend on which inputs the caller supplied, and its message names the pipe, where in the descriptor the fault sits (`fields[3].item`) and what was found there.
|
|
239
249
|
|
|
240
250
|
All preparation failures are raised **before any run is created**.
|
|
241
251
|
|