@pipelex/sdk 0.62.0 → 0.64.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
@@ -178,10 +178,34 @@ const copy = await client.copyMethod(
178
178
  );
179
179
  if (copy.outcome === "copied") console.log(copy.method.copied_from); // the original it names
180
180
 
181
+ // Store a layout a producer designed for one pipe of a published version, over that version's
182
+ // tree; a write of the same tree, pipe, catalog and prompt hash replaces it. A validate by
183
+ // method_id naming `layouts` then carries the layouts beside the descriptor, newest first, and
184
+ // leaves `layouts` out when there are none: render the plain form then.
185
+ const v1 = await client.getMethodVersion(created.method_id, 1);
186
+ const layoutKey = crypto.randomUUID(); // one key per layout, sent again unchanged on each retry
187
+ await client.storeMethodLayout(created.method_id, 1, {
188
+ tree_digest: v1.tree_digest!,
189
+ pipe_ref: "greeting.greet",
190
+ catalog: "composer",
191
+ prompt_hash: "a1b2c3d4e5f6",
192
+ jsonl: '{"op":"add","path":"/root","value":"page"}',
193
+ provenance: { producer: "pipelex-method", model: "claude-4.8-opus", style: "editorial" },
194
+ }, { idempotencyKey: layoutKey });
195
+ const laidOut = await client.validate({ method_id: `${created.method_id}@1` }, false, {
196
+ views: ["input_form", "layouts"],
197
+ });
198
+ if (laidOut.is_valid) console.log(laidOut.layouts?.map((layout) => layout.catalog) ?? "plain form");
199
+
181
200
  // The template catalog: the public methods every organization reads, in the platform's order, with
182
- // the categories that have a template and each card's thumbnails behind short-lived links.
201
+ // the categories that have a template and each card's thumbnails behind short-lived links. A
202
+ // category's `labels` name it by language tag and may lack a language, in which case a client
203
+ // shows `label`, the English one.
183
204
  const templates = await client.listMethodTemplates({ category: "human-resources", limit: 8 });
184
- for (const tab of templates.categories) console.log(tab.slug, tab.label);
205
+ for (const tab of templates.categories) console.log(tab.slug, tab.labels.fr ?? tab.label);
206
+ // The catalog's whole list of categories, a category no template shows included.
207
+ const { categories } = await client.listMethodCategories();
208
+ console.log(categories.map((category) => category.slug));
185
209
  // Another organization's public method reads in a narrower shape, its latest published version
186
210
  // and no draft: `updated_at` is what tells it from one of your own.
187
211
  const first = templates.items[0];
