@pipelex/sdk 0.63.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,164 @@
1
+ /**
2
+ * A method's stored layouts: the layout a producer designed for one pipe of a published version,
3
+ * which the hosted platform stores beside the method (`storeMethodLayout`,
4
+ * `POST /v1/methods/{id}/versions/{n}/layouts`) and hands back on the validate of the method by id
5
+ * that names `layouts` among its `views` (`PipelexValidationReport.layouts`).
6
+ *
7
+ * A layout is a data file: a tree of elements written in the vocabulary of a catalog of
8
+ * components, every value bound to a path in the host's form state, carrying no field
9
+ * descriptions and no data of its own. A host renders it over the input-form descriptor the same
10
+ * validate projects, and renders the plain form instead whenever it cannot. The platform stores and
11
+ * carries it and never judges it. The shapes are stated brand-neutrally, as the input-form
12
+ * descriptor they are rendered over is: snake_case members with no `pipelex_` prefix. The storage
13
+ * and the `layouts` token are the hosted plane's.
14
+ *
15
+ * The readers here check a layout member by member, as `readProblemItems` checks a verdict's
16
+ * items: a validate never loses its verdict, nor its other layouts, to one layout it cannot read.
17
+ */
18
+ /**
19
+ * How a stored layout was made, as its producer stated it when it wrote the layout. Every member
20
+ * is present on a layout the SDK hands back.
21
+ */
22
+ export interface LayoutProvenance {
23
+ /** Who produced the layout, a short kebab-case slug such as `pipelex-method`. */
24
+ producer: string;
25
+ /** The id of the model the producer ran, such as `claude-4.8-opus`. */
26
+ model: string;
27
+ /** The creative seed handed to the model with the brief, verbatim; `null` when none was. */
28
+ seed: string | null;
29
+ /**
30
+ * The id of the app style the layout was designed under, such as `editorial`; `null` when it
31
+ * was designed under none. The platform holds it to the shape `^[a-z0-9-]{1,32}$` and never to
32
+ * a list of styles, so a host reads an id it does not know as no style.
33
+ */
34
+ style: string | null;
35
+ /** How many repair rounds the producer ran before the layout validated against its catalog. */
36
+ repair_rounds: number;
37
+ }
38
+ /**
39
+ * One stored layout — what `storeMethodLayout` answers, and an entry of
40
+ * `PipelexValidationReport.layouts`.
41
+ *
42
+ * Its identity is `tree_digest`, `pipe_ref`, `catalog` and `prompt_hash` within one method: it
43
+ * belongs to the content it was designed for, never to a version number, so it is served for
44
+ * whatever selector of that method resolves the same tree, and a write of the same identity
45
+ * replaces it. It names no method and no version.
46
+ */
47
+ export interface MethodLayout {
48
+ /**
49
+ * The digest of the method tree the layout was produced against, `sha256:` and 64 lowercase
50
+ * hexadecimal characters: the MTHDS standard's digest of the tree's listing, as a published
51
+ * version's `tree_digest` states it.
52
+ */
53
+ tree_digest: string;
54
+ /**
55
+ * The namespaced pipe reference it lays out, `domain_path.pipe_code`, as the input-form
56
+ * descriptor keys its forms.
57
+ */
58
+ pipe_ref: string;
59
+ /** The id of the catalog whose vocabulary it is written in, a slug such as `composer`. */
60
+ catalog: string;
61
+ /** The hash of the prompt the producer's model read, 12 to 64 lowercase hexadecimal digits. */
62
+ prompt_hash: string;
63
+ /**
64
+ * The layout itself, as the producer's model emitted it: JSON patch lines, one per line,
65
+ * carried as one string exactly as written. The platform never parses it.
66
+ */
67
+ jsonl: string;
68
+ /** How it was made. */
69
+ provenance: LayoutProvenance;
70
+ /** The moment the write that stored it landed, ISO 8601 in UTC: the one member the platform sets. */
71
+ stored_at: string;
72
+ }
73
+ /**
74
+ * The provenance a write states — `MethodLayoutInput.provenance`. A `null` or omitted optional
75
+ * member is not sent, and the platform stores its default: no seed, no style, no repair round.
76
+ */
77
+ export interface LayoutProvenanceInput {
78
+ /** Who produced the layout, a slug: 1 to 64 lowercase letters, digits and hyphens, not starting with a hyphen. */
79
+ producer: string;
80
+ /** The id of the model the producer ran: 1 to 128 characters, none of them a control character. */
81
+ model: string;
82
+ /** The creative seed handed to the model with the brief, verbatim, at most 4,000 characters. */
83
+ seed?: string | null;
84
+ /** The id of the app style the layout was designed under, of the shape `^[a-z0-9-]{1,32}$`. */
85
+ style?: string | null;
86
+ /** How many repair rounds the producer ran before the layout validated, from 0 to 100. */
87
+ repair_rounds?: number | null;
88
+ }
89
+ /**
90
+ * The write — `storeMethodLayout`, `POST /v1/methods/{id}/versions/{n}/layouts`: the stored layout
91
+ * without `stored_at`. The wire body is closed at both levels, so the client sends these members
92
+ * and nothing else; a `MethodLayout` read back can be passed as it is, its `stored_at` left out.
93
+ * The bounds below are the platform's, which refuses a member breaking one with a `422`
94
+ * `request.invalid` naming it, `provenance.<member>` for one of the provenance's.
95
+ */
96
+ export interface MethodLayoutInput {
97
+ /**
98
+ * The digest of the tree the layout was produced against, which must be the `tree_digest` of the
99
+ * version the call names (`MethodVersionSummary.tree_digest`): `sha256:` and 64 lowercase
100
+ * hexadecimal characters.
101
+ */
102
+ tree_digest: string;
103
+ /**
104
+ * The pipe it lays out, `domain_path.pipe_code`: two or more dot-separated identifiers, at most
105
+ * 256 characters in all.
106
+ */
107
+ pipe_ref: string;
108
+ /** The catalog's id, a slug of the same shape as `provenance.producer`. */
109
+ catalog: string;
110
+ /** The hash of the prompt the producer's model read, 12 to 64 lowercase hexadecimal digits. */
111
+ prompt_hash: string;
112
+ /**
113
+ * The layout as the producer's model emitted it, not empty, at most 128 KiB of UTF-8, holding
114
+ * no raw control character but the tab, the line feed and the carriage return.
115
+ */
116
+ jsonl: string;
117
+ /** How it was made. */
118
+ provenance: LayoutProvenanceInput;
119
+ }
120
+ /**
121
+ * The members the write sends besides `provenance`, every one each time. Checked against
122
+ * `MethodLayoutInput` with `satisfies`, so a member the interface gains and this list lacks fails
123
+ * the build rather than being dropped from the body without a word.
124
+ */
125
+ export declare const LAYOUT_INPUT_MEMBERS: {
126
+ readonly tree_digest: true;
127
+ readonly pipe_ref: true;
128
+ readonly catalog: true;
129
+ readonly prompt_hash: true;
130
+ readonly jsonl: true;
131
+ };
132
+ /**
133
+ * The members of the write's provenance, `required` ones sent each time and `optional` ones only
134
+ * when given and not `null`, checked against `LayoutProvenanceInput` as `LAYOUT_INPUT_MEMBERS` is.
135
+ */
136
+ export declare const PROVENANCE_INPUT_MEMBERS: {
137
+ readonly producer: "required";
138
+ readonly model: "required";
139
+ readonly seed: "optional";
140
+ readonly style: "optional";
141
+ readonly repair_rounds: "optional";
142
+ };
143
+ /**
144
+ * What keeps `value` from reading as a stored layout, as the end of a sentence naming it ("a
145
+ * layout whose `jsonl` is not a string"), or `null` when it reads as one: an object whose
146
+ * `tree_digest`, `pipe_ref`, `catalog`, `prompt_hash`, `jsonl` and `stored_at` are strings and
147
+ * whose `provenance` is an object with a string `producer` and `model`. The provenance's optional
148
+ * members never keep a layout from reading (`readMethodLayout`).
149
+ */
150
+ export declare function methodLayoutFault(value: unknown): string | null;
151
+ /**
152
+ * `value` read as a stored layout, or `null` when it does not read as one (`methodLayoutFault`).
153
+ * The provenance's `seed` and `style` are kept only when each is a string, and read as `null`
154
+ * otherwise, and its `repair_rounds` only when it is a non-negative integer, and read as `0`
155
+ * otherwise: the platform's own defaults, so each member is what `LayoutProvenance` declares. The
156
+ * rest is returned as it came.
157
+ */
158
+ export declare function readMethodLayout(value: unknown): MethodLayout | null;
159
+ /**
160
+ * A valid report's `layouts` read entry by entry: each entry that reads as a layout
161
+ * (`readMethodLayout`), in the answer's order, newest first as the platform sends them, and
162
+ * anything else left out. A value that is not an array reads as no layout.
163
+ */
164
+ export declare function readMethodLayouts(value: unknown): MethodLayout[];
@@ -0,0 +1,115 @@
1
+ /**
2
+ * A method's stored layouts: the layout a producer designed for one pipe of a published version,
3
+ * which the hosted platform stores beside the method (`storeMethodLayout`,
4
+ * `POST /v1/methods/{id}/versions/{n}/layouts`) and hands back on the validate of the method by id
5
+ * that names `layouts` among its `views` (`PipelexValidationReport.layouts`).
6
+ *
7
+ * A layout is a data file: a tree of elements written in the vocabulary of a catalog of
8
+ * components, every value bound to a path in the host's form state, carrying no field
9
+ * descriptions and no data of its own. A host renders it over the input-form descriptor the same
10
+ * validate projects, and renders the plain form instead whenever it cannot. The platform stores and
11
+ * carries it and never judges it. The shapes are stated brand-neutrally, as the input-form
12
+ * descriptor they are rendered over is: snake_case members with no `pipelex_` prefix. The storage
13
+ * and the `layouts` token are the hosted plane's.
14
+ *
15
+ * The readers here check a layout member by member, as `readProblemItems` checks a verdict's
16
+ * items: a validate never loses its verdict, nor its other layouts, to one layout it cannot read.
17
+ */
18
+ import { isPlainObject } from "./records.js";
19
+ /**
20
+ * The members the write sends besides `provenance`, every one each time. Checked against
21
+ * `MethodLayoutInput` with `satisfies`, so a member the interface gains and this list lacks fails
22
+ * the build rather than being dropped from the body without a word.
23
+ */
24
+ export const LAYOUT_INPUT_MEMBERS = {
25
+ tree_digest: true,
26
+ pipe_ref: true,
27
+ catalog: true,
28
+ prompt_hash: true,
29
+ jsonl: true,
30
+ };
31
+ /**
32
+ * The members of the write's provenance, `required` ones sent each time and `optional` ones only
33
+ * when given and not `null`, checked against `LayoutProvenanceInput` as `LAYOUT_INPUT_MEMBERS` is.
34
+ */
35
+ export const PROVENANCE_INPUT_MEMBERS = {
36
+ producer: "required",
37
+ model: "required",
38
+ seed: "optional",
39
+ style: "optional",
40
+ repair_rounds: "optional",
41
+ };
42
+ /** The members of a layout read as strings, in the order a fault names the first one missing. */
43
+ const LAYOUT_STRING_MEMBERS = [
44
+ "tree_digest",
45
+ "pipe_ref",
46
+ "catalog",
47
+ "prompt_hash",
48
+ "jsonl",
49
+ "stored_at",
50
+ ];
51
+ /** The members of a layout's provenance read as strings. */
52
+ const PROVENANCE_STRING_MEMBERS = ["producer", "model"];
53
+ /**
54
+ * What keeps `value` from reading as a stored layout, as the end of a sentence naming it ("a
55
+ * layout whose `jsonl` is not a string"), or `null` when it reads as one: an object whose
56
+ * `tree_digest`, `pipe_ref`, `catalog`, `prompt_hash`, `jsonl` and `stored_at` are strings and
57
+ * whose `provenance` is an object with a string `producer` and `model`. The provenance's optional
58
+ * members never keep a layout from reading (`readMethodLayout`).
59
+ */
60
+ export function methodLayoutFault(value) {
61
+ if (!isPlainObject(value))
62
+ return "that is not an object";
63
+ for (const member of LAYOUT_STRING_MEMBERS) {
64
+ if (typeof value[member] !== "string")
65
+ return `whose \`${member}\` is not a string`;
66
+ }
67
+ const provenance = value.provenance;
68
+ if (!isPlainObject(provenance))
69
+ return "whose `provenance` is not an object";
70
+ for (const member of PROVENANCE_STRING_MEMBERS) {
71
+ if (typeof provenance[member] !== "string") {
72
+ return `whose \`provenance.${member}\` is not a string`;
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * `value` read as a stored layout, or `null` when it does not read as one (`methodLayoutFault`).
79
+ * The provenance's `seed` and `style` are kept only when each is a string, and read as `null`
80
+ * otherwise, and its `repair_rounds` only when it is a non-negative integer, and read as `0`
81
+ * otherwise: the platform's own defaults, so each member is what `LayoutProvenance` declares. The
82
+ * rest is returned as it came.
83
+ */
84
+ export function readMethodLayout(value) {
85
+ if (methodLayoutFault(value) !== null)
86
+ return null;
87
+ const layout = value;
88
+ const { seed, style, repair_rounds: rounds } = layout.provenance;
89
+ return {
90
+ ...layout,
91
+ provenance: {
92
+ ...layout.provenance,
93
+ seed: typeof seed === "string" ? seed : null,
94
+ style: typeof style === "string" ? style : null,
95
+ repair_rounds: Number.isSafeInteger(rounds) && rounds >= 0 ? rounds : 0,
96
+ },
97
+ };
98
+ }
99
+ /**
100
+ * A valid report's `layouts` read entry by entry: each entry that reads as a layout
101
+ * (`readMethodLayout`), in the answer's order, newest first as the platform sends them, and
102
+ * anything else left out. A value that is not an array reads as no layout.
103
+ */
104
+ export function readMethodLayouts(value) {
105
+ if (!Array.isArray(value))
106
+ return [];
107
+ const layouts = [];
108
+ for (const entry of value) {
109
+ const layout = readMethodLayout(entry);
110
+ if (layout !== null)
111
+ layouts.push(layout);
112
+ }
113
+ return layouts;
114
+ }
115
+ //# sourceMappingURL=method-layouts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"method-layouts.js","sourceRoot":"","sources":["../src/method-layouts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA4G7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,WAAW,EAAE,IAAI;IACjB,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,IAAI;IACjB,KAAK,EAAE,IAAI;CACoE,CAAC;AAElF;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,UAAU;IACjB,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,UAAU;IACjB,aAAa,EAAE,UAAU;CACsD,CAAC;AAElF,iGAAiG;AACjG,MAAM,qBAAqB,GAAG;IAC5B,aAAa;IACb,UAAU;IACV,SAAS;IACT,aAAa;IACb,OAAO;IACP,WAAW;CACH,CAAC;AAEX,4DAA4D;AAC5D,MAAM,yBAAyB,GAAG,CAAC,UAAU,EAAE,OAAO,CAAU,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,uBAAuB,CAAC;IAC1D,KAAK,MAAM,MAAM,IAAI,qBAAqB,EAAE,CAAC;QAC3C,IAAI,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,QAAQ;YAAE,OAAO,WAAW,MAAM,oBAAoB,CAAC;IACtF,CAAC;IACD,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACpC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC;QAAE,OAAO,qCAAqC,CAAC;IAC7E,KAAK,MAAM,MAAM,IAAI,yBAAyB,EAAE,CAAC;QAC/C,IAAI,OAAO,UAAU,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC3C,OAAO,sBAAsB,MAAM,oBAAoB,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,iBAAiB,CAAC,KAAK,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,MAAM,GAAG,KAA0E,CAAC;IAC1F,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC;IACjE,OAAO;QACL,GAAG,MAAM;QACT,UAAU,EAAE;YACV,GAAG,MAAM,CAAC,UAAU;YACpB,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;YAC5C,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;YAC/C,aAAa,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAK,MAAiB,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;SACpF;KACyB,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,KAAkB,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/models.d.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  * `mthds/protocol` subpath.
11
11
  */
12
12
  import type { InputForm, MethodFiles, ProblemItem, MethodTreeSource, OutputForm, PipeIOContracts, RunResultExecute, RunResultStart } from "mthds/protocol";
13
+ import type { MethodLayout } from "./method-layouts.js";
13
14
  import type { MethodTreeForm, NoMethodTree } from "./method-trees.js";
14
15
  export interface DictStuff {
15
16
  concept: string;
@@ -141,6 +142,14 @@ export type PipelexValidateSource = (MethodTreeForm & {
141
142
  method_ref?: never;
142
143
  method_id?: never;
143
144
  }) | (ValidateMethodSelector & NoMethodTree);
145
+ /**
146
+ * The `views` tokens this SDK knows, each adding the same-named top-level member to the valid
147
+ * report: `input_form` and `output_form`, the runner's per-pipe form descriptors, and `layouts`, the
148
+ * hosted plane's stored layouts of a method validated by `method_id`. `ValidateOptions.views` reads
149
+ * them open: a token a later server adds is sent as its raw string, and a server ignores a token it
150
+ * does not know, never answering a `422` for one.
151
+ */
152
+ export type ValidateView = "input_form" | "output_form" | "layouts";
144
153
  /** Per-pipe dry-run verdict in `validated_pipes[]` — mirror of pipelex's `DryRunStatus`. */
145
154
  export type DryRunStatus = "SUCCESS" | "FAILURE" | "SKIPPED";
146
155
  /** One entry of `PipelexValidationReport.validated_pipes` — `{pipe_ref, status}`. */
@@ -262,6 +271,24 @@ export interface PipelexValidationReport {
262
271
  * through `views: ["output_form"]` and absent from any verdict that did not ask.
263
272
  */
264
273
  output_form?: OutputForm;
274
+ /**
275
+ * The layouts stored for the method's content, newest first by `stored_at`, at most 16: a
276
+ * Pipelex hosted extension, requested through `views: ["layouts"]` on a validate naming a stored
277
+ * method by `method_id`, and carried for every selector of that method resolving the same tree
278
+ * (the version it was written against, a later version holding the same tree, the draft holding
279
+ * it). Ask for `["input_form", "layouts"]` to receive the descriptor and the layouts over it in
280
+ * one answer.
281
+ *
282
+ * **Absent means none**: the member is left out when the validate names no `method_id`, when
283
+ * `views` does not name `layouts`, when no layout is stored for the resolved tree, and when the
284
+ * platform could not read them, so a host never reads an empty array and renders the plain form
285
+ * whenever it is absent. A platform that does not carry the view leaves it out too; its
286
+ * `GET /v1/version` does not list `method_layouts` among its `extensions`.
287
+ *
288
+ * Read entry by entry (`readMethodLayouts`): an entry that does not read as a layout is left
289
+ * out rather than failing the verdict, and a list left with none reads as absent.
290
+ */
291
+ layouts?: MethodLayout[];
265
292
  /** Pipes the runtime may skip when an optional slot resolves absent. */
266
293
  liftable_pipes: LiftablePipeEntry[];
267
294
  /**
package/dist/version.d.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  * `User-Agent` without importing the public barrel. Kept in sync with
4
4
  * `package.json` — enforced by `tests/index.test.ts`.
5
5
  */
6
- export declare const SDK_VERSION = "0.63.0";
6
+ export declare const SDK_VERSION = "0.64.0";
package/dist/version.js CHANGED
@@ -3,5 +3,5 @@
3
3
  * `User-Agent` without importing the public barrel. Kept in sync with
4
4
  * `package.json` — enforced by `tests/index.test.ts`.
5
5
  */
6
- export const SDK_VERSION = "0.63.0";
6
+ export const SDK_VERSION = "0.64.0";
7
7
  //# sourceMappingURL=version.js.map
@@ -117,6 +117,7 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
117
117
  - `client.ts` — `PipelexApiClient`: the `request()` pipeline (auth, base URL, timeouts/abort, problem-details parsing) and every route. It implements the standard's `MTHDSProtocol<DictPipeOutput>` for every route but `validate`, so the protocol-execution methods stay shaped like the standard: `validate` answers Pipelex's own verdict, `PipelexValidationResult`, because the hosted plane still sends the verdict that predates the protocol's problem items, where the standard's `ValidationResult` lists them.
118
118
  - `models.ts` — Dict-serialized concretes (`DictStuff` / `DictWorkingMemory` / `DictPipeOutput` / `DictRunResultExecute`), the `/v1/validate` surface (`PipelexValidationResult`, whose two arms are declared whole rather than as narrowings of the standard's `ValidationResult`, listing their faults and warnings as the standard's `ProblemItem`, with its `SuggestedFix` and `FixOp`, see [errors.md](./errors.md#a-verdicts-problem-items)), the tools surface (`Diagnostic` / `LintResponse` / `FormatResponse`), and the crate-route request/response models — the shared `CrateRequestBase` / `CrateInvalidReport` vocabulary plus the `/v1/resolve`, `/v1/codegen` and `/v1/pipe-io` shapes built on it, `GeneratedArtifact` among them.
119
119
  - `product-models.ts` — the snake_case wire shapes for the hosted-product management routes (profile, methods catalog, organizations, billing, API keys, onboarding, storage, an organization's files, runs list/detail/update). Each interface models only the fields the product actually consumes — no speculative mirrors.
120
+ - `method-layouts.ts` — a method's stored layouts: `MethodLayoutInput` and `LayoutProvenanceInput`, the write `storeMethodLayout` sends, `MethodLayout` and `LayoutProvenance`, what the platform stores, answers and carries on a validate's `layouts`, and the reader both go through, `readMethodLayout` (with `methodLayoutFault`, which names the member an unreadable layout breaks, and `readMethodLayouts`, which reads a report's list entry by entry). It imports the plain-object guard of `records.ts` and nothing else. The Python twin is `pipelex_sdk/method_layout_models.py`. See [method-layouts.md](./method-layouts.md).
120
121
  - `answer-reading.ts` — `readHostedAnswer`, the reading of an answer that is not the route's result as its problem: a problem document as received, and an answer whose body is none, the hosted edge's, a gateway's or a proxy's, by its status, `client.outcome_unknown` for a start or an execute that may have created a run; and `outcomeUnknownProblem`. `ApiResponseError` reads every answer through it. The Python twin is `pipelex_sdk/answer_reading.py`. See [errors.md](./errors.md#an-answer-that-is-no-problem-document).
121
122
  - `transport-reading.ts` — `requestMayHaveLeft`, the **pure** reading of whether a request that got no answer may have left this machine, every failure counting unless it proves nothing was sent (a failure of the connection's set-up). `ApiUnreachableError` applies it on a start or an execute, which the client marks with `mayStartRun`, to read the failure as `client.unreachable` or `client.outcome_unknown`. The Python twin is `pipelex_sdk/transport_reading.py`. See [errors.md](./errors.md#a-run-that-may-have-started--outcome_unknown).
122
123
  - `problem-items.ts` — `readProblemItems`, the reading of a verdict's `errors` and `warnings` as the standard's problem items, member by member, so one malformed member never costs the caller its fault. The Python twin is `pipelex_sdk/problem_items.py`. See [errors.md](./errors.md#a-verdicts-problem-items).
@@ -140,7 +141,7 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
140
141
  ## Client surface (current)
141
142
 
142
143
  - **Protocol execution:** `execute` (returns a `PipelexExecuteResult` with a resolved `.main_stuff`), `start`, `validate` (returns `PipelexValidationResult`), `models`, `version`.
143
- - **Two independent opt-in lists on `validate`.** `render` asks the server to render a *view format* and adds a `rendered_<format>` field; `views` asks for a *structured view* and adds a same-named top-level field — today `input_form` and `output_form`, the per-pipe form descriptors. A caller that wants the forms without the dry-run verdict reads `pipeIo` instead, which derives them with no dry run. Each resolves its own tokens against its own supported set and unknown tokens are lenient-ignored on both, never a `422`. The client always asks for `render: ["markdown"]` but sends `views` only when a caller names a token, because an opt-in view exists precisely so the default response stays byte-identical for the consumers that discard it. `pipelex-api` gates `input_form` on the token as of 0.18.0; a 0.17.0 runner resolved no token and emitted the field on every valid verdict, which is why `PipelexValidationReport.input_form` is typed optional rather than required — the one typing that reads a response from either runner correctly.
144
+ - **Two independent opt-in lists on `validate`.** `render` asks the server to render a *view format* and adds a `rendered_<format>` field; `views` asks for a *structured view* and adds a same-named top-level field — today `input_form` and `output_form`, the per-pipe form descriptors, and on a hosted validate by `method_id`, `layouts`, the layouts stored for the resolved tree, read entry by entry and absent when there are none ([method-layouts.md](./method-layouts.md)). `ValidateView` names the tokens this SDK knows, and `views` still takes any other as its raw string. A caller that wants the forms without the dry-run verdict reads `pipeIo` instead, which derives them with no dry run. Each resolves its own tokens against its own supported set and unknown tokens are lenient-ignored on both, never a `422`. The client always asks for `render: ["markdown"]` but sends `views` only when a caller names a token, because an opt-in view exists precisely so the default response stays byte-identical for the consumers that discard it. `pipelex-api` gates `input_form` on the token as of 0.18.0; a 0.17.0 runner resolved no token and emitted the field on every valid verdict, which is why `PipelexValidationReport.input_form` is typed optional rather than required — the one typing that reads a response from either runner correctly.
144
145
  - **The method's tree:** `execute` / `start` / `startAndWaitForResult` (both paths) and `validate` take the whole method as its tree — every `.mthds` bundle, its `funcs/*.py`, `structures/*.py` and `requirements.txt`, and every file a step reads, such as a `PipeDocGen` step's template — in one of four forms, `directory`, `contents`, `tree` or `files`, sent inline or by hash. `startAndWaitForResult` prepares the tree once, under its `signal`, before it starts the run, so its fallback to the blocking execute sends the same files without reading or uploading them again. A tree is self-contained, so it satisfies the "something to run" precondition on its own — `pipe_code` is not required beside it. See [run-sources](#run-sources) above and [method-trees.md](./method-trees.md).
145
146
  - **Method selectors on the run methods:** `execute` / `start` / `startAndWaitForResult` take the Pipelex API's `method_ref` (an address the runner resolves; provenance comes back typed as `method_provenance`) and the platform's `method_id` as **typed named options** (`PipelexRunOptions` / `PipelexStartOptions`), never `extra` entries. See [Pipelex API run extensions](#pipelex-api-run-extensions-method_ref) and [hosted run extensions](#hosted-run-extensions-method_id) above.
146
147
  - **An idempotency key on a run start (hosted):** `start` and `startAndWaitForResult` take `idempotencyKey` (`PipelexStartRequestOptions`, intersected into `PipelexStartOptions`), sent as the `Idempotency-Key` header of `POST /v1/start` and never in the body; with no key there is no header. The hosted API binds the key to the run, answers a repeat of the same start with that run, and refuses the key with other options; a bare runner ignores the header. The client refuses a key that is not 1 to 255 visible ASCII characters with a `RequestArgumentError` before any request, the blocking `execute` fallback of `startAndWaitForResult` sends none, and the key leaves the error's problem as it is: a start that may have reached the server stays `client.outcome_unknown`, and the caller who sent a key recovers the run by sending the same start again under it. [errors.md](./errors.md#a-starts-idempotency-key--idempotencykey) holds the contract.
@@ -161,6 +162,7 @@ The hosted catalog/account routes the webapp drives. All ride the same `{base}/v
161
162
  - **Method examples:** `createMethodExample`, `listMethodExamples`, `iterateMethodExamples`, `getMethodExample`, `updateMethodExample`, `recordMethodExampleLatestRun`, `saveAssistantMethodExample`, `deleteMethodExample`, on `/v1/methods/{id}/examples`. An example is a named set of inputs saved on a method, shared across the organization, with an `input_revision` that rises each time its inputs change and the `latest_run_id` of those inputs, which an inputs edit clears. `recordMethodExampleLatestRun` records a run against the revision whose inputs it used and answers a `MethodExampleLatestRunResult` discriminated on `outcome`, `recorded` or `stale`, each arm carrying the `example`: a run that outlasted an edit is a verdict, never thrown, and an answer off the union is one the SDK cannot read; `saveAssistantMethodExample` is the build assistant's save, which writes into the example it names only while that example is still the assistant's (`kept_by_assistant`) at the revision it read, and otherwise creates one, answering a `MethodExampleAssistantSaveResult` discriminated on `outcome`, `kept` or `created`; one helper, `readVerdict`, reads these verdicts and `publishMethod`'s, the example checked once, by the reader every example goes through. Every answer carrying an example is read with `parseExactJson` and every request body written with `stringifyExactJson`, so an integer past `Number.MAX_SAFE_INTEGER` in the inputs reads as a `bigint` and goes back digit for digit, to an edit or as a run's inputs; `3.0` still reads as `3`, and inputs holding one sent back unchanged are an edit for the platform, which compares inputs as canonical JSON. Every example the client hands back, the page rows and the verdict's `example` included, is checked by `readMethodExample` against the fields the Python SDK's model requires, with the strict types it holds them to, an example breaking one being an answer the SDK cannot read. The list is paged oldest first by an opaque cursor, and a page may be short while `nextCursor` is set; `iterateMethodExamples` follows it through the loop `iterateMethods` and `iterateRuns` share, and refuses a method id at the call rather than at the first pull. Every method takes a bare catalog id, and the client refuses an example id that is empty, `.` or `..` or holds a lone surrogate, a create without its name or inputs or whose `run_id` is given but empty, an edit naming neither `name` nor `inputs`, and a latest-run write without a run id or a positive integer revision before sending. See [method-examples.md](./method-examples.md).
162
163
  - **Template catalog:** `listMethodTemplates`, `iterateMethodTemplates`, on `GET /v1/method-templates`, and `listMethodCategories`, on `GET /v1/method-categories`. The public methods of the deployment's templates organization, in the order the platform serves for the `ordering` asked for, filtered by `category`, with the page's `categories` and each card's thumbnails behind short-lived links. `listMethodTemplates` reads the page with its own reader rather than `requestPage`, since its `ordering` and `categories` sit beside `items` and `nextCursor`, and redacts the links of an answer it cannot read; `iterateMethodTemplates` follows the cursor with the same ordering and category. `listMethodCategories` reads the catalog's whole list of categories, which an admin of the platform keeps, in its order. Both check each category with one reader, `methodCategoriesFault`: a string `slug` and `label`, and `labels`, an object of strings by language tag ([public-methods.md](./public-methods.md)).
163
164
  - **Method copies:** `copyMethod`, on `/v1/methods/{id}/copy`. `copyMethod` makes a new, unpublished method in an organization the caller may change methods in, the method's own for a duplicate, holding the original's draft tree, its form inputs and its examples, the stored files they name copied into the destination, and answers a `MethodCopyResult` discriminated on `outcome`: `copied` with the copy as `method` and what was copied, or `refused` with the `references` of a bundle naming storage the destination cannot read or, in a duplicate, a file under the original's run files, which erasing the original would remove. The two arms carry different members, so the verdict is read by the copy itself rather than by `readVerdict`, which reads verdicts whose arms share one: an `outcome` off the union, a `copied` answer whose `method` is not an object and a `refused` one whose `references` is not an array are each an answer the SDK cannot read. Its body is closed, `destination_org_id` with `name` and `expected_draft_updated_at` when given, and its last argument takes an `idempotencyKey`, sent as the `Idempotency-Key` header through the same `requestRaw` option and the same `idempotencyKeyOf` check a start uses. Every method read carries `copied_from`, the original a copy names, `null` on a method created afresh. `MethodErrorCode` names the copy's own codes, `pipelex.org.not_found` and `pipelex.catalog.copy_too_large`, whose `limit` member is typed `MethodCopyLimit`. It takes a bare catalog id. See [method-copy.md](./method-copy.md).
165
+ - **Method layouts:** `storeMethodLayout`, on `/v1/methods/{id}/versions/{n}/layouts`, and the `layouts` view of `validate`. A layout is a data file a producer designed for one pipe of a published version, which the platform stores keyed by the content it was designed for, `tree_digest`, `pipe_ref`, `catalog` and `prompt_hash`, a write of the same identity replacing it, and carries on every validate by `method_id` that names `layouts` and resolves the same tree. The write takes a bare catalog id and a positive integer version, refuses a layout or a provenance that is not an object before sending, and sends the closed body alone, its members listed once in `method-layouts.ts` (`LAYOUT_INPUT_MEMBERS`, `PROVENANCE_INPUT_MEMBERS`) and held to the input interfaces by `satisfies`, each optional provenance member that is `null` or omitted left out; its last argument takes an `idempotencyKey` (`MethodLayoutWriteOptions`), sent as the `Idempotency-Key` header through the same `idempotencyKeyOf` check a start and a copy use, since a write sent again without one runs again, restamping the layout and applying the bound again; the platform's refusals (`422` `request.invalid` naming the member, `tree_digest` among them for a tree that is not the version's, `403` `auth.forbidden` for a runner, `404` `pipelex.catalog.not_found` naming the method or the version, `409` `pipelex.catalog.being_deleted`) are `ApiResponseError`s. One reader in `method-layouts.ts`, `readMethodLayout`, holds the write's answer and each entry of a report's `layouts` to the same rule, the six identity and payload members strings and the provenance an object with a string `producer` and `model`, its `seed` and `style` read as `null` and its `repair_rounds` as `0` when they do not fit; an answer breaking it is one the SDK cannot read, and an entry breaking it is left out of the report, a list left with none removed, so `layouts` is absent or holds at least one layout. See [method-layouts.md](./method-layouts.md).
164
166
  - **Organizations:** `listMemberships`, `createOrganization`, `renameOrganization` (`PATCH`). Each membership carries the caller's `role_in_org`, an `OrgRole` (`admin`, `member` or `runner`) read open: a role a later platform adds arrives as its raw string, so a `switch` over it says what an unknown role does. The role is for display; what it may do is the platform's to decide, and a call refused to the caller's role is an `ApiResponseError`. The org "switch" is a WorkOS-session op (`/api/active-org`), not a `/v1` route — out of SDK scope.
165
167
  - **Organization branding:** `getBranding` (`GET /v1/branding`, any role, web session or API key) and `updateOrganizationBranding` (`PATCH /v1/organizations/{org_id}/branding`, a write by the `admin` or the `member` role, never a `runner`) answer the same `OrganizationBranding`: the organization's name, its light and dark colour sets (`BrandingColors`), its square and wide logos (`LogoDescriptor`), its app `style` and `typeface`, and its read-only `white_label` entitlement, the last three read as `null`, `null` and `false` from a platform that predates them. The write's `OrganizationBrandingUpdate` sets a key given a value, clears a key given `null` and keeps a key left out; a refused field is named by its `location.field`, a colour by its dotted key, a logo by its slot in `ApiResponseError.errors` with a `LogoRefusalCode` when the platform measured it. See [organization-branding.md](./organization-branding.md).
166
168
  - **Caller webhooks:** `listRunWebhookDeliveries`, `getRunWebhookDelivery` and `replayRunWebhookDelivery`, on `/v1/runs/{id}/webhook-deliveries`, `/v1/runs/{id}/webhook-deliveries/{delivery_id}` and its `/replay`, read a run's delivery log and send a settled delivery again, any member by web session or API key, the replay taking an `idempotencyKey` under which the platform stores its answer; `getWebhookSecret`, on `GET /v1/organizations/{org_id}/webhook-secret`, and `rotateWebhookSecret`, on `POST /v1/organizations/{org_id}/webhook-secret/rotate`, read and rotate the organization's signing secret, an admin's routes from a web session only, the rotation taking an `idempotencyKey` the platform never replays. See [webhooks.md](./webhooks.md).
package/docs/errors.md CHANGED
@@ -96,6 +96,8 @@ What the hosted API answers a start sent under a key:
96
96
 
97
97
  **A copy takes a key too.** `copyMethod` sends an `idempotencyKey` the same way, held to the same rule, and the hosted API keeps it for the same 24 hours, scoped to the caller and the method's organization. A copy binds no run, so its answers under a key differ from a start's: [method-copy.md](./method-copy.md#retrying-a-copy--idempotencykey) gives them.
98
98
 
99
+ **And a layout write.** `storeMethodLayout` sends an `idempotencyKey` the same way, held to the same rule. A write sent again without one runs again, restamping the layout and applying the bound of layouts a content keeps again, so a write whose answer was lost is sent again under the key it was first sent with, which answers it as the first write was answered: [method-layouts.md](./method-layouts.md#storing-a-layout) gives the answers.
100
+
99
101
  **So do a secret rotation and a delivery replay.** `rotateWebhookSecret` and `replayRunWebhookDelivery` send an `idempotencyKey` the same way, held to the same rule. A rotation's answer carries a secret, so the platform never replays it, while a replay's stored answer is given again: [webhooks.md](./webhooks.md) gives both.
100
102
 
101
103
  ## Each class's problem
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/sdk",
3
- "version": "0.63.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": {