@pipelex/sdk 0.49.0 → 0.51.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 CHANGED
@@ -167,11 +167,31 @@ for await (const saved of client.iterateMethodExamples(created.method_id)) {
167
167
  // every example, oldest first
168
168
  }
169
169
 
170
+ // Copy a method into a team, or into your personal workspace, with its draft, its form inputs and
171
+ // its examples (web sessions only). A bundle naming storage the destination cannot read answers
172
+ // `refused`, a verdict rather than an error; the key makes a retry copy once.
173
+ const copy = await client.copyMethod(
174
+ created.method_id,
175
+ { destination_org_id: "org_team", expected_draft_updated_at: draft.updated_at },
176
+ { idempotencyKey: crypto.randomUUID() },
177
+ );
178
+ if (copy.outcome === "copied") console.log(copy.method.copied_from); // the original it names
179
+ const copies = await client.listMethodCopies(created.method_id); // where the copies went
180
+
170
181
  // The organization's files, newest first: uploads, test files and the files runs made. Each row's
171
182
  // `uri` goes into a run's inputs as any address does. A filtered page can be empty and still carry
172
183
  // a nextCursor; iterateFiles follows it.
173
184
  const files = await client.listFiles({ q: "invoice", kind: "uploaded", format: ["pdf"] });
174
185
  await client.start({ method_id: created.method_id, inputs: { invoice: { url: files.items[0]!.uri } } });
186
+ // One file with its uses, a link that downloads it under its name, and a zip of a run's files.
187
+ const fileDetail = await client.getFile(files.items[0]!.file_id);
188
+ for (const link of (await client.linkFiles([{ file_id: fileDetail.file_id }])).items) {
189
+ // A fifteen-minute link, or why this file got none. Hand link.url to a browser, never a log.
190
+ if (link.error === null) console.log(`${link.name} downloadable until ${link.expires_at}`);
191
+ else console.error(link.error.code);
192
+ }
193
+ const archive = await client.requestFileArchive({ run_id: run.pipeline_run_id });
194
+ // then getFileArchive(archive.archive_id) until its state is ready or failed.
175
195
  // An upload recorded as a test file of the method it was made to try.
176
196
  await client.uploadFile("cases/receipt.pdf", { kind: "test", method_id: created.method_id });
177
197
 
