@pipelex/sdk 0.38.0 → 0.40.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 +3 -2
- package/dist/bundle-resources.d.ts +62 -0
- package/dist/bundle-resources.js +165 -0
- package/dist/bundle-resources.js.map +1 -0
- package/dist/cli/run.js +8 -111
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/script.d.ts +1 -1
- package/dist/cli/source.d.ts +2 -1
- package/dist/cli/source.js +61 -3
- package/dist/cli/source.js.map +1 -1
- package/dist/client.d.ts +116 -30
- package/dist/client.js +235 -75
- package/dist/client.js.map +1 -1
- package/dist/error-models.d.ts +64 -18
- package/dist/error-models.js +20 -9
- package/dist/error-models.js.map +1 -1
- package/dist/errors.d.ts +125 -23
- package/dist/errors.js +124 -22
- package/dist/errors.js.map +1 -1
- package/dist/execute-result.js +4 -4
- package/dist/execute-result.js.map +1 -1
- package/dist/hooks/bundle-gather.d.ts +28 -1
- package/dist/hooks/bundle-gather.js +163 -3
- package/dist/hooks/bundle-gather.js.map +1 -1
- package/dist/hooks/claude-mthds-check.d.ts +4 -2
- package/dist/hooks/claude-mthds-check.js +5 -2
- package/dist/hooks/claude-mthds-check.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +42 -4
- package/dist/prepare-inputs.d.ts +58 -4
- package/dist/prepare-inputs.js +307 -61
- package/dist/prepare-inputs.js.map +1 -1
- package/dist/product-models.d.ts +13 -5
- package/dist/run-may-have-started.d.ts +31 -0
- package/dist/run-may-have-started.js +138 -0
- package/dist/run-may-have-started.js.map +1 -0
- package/dist/runs.d.ts +95 -7
- package/dist/runs.js +18 -0
- package/dist/runs.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +6 -3
- package/docs/bundle-resources.md +107 -0
- package/docs/cli.md +2 -1
- package/docs/crate-routes.md +5 -1
- package/docs/errors.md +87 -17
- package/docs/hook-bundle.md +2 -0
- package/docs/input-preparation.md +26 -4
- package/docs/run-results.md +39 -0
- package/package.json +2 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a failed request that may create a run, a start or a blocking execute, may still have
|
|
3
|
+
* started one whose id never reached the caller. `ApiUnreachableError` and `ApiResponseError` read
|
|
4
|
+
* it when the client tells them their request may create a run, and carry the answer as
|
|
5
|
+
* `runMayHaveStarted`; the Python twin, `pipelex-sdk`, reads the same failures the same way in
|
|
6
|
+
* `pipelex_sdk.run_may_have_started`.
|
|
7
|
+
*
|
|
8
|
+
* The reading errs on the safe side. A false "may have started" costs the person a look at the run
|
|
9
|
+
* history; a false "did not start" costs a second paid run. So every failure counts as one that
|
|
10
|
+
* may have started a run unless it proves otherwise: a transport failure that proves nothing was
|
|
11
|
+
* sent, and an answer that says the request was refused before anything ran.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Whether an answer to a request that may create a run leaves one possibly started:
|
|
15
|
+
*
|
|
16
|
+
* - any `2xx`, which the client met only because it could not read it: the server accepted the
|
|
17
|
+
* request, and only its acknowledgement was lost;
|
|
18
|
+
* - a `408`, which a proxy in front of the platform may answer for a request it forwarded;
|
|
19
|
+
* - every `5xx` but a `501`, which says the deployment does not serve the route, and a `503`
|
|
20
|
+
* carrying the platform's code `pipeline_start_unavailable`, which says the runner was never
|
|
21
|
+
* reached: the server or a gateway failed while handling the request.
|
|
22
|
+
*
|
|
23
|
+
* Any other status is the request refused before anything ran.
|
|
24
|
+
*/
|
|
25
|
+
export declare function answerMayHaveStartedRun(status: number, code: string | undefined): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether a transport failure of a request that may create a run leaves one possibly started:
|
|
28
|
+
* every failure but one that proves nothing was sent. `cause` is the failure fetch threw, which
|
|
29
|
+
* `ApiUnreachableError` keeps as its own `cause`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function transportMayHaveStartedRun(cause: unknown): boolean;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a failed request that may create a run, a start or a blocking execute, may still have
|
|
3
|
+
* started one whose id never reached the caller. `ApiUnreachableError` and `ApiResponseError` read
|
|
4
|
+
* it when the client tells them their request may create a run, and carry the answer as
|
|
5
|
+
* `runMayHaveStarted`; the Python twin, `pipelex-sdk`, reads the same failures the same way in
|
|
6
|
+
* `pipelex_sdk.run_may_have_started`.
|
|
7
|
+
*
|
|
8
|
+
* The reading errs on the safe side. A false "may have started" costs the person a look at the run
|
|
9
|
+
* history; a false "did not start" costs a second paid run. So every failure counts as one that
|
|
10
|
+
* may have started a run unless it proves otherwise: a transport failure that proves nothing was
|
|
11
|
+
* sent, and an answer that says the request was refused before anything ran.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The platform code of the `503` a start answers when the platform could not reach the runner at
|
|
15
|
+
* all: it finalized the run's row `FAILED` and started nothing. Every other `503`, the platform's
|
|
16
|
+
* `service_unavailable` included, can follow a run the runner already started.
|
|
17
|
+
*/
|
|
18
|
+
const START_UNAVAILABLE_CODE = "pipeline_start_unavailable";
|
|
19
|
+
/**
|
|
20
|
+
* Whether an answer to a request that may create a run leaves one possibly started:
|
|
21
|
+
*
|
|
22
|
+
* - any `2xx`, which the client met only because it could not read it: the server accepted the
|
|
23
|
+
* request, and only its acknowledgement was lost;
|
|
24
|
+
* - a `408`, which a proxy in front of the platform may answer for a request it forwarded;
|
|
25
|
+
* - every `5xx` but a `501`, which says the deployment does not serve the route, and a `503`
|
|
26
|
+
* carrying the platform's code `pipeline_start_unavailable`, which says the runner was never
|
|
27
|
+
* reached: the server or a gateway failed while handling the request.
|
|
28
|
+
*
|
|
29
|
+
* Any other status is the request refused before anything ran.
|
|
30
|
+
*/
|
|
31
|
+
export function answerMayHaveStartedRun(status, code) {
|
|
32
|
+
if (status >= 200 && status < 300)
|
|
33
|
+
return true;
|
|
34
|
+
if (status === 408)
|
|
35
|
+
return true;
|
|
36
|
+
if (status === 501)
|
|
37
|
+
return false;
|
|
38
|
+
if (status === 503 && code === START_UNAVAILABLE_CODE)
|
|
39
|
+
return false;
|
|
40
|
+
return status >= 500 && status < 600;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether a transport failure of a request that may create a run leaves one possibly started:
|
|
44
|
+
* every failure but one that proves nothing was sent. `cause` is the failure fetch threw, which
|
|
45
|
+
* `ApiUnreachableError` keeps as its own `cause`.
|
|
46
|
+
*/
|
|
47
|
+
export function transportMayHaveStartedRun(cause) {
|
|
48
|
+
return !provesNothingSent(cause);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether a transport failure proves the request never left, so that no run was created by it: it
|
|
52
|
+
* names a step of the connection's set-up, which no request follows. That is the name lookup or
|
|
53
|
+
* the connection itself failing (the system calls `getaddrinfo` and `connect`: an unknown host, a
|
|
54
|
+
* refused connection, no route to the host or the network, the system's connect time limit),
|
|
55
|
+
* undici's own connect time limit, which covers the TLS handshake, and a server certificate the
|
|
56
|
+
* TLS handshake refused. Several addresses each failing so, which Node reports together as an
|
|
57
|
+
* `AggregateError`, prove it too. A host that drops packets is a connection that never comes:
|
|
58
|
+
* undici gives it up after ten seconds (`UND_ERR_CONNECT_TIMEOUT`), before the time limit of any
|
|
59
|
+
* request that may create a run, as the Python SDK meets httpx's connect timeout first and reads
|
|
60
|
+
* it the same way.
|
|
61
|
+
*
|
|
62
|
+
* Anything else may have come once the request had left, the Python SDK reading the same failures
|
|
63
|
+
* the same way. A reset (`ECONNRESET`) comes from a `read` whether the connection dropped during
|
|
64
|
+
* the TLS handshake or after the request, and so does an `EHOSTUNREACH` met once connected, so
|
|
65
|
+
* neither proves anything; nor does another TLS failure, which may come after the handshake; nor
|
|
66
|
+
* does the SDK's own time limit (`ABORT_TIMEOUT`), which fetch cannot place before or after the
|
|
67
|
+
* connection was made; nor does a body that arrived and could not be decoded. Every request that
|
|
68
|
+
* may create a run has a limit longer than undici's connect limit, so with Node's own dispatcher
|
|
69
|
+
* it runs out only once the request has left.
|
|
70
|
+
*/
|
|
71
|
+
function provesNothingSent(fetchFailure) {
|
|
72
|
+
// undici gives the transport's own failure as the `cause` of fetch's.
|
|
73
|
+
return fetchFailure instanceof Error && isConnectionSetupFailure(fetchFailure.cause);
|
|
74
|
+
}
|
|
75
|
+
function isConnectionSetupFailure(failure) {
|
|
76
|
+
if (failure instanceof AggregateError) {
|
|
77
|
+
return failure.errors.length > 0 && failure.errors.every(isConnectionSetupFailure);
|
|
78
|
+
}
|
|
79
|
+
if (typeof failure !== "object" || failure === null)
|
|
80
|
+
return false;
|
|
81
|
+
const { code, syscall } = failure;
|
|
82
|
+
if (typeof code === "string" && CONNECTION_SETUP_CODES.has(code))
|
|
83
|
+
return true;
|
|
84
|
+
return typeof syscall === "string" && CONNECTION_SETUP_CALLS.has(syscall);
|
|
85
|
+
}
|
|
86
|
+
/** The system calls that resolve the host's name and open the connection. */
|
|
87
|
+
const CONNECTION_SETUP_CALLS = new Set(["getaddrinfo", "connect"]);
|
|
88
|
+
/**
|
|
89
|
+
* The codes only a step of the connection's set-up gives: the name lookup's and the connect call's
|
|
90
|
+
* own (set even where Node reports several addresses together, without a system call), undici's
|
|
91
|
+
* connect time limit, Node's limit on one attempt among several addresses, Node's own check of the
|
|
92
|
+
* host's name, and every code Node gives a server certificate the TLS handshake refused.
|
|
93
|
+
*/
|
|
94
|
+
const CONNECTION_SETUP_CODES = new Set([
|
|
95
|
+
"ECONNREFUSED",
|
|
96
|
+
"ENOTFOUND",
|
|
97
|
+
"EAI_AGAIN",
|
|
98
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
99
|
+
"ERR_SOCKET_CONNECTION_TIMEOUT",
|
|
100
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
101
|
+
"ERR_TLS_CERT_ALTNAME_FORMAT",
|
|
102
|
+
// Node names the verdicts of OpenSSL's certificate check from a fixed table, the "X509 certificate
|
|
103
|
+
// error codes" of its `tls` docs, and gives every other verdict, such as an unhandled critical
|
|
104
|
+
// extension or a CA certificate that may not sign certificates, the code `UNSPECIFIED`. The
|
|
105
|
+
// error carries nothing else that marks it, so these codes are the mark: the table as Node 24
|
|
106
|
+
// compiles it, in its order, then its default. The Python SDK reads every
|
|
107
|
+
// `ssl.SSLCertVerificationError` the same way.
|
|
108
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
109
|
+
"UNABLE_TO_GET_CRL",
|
|
110
|
+
"UNABLE_TO_DECRYPT_CERT_SIGNATURE",
|
|
111
|
+
"UNABLE_TO_DECRYPT_CRL_SIGNATURE",
|
|
112
|
+
"UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY",
|
|
113
|
+
"CERT_SIGNATURE_FAILURE",
|
|
114
|
+
"CRL_SIGNATURE_FAILURE",
|
|
115
|
+
"CERT_NOT_YET_VALID",
|
|
116
|
+
"CERT_HAS_EXPIRED",
|
|
117
|
+
"CRL_NOT_YET_VALID",
|
|
118
|
+
"CRL_HAS_EXPIRED",
|
|
119
|
+
"ERROR_IN_CERT_NOT_BEFORE_FIELD",
|
|
120
|
+
"ERROR_IN_CERT_NOT_AFTER_FIELD",
|
|
121
|
+
"ERROR_IN_CRL_LAST_UPDATE_FIELD",
|
|
122
|
+
"ERROR_IN_CRL_NEXT_UPDATE_FIELD",
|
|
123
|
+
"OUT_OF_MEM",
|
|
124
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
125
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
126
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
127
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
128
|
+
"CERT_CHAIN_TOO_LONG",
|
|
129
|
+
"CERT_REVOKED",
|
|
130
|
+
"INVALID_CA",
|
|
131
|
+
"PATH_LENGTH_EXCEEDED",
|
|
132
|
+
"INVALID_PURPOSE",
|
|
133
|
+
"CERT_UNTRUSTED",
|
|
134
|
+
"CERT_REJECTED",
|
|
135
|
+
"HOSTNAME_MISMATCH",
|
|
136
|
+
"UNSPECIFIED",
|
|
137
|
+
]);
|
|
138
|
+
//# sourceMappingURL=run-may-have-started.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-may-have-started.js","sourceRoot":"","sources":["../src/run-may-have-started.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,MAAM,sBAAsB,GAAG,4BAA4B,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc,EAAE,IAAwB;IAC9E,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,MAAM,KAAK,GAAG,IAAI,IAAI,KAAK,sBAAsB;QAAE,OAAO,KAAK,CAAC;IACpE,OAAO,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,CAAC;AACvC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAc;IACvD,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,iBAAiB,CAAC,YAAqB;IAC9C,sEAAsE;IACtE,OAAO,YAAY,YAAY,KAAK,IAAI,wBAAwB,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,wBAAwB,CAAC,OAAgB;IAChD,IAAI,OAAO,YAAY,cAAc,EAAE,CAAC;QACtC,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAClE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAgD,CAAC;IAC3E,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9E,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC5E,CAAC;AAED,6EAA6E;AAC7E,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;AAExF;;;;;GAKG;AACH,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC;IAC1D,cAAc;IACd,WAAW;IACX,WAAW;IACX,yBAAyB;IACzB,+BAA+B;IAC/B,8BAA8B;IAC9B,6BAA6B;IAC7B,mGAAmG;IACnG,+FAA+F;IAC/F,4FAA4F;IAC5F,8FAA8F;IAC9F,0EAA0E;IAC1E,+CAA+C;IAC/C,2BAA2B;IAC3B,mBAAmB;IACnB,kCAAkC;IAClC,iCAAiC;IACjC,oCAAoC;IACpC,wBAAwB;IACxB,uBAAuB;IACvB,oBAAoB;IACpB,kBAAkB;IAClB,mBAAmB;IACnB,iBAAiB;IACjB,gCAAgC;IAChC,+BAA+B;IAC/B,gCAAgC;IAChC,gCAAgC;IAChC,YAAY;IACZ,6BAA6B;IAC7B,2BAA2B;IAC3B,mCAAmC;IACnC,iCAAiC;IACjC,qBAAqB;IACrB,cAAc;IACd,YAAY;IACZ,sBAAsB;IACtB,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,mBAAmB;IACnB,aAAa;CACd,CAAC,CAAC"}
|
package/dist/runs.d.ts
CHANGED
|
@@ -46,9 +46,16 @@ export interface RunPublic {
|
|
|
46
46
|
created_at: string;
|
|
47
47
|
finished_at?: string | null;
|
|
48
48
|
/**
|
|
49
|
-
* Why the run failed: the
|
|
50
|
-
* `
|
|
51
|
-
*
|
|
49
|
+
* Why the run failed: the stored error report as the platform serves it, the runner's STRICT
|
|
50
|
+
* projection, typed whole (see `RunErrorReport`). The runner writes it for a run that fails
|
|
51
|
+
* while it runs; the platform writes one in the runner's shape for a run it ends or settles
|
|
52
|
+
* itself — a workflow task that kept failing (`WorkflowTaskKeptFailingError`), a timeout
|
|
53
|
+
* (`RunTimedOutError`), a workflow that vanished (`RunWorkflowNotFoundError`). A cancelled,
|
|
54
|
+
* terminated or timed-out run holds a report when a completion callback carried one, and a
|
|
55
|
+
* timed-out run otherwise the platform's timeout report. `null` on a completed run and on a
|
|
56
|
+
* run that ended without a report — one whose start failed, a blocking `execute` run, one
|
|
57
|
+
* cancelled, terminated or given its status by hand with no completion callback carrying a
|
|
58
|
+
* report, one that ended before the platform wrote reports of its own; absent on a server that
|
|
52
59
|
* does not serve it. The results read of the same run carries the same object in its `409`,
|
|
53
60
|
* which is where `getRunResult`'s failed arm and `RunFailedError` take it from.
|
|
54
61
|
*/
|
|
@@ -70,15 +77,96 @@ export interface RunPublic {
|
|
|
70
77
|
[extension: string]: unknown;
|
|
71
78
|
}
|
|
72
79
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
80
|
+
* Why a status read is `degraded` — `RunDegradation.reason`:
|
|
81
|
+
*
|
|
82
|
+
* - `status_unavailable`: the platform could not reach the workflow engine, so `status` is the
|
|
83
|
+
* last one recorded. It says nothing about the run itself, which may be fine; read again after
|
|
84
|
+
* `retry_after_seconds`.
|
|
85
|
+
* - `workflow_task_failing`: `status` is live, and the run is not making progress. The workflow
|
|
86
|
+
* task that drives it has failed `failed_attempts` times in a row since `failing_since`; the
|
|
87
|
+
* workflow engine keeps retrying it, and if it is still failing at `ends_at` the platform ends
|
|
88
|
+
* the run `FAILED` with a `WorkflowTaskKeptFailingError` report.
|
|
89
|
+
*
|
|
90
|
+
* Read open, as `RunDegradation.reason` types it: a reason a later platform adds arrives as its
|
|
91
|
+
* raw string rather than failing the read, and a consumer that does not know it should not tell
|
|
92
|
+
* the person the run is fine.
|
|
93
|
+
*/
|
|
94
|
+
export type DegradationReason = "status_unavailable" | "workflow_task_failing";
|
|
95
|
+
/**
|
|
96
|
+
* What a `degraded` status read means for this run — `RunRead.degradation`.
|
|
97
|
+
*
|
|
98
|
+
* Branch on `reason` (see `DegradationReason`), never on the wording of `message`. Each field is
|
|
99
|
+
* checked by the read: a known field whose value does not have its type is absent, and members
|
|
100
|
+
* this SDK does not name are relayed as sent.
|
|
101
|
+
*/
|
|
102
|
+
export interface RunDegradation {
|
|
103
|
+
/** Why the read is degraded: a `DegradationReason`, or the raw string of one this SDK does not know. */
|
|
104
|
+
reason: DegradationReason | (string & {});
|
|
105
|
+
/**
|
|
106
|
+
* What the platform says about it, in a sentence. For `workflow_task_failing` it says how many
|
|
107
|
+
* attempts have failed in a row, that the run is not making progress and when the platform will
|
|
108
|
+
* end it. It does not quote the worker's own failure text, which only a platform run locally
|
|
109
|
+
* with `ERROR_DISCLOSURE=verbose` adds. `failed_attempts` and `ends_at` carry the same facts as
|
|
110
|
+
* fields.
|
|
111
|
+
*/
|
|
112
|
+
message?: string | null;
|
|
113
|
+
/** How many times in a row the run's workflow task has failed; `null` for `status_unavailable`. */
|
|
114
|
+
failed_attempts?: number | null;
|
|
115
|
+
/**
|
|
116
|
+
* ISO-8601 instant of the first failure in the current run of failures. `null` when the
|
|
117
|
+
* platform could not find the recorded failure in the workflow's history, and always for
|
|
118
|
+
* `status_unavailable`.
|
|
119
|
+
*/
|
|
120
|
+
failing_since?: string | null;
|
|
121
|
+
/**
|
|
122
|
+
* ISO-8601 instant at which the platform ends the run `FAILED` if its workflow task is still
|
|
123
|
+
* failing. `null` when the platform could not find the recorded failure: it then does not end
|
|
124
|
+
* the run itself, and the run's own time limit ends it `TIMED_OUT`. Always `null` for
|
|
125
|
+
* `status_unavailable`.
|
|
126
|
+
*/
|
|
127
|
+
ends_at?: string | null;
|
|
128
|
+
/** Members a later platform adds. */
|
|
129
|
+
[extension: string]: unknown;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A run read through the self-healing status path (`RunPublic` + `degraded`, `degradation`,
|
|
133
|
+
* `request_id` and the `Retry-After` hint).
|
|
134
|
+
*
|
|
135
|
+
* `degraded` is true when the read is not a plain fresh reading of a healthy run, and
|
|
136
|
+
* `degradation.reason` says which of two things holds (see `DegradationReason`): the platform
|
|
137
|
+
* could not reach the workflow engine and `status` is the last one recorded
|
|
138
|
+
* (`status_unavailable`, paired with `retry_after_seconds`), or `status` is live and the run is
|
|
139
|
+
* stuck, its workflow task failing until the platform ends it (`workflow_task_failing`). So
|
|
140
|
+
* `degraded` alone no longer means the run is fine. A platform older than the degradation sends
|
|
141
|
+
* `degraded` with no `degradation`; read that as `status_unavailable`, the only meaning `degraded`
|
|
142
|
+
* had then.
|
|
77
143
|
*/
|
|
78
144
|
export interface RunRead extends RunPublic {
|
|
79
145
|
degraded: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* What `degraded` means for this run; `null` whenever `degraded` is false, and absent on a
|
|
148
|
+
* platform that predates it. Checked field by field, and `null` when it is not an object or its
|
|
149
|
+
* `reason` is not a string (see `RunDegradation`).
|
|
150
|
+
*/
|
|
151
|
+
degradation?: RunDegradation | null;
|
|
152
|
+
/**
|
|
153
|
+
* The id of the request that started the run — the one to quote to support. `null` for a run
|
|
154
|
+
* saved before the platform recorded it; absent on a server that does not serve it.
|
|
155
|
+
*/
|
|
156
|
+
request_id?: string | null;
|
|
157
|
+
/** The `Retry-After` header's delay, in seconds; set when the platform sent one. */
|
|
80
158
|
retry_after_seconds?: number | null;
|
|
81
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* A status read's `degradation`, checked field by field so that it never fails the poll carrying
|
|
162
|
+
* it: a value that is not an object, or whose `reason` is not a string, reads as `null`, since a
|
|
163
|
+
* degradation without a reason cannot be branched on; `message`, `failing_since` and `ends_at` are
|
|
164
|
+
* kept when they are strings and `failed_attempts` when it is an integer, `null` kept for each;
|
|
165
|
+
* members this SDK does not name are relayed as sent.
|
|
166
|
+
*
|
|
167
|
+
* Internal: `getRunStatus` applies it.
|
|
168
|
+
*/
|
|
169
|
+
export declare function readRunDegradation(value: unknown): RunDegradation | null;
|
|
82
170
|
/**
|
|
83
171
|
* One inference call's token usage — the client-facing wire record.
|
|
84
172
|
*
|
package/dist/runs.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RequestArgumentError, RunFailedError, RunTimeoutError } from "./errors.js";
|
|
2
2
|
import { MAX_TIMER_DELAY_MS } from "./timers.js";
|
|
3
|
+
import { dropUnfit, isPlainObject } from "./error-models.js";
|
|
3
4
|
const TERMINAL_RUN_STATUSES = new Set([
|
|
4
5
|
"COMPLETED",
|
|
5
6
|
"FAILED",
|
|
@@ -15,6 +16,23 @@ export function isTerminalRunStatus(status) {
|
|
|
15
16
|
export function isSuccessRunStatus(status) {
|
|
16
17
|
return status === "COMPLETED";
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* A status read's `degradation`, checked field by field so that it never fails the poll carrying
|
|
21
|
+
* it: a value that is not an object, or whose `reason` is not a string, reads as `null`, since a
|
|
22
|
+
* degradation without a reason cannot be branched on; `message`, `failing_since` and `ends_at` are
|
|
23
|
+
* kept when they are strings and `failed_attempts` when it is an integer, `null` kept for each;
|
|
24
|
+
* members this SDK does not name are relayed as sent.
|
|
25
|
+
*
|
|
26
|
+
* Internal: `getRunStatus` applies it.
|
|
27
|
+
*/
|
|
28
|
+
export function readRunDegradation(value) {
|
|
29
|
+
if (!isPlainObject(value) || typeof value.reason !== "string")
|
|
30
|
+
return null;
|
|
31
|
+
const degradation = { ...value };
|
|
32
|
+
dropUnfit(degradation, ["message", "failing_since", "ends_at"], (field) => typeof field === "string");
|
|
33
|
+
dropUnfit(degradation, ["failed_attempts"], Number.isInteger);
|
|
34
|
+
return degradation;
|
|
35
|
+
}
|
|
18
36
|
// ── Results artifact selection ───────────────────────────────────
|
|
19
37
|
/**
|
|
20
38
|
* The result artifacts a results read can be narrowed to, in the platform's order. Passing some
|
package/dist/runs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../src/runs.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;
|
|
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
|
@@ -92,6 +92,7 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
|
|
|
92
92
|
- `models.ts` — Dict-serialized concretes (`DictStuff` / `DictWorkingMemory` / `DictPipeOutput` / `DictRunResultExecute`), the `/v1/validate` surface (`PipelexValidationResult`, `ValidationErrorItem`, and the `SuggestedFix` / `FixOp` repair vocabulary its items carry — the standard's declarations, restated because `mthds` exports them only from its package root and pinned to it by a type-level test, see [errors.md](./errors.md#a-validation-item--validationerroritem)), the tools surface (`Diagnostic` / `LintResponse` / `FormatResponse`), and the crate-route request/response models — the shared `MthdsFileItem` / `CrateRequestBase` / `CrateInvalidReport` vocabulary plus the `/v1/resolve`, `/v1/codegen` and `/v1/pipe-io` shapes built on it, `GeneratedArtifact` among them.
|
|
93
93
|
- `product-models.ts` — the snake_case wire shapes for the hosted-product management routes (profile, methods catalog, organizations, billing, API keys, onboarding, storage, runs list/detail/update). Each interface models only the fields the product actually consumes — no speculative mirrors.
|
|
94
94
|
- `error-models.ts` — the error-report wire models: `RunErrorReport`, the runner's `ErrorReport` typed with every field it carries (with `UserAction`, `ProviderErrorMetadata` and `MigrationErrorBlock`), which is the one type for a failed run's report on `RunPublic.error`, `PipelineRun.error`, `RunHistoryItem.error`, the failed arm of `RunResultState` and `RunFailedError.error`; and `ProblemDetails` / `FieldError`, the typed members of a problem document that `ApiResponseError` exposes. Every field is optional and every interface keeps an index signature, because the runner owns the shape and adds fields without asking. It also holds the one function that checks a stored report, the internal `readRunErrorReport`, which every read handing a report back applies: each named field is kept only when it has its declared type, and members the SDK does not name are relayed as sent. It imports the `ValidationErrorItem` type and nothing else. See [errors.md](./errors.md#a-failed-run--runerrorreport).
|
|
95
|
+
- `bundle-resources.ts` — the files a bundle's steps read, sent as the request's `resources`: the `BundleResources` and `BundleResourceBytes` types the barrel exports, the runner's path rule (`bundleResourcePathRefusal`) and canonical spelling, and `encodeBundleResources`, which checks every key before it reads any value and turns the bytes into standard base64 through `upload.ts`'s `bytesToBase64`. Internal apart from its types; the hook's bundle gatherer reuses its path rule. See [bundle-resources.md](./bundle-resources.md).
|
|
95
96
|
- `method-source.ts` — `methodSourceToContents`, the canonical parser turning a stored method's polymorphic `mthds` source (raw single-bundle text XOR a JSON `{name, content}[]` file array) into a flat list of file contents. A verbatim port of the platform's `method_source_to_contents` so the SDK and runtime read a stored source identically; exported from the barrel and consumed by `getMethodClosure`. See [input-preparation.md](./input-preparation.md#getmethodclosure--the-explicit-expansion-utility).
|
|
96
97
|
- `codegen-check.ts` — `runCodegenCheck`, the **pure** offline drift check over a committed codegen tree: the stamp grammar, the `codegen.lock` parser, the hash, and the drift taxonomy, ported from pipelex's `codegen/check.py` so a verdict computed here equals the one `pipelex codegen check` computes over the same bytes (the drift `detail` sentences included, verbatim). It touches neither the client nor `mthds/protocol` — its only runtime imports are `smol-toml` and the plain-object guard of the pure `error-models.ts` — and it hashes through WebCrypto rather than `node:crypto` so it adds no Node builtin to the barrel's import graph (`upload.ts` still names `node:fs/promises`, so a browser-targeting bundler must mark `node:*` external). It raises `CodegenLockError`, which extends `Error` rather than the protocol base and declares the error verdict itself (`input`, not retryable). No filesystem, no fetch, no API key — the caller walks its own tree and hands in the text. See [crate-routes.md](./crate-routes.md#the-offline-check--runcodegencheck).
|
|
97
98
|
- `upload-grant.ts` — `uploadWithGrant`, the sender of an upload grant (`requestUploadGrant`, `POST /v1/upload/grant`): the file as the raw body of a `PUT` to the grant's URL with its signed headers unchanged, redirects refused, the whole exchange bounded by a time limit scaled by the file's size unless the caller passes `timeoutMs`, only the first 16 KiB of storage's error body read, and storage's refusals mapped onto the input-preparation errors (`RejectedAssetError` with a `code` for a `4xx` such as the `412` of a used grant or the `403` of a signature mismatch, `UploadTransportError` with a `code` for the time limit running out, a `5xx`, storage's `RequestTimeout`, a `409` conflict, a redirect or no response). It is the browser-safe `@pipelex/sdk/upload` entry, so it imports the error classes, the timer bound in `timers.ts` and the wire types only, never the client. See [input-preparation.md](./input-preparation.md#uploading-with-a-grant--requestuploadgrant--uploadwithgrant).
|
|
@@ -99,8 +100,9 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
|
|
|
99
100
|
- `runs.ts` — the run-lifecycle types (`RunStatus`, `RunRead`, `RunResults`, `RunResultState`) and the single poll loop (`pollUntilResult`). `RunResults.main_stuff` (the resolved main output content) is always present for a completed run: on the hosted path it is the `main_stuff.json` S3 artifact; on the bare-runner blocking path the SDK resolves it from the returned working memory via the response's `main_stuff_name`, so both paths deliver the same shape. Consumers read `main_stuff` directly. `working_memory` — every named stuff of the run, `{ root, aliases }`, typed `DictWorkingMemory` from `models.ts` — reads the same on both paths: the hosted path relays the `working_memory.json` artifact, and the blocking path lifts it off `pipe_output`, which still rides whole (blocking path only). `graph_spec` — the executed graph — also reaches the client on both paths: the hosted path relays the `graphspec.json` artifact, and the blocking path lifts it off `pipe_output` beside `graph_assembly_error`, the graph's twin of `usage_assembly_error`. That error field is declared ahead of the wire on the hosted side, where the platform's results body relays no such key and the field is therefore absent, reading `undefined` rather than `null`. A completed run that cannot deliver a main stuff throws `MissingMainStuffError` when the read asked for it. `runs.ts` also declares `RUN_RESULT_ARTIFACTS` / `RunResultArtifact`, the artifacts a results read can be narrowed to with `getRunResult`'s `artifacts` option; with a selection an unselected artifact is absent while a selected unwritten one is `null`. `runs.ts` also owns `TokensUsageRecord` — one client-facing usage record per inference call, carried by `RunResults.tokens_usages` on both paths. That is a mirror of a wire contract this SDK does not own (inference accounting is a Pipelex runtime extension rather than part of the MTHDS standard): every field is optional and the interface carries an index signature so pre-contract artifacts (relayed verbatim, never migrated) still type-check. See [`run-usage.md`](./run-usage.md) for the field reference, the cost/null semantics, and the old-artifact rules, and [`run-results.md`](./run-results.md) for the field-by-field account of `RunResults` — the durable handle, the output and how to read it, the run's working memory, the executed graph and how to render or keep it, and the produced files with their short-lived links.
|
|
100
101
|
- `usage.ts` — `summarizeUsage`, the **pure** fold of a run's usage pair (`tokens_usages` / `usage_assembly_error`) into one `UsageSummary`: a `state` (`records`, `no_inference` or `unavailable`), the null-aware total cost with its `cost_partial` flag, the `input` and `output` token totals, the call count, the relayed assembly error, and a `by_pipe` rollup sorted by cost with the unattributed calls grouped under a `null` `pipe_code`. It applies the rules [`run-usage.md`](./run-usage.md#summarizing-a-run--summarizeusage) states, so that every consumer reads one computation rather than re-deriving those rules by hand. It imports types from `runs.ts` and nothing else: no client, no fetch, no `mthds/protocol`.
|
|
101
102
|
- `execute-result.ts` — `PipelexExecuteResult`, a `DictRunResultExecute` subtype that adds a resolved `.main_stuff` accessor (dug out of `pipe_output`'s working memory via the response's `main_stuff_name`, throwing `MissingMainStuffError` if unlocatable). `execute()` returns it, giving blocking and durable results the **same output accessor** — `result.main_stuff` — so callers never branch on which path ran. The neutral `mthds` wire model leaves `main_stuff_name` in its extension index signature; this Pipelex-branded subtype declares it as a typed field (Pipelex owns that concept). `resultsFromExecute` (the public lift from that result onto `RunResults`, in the same module) reads that accessor too, keeping the resolution single-sourced; the `executeBlocking` fallback is its only caller inside the SDK, and a consumer driving `execute()` directly calls it by name.
|
|
102
|
-
- `errors.ts` — the error verdict and the typed errors. The verdict is `ErrorDomain` (`input`, `config`, `runtime`: who can fix the failure) and `ErrorVerdict` (that domain and `retryable`), carried as two readonly own properties by the abstract `PipelexRequestError`, which refines the protocol-base `PipelineRequestError` (re-exported from `mthds/protocol`) and takes the verdict as a constructor argument, so every subclass declares its verdict where it is defined and one that forgets it does not compile; `errorVerdictOf` reads the pair structurally from anything a `catch` holds. Under that base sit `RequestArgumentError` (the client refusing a call's arguments before sending any request, `input` unless the site declares otherwise, as the invalid base URL declares `config`), the transport and run-lifecycle errors, `PagingNotTerminatingError`, the input-preparation family under `InputPreparationError`, and the artifact family under `ArtifactOperationError` (`ScopeUnavailableError`, `ArtifactFetchError`, `ArtifactAuthenticationError`, the last carrying the download verdict so far); both family bases take an optional verdict for the sites, and the consumer subclasses, whose verdict is not the family's. `ApiResponseError` carries every member of the problem document it was built from, its nested members checked one level down by the readers a stored report goes through, and also stands for a 2xx answer the SDK could not read — the branch fields `errorDomain` and `type`, `retryable`, `userAction`, `requestId` (the body's, else the `X-Request-ID` header's), the native `code` and `errorType`, the platform's `errors[]`, the inference members, and the decoded document whole as `problemDocument` — and resolves `retryable` and `errorDomain` from the document's values when they are valid and the fallback's otherwise, except that a valid `input` or `config` domain sent without `retryable` is not retryable. Its member names match `mthds`'s own `ApiResponseError` wherever the two overlap, and so do their types except the verdict, which this SDK narrows to always decided; a type-level test pins the shared `ProblemDetails` and `UserAction` because this package may not import `mthds/errors`. `RunFailedError` carries the run's `status` and its stored report as `error`, and takes its verdict from that report. It is not the whole error surface: `codegen-check.ts` exports `CodegenLockError`, which extends plain `Error` because nothing was requested over the wire, and declares the two verdict members itself. See [errors.md](./errors.md#the-verdict-every-error-carries).
|
|
103
|
+
- `errors.ts` — the error verdict and the typed errors. The verdict is `ErrorDomain` (`input`, `config`, `runtime`: who can fix the failure) and `ErrorVerdict` (that domain and `retryable`), carried as two readonly own properties by the abstract `PipelexRequestError`, which refines the protocol-base `PipelineRequestError` (re-exported from `mthds/protocol`) and takes the verdict as a constructor argument, so every subclass declares its verdict where it is defined and one that forgets it does not compile; `errorVerdictOf` reads the pair structurally from anything a `catch` holds. The base also carries `runMayHaveStarted`, false unless a request that may create a run failed in a way that may have started one, and `runMayHaveStartedOf` reads it structurally the same way. Under that base sit `RequestArgumentError` (the client refusing a call's arguments before sending any request, `input` unless the site declares otherwise, as the invalid base URL declares `config`), the transport and run-lifecycle errors, `PagingNotTerminatingError`, the input-preparation family under `InputPreparationError`, and the artifact family under `ArtifactOperationError` (`ScopeUnavailableError`, `ArtifactFetchError`, `ArtifactAuthenticationError`, the last carrying the download verdict so far); both family bases take an optional verdict for the sites, and the consumer subclasses, whose verdict is not the family's. `ApiResponseError` carries every member of the problem document it was built from, its nested members checked one level down by the readers a stored report goes through, and also stands for a 2xx answer the SDK could not read — the branch fields `errorDomain` and `type`, `retryable`, `userAction`, `requestId` (the body's, else the `X-Request-ID` header's), the native `code` and `errorType`, the platform's `errors[]`, the inference members, and the decoded document whole as `problemDocument` — and resolves `retryable` and `errorDomain` from the document's values when they are valid and the fallback's otherwise, except that a valid `input` or `config` domain sent without `retryable` is not retryable. Its member names match `mthds`'s own `ApiResponseError` wherever the two overlap, and so do their types except the verdict, which this SDK narrows to always decided; a type-level test pins the shared `ProblemDetails` and `UserAction` because this package may not import `mthds/errors`. `RunFailedError` carries the run's `status` and its stored report as `error`, and takes its verdict from that report. It is not the whole error surface: `codegen-check.ts` exports `CodegenLockError`, which extends plain `Error` because nothing was requested over the wire, and declares the two verdict members itself. See [errors.md](./errors.md#the-verdict-every-error-carries).
|
|
103
104
|
- `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
|
+
- `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).
|
|
104
106
|
- `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).
|
|
105
107
|
- `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.
|
|
106
108
|
- `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.
|
|
@@ -114,10 +116,11 @@ Flat `src/` (mirrors `mthds-js`'s `runners/api` flatness — the SDK has one cli
|
|
|
114
116
|
- **Method-bundle transport (pipelex-api extension):** `execute` / `start` / the blocking fallback accept the whole method — the `.mthds` plus its `funcs/*.py`, `structures/*.py`, `requirements.txt` — instead of only the inline `mthds_contents` text, so custom PipeFunc Python travels with the method. Two equivalent, mutually exclusive encodings: `files` (a `{ relativePath: text }` map) and `bundle_b64` (the same bundle as a base64 zip). A bundle is self-contained, so it satisfies the "something to run" precondition on its own — neither `pipe_code` nor `mthds_contents` is required beside it. See [run-sources](#run-sources) above.
|
|
115
117
|
- **Method selectors on the run methods:** `execute` / `start` / `startAndWaitForResult` take the Pipelex API's `method_ref` (an address the runner resolves; provenance comes back typed as `method_provenance`) and the platform's `method_id` as **typed named options** (`PipelexRunOptions` / `PipelexStartOptions`), never `extra` entries. See [Pipelex API run extensions](#pipelex-api-run-extensions-method_ref) and [hosted run extensions](#hosted-run-extensions-method_id) above.
|
|
116
118
|
- **Method selectors on `validate`:** the first argument is inline contents (`string[]`) or a `ValidateMethodSelector` — `{ method_ref }` (runner-resolved by address) or `{ method_id }` (hosted, platform-resolved) — under the strict tooling XOR: exactly one selector, no linkage exception, `mthds_sources` legal only beside inline contents. A selector-resolution failure is a non-2xx `ApiResponseError`, never an `is_valid: false` verdict.
|
|
119
|
+
- **The files a bundle's steps read (`resources`, pipelex-api extension):** `validate` / `validateFiles` (in their last options argument), `execute` / `start` / `startAndWaitForResult` (both paths), the crate routes and `prepareInputs` (beside inline `files`) take `resources`, the files a `PipeDocGen` step's `template_file` names, keyed by the path the step names, each as its bytes (`Uint8Array`, `ArrayBuffer` or `Blob`), and send them as the wire's base64 map. `bundle-resources.ts` encodes them and refuses, before any request, a key the runner's path rule refuses and two keys for one file; the run routes and `validate` also mirror the runner's pairing refusals (beside a `method_ref`, or with no source on a run), while the crate routes leave theirs to the server, as they do the selector XOR. `resources` is reserved on `extra`, so it cannot bypass those checks. The valid verdict's `warnings` carry the template check's `template_check_warning` items, whose `template_file` and `template_location` `ValidationErrorItem` declares ahead of `mthds`, a difference the type-level pin states. See [bundle-resources.md](./bundle-resources.md).
|
|
117
120
|
- **Crate routes (`/v1/resolve`, `/v1/codegen`, `/v1/pipe-io`):** `resolve` emits the **normalized library crate** (the MTHDS standard's Library Crate Format, fingerprint riding inside the payload); `codegen` projects that crate through two explicit axes — `kind` (`types`) × `target` (`ts-zod` / `python-pydantic` / `python-structures`) — into stamped artifacts plus their `codegen.lock`. All three share one closure envelope (`files: [{content, source?}]` XOR a `method_ref`, the address form server-resolved and the registry form a `501`) and return a **discriminated 200 verdict** (`is_valid`, with `CrateInvalidReport` as the shared invalid arm) rather than a bare payload, the same discipline as `validate`. They also take the hosted `method_id` (`PipelexHostedToolingExtensions`) as a third closure form under the strict three-way XOR — a pass-through the platform resolves, meaningless against a bare runner. Writing the artifacts and lock verbatim reproduces a local `pipelex codegen types` run byte-for-byte, so the offline `pipelex codegen check` passes — the SDK stays transport-only and does not write files. Verifying such a tree afterwards is `runCodegenCheck`, a standalone pure helper rather than a client method, because it needs no server at all. `pipeIo` derives a method's three I/O artifacts — the standard's `PipeIOContracts`, `InputForm` and `OutputForm`, imported from `mthds/protocol` — for the pipe the route selects (the request's qualified `pipe_ref`, else the method's own entry pipe) or for every pipe under `all_pipes`, with the runnability facts beside them and the closure's files echoed on `include_files`, and it runs no dry run; a selection it refuses is a `422` typed `EntryPipeNotFoundError` or `EntryPipeAmbiguousError`. See [crate-routes.md](./crate-routes.md).
|
|
118
121
|
- **Model reference check (`/v1/models/check`):** `checkModelReference(reference, category?)`, a Pipelex API extension served by any `pipelex-api` runner from pipelex 0.78.0 and on the hosted API, whose platform relays it byte-identically. It is a **diagnostic endpoint** like `validate`: a reference that resolves nowhere is a `200` verdict with `resolution: "not_found"` and the names the caller may have meant (`suggestions`, `other_kinds`, `other_categories`), never a thrown error. Only a request that cannot produce a verdict throws `ApiResponseError`, a `422` typed by its `errorType`: `InvalidModelReference` for a reference that is blank, a sigil or a namespace alone, or past the runner's length limit, `InvalidModelCategory` for an unknown `type`, `ValidationError` for a missing or repeated parameter. The SDK validates none of it, so the runner's rule is the only one. `ModelReferenceVerdict` is a union of one arm per reference kind, discriminated on the wire's own `kind` (`PresetReferenceVerdict`, `AliasReferenceVerdict`, `WaterfallReferenceVerdict`, `HandleReferenceVerdict`): a `matches` entry carries no kind of its own, but every entry of one verdict shares the verdict's, so narrowing on `kind` types `matches` as `PresetMatch[]`, `AliasMatch[]`, `WaterfallMatch[]` or `HandleMatch[]` without a field the wire does not carry. `ModelCheckCategory` is the protocol's model categories plus `doc_gen`, the family of `PipeDocGen`, which the protocol and so `models` leave out but a method's `model` field can name. `resolution` is typed open, `ModelReferenceResolution | (string & {})`, under the spec's reader rule that a client reading a value it does not know treats the reference as unresolved, and so is every category a verdict names, `ModelCheckCategory | (string & {})` as `mthds` types `ModelInfo.type`, since a runner of a later version may add one; the `category` a request names stays closed. A match's `resolves_to` is `null` when the reference resolves by name but a run through it would reach no model the runner can call, which a consumer shows as a warning rather than as an unknown name.
|
|
119
122
|
- **Tools (`/v1/lint`, `/v1/format`):** `lint`, `format` — single-file static diagnostics and canonical formatting, served by any pipelex-api runner (no inference, no bundle load). Both are **diagnostic endpoints** like `validate`: a malformed `.mthds` file is a produced verdict on a **200** carrying `diagnostics[]`, never a thrown error — `format` additionally echoes the content back unchanged with `changed: false`. Only a no-verdict condition is non-2xx: a malformed body, **malformed formatter `options`** (e.g. a non-numeric `column_width` ⇒ 422), auth, or a server fault, all mapped to the typed `ApiResponseError`. `lint` is the cheap static check (syntax / schema / semantic); it does not replace `validate`, which loads the bundle, resolves across files, and dry-runs the pipes.
|
|
120
|
-
- **Durable run lifecycle (hosted extension — NOT protocol):** `getRunStatus`, `getRunResult` (with an optional `artifacts` selection sent as `?artifacts=`, see [run-results.md](./run-results.md#reading-only-some-artifacts--artifacts)), `waitForResult`, `startAndWaitForResult` (handshakes `/v1/version`, takes the durable start+poll path on a hosted deployment, and self-heals to the blocking `execute` against a bare runner). The handshake's outcome is cached for the client's lifetime, under the rule the Python SDK follows too: an answer that is not a usable version assumes hosted and lets the start surface the real error, while no answer at all throws its `ApiUnreachableError` at once, caches nothing and sends no start. An answer is anything the server sent back: an error status, a body that is not JSON, not UTF-8 or no version, and a body that arrived but could not be decoded, such as a broken gzip stream, which fetch fails with the decompressor's code (`Z_DATA_ERROR` and the other zlib codes, brotli's `ERR__ERROR_…`, zstd's `ZSTD_error_…`). A run that ended without completing answers the results read with a `409` whose `run_status` and `error` members carry its status and its stored error report; `getRunResult` turns it into the `failed` arm (`status`, `message` from `detail`, `error`) and the waiting calls on the durable path into a `RunFailedError` carrying the same (the blocking fallback's failure is the runner's `ApiResponseError`), the status read from the member, or from the `detail` sentence on a platform that does not send it yet. The status read serves the same report as `RunRead.error`. See [errors.md](./errors.md#a-failed-run--runerrorreport). Reading what a completed run consumed is `summarizeUsage(results)`, a standalone pure helper rather than a client method, because it folds the usage pair the results already carry and needs no server. See [run-usage.md](./run-usage.md#summarizing-a-run--summarizeusage).
|
|
123
|
+
- **Durable run lifecycle (hosted extension — NOT protocol):** `getRunStatus`, `getRunResult` (with an optional `artifacts` selection sent as `?artifacts=`, see [run-results.md](./run-results.md#reading-only-some-artifacts--artifacts)), `waitForResult`, `startAndWaitForResult` (handshakes `/v1/version`, takes the durable start+poll path on a hosted deployment, and self-heals to the blocking `execute` against a bare runner). The handshake's outcome is cached for the client's lifetime, under the rule the Python SDK follows too: an answer that is not a usable version assumes hosted and lets the start surface the real error, while no answer at all throws its `ApiUnreachableError` at once, caches nothing and sends no start. An answer is anything the server sent back: an error status, a body that is not JSON, not UTF-8 or no version, and a body that arrived but could not be decoded, such as a broken gzip stream, which fetch fails with the decompressor's code (`Z_DATA_ERROR` and the other zlib codes, brotli's `ERR__ERROR_…`, zstd's `ZSTD_error_…`). A run that ended without completing answers the results read with a `409` whose `run_status` and `error` members carry its status and its stored error report; `getRunResult` turns it into the `failed` arm (`status`, `message` from `detail`, `error`) and the waiting calls on the durable path into a `RunFailedError` carrying the same (the blocking fallback's failure is the runner's `ApiResponseError`), the status read from the member, or from the `detail` sentence on a platform that does not send it yet. The status read serves the same report as `RunRead.error`. See [errors.md](./errors.md#a-failed-run--runerrorreport). The status read also carries `degraded` and the `degradation` that explains it, whose `reason` tells a status the platform could not refresh (`status_unavailable`) from a run whose workflow task keeps failing until the platform ends it (`workflow_task_failing`), checked field by field so it never fails the poll, and the run's `request_id`, the id to quote to support. See [run-results.md](./run-results.md#a-degraded-status--degraded-and-degradation). Reading what a completed run consumed is `summarizeUsage(results)`, a standalone pure helper rather than a client method, because it folds the usage pair the results already carry and needs no server. See [run-usage.md](./run-usage.md#summarizing-a-run--summarizeusage).
|
|
121
124
|
- **Health:** `health` (origin-level `/health`), on the same transport as every route: an unreachable origin is an `ApiUnreachableError` and a non-2xx answer an `ApiResponseError` naming `/health`, whose verdict the fallback reads from the status. A 2xx whose body is not a JSON object is an `ApiResponseError` too, as on every protocol, extension and run-lifecycle route.
|
|
122
125
|
|
|
123
126
|
### Product routes (hosted management surface)
|
|
@@ -135,7 +138,7 @@ The hosted catalog/account routes the webapp drives. All ride the same `{base}/v
|
|
|
135
138
|
|
|
136
139
|
## Hook bundle (`dist-hooks/check.mjs`)
|
|
137
140
|
|
|
138
|
-
`src/hooks/` holds the post-edit `.mthds` hook pipeline (local lint/format via `@pipelex/tools-wasm`, then a `POST /v1/validate` verdict through this SDK), bundled into a single dependency-free ESM file by `npm run build:hook`. Its validate client is built by `src/hooks/validate-client.ts` with `appInfo: { name: "pipelex-mthds-check", version: SDK_VERSION }`, the `hook` surface's registry token, so the platform attributes its calls to the hook rather than to a plain SDK caller. The one specifier left external is `undici`, reached only by the artifact fetch path through a dynamic import the hook never executes, so the bundle does not inline an HTTP client it cannot use. **Distribution is source-checkout vendoring, not npm:** `pipelex-plugins` runs `make vendor-hook`, which builds `dist-hooks/check.mjs` in a sibling checkout of this repo and copies it in as a static hook asset — the published `@pipelex/sdk` tarball deliberately does not ship the bundle (it inlines the ~4 MB WASM engine, and no npm consumer of the SDK's library API uses it). `dist-hooks/` is gitignored; the consumer repo's own checks gate a stale vendored copy. `@pipelex/tools-wasm` is pinned to an exact version in `package.json`, because the engine carries the MTHDS schema the hook lints against: a newer schema reaches the hook only by a deliberate bump followed by a rebuild and a re-vendor, and the bundle's provenance line (`@pipelex/sdk <version> (<commit>) + @pipelex/tools-wasm <version> (npm)`) names the engine it embeds. Which file the hook checks on each platform, and how it finds the file a Codex patch run through the shell wrote, is in [`hook-bundle.md`](./hook-bundle.md).
|
|
141
|
+
`src/hooks/` holds the post-edit `.mthds` hook pipeline (local lint/format via `@pipelex/tools-wasm`, then a `POST /v1/validate` verdict through this SDK), bundled into a single dependency-free ESM file by `npm run build:hook`. Its validate client is built by `src/hooks/validate-client.ts` with `appInfo: { name: "pipelex-mthds-check", version: SDK_VERSION }`, the `hook` surface's registry token, so the platform attributes its calls to the hook rather than to a plain SDK caller. The one specifier left external is `undici`, reached only by the artifact fetch path through a dynamic import the hook never executes, so the bundle does not inline an HTTP client it cannot use. **Distribution is source-checkout vendoring, not npm:** `pipelex-plugins` runs `make vendor-hook`, which builds `dist-hooks/check.mjs` in a sibling checkout of this repo and copies it in as a static hook asset — the published `@pipelex/sdk` tarball deliberately does not ship the bundle (it inlines the ~4 MB WASM engine, and no npm consumer of the SDK's library API uses it). `dist-hooks/` is gitignored; the consumer repo's own checks gate a stale vendored copy. `@pipelex/tools-wasm` is pinned to an exact version in `package.json`, because the engine carries the MTHDS schema the hook lints against: a newer schema reaches the hook only by a deliberate bump followed by a rebuild and a re-vendor, and the bundle's provenance line (`@pipelex/sdk <version> (<commit>) + @pipelex/tools-wasm <version> (npm)`) names the engine it embeds. Its bundle gatherer (`src/hooks/bundle-gather.ts`) parses each `.mthds` file it sends with `smol-toml`, which the bundle inlines, to find the template files its `PipeDocGen` steps name, and sends them as the request's `resources`. Which file the hook checks on each platform, what it sends with it, and how it finds the file a Codex patch run through the shell wrote, is in [`hook-bundle.md`](./hook-bundle.md).
|
|
139
142
|
|
|
140
143
|
## Testing
|
|
141
144
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# The files a bundle's steps read (`resources`)
|
|
2
|
+
|
|
3
|
+
A bundle sent to the API as content has no directory, so a file one of its steps reads from beside the bundle has to travel with it. Today that is a `PipeDocGen` step's `template_file`: the Word, Excel or PowerPoint template it fills, or the HTML template it prints as a PDF. The runner takes such files as the request's `resources`, a JSON object mapping each file's path, exactly as the step names it, to the file's bytes in standard padded base64. This SDK takes the bytes from you, keyed by that path, and encodes them.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
|
|
8
|
+
const mthds = await readFile("invoicing/bundle.mthds", "utf8");
|
|
9
|
+
const resources = {
|
|
10
|
+
"templates/invoice.docx": await readFile("invoicing/templates/invoice.docx"),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const verdict = await client.validate([mthds], false, ["bundle.mthds"], undefined, undefined, {
|
|
14
|
+
resources,
|
|
15
|
+
});
|
|
16
|
+
const result = await client.startAndWaitForResult({ mthds_contents: [mthds], resources, inputs });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Where they go
|
|
20
|
+
|
|
21
|
+
| Call | Where `resources` goes |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `validate(contents, …, options)` | `options.resources`, beside inline contents or a hosted `{ method_id }` |
|
|
24
|
+
| `validateFiles(files, options)` | `options.resources` |
|
|
25
|
+
| `execute(options)`, `start(options)`, `startAndWaitForResult(options)` | `options.resources`, beside `mthds_contents`, a method bundle (`files` or `bundle_b64`) or a hosted `method_id`; the blocking fallback a bare runner takes sends them too |
|
|
26
|
+
| `pipeIo(request)`, `resolve(request)`, `codegen(request)` | `request.resources`, beside inline `files` |
|
|
27
|
+
| `prepareInputs(request)` | `request.resources`, beside inline `files`, sent with the one pipe I/O request that reads the signature |
|
|
28
|
+
|
|
29
|
+
An absent, `null` or empty map is not sent.
|
|
30
|
+
|
|
31
|
+
A request carrying `resources` can be large, so every route gives it the 20-minute execute ceiling: `validate` and `validateFiles` have it by default (a caller's `timeoutMs` still overrides it), and `start` and the crate routes raise their shorter default to it for a request that carries a file. On the hosted API the gateway caps a request at about 30 seconds whatever the client allows.
|
|
32
|
+
|
|
33
|
+
## The key is the path the step names
|
|
34
|
+
|
|
35
|
+
Each key is the file's path relative to the bundle, written with forward slashes, exactly as the step's `template_file` writes it: `templates/invoice.docx`. The runner collapses `.` segments and repeated slashes, so `./templates/invoice.docx` names the same file, and the SDK sends the key as you gave it. Every bundle of a request is read as sitting in one directory, so a step in `steps/print.mthds` that names `templates/invoice.docx` reads the file sent under `templates/invoice.docx`, wherever it sat on your disk.
|
|
36
|
+
|
|
37
|
+
## The value is the file's bytes
|
|
38
|
+
|
|
39
|
+
A value is a `Uint8Array` (a Node `Buffer` is one), an `ArrayBuffer` or a `Blob`, the byte forms `uploadFile` takes. The SDK sends each in standard base64 with padding. A string is refused: it would have to be a path the SDK reads or base64 it decodes, and it is neither. The SDK reads no file because a request named it, which keeps a server that sends a request on a caller's behalf from reading its own files; read the file yourself and pass its bytes.
|
|
40
|
+
|
|
41
|
+
## What the SDK refuses before any request
|
|
42
|
+
|
|
43
|
+
Each of these is a `RequestArgumentError`, `input` and not retryable, thrown before anything is encoded or sent:
|
|
44
|
+
|
|
45
|
+
- **A key that cannot name a file inside the bundle**: an empty path or one of nothing but whitespace, as Python counts it, a backslash, a colon, a NUL character, an absolute path, a `..` segment, or a path that names no file (`./`). It is the runner's own rule, which it answers with a `422` `InvalidResourcePath`.
|
|
46
|
+
- **Two keys that name one file** once `.` segments and repeated slashes collapse, such as `a.docx` and `./a.docx`.
|
|
47
|
+
- **A value that is not bytes**, and a `resources` that is not a plain object mapping paths to bytes: a `Map`, an array or a class instance is refused rather than read as carrying no file (pass `Object.fromEntries(map)`).
|
|
48
|
+
- **`resources` in `extra`**: it is a named option, so the passthrough cannot carry it past these checks.
|
|
49
|
+
- **A pairing the runner refuses**, on the routes where the SDK already mirrors the runner's pairings. On `execute` and `start`, `resources` beside a `method_ref`, whose files are its package's own, or with no source at all for them to accompany. On `validate`, `resources` beside a `{ method_ref }`. A hosted `method_id` without inline source is left to the server, since the platform resolves it into the bundle the runner reads the files with. `prepareInputs` takes `resources` beside inline `files` only, and refuses them beside a `method_ref` or a `method_id` with an `InputPreparationError`.
|
|
50
|
+
|
|
51
|
+
The crate routes post their request as given, apart from encoding `resources`, and leave their pairings to the server, which answers `resources` beside a `method_ref` with a `422` `ValidationError`.
|
|
52
|
+
|
|
53
|
+
The sizes are left to the server, since a deployment configures them.
|
|
54
|
+
|
|
55
|
+
## What the server refuses
|
|
56
|
+
|
|
57
|
+
Every refusal is an `ApiResponseError`, `input` and not retryable; branch on its `errorType`:
|
|
58
|
+
|
|
59
|
+
| Status | `errorType` | When |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `422` | `InvalidResourcePath` | A key that cannot name a file inside the bundle, two keys for one file, or, on a run route, two different files under one path, one sent in `resources` and one shipped in a method bundle. |
|
|
62
|
+
| `422` | `BundleResourceNotNamedError` | A file no step of the bundle names, on every route, the diagnostic ones included; its detail lists the unnamed paths. A misspelled key fails here rather than leaving its step without its file. |
|
|
63
|
+
| `422` | `ValidationError` | `resources` beside a `method_ref`, or on a run route with no bundle. |
|
|
64
|
+
| `413` | `PayloadTooLarge` | More files than the deployment allows (32 by default), a file over its per-file limit (5 MiB by default), or more bytes in all than its total (10 MiB by default), counting the files a method bundle ships for its steps. |
|
|
65
|
+
| `413` | `BundleResourcesTooLargeError` | The `.html` template files, which travel inline with the method wherever it runs, over 512 KiB in all. An office template file is not counted, since a run handed to a remote orchestrator carries it by reference to the run's storage. |
|
|
66
|
+
| `400` | `InvalidBase64` | A value that is not base64, which a request this SDK encoded never carries. |
|
|
67
|
+
|
|
68
|
+
## A step whose file was not sent
|
|
69
|
+
|
|
70
|
+
A step that names a template file the request did not send cannot load. On `validate`, `pipeIo`, `resolve` and `codegen` that is an `is_valid: false` verdict whose validation error says the file was not supplied with its bundle and names the key it is expected under; `prepareInputs` throws it as a `MethodLoadError`. On `execute` and `start` it is a `422` `ValidateBundleError`, whose `validationErrors` say the same.
|
|
71
|
+
|
|
72
|
+
## The template check's warnings
|
|
73
|
+
|
|
74
|
+
A dry run hands an office template file it was sent to the document engine's template check. An error finding fails the step's dry run, and so the verdict, as a `dry_run` validation error on the step. A warning finding, such as a field of the template that matches no input, rides the valid verdict's `warnings` as a `template_check_warning` item:
|
|
75
|
+
|
|
76
|
+
| Member | Value |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `category` | `dry_run` |
|
|
79
|
+
| `error_type` | `template_check_warning` |
|
|
80
|
+
| `pipe_code`, `domain_code` | The `PipeDocGen` step |
|
|
81
|
+
| `template_file` | The template file as the step names it; `null` for a step of the server's own library, whose path is never shown |
|
|
82
|
+
| `template_location` | The place in the file the finding names, in the engine's words (a defined name, a table, a cell, a shape, a tag); `null` when it names none |
|
|
83
|
+
| `message` | The finding in words |
|
|
84
|
+
|
|
85
|
+
The warnings come in families, always in this order: `optional_force_redundant`, `input_presence_vacuous`, the `hint_*` lints, then `template_check_warning`. None of them flips `is_valid`.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
if (verdict.is_valid) {
|
|
89
|
+
for (const warning of verdict.warnings) {
|
|
90
|
+
if (warning.error_type === "template_check_warning") {
|
|
91
|
+
console.warn(`${warning.template_file ?? "?"} ${warning.template_location ?? ""}: ${warning.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Method bundles
|
|
98
|
+
|
|
99
|
+
On `execute` and `start`, a method bundle sent as a zip (`bundle_b64`) may ship a template file beside the `.mthds` file whose step names it, as it sits on disk, and the runner reads it as if it had come in `resources`, under the same limits. A file both shipped and sent must hold the same bytes. The `files` map of a method bundle carries text, so it can ship an `.html` template but not an office one, which goes in `bundle_b64` or in `resources`.
|
|
100
|
+
|
|
101
|
+
## The `.mthds` check hook
|
|
102
|
+
|
|
103
|
+
The post-edit hook this package builds sends the template files of the bundle it validates: see [`hook-bundle.md`](./hook-bundle.md#what-the-hook-does-with-a-file).
|
|
104
|
+
|
|
105
|
+
## Which servers read them
|
|
106
|
+
|
|
107
|
+
A `pipelex-api` runner reads `resources` from the first pipelex release after 0.84.0. The hosted API reads them once its platform forwards them to a runner on such a release. A runner that predates the field ignores it, so a step that names a template file fails to load there, the verdict saying the file was not supplied.
|