@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.
- package/README.md +20 -0
- package/dist/cli/script.d.ts +1 -1
- package/dist/client.d.ts +86 -6
- package/dist/client.js +137 -9
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js.map +1 -1
- package/dist/method-layouts.d.ts +164 -0
- package/dist/method-layouts.js +115 -0
- package/dist/method-layouts.js.map +1 -0
- package/dist/models.d.ts +27 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +3 -1
- package/docs/errors.md +2 -0
- package/docs/method-layouts.md +133 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,6 +178,25 @@ 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
201
|
// the categories that have a template and each card's thumbnails behind short-lived links. A
|
|
183
202
|
// category's `labels` name it by language tag and may lack a language, in which case a client
|
|
@@ -302,6 +321,7 @@ These pages ship inside the published package, so a reader who has only installe
|
|
|
302
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 |
|
|
303
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 |
|
|
304
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` |
|
|
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 |
|
|
305
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 |
|
|
306
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 |
|
|
307
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 |
|
package/dist/cli/script.d.ts
CHANGED
|
@@ -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.
|
|
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,5 +1,6 @@
|
|
|
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
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";
|
|
@@ -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
|
-
/**
|
|
195
|
-
|
|
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`, `
|
|
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
|
|
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
|
|
@@ -1372,6 +1407,51 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
1372
1407
|
* one, which names no tree to serve.
|
|
1373
1408
|
*/
|
|
1374
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>;
|
|
1375
1455
|
/**
|
|
1376
1456
|
* Copy a method into an organization the caller may change methods in —
|
|
1377
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,
|
|
378
|
-
*
|
|
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`, `
|
|
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
|
|
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
|
//
|
|
@@ -2635,6 +2656,66 @@ export class PipelexApiClient {
|
|
|
2635
2656
|
const res = await this.requestProductAnswer("GET", endpoint, undefined);
|
|
2636
2657
|
return this.readSavedTree("GET", endpoint, withLinks(res), "a published version");
|
|
2637
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
|
+
}
|
|
2638
2719
|
/**
|
|
2639
2720
|
* Copy a method into an organization the caller may change methods in —
|
|
2640
2721
|
* `POST /v1/methods/{id}/copy`: the method's own organization for a duplicate, another team, or
|
|
@@ -4402,13 +4483,14 @@ const MAX_IDEMPOTENCY_KEY_LENGTH = 255;
|
|
|
4402
4483
|
*/
|
|
4403
4484
|
const VISIBLE_ASCII = /^[\x21-\x7E]+$/;
|
|
4404
4485
|
/**
|
|
4405
|
-
* The idempotency key a start, a method copy, a secret rotation or a delivery
|
|
4486
|
+
* The idempotency key a start, a method copy, a layout write, a secret rotation or a delivery
|
|
4487
|
+
* replay sends, or
|
|
4406
4488
|
* `undefined` when the options carry none (absent or `null`). A key that is not a string of 1 to
|
|
4407
4489
|
* 255 visible ASCII characters is refused with a `RequestArgumentError` rather than dropped, an
|
|
4408
4490
|
* empty one included, since a request sent without the key its caller gave would lose the
|
|
4409
4491
|
* protection the caller asked for without a word. `request` says what the request does, as the
|
|
4410
|
-
* refusal's last sentence names it ("start the run", "copy the method", "
|
|
4411
|
-
* "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
|
|
4412
4494
|
* something private. The Python SDK holds a key to the same rule, in the same words.
|
|
4413
4495
|
*/
|
|
4414
4496
|
function idempotencyKeyOf(options, request = "start the run") {
|
|
@@ -4771,6 +4853,52 @@ function withVerdictItems(answer) {
|
|
|
4771
4853
|
read.warnings = readProblemItems(answer.warnings);
|
|
4772
4854
|
return read;
|
|
4773
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
|
+
}
|
|
4774
4902
|
/**
|
|
4775
4903
|
* A run record with its `error` kept only when it is a problem document, and read as `null`
|
|
4776
4904
|
* otherwise. Only when the record carries the `error` key, so a server that does not serve it
|