@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.
@@ -0,0 +1,133 @@
1
+ # A method's stored layouts
2
+
3
+ A **layout** is a data file a producer designed for one pipe of a method: a tree of elements written in the vocabulary of a catalog of components, every value bound to a path in the host's form state, carrying no field descriptions and no data of its own. A host renders it over the input-form descriptor the same method's validate projects, and renders the plain form instead whenever it cannot. The hosted platform stores layouts beside a method's published versions and hands them back on the validate a host already makes, so a method laid out once opens with its layout for everyone who opens it. It never produces a layout, never parses one and never judges one against a catalog or a descriptor: that is the producer's work before the write and the host's before it renders.
4
+
5
+ The client reaches the write on the hosted API's `POST /v1/methods/{method_id}/versions/{version}/layouts` and the read on `POST /v1/validate`, under the `layouts` view. The types are in `src/method-layouts.ts`, exported from the package's root: `MethodLayoutInput` and `LayoutProvenanceInput` for the write, `MethodLayout` and `LayoutProvenance` for what is stored. The storage and the `layouts` token are a Pipelex hosted feature; the shapes are stated brand-neutrally, snake_case with no `pipelex_` prefix, as the input-form descriptor they are rendered over is. The conformance spec `pipelex-hosted-method-layouts.md`, in the workspace's `conformance` repository, states the contract.
6
+
7
+ ```ts
8
+ import { PipelexApiClient } from "@pipelex/sdk";
9
+ import type { MethodLayout } from "@pipelex/sdk";
10
+
11
+ const client = new PipelexApiClient();
12
+
13
+ // A producer has designed a layout over version 3's descriptor: store it beside that version.
14
+ const version = await client.getMethodVersion("mt_abc123", 3);
15
+ // The producer model's JSON patch lines, carried as emitted.
16
+ const jsonl = '{"op":"add","path":"/root","value":"page"}';
17
+ // One key per layout to store, made once and sent again unchanged on each retry of this write.
18
+ const layoutKey = crypto.randomUUID();
19
+ await client.storeMethodLayout(
20
+ "mt_abc123",
21
+ 3,
22
+ {
23
+ tree_digest: version.tree_digest!, // the tree the layout was designed over
24
+ pipe_ref: "receipts.review_receipt",
25
+ catalog: "composer",
26
+ prompt_hash: "a1b2c3d4e5f6",
27
+ jsonl,
28
+ provenance: { producer: "pipelex-method", model: "claude-4.8-opus", style: "editorial" },
29
+ },
30
+ { idempotencyKey: layoutKey },
31
+ );
32
+
33
+ // A host reads the descriptor and the layouts over it in one call.
34
+ const report = await client.validate({ method_id: "mt_abc123@3" }, false, {
35
+ views: ["input_form", "layouts"],
36
+ });
37
+ if (report.is_valid) {
38
+ const layout: MethodLayout | undefined = report.layouts?.find(
39
+ (candidate) =>
40
+ candidate.pipe_ref === "receipts.review_receipt" && candidate.catalog === "composer",
41
+ );
42
+ // No layout, or none the host can render: the plain form over `report.input_form`.
43
+ }
44
+ ```
45
+
46
+ ## The layout
47
+
48
+ | Member | Meaning |
49
+ |---|---|
50
+ | `tree_digest` | The digest of the method tree the layout was produced against, the MTHDS standard's `sha256:` digest of the tree's listing: `sha256:` and 64 lowercase hexadecimal characters. |
51
+ | `pipe_ref` | The namespaced pipe reference it lays out, `domain_path.pipe_code`, as the input-form descriptor keys its forms. |
52
+ | `catalog` | The id of the catalog whose vocabulary it is written in, a slug such as `composer`. |
53
+ | `prompt_hash` | The hash of the prompt the producer's model read, 12 to 64 lowercase hexadecimal digits, so a host recognises a layout designed under an older recipe. |
54
+ | `jsonl` | The layout itself, as the producer's model emitted it: JSON patch lines, one per line, carried as one string exactly as written. |
55
+ | `provenance` | How it was made: `producer`, `model`, `seed`, `style` and `repair_rounds` (below). |
56
+ | `stored_at` | The moment the write landed, ISO 8601 in UTC. The one member the platform sets, absent from the write. |
57
+
58
+ | `provenance` member | Meaning |
59
+ |---|---|
60
+ | `producer` | Who produced the layout, a slug of the same shape as `catalog`, such as `pipelex-method`. |
61
+ | `model` | The id of the model the producer ran, such as `claude-4.8-opus`. |
62
+ | `seed` | The creative seed handed to the model with the brief, verbatim, or `null` when none was. |
63
+ | `style` | The id of the app style the layout was designed under, such as `editorial`, or `null` when it was designed under none. The platform holds it to the shape `^[a-z0-9-]{1,32}$` and never to a list of styles, so a host reads an id it does not know as no style. |
64
+ | `repair_rounds` | How many repair rounds the producer ran before the layout validated against its catalog. |
65
+
66
+ **A layout belongs to the content it was designed for, never to a version number.** Its identity is `tree_digest`, `pipe_ref`, `catalog` and `prompt_hash` within one method, and it names no method and no version. It is written against a published version, and served for whatever selector of that method resolves the same tree: the version it was written against, a later version that holds the same tree, or the draft while it holds it. The same tree saved as another method carries none of it, and a copy of a method takes no layout.
67
+
68
+ ## Storing a layout
69
+
70
+ | Method | Route | Answer |
71
+ |---|---|---|
72
+ | `storeMethodLayout(methodId, version, layout, options?)` | `POST /v1/methods/{id}/versions/{n}/layouts` | The `MethodLayout` as stored, with its `stored_at`. |
73
+
74
+ It takes a bare catalog id, as every method route does, and refuses a suffixed one (`mt_…@3`) with a `RequestArgumentError` before anything is sent; `version` is the number of the published version the layout was designed for, and a value that is not a positive integer is refused the same way.
75
+
76
+ **The body is closed at both levels.** The client sends `tree_digest`, `pipe_ref`, `catalog`, `prompt_hash`, `jsonl` and `provenance` with its `producer` and `model`, and `seed`, `style` and `repair_rounds` when each is given and not `null`, and nothing else. A layout read back can be passed as it is: its `stored_at` is left out. A layout or a provenance that is not an object is refused with a `RequestArgumentError` before any request; every other member is sent as given, and the platform holds it to its shape.
77
+
78
+ **`tree_digest` must be the tree the version holds**, its `MethodVersionSummary.tree_digest`, which `getMethodVersion` and `listMethodVersions` read: a producer designs over the descriptor of the version it was handed, so a layout stating another tree describes content the version does not hold, and is refused.
79
+
80
+ **A write of the same identity replaces the layout.** The answer is the same `200` whether the write stored a new layout or replaced one, and the replaced layout is carried no more.
81
+
82
+ **A write is sent again under its idempotency key.** `options.idempotencyKey` (`MethodLayoutWriteOptions`) is sent as the `Idempotency-Key` header, and nothing is sent when it is absent or `null`. Under the same key the hosted API answers a retry with the first write's stored answer and runs nothing, so a write whose answer was lost, on a client timeout for instance, is sent again unchanged under the key it was first sent with. Without a key the write runs again: it stamps the layout with a new `stored_at`, which moves it ahead of every layout stored since in the newest-first order, and applies the bound below again, so when another producer stored a newer prompt of the same pipe and catalog meanwhile, the resent layout can come back ahead of it and, at the bound, that newer layout can be the one removed. Generate one key per layout to store, a `crypto.randomUUID()` for instance. The write's `200` and its `4xx` refusals are stored under the key alike; a `5xx` and a refusal for its timing (a `408`, `425` or `429`) are not, so the same key sends the write afresh, and neither are the key's own `409` answers below, so a retry after `request.idempotency_key_in_use` runs as the key's first request would. A key is 1 to 255 visible ASCII characters, and anything else is a `RequestArgumentError` before any request.
83
+
84
+ **A content keeps at most 16 layouts**, every pipe, catalog and prompt hash together, which is also the most a validate carries. Past the bound, a write removes first the layouts that newer prompt hashes of their own pipe and catalog superseded, the most superseded and then the oldest first, and the last layout of a pipe and catalog leaves only once more than 16 pipes and catalogs hold layouts. A host laying out several pipes, each in a composer and a result catalog, keeps every one's current layout while another takes new prompts.
85
+
86
+ **Who may write.** The write changes the method, so it takes the roles that change methods: an `admin` or a `member` of the method's organization. A `runner` is refused.
87
+
88
+ ## The refusals
89
+
90
+ Each is an `ApiResponseError` carrying the platform's problem, and none stores anything. Branch on its `code`, never on the status.
91
+
92
+ | Status | `code` | When |
93
+ |---|---|---|
94
+ | `422` | `request.invalid` | A member breaking its shape or bound, named in the problem's `params.field` and `location.field`, `provenance.<member>` for one of the provenance's: a missing member, a `tree_digest` that is not `sha256:` and 64 lowercase hexadecimal characters, a `pipe_ref` that is not a dotted reference or is over 256 characters, a `catalog` or `producer` that is not a slug, a `prompt_hash` that is not 12 to 64 lowercase hexadecimal digits, an empty `jsonl`, one over 128 KiB of UTF-8 or one holding a raw control character other than the tab, the line feed and the carriage return, a `model` that is empty, over 128 characters or holds a control character, a `seed` over 4,000 characters, a `style` off its shape, a `repair_rounds` outside 0 to 100, and a lone UTF-16 surrogate in `jsonl`, `model` or `seed`. |
95
+ | `422` | `request.invalid` naming `tree_digest` | `tree_digest` is not the digest of the tree the version holds. |
96
+ | `403` | `auth.forbidden` | A `runner`, or a caller with no recognised role; its `params.requirement` is `member`. |
97
+ | `404` | `pipelex.catalog.not_found` | No such method in the caller's organization, another organization's included, its `params.resource` `method`; or a version the method never published, its `params.resource` `version`. |
98
+ | `409` | `pipelex.catalog.being_deleted` | The method's erasure is pending, under way or stopped. |
99
+ | `409` | `request.idempotency_conflict` | The idempotency key was sent before with a different body; its `retry` is `no`. |
100
+ | `409` | `request.idempotency_key_in_use` | The key's earlier request is still being handled; its `retry` is `yes`, under the same key in a few seconds. |
101
+ | `500` | `runtime.unexpected` | The version was stored before methods were trees and names no tree, so it takes no layout. |
102
+
103
+ An answer that does not read as a layout, one whose `tree_digest`, `pipe_ref`, `catalog`, `prompt_hash`, `jsonl` or `stored_at` is not a string, or whose `provenance` is not an object with a string `producer` and `model`, is an answer the SDK cannot read, thrown as an `ApiResponseError` coded `client.response_unreadable` whose message names the member.
104
+
105
+ ## Reading the layouts: the `layouts` view
106
+
107
+ A host reads a method's layouts on the validate it makes to read the method's input form. A `validate` naming a stored method by `method_id` (`mt_…`, `mt_…@<n>` or `mt_…@draft`) and naming `layouts` among its `views` is answered, on the valid report, with `layouts`: every layout stored for the tree the selector resolved, newest first by `stored_at`, at most 16. Ask for `views: ["input_form", "layouts"]` to receive the descriptor and the layouts over it in one answer. `ValidateView` types the tokens this SDK knows, `input_form`, `output_form` and `layouts`, and `ValidateOptions.views` still takes any other token as its raw string.
108
+
109
+ The token rides the `views` list with that list's mechanics: the client sends it verbatim and only when asked, a runner that does not know it ignores it, and it never causes a `422`. The platform forwards `views` as sent and adds the member to the runner's answer.
110
+
111
+ **Absent means none.** `PipelexValidationReport.layouts` is optional, and the platform leaves it out, so a host renders the plain form, whenever:
112
+
113
+ - the validate names no `method_id`, since a method sent as its tree has no stored layouts, whatever its tree;
114
+ - `views` does not name `layouts`;
115
+ - the verdict is invalid, since a layout is rendered over the descriptor and the descriptor rides the valid report alone;
116
+ - no layout is stored for the resolved tree;
117
+ - the platform could not read the stored layouts, which costs the validate nothing else.
118
+
119
+ A host therefore never reads an empty array. Publishing a version with another tree leaves the layouts of the old tree where they were: the bare id, now resolving the new tree, carries none of them, and the old version named by its number still carries them all. Another organization's public method carries the layouts its holder's builders stored.
120
+
121
+ **The client reads `layouts` entry by entry**, as it reads a verdict's problem items, so one layout it cannot read never costs the caller the verdict or the other layouts: an entry that does not read as a layout, by the rule the write's answer is held to, is left out, and a list left with none is removed. In each entry, a provenance `seed` or `style` that is missing or not a string reads as `null`, and a `repair_rounds` that is missing or not a non-negative integer reads as `0`, the platform's own defaults; the write's answer is read the same way.
122
+
123
+ **Discovery.** `GET /v1/version` lists `method_layouts` among its `extensions` once the platform stores layouts and carries the view. Without it, a validate answering no `layouts` cannot tell a method nobody laid out from a platform that ignores the token, so a host that does not find it offers no layout to produce, since the write would be refused, and renders the plain form. The SDK types no member for it: read it off `version()`, whose answer keeps every extension member as the server sent it.
124
+
125
+ ```ts
126
+ const info = await client.version();
127
+ const storesLayouts =
128
+ Array.isArray(info.extensions) && info.extensions.includes("method_layouts");
129
+ ```
130
+
131
+ ## What a host does with a layout
132
+
133
+ A host renders a layout only when it can, and renders the plain form over the descriptor otherwise. It sets a layout aside when it does not render its `catalog`, when it no longer accepts its `prompt_hash`, when the layout's `pipe_ref` is not the pipe it is showing, or when the layout no longer fits the descriptor, such as a binding to a path the descriptor does not hold. Among the layouts it can render it picks one by its own rule, which may read `provenance.style` to prefer the layout designed under the style it shows. A host never sends a layout back, and never fails the page for want of one.
@@ -7,7 +7,9 @@ import { PipelexApiClient } from "@pipelex/sdk";
7
7
 
