@pipelex/sdk 0.57.0 → 0.59.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/dist/cli/script.d.ts +1 -1
- package/dist/client.d.ts +101 -5
- package/dist/client.js +145 -12
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +3 -2
- package/dist/errors.js +3 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +8 -0
- package/dist/product-models.d.ts +24 -4
- package/dist/runs.d.ts +7 -0
- package/dist/runs.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/webhooks.d.ts +286 -0
- package/dist/webhooks.js +318 -0
- package/dist/webhooks.js.map +1 -0
- package/docs/architecture.md +8 -2
- package/docs/webhooks.md +119 -0
- package/package.json +1 -1
package/dist/cli/script.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { CommandIO } from "./io.js";
|
|
|
15
15
|
* update notice and npm 12's run notices off the script's stderr, which carries the command's
|
|
16
16
|
* own lines, while npm's warnings and errors still print.
|
|
17
17
|
*/
|
|
18
|
-
export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.
|
|
18
|
+
export declare const SCRIPT_RUNNER = "npx --yes --no-update-notifier --loglevel=warn @pipelex/sdk@0.59.0";
|
|
19
19
|
export declare const SCRIPT_WRITER = "@pipelex/sdk";
|
|
20
20
|
export declare const SCRIPT_NEEDS = "Node 22.12 or later";
|
|
21
21
|
/** Run `pipelex-sdk script` and return its exit code. */
|
package/dist/client.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type GetRunResultOptions, type RunProgress, type RunRead, type RunResul
|
|
|
4
4
|
import type { BillingPortalResponse, ChangePlanResponse, CheckoutResponse, DraftFileFromFileInput, DraftFileFromFileResult, FileArchive, FileArchiveRequest, FileDetail, FileLinkTarget, FileLinksResult, FilePage, FileRecord, FileRecordList, FileRenameInput, FileSummary, GetFileQuery, GetFilesByUriOptions, InvoiceView, Membership, MembershipsResponse, OrganizationBranding, OrganizationBrandingUpdate, ListFilesQuery, ListMethodCopiesQuery, ListMethodExamplesQuery, ListMethodsQuery, ListMethodVersionsQuery, ListRunFilesQuery, MethodCopyInput, MethodCopyPage, MethodCopyResult, MethodData, MethodDeletionAccepted, MethodDetail, MethodDraftInput, MethodExample, MethodExampleAssistantSaveInput, MethodExampleAssistantSaveResult, MethodExampleCreateInput, MethodExampleLatestRunInput, MethodExampleLatestRunResult, MethodExamplePage, MethodExampleUpdateInput, MethodPage, MethodPublishInput, MethodPublishResult, MethodRenameInput, MethodSummary, MethodVersion, MethodVersionPage, MethodWriteInput, OnboardingSubmission, PipelexApiKeyCreated, PipelexApiKeyList, ListRunsQuery, RunHistoryItem, RunDetail, RunFilesPage, RunPage, PlanView, ResolvedStorageUrl, SavedMethodFile, SubscriptionResponse, UpdateRunInput, UploadGrant, UploadGrantInput, UploadInput, UploadedFile, UserProfile } from "./product-models.js";
|
|
5
5
|
import type { MethodTreeForms, MethodTreeLimits } from "./method-trees.js";
|
|
6
6
|
import type { AppInfo } from "./user-agent.js";
|
|
7
|
+
import type { WebhookDeliveryDetail, WebhookDeliveryList, WebhookReplayAccepted, WebhookSecret, WebhookSecretRotateOptions } from "./webhooks.js";
|
|
7
8
|
import type { UploadableAsset, UploadFileOptions, UploadRecord } from "./upload.js";
|
|
8
9
|
import type { PrepareInputsRequest, PreparedInputs } from "./prepare-inputs.js";
|
|
9
10
|
import type { BulkResolveStorageUrlsInput, BulkResolvedStorageUrls, DownloadArtifactsRequest, DownloadArtifactsResult, FetchArtifactOptions, ResolvedArtifact } from "./artifacts.js";
|
|
@@ -37,6 +38,27 @@ export interface PipelexApiRunExtensions {
|
|
|
37
38
|
* An empty string is treated as absent and is not sent.
|
|
38
39
|
*/
|
|
39
40
|
method_ref?: string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Webhook URLs to call once the run has ended, each with a signed `run.finished` event whose
|
|
43
|
+
* `data` is the run's status read, so a caller learns of the end without polling. Read the call
|
|
44
|
+
* with `verifyWebhook`, then fetch the results with the event's `pipeline_run_id`; see
|
|
45
|
+
* `docs/webhooks.md`.
|
|
46
|
+
*
|
|
47
|
+
* A layer-2 extension: the hosted API and a self-hosted runner both take it on `start` and
|
|
48
|
+
* `execute`, and each holds the URLs to its own rules, which this client does not restate. The
|
|
49
|
+
* hosted API takes at most 5 HTTPS URLs of at most 2,048 characters, none repeated, and refuses a
|
|
50
|
+
* URL whose host resolves to a private, loopback, link-local or metadata address with a `422`
|
|
51
|
+
* `request.invalid` naming it as `callback_urls.<index>`; a deployment that does not deliver
|
|
52
|
+
* caller webhooks refuses the list with a `422` `request.unsupported`. A self-hosted runner
|
|
53
|
+
* whose operator configured no signing secret refuses it with a `422`
|
|
54
|
+
* `pipelex.webhook.secret_not_configured`. The start's acknowledgement and every read of the run
|
|
55
|
+
* list the URLs redacted, as `callback_urls`.
|
|
56
|
+
*
|
|
57
|
+
* Not a run source: a run still needs something to run. A value that is not an array of strings
|
|
58
|
+
* is a `RequestArgumentError` before anything is sent, and an empty array is treated as absent
|
|
59
|
+
* and is not sent.
|
|
60
|
+
*/
|
|
61
|
+
callback_urls?: readonly string[] | null;
|
|
40
62
|
}
|
|
41
63
|
/**
|
|
42
64
|
* The hosted API's own run arguments — the layer-3 extensions this client adds
|
|
@@ -1557,6 +1579,31 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
1557
1579
|
* the write may have landed or not: the same input sent again is safe.
|
|
1558
1580
|
*/
|
|
1559
1581
|
updateOrganizationBranding(orgId: string, input: OrganizationBrandingUpdate): Promise<OrganizationBranding>;
|
|
1582
|
+
/**
|
|
1583
|
+
* The organization's webhook signing secret — `GET /v1/organizations/{org_id}/webhook-secret`:
|
|
1584
|
+
* the `whsec_` secret every caller webhook of the organization is signed with, which a receiver
|
|
1585
|
+
* hands to `verifyWebhook`. The platform generates it on this first read when the organization
|
|
1586
|
+
* has none. Treat the answer as a credential: never log or cache it.
|
|
1587
|
+
*
|
|
1588
|
+
* An admin's route, from a web session only: the hosted gateway refuses an API key on it. Throws
|
|
1589
|
+
* `ApiResponseError`: `403` `auth.forbidden` whose `params.requirement` names `admin` for any
|
|
1590
|
+
* other role, `403` `pipelex.org.mismatch` for an organization other than the caller's active
|
|
1591
|
+
* one, and `404` `pipelex.org.not_found` for one with no record.
|
|
1592
|
+
*/
|
|
1593
|
+
getWebhookSecret(orgId: string): Promise<WebhookSecret>;
|
|
1594
|
+
/**
|
|
1595
|
+
* Rotate the organization's webhook signing secret — `POST
|
|
1596
|
+
* /v1/organizations/{org_id}/webhook-secret/rotate`: a new secret is current at once, and the
|
|
1597
|
+
* one it replaces keeps signing beside it for 24 hours, then is dropped, so a receiver has a day
|
|
1598
|
+
* to take the new one. An organization with no secret yet gets its first. Answers the new secret,
|
|
1599
|
+
* as `getWebhookSecret` does, with its guards and refusals.
|
|
1600
|
+
*
|
|
1601
|
+
* `options.idempotencyKey` is sent as the `Idempotency-Key` header. The answer carries a secret,
|
|
1602
|
+
* so the platform never replays it: the same key sent again is refused `409`
|
|
1603
|
+
* `request.already_executed` and rotates nothing more, and the secret is then read with
|
|
1604
|
+
* `getWebhookSecret`. A malformed key is a `RequestArgumentError` before anything is sent.
|
|
1605
|
+
*/
|
|
1606
|
+
rotateWebhookSecret(orgId: string, options?: WebhookSecretRotateOptions): Promise<WebhookSecret>;
|
|
1560
1607
|
/** The active org's subscription state — `GET /v1/billing/subscription`. */
|
|
1561
1608
|
getSubscription(): Promise<SubscriptionResponse>;
|
|
1562
1609
|
/** Available plans (with `is_current`) — `GET /v1/billing/plans`. */
|
|
@@ -1965,9 +2012,14 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
1965
2012
|
* each from the catalog and the organization's members.
|
|
1966
2013
|
*
|
|
1967
2014
|
* Each item is a `RunHistoryItem` — the id, method, author, status,
|
|
1968
|
-
* timestamps, pipe and, for a failed run, its own problem.
|
|
1969
|
-
*
|
|
1970
|
-
*
|
|
2015
|
+
* timestamps, pipe and, for a failed run, its own problem. A row also says
|
|
2016
|
+
* whether its results are stored (`results_stored`), so a history reads no
|
|
2017
|
+
* results for a row whose results were not kept, such as a blocking
|
|
2018
|
+
* `execute` run's, whose `getRunResult` would answer
|
|
2019
|
+
* `404 run.results_not_stored`. A platform that predates the field leaves it
|
|
2020
|
+
* out, and an absent field does not mean `false`. The run's organization,
|
|
2021
|
+
* workflow id and result location are not on the list; `getRunDetail`
|
|
2022
|
+
* returns them.
|
|
1971
2023
|
*
|
|
1972
2024
|
* `createdFrom` / `createdTo` are applied server-side as index key
|
|
1973
2025
|
* conditions, so a bounded page genuinely reads less. They are INSTANTS,
|
|
@@ -2061,6 +2113,50 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
2061
2113
|
* string nor `null`, is an answer the SDK cannot read, thrown as an `ApiResponseError` too.
|
|
2062
2114
|
*/
|
|
2063
2115
|
listRunFiles(runId: string, query?: ListRunFilesQuery): Promise<RunFilesPage>;
|
|
2116
|
+
/**
|
|
2117
|
+
* A run's caller-webhook deliveries — `GET /v1/runs/{id}/webhook-deliveries`: one per URL the
|
|
2118
|
+
* run registered in `callback_urls`, in their order, once the run has ended, each with its
|
|
2119
|
+
* state and every attempt, its URL redacted. A run that registered none, or has not ended, lists
|
|
2120
|
+
* none. Any member of the organization reads them, by web session or API key.
|
|
2121
|
+
*
|
|
2122
|
+
* Throws `RequestArgumentError` for a run id that is empty, `.` or `..`, or holds a lone
|
|
2123
|
+
* surrogate, before any request. Throws `ApiResponseError`: `404` `run.not_found` for a run that
|
|
2124
|
+
* is not the organization's, exactly as for one that does not exist. An answer whose
|
|
2125
|
+
* `deliveries` is not an array is an answer the SDK cannot read, thrown as an `ApiResponseError`
|
|
2126
|
+
* too.
|
|
2127
|
+
*/
|
|
2128
|
+
listRunWebhookDeliveries(runId: string): Promise<WebhookDeliveryList>;
|
|
2129
|
+
/**
|
|
2130
|
+
* One delivery of a run's webhooks, with its event as sent —
|
|
2131
|
+
* `GET /v1/runs/{id}/webhook-deliveries/{delivery_id}`: the body of its latest attempt, which a
|
|
2132
|
+
* receiver's log can be matched against byte for byte once written as compact JSON.
|
|
2133
|
+
* `deliveryId` is the delivery's `delivery_id`, the receiver's `webhook-id`.
|
|
2134
|
+
*
|
|
2135
|
+
* Throws `RequestArgumentError` for an id that is empty, `.` or `..`, or holds a lone surrogate,
|
|
2136
|
+
* before any request. Throws `ApiResponseError`: `404` `run.not_found` for a run the caller
|
|
2137
|
+
* cannot read, and `422` `request.invalid` naming `delivery_id` for a delivery the run does not
|
|
2138
|
+
* have.
|
|
2139
|
+
*/
|
|
2140
|
+
getRunWebhookDelivery(runId: string, deliveryId: string): Promise<WebhookDeliveryDetail>;
|
|
2141
|
+
/**
|
|
2142
|
+
* Send a settled delivery again — `POST /v1/runs/{id}/webhook-deliveries/{delivery_id}/replay`:
|
|
2143
|
+
* one new attempt under the same `webhook-id`, with the same `data`, the next `attempt`, a fresh
|
|
2144
|
+
* timestamp, and the signatures of the secrets live when it is sent. A replay the receiver
|
|
2145
|
+
* accepts marks the delivery `delivered`; one it does not leaves its state as it was, and the
|
|
2146
|
+
* attempt is recorded either way. A replay never returns a delivery to `pending`. Anyone who
|
|
2147
|
+
* can read the run asks for one, an API key included, under the organization's replay rate.
|
|
2148
|
+
*
|
|
2149
|
+
* Answers the attempt number the replay is sent under; a replay asked again before it is sent is
|
|
2150
|
+
* that same attempt. Throws `RequestArgumentError` for an id that is empty, `.` or `..`, or holds
|
|
2151
|
+
* a lone surrogate, before any request. Throws `ApiResponseError`: `404` `run.not_found` for a
|
|
2152
|
+
* run the caller cannot read; `422` `request.invalid` naming `delivery_id` for a delivery the run
|
|
2153
|
+
* does not have; `409` `pipelex.org.not_manageable` with the reason `webhook_delivery_pending`
|
|
2154
|
+
* for a delivery still on its retry schedule, or `webhook_delivery_attempts_exhausted` for one
|
|
2155
|
+
* holding the most attempts a delivery holds; `429` `account.rate_limited`, with its
|
|
2156
|
+
* `retry_after`, past the organization's replay rate; and a `500` or `503` when the replay could
|
|
2157
|
+
* not be queued, which then counts nothing against the rate.
|
|
2158
|
+
*/
|
|
2159
|
+
replayRunWebhookDelivery(runId: string, deliveryId: string): Promise<WebhookReplayAccepted>;
|
|
2064
2160
|
/** Patch a run's status (admin/manual) — `PUT /v1/runs/{id}`. */
|
|
2065
2161
|
updateRun(runId: string, input: UpdateRunInput): Promise<void>;
|
|
2066
2162
|
/**
|
|
@@ -2074,8 +2170,8 @@ export declare class PipelexApiClient implements HostedProtocolSurface {
|
|
|
2074
2170
|
/**
|
|
2075
2171
|
* Blocking `POST /v1/execute` adapted onto `RunResults` — the bare-runner
|
|
2076
2172
|
* path. Forwards every protocol field PLUS every extension surface: the
|
|
2077
|
-
* runner-resolved `method_ref`, the
|
|
2078
|
-
* `extra` passthrough. An extension-only call (`{ extra }` with no
|
|
2173
|
+
* runner-resolved `method_ref`, the run's `callback_urls`, the hosted
|
|
2174
|
+
* `method_id`, and the generic `extra` passthrough. An extension-only call (`{ extra }` with no
|
|
2079
2175
|
* pipe_code/bundle) or a vendor selector riding `extra` must survive this
|
|
2080
2176
|
* path, not just the durable one — a `method_ref` run must run the same
|
|
2081
2177
|
* fetched package here, and a hosted `method_id` must reach the server too,
|
package/dist/client.js
CHANGED
|
@@ -240,6 +240,10 @@ const ARCHIVE_ID_ARGUMENT = {
|
|
|
240
240
|
name: "archive_id",
|
|
241
241
|
what: "an archive id, the archive's non-empty `archive_id`",
|
|
242
242
|
};
|
|
243
|
+
const DELIVERY_ID_ARGUMENT = {
|
|
244
|
+
name: "delivery_id",
|
|
245
|
+
what: "a delivery id, the delivery's non-empty `delivery_id`",
|
|
246
|
+
};
|
|
243
247
|
/** What the run id of each example write is, as a refusal of it names it. */
|
|
244
248
|
const CREATE_RUN_ID_ARGUMENT = {
|
|
245
249
|
name: "run_id",
|
|
@@ -2939,6 +2943,39 @@ export class PipelexApiClient {
|
|
|
2939
2943
|
const segment = idPathSegment(orgId, "updateOrganizationBranding", ORG_ID_ARGUMENT);
|
|
2940
2944
|
return this.requestProductObject("PATCH", `organizations/${segment}/branding`, input);
|
|
2941
2945
|
}
|
|
2946
|
+
/**
|
|
2947
|
+
* The organization's webhook signing secret — `GET /v1/organizations/{org_id}/webhook-secret`:
|
|
2948
|
+
* the `whsec_` secret every caller webhook of the organization is signed with, which a receiver
|
|
2949
|
+
* hands to `verifyWebhook`. The platform generates it on this first read when the organization
|
|
2950
|
+
* has none. Treat the answer as a credential: never log or cache it.
|
|
2951
|
+
*
|
|
2952
|
+
* An admin's route, from a web session only: the hosted gateway refuses an API key on it. Throws
|
|
2953
|
+
* `ApiResponseError`: `403` `auth.forbidden` whose `params.requirement` names `admin` for any
|
|
2954
|
+
* other role, `403` `pipelex.org.mismatch` for an organization other than the caller's active
|
|
2955
|
+
* one, and `404` `pipelex.org.not_found` for one with no record.
|
|
2956
|
+
*/
|
|
2957
|
+
async getWebhookSecret(orgId) {
|
|
2958
|
+
const segment = idPathSegment(orgId, "getWebhookSecret", ORG_ID_ARGUMENT);
|
|
2959
|
+
return this.requestProductObject("GET", `organizations/${segment}/webhook-secret`);
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Rotate the organization's webhook signing secret — `POST
|
|
2963
|
+
* /v1/organizations/{org_id}/webhook-secret/rotate`: a new secret is current at once, and the
|
|
2964
|
+
* one it replaces keeps signing beside it for 24 hours, then is dropped, so a receiver has a day
|
|
2965
|
+
* to take the new one. An organization with no secret yet gets its first. Answers the new secret,
|
|
2966
|
+
* as `getWebhookSecret` does, with its guards and refusals.
|
|
2967
|
+
*
|
|
2968
|
+
* `options.idempotencyKey` is sent as the `Idempotency-Key` header. The answer carries a secret,
|
|
2969
|
+
* so the platform never replays it: the same key sent again is refused `409`
|
|
2970
|
+
* `request.already_executed` and rotates nothing more, and the secret is then read with
|
|
2971
|
+
* `getWebhookSecret`. A malformed key is a `RequestArgumentError` before anything is sent.
|
|
2972
|
+
*/
|
|
2973
|
+
async rotateWebhookSecret(orgId, options = {}) {
|
|
2974
|
+
const endpoint = `organizations/${idPathSegment(orgId, "rotateWebhookSecret", ORG_ID_ARGUMENT)}/webhook-secret/rotate`;
|
|
2975
|
+
const idempotencyKey = idempotencyKeyOf(options, "rotate the secret");
|
|
2976
|
+
const res = await this.requestProductAnswer("POST", endpoint, undefined, { idempotencyKey });
|
|
2977
|
+
return this.readObjectAnswer("POST", endpoint, res);
|
|
2978
|
+
}
|
|
2942
2979
|
/** The active org's subscription state — `GET /v1/billing/subscription`. */
|
|
2943
2980
|
async getSubscription() {
|
|
2944
2981
|
return this.requestProduct("GET", "billing/subscription");
|
|
@@ -3522,9 +3559,14 @@ export class PipelexApiClient {
|
|
|
3522
3559
|
* each from the catalog and the organization's members.
|
|
3523
3560
|
*
|
|
3524
3561
|
* Each item is a `RunHistoryItem` — the id, method, author, status,
|
|
3525
|
-
* timestamps, pipe and, for a failed run, its own problem.
|
|
3526
|
-
*
|
|
3527
|
-
*
|
|
3562
|
+
* timestamps, pipe and, for a failed run, its own problem. A row also says
|
|
3563
|
+
* whether its results are stored (`results_stored`), so a history reads no
|
|
3564
|
+
* results for a row whose results were not kept, such as a blocking
|
|
3565
|
+
* `execute` run's, whose `getRunResult` would answer
|
|
3566
|
+
* `404 run.results_not_stored`. A platform that predates the field leaves it
|
|
3567
|
+
* out, and an absent field does not mean `false`. The run's organization,
|
|
3568
|
+
* workflow id and result location are not on the list; `getRunDetail`
|
|
3569
|
+
* returns them.
|
|
3528
3570
|
*
|
|
3529
3571
|
* `createdFrom` / `createdTo` are applied server-side as index key
|
|
3530
3572
|
* conditions, so a bounded page genuinely reads less. They are INSTANTS,
|
|
@@ -3664,6 +3706,69 @@ export class PipelexApiClient {
|
|
|
3664
3706
|
}
|
|
3665
3707
|
return { ...rest, nextCursor };
|
|
3666
3708
|
}
|
|
3709
|
+
/**
|
|
3710
|
+
* A run's caller-webhook deliveries — `GET /v1/runs/{id}/webhook-deliveries`: one per URL the
|
|
3711
|
+
* run registered in `callback_urls`, in their order, once the run has ended, each with its
|
|
3712
|
+
* state and every attempt, its URL redacted. A run that registered none, or has not ended, lists
|
|
3713
|
+
* none. Any member of the organization reads them, by web session or API key.
|
|
3714
|
+
*
|
|
3715
|
+
* Throws `RequestArgumentError` for a run id that is empty, `.` or `..`, or holds a lone
|
|
3716
|
+
* surrogate, before any request. Throws `ApiResponseError`: `404` `run.not_found` for a run that
|
|
3717
|
+
* is not the organization's, exactly as for one that does not exist. An answer whose
|
|
3718
|
+
* `deliveries` is not an array is an answer the SDK cannot read, thrown as an `ApiResponseError`
|
|
3719
|
+
* too.
|
|
3720
|
+
*/
|
|
3721
|
+
async listRunWebhookDeliveries(runId) {
|
|
3722
|
+
const segment = idPathSegment(runId, "listRunWebhookDeliveries", RUN_ID_ARGUMENT);
|
|
3723
|
+
const endpoint = `runs/${segment}/webhook-deliveries`;
|
|
3724
|
+
const res = await this.requestProductAnswer("GET", endpoint, undefined);
|
|
3725
|
+
const list = this.readObjectAnswer("GET", endpoint, res);
|
|
3726
|
+
if (!Array.isArray(list.deliveries)) {
|
|
3727
|
+
throw this.unreadableAnswer("GET", `/${API_PREFIX}/${endpoint}`, res, "a run's deliveries whose `deliveries` is not an array");
|
|
3728
|
+
}
|
|
3729
|
+
return list;
|
|
3730
|
+
}
|
|
3731
|
+
/**
|
|
3732
|
+
* One delivery of a run's webhooks, with its event as sent —
|
|
3733
|
+
* `GET /v1/runs/{id}/webhook-deliveries/{delivery_id}`: the body of its latest attempt, which a
|
|
3734
|
+
* receiver's log can be matched against byte for byte once written as compact JSON.
|
|
3735
|
+
* `deliveryId` is the delivery's `delivery_id`, the receiver's `webhook-id`.
|
|
3736
|
+
*
|
|
3737
|
+
* Throws `RequestArgumentError` for an id that is empty, `.` or `..`, or holds a lone surrogate,
|
|
3738
|
+
* before any request. Throws `ApiResponseError`: `404` `run.not_found` for a run the caller
|
|
3739
|
+
* cannot read, and `422` `request.invalid` naming `delivery_id` for a delivery the run does not
|
|
3740
|
+
* have.
|
|
3741
|
+
*/
|
|
3742
|
+
async getRunWebhookDelivery(runId, deliveryId) {
|
|
3743
|
+
const call = "getRunWebhookDelivery";
|
|
3744
|
+
const run = idPathSegment(runId, call, RUN_ID_ARGUMENT);
|
|
3745
|
+
const delivery = idPathSegment(deliveryId, call, DELIVERY_ID_ARGUMENT);
|
|
3746
|
+
return this.requestProductObject("GET", `runs/${run}/webhook-deliveries/${delivery}`);
|
|
3747
|
+
}
|
|
3748
|
+
/**
|
|
3749
|
+
* Send a settled delivery again — `POST /v1/runs/{id}/webhook-deliveries/{delivery_id}/replay`:
|
|
3750
|
+
* one new attempt under the same `webhook-id`, with the same `data`, the next `attempt`, a fresh
|
|
3751
|
+
* timestamp, and the signatures of the secrets live when it is sent. A replay the receiver
|
|
3752
|
+
* accepts marks the delivery `delivered`; one it does not leaves its state as it was, and the
|
|
3753
|
+
* attempt is recorded either way. A replay never returns a delivery to `pending`. Anyone who
|
|
3754
|
+
* can read the run asks for one, an API key included, under the organization's replay rate.
|
|
3755
|
+
*
|
|
3756
|
+
* Answers the attempt number the replay is sent under; a replay asked again before it is sent is
|
|
3757
|
+
* that same attempt. Throws `RequestArgumentError` for an id that is empty, `.` or `..`, or holds
|
|
3758
|
+
* a lone surrogate, before any request. Throws `ApiResponseError`: `404` `run.not_found` for a
|
|
3759
|
+
* run the caller cannot read; `422` `request.invalid` naming `delivery_id` for a delivery the run
|
|
3760
|
+
* does not have; `409` `pipelex.org.not_manageable` with the reason `webhook_delivery_pending`
|
|
3761
|
+
* for a delivery still on its retry schedule, or `webhook_delivery_attempts_exhausted` for one
|
|
3762
|
+
* holding the most attempts a delivery holds; `429` `account.rate_limited`, with its
|
|
3763
|
+
* `retry_after`, past the organization's replay rate; and a `500` or `503` when the replay could
|
|
3764
|
+
* not be queued, which then counts nothing against the rate.
|
|
3765
|
+
*/
|
|
3766
|
+
async replayRunWebhookDelivery(runId, deliveryId) {
|
|
3767
|
+
const call = "replayRunWebhookDelivery";
|
|
3768
|
+
const run = idPathSegment(runId, call, RUN_ID_ARGUMENT);
|
|
3769
|
+
const delivery = idPathSegment(deliveryId, call, DELIVERY_ID_ARGUMENT);
|
|
3770
|
+
return this.requestProductObject("POST", `runs/${run}/webhook-deliveries/${delivery}/replay`);
|
|
3771
|
+
}
|
|
3667
3772
|
/** Patch a run's status (admin/manual) — `PUT /v1/runs/{id}`. */
|
|
3668
3773
|
async updateRun(runId, input) {
|
|
3669
3774
|
const segment = idPathSegment(runId, "updateRun", RUN_ID_ARGUMENT);
|
|
@@ -3680,6 +3785,8 @@ export class PipelexApiClient {
|
|
|
3680
3785
|
const extensions = buildExtensions(options.extra);
|
|
3681
3786
|
const api = buildApiRunExtensions(options);
|
|
3682
3787
|
const hosted = buildHostedRunExtensions(options);
|
|
3788
|
+
// Not a run source, so outside `api`, whose size the precondition below reads.
|
|
3789
|
+
const webhooks = buildCallbackUrls(call, options);
|
|
3683
3790
|
const forms = protocolTreeForms(call, options);
|
|
3684
3791
|
const treeForms = treeFormsGiven(forms);
|
|
3685
3792
|
if (!options.pipe_code &&
|
|
@@ -3691,13 +3798,13 @@ export class PipelexApiClient {
|
|
|
3691
3798
|
}
|
|
3692
3799
|
assertMethodRefPairsWithNothing(options, treeForms);
|
|
3693
3800
|
assertLinkageMethodIdIsBare(options, treeForms);
|
|
3694
|
-
return { forms, extensions, api, hosted };
|
|
3801
|
+
return { forms, extensions, api, hosted, webhooks };
|
|
3695
3802
|
}
|
|
3696
3803
|
/**
|
|
3697
3804
|
* Blocking `POST /v1/execute` adapted onto `RunResults` — the bare-runner
|
|
3698
3805
|
* path. Forwards every protocol field PLUS every extension surface: the
|
|
3699
|
-
* runner-resolved `method_ref`, the
|
|
3700
|
-
* `extra` passthrough. An extension-only call (`{ extra }` with no
|
|
3806
|
+
* runner-resolved `method_ref`, the run's `callback_urls`, the hosted
|
|
3807
|
+
* `method_id`, and the generic `extra` passthrough. An extension-only call (`{ extra }` with no
|
|
3701
3808
|
* pipe_code/bundle) or a vendor selector riding `extra` must survive this
|
|
3702
3809
|
* path, not just the durable one — a `method_ref` run must run the same
|
|
3703
3810
|
* fetched package here, and a hosted `method_id` must reach the server too,
|
|
@@ -3719,6 +3826,8 @@ export class PipelexApiClient {
|
|
|
3719
3826
|
tree: options.tree ?? undefined,
|
|
3720
3827
|
files: options.files ?? undefined,
|
|
3721
3828
|
method_ref: options.method_ref ?? undefined,
|
|
3829
|
+
// A runner calls the run's webhooks once the blocking run has ended, as the platform does.
|
|
3830
|
+
callback_urls: options.callback_urls ?? undefined,
|
|
3722
3831
|
method_id: options.method_id ?? undefined,
|
|
3723
3832
|
extra: options.extra ?? undefined,
|
|
3724
3833
|
}, signal, sending);
|
|
@@ -3793,12 +3902,12 @@ const PROTOCOL_REQUEST_KEYS = [
|
|
|
3793
3902
|
"dynamic_output_concept_ref",
|
|
3794
3903
|
"files",
|
|
3795
3904
|
];
|
|
3796
|
-
// The PIPELEX API's own request
|
|
3797
|
-
// itself
|
|
3798
|
-
//
|
|
3799
|
-
//
|
|
3800
|
-
//
|
|
3801
|
-
const PIPELEX_API_REQUEST_KEYS = ["method_ref"];
|
|
3905
|
+
// The PIPELEX API's own request fields — the layer-2 extensions the runner reads
|
|
3906
|
+
// itself: `method_ref`, a run source in its own right, and `callback_urls`, the
|
|
3907
|
+
// webhooks to call once the run has ended. Reserved on `extra` for the same
|
|
3908
|
+
// reason the protocol fields are: `extra` merges last, so a smuggled copy would
|
|
3909
|
+
// overwrite the validated named option and bypass its checks.
|
|
3910
|
+
const PIPELEX_API_REQUEST_KEYS = ["method_ref", "callback_urls"];
|
|
3802
3911
|
// The HOSTED API's own request fields — the layer-3 extensions this client
|
|
3803
3912
|
// names itself. Reserved on `extra` for the same reason the protocol fields
|
|
3804
3913
|
// are: `extra` merges last, so a smuggled copy would overwrite the validated
|
|
@@ -3901,6 +4010,7 @@ function runRequestBody(call, options, files, parts) {
|
|
|
3901
4010
|
: options.dynamic_output_concept_ref,
|
|
3902
4011
|
files: files ?? undefined,
|
|
3903
4012
|
...parts.api,
|
|
4013
|
+
...parts.webhooks,
|
|
3904
4014
|
...parts.hosted,
|
|
3905
4015
|
...parts.extensions,
|
|
3906
4016
|
};
|
|
@@ -3981,6 +4091,29 @@ function buildApiRunExtensions(options) {
|
|
|
3981
4091
|
const methodRef = nonEmptyString(options.method_ref);
|
|
3982
4092
|
return methodRef === undefined ? {} : { method_ref: methodRef };
|
|
3983
4093
|
}
|
|
4094
|
+
/**
|
|
4095
|
+
* Copy a run's `callback_urls` onto the wire body, `call` naming the caller's method in a refusal.
|
|
4096
|
+
* An absent, `null` or empty list yields an empty object, so the key is not sent, as the platform
|
|
4097
|
+
* treats an empty list as absent. A value that is not an array, or an array holding anything but
|
|
4098
|
+
* strings, is refused before anything is sent; the URLs themselves are the server's to judge,
|
|
4099
|
+
* since the hosted API and a self-hosted runner each hold them to their own rules. The Python SDK
|
|
4100
|
+
* reads the argument the same way.
|
|
4101
|
+
*/
|
|
4102
|
+
function buildCallbackUrls(call, options) {
|
|
4103
|
+
const urls = options.callback_urls;
|
|
4104
|
+
if (urls == null)
|
|
4105
|
+
return {};
|
|
4106
|
+
if (!Array.isArray(urls)) {
|
|
4107
|
+
throw new RequestArgumentError(`${call}() takes callback_urls as an array of URLs; got ${typeof urls}.`, { problem: argumentInvalidProblem("callback_urls", "not_a_list") });
|
|
4108
|
+
}
|
|
4109
|
+
const copy = [...urls];
|
|
4110
|
+
if (copy.some((url) => typeof url !== "string")) {
|
|
4111
|
+
throw new RequestArgumentError(`${call}() takes each of callback_urls as a string URL.`, {
|
|
4112
|
+
problem: argumentInvalidProblem("callback_urls", "not_a_string"),
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
return copy.length === 0 ? {} : { callback_urls: copy };
|
|
4116
|
+
}
|
|
3984
4117
|
/**
|
|
3985
4118
|
* Copy the hosted API's own run arguments onto the wire body. Named options,
|
|
3986
4119
|
* not `extra` entries — see `PipelexHostedRunExtensions`.
|