@@ -233,19 +253,18 @@ const client = new PipelexApiClient({ appInfo: { name: "acme-invoicer", version:
233
253
 
234
254
  ### Uploading from a browser
235
255
 
236
- A browser page that holds a file but not the API key can still store it: the server that holds the key asks for an upload grant, and the page sends the file straight to storage with it. The file never crosses your server or the API gateway, so it can be as large as the service's own limit. The page imports `@pipelex/sdk/upload`, which carries only the grant's sender, its types and its errors. Both entries bundle for the browser with nothing marked external, so a page may also import the main entry's error classes and types, while the client itself stays on the server that holds the key.
256
+ A browser page that holds a file but not the API key can still store it: the page describes the file, its SHA-256 included, the server that holds the key asks for an upload grant with that description, and the page sends the file straight to storage with it, unless the grant says your organization already holds those bytes, in which case nothing is sent at all. The file never crosses your server or the API gateway, so it can be as large as the service's own limit. The page imports `@pipelex/sdk/upload`, which carries only the description, the grant's sender, their types and their errors. Both entries bundle for the browser with nothing marked external, so a page may also import the main entry's error classes and types, while the client itself stays on the server that holds the key.
237
257
 
238
258
  ```ts
239
- // On the server, which holds the key:
240
- const grant = await client.requestUploadGrant({
241
- filename: "report.pdf",
242
- content_type: "application/pdf",
243
- size: 48213,
244
- });
245
-
246
259
  // In the page, which holds the file:
247
- import { uploadWithGrant } from "@pipelex/sdk/upload";
248
- const { uri } = await uploadWithGrant(grant, file); // pipelex-storage://…
260
+ import { describeUpload, uploadWithGrant } from "@pipelex/sdk/upload";
261
+ const description = await describeUpload(file); // { filename, content_type, size, sha256 }
262
+
263
+ // On the server, which holds the key, given the description:
264
+ const grant = await client.requestUploadGrant(description); // { stored, uri, file_id, url, … }
265
+
266
+ // Back in the page, with the grant:
267
+ const { uri, file_id } = await uploadWithGrant(grant, file); // sends nothing when grant.stored
249
268
  ```
250
269
 
251
270
  The full client surface is documented in [`docs/architecture.md`](./docs/architecture.md).
@@ -263,8 +282,9 @@ These pages ship inside the published package, so a reader who has only installe
263
282
  | [`docs/artifact-download.md`](./docs/artifact-download.md) | Turning the `pipelex-storage://` references a run produced back into bytes: `locateArtifacts`, `collectArtifacts`, `resolveArtifacts`, `fetchArtifact`, `downloadArtifacts`, and how a saved file is named after the field it fills |
264
283
  | [`docs/input-preparation.md`](./docs/input-preparation.md) | The other direction — `uploadFile` and `prepareInputs`, which turn local files into references a run can take, and the upload grant a browser page sends a file with |
265
284
  | [`docs/method-trees.md`](./docs/method-trees.md) | A method's tree: the four forms a run, a validation, a save and the crate routes take it in (a directory on disk among them), how it travels inline or by hash and what is uploaded, the files a step reads, the template check's warnings and which step reads which file of the tree, the deployment's limits, the read's listing with its links, `writeMethodTree`, and `MethodFileTransferError` |
266
- | [`docs/files.md`](./docs/files.md) | An organization's files: `listFiles` and `iterateFiles`, the row and its provenance, the query and its repeated filters, paging through filtered pages, and an upload recorded as a method's test file with the refusals that come with it |
267
- | [`docs/method-examples.md`](./docs/method-examples.md) | A method's saved examples: the create, the paged list and its iterator, the edit that raises the input revision, the latest-run write and its `recorded` or `stale` verdict, how an integer past `Number.MAX_SAFE_INTEGER` in the inputs stays exact, and what is refused before sending |
285
+ | [`docs/files.md`](./docs/files.md) | An organization's files: `listFiles` and `iterateFiles`, the row, the whole record and its provenance, the query and its repeated filters, paging through filtered pages, one file and its uses, the records of an address, the rename rule, links that name the file, a zip of files and its caps, a run's files, the copy into a method's draft, and an upload recorded as a method's test file with the refusals that come with it |
286
+ | [`docs/method-examples.md`](./docs/method-examples.md) | A method's saved examples: the create, the paged list and its iterator, the edit that raises the input revision, the latest-run write and its `recorded` or `stale` verdict, the build assistant's save and its `kept` or `created` verdict, how an integer past `Number.MAX_SAFE_INTEGER` in the inputs stays exact, and what is refused before sending |
287
+ | [`docs/method-copy.md`](./docs/method-copy.md) | Copying a method into another organization or duplicating it: what a copy holds and the stored files it copies, the `copied` or `refused` verdict, every refusal, the copy's idempotency key, the copies a method lists, and `copied_from` |
268
288
  | [`docs/organization-branding.md`](./docs/organization-branding.md) | An organization's logos and colours: `getBranding`, which every member and API key reads, `updateOrganizationBranding`, an admin's write that sets, clears or keeps each key, and how a refused logo or colour names its slot in `errors` so a settings screen shows the sentence under it |
269
289
  | [`docs/exact-integers.md`](./docs/exact-integers.md) | Which reads keep an integer past `Number.MAX_SAFE_INTEGER` exact, as a `bigint` (a run's inputs and outputs, an example's inputs), how a request body writes it back with its digits, and `parseExactJson` and `stringifyExactJson` for code showing such data as JSON text |
270
290
  | [`docs/crate-routes.md`](./docs/crate-routes.md) | `resolve`, `codegen` and `pipeIo` — the method's tree as the protocol's `files` map, with its dependencies, the normalized crate, the stamped types, and a method's I/O artifacts without a validation — and the offline `runCodegenCheck` that guards a committed tree |
@@ -15,7 +15,7 @@ import type { CommandIO } from "./io.js";
15
15
  * update notice and npm 12's run notices off the script's stderr, which carries the command's
16
16
  * own lines, while npm's warnings and errors still print.
17
17
  */
18
- export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.49.0";
18
+ export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.51.0";
19
19
  export declare const SCRIPT_WRITER = "@pipelex/sdk";
20
20
  export declare const SCRIPT_NEEDS = "Node 22.12 or later";
21
21
  /** Run `pipelex-sdk script` and return its exit code. */
package/dist/client.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { MTHDSProtocol, ModelCategory, ModelDeck, RunOptions, StartOptions, VersionInfo } from "mthds/protocol";
2
2
  import type { CodegenRequest, CodegenResponse, CrateRouteOptions, DictPipeOutput, FormatResponse, LintResponse, ModelCheckCategory, ModelReferenceVerdict, PipeIORequest, PipeIOResponse, PipelexRunResultStart, PipelexValidateSource, PipelexValidationResult, ResolveRequest, ResolveResponse, ValidateMethodSelector } from "./models.js";
3
3
  import { type GetRunResultOptions, type RunProgress, type RunRead, type RunResults, type RunResultState, type StartAndWaitForResultOptions, type WaitForResultOptions } from "./runs.js";
4
- import type { BillingPortalResponse, ChangePlanResponse, CheckoutResponse, FilePage, FileSummary, InvoiceView, Membership, MembershipsResponse, OrganizationBranding, OrganizationBrandingUpdate, ListFilesQuery, ListMethodExamplesQuery, ListMethodsQuery, ListMethodVersionsQuery, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, PipelexApiKeyCreated, PipelexApiKeyList, ListRunsQuery, RunHistoryItem, RunDetail, RunPage, PlanView, ResolvedStorageUrl, SavedMethodFile, SubscriptionResponse, UpdateRunInput, UploadGrant, UploadGrantInput, UploadInput, UploadedFile, UserProfile } from "./product-models.js";
4
+ import type { BillingPortalResponse, ChangePlanResponse, CheckoutResponse, DraftFileFromFileInput, DraftFileFromFileResult, FileArchive, FileArchiveRequest, FileDetail, FileLinkTarget, FileLinksResult, FilePage, FileRecord, FileRecordList, FileRenameInput, FileSummary, GetFileQuery, GetFilesByUriOptions, InvoiceView, Membership, MembershipsResponse, OrganizationBranding, OrganizationBrandingUpdate, ListFilesQuery, ListMethodCopiesQuery, ListMethodExamplesQuery, ListMethodsQuery, ListMethodVersionsQuery, ListRunFilesQuery, MethodCopyInput, MethodCopyPage, MethodCopyResult, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleAssistantSaveInput, MethodExampleAssistantSaveResult, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, PipelexApiKeyCreated, PipelexApiKeyList, ListRunsQuery, RunHistoryItem, RunDetail, RunFilesPage, RunPage, PlanView, ResolvedStorageUrl, SavedMethodFile, SubscriptionResponse, UpdateRunInput, UploadGrant, UploadGrantInput, UploadInput, UploadedFile, UserProfile } from "./product-models.js";
5
5
  import type { MethodTreeForms, MethodTreeLimits } from "./method-trees.js";
6
6
  import type { AppInfo } from "./user-agent.js";
7
7
  import type { UploadableAsset, UploadFileOptions, UploadRecord } from "./upload.js";
@@ -122,6 +122,34 @@ export interface PipelexStartRequestOptions {
122
122
  */
123
123
  idempotencyKey?: string | null;
124
124
  }
125
+ /**
126
+ * What a method copy sends beside its body: the request's own header, which no copy argument
127
+ * reaches, camelCase since it is no wire field of the copy.
128
+ */
129
+ export interface MethodCopyRequestOptions {
130
+ /**
131
+ * The copy's idempotency key, sent as the `Idempotency-Key` header of
132
+ * `POST /v1/methods/{id}/copy`, and not sent at all when absent or `null`. Generate one per copy
133
+ * the caller means to make (a `crypto.randomUUID()` is a good key), and keep it, with the
134
+ * arguments unchanged, across that copy's retries.
135
+ *
136
+ * The hosted API stores the copy's answer under the key for 24 hours, scoped to the caller and
137
+ * the method's organization, so the same copy sent again under the same key is answered with the
138
+ * stored answer and copies nothing more: a copy whose answer was lost, as on a client timeout,
139
+ * can be sent again unchanged and makes one copy, not two. A verdict and a `4xx` refusal of the
140
+ * copy are stored alike, so the same copy under the same key is answered the same again; a `5xx`
141
+ * and a refusal for its timing (a `408`, `425` or `429`) are not stored, and a copy that failed
142
+ * with one left nothing visible, so the same key sends it afresh.
143
+ * The same key with a different body is refused (`409` `conflict`), as is a key whose earlier
144
+ * request is still being handled (`409` `idempotency_key_in_use`, retryable: send it again
145
+ * under the same key in a few seconds); a changed copy, such as one with a fresh draft token,
146
+ * goes under a new key. See `docs/method-copy.md`.
147
+ *
148
+ * A key is held to the rule a start's is (`PipelexStartRequestOptions.idempotencyKey`): 1 to
149
+ * 255 visible ASCII characters, anything else a `RequestArgumentError` before any request.
150
+ */
151
+ idempotencyKey?: string | null;
152
+ }
125
153
  /**
126
154
  * `start()` / `startAndWaitForResult()` options — the same, for the durable path, plus the start
127
155
  * request's own `idempotencyKey`.
@@ -231,7 +259,7 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
231
259
  * resolved absolute URL. `mayStartRun` marks a request that may create a run,
232
260
  * whose `ApiUnreachableError` then says whether it may have (see `RunCreation`).
233
261
  * `idempotencyKey`, already checked by `idempotencyKeyOf`, is sent as the
234
- * `Idempotency-Key` header; only `start` passes one.
262
+ * `Idempotency-Key` header; only `start` and `copyMethod` pass one.
235
263
  */
236
264
  private requestRaw;
237
265
  /**
@@ -1215,6 +1243,71 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1215
1243
  * never rewritten as one, which names no tree to serve.
1216
1244
  */
1217
1245
  getMethodVersion(methodId: string, version: number): Promise<MethodVersion>;
1246
+ /**
1247
+ * Copy a method into an organization the caller may change methods in —
1248
+ * `POST /v1/methods/{id}/copy`: the method's own organization for a duplicate, another team, or
1249
+ * the caller's personal workspace.
1250
+ *
1251
+ * The copy is a new, unpublished method of the destination, the caller's, holding the
1252
+ * original's draft tree (the same `draft_digest`), its name unless `name` gives another, its
1253
+ * form inputs and every saved example, each stored file they name copied into the destination,
1254
+ * and `copied_from` naming the original. Published versions and runs are not copied, and the
1255
+ * original stays as it was. The copy takes the draft, never the latest published version; with
1256
+ * `expected_draft_updated_at`, it takes exactly the draft the caller last saw.
1257
+ *
1258
+ * The answer is a `MethodCopyResult` discriminated on `outcome` — branch on it: `copied` with
1259
+ * the copy as `method`, `examples_copied` and `stored_files_copied`; `refused` with the
1260
+ * `reason` `bundle_names_other_storage`, a `message` and every `references` entry (`{ path, uri
1261
+ * }`) a `.mthds` file of the draft holds that the destination cannot read, nothing written.
1262
+ *
1263
+ * The route serves web sessions only: a Pipelex API key belongs to one organization and cannot
1264
+ * speak for another. `options.idempotencyKey` is sent as the `Idempotency-Key` header, so a copy
1265
+ * sent again under the same key is answered with the first one's answer rather than copied
1266
+ * twice (see `MethodCopyRequestOptions`).
1267
+ *
1268
+ * Takes a bare catalog id, as `getMethod` does. Sends `destination_org_id` and, when given,
1269
+ * `name` and `expected_draft_updated_at`, and nothing else: the body is closed, and a `null`
1270
+ * reads as not given. Throws `RequestArgumentError` before any request for a
1271
+ * `destination_org_id` that is not a non-empty string, a `name` or an
1272
+ * `expected_draft_updated_at` given that is not a string, and a malformed idempotency key.
1273
+ * Throws `ApiResponseError` when no verdict was produced, each refusal writing nothing a reader
1274
+ * sees: `403` `forbidden` for a request that is not from a web session; `403`
1275
+ * `role_not_permitted` for a role that may not change methods, in the method's organization or
1276
+ * in the destination; `404` `organization_not_found` for a destination the caller does not
1277
+ * belong to, still being set up or unknown, alike; `404` `not_found` for an unknown method;
1278
+ * `409` `method_being_deleted`; `409` `method_update_conflict` for a draft that moved since
1279
+ * `expected_draft_updated_at`; `409` `conflict`, `runtime`, for a draft whose tree the
1280
+ * organization holds no full record of; `413` `payload_too_large` for an example or a copy too
1281
+ * large for one stored item; `422` `validation_failed` for a body that does not validate, such
1282
+ * as a name holding a lone surrogate; `422` `method_copy_too_large` past one of the copy's
1283
+ * bounds on the stored files it copies, named by `problemDocument.limit` (`MethodCopyLimit`);
1284
+ * and, each retryable, a `502` when the platform could not read the caller's memberships, a
1285
+ * `500` `storage_backend_failed` and a `503` past the copy's deadline. An answer whose
1286
+ * `outcome` is neither arm's, a `copied` one whose `method` is not an object or a `refused` one
1287
+ * whose `references` is not an array, is an answer the SDK cannot read, thrown as an
1288
+ * `ApiResponseError` too.
1289
+ */
1290
+ copyMethod(methodId: string, input: MethodCopyInput, options?: MethodCopyRequestOptions): Promise<MethodCopyResult>;
1291
+ /**
1292
+ * One page of the copies made of a method, in the order of the copies' ids, which says nothing
1293
+ * of when each was made — `GET /v1/methods/{id}/copies`.
1294
+ *
1295
+ * Each row is the `MethodCopyMarker` the original keeps of a copy: the copy's id, the
1296
+ * destination's id, the member who made it and when. The destination is named by its id alone,
1297
+ * never its name, so a surface shows a destination's name only from the reader's own
1298
+ * memberships, and a copy in an organization the reader does not belong to stays a bare id. A
1299
+ * marker stays when its copy is erased, and the copy then reads as not found. Pass the page's
1300
+ * `nextCursor` back as `cursor` to continue; `null` is the last page. A method never copied,
1301
+ * and a copy itself, answer an empty page. The listing is open to every member of the method's
1302
+ * organization, a runner included, as every read of a method is.
1303
+ *
1304
+ * Takes a bare catalog id, as `getMethod` does. Throws `ApiResponseError`: `404` `not_found` for
1305
+ * an unknown method, `409` `method_being_deleted`, `400` `malformed_request` for a cursor the
1306
+ * listing did not issue for this method, `422` for a `limit` outside 1 to 100. A page whose
1307
+ * `items` is not an array or whose `next_cursor` is neither a string nor `null` is an answer the
1308
+ * SDK cannot read, thrown as an `ApiResponseError` too.
1309
+ */
1310
+ listMethodCopies(methodId: string, query?: ListMethodCopiesQuery): Promise<MethodCopyPage>;
1218
1311
  /**
1219
1312
  * Erase a method and everything it produced — `DELETE /v1/methods/{id}`.
1220
1313
  *
@@ -1342,6 +1435,36 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1342
1435
  * `ApiResponseError` too, read as `publishMethod` reads its verdict.
1343
1436
  */
1344
1437
  recordMethodExampleLatestRun(methodId: string, exampleId: string, input: MethodExampleLatestRunInput): Promise<MethodExampleLatestRunResult>;
1438
+ /**
1439
+ * Save the build assistant's test inputs as its example of the method —
1440
+ * `POST /v1/methods/{id}/examples/assistant-save` — **never writing over a person's edit**.
1441
+ *
1442
+ * The assistant keeps one example of a method current with its test inputs, until a person
1443
+ * edits that example's inputs. Find its example as the method's newest with `kept_by_assistant`
1444
+ * true (`listMethodExamples` answers them oldest first), and send its `example_id` with the
1445
+ * `input_revision` you read it at. The platform writes the inputs into it only while it still
1446
+ * exists, still carries the flag and still holds that revision, in one conditional write that it
1447
+ * never retries. Otherwise, and when no example is named, which is how the first save on a
1448
+ * method creates the assistant's example, it saves the inputs as a new example flagged as the
1449
+ * assistant's, under `name`.
1450
+ *
1451
+ * The answer is a `MethodExampleAssistantSaveResult` discriminated on `outcome` — branch on it:
1452
+ * `kept`, the named example now holds the inputs, its name untouched; `created`, a new example
1453
+ * holds them. Both carry the `example`, which is the assistant's from then on. A retry of a save
1454
+ * that landed answers `kept` and writes nothing again.
1455
+ *
1456
+ * Sends `name`, `inputs` and, when given, `example_id` with `input_revision`, and nothing else:
1457
+ * the body is closed, and `null` reads as not given. Throws `RequestArgumentError` before any
1458
+ * request when `name` or `inputs` is not given, for an `example_id` that is not a non-empty
1459
+ * string or an `input_revision` that is not a positive integer, and when only one of the two is
1460
+ * given. Throws `ApiResponseError`: `403` `role_not_permitted` for a role that may not change
1461
+ * methods, such as a runner; `413` `payload_too_large` for an example larger than one stored item
1462
+ * holds; `422` for a name that is empty, blank or over 200 characters, or inputs that are not a
1463
+ * JSON object; and the codes every example route answers. An answer whose `outcome` is neither
1464
+ * arm's, or whose `example` is not an example `readMethodExample` can read, is an answer the SDK
1465
+ * cannot read, thrown as an `ApiResponseError` too.
1466
+ */
1467
+ saveAssistantMethodExample(methodId: string, input: MethodExampleAssistantSaveInput): Promise<MethodExampleAssistantSaveResult>;
1345
1468
  /**
1346
1469
  * Delete an example — `DELETE /v1/methods/{id}/examples/{example_id}`. The platform answers
1347
1470
  * `204`. Deleting the example leaves its latest run in the method's history.
@@ -1484,7 +1607,8 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1484
1607
  */
1485
1608
  downloadArtifacts(request: DownloadArtifactsRequest): Promise<DownloadArtifactsResult>;
1486
1609
  /**
1487
- * Upload a base64 file — `POST /v1/upload`. An answer with no non-empty string `uri` names no
1610
+ * Upload a base64 file — `POST /v1/upload`, answering the file's `uri`, the `filename` as sent
1611
+ * and the `file_id` of its record. An answer with no non-empty string `uri` names no
1488
1612
  * stored file, so it is an answer the SDK cannot read: an `ApiResponseError`, `runtime` and
1489
1613
  * not retryable, which `uploadFile` wraps as an `UploadTransportError` with code `unexpected`.
1490
1614
  */
@@ -1492,18 +1616,24 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1492
1616
  /**
1493
1617
  * Request a grant to upload one file straight to storage — `POST /v1/upload/grant`.
1494
1618
  * `upload` for a caller that holds the bytes but not this client's credential, such
1495
- * as a browser page: the credential-holding side asks for the grant, hands it over,
1496
- * and the holder of the bytes sends them with `uploadWithGrant`, from the
1497
- * browser-safe `@pipelex/sdk/upload` entry. The bytes cross neither this client nor
1498
- * the API gateway, so the gateway's request quota does not cap the file below the
1499
- * service's own limit (`max_bytes`).
1500
- *
1501
- * The grant describes one new object: its `uri` exists once the `PUT` succeeds, and
1502
- * not before. It is create-only and short-lived, and it is a bearer capability, so
1503
- * keep it out of logs. The route never replays a grant: ask again for a new one
1504
- * rather than retrying. A declared `size` over the cap is a `413` `ApiResponseError`
1505
- * (`code` `payload_too_large`); a deployment without the route answers a `404`. See
1506
- * `docs/input-preparation.md`.
1619
+ * as a browser page: the holder of the bytes describes the file with `describeUpload`,
1620
+ * its SHA-256 included, the credential-holding side asks for the grant with that
1621
+ * description and hands the grant over, and the holder of the bytes sends them with
1622
+ * `uploadWithGrant`. Both are on the browser-safe `@pipelex/sdk/upload` entry. The
1623
+ * bytes cross neither this client nor the API gateway, so the gateway's request quota
1624
+ * does not cap the file below the service's own limit (`max_bytes`).
1625
+ *
1626
+ * **Branch on the answer's `stored`.** With the file's `sha256`, the object is named
1627
+ * after its bytes: when the organization already holds them, the grant answers
1628
+ * `stored: true` with the file's `uri` and `file_id` and no upload target, and nothing
1629
+ * is to be sent. Otherwise, and always without a `sha256`, it answers a presigned,
1630
+ * create-only `PUT` whose `uri` exists once the `PUT` succeeds, and not before. That
1631
+ * target is short-lived and a bearer capability, so keep it out of logs. The route
1632
+ * never replays a grant: ask again for a new one rather than retrying; with a
1633
+ * `sha256`, a new grant answers the same file record. A declared `size` over the cap
1634
+ * is a `413` `ApiResponseError` (`code` `payload_too_large`), a malformed `sha256` and
1635
+ * a `size` other than the one the organization holds those bytes at are a `422`, and a
1636
+ * deployment without the route answers a `404`. See `docs/input-preparation.md`.
1507
1637
  */
1508
1638
  requestUploadGrant(input: UploadGrantInput, options?: {
1509
1639
  signal?: AbortSignal;
@@ -1595,6 +1725,185 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1595
1725
  * `PagingNotTerminatingError` past a page ceiling no real file list reaches.
1596
1726
  */
1597
1727
  iterateFiles(query?: Omit<ListFilesQuery, "cursor">): AsyncGenerator<FileSummary, void, undefined>;
1728
+ /**
1729
+ * One file with its provenance, the methods whose lists hold it, and one page of its uses —
1730
+ * `GET /v1/files/{file_id}`.
1731
+ *
1732
+ * The answer is a `FileDetail`: the file's whole record (`FileRecord`), its `method_ids`, and
1733
+ * `uses`, one page of the examples that hold its address, then the runs that took it. Page the
1734
+ * uses by passing `uses.next_cursor` back as `usesCursor`; `null` is the last page. A record whose
1735
+ * upload looked unfinished past the upload grant's lifespan is settled by the read, so it answers
1736
+ * `stored` when its bytes are there.
1737
+ *
1738
+ * Throws `RequestArgumentError` for a file id that is empty, `.` or `..`, or holds a lone
1739
+ * surrogate, before any request. Throws `ApiResponseError`: `404` `not_found` for a file that is
1740
+ * not the organization's; `422` for a `usesLimit` outside 1 to 200 or an id that cannot be one.
1741
+ * An answer that is not an object, or whose `uses` is not a page of `items` and `next_cursor`, is
1742
+ * an answer the SDK cannot read, thrown as an `ApiResponseError` too. See `docs/files.md`.
1743
+ */
1744
+ getFile(fileId: string, query?: GetFileQuery): Promise<FileDetail>;
1745
+ /**
1746
+ * The records holding one `pipelex-storage://` address — `GET /v1/files/by-uri` — in the order a
1747
+ * surface picks the one to show, the first being it.
1748
+ *
1749
+ * Several records hold one address when several members uploaded the same bytes. With `runId`,
1750
+ * the records of the member who started that run come first, so a run's inputs show the name its
1751
+ * starter gave the file; then the caller's; then the rest, each group oldest first. A record
1752
+ * whose upload has not finished is answered with its `state`, `pending`. An address no record
1753
+ * holds answers an empty `items`, which is how a client tells a file stored before the platform
1754
+ * kept records.
1755
+ *
1756
+ * Throws `RequestArgumentError` for a `uri` that is not a non-empty string, before any request.
1757
+ * Throws `ApiResponseError`: `400` `invalid_storage_uri` for a malformed address, `403`
1758
+ * `forbidden` for another organization's, `404` `run_not_found` for a run that is not the
1759
+ * organization's, and `422` for an address over 512 characters. An answer whose `items` is not an
1760
+ * array is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
1761
+ */
1762
+ getFilesByUri(uri: string, options?: GetFilesByUriOptions): Promise<FileRecordList>;
1763
+ /**
1764
+ * Rename a file — `PATCH /v1/files/{file_id}` — and return its record as it now stands.
1765
+ *
1766
+ * **The platform holds the name to one rule, for every client**: it removes the characters no
1767
+ * list or header can show (control and format characters, a right-to-left override among
1768
+ * them), trims the ends, refuses a name with nothing left, and adds the file's own extension,
1769
+ * the one its `original_name` ends with, when the name does not end with it, case aside:
1770
+ * `March invoice` becomes `March invoice.pdf`, and `March.docx` becomes `March.docx.pdf`. A file
1771
+ * whose original name has no extension gains none. Read the saved name from the answer's
1772
+ * `name`. The rename changes `name` alone, never the file's bytes, its address or its
1773
+ * `original_name`.
1774
+ *
1775
+ * An `admin` or a `member` renames any file; any other role, a runner among them, only the files
1776
+ * whose record names it as `created_by`: its own uploads and the outputs of the runs it started.
1777
+ *
1778
+ * Sends `name` alone: the body is closed. Throws `RequestArgumentError` for a file id that is
1779
+ * empty, `.` or `..`, or holds a lone surrogate, and for a `name` that is not a string, before
1780
+ * any request. Throws `ApiResponseError`: `403` `role_not_permitted` past the rule above; `404`
1781
+ * `not_found` for a file that is not the organization's; `422` for a name over 512 characters or
1782
+ * with nothing left once cleaned. An answer that is not an object is an answer the SDK cannot
1783
+ * read, thrown as an `ApiResponseError` too.
1784
+ */
1785
+ renameFile(fileId: string, input: FileRenameInput): Promise<FileRecord>;
1786
+ /**
1787
+ * Fresh links to read files — `POST /v1/files/links` — one per requested file, in the request's
1788
+ * order, each named by exactly one of its record's id (`file_id`) and its address (`uri`).
1789
+ *
1790
+ * **Branch on each item's `error`, never on the status**: the request is a `200` whenever every
1791
+ * file got a verdict. A linked file carries its `url`, a presigned `GET` valid for fifteen
1792
+ * minutes, with its `expires_at`, the `name` the download saves under and its `media_type`, and
1793
+ * a `null` `error`. A file that got no link carries `error`, `{ code, detail }`, and `null` link
1794
+ * members: `not_found` for an unknown file id, `conflict` for a file whose upload has not
1795
+ * finished, `invalid_storage_uri` for a malformed address and `forbidden` for another
1796
+ * organization's.
1797
+ *
1798
+ * Each link is signed with a `Content-Disposition` naming the file, so a download saves under
1799
+ * the name a person sees rather than the bytes' hash. It opens in place (`inline`) for an image,
1800
+ * a PDF or plain text and downloads (`attachment`) for every other type, SVG, HTML and XML
1801
+ * included, so a file that could run scripts never opens from a link. A file named by its id is
1802
+ * linked under its record's name; an address under the name of its first stored record, the
1803
+ * caller's before another member's, whose id the item's `file_id` names. A link is a bearer
1804
+ * capability: keep it out of logs, and ask again for a fresh one rather than storing it.
1805
+ *
1806
+ * Sends each file as `{ file_id }` or `{ uri }` and nothing else: the body is closed. Throws
1807
+ * `RequestArgumentError` before any request when `files` is not a list, or one of its items does
1808
+ * not name exactly one of `file_id` and `uri` as a non-empty string. Throws `ApiResponseError`:
1809
+ * `422` for more than 100 files, or an id or address longer than one can be; `500` `config` when
1810
+ * the platform cannot sign at all. An answer that is not one item per requested file, each an
1811
+ * object with its `url` or its `error`, is an answer the SDK cannot read, thrown as an
1812
+ * `ApiResponseError` whose body hides every link.
1813
+ */
1814
+ linkFiles(files: readonly FileLinkTarget[]): Promise<FileLinksResult>;
1815
+ /**
1816
+ * Ask for one zip of chosen files, or of a run's files — `POST /v1/files/archives` — and return
1817
+ * the archive, `queued`.
1818
+ *
1819
+ * The platform cannot build a zip inside a request, so the zip is two calls: this one queues the
1820
+ * archive and answers at once (`202`), and `getFileArchive` reads it until its `state` is `ready`,
1821
+ * whose `url` downloads the zip, or `failed`, whose `failure` says why. Name the files by exactly
1822
+ * one of `file_ids`, in the order the zip holds them, and `run_id`, whose zip holds what went in
1823
+ * and what came out of the run, never a file it made along the way nor an input given as a web
1824
+ * link. `name` is the name the zip downloads under (see `FileArchiveRequest`). An archive is never
1825
+ * a file: no list shows it, and it is gone a day after it was asked for.
1826
+ *
1827
+ * Sends `file_ids` or `run_id`, and `name` when given, and nothing else: the body is closed, and a
1828
+ * `null` member reads as not given. Throws `RequestArgumentError` before any request when the
1829
+ * request names both or neither of `file_ids` and `run_id`, when `file_ids` is not a non-empty
1830
+ * list of non-empty strings, when `run_id` is not a non-empty string, and when `name` is not a
1831
+ * string. Throws `ApiResponseError`:
1832
+ *
1833
+ * - `422` `file_archive_too_large` past a cap, at most 500 files or 2,000,000,000 bytes, whose
1834
+ * `problemDocument.limit` names it (`FileArchiveLimit`), so the person can be told how far over
1835
+ * the selection is;
1836
+ * - `422` `validation_failed` for more than 1,000 ids listed, or an id longer than one can be;
1837
+ * - `404` `not_found` for a file that is not the organization's, the `detail` naming up to five
1838
+ * of them, or a run whose files hold nothing stored to zip; `404` `run_not_found` for a run
1839
+ * that is not the organization's;
1840
+ * - `409` `conflict`: an `input` one, not retryable, for a selected file whose upload has not
1841
+ * finished; a `runtime` one, retryable, for a run whose files are not all recorded yet, which
1842
+ * the same request clears a few seconds after the run ends;
1843
+ * - `503` `service_unavailable`, retryable, when the archive could not be queued: the same request
1844
+ * sent again asks for a new archive.
1845
+ *
1846
+ * An answer that is not an archive, an object with a string `archive_id` and a known `state`, is
1847
+ * an answer the SDK cannot read, thrown as an `ApiResponseError` too.
1848
+ */
1849
+ requestFileArchive(request: FileArchiveRequest): Promise<FileArchive>;
1850
+ /**
1851
+ * Where an archive stands — `GET /v1/files/archives/{archive_id}` — with a fresh link to its zip
1852
+ * once it is `ready`.
1853
+ *
1854
+ * **Branch on `state`.** `queued` and `building` mean read it again shortly: a zip of a few files
1855
+ * is ready within seconds, and the largest the caps allow within a few minutes. `ready` carries
1856
+ * `url`, a presigned `GET` of the zip valid for fifteen minutes and signed with a
1857
+ * `Content-Disposition` naming the archive's `name`, with `archive_bytes` and `ready_at`; each read
1858
+ * signs a fresh link, so a caller that let one lapse reads the archive again. `failed` carries
1859
+ * `failure`, `file_gone` or `build_failed`, and the remedy for both is a new request. An archive
1860
+ * still `queued` or `building` an hour after it was asked for reads as `failed`.
1861
+ *
1862
+ * Throws `RequestArgumentError` for an archive id that is empty, `.` or `..`, or holds a lone
1863
+ * surrogate, before any request. Throws `ApiResponseError`: `404` `not_found` for an archive that
1864
+ * is not the organization's, never existed, or has expired, a day after `created_at`; `500` when
1865
+ * the link cannot be signed. An answer that is not an archive is an answer the SDK cannot read,
1866
+ * thrown as an `ApiResponseError` whose body hides the link.
1867
+ */
1868
+ getFileArchive(archiveId: string): Promise<FileArchive>;
1869
+ /**
1870
+ * An archive as `res` carried it, checked to be one: an object with a string `archive_id` and a
1871
+ * `state` this SDK knows, the field a caller branches on. Anything else is an answer the SDK
1872
+ * cannot read, thrown as the `ApiResponseError` `unreadableAnswer` builds with every link hidden,
1873
+ * since a ready archive's answer carries one.
1874
+ */
1875
+ private readFileArchive;
1876
+ /**
1877
+ * Copy one of the organization's files into a method's draft —
1878
+ * `POST /v1/methods/{id}/draft/files/from-file`, "Add to method files".
1879
+ *
1880
+ * The file becomes a file of the draft at `path`, replacing a file already there. Its bytes are
1881
+ * copied into the organization's blob store by the storage service itself, so they never cross
1882
+ * the client or the API whatever their size, and are skipped when the store already holds them.
1883
+ * The method file then lives apart from its source: erasing the file's record, the run that made
1884
+ * it or the method it came from leaves it readable.
1885
+ *
1886
+ * The answer is a `DraftFileFromFileResult`, a `200` discriminated on `blob`: `copied`, or
1887
+ * `already_held` when the store held the bytes and nothing was copied. Both carry the `file` as
1888
+ * the draft's tree now names it and the `method` as the draft write stored it, whose
1889
+ * `updated_at` is the new draft token. With `expected_updated_at`, the write is a
1890
+ * compare-and-swap on the draft token; without it, the file is added to the draft as it stands,
1891
+ * never erasing someone else's save.
1892
+ *
1893
+ * Takes a bare catalog id, as `writeDraft` does. Sends `file_id`, `path` and, when given,
1894
+ * `expected_updated_at`, and nothing else: the body is closed, and a `null` token reads as not
1895
+ * given. Throws `RequestArgumentError` before any request for a `file_id` or a `path` that is not
1896
+ * a non-empty string, and an `expected_updated_at` that is not a string. Throws
1897
+ * `ApiResponseError`: `403` `role_not_permitted` for a role that may not change methods; `404`
1898
+ * for an unknown method or a file that is not the organization's; `409` `method_being_deleted`,
1899
+ * `409` `method_update_conflict` for a stale `expected_updated_at`, and `409` `conflict` for a
1900
+ * file whose upload has not finished or whose stored bytes do not match its record; `422`
1901
+ * `invalid_method_files` for a path the draft cannot hold, such as `.env` or `../logo.png`, and
1902
+ * `422` `method_tree_too_large` past the deployment's limits. An answer whose `blob` is neither
1903
+ * arm's, or whose `file` or `method` is not an object, is an answer the SDK cannot read, thrown
1904
+ * as an `ApiResponseError` too.
1905
+ */
1906
+ copyFileIntoDraft(methodId: string, input: DraftFileFromFileInput): Promise<DraftFileFromFileResult>;
1598
1907
  /**
1599
1908
  * One PAGE of a method's runs, newest first — `GET /v1/runs?method_id=`.
1600
1909
  *
@@ -1673,6 +1982,29 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1673
1982
  * example's go back as the run used them.
1674
1983
  */
1675
1984
  getRunDetail(runId: string): Promise<RunDetail>;
1985
+ /**
1986
+ * One page of a run's files, sorted by where they sit in the run — `GET /v1/runs/{id}/files`.
1987
+ *
1988
+ * Each entry carries its `mark`, `input`, `final` or `intermediate`, and the record a surface
1989
+ * shows for it (`RunFileEntry`): `inputs` are what went in, the inputs given by their address
1990
+ * with the record of the member who started the run first, then the inline inputs; `outputs` are
1991
+ * the run's final outputs; `intermediates` the files it made along the way. An input given as a
1992
+ * web link gets no record and is listed in `links`, with the warning that replaying the run needs
1993
+ * it to still serve the same file.
1994
+ *
1995
+ * **`state` is `listing` until the platform has recorded every file of the run**, a few seconds
1996
+ * after it ends: read it again until it is `listed` before taking the list as whole. The run's
1997
+ * own records are paged by address with `limit` and `cursor`; the inputs by address and the links
1998
+ * come on the first page only. Pass `nextCursor` back as `cursor`; `null` is the last page.
1999
+ *
2000
+ * Throws `RequestArgumentError` for a run id that is empty, `.` or `..`, or holds a lone
2001
+ * surrogate, before any request. Throws `ApiResponseError`: `404` for a run that is not the
2002
+ * organization's, exactly as for one that does not exist; `400` for a run id holding a character
2003
+ * outside letters, digits, `_` and `-`, or a cursor another listing issued; `422` for a `limit`
2004
+ * outside 1 to 200. A page whose `state` is unknown, whose lists are not arrays, or whose `next_cursor` is neither a
2005
+ * string nor `null`, is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
2006
+ */
2007
+ listRunFiles(runId: string, query?: ListRunFilesQuery): Promise<RunFilesPage>;
1676
2008
  /** Patch a run's status (admin/manual) — `PUT /v1/runs/{id}`. */
1677
2009
  updateRun(runId: string, input: UpdateRunInput): Promise<void>;
1678
2010
  /**