@pipelex/sdk 0.42.0 → 0.43.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 +17 -0
- package/dist/cli/present.d.ts +3 -2
- package/dist/cli/present.js +5 -4
- package/dist/cli/present.js.map +1 -1
- package/dist/cli/script.d.ts +1 -1
- package/dist/client.d.ts +176 -11
- package/dist/client.js +523 -102
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -2
- package/dist/exact-json.d.ts +64 -0
- package/dist/exact-json.js +127 -0
- package/dist/exact-json.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/product-models.d.ts +136 -2
- package/dist/product-models.js +1 -1
- package/dist/runs.d.ts +3 -1
- package/dist/runs.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +4 -1
- package/docs/cli.md +1 -1
- package/docs/errors.md +4 -3
- package/docs/exact-integers.md +32 -0
- package/docs/method-examples.md +89 -0
- package/docs/run-results.md +2 -0
- package/package.json +1 -1
package/dist/product-models.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pipelex-product wire models — the snake_case JSON shapes the hosted-product
|
|
3
|
-
* routes (`/v1/me`, `/v1/methods
|
|
3
|
+
* routes (`/v1/me`, `/v1/methods` and its examples, `/v1/organizations`, `/v1/billing/*`,
|
|
4
4
|
* `/v1/pipelex-api-keys`, `/v1/onboarding/submit`,
|
|
5
5
|
* `/v1/resolve-storage-url`, `/v1/upload`, `/v1/upload/grant`, `/v1/runs`) speak.
|
|
6
6
|
*
|
|
@@ -342,6 +342,135 @@ export interface MethodVersionPage {
|
|
|
342
342
|
items: MethodVersionSummary[];
|
|
343
343
|
nextCursor: string | null;
|
|
344
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* One saved example of a method — what `createMethodExample`, `getMethodExample`,
|
|
347
|
+
* `updateMethodExample` and both arms of `recordMethodExampleLatestRun` answer, and a row of
|
|
348
|
+
* `listMethodExamples`.
|
|
349
|
+
*/
|
|
350
|
+
export interface MethodExample {
|
|
351
|
+
/** The example's id, `ex_…`, minted by the platform. */
|
|
352
|
+
example_id: string;
|
|
353
|
+
/** The bare id of the method the example belongs to. */
|
|
354
|
+
method_id: string;
|
|
355
|
+
/** The example's name, 1 to 200 characters and not blank. */
|
|
356
|
+
name: string;
|
|
357
|
+
/**
|
|
358
|
+
* The inputs a run of the example sends, a JSON object, which the platform stores and answers
|
|
359
|
+
* exactly as they were saved. **An integer past `Number.MAX_SAFE_INTEGER`, in either
|
|
360
|
+
* direction and at any depth, reads as a `bigint`** holding its exact digits, since a double
|
|
361
|
+
* would round it; the client sends a `bigint` back as those digits, to `updateMethodExample` or
|
|
362
|
+
* as a run's inputs, so such inputs go back unchanged. `JSON.stringify` throws on a `bigint`:
|
|
363
|
+
* show the inputs as JSON text with `stringifyExactJson`, and read edited text back with
|
|
364
|
+
* `parseExactJson`. Every other number reads as a double, so a float with no fractional part,
|
|
365
|
+
* `3.0`, reads as `3` and goes back as `3`; the platform compares inputs as canonical JSON, so
|
|
366
|
+
* inputs holding one, sent back unchanged to `updateMethodExample`, raise `input_revision` and
|
|
367
|
+
* clear `latest_run_id`. Inputs saved from JavaScript never hold one, since JavaScript cannot
|
|
368
|
+
* write it; inputs another client saved can.
|
|
369
|
+
*/
|
|
370
|
+
inputs: Record<string, unknown>;
|
|
371
|
+
/**
|
|
372
|
+
* Starts at 1 and rises by one each time the inputs change. Send it, as read when the run
|
|
373
|
+
* was started, to `recordMethodExampleLatestRun`.
|
|
374
|
+
*/
|
|
375
|
+
input_revision: number;
|
|
376
|
+
/**
|
|
377
|
+
* The latest run of the current inputs, `null` when none was recorded since they last
|
|
378
|
+
* changed. It may name a run deleted since, which then reads as not found.
|
|
379
|
+
*/
|
|
380
|
+
latest_run_id: string | null;
|
|
381
|
+
/** The canonical id of the member who saved the example; it confers nothing. */
|
|
382
|
+
created_by_user_id: string;
|
|
383
|
+
/** ISO-8601 UTC instant of the example's creation. */
|
|
384
|
+
created_at: string;
|
|
385
|
+
/** ISO-8601 UTC instant of its last change: its name, its inputs or its latest run. */
|
|
386
|
+
updated_at: string;
|
|
387
|
+
}
|
|
388
|
+
/** The create — `createMethodExample`, `POST /v1/methods/{id}/examples`. */
|
|
389
|
+
export interface MethodExampleCreateInput {
|
|
390
|
+
/** The example's name, 1 to 200 characters and not blank. */
|
|
391
|
+
name: string;
|
|
392
|
+
/**
|
|
393
|
+
* The inputs a run of the example sends, a JSON object; a `bigint` in them is sent as its
|
|
394
|
+
* exact digits.
|
|
395
|
+
*/
|
|
396
|
+
inputs: Record<string, unknown>;
|
|
397
|
+
/**
|
|
398
|
+
* A run of this method that used exactly these inputs, recorded as the example's latest run
|
|
399
|
+
* at revision 1 — the run a person saves as an example. Omitted, or `null`, the example has no
|
|
400
|
+
* latest run; an empty string is refused before anything is sent.
|
|
401
|
+
*/
|
|
402
|
+
run_id?: string | null;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* The edit — `updateMethodExample`, `PATCH /v1/methods/{id}/examples/{example_id}`: a new name,
|
|
406
|
+
* new inputs, or both, and at least one of them. An omitted field keeps the stored value.
|
|
407
|
+
*/
|
|
408
|
+
export interface MethodExampleUpdateInput {
|
|
409
|
+
/** The example's new name, 1 to 200 characters and not blank. */
|
|
410
|
+
name?: string;
|
|
411
|
+
/**
|
|
412
|
+
* The example's new inputs. Inputs that differ from the stored ones raise `input_revision`
|
|
413
|
+
* by one and clear `latest_run_id`; the same inputs, their keys in another order included,
|
|
414
|
+
* change nothing. A `bigint` in them is sent as its exact digits. Send them only when they
|
|
415
|
+
* changed: inputs read back that held `3.0` differ once JavaScript has read them, as `3` (see
|
|
416
|
+
* `MethodExample.inputs`).
|
|
417
|
+
*/
|
|
418
|
+
inputs?: Record<string, unknown>;
|
|
419
|
+
}
|
|
420
|
+
/** The latest-run write — `recordMethodExampleLatestRun`. */
|
|
421
|
+
export interface MethodExampleLatestRunInput {
|
|
422
|
+
/** A run of this method that used the example's inputs. */
|
|
423
|
+
run_id: string;
|
|
424
|
+
/**
|
|
425
|
+
* The example's `input_revision` whose inputs the run used, as the caller read it when it
|
|
426
|
+
* started the run. The run is recorded only while the example still holds that revision.
|
|
427
|
+
*/
|
|
428
|
+
input_revision: number;
|
|
429
|
+
}
|
|
430
|
+
/** What a latest-run write did — the discriminant of `MethodExampleLatestRunResult`. */
|
|
431
|
+
export type MethodExampleLatestRunOutcome = "recorded" | "stale";
|
|
432
|
+
/** The run is now the example's latest; `example` is the example as it now stands. */
|
|
433
|
+
export interface MethodExampleLatestRunRecorded {
|
|
434
|
+
outcome: "recorded";
|
|
435
|
+
example: MethodExample;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* The run used inputs the example no longer holds, and nothing was written; `example` is the
|
|
439
|
+
* example as it stands, with its current revision and latest run.
|
|
440
|
+
*/
|
|
441
|
+
export interface MethodExampleLatestRunStale {
|
|
442
|
+
outcome: "stale";
|
|
443
|
+
example: MethodExample;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* The answer of `recordMethodExampleLatestRun`, a `200` discriminated on `outcome`. A stale run
|
|
447
|
+
* is the expected end of a run that outlasted an edit of the example's inputs, a verdict about
|
|
448
|
+
* the run rather than a failed request, so it is answered rather than thrown. Branch on
|
|
449
|
+
* `outcome`, never on the status: an unknown example, a run that is not the method's or a
|
|
450
|
+
* method being deleted are thrown as `ApiResponseError` instead.
|
|
451
|
+
*/
|
|
452
|
+
export type MethodExampleLatestRunResult = MethodExampleLatestRunRecorded | MethodExampleLatestRunStale;
|
|
453
|
+
/** Query for one page of a method's examples. */
|
|
454
|
+
export interface ListMethodExamplesQuery {
|
|
455
|
+
/**
|
|
456
|
+
* Page size, from 1 to 100; the API defaults to 50 and refuses a `limit` outside that range
|
|
457
|
+
* with a `422`.
|
|
458
|
+
*/
|
|
459
|
+
limit?: number;
|
|
460
|
+
/** Opaque `nextCursor` from the previous page. */
|
|
461
|
+
cursor?: string;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* One page of a method's examples, oldest first, inputs included.
|
|
465
|
+
*
|
|
466
|
+
* `nextCursor` is opaque — pass it straight back to `listMethodExamples` to continue; `null`
|
|
467
|
+
* means this was the last page. Examples are read whole on the server, and a read stops at
|
|
468
|
+
* 1 MB, so a page may be short while `nextCursor` is set; `iterateMethodExamples` follows it.
|
|
469
|
+
*/
|
|
470
|
+
export interface MethodExamplePage {
|
|
471
|
+
items: MethodExample[];
|
|
472
|
+
nextCursor: string | null;
|
|
473
|
+
}
|
|
345
474
|
export interface Membership {
|
|
346
475
|
org_id: string;
|
|
347
476
|
/** Null for the implicit personal org (no backing WorkOS organization). */
|
|
@@ -574,7 +703,12 @@ export interface RunDetail extends PipelineRun {
|
|
|
574
703
|
* the protocol's validate call takes and echoes back. A single-file method
|
|
575
704
|
* is an array of one. */
|
|
576
705
|
mthds_contents?: string[] | null;
|
|
577
|
-
/**
|
|
706
|
+
/**
|
|
707
|
+
* The inputs the run was started with, as sent, an integer past `Number.MAX_SAFE_INTEGER` read
|
|
708
|
+
* as a `bigint` holding its exact digits, so the inputs passed back to `start` run what the run
|
|
709
|
+
* ran. Show them as JSON text with `stringifyExactJson`, since `JSON.stringify` throws on a
|
|
710
|
+
* `bigint`.
|
|
711
|
+
*/
|
|
578
712
|
inputs?: Record<string, unknown> | null;
|
|
579
713
|
/**
|
|
580
714
|
* The id of the request that started the run — the one to quote to support, and the one the
|
package/dist/product-models.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pipelex-product wire models — the snake_case JSON shapes the hosted-product
|
|
3
|
-
* routes (`/v1/me`, `/v1/methods
|
|
3
|
+
* routes (`/v1/me`, `/v1/methods` and its examples, `/v1/organizations`, `/v1/billing/*`,
|
|
4
4
|
* `/v1/pipelex-api-keys`, `/v1/onboarding/submit`,
|
|
5
5
|
* `/v1/resolve-storage-url`, `/v1/upload`, `/v1/upload/grant`, `/v1/runs`) speak.
|
|
6
6
|
*
|
package/dist/runs.d.ts
CHANGED
|
@@ -289,7 +289,9 @@ export interface RunResults {
|
|
|
289
289
|
* a multiple output the envelope `{ items: [...] }` the runtime's `ListContent` serialises to,
|
|
290
290
|
* and a native is wrapped too (`{ text }`, `{ number }`). It may be a valid empty value — an
|
|
291
291
|
* empty `items`, an empty `text` — but it is never null. It is absent only from a read whose
|
|
292
|
-
* `artifacts` selection left it out.
|
|
292
|
+
* `artifacts` selection left it out. An integer past `Number.MAX_SAFE_INTEGER` in it, as anywhere
|
|
293
|
+
* in the results, is a `bigint` holding its exact digits (`docs/exact-integers.md`). See
|
|
294
|
+
* `docs/run-results.md`.
|
|
293
295
|
*/
|
|
294
296
|
main_stuff?: unknown;
|
|
295
297
|
/**
|
package/dist/runs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../src/runs.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../src/runs.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/docs/architecture.md
CHANGED
|
@@ -44,6 +44,7 @@ The map has a second entry, **`@pipelex/sdk/upload`** (`dist/upload-grant.js`),
|
|
|
44
44
|
|
|
45
45
|
- Base URL from env (default `https://api.pipelex.com`); every route under `/v1/*`.
|
|
46
46
|
- Auth: `Authorization: Bearer <token>`; organization resolved from the JWT `org_id` claim, not a header.
|
|
47
|
+
- A caller's id enters a route's path through one guard, `idPathSegment` in `client.ts`, as one percent-encoded segment: a method's, an example's, a run's, an organization's or an API key's. `encodeURIComponent` leaves the dot alone, and the URL resolves a dot segment before the request leaves, so an id of `.` or `..` would address another route (an example id `..` deleting the method itself), and an empty one the route above it. Each is a `RequestArgumentError` before anything is sent, as is an id that is not a string and one holding a lone surrogate, which has no UTF-8 spelling and so no percent-encoding. The non-empty check is `requireNonEmptyId`, which the run ids an example write records go through too.
|
|
47
48
|
- A diagnostic verdict (e.g. `/v1/validate`) is a `200` discriminated on a body field (`is_valid`); non-2xx is reserved for "no verdict could be produced" (request-shape `422`, auth `401`/`403`, server `5xx`), carried as RFC 7807 `problem+json` and mapped to typed product errors.
|
|
48
49
|
|
|
49
50
|
### Standard artifacts on the validate report
|
|
@@ -104,10 +105,11 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
|
|
|
104
105
|
- `error-verdicts.ts` — `fallbackVerdict`, the table an `ApiResponseError` falls back to for a member the server did not send: the HTTP status, the platform `code`, and whether the body is named, which decides a `404`. Internal and pure. `pipelex-sdk` (Python) must apply the same table: `tests/fixtures/error-verdicts.json`, the case file this package's suite drives, is the contract, and the Python suite drives its own code through the identical copy `make shared-files` writes.
|
|
105
106
|
- `run-may-have-started.ts` — the **pure** reading of whether a failed request that may create a run may still have started one: `answerMayHaveStartedRun(status, code)` for an answer and `transportMayHaveStartedRun(cause)` for a transport failure, every failure counting unless it proves otherwise (a failure of the connection's set-up, a `501`, the platform's `503` coded `pipeline_start_unavailable`, or any status that refuses the request). The client marks the requests that may create a run, `start` and the blocking `execute`, with a `RunCreation` flag it threads through `requestRaw`, `readObjectAnswer` and its error helpers into the constructors of `ApiUnreachableError` and `ApiResponseError`, which apply the reading and carry the answer as `runMayHaveStarted`, forcing `retryable` false when it is true; nothing changes an error after it is thrown. The Python twin is `pipelex_sdk/run_may_have_started.py`. See [errors.md](./errors.md#a-failed-run-start--runmayhavestarted).
|
|
106
107
|
- `user-agent.ts` — client identification per the spec `conformance/specs/client-identification.md`, in the `conformance` repository beside the specs' tests: the exported `AppInfo` option, its RFC 9110 token validation (a `TypeError` on a bad field, a `RangeError` past the 512-character ceiling), its rendering, the runtime detection (Node, Bun, Deno, or a browser, where no header is set) and `buildUserAgent`, which the client calls once at construction. The barrel re-exports `buildUserAgent`, `validateAppInfo`, `MAX_USER_AGENT_LENGTH` and the `RuntimeInfo` type, so a consumer's raw fetches send the same value. The client's private `requestHeaders` is the one place API request headers are built, so every route carries the value; the artifact fetch of a presigned link never goes through it. See [client-identification.md](./client-identification.md).
|
|
108
|
+
- `exact-json.ts` — `parseExactJson` and `stringifyExactJson`, JSON whose integers stay exact, and `ExactJsonUnsupportedError`. The reader, which the client applies to every answer carrying a person's data (a method example, a run's record, a run's results, the blocking execute's answer), is one `JSON.parse` call whose reviver turns a number past `Number.MAX_SAFE_INTEGER` spelled as an integer into a `bigint` of its source text (`context.source`), so it reads everything else exactly as `JSON.parse` does, with no second parser to keep in step; a text holding no run of 16 digits is parsed with no reviver at all. On a runtime whose reviver gets no source text, it refuses a whole number past the safe range with `ExactJsonUnsupportedError`, which the client reports as an answer it cannot read, rather than rounding it. The writer, which `requestRaw` applies to every request body before the request is made, is one `JSON.stringify` call whose replacer turns each `bigint` into `JSON.rawJSON` text, so every getter and `toJSON` in the body runs once, and a `bigint` an inherited `toJSON` such as the `BigInt.prototype.toJSON` polyfill already turned into a string is looked up in its holder and written as its digits; a runtime without `JSON.rawJSON` refuses the `bigint`, as a `RequestArgumentError` from the client, rather than rounding it. The barrel exports all three, for a caller showing such data as JSON text; the error declares its verdict itself, `config` and not retryable, as `CodegenLockError` does. It imports nothing but the `ErrorDomain` type. See [exact-integers.md](./exact-integers.md).
|
|
107
109
|
- `timers.ts` — `MAX_TIMER_DELAY_MS`, the longest delay `setTimeout` honours, and `isTimerDelay`, which every caller-supplied `timeoutMs` is checked against before it reaches a timer: a longer delay overflows and fires at once. It imports nothing, so the browser-safe upload entry uses it too.
|
|
108
110
|
- `version.ts` — `SDK_VERSION`, in its own module so the client stamps it on the `User-Agent` without importing the barrel, which re-exports it.
|
|
109
111
|
- `cli.ts` and `cli/` — the `pipelex-sdk` command, `run` and `script` ([`cli.md`](cli.md)): `cli.ts` is the executable `package.json` declares as the package's one `bin`, handing the process to `cli/main.ts`'s `runCommand`, and `cli/` holds the flags, the bundle reader, the inputs, the environment, the two subcommands and what they print. It sits at the TOP of the graph, the other way round from every other module: nothing imports it, the barrel does not export it, and it reaches the SDK only through `index.ts`, which `dependency-cruiser` enforces in both directions.
|
|
110
|
-
- `index.ts` — the public barrel: `export * from "mthds/protocol"` (single import source for the standard surface) plus the client, models, run types, the standalone pure helpers (`runCodegenCheck`, `summarizeUsage`, `locateArtifacts`, `collectArtifacts`, `artifactFilename`), and errors.
|
|
112
|
+
- `index.ts` — the public barrel: `export * from "mthds/protocol"` (single import source for the standard surface) plus the client, models, run types, the standalone pure helpers (`runCodegenCheck`, `summarizeUsage`, `locateArtifacts`, `collectArtifacts`, `artifactFilename`, `parseExactJson`, `stringifyExactJson`), and errors.
|
|
111
113
|
|
|
112
114
|
## Client surface (current)
|
|
113
115
|
|
|
@@ -130,6 +132,7 @@ The hosted catalog/account routes the webapp drives. All ride the same `{base}/v
|
|
|
130
132
|
|
|
131
133
|
- **User profile:** `getMe` (`GET /v1/me`).
|
|
132
134
|
- **Methods catalog:** `listMethods`, `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod`, `listMethodVersions`, `getMethodVersion`, `deleteMethod`. **A saved method has a draft and immutable published versions.** Its content fields (`mthds`, `python`, `input_data`) are its draft, which `writeDraft` (`PUT /v1/methods/{id}/draft`) replaces without validating it; `updated_at` is the draft's token, which moves on every draft write and nothing else, and `draft_digest` the SHA-256 of its two file sets. A draft write may carry `expected_updated_at`, the token the caller last saw, and is then a compare-and-swap: a draft that moved since is a `409` `method_update_conflict` and nothing is written. `renameMethod` (`PATCH /v1/methods/{id}`) changes the name alone and moves no token. `publishMethod` (`POST /v1/methods/{id}/publish`) takes the required `expected_draft_updated_at` and answers a `MethodPublishResult` discriminated on `outcome` — `published` with the new version, `unchanged` with the existing latest version when the draft's digest equals it, `refused` with a `reason` (`invalid` or `not_runnable`), a `message` and the runner's `validation` verdict — every arm carrying the `method`; the SDK reads the nested method as `getMethod` does, and an answer whose `outcome` is none of these is an answer it cannot read. A publish that produced no verdict is thrown rather than answered: only a draft that differs from the latest version reaches the runner, and a runner that cannot be reached, answers unusably or does not finish within the platform's deadline is a `502` or `503` with nothing written, so a retry is safe, and a retry of a publish that landed while its answer was lost answers `unchanged` with the version it wrote. A method is stored as one item that keeps room for the summary a publish adds, so a draft that saves can always be published: a creation or a draft write is a `413` `payload_too_large` when it would grow the method past that room, below the store's item limit, and a rename is the same `413` for a name so long it would leave the method too large to publish. A version is numbered from 1, never reused, and holds the draft's files as they stood, its `source_digest`, its `crate_fingerprint` (`null` when the runner could not resolve the bundle in memory), the runner's version, and when and by whom it was published; `listMethodVersions` pages the summaries newest first (`MethodVersionPage`, `{items, nextCursor}`, a `limit` from 1 to 100, 20 by default, and a `422` outside that range), and `getMethodVersion` reads one with its sources, its `python` parsed as a method's is. A method read carries `latest_version` and `latest_published`, both `null` until the first publish, so the publish state is derived: never published, draft unchanged (`draft_digest === latest_published.source_digest`), or draft ahead. The method routes take a bare id and address the method itself, never one of its versions; the platform does not parse a suffix there, so `mt_x@3` would answer a `404` that reads as a method that does not exist, and each route refuses a suffixed id with `RequestArgumentError` before anything is sent, naming `parseMethodSelector` and `getMethodVersion`. The whole-method `updateMethod` is gone, replaced by the draft write and the rename. **`deleteMethod` is asynchronous and its return type says so:** `DELETE /v1/methods/{id}` answers `202` with a `MethodDeletionAccepted` (`method_id`, `deletion_state`, `deletion_job_id`) the moment the platform has claimed the method and terminated its in-flight workflows — the rest of the cascade (runs, events, S3 objects) is enqueued. A resolved promise means "accepted", never "gone"; completion is the row disappearing from `listMethods`, not any field of that body. The claim is a conditional write, so a double-clicked delete is a `409 method_being_deleted` rather than a second cascade over the same runs, and an unknown or foreign-org id is a `404`. The erasure deletes the method's versions with the rest. (This doc previously stated the SDK had no `delete` because the platform had no such route — the route has always existed; the claim was simply false.) `listMethods` returns **one page**, not the whole catalog — a `MethodPage` (`{items, nextCursor}`) served from a narrow index projection, so its cost is the page size rather than the size of the catalog. It used to return every method with its full `.mthds` bundle attached, which hit DynamoDB's 1 MB page cap at a couple hundred methods and came back truncated with no error and no flag, so methods silently vanished from the UI. Rows are therefore `MethodSummary`, **not** `MethodData`: no `mthds`, no `python`, no `updated_at`, because none of them is in the projection and putting `mthds` back is what restored the truncation. Reach for `getMethod(id)` when you need a method's source. `query.q` is applied server-side across the whole catalog, not over the page — filtering one page client-side would be searching 50 rows of 10,000 and calling it a search. `query.published` filters the same way, sent as `published=true` (the methods with a published version) or `published=false` (those never published), and combines with `q`. Each row carries the method's publish state, so a list tells its methods apart without reading each one: `latest_version` and `latest_published` (a `MethodVersionSummary`), both `null` when the method was never published, and `draft_digest`, which is `null` on a row that never stored one until its next draft write, unlike on `MethodData`, because the list reads an index that holds no bundle to digest; all three are optional, absent from a platform that does not serve them. Never published is `latest_version === null`, the draft unchanged is `draft_digest === latest_published.source_digest`, and the draft ahead is two digests that differ. Both filters run after the index read, so **a filtered page can come back empty and still carry a `nextCursor`**: an empty page is not the end of the list, and only a `null` cursor is. `iterateMethods` is the async iterator that follows the cursor for callers who genuinely want everything (`for await (const m of client.iterateMethods())`); it keeps going **past empty pages**, since `{items: [], nextCursor: "…"}` means "nothing matched in the slice I just read, keep going". A server that keeps minting fresh cursors forever stops it at a page ceiling far past any real catalog with a `PagingNotTerminatingError` carrying `pageLimit`, never a silently truncated stream; `iterateRuns` shares the same loop. It gives up only when the server stops advancing its cursor, and **throws** rather than returning a partial answer; like `iterateRuns` it is deliberately not a `listAllMethods(): Promise<MethodSummary[]>`, because an all-at-once helper needs a page cap and a cap means silently returning a truncated list. `MethodSummary.deletion_state` rides the list rows: a method mid-erasure stays listed so the UI can render it as "Deleting…", while `getMethod` refuses it with a 409. Full read responses (`MethodData`, from `getMethod`) carry `org_id`, `created_by_user_id`, a server-derived `description` (recomputed from the draft's top-level `description` on every draft write), the draft token and digest and the latest version's summary on top of the write contract's `name` / `mthds` / `input_data`. `getMethodClosure` is a client-side semantic layer over `getMethod` (not a route) that parses the draft's polymorphic `mthds` source into a runnable, provenance-labelled closure — the draft, not what a bare id runs — see [input-preparation.md](./input-preparation.md#getmethodclosure--the-explicit-expansion-utility).
|
|
135
|
+
- **Method examples:** `createMethodExample`, `listMethodExamples`, `iterateMethodExamples`, `getMethodExample`, `updateMethodExample`, `recordMethodExampleLatestRun`, `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; one helper, `readVerdict`, reads this verdict 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).
|
|
133
136
|
- **Organizations:** `listMemberships`, `createOrganization`, `renameOrganization` (`PATCH`). The org "switch" is a WorkOS-session op (`/api/active-org`), not a `/v1` route — out of SDK scope.
|
|
134
137
|
- **Billing:** `getSubscription`, `listPlans`, `listInvoices`, `createCheckout`, `changePlan` (409 `conflict` ⇒ no subscription), `getBillingPortal` (409 `conflict` ⇒ no subscription).
|
|
135
138
|
- **Pipelex API keys (`plx_sk_…`):** `listPipelexApiKeys`, `createPipelexApiKey` (409 `pipelex_api_key_limit_reached`), `revokePipelexApiKey`, `rotatePipelexApiKey`. The plaintext `api_key` is returned once, on create/rotate.
|
package/docs/cli.md
CHANGED
|
@@ -26,7 +26,7 @@ pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> |
|
|
|
26
26
|
|
|
27
27
|
The run goes through the SDK's `startAndWaitForResult`: on the hosted API a durable start, then polls until the run ends, however long it takes, and on a bare runner the blocking execute. Its `onStarted` callback prints `Run started: <id>` on stderr as soon as the run exists ([`run-results.md`](run-results.md)). The poll reads only the `main_stuff` artifact.
|
|
28
28
|
|
|
29
|
-
**stdout carries the run's main output and nothing else**, as indented JSON (two spaces), followed by a newline: exactly what `JSON.stringify(value, null, 2)` prints, non-ASCII characters as they are. It is printed as the method produced it, unvalidated and unrepaired: a list output arrives either bare or wrapped as `{"items": […]}` depending on the runtime path, and is printed as it arrived. An output the method left absent, which the run delivers as the runtime's absence document (matched by its whole shape, as `cli-python` matches it) or as nothing, prints `null`, and stderr says `The method left its output "<name>" absent (<reason>), so the result is null.` A produced file is not downloaded: its short-lived signed URL is in the printed JSON. A reader that stops early and closes either stream, as `| head` or `2>&1 | head -1` does, ends nothing: the lines it no longer reads are dropped, and the command goes on waiting for the run it started and exits as it would have.
|
|
29
|
+
**stdout carries the run's main output and nothing else**, as indented JSON (two spaces), followed by a newline: exactly what `JSON.stringify(value, null, 2)` prints, non-ASCII characters as they are, except that an integer past `Number.MAX_SAFE_INTEGER`, which the client reads exactly, is printed with its exact digits, as the Python command prints it ([exact-integers.md](./exact-integers.md)). It is printed as the method produced it, unvalidated and unrepaired: a list output arrives either bare or wrapped as `{"items": […]}` depending on the runtime path, and is printed as it arrived. An output the method left absent, which the run delivers as the runtime's absence document (matched by its whole shape, as `cli-python` matches it) or as nothing, prints `null`, and stderr says `The method left its output "<name>" absent (<reason>), so the result is null.` A produced file is not downloaded: its short-lived signed URL is in the printed JSON. A reader that stops early and closes either stream, as `| head` or `2>&1 | head -1` does, ends nothing: the lines it no longer reads are dropped, and the command goes on waiting for the run it started and exits as it would have.
|
|
30
30
|
|
|
31
31
|
## `script`
|
|
32
32
|
|
package/docs/errors.md
CHANGED
|
@@ -43,14 +43,14 @@ On an error from a request that may have started a run, `retryable` is `false` w
|
|
|
43
43
|
| Class | `errorDomain` | `retryable` | Why |
|
|
44
44
|
|---|---|---|---|
|
|
45
45
|
| `ApiResponseError` | the server's, else the fallback | the server's, else no for a sent `input` or `config` domain, else the fallback | See [below](#a-refused-requests-verdict). |
|
|
46
|
-
| `RequestArgumentError` | `input` | no | The client refused a call's arguments before sending any request: no run source given to `execute` or `start`, run sources or method selectors that exclude each other, a selector rule of `validate`, an empty `validateFiles`, a reserved key in `extra`, a `publishMethod` without its token, a `getMethodVersion` version that is not a positive integer, a selector `parseMethodSelector` refuses, an artifact selection that is empty or names an unknown artifact,
|
|
46
|
+
| `RequestArgumentError` | `input` | no | The client refused a call's arguments before sending any request: no run source given to `execute` or `start`, run sources or method selectors that exclude each other, a selector rule of `validate`, an empty `validateFiles`, a reserved key in `extra`, a `publishMethod` without its token, a `getMethodVersion` version that is not a positive integer, an id a route puts in its path (a method's, an example's, a run's, an organization's or an API key's) that is empty, `.` or `..` or is not a string, since the URL would resolve it into another route's path, or that holds a lone surrogate, which a URL cannot carry, a `createMethodExample` without its `name` or `inputs` or whose `run_id` is given but empty, an `updateMethodExample` naming neither `name` nor `inputs`, a `recordMethodExampleLatestRun` without a run id or a positive integer `input_revision`, a selector `parseMethodSelector` refuses, an artifact selection that is empty or names an unknown artifact, a start's `idempotencyKey` that is not 1 to 255 visible ASCII characters ([below](#a-starts-idempotency-key--idempotencykey)), or a request body JSON cannot carry, such as one holding a cycle, or a `bigint` on a runtime without `JSON.rawJSON` (the body is written with `stringifyExactJson`, which writes a `bigint` as its exact digits; see [exact-integers.md](./exact-integers.md)), with the failure as `cause`. Its options take an optional `verdict`, and the client declares `config` for a base URL that is not host-only, since it typically comes from `PIPELEX_BASE_URL`. That refusal shows the base URL's scheme and host alone and names the other parts it carried (credentials, a path, a query, a fragment) without their text, since those are where a secret travels in a URL; a value that is not an http or https URL is not shown at all. It is a `PipelineRequestError`, as the bare refusal it replaced was, and a refusal the standard's own run-source check raises keeps that original as `cause`. |
|
|
47
47
|
| `ApiUnreachableError` | `config` | yes | The address or the network must be checked, and a later attempt can get through. With the code `ABORT_TIMEOUT`, the SDK's own request timeout, it is `runtime`: the API took the request and did not answer in time. |
|
|
48
48
|
| `PipelineExecuteTimeoutError` | `input` | no | The blocking `execute` cannot outlast the gateway's limit; start the run and poll instead. The run goes on, so its `runMayHaveStarted` is always `true`. |
|
|
49
49
|
| `RunFailedError` | the report's | the report's | See [a failed run](#a-failed-run--runerrorreport). |
|
|
50
50
|
| `RunTimeoutError`, `RunStillRunningError` | `runtime` | yes | The run is still going; waiting again, by its id, can succeed. |
|
|
51
51
|
| `RunLifecycleUnavailableError` | `config` | no | The base URL points at a bare runner without the run lifecycle. |
|
|
52
52
|
| `MissingMainStuffError` | `runtime` | no | The API broke its own contract on a completed run. |
|
|
53
|
-
| `PagingNotTerminatingError` | `runtime` | no | A server minting cursors forever; `pageLimit` is the ceiling `iterateMethods` or `
|
|
53
|
+
| `PagingNotTerminatingError` | `runtime` | no | A server minting cursors forever; `pageLimit` is the ceiling `iterateMethods`, `iterateRuns` or `iterateMethodExamples` stopped at. |
|
|
54
54
|
| `InputPreparationError` and `EmptyMethodSourceError`, `InvalidLocalSourceError`, `InvalidInputValueError`, `UploadNotAllowedError`, `MethodLoadError`, `RejectedAssetError` | `input` | no | The asset, the inputs or the method selector must change. The family's base takes an optional `verdict` in its options, so a subclass of your own can declare another; `prepareInputs` declares `runtime` for a pipe I/O answer that breaks the route's contract: no boolean `is_valid`, an `input_form` that does not describe the pipe it selected, or a descriptor that is malformed or names a kind this SDK does not know. |
|
|
55
55
|
| `UnsupportedUploadCapabilityError`, `UploadAuthenticationError` | `config` | no | The deployment or the credential must change. |
|
|
56
56
|
| `UploadTransportError` | the cause's, else by `code` | the cause's, else by `code` | `uploadFile` wraps the `ApiResponseError` or `ApiUnreachableError` the client threw, and takes its verdict, so a wrapped `402` is `config` and not retryable. Without such a cause, which is always the case from `uploadWithGrant`: `timeout`, `storage_timeout` and `conflict` are `runtime` and retryable, `conflict` because storage documents its `409 ConditionalRequestConflict` as retryable and the grant is not spent by it (a spent grant is the `412` of a `RejectedAssetError` with code `grant_used`); `server_error` is `runtime`, and retryable when [the fallback table](#a-refused-requests-verdict) would call its `status` retryable, any `5xx` but the `501` storage answers for a request it does not implement, or when it carries no status; `unexpected` is `runtime`, and retryable when that table would call its `status` retryable, which is the `408` or `429` storage answers when it times out or throttles the request rather than refusing the file, and not retryable for any other status or none; `unreachable` is `config` and retryable; `redirected` is `config` and not retryable; `invalid_grant_url` is `runtime` and not retryable. |
|
|
@@ -58,6 +58,7 @@ On an error from a request that may have started a run, `retryable` is `false` w
|
|
|
58
58
|
| `ArtifactAuthenticationError` | `config` | no | The credential must change. Its `verdict` member is the download's result so far, not the error's. |
|
|
59
59
|
| `ArtifactFetchError` | by `code` | by `code` | `invalid_storage_uri`, `forbidden`, `unsupported_url`, `not_found` and `too_large` are `input` and not retryable; `plain_http_refused` is `config` and not retryable; `redirect_refused` and `store_refused` are `runtime` and not retryable; `timeout` and `network` are `runtime` and retryable; `store_error` is `runtime`, and retryable when [the fallback table](#a-refused-requests-verdict) would call its `status` retryable: a `408`, a `429`, or a `5xx` other than `501`. |
|
|
60
60
|
| `CodegenLockError` | `input` | no | The lock is the caller's own file. It extends `Error`, since nothing was requested over the wire, and declares the two members itself. |
|
|
61
|
+
| `ExactJsonUnsupportedError` | `config` | no | `parseExactJson` met a number past `Number.MAX_SAFE_INTEGER` on a runtime whose `JSON.parse` hands a reviver no source text, so it could only read it rounded; running on a runtime the package supports fixes it ([exact-integers.md](./exact-integers.md)). It extends `Error`, since nothing was requested, and declares the verdict's members itself. The client reports it as the `ApiResponseError` of an answer it cannot read, with this error as `cause`. |
|
|
61
62
|
|
|
62
63
|
### A refused request's verdict
|
|
63
64
|
|
|
@@ -258,7 +259,7 @@ So `caller_facing_message` is how a consumer tells the author's copy from the pl
|
|
|
258
259
|
|
|
259
260
|
Every non-2xx answer from a `/v1` route is a problem document, and the SDK throws an `ApiResponseError` whose fields are its members. Each field is `undefined` when the document did not carry it, and a member of the wrong type (a numeric `type`, a `retryable` of `"no"`, a `user_action` without a `detail`) reads as absent rather than as a wrong value. The nested members are checked one level down, exactly as a [stored report's](#a-failed-run--runerrorreport) are: `providerMetadata` and `migration` keep each named field only when it has its type, `validationErrors` keeps only the items that are objects with a string `category` and `message`, and each item of `errors` keeps its `field`, `code` and `detail` only when they are strings; members the SDK does not name are relayed as sent. The verdict is the exception: `errorDomain` and `retryable` are always decided, the server's value when it sent a valid one and otherwise [as the verdict section says](#a-refused-requests-verdict).
|
|
260
261
|
|
|
261
|
-
**An answer the SDK could not read is an `ApiResponseError` too.** A `2xx` whose body is not JSON, such as a gateway's HTML page or an empty body where the route answers one, is no result the SDK can hand back, so every route throws an `ApiResponseError` for it instead of letting the runtime's `SyntaxError` escape. So is a `2xx` whose body is JSON but not the object the route answers — `null`, a number, a string, an array — on every protocol, extension and run-lifecycle route, on `health()`, and on the product routes whose answer the SDK or its caller reads before anything else (`listMethods`, `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod`, `listMethodVersions`, `getMethodVersion`, `listRuns`, `getRunDetail`, `upload`, `requestUploadGrant`, `resolveStorageUrls`), instead of the `TypeError` that reading it would raise. So is an object missing the member those reads depend on: a page of `listMethods`, `listMethodVersions` or `listRuns` whose `items` is not an array or whose `next_cursor` is neither a string nor `null`, which
|
|
262
|
+
**An answer the SDK could not read is an `ApiResponseError` too.** A `2xx` whose body is not JSON, such as a gateway's HTML page or an empty body where the route answers one, is no result the SDK can hand back, so every route throws an `ApiResponseError` for it instead of letting the runtime's `SyntaxError` escape. So is a `2xx` read exactly, such as a run's results or a method example, that holds a number past `Number.MAX_SAFE_INTEGER` on a runtime whose `JSON.parse` hands a reviver no source text, which could only read it rounded; its `cause` is the reader's `ExactJsonUnsupportedError` ([exact-integers.md](./exact-integers.md)). So is a `2xx` whose body is JSON but not the object the route answers — `null`, a number, a string, an array — on every protocol, extension and run-lifecycle route, on `health()`, and on the product routes whose answer the SDK or its caller reads before anything else (`listMethods`, `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod`, `listMethodVersions`, `getMethodVersion`, the method example routes, `listRuns`, `getRunDetail`, `upload`, `requestUploadGrant`, `resolveStorageUrls`), instead of the `TypeError` that reading it would raise. So is an object missing the member those reads depend on: a page of `listMethods`, `listMethodVersions`, `listMethodExamples` or `listRuns` whose `items` is not an array or whose `next_cursor` is neither a string nor `null`, which the iterators could not walk or would follow forever; a publish result whose `outcome` is not `published`, `unchanged` or `refused`, or whose `method` is not an object, and a latest-run result whose `outcome` is not `recorded` or `stale`, or whose `example` is not an object, which a caller branching on `outcome` could not read; a method example, as `createMethodExample`, `getMethodExample` and `updateMethodExample` answer it, as a row of `listMethodExamples` and as a latest-run result's `example`, that misses a field the Python SDK's model of an example requires or holds one of another type, its message naming the field; an `upload` answer with no non-empty string `uri`, which names no stored file; and a stored method or a published version whose `mthds` field is neither a string nor absent (a `null` one is still no source, which `getMethodClosure` reports as `EmptyMethodSourceError`) or whose `python` field is not the serialized `[{ name, content }]` list, as `getMethod`, `createMethod`, `writeDraft`, `renameMethod`, `publishMethod` and `getMethodVersion` answer it: that is server data no change to the call fixes, not an argument the caller passed. Its `status` is the `2xx`, `responseBody` the raw text, `cause` what made the answer unreadable (the parse failure, or `mthds`'s refusal of the `python` field; nothing for a body that parsed to something the route does not answer), `problemDocument` and the problem members are `undefined`, and its verdict is the fallback's for a status no refusal carries: `runtime`, not retryable. On `start` and `execute` its `runMayHaveStarted` is `true`, since the server accepted the request; that includes a start acknowledgement whose `pipeline_run_id` is not a string, and an `execute` `202` that names no run, which leave the caller a run it could not follow, and an `execute` answer that is not the completed run: one whose `pipeline_run_id` is not a string, or whose `pipe_output` carries no string `pipeline_run_id` or no `working_memory` with `root` and `aliases` objects, the members the Python SDK's model of the answer requires. A product route that answers a `2xx` with no body at all, such as `revokePipelexApiKey`, still resolves to `undefined`, and the other product routes hand their decoded body back as it came.
|
|
262
263
|
|
|
263
264
|
| Field | Wire member | Meaning |
|
|
264
265
|
|---|---|---|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Integers past `Number.MAX_SAFE_INTEGER`
|
|
2
|
+
|
|
3
|
+
JavaScript reads every JSON number as a double, which holds an integer exactly only up to `Number.MAX_SAFE_INTEGER` (2^53 - 1). A person's data can hold a larger one, such as `12345678901234567891`, which a double reads as `12345678901234567000`. Read that way and sent back, as a rerun's inputs or an example's, the rounded number would run the method on data nobody gave it, with no sign that anything changed. The Python SDK has no such limit, since Python's integers are exact.
|
|
4
|
+
|
|
5
|
+
So the client reads every answer that carries a person's data with `parseExactJson`, and writes every request body with `stringifyExactJson`. Both live in `src/exact-json.ts`, and the barrel exports them.
|
|
6
|
+
|
|
7
|
+
## Which reads are exact
|
|
8
|
+
|
|
9
|
+
| Read | What it carries |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `getRunDetail` | The run's `inputs`. |
|
|
12
|
+
| `getRunResult`, and `waitForResult` and `startAndWaitForResult` through it | A completed run's results: `main_stuff`, `working_memory`, `graph_spec` and every other field of the answer. |
|
|
13
|
+
| `execute`, and `startAndWaitForResult`'s blocking path through it | The working memory in `pipe_output`, and so the `main_stuff` resolved from it. |
|
|
14
|
+
| `createMethodExample`, `listMethodExamples`, `iterateMethodExamples`, `getMethodExample`, `updateMethodExample`, `recordMethodExampleLatestRun` | Each example's `inputs` ([method-examples.md](./method-examples.md#numbers-in-an-examples-inputs)). |
|
|
15
|
+
|
|
16
|
+
In these answers, an integer past the safe range in either direction, at any depth, reads as a `bigint` holding its exact digits, and every other value reads as `JSON.parse` reads it. Every other read uses `JSON.parse`: the run status read and the run history, whose fields are the run's bookkeeping rather than its data, and the rest of the product surface.
|
|
17
|
+
|
|
18
|
+
**A `bigint` needs the SDK's writer to become JSON text.** `String(value)` shows its digits, but `JSON.stringify` throws a `TypeError` on one, so code that shows such data as JSON text writes it with `stringifyExactJson(value, 2)`, and reads text a person edited back with `parseExactJson`. That includes a run's `graph_spec`: it is read exactly too, so a graph handed to a renderer can carry a `bigint`. The `pipelex-sdk` command prints a run's output this way. Arithmetic on a `bigint` takes another `bigint`, so code that sums the numbers of an output converts it first, knowing it may lose digits.
|
|
19
|
+
|
|
20
|
+
## The writer
|
|
21
|
+
|
|
22
|
+
`stringifyExactJson` writes a value as `JSON.stringify` writes it, in one `JSON.stringify` call whose replacer writes each `bigint` as raw JSON text (`JSON.rawJSON`), so its digits go to the platform unchanged. Being one call, it runs every getter and every `toJSON` in the value once, and whatever one of them throws propagates as thrown, never retried. A `toJSON` a `bigint` inherits, such as the common polyfill `BigInt.prototype.toJSON = function () { return this.toString(); }`, runs before the replacer, so the writer looks the `bigint` up in the object or array holding it and writes its digits, never the quoted string the polyfill makes of it; the one `bigint` it cannot look up is one a getter returns, since reading it again would run the getter twice, and that one is written as the polyfill writes it. The client writes every request body this way before the request is made, so a body JSON cannot carry, such as one holding a cycle or whose own `toJSON` throws, is refused with `RequestArgumentError` before anything is sent, the failure as its `cause` ([errors.md](./errors.md)).
|
|
23
|
+
|
|
24
|
+
## A float keeps its value, not its spelling
|
|
25
|
+
|
|
26
|
+
A number with a fraction or an exponent stays a double. A float with no fractional part, `3.0`, reads as `3` and is written back as `3`, as everywhere in JavaScript: the value is the same, so a run started with it runs as given. Only an example's edit can tell them apart, since the platform compares an example's inputs as canonical JSON ([method-examples.md](./method-examples.md#numbers-in-an-examples-inputs)).
|
|
27
|
+
|
|
28
|
+
## The runtime it needs
|
|
29
|
+
|
|
30
|
+
Both functions rest on the JSON source-text proposal, which every runtime the package supports (Node 22.12 and later) provides: the reader on the source text `JSON.parse` hands its reviver, the writer on `JSON.rawJSON`. The reader is that one `JSON.parse` call with a reviver, so it reads strings, escapes, repeated keys and `__proto__` exactly as `JSON.parse` does; a text holding no run of 16 digits, and so no integer past the safe range, is read without the reviver.
|
|
31
|
+
|
|
32
|
+
On a runtime without them, neither ever rounds. The reader refuses a number past the safe range that reads as a whole number with `ExactJsonUnsupportedError` (`config`, not retryable), since without the source text it cannot tell `12345678901234567891` from `1.2345678901234567e19`; the client reports it as the `ApiResponseError` of an answer it cannot read (`runtime`, not retryable), with that refusal as `cause`. The writer refuses a `bigint` with the `TypeError` `JSON.stringify` throws for one, which the client reports as `RequestArgumentError` before anything is sent.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# A method's saved examples
|
|
2
|
+
|
|
3
|
+
An **example** is a named set of inputs saved on a stored method, so the method can be run on them again in one gesture, together with the latest run of exactly those inputs. A method has any number of them. Like the method itself, an example belongs to the organization: any member reads, edits, runs and deletes any example of any of its methods, and `created_by_user_id` only records who saved it. The client reaches them on the hosted API's `/v1/methods/{method_id}/examples` routes, and the types are in `src/product-models.ts`.
|
|
4
|
+
|
|
5
|
+
## The example
|
|
6
|
+
|
|
7
|
+
| Field | Meaning |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `example_id` | `ex_…`, minted by the platform. |
|
|
10
|
+
| `method_id` | The bare id of the method it belongs to. |
|
|
11
|
+
| `name` | 1 to 200 characters, not blank. |
|
|
12
|
+
| `inputs` | A JSON object: the inputs as a run of the method takes them. The platform answers them exactly as they were saved; the client reads an integer past `Number.MAX_SAFE_INTEGER` as a `bigint` holding its exact digits and every other number as a double, so `3.0` reads as `3` ([below](#numbers-in-an-examples-inputs)). |
|
|
13
|
+
| `input_revision` | Starts at 1 and rises by one each time the inputs change. |
|
|
14
|
+
| `latest_run_id` | The latest run of the current inputs, or `null`. It may name a run deleted since, whose read then answers `404`. |
|
|
15
|
+
| `created_by_user_id`, `created_at`, `updated_at` | Who saved it, when, and when it last changed. |
|
|
16
|
+
|
|
17
|
+
An example holds no output and no baseline: its result is its latest run's, read by the run's id (`getRunResult`).
|
|
18
|
+
|
|
19
|
+
## The methods
|
|
20
|
+
|
|
21
|
+
| Method | Route | Answer |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `createMethodExample(methodId, { name, inputs, run_id? })` | `POST /v1/methods/{id}/examples` | The `MethodExample` as stored, at revision 1. With `run_id`, a run of this method that used exactly these inputs, that run is its latest. |
|
|
24
|
+
| `listMethodExamples(methodId, { limit?, cursor? })` | `GET /v1/methods/{id}/examples` | One `MethodExamplePage`, `{ items, nextCursor }`, oldest first, inputs included. `limit` is 1 to 100, 50 by default. |
|
|
25
|
+
| `iterateMethodExamples(methodId, { limit? })` | the same, page after page | Every example, oldest first, as an async iterator. A method id it cannot take is refused at the call, before the iterator is pulled. |
|
|
26
|
+
| `getMethodExample(methodId, exampleId)` | `GET /v1/methods/{id}/examples/{example_id}` | The `MethodExample`. |
|
|
27
|
+
| `updateMethodExample(methodId, exampleId, { name?, inputs? })` | `PATCH /v1/methods/{id}/examples/{example_id}` | The `MethodExample` as it now stands. |
|
|
28
|
+
| `recordMethodExampleLatestRun(methodId, exampleId, { run_id, input_revision })` | `PUT /v1/methods/{id}/examples/{example_id}/latest-run` | A `MethodExampleLatestRunResult`: `{ outcome: "recorded" \| "stale", example }`. |
|
|
29
|
+
| `deleteMethodExample(methodId, exampleId)` | `DELETE /v1/methods/{id}/examples/{example_id}` | Nothing; the platform answers `204`. |
|
|
30
|
+
|
|
31
|
+
Every method takes a bare catalog id and refuses a suffixed one (`mt_x@3`) with `RequestArgumentError` before sending, as the other method routes do, and refuses a method id or an example id that is empty, `.` or `..`, or that holds a lone surrogate, the same way, as every route that puts an id in its path does ([What is refused](#what-is-refused-and-where)). The bodies are closed on the platform, so the client sends the fields the route takes and nothing else an untyped caller adds.
|
|
32
|
+
|
|
33
|
+
**Every example the client hands back has been checked.** Each route answering one, the page rows and the latest-run verdict's `example` included, checks it once, against every field the Python SDK's model requires, with the strict type that model holds it to: string `example_id`, `method_id`, `name`, `created_by_user_id`, `created_at` and `updated_at`, object `inputs`, `input_revision` an integer within `Number.MAX_SAFE_INTEGER` either way, and `latest_run_id` present as a string or `null`. An example breaking one is an answer the SDK cannot read, thrown as an `ApiResponseError` (`runtime`, not retryable) whose message names the field, so a missing `latest_run_id` never reads as an example with no latest run, and a revision of `"2"` or `true` is never read as one. Both SDKs refuse the same answers but one: a revision spelled `2.0`, which JavaScript reads as the number `2` and cannot tell from it, while the Python SDK refuses the float.
|
|
34
|
+
|
|
35
|
+
**The list is paged by an opaque cursor.** Pass `nextCursor` back as `cursor`; `null` is the last page. The platform reads examples whole and stops a read at 1 MB, so a page may be short while `nextCursor` is set: a caller that wants every example follows the cursor, which `iterateMethodExamples` does through the loop `iterateMethods` and `iterateRuns` share, stopping on a `null` cursor or one the server failed to advance and throwing `PagingNotTerminatingError` past the same page ceiling.
|
|
36
|
+
|
|
37
|
+
## Revisions and the latest run
|
|
38
|
+
|
|
39
|
+
**New inputs raise the revision and clear the latest run.** An edit whose `inputs` differ from the stored ones sets `input_revision` to the next number and `latest_run_id` to `null`, since the run no longer shows what the inputs produce. The platform compares the inputs as canonical JSON with sorted keys, so the same inputs sent again, in another key order included, change nothing, while `3` becoming `3.0` is a change. A rename alone keeps both. The edit takes no revision or token of its own: concurrent edits are serialized on the platform, which answers a retryable `503` with nothing written when they kept landing under one.
|
|
40
|
+
|
|
41
|
+
**A run is recorded against the revision the client names.** To run an example and keep its result, read the example, start a run with its `inputs`, and record that run with the `input_revision` you read:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const example = await client.getMethodExample(methodId, exampleId);
|
|
45
|
+
const started = await client.start({ method_id: methodId, inputs: example.inputs });
|
|
46
|
+
const verdict = await client.recordMethodExampleLatestRun(methodId, exampleId, {
|
|
47
|
+
run_id: started.pipeline_run_id,
|
|
48
|
+
input_revision: example.input_revision,
|
|
49
|
+
});
|
|
50
|
+
if (verdict.outcome === "stale") {
|
|
51
|
+
// Someone edited the inputs while the run was in flight: verdict.example holds them now.
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A run started this way sends the inputs the example holds, digit for digit, an integer past `Number.MAX_SAFE_INTEGER` included ([below](#numbers-in-an-examples-inputs)). The write is conditioned on the example still holding that revision. **A stale run is a verdict, not an error**: a run outlasting an edit is the expected course of events whenever someone edits an example while one of its runs is in flight, so the platform answers a `200` discriminated on `outcome`, as it does for a publish, and both arms carry the example as it stands. Branch on `outcome`; an answer whose `outcome` is neither, or whose `example` is not an example the client can read, is an answer the SDK cannot read, thrown as an `ApiResponseError` (`runtime`, not retryable). The platform trusts the revision and does not compare the run's own inputs with the example's, so record a run only against the revision whose inputs it was started with. A run recorded when the example is created goes in `createMethodExample`'s `run_id` instead.
|
|
56
|
+
|
|
57
|
+
## Numbers in an example's inputs
|
|
58
|
+
|
|
59
|
+
The platform stores an example's inputs as JSON text and answers them exactly as they were saved, but JavaScript reads every JSON number as a double, which holds an integer exactly only up to `Number.MAX_SAFE_INTEGER` (2^53 - 1). Inputs saved by another client, the Python SDK or a raw request, can hold a larger one, such as `12345678901234567891`, which a double reads as `12345678901234567000`. Sent back, to `updateMethodExample` or as a run's inputs, the rounded number would overwrite the saved inputs, clearing the latest run, or run the method on inputs nobody saved and record that run under the revision that holds the real ones, since the platform trusts the revision it is given.
|
|
60
|
+
|
|
61
|
+
**So the client keeps such an integer exact.** Every answer carrying an example, the page of `listMethodExamples` and the verdict of `recordMethodExampleLatestRun` included, is read with `parseExactJson`, which reads an integer past `Number.MAX_SAFE_INTEGER` in either direction, at any depth of the inputs, as a `bigint` holding its exact digits, and every other value as `JSON.parse` reads it. Every request body is written with `stringifyExactJson`, which writes a `bigint` as those digits where `JSON.stringify` throws, so inputs read from an example go back unchanged, whichever route they are sent to. One such example never makes a page unreadable: the page reads whole, that integer as a `bigint`. A run's inputs and outputs are read the same way, so an example saved from a run's inputs keeps every digit too; [exact-integers.md](./exact-integers.md) lists every exact read, says what a `bigint` asks of code showing the inputs as JSON text (`stringifyExactJson(example.inputs, 2)`, and `parseExactJson` to read edited text back), and what a runtime without the JSON source-text proposal refuses rather than rounds.
|
|
62
|
+
|
|
63
|
+
**A float keeps its value, not its spelling.** A float with no fractional part, `3.0`, reads as `3` and goes back as `3`, as it does everywhere in JavaScript; the value is the same, so a run started with it runs as saved. The platform compares inputs as canonical JSON, though, where `3` and `3.0` differ, so `updateMethodExample` given inputs read from an example that held one raises `input_revision` and clears `latest_run_id`, though the caller changed nothing. Send `inputs` only when the person changed them; a rename sent alone keeps the revision and the latest run. Inputs saved from JavaScript never hold a `3.0`, since JavaScript cannot write one.
|
|
64
|
+
|
|
65
|
+
## What is refused, and where
|
|
66
|
+
|
|
67
|
+
The client refuses before sending, with `RequestArgumentError` (`input`, not retryable):
|
|
68
|
+
|
|
69
|
+
- a suffixed method id, on every method;
|
|
70
|
+
- a method id or an example id that is empty, `.` or `..`, or that is not a string: the URL resolves a dot segment before the request leaves, so an example id `..` would address the method itself, and deleting it would erase the method, while an empty example id would address the examples list;
|
|
71
|
+
- a method id or an example id holding a lone surrogate, which has no UTF-8 spelling and so cannot enter a URL;
|
|
72
|
+
- a `createMethodExample` without its `name` or its `inputs` (a `null` one counting as not given), or whose `run_id` is given but is not a non-empty string, a `null` reading as not given;
|
|
73
|
+
- an `updateMethodExample` naming neither `name` nor `inputs` (a `null` field counts as not given, as the platform reads it);
|
|
74
|
+
- a `recordMethodExampleLatestRun` whose `run_id` is not a non-empty string or whose `input_revision` is not a positive integer, the example id checked first, then the run id, then the revision;
|
|
75
|
+
- an `iterateMethodExamples` whose method id any method above refuses, at the call itself rather than when the iterator is first pulled;
|
|
76
|
+
- a body JSON cannot carry, such as inputs holding a cycle, or a `bigint` on a runtime without `JSON.rawJSON` ([exact-integers.md](./exact-integers.md)).
|
|
77
|
+
|
|
78
|
+
The platform refuses the rest, each as an `ApiResponseError` whose `code` names it (see [errors.md](./errors.md)):
|
|
79
|
+
|
|
80
|
+
| `code` | Status | When |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `not_found` | `404` | An unknown method or example, an example already deleted included. |
|
|
83
|
+
| `run_not_found` | `404` | A `run_id`, on the create or the latest-run write, that is not one of this method's runs in the caller's organization; nothing is written. |
|
|
84
|
+
| `method_being_deleted` | `409` | The method's erasure has started. Its examples are erased with it. |
|
|
85
|
+
| `payload_too_large` | `413` | An example larger than one stored item holds. |
|
|
86
|
+
| `validation_failed` | `422` | A name empty, blank or over 200 characters, inputs that are not a JSON object, a `limit` outside 1 to 100, or an example id longer than one can be (64 characters). |
|
|
87
|
+
| `malformed_request` | `400` | A cursor the listing did not issue for this method. |
|
|
88
|
+
|
|
89
|
+
Every write is `403` to a read-only (`purpose=sandbox`) key, which may still read the examples.
|
package/docs/run-results.md
CHANGED
|
@@ -123,6 +123,8 @@ It is typed `unknown` because the content is polymorphic: a structured output ar
|
|
|
123
123
|
|
|
124
124
|
For a multiple output that means reading `items` off the object and mapping the generated per-concept parser over its members, because codegen emits a parser per concept and no wrapper type for the envelope. **Do not rely on the parser to catch the mistake for you.** The generated schemas are not strict, so a concept with a required field rejects the envelope loudly, while one whose fields are all optional parses it to `{}` and discards the output in silence.
|
|
125
125
|
|
|
126
|
+
**An integer past `Number.MAX_SAFE_INTEGER` reads as a `bigint`.** The results are read with `parseExactJson`, on both paths, so such an integer anywhere in them, in `main_stuff`, `working_memory` or `graph_spec`, holds its exact digits, and an output passed back to `start` goes back as the run produced it. `JSON.stringify` throws on a `bigint`, so code showing the results as JSON text writes them with `stringifyExactJson` ([exact-integers.md](./exact-integers.md)).
|
|
127
|
+
|
|
126
128
|
```ts
|
|
127
129
|
const state = await client.getRunResult(runId);
|
|
128
130
|
|