8
8
  const client = new PipelexApiClient();
9
9
  const page = await client.listMethodTemplates({ ordering: "default", category: "finance", limit: 8 });
10
- for (const tab of page.categories) console.log(tab.slug, tab.label); // every category with a template
10
+ // Every category with a template, labelled in the reader's language, else in English.
11
+ const locale = "fr";
12
+ for (const tab of page.categories) console.log(tab.slug, tab.labels[locale] ?? tab.label);
11
13
  for (const template of page.items) {
12
14
  // The thumbnail whose ratio is closest to a 2:1 card.
13
15
  const card = template.thumbnails.reduce<(typeof template.thumbnails)[number] | undefined>(
@@ -34,16 +36,27 @@ if (first !== undefined) {
34
36
  |---|---|---|
35
37
  | `listMethodTemplates({ ordering?, category?, limit?, cursor? })` | `GET /v1/method-templates` | A `MethodTemplatePage`: `{ items, nextCursor, ordering, categories }`. |
36
38
  | `iterateMethodTemplates({ ordering?, category?, limit? })` | the same, following the cursor | Every `MethodTemplate`, in the order served. |
39
+ | `listMethodCategories()` | `GET /v1/method-categories` | A `MethodCategoryList`: `{ categories }`, the catalog's whole list. |
37
40
 
38
41
  **The order is the platform's.** An ordering is a named list an admin writes, such as `default` or one arm of a test. The templates it lists lead, in its order, and every other template follows by name, so a template appears as soon as it is public. `ordering` asks for one by name; an absent, unknown or malformed name serves `default`, and the page's `ordering` names the one served, `null` when there is none, the templates then listed by name. A client never sorts the cards: it keeps the order served.
39
42
 
40
- **A category filters and keeps the order.** `category` takes a slug of the catalog's list, such as `human-resources`, and keeps that category's templates in the same order; a slug no template shows keeps none. Whatever the filter, the page's `categories` names, in the catalog's order, every category at least one template shows, each a `MethodCategory` with its `slug` and its English `label`, so a tab bar drawn from it never shows an empty tab. A client translating the labels translates by slug and falls back on the label.
43
+ **A category filters and keeps the order.** `category` takes a slug of the catalog's list, such as `human-resources`, and keeps that category's templates in the same order; a slug no template shows keeps none. Whatever the filter, the page's `categories` names, in the catalog's order, every category at least one template shows, so a tab bar drawn from it never shows an empty tab.
44
+
45
+ **The categories are a list the platform keeps.** An admin of the platform adds, renames, reorders and removes the catalog's categories, with no release of any client, so a client never holds its own copy of the list. `listMethodCategories` reads the whole list in its order, a category no template shows included; the page's `categories` is the part of it some template shows. Each is a `MethodCategory`:
46
+
47
+ - `slug`, such as `human-resources`, which a manifest declares and never changes: a new meaning is a new slug;
48
+ - `label`, the English label, which a client that does not translate shows;
49
+ - `labels`, the label in each language the platform holds one for, by language tag, such as `{ en: "Human resources", fr: "Ressources humaines" }`, `labels.en` equalling `label`. A category stored before the platform spoke a language lacks a label in it, so a client shows `labels[locale] ?? label`, and the type, `Partial<Record<string, string>>`, makes it handle that case.
50
+
51
+ A template shows under the categories its manifest declares that are on the list. A slug off the list is kept on the method and its versions but shown under no tab, and appears as soon as an admin adds it to the list, with no new publish.
41
52
 
42
53
  **A card is the latest published version.** Each `MethodTemplate` carries `method_id`, `name`, `description` (`null` when it has none), `latest_version`, `categories` (slugs), `organization_name`, the display name of the organization holding it, and `thumbnails`, each a `MethodTemplateThumbnail`: the image's `path`, `media_type`, `width`, `height` and `sha256`, with a `url` signed by storage and its `expires_at`, about fifteen minutes after the read. The ratio is never declared: draw the thumbnail whose `width` and `height` come closest to your frame. The link is a bearer credential until it expires, so it stays out of logs and stored records, and an answer the SDK cannot read is thrown with its links redacted.
43
54
 
44
55
  **Paging.** A page holds 50 templates by default and at most 200; a `limit` outside 1 to 200 is a `422` `request.invalid`. `nextCursor` resumes after the last template the page served, wherever the catalog now places it, so a template added or removed elsewhere between two pages neither repeats nor skips the ones around that point. The cursor holds neither the ordering nor the category: pass both again with it, as `iterateMethodTemplates` does. A cursor of another listing is a `422` `request.invalid` and one that does not decode a `422` `request.malformed`.
45
56
 
46
- **Refusals.** `500` `runtime.misconfigured` when the templates organization the deployment names has no record or a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A deployment that names no templates organization serves an empty catalog.
57
+ **Who reads them.** Any member of any organization reads the catalog and the list of categories, with a web session or an API key, and every caller reads the same; a caller with no active organization is refused with a `403` `pipelex.org.required`.
58
+
59
+ **Refusals.** `500` `runtime.misconfigured` when the templates organization the deployment names has no record or a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A deployment that names no templates organization serves an empty catalog. An answer the SDK cannot read, such as a category without a string `slug` and `label` or whose `labels` is not an object of strings, is thrown as an `ApiResponseError`.
47
60
 
48
61
  ## Reading another organization's public method
49
62
 
@@ -80,7 +93,7 @@ if ("updated_at" in method) {
80
93
  ## Visibility, categories and thumbnails on a method
81
94
 
82
95
  - **`visibility`**, on every method read (`MethodData`, `MethodDetail`, `PublicMethodDetail`): `internal` or `public`, read open, so a value a later platform adds arrives as its raw string. Absent from a platform that predates it, which means `internal`. The list rows (`MethodSummary`) do not carry it.
83
- - **`categories`**, on a method and on each version summary: the slugs the manifest declares under `[package]` that are on the catalog's list, in their declared order. The platform ignores a value it does not know, so a typo reads as no category.
96
+ - **`categories`**, on a method and on each version summary: the slugs the manifest declares under `[package]`, each well-formed one once, in their declared order, whether or not they are on the catalog's list. The catalog shows a template under those on the list alone, so a typo shows under no tab.
84
97
  - **`thumbnails`**, on each version summary: the valid images directly under the tree's `thumbnails/` folder, each a `MethodThumbnail` (`path`, `media_type`, `width`, `height`, `sha256`), measured by the publish. A PNG, JPEG or WebP of at least 800 by 400 pixels is valid; an invalid image is simply not recorded.
85
98
  - **`description`**: the manifest's `description`, else the primary bundle's top-level `description`, else its main pipe's, a blank one counting as none at each step.
86
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/sdk",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "TypeScript SDK for the Pipelex hosted API — execute methods, manage runs, and call the product surface.",
5
5
  "license": "MIT",
6
6
  "repository": {