@@ -297,7 +321,8 @@ These pages ship inside the published package, so a reader who has only installe
297
321
  | [`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 |
298
322
  | [`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 |
299
323
  | [`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, and `copied_from` |
300
- | [`docs/public-methods.md`](./docs/public-methods.md) | Public methods and the template catalog: `listMethodTemplates` and its iterator, the ordering, the categories and the thumbnails' links, another organization's public method read in its narrower shape, the copy of a public method, `visibility` and `categories` on a method, and the refusals a public method's publish adds |
324
+ | [`docs/method-layouts.md`](./docs/method-layouts.md) | A method's stored layouts: `storeMethodLayout`, which stores a layout designed for one pipe of a published version, its closed body and its refusals, the layout and its provenance, and the `layouts` a validate by `method_id` carries when `views` names `layouts`, absent when there are none |
325
+ | [`docs/public-methods.md`](./docs/public-methods.md) | Public methods and the template catalog: `listMethodTemplates` and its iterator, the ordering, the categories with their labels by language and `listMethodCategories`, the thumbnails' links, another organization's public method read in its narrower shape, the copy of a public method, `visibility` and `categories` on a method, and the refusals a public method's publish adds |
301
326
  | [`docs/organization-branding.md`](./docs/organization-branding.md) | An organization's logos, colours, app style and typeface, and its white-label entitlement: `getBranding`, which every member and API key reads, `updateOrganizationBranding`, a write by the `admin` or the `member` role that sets, clears or keeps each key, and how a refused logo, colour, style or typeface names its field so a settings screen shows the sentence under it |
302
327
  | [`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 |
303
328
  | [`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.62.0";
18
+ export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.64.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,8 @@
1
1
  import type { MTHDSProtocol, ModelCategory, ModelDeck, RunOptions, StartOptions, VersionInfo } from "mthds/protocol";
2
- import type { CodegenRequest, CodegenResponse, CrateRouteOptions, DictPipeOutput, FormatResponse, LintResponse, ModelCheckCategory, ModelReferenceVerdict, PipeIORequest, PipeIOResponse, PipelexRunResultStart, PipelexValidateSource, PipelexValidationResult, ResolveRequest, ResolveResponse, ValidateMethodSelector } from "./models.js";
2
+ import type { CodegenRequest, CodegenResponse, CrateRouteOptions, DictPipeOutput, FormatResponse, LintResponse, ModelCheckCategory, ModelReferenceVerdict, PipeIORequest, PipeIOResponse, PipelexRunResultStart, PipelexValidateSource, PipelexValidationResult, ResolveRequest, ResolveResponse, ValidateMethodSelector, ValidateView } from "./models.js";
3
+ import type { MethodLayout, MethodLayoutInput } from "./method-layouts.js";
3
4
  import { type GetRunResultOptions, type RunProgress, type RunRead, type RunResults, type RunResultState, type StartAndWaitForResultOptions, type WaitForResultOptions } from "./runs.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, ListMethodExamplesQuery, ListMethodsQuery, ListMethodTemplatesQuery, ListMethodVersionsQuery, ListRunFilesQuery, MethodCopyInput, MethodCopyResult, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleAssistantSaveInput, MethodExampleAssistantSaveResult, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodTemplate, MethodTemplatePage, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, PublicMethodDetail, PipelexApiKeyCreated, PipelexApiKeyList, ListRunsQuery, RunHistoryItem, RunDetail, RunFilesPage, RunPage, PlanView, ResolvedStorageUrl, SavedMethodFile, SubscriptionResponse, UpdateRunInput, UploadGrant, UploadGrantInput, UploadInput, UploadedFile, UserProfile } from "./product-models.js";
5
+ 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, ListMethodExamplesQuery, ListMethodsQuery, ListMethodTemplatesQuery, ListMethodVersionsQuery, ListRunFilesQuery, MethodCategoryList, MethodCopyInput, MethodCopyResult, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleAssistantSaveInput, MethodExampleAssistantSaveResult, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodTemplate, MethodTemplatePage, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, PublicMethodDetail, PipelexApiKeyCreated, PipelexApiKeyList, ListRunsQuery, RunHistoryItem, RunDetail, RunFilesPage, RunPage, PlanView, ResolvedStorageUrl, SavedMethodFile, SubscriptionResponse, UpdateRunInput, UploadGrant, UploadGrantInput, UploadInput, UploadedFile, UserProfile } from "./product-models.js";
5
6
  import type { MethodTreeForms, MethodTreeLimits } from "./method-trees.js";
6
7
  import type { AppInfo } from "./user-agent.js";
7
8
  import type { WebhookDeliveryDetail, WebhookDeliveryList, WebhookReplayAccepted, WebhookReplayOptions, WebhookSecret, WebhookSecretRotateOptions } from "./webhooks.js";
@@ -173,6 +174,32 @@ export interface MethodCopyRequestOptions {
173
174
  */
174
175
  idempotencyKey?: string | null;
175
176
  }
177
+ /** Options for `storeMethodLayout`. */
178
+ export interface MethodLayoutWriteOptions {
179
+ /**
180
+ * The write's idempotency key, sent as the `Idempotency-Key` header of
181
+ * `POST /v1/methods/{id}/versions/{n}/layouts`, and not sent at all when absent or `null`.
182
+ * Generate one per layout the caller means to store (a `crypto.randomUUID()` is a good key), and
183
+ * keep it, with the arguments unchanged, across that write's retries.
184
+ *
185
+ * A write sent again without a key runs again: it stamps the layout with a new `stored_at`, which
186
+ * moves it ahead of every layout stored since in the newest-first order, and applies the bound
187
+ * again, so a layout another producer stored meanwhile, a newer prompt of the same pipe and
188
+ * catalog included, can be the one removed. Under the same key the hosted API answers the retry
189
+ * with the first write's stored answer and runs nothing, so a write whose answer was lost, as on
190
+ * a client timeout, can be sent again unchanged. The write's `200` and its `4xx` refusals are
191
+ * stored alike; a `5xx` and a refusal for its timing (a `408`, `425` or `429`) are not, so the
192
+ * same key sends the write afresh. The same key with a different body is refused (`409`
193
+ * `request.idempotency_conflict`, whose `retry` is `no`), as is a key whose earlier request is
194
+ * still being handled (`409` `request.idempotency_key_in_use`, whose `retry` is `yes`); these
195
+ * two are the key's own answers and are never stored, so a retry after the second runs as the
196
+ * key's first request would.
197
+ *
198
+ * A key is held to the rule a start's is (`PipelexStartRequestOptions.idempotencyKey`): 1 to
199
+ * 255 visible ASCII characters, anything else a `RequestArgumentError` before any request.
200
+ */
201
+ idempotencyKey?: string | null;
202
+ }
176
203
  /**
177
204
  * `start()` / `startAndWaitForResult()` options — the same, for the durable path, plus the start
178
205
  * request's own `idempotencyKey`.
@@ -191,8 +218,13 @@ export interface ValidateOptions {
191
218
  * `"markdown"`, so both arms carry `rendered_markdown`.
192
219
  */
193
220
  render?: string[];
194
- /** Structured-view opt-in tokens, e.g. `["input_form", "output_form"]`; sent only when given. */
195
- views?: string[];
221
+ /**
222
+ * Structured-view opt-in tokens, e.g. `["input_form", "output_form"]`, or
223
+ * `["input_form", "layouts"]` for a stored method's descriptor and the layouts over it; sent
224
+ * verbatim, and only when given. A token this SDK does not know (`ValidateView`) is sent as its
225
+ * raw string.
226
+ */
227
+ views?: (ValidateView | (string & {}))[];
196
228
  /**
197
229
  * Per-call request ceiling; defaults to the 20-min execute ceiling. A positive number
198
230
  * no larger than 2147483647, the longest delay a timer honours, else a `RangeError`.
@@ -284,8 +316,8 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
284
316
  * `url` is a fully resolved absolute URL. `mayStartRun` marks a request that may create a run,
285
317
  * whose errors then say whether it may have (see `RunCreation`).
286
318
  * `idempotencyKey`, already checked by `idempotencyKeyOf`, is sent as the
287
- * `Idempotency-Key` header; only `start`, `copyMethod`, `rotateWebhookSecret` and
288
- * `replayRunWebhookDelivery` pass one.
319
+ * `Idempotency-Key` header; only `start`, `copyMethod`, `storeMethodLayout`,
320
+ * `rotateWebhookSecret` and `replayRunWebhookDelivery` pass one.
289
321
  */
290
322
  private requestRaw;
291
323
  /**
@@ -565,7 +597,10 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
565
597
  * `options.views` is the sibling opt-in for *structured* views where `render`
566
598
  * carries *rendered text*. The two lists are independent, each resolving its own
567
599
  * tokens against its own supported set, and each supported token adds a same-named
568
- * top-level field to the valid arm — `input_form` and `output_form`. Unlike
600
+ * top-level field to the valid arm — `input_form` and `output_form`, and on a hosted
601
+ * validate by `method_id`, `layouts`, the layouts stored for the resolved tree
602
+ * (`PipelexValidationReport.layouts`, absent when there are none), each entry read as
603
+ * `storeMethodLayout`'s answer is and one that does not read as a layout left out. Unlike
569
604
  * `render`, this client sends `views` ONLY when the caller asks: the point of an
570
605
  * opt-in view is that the default response stays byte-identical, and the
571
606
  * highest-frequency consumers (hook pipelines, CI gates, agent loops) never pay for
@@ -996,8 +1031,8 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
996
1031
  * unknown or malformed name serves `default`, and the page's `ordering` names the one served,
997
1032
  * `null` when there is none. `category` keeps one category's templates, by slug, in the same
998
1033
  * order; an unknown slug keeps none. Whatever the filter, the page's `categories` names, with its
999
- * label, every category at least one template shows, in the catalog's order, so no tab drawn
1000
- * from it is ever empty.
1034
+ * labels, every category at least one template shows, in the catalog's order, so no tab drawn
1035
+ * from it is ever empty; `listMethodCategories` reads the whole list.
1001
1036
  *
1002
1037
  * Each `MethodTemplate` carries the method's id and name, its latest published version's number,
1003
1038
  * description and categories, its organization's display name and each thumbnail with its size
@@ -1007,17 +1042,18 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1007
1042
  * Pass the page's `nextCursor` back as `cursor` with the same `ordering` and `category`, which the
1008
1043
  * cursor does not hold; `null` is the last page. `iterateMethodTemplates` follows it for you.
1009
1044
  *
1010
- * Throws `ApiResponseError`: `422` `request.invalid` for a `limit` outside 1 to 200 or a cursor of
1011
- * another listing, `422` `request.malformed` for a cursor that does not decode, `500`
1012
- * `runtime.misconfigured` when the templates organization the deployment names has no record or
1013
- * a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A page whose `items`
1014
- * or `categories` is not an array, whose `next_cursor` or `ordering` is neither a string nor
1015
- * `null`, a template lacking a member a card is drawn from (a string `method_id`, `name` and
1016
- * `organization_name`, a `description` that is a string or `null`, a positive integer
1017
- * `latest_version`, an array of string `categories`, and `thumbnails` each with a string `path`,
1018
- * `media_type`, `sha256`, `url` and `expires_at` and a positive integer `width` and `height`), and a
1019
- * category without a string `slug` and `label` are an answer the SDK cannot read, thrown as an
1020
- * `ApiResponseError` too, its links redacted.
1045
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` without an active organization, `422`
1046
+ * `request.invalid` for a `limit` outside 1 to 200 or a cursor of another listing, `422`
1047
+ * `request.malformed` for a cursor that does not decode, `500` `runtime.misconfigured` when the
1048
+ * templates organization the deployment names has no record or a link cannot be signed, and
1049
+ * `503` `runtime.unavailable`, safe to retry. A page whose `items` or `categories` is not an
1050
+ * array, whose `next_cursor` or `ordering` is neither a string nor `null`, a template lacking a
1051
+ * member a card is drawn from (a string `method_id`, `name` and `organization_name`, a
1052
+ * `description` that is a string or `null`, a positive integer `latest_version`, an array of
1053
+ * string `categories`, and `thumbnails` each with a string `path`, `media_type`, `sha256`, `url`
1054
+ * and `expires_at` and a positive integer `width` and `height`), and a category without a string
1055
+ * `slug` and `label` or whose `labels` is not an object of strings are an answer the SDK cannot
1056
+ * read, thrown as an `ApiResponseError` too, its links redacted.
1021
1057
  */
1022
1058
  listMethodTemplates(query?: ListMethodTemplatesQuery): Promise<MethodTemplatePage>;
1023
1059
  /**
@@ -1028,6 +1064,23 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1028
1064
  * read one page with `listMethodTemplates` when you need them.
1029
1065
  */
1030
1066
  iterateMethodTemplates(query?: Omit<ListMethodTemplatesQuery, "cursor">): AsyncGenerator<MethodTemplate, void, undefined>;
1067
+ /**
1068
+ * The catalog's whole list of categories — `GET /v1/method-categories`: every category an admin
1069
+ * of the platform put on it, in the catalog's order, whether or not a template shows it, the same
1070
+ * for every caller. Each `MethodCategory` carries its `slug`, the one a manifest declares, its
1071
+ * English `label`, and its `labels` by language tag, which may lack a language the platform
1072
+ * added after the category was stored; show `labels[locale] ?? label`.
1073
+ *
1074
+ * A template shows under the categories its manifest declares that are on this list, so this is
1075
+ * the list a method's categories are picked from. `listMethodTemplates` names, on every page, only
1076
+ * the categories some template shows, for the tabs a client draws.
1077
+ *
1078
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` without an active organization, and
1079
+ * `503` `runtime.unavailable`, safe to retry. An answer whose `categories` is not an array, or
1080
+ * holds a category without a string `slug` and `label` or whose `labels` is not an object of
1081
+ * strings, is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
1082
+ */
1083
+ listMethodCategories(): Promise<MethodCategoryList>;
1031
1084
  /**
1032
1085
  * Read one method with its files — `GET /v1/methods/{id}`. One of the caller's organization's
1033
1086
  * methods answers a `MethodDetail`: its identity, its draft
@@ -1354,6 +1407,51 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
1354
1407
  * one, which names no tree to serve.
1355
1408
  */
1356
1409
  getMethodVersion(methodId: string, version: number): Promise<MethodVersion>;
1410
+ /**
1411
+ * Store a layout designed for one pipe of a published version of a method —
1412
+ * `POST /v1/methods/{id}/versions/{n}/layouts` — and answer it as stored, with the `stored_at` the
1413
+ * platform stamped.
1414
+ *
1415
+ * A layout is a data file a producer designed over the version's input-form descriptor, in the
1416
+ * vocabulary of a catalog of components. The platform stores it beside the method, keyed by the
1417
+ * content it was designed for, and hands it back on every validate of the method by `method_id`
1418
+ * that names `layouts` among its `views` and resolves the same tree
1419
+ * (`PipelexValidationReport.layouts`). It never produces, parses or judges one.
1420
+ *
1421
+ * `layout.tree_digest` must be the digest of the tree `version` holds, its
1422
+ * `MethodVersionSummary.tree_digest`: a producer designs over the descriptor of the version it was
1423
+ * handed. **A write of the same identity replaces the layout**, the identity being `tree_digest`,
1424
+ * `pipe_ref`, `catalog` and `prompt_hash`, and is answered the same `200` whether it stored a new
1425
+ * layout or replaced one. `options.idempotencyKey` is sent as the `Idempotency-Key` header, so a
1426
+ * write whose answer was lost is sent again under the same key and answered as the first was; sent
1427
+ * again without one, it runs again, with a new `stored_at` and the bound applied again
1428
+ * (`MethodLayoutWriteOptions`). A content keeps at most 16
1429
+ * layouts, every pipe, catalog and prompt hash together; past the bound a write removes first what
1430
+ * newer prompts of the same pipe and catalog superseded (see `docs/method-layouts.md`).
1431
+ *
1432
+ * The write changes the method, so it takes an `admin` or a `member` of the method's
1433
+ * organization. Takes a bare catalog id, as `getMethod` does, and the version number. Sends the
1434
+ * members of `MethodLayoutInput` and nothing else, the route's body being closed at both levels,
1435
+ * and leaves out each optional provenance member that is `null` or omitted, for the platform's
1436
+ * default. Throws `RequestArgumentError` before any request for a `version` that is not a positive
1437
+ * integer and for a `layout` or a `provenance` that is not an object; every other member is sent
1438
+ * as given. Throws `ApiResponseError` when the platform refuses the write, which stores nothing:
1439
+ * `422` `request.invalid` for a member breaking its shape or bound, naming it in `params.field`
1440
+ * and `location.field` (`provenance.<member>` for one of the provenance's), and, naming
1441
+ * `tree_digest`, for a digest that is not the tree the version holds; `403` `auth.forbidden`
1442
+ * naming the `member` requirement for a `runner` or a caller with no recognised role; `404`
1443
+ * `pipelex.catalog.not_found` for an unknown method or another organization's, its
1444
+ * `params.resource` `method`, and for a version the method never published, its
1445
+ * `params.resource` `version`; `409` `pipelex.catalog.being_deleted` while the method's erasure is
1446
+ * pending, under way or stopped; and `500` `runtime.unexpected` for a version stored before
1447
+ * methods were trees, which names no tree. Under an idempotency key, `409`
1448
+ * `request.idempotency_conflict` for the key sent with a different body and `409`
1449
+ * `request.idempotency_key_in_use` while its earlier request is still being handled; a key that is
1450
+ * not 1 to 255 visible ASCII characters is a `RequestArgumentError` before any request. An answer
1451
+ * that does not read as a layout (`methodLayoutFault`) is an answer the SDK cannot read, thrown as
1452
+ * an `ApiResponseError` too.
1453
+ */
1454
+ storeMethodLayout(methodId: string, version: number, layout: MethodLayoutInput, options?: MethodLayoutWriteOptions): Promise<MethodLayout>;
1357
1455
  /**
1358
1456
  * Copy a method into an organization the caller may change methods in —
1359
1457
  * `POST /v1/methods/{id}/copy`: the method's own organization for a duplicate, another team, or
package/dist/client.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // source combinations are legal is an invariant of `RunRequest` itself, so this
3
3
  // client and the MTHDS runners cannot disagree about what they reject.
4
4
  import { assertOneMethodSource, buildProblem, isProblem, parseJsonBody, readProblemBody, } from "mthds/protocol";
5
+ import { LAYOUT_INPUT_MEMBERS, PROVENANCE_INPUT_MEMBERS, methodLayoutFault, readMethodLayout, readMethodLayouts, } from "./method-layouts.js";
5
6
  import { assertArtifactSelection, assertWaitOptions, pollUntilResult, readRunDegradation, RUN_STATUSES, runProgressFault, runStatusFilter, selectionIncludesMainStuff, throwIfAborted, withCheckedProgressHealth, } from "./runs.js";
6
7
  import { ABORT_TIMEOUT, ApiResponseError, argumentInvalidProblem, ApiUnreachableError, MissingMainStuffError, PagingNotTerminatingError, PipelineExecuteTimeoutError, ProblemError, RequestArgumentError, RunLifecycleUnavailableError, RunStillRunningError, } from "./errors.js";
7
8
  import { isPlainObject } from "./records.js";
@@ -326,6 +327,19 @@ const COPY_DRAFT_TOKEN_ARGUMENT = {
326
327
  what: "an expected_draft_updated_at, the draft token the caller last read, the method's " +
327
328
  "`updated_at`, a string, or none",
328
329
  };
330
+ /** What each argument of a layout write is, as a refusal of it names it. */
331
+ const LAYOUT_VERSION_ARGUMENT = {
332
+ name: "version",
333
+ what: "a version number, the published version the layout was designed for, a positive integer",
334
+ };
335
+ const LAYOUT_ARGUMENT = {
336
+ name: "layout",
337
+ what: "the layout to store as an object, a MethodLayoutInput",
338
+ };
339
+ const LAYOUT_PROVENANCE_ARGUMENT = {
340
+ name: "provenance",
341
+ what: "the layout's provenance as an object naming at least its producer and its model",
342
+ };
329
343
  /**
330
344
  * The refusal of an argument a call cannot take, worded as every such refusal is:
331
345
  * `<call>() takes <what>; got <got>.`, with what made it unusable as `cause` when there is one.
@@ -374,12 +388,16 @@ function requireNameAndInputs(name, inputs, call, needs) {
374
388
  }
375
389
  /**
376
390
  * A caller's value as the refusal of a number argument names it: a number by its value, anything
377
- * else by its type, as `requireNonEmptyId` names a value, and `null` as itself. Never by `String`,
378
- * which throws for an object with no prototype and would replace the refusal with a `TypeError`.
391
+ * else by its type, as `requireNonEmptyId` names a value, `null` as itself and an array as one, so
392
+ * a refusal of a value that is not an object never answers an array with "got object". Never by
393
+ * `String`, which throws for an object with no prototype and would replace the refusal with a
394
+ * `TypeError`.
379
395
  */
380
396
  function describeNumberArgument(value) {
381
397
  if (typeof value === "number")
382
398
  return String(value);
399
+ if (Array.isArray(value))
400
+ return "an array";
383
401
  return value === null ? "null" : typeof value;
384
402
  }
385
403
  /**
@@ -582,8 +600,8 @@ export class PipelexApiClient {
582
600
  * `url` is a fully resolved absolute URL. `mayStartRun` marks a request that may create a run,
583
601
  * whose errors then say whether it may have (see `RunCreation`).
584
602
  * `idempotencyKey`, already checked by `idempotencyKeyOf`, is sent as the
585
- * `Idempotency-Key` header; only `start`, `copyMethod`, `rotateWebhookSecret` and
586
- * `replayRunWebhookDelivery` pass one.
603
+ * `Idempotency-Key` header; only `start`, `copyMethod`, `storeMethodLayout`,
604
+ * `rotateWebhookSecret` and `replayRunWebhookDelivery` pass one.
587
605
  */
588
606
  async requestRaw(method, url, options = {}) {
589
607
  const hasBody = options.body !== undefined;
@@ -1181,7 +1199,10 @@ export class PipelexApiClient {
1181
1199
  * `options.views` is the sibling opt-in for *structured* views where `render`
1182
1200
  * carries *rendered text*. The two lists are independent, each resolving its own
1183
1201
  * tokens against its own supported set, and each supported token adds a same-named
1184
- * top-level field to the valid arm — `input_form` and `output_form`. Unlike
1202
+ * top-level field to the valid arm — `input_form` and `output_form`, and on a hosted
1203
+ * validate by `method_id`, `layouts`, the layouts stored for the resolved tree
1204
+ * (`PipelexValidationReport.layouts`, absent when there are none), each entry read as
1205
+ * `storeMethodLayout`'s answer is and one that does not read as a layout left out. Unlike
1185
1206
  * `render`, this client sends `views` ONLY when the caller asks: the point of an
1186
1207
  * opt-in view is that the default response stays byte-identical, and the
1187
1208
  * highest-frequency consumers (hook pipelines, CI gates, agent loops) never pay for
@@ -1239,7 +1260,7 @@ export class PipelexApiClient {
1239
1260
  if (res.status < 200 || res.status >= 300) {
1240
1261
  this.throwApiResponseError("POST", "validate", res);
1241
1262
  }
1242
- return withVerdictItems(this.readObjectAnswer("POST", "validate", res));
1263
+ return withReadLayouts(withVerdictItems(this.readObjectAnswer("POST", "validate", res)));
1243
1264
  }
1244
1265
  // ── Tools extensions (Pipelex API — `/v1/lint`, `/v1/format`) ─────────
1245
1266
  //
@@ -1943,8 +1964,8 @@ export class PipelexApiClient {
1943
1964
  * unknown or malformed name serves `default`, and the page's `ordering` names the one served,
1944
1965
  * `null` when there is none. `category` keeps one category's templates, by slug, in the same
1945
1966
  * order; an unknown slug keeps none. Whatever the filter, the page's `categories` names, with its
1946
- * label, every category at least one template shows, in the catalog's order, so no tab drawn
1947
- * from it is ever empty.
1967
+ * labels, every category at least one template shows, in the catalog's order, so no tab drawn
1968
+ * from it is ever empty; `listMethodCategories` reads the whole list.
1948
1969
  *
1949
1970
  * Each `MethodTemplate` carries the method's id and name, its latest published version's number,
1950
1971
  * description and categories, its organization's display name and each thumbnail with its size
@@ -1954,17 +1975,18 @@ export class PipelexApiClient {
1954
1975
  * Pass the page's `nextCursor` back as `cursor` with the same `ordering` and `category`, which the
1955
1976
  * cursor does not hold; `null` is the last page. `iterateMethodTemplates` follows it for you.
1956
1977
  *
1957
- * Throws `ApiResponseError`: `422` `request.invalid` for a `limit` outside 1 to 200 or a cursor of
1958
- * another listing, `422` `request.malformed` for a cursor that does not decode, `500`
1959
- * `runtime.misconfigured` when the templates organization the deployment names has no record or
1960
- * a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A page whose `items`
1961
- * or `categories` is not an array, whose `next_cursor` or `ordering` is neither a string nor
1962
- * `null`, a template lacking a member a card is drawn from (a string `method_id`, `name` and
1963
- * `organization_name`, a `description` that is a string or `null`, a positive integer
1964
- * `latest_version`, an array of string `categories`, and `thumbnails` each with a string `path`,
1965
- * `media_type`, `sha256`, `url` and `expires_at` and a positive integer `width` and `height`), and a
1966
- * category without a string `slug` and `label` are an answer the SDK cannot read, thrown as an
1967
- * `ApiResponseError` too, its links redacted.
1978
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` without an active organization, `422`
1979
+ * `request.invalid` for a `limit` outside 1 to 200 or a cursor of another listing, `422`
1980
+ * `request.malformed` for a cursor that does not decode, `500` `runtime.misconfigured` when the
1981
+ * templates organization the deployment names has no record or a link cannot be signed, and
1982
+ * `503` `runtime.unavailable`, safe to retry. A page whose `items` or `categories` is not an
1983
+ * array, whose `next_cursor` or `ordering` is neither a string nor `null`, a template lacking a
1984
+ * member a card is drawn from (a string `method_id`, `name` and `organization_name`, a
1985
+ * `description` that is a string or `null`, a positive integer `latest_version`, an array of
1986
+ * string `categories`, and `thumbnails` each with a string `path`, `media_type`, `sha256`, `url`
1987
+ * and `expires_at` and a positive integer `width` and `height`), and a category without a string
1988
+ * `slug` and `label` or whose `labels` is not an object of strings are an answer the SDK cannot
1989
+ * read, thrown as an `ApiResponseError` too, its links redacted.
1968
1990
  */
1969
1991
  async listMethodTemplates(query = {}) {
1970
1992
  const search = productQuery({
@@ -2010,6 +2032,35 @@ export class PipelexApiClient {
2010
2032
  async *iterateMethodTemplates(query = {}) {
2011
2033
  yield* followCursor("listMethodTemplates", (cursor) => this.listMethodTemplates({ ...query, cursor }));
2012
2034
  }
2035
+ /**
2036
+ * The catalog's whole list of categories — `GET /v1/method-categories`: every category an admin
2037
+ * of the platform put on it, in the catalog's order, whether or not a template shows it, the same
2038
+ * for every caller. Each `MethodCategory` carries its `slug`, the one a manifest declares, its
2039
+ * English `label`, and its `labels` by language tag, which may lack a language the platform
2040
+ * added after the category was stored; show `labels[locale] ?? label`.
2041
+ *
2042
+ * A template shows under the categories its manifest declares that are on this list, so this is
2043
+ * the list a method's categories are picked from. `listMethodTemplates` names, on every page, only
2044
+ * the categories some template shows, for the tabs a client draws.
2045
+ *
2046
+ * Throws `ApiResponseError`: `403` `pipelex.org.required` without an active organization, and
2047
+ * `503` `runtime.unavailable`, safe to retry. An answer whose `categories` is not an array, or
2048
+ * holds a category without a string `slug` and `label` or whose `labels` is not an object of
2049
+ * strings, is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
2050
+ */
2051
+ async listMethodCategories() {
2052
+ const endpoint = "method-categories";
2053
+ const res = await this.requestProductAnswer("GET", endpoint, undefined);
2054
+ const list = this.readObjectAnswer("GET", endpoint, res);
2055
+ const { categories } = list;
2056
+ const fault = Array.isArray(categories)
2057
+ ? methodCategoriesFault(categories)
2058
+ : "`categories` is not an array";
2059
+ if (fault !== null) {
2060
+ throw this.unreadableAnswer("GET", `/${API_PREFIX}/${endpoint}`, res, `a list of categories whose ${fault}`);
2061
+ }
2062
+ return list;
2063
+ }
2013
2064
  /**
2014
2065
  * Read one method with its files — `GET /v1/methods/{id}`. One of the caller's organization's
2015
2066
  * methods answers a `MethodDetail`: its identity, its draft
@@ -2605,6 +2656,66 @@ export class PipelexApiClient {
2605
2656
  const res = await this.requestProductAnswer("GET", endpoint, undefined);
2606
2657
  return this.readSavedTree("GET", endpoint, withLinks(res), "a published version");
2607
2658
  }
2659
+ /**
2660
+ * Store a layout designed for one pipe of a published version of a method —
2661
+ * `POST /v1/methods/{id}/versions/{n}/layouts` — and answer it as stored, with the `stored_at` the
2662
+ * platform stamped.
2663
+ *
2664
+ * A layout is a data file a producer designed over the version's input-form descriptor, in the
2665
+ * vocabulary of a catalog of components. The platform stores it beside the method, keyed by the
2666
+ * content it was designed for, and hands it back on every validate of the method by `method_id`
2667
+ * that names `layouts` among its `views` and resolves the same tree
2668
+ * (`PipelexValidationReport.layouts`). It never produces, parses or judges one.
2669
+ *
2670
+ * `layout.tree_digest` must be the digest of the tree `version` holds, its
2671
+ * `MethodVersionSummary.tree_digest`: a producer designs over the descriptor of the version it was
2672
+ * handed. **A write of the same identity replaces the layout**, the identity being `tree_digest`,
2673
+ * `pipe_ref`, `catalog` and `prompt_hash`, and is answered the same `200` whether it stored a new
2674
+ * layout or replaced one. `options.idempotencyKey` is sent as the `Idempotency-Key` header, so a
2675
+ * write whose answer was lost is sent again under the same key and answered as the first was; sent
2676
+ * again without one, it runs again, with a new `stored_at` and the bound applied again
2677
+ * (`MethodLayoutWriteOptions`). A content keeps at most 16
2678
+ * layouts, every pipe, catalog and prompt hash together; past the bound a write removes first what
2679
+ * newer prompts of the same pipe and catalog superseded (see `docs/method-layouts.md`).
2680
+ *
2681
+ * The write changes the method, so it takes an `admin` or a `member` of the method's
2682
+ * organization. Takes a bare catalog id, as `getMethod` does, and the version number. Sends the
2683
+ * members of `MethodLayoutInput` and nothing else, the route's body being closed at both levels,
2684
+ * and leaves out each optional provenance member that is `null` or omitted, for the platform's
2685
+ * default. Throws `RequestArgumentError` before any request for a `version` that is not a positive
2686
+ * integer and for a `layout` or a `provenance` that is not an object; every other member is sent
2687
+ * as given. Throws `ApiResponseError` when the platform refuses the write, which stores nothing:
2688
+ * `422` `request.invalid` for a member breaking its shape or bound, naming it in `params.field`
2689
+ * and `location.field` (`provenance.<member>` for one of the provenance's), and, naming
2690
+ * `tree_digest`, for a digest that is not the tree the version holds; `403` `auth.forbidden`
2691
+ * naming the `member` requirement for a `runner` or a caller with no recognised role; `404`
2692
+ * `pipelex.catalog.not_found` for an unknown method or another organization's, its
2693
+ * `params.resource` `method`, and for a version the method never published, its
2694
+ * `params.resource` `version`; `409` `pipelex.catalog.being_deleted` while the method's erasure is
2695
+ * pending, under way or stopped; and `500` `runtime.unexpected` for a version stored before
2696
+ * methods were trees, which names no tree. Under an idempotency key, `409`
2697
+ * `request.idempotency_conflict` for the key sent with a different body and `409`
2698
+ * `request.idempotency_key_in_use` while its earlier request is still being handled; a key that is
2699
+ * not 1 to 255 visible ASCII characters is a `RequestArgumentError` before any request. An answer
2700
+ * that does not read as a layout (`methodLayoutFault`) is an answer the SDK cannot read, thrown as
2701
+ * an `ApiResponseError` too.
2702
+ */
2703
+ async storeMethodLayout(methodId, version, layout, options = {}) {
2704
+ const call = "storeMethodLayout";
2705
+ if (!Number.isSafeInteger(version) || version < 1) {
2706
+ throw argumentRefusal(call, LAYOUT_VERSION_ARGUMENT, describeNumberArgument(version), "not_a_positive_integer");
2707
+ }
2708
+ const endpoint = `${methodPath(methodId, call)}/versions/${version}/layouts`;
2709
+ const body = methodLayoutBody(layout, call);
2710
+ const idempotencyKey = idempotencyKeyOf(options, "store the layout");
2711
+ const res = await this.requestProductAnswer("POST", endpoint, body, { idempotencyKey });
2712
+ const answer = this.readObjectAnswer("POST", endpoint, res);
2713
+ const stored = readMethodLayout(answer);
2714
+ if (stored === null) {
2715
+ throw this.unreadableAnswer("POST", `/${API_PREFIX}/${endpoint}`, res, `a layout ${methodLayoutFault(answer) ?? "that is not one"}`);
2716
+ }
2717
+ return stored;
2718
+ }
2608
2719
  /**
2609
2720
  * Copy a method into an organization the caller may change methods in —
2610
2721
  * `POST /v1/methods/{id}/copy`: the method's own organization for a duplicate, another team, or
@@ -4372,13 +4483,14 @@ const MAX_IDEMPOTENCY_KEY_LENGTH = 255;
4372
4483
  */
4373
4484
  const VISIBLE_ASCII = /^[\x21-\x7E]+$/;
4374
4485
  /**
4375
- * The idempotency key a start, a method copy, a secret rotation or a delivery replay sends, or
4486
+ * The idempotency key a start, a method copy, a layout write, a secret rotation or a delivery
4487
+ * replay sends, or
4376
4488
  * `undefined` when the options carry none (absent or `null`). A key that is not a string of 1 to
4377
4489
  * 255 visible ASCII characters is refused with a `RequestArgumentError` rather than dropped, an
4378
4490
  * empty one included, since a request sent without the key its caller gave would lose the
4379
4491
  * protection the caller asked for without a word. `request` says what the request does, as the
4380
- * refusal's last sentence names it ("start the run", "copy the method", "rotate the secret",
4381
- * "replay the delivery"). The message never quotes the key, which a caller may derive from
4492
+ * refusal's last sentence names it ("start the run", "copy the method", "store the layout",
4493
+ * "rotate the secret", "replay the delivery"). The message never quotes the key, which a caller may derive from
4382
4494
  * something private. The Python SDK holds a key to the same rule, in the same words.
4383
4495
  */
4384
4496
  function idempotencyKeyOf(options, request = "start the run") {
@@ -4604,7 +4716,10 @@ function methodTemplatesFault(items) {
4604
4716
  }
4605
4717
  return null;
4606
4718
  }
4607
- /** Why the catalog's categories are not ones the SDK reads, or `null`: each a string `slug` and `label`. */
4719
+ /**
4720
+ * Why the catalog's categories are not ones the SDK reads, or `null`: each a string `slug` and
4721
+ * `label`, and `labels`, an object whose every value is a string.
4722
+ */
4608
4723
  function methodCategoriesFault(categories) {
4609
4724
  for (let index = 0; index < categories.length; index += 1) {
4610
4725
  const category = categories[index];
@@ -4613,6 +4728,11 @@ function methodCategoriesFault(categories) {
4613
4728
  typeof category.label !== "string") {
4614
4729
  return `\`categories\` holds an entry without a string \`slug\` and \`label\`, at [${index}]`;
4615
4730
  }
4731
+ const labels = category.labels;
4732
+ if (!isPlainObject(labels) ||
4733
+ !Object.values(labels).every((label) => typeof label === "string")) {
4734
+ return `\`categories\` holds an entry whose \`labels\` is not an object of strings, at [${index}]`;
4735
+ }
4616
4736
  }
4617
4737
  return null;
4618
4738
  }
@@ -4733,6 +4853,52 @@ function withVerdictItems(answer) {
4733
4853
  read.warnings = readProblemItems(answer.warnings);
4734
4854
  return read;
4735
4855
  }
4856
+ /**
4857
+ * A valid verdict with its `layouts` read entry by entry (`readMethodLayouts`), only when the
4858
+ * answer carries the key: each entry that reads as a layout is kept, in the answer's order, and a
4859
+ * list left with none is removed, so `layouts` is either absent or holds at least one layout, as
4860
+ * the platform sends it. Any other answer is returned as it came.
4861
+ */
4862
+ function withReadLayouts(answer) {
4863
+ if (!isPlainObject(answer) || answer.is_valid !== true || !Object.hasOwn(answer, "layouts")) {
4864
+ return answer;
4865
+ }
4866
+ const read = { ...answer };
4867
+ const layouts = readMethodLayouts(answer.layouts);
4868
+ if (layouts.length === 0)
4869
+ delete read.layouts;
4870
+ else
4871
+ read.layouts = layouts;
4872
+ return read;
4873
+ }
4874
+ /**
4875
+ * The body `storeMethodLayout` sends for `layout`: the members of `MethodLayoutInput` and nothing
4876
+ * else, since the route's body is closed at both levels, each provenance member that is `null` or
4877
+ * omitted left out for the platform's default. The members are `LAYOUT_INPUT_MEMBERS` and
4878
+ * `PROVENANCE_INPUT_MEMBERS`, which the build holds to the interfaces. A layout or a provenance that is not an object is
4879
+ * refused with `RequestArgumentError` before anything is sent; every other member is sent as given,
4880
+ * for the platform to hold to its shapes, its `422` naming the member it refuses.
4881
+ */
4882
+ function methodLayoutBody(layout, call) {
4883
+ if (!isPlainObject(layout)) {
4884
+ throw argumentRefusal(call, LAYOUT_ARGUMENT, describeNumberArgument(layout), "not_an_object");
4885
+ }
4886
+ const provenance = layout.provenance;
4887
+ if (!isPlainObject(provenance)) {
4888
+ throw argumentRefusal(call, LAYOUT_PROVENANCE_ARGUMENT, describeNumberArgument(provenance), "not_an_object");
4889
+ }
4890
+ const sentProvenance = {};
4891
+ for (const [member, presence] of Object.entries(PROVENANCE_INPUT_MEMBERS)) {
4892
+ const value = provenance[member];
4893
+ if (presence === "required" || value != null)
4894
+ sentProvenance[member] = value;
4895
+ }
4896
+ const body = {};
4897
+ for (const member of Object.keys(LAYOUT_INPUT_MEMBERS))
4898
+ body[member] = layout[member];
4899
+ body.provenance = sentProvenance;
4900
+ return body;
4901
+ }
4736
4902
  /**
4737
4903
  * A run record with its `error` kept only when it is a problem document, and read as `null`
4738
4904
  * otherwise. Only when the record carries the `error` key, so a server that does not serve it