@pithy-sh/cloudflare 0.4.2 → 0.6.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 -3
- package/dist/ai/aiManager.d.ts +15 -1
- package/dist/ai/aiManager.d.ts.map +1 -1
- package/dist/ai/aiManager.js +39 -6
- package/dist/analytics/analyticsManager.d.ts +78 -0
- package/dist/analytics/analyticsManager.d.ts.map +1 -0
- package/dist/analytics/analyticsManager.js +206 -0
- package/dist/analytics/datasets.d.ts +26 -0
- package/dist/analytics/datasets.d.ts.map +1 -0
- package/dist/analytics/datasets.js +26 -0
- package/dist/analytics/window.d.ts +50 -0
- package/dist/analytics/window.d.ts.map +1 -0
- package/dist/analytics/window.js +67 -0
- package/dist/artifacts/artifactsManager.d.ts +52 -0
- package/dist/artifacts/artifactsManager.d.ts.map +1 -0
- package/dist/artifacts/artifactsManager.js +150 -0
- package/dist/client/clients.d.ts +13 -1
- package/dist/client/clients.d.ts.map +1 -1
- package/dist/client/clients.js +22 -1
- package/dist/client/manager.d.ts +13 -2
- package/dist/client/manager.d.ts.map +1 -1
- package/dist/client/manager.js +18 -5
- package/dist/d1/d1Manager.d.ts.map +1 -1
- package/dist/d1/d1Manager.js +3 -3
- package/dist/d1/d1PreparedStatement.d.ts +12 -0
- package/dist/d1/d1PreparedStatement.d.ts.map +1 -1
- package/dist/d1/d1PreparedStatement.js +13 -3
- package/dist/media/imageManager.d.ts +25 -0
- package/dist/media/imageManager.d.ts.map +1 -1
- package/dist/media/imageManager.js +58 -2
- package/dist/media/streamManager.d.ts +15 -0
- package/dist/media/streamManager.d.ts.map +1 -1
- package/dist/media/streamManager.js +28 -1
- package/dist/oauth/callback.d.ts +32 -0
- package/dist/oauth/callback.d.ts.map +1 -0
- package/dist/oauth/callback.js +58 -0
- package/dist/oauth/catalog.d.ts +26 -0
- package/dist/oauth/catalog.d.ts.map +1 -0
- package/dist/oauth/catalog.js +30 -0
- package/dist/oauth/client.d.ts +126 -0
- package/dist/oauth/client.d.ts.map +1 -0
- package/dist/oauth/client.js +200 -0
- package/dist/oauth/scopes.d.ts +62 -0
- package/dist/oauth/scopes.d.ts.map +1 -0
- package/dist/oauth/scopes.js +79 -0
- package/dist/queue/queueProvisioner.d.ts +30 -0
- package/dist/queue/queueProvisioner.d.ts.map +1 -0
- package/dist/queue/queueProvisioner.js +72 -0
- package/dist/r2/r2Manager.d.ts +11 -1
- package/dist/r2/r2Manager.d.ts.map +1 -1
- package/dist/r2/r2Manager.js +18 -1
- package/dist/secrets/secretsStoreManager.d.ts.map +1 -1
- package/dist/secrets/secretsStoreManager.js +3 -2
- package/dist/test-utils/settle.d.ts +79 -0
- package/dist/test-utils/settle.d.ts.map +1 -0
- package/dist/test-utils/settle.js +105 -0
- package/dist/tokens/accountTokensManager.d.ts +8 -7
- package/dist/tokens/accountTokensManager.d.ts.map +1 -1
- package/dist/tokens/accountTokensManager.js +2 -1
- package/dist/tokens/permissions.d.ts +49 -0
- package/dist/tokens/permissions.d.ts.map +1 -1
- package/dist/tokens/permissions.js +56 -2
- package/dist/tokens/profiles.d.ts +10 -30
- package/dist/tokens/profiles.d.ts.map +1 -1
- package/dist/tokens/profiles.js +48 -36
- package/dist/tokens/runtime.d.ts +16 -0
- package/dist/tokens/runtime.d.ts.map +1 -0
- package/dist/tokens/runtime.js +18 -0
- package/dist/workers/buildsManager.js +1 -1
- package/dist/workflows/stepFailure.js +1 -1
- package/dist/workflows/workflowsClient.js +1 -1
- package/package.json +3 -3
- package/src/ai/aiManager.ts +44 -5
- package/src/analytics/analyticsManager.ts +311 -0
- package/src/analytics/datasets.ts +30 -0
- package/src/analytics/window.ts +101 -0
- package/src/artifacts/artifactsManager.ts +199 -0
- package/src/client/clients.ts +28 -1
- package/src/client/manager.ts +24 -6
- package/src/d1/d1Manager.ts +7 -3
- package/src/d1/d1PreparedStatement.ts +21 -2
- package/src/media/imageManager.ts +93 -1
- package/src/media/streamManager.ts +34 -0
- package/src/oauth/callback.ts +70 -0
- package/src/oauth/catalog.ts +39 -0
- package/src/oauth/client.ts +265 -0
- package/src/oauth/scopes.ts +91 -0
- package/src/queue/queueProvisioner.ts +79 -0
- package/src/r2/r2Manager.ts +29 -2
- package/src/secrets/secretsStoreManager.ts +2 -1
- package/src/test-utils/settle.ts +139 -0
- package/src/tokens/accountTokensManager.ts +12 -0
- package/src/tokens/permissions.ts +81 -0
- package/src/tokens/profiles.ts +58 -70
- package/src/tokens/runtime.ts +18 -0
|
@@ -25,9 +25,58 @@ export declare const PERMISSION_GROUPS: {
|
|
|
25
25
|
readonly "ai:read": readonly ["Workers AI Read"];
|
|
26
26
|
readonly "zone:read": readonly ["Zone Read"];
|
|
27
27
|
readonly "routes:write": readonly ["Workers Routes Write"];
|
|
28
|
+
readonly "analytics:read": readonly ["Account Analytics Read"];
|
|
29
|
+
readonly "images:write": readonly ["Images Write"];
|
|
30
|
+
readonly "stream:write": readonly ["Stream Write"];
|
|
31
|
+
readonly "queues:write": readonly ["Queues Write"];
|
|
32
|
+
readonly "artifacts:write": readonly ["Artifacts Write"];
|
|
28
33
|
};
|
|
29
34
|
/** A known permission key — one of {@link PERMISSION_GROUPS}'s keys. */
|
|
30
35
|
export type PermissionKey = keyof typeof PERMISSION_GROUPS;
|
|
36
|
+
/**
|
|
37
|
+
* **The third column: the OAuth scope each key needs a signed-in session to hold (#759).**
|
|
38
|
+
*
|
|
39
|
+
* A session mints only what it could do itself. Cloudflare refuses a mint whose permission groups the
|
|
40
|
+
* session's scopes do not cover — *"The OAuth session does not include the scopes required for the
|
|
41
|
+
* requested permission groups"*, measured 2026-10-15 — so the scopes `pithy cloudflare connect` asks for
|
|
42
|
+
* are derived from the permissions the project's tokens carry, through this table, and never listed by
|
|
43
|
+
* hand. `satisfies` over {@link PermissionKey} makes a key added above without a scope here a type error.
|
|
44
|
+
*
|
|
45
|
+
* **The ids pair with the group names one to one.** `GET /oauth/scopes` names each scope exactly as the
|
|
46
|
+
* permission group it grants — `d1.write` is "D1 Write", `workers-routes.write` is "Workers Routes Write" —
|
|
47
|
+
* which is what lets the integration test check this column against Cloudflare's own catalog the way
|
|
48
|
+
* the group names are checked against the account. A write scope also covers its read: a session holding
|
|
49
|
+
* `d1.write` minted a "D1 Read" token, so a key never needs both.
|
|
50
|
+
*
|
|
51
|
+
* Dot-delimited, always. The colon form (`account:read`) is what a first-party client like wrangler asks
|
|
52
|
+
* for, and a third-party client can neither register one nor request it.
|
|
53
|
+
*/
|
|
54
|
+
export declare const OAUTH_SCOPES: {
|
|
55
|
+
readonly "d1:read": readonly ["d1.read"];
|
|
56
|
+
readonly "d1:write": readonly ["d1.write"];
|
|
57
|
+
readonly "workers:write": readonly ["workers-scripts.write"];
|
|
58
|
+
readonly "secrets:read": readonly ["secrets-store.read"];
|
|
59
|
+
readonly "secrets:write": readonly ["secrets-store.write"];
|
|
60
|
+
readonly "email:routing": readonly ["email-routing-rule.write"];
|
|
61
|
+
readonly "kv:write": readonly ["workers-kv-storage.write"];
|
|
62
|
+
readonly "r2:read": readonly ["workers-r2.read"];
|
|
63
|
+
readonly "r2:write": readonly ["workers-r2.write"];
|
|
64
|
+
readonly "vectorize:read": readonly ["vectorize.read"];
|
|
65
|
+
readonly "vectorize:write": readonly ["vectorize.write"];
|
|
66
|
+
readonly "ai:read": readonly ["ai.read"];
|
|
67
|
+
readonly "zone:read": readonly ["zone.read"];
|
|
68
|
+
readonly "routes:write": readonly ["workers-routes.write"];
|
|
69
|
+
readonly "analytics:read": readonly ["account-analytics.read"];
|
|
70
|
+
readonly "images:write": readonly ["images.write"];
|
|
71
|
+
readonly "stream:write": readonly ["stream.write"];
|
|
72
|
+
readonly "queues:write": readonly ["queues.write"];
|
|
73
|
+
readonly "artifacts:write": readonly ["artifacts.write"];
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Resolve permission keys to the OAuth scopes a session needs to act — or mint — with them, de-duped in
|
|
77
|
+
* first-seen order. An unknown key is refused exactly as {@link resolvePermissionKeys} refuses it.
|
|
78
|
+
*/
|
|
79
|
+
export declare function oauthScopesFor(keys: readonly string[]): string[];
|
|
31
80
|
/**
|
|
32
81
|
* The keys whose Cloudflare permission group is **zone-scoped**: a policy carrying one must name zone
|
|
33
82
|
* resources (`com.cloudflare.api.account.zone.<id>`), never the account resource.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/tokens/permissions.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB;aAC5B,SAAS,YAAG,SAAS;aAIrB,UAAU,YAAG,UAAU;aACvB,eAAe,YAAG,uBAAuB;aACzC,cAAc,YAAG,oBAAoB;aACrC,eAAe,YAAG,qBAAqB;aACvC,eAAe,YAAG,2BAA2B;aAC7C,UAAU,YAAG,0BAA0B;aACvC,SAAS,YAAG,yBAAyB;aACrC,UAAU,YAAG,0BAA0B;aACvC,gBAAgB,YAAG,gBAAgB;aACnC,iBAAiB,YAAG,iBAAiB;aACrC,SAAS,YAAG,iBAAiB;aAK7B,WAAW,YAAG,WAAW;
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/tokens/permissions.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB;aAC5B,SAAS,YAAG,SAAS;aAIrB,UAAU,YAAG,UAAU;aACvB,eAAe,YAAG,uBAAuB;aACzC,cAAc,YAAG,oBAAoB;aACrC,eAAe,YAAG,qBAAqB;aACvC,eAAe,YAAG,2BAA2B;aAC7C,UAAU,YAAG,0BAA0B;aACvC,SAAS,YAAG,yBAAyB;aACrC,UAAU,YAAG,0BAA0B;aACvC,gBAAgB,YAAG,gBAAgB;aACnC,iBAAiB,YAAG,iBAAiB;aACrC,SAAS,YAAG,iBAAiB;aAK7B,WAAW,YAAG,WAAW;aAiBzB,cAAc,YAAG,sBAAsB;aAQvC,gBAAgB,YAAG,wBAAwB;aAK3C,cAAc,YAAG,cAAc;aAC/B,cAAc,YAAG,cAAc;aAI/B,cAAc,YAAG,cAAc;aAI/B,iBAAiB,YAAG,iBAAiB;CAC7B,CAAC;AAEX,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,iBAAiB,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;CAoBoC,CAAC;AAE9D;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAShE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,SAAS,aAAa,EAAqB,CAAC;AAElF,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,aAAa,CAEjE;AAED,6FAA6F;AAC7F,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAe9D"}
|
|
@@ -26,9 +26,63 @@ const PERMISSION_GROUPS = {
|
|
|
26
26
|
"vectorize:write": ["Vectorize Write"],
|
|
27
27
|
"ai:read": ["Workers AI Read"],
|
|
28
28
|
"zone:read": ["Zone Read"],
|
|
29
|
-
"routes:write": ["Workers Routes Write"]
|
|
29
|
+
"routes:write": ["Workers Routes Write"],
|
|
30
|
+
"analytics:read": ["Account Analytics Read"],
|
|
31
|
+
"images:write": ["Images Write"],
|
|
32
|
+
"stream:write": ["Stream Write"],
|
|
33
|
+
"queues:write": ["Queues Write"],
|
|
34
|
+
"artifacts:write": ["Artifacts Write"]
|
|
30
35
|
};
|
|
31
36
|
/**
|
|
37
|
+
* **The third column: the OAuth scope each key needs a signed-in session to hold (#759).**
|
|
38
|
+
*
|
|
39
|
+
* A session mints only what it could do itself. Cloudflare refuses a mint whose permission groups the
|
|
40
|
+
* session's scopes do not cover — *"The OAuth session does not include the scopes required for the
|
|
41
|
+
* requested permission groups"*, measured 2026-10-15 — so the scopes `pithy cloudflare connect` asks for
|
|
42
|
+
* are derived from the permissions the project's tokens carry, through this table, and never listed by
|
|
43
|
+
* hand. `satisfies` over {@link PermissionKey} makes a key added above without a scope here a type error.
|
|
44
|
+
*
|
|
45
|
+
* **The ids pair with the group names one to one.** `GET /oauth/scopes` names each scope exactly as the
|
|
46
|
+
* permission group it grants — `d1.write` is "D1 Write", `workers-routes.write` is "Workers Routes Write" —
|
|
47
|
+
* which is what lets the integration test check this column against Cloudflare's own catalog the way
|
|
48
|
+
* the group names are checked against the account. A write scope also covers its read: a session holding
|
|
49
|
+
* `d1.write` minted a "D1 Read" token, so a key never needs both.
|
|
50
|
+
*
|
|
51
|
+
* Dot-delimited, always. The colon form (`account:read`) is what a first-party client like wrangler asks
|
|
52
|
+
* for, and a third-party client can neither register one nor request it.
|
|
53
|
+
*/
|
|
54
|
+
const OAUTH_SCOPES = {
|
|
55
|
+
"d1:read": ["d1.read"],
|
|
56
|
+
"d1:write": ["d1.write"],
|
|
57
|
+
"workers:write": ["workers-scripts.write"],
|
|
58
|
+
"secrets:read": ["secrets-store.read"],
|
|
59
|
+
"secrets:write": ["secrets-store.write"],
|
|
60
|
+
"email:routing": ["email-routing-rule.write"],
|
|
61
|
+
"kv:write": ["workers-kv-storage.write"],
|
|
62
|
+
"r2:read": ["workers-r2.read"],
|
|
63
|
+
"r2:write": ["workers-r2.write"],
|
|
64
|
+
"vectorize:read": ["vectorize.read"],
|
|
65
|
+
"vectorize:write": ["vectorize.write"],
|
|
66
|
+
"ai:read": ["ai.read"],
|
|
67
|
+
"zone:read": ["zone.read"],
|
|
68
|
+
"routes:write": ["workers-routes.write"],
|
|
69
|
+
"analytics:read": ["account-analytics.read"],
|
|
70
|
+
"images:write": ["images.write"],
|
|
71
|
+
"stream:write": ["stream.write"],
|
|
72
|
+
"queues:write": ["queues.write"],
|
|
73
|
+
"artifacts:write": ["artifacts.write"]
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Resolve permission keys to the OAuth scopes a session needs to act — or mint — with them, de-duped in
|
|
77
|
+
* first-seen order. An unknown key is refused exactly as {@link resolvePermissionKeys} refuses it.
|
|
78
|
+
*/
|
|
79
|
+
function oauthScopesFor(keys) {
|
|
80
|
+
resolvePermissionKeys([...keys]);
|
|
81
|
+
const scopes = [];
|
|
82
|
+
for (const key of keys) for (const scope of OAUTH_SCOPES[key]) if (!scopes.includes(scope)) scopes.push(scope);
|
|
83
|
+
return scopes;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
32
86
|
* The keys whose Cloudflare permission group is **zone-scoped**: a policy carrying one must name zone
|
|
33
87
|
* resources (`com.cloudflare.api.account.zone.<id>`), never the account resource.
|
|
34
88
|
*
|
|
@@ -64,4 +118,4 @@ function resolvePermissionKeys(keys) {
|
|
|
64
118
|
return names;
|
|
65
119
|
}
|
|
66
120
|
//#endregion
|
|
67
|
-
export { PERMISSION_GROUPS, ZONE_SCOPED_PERMISSIONS, isPermissionKey, isZoneScopedPermission, resolvePermissionKeys };
|
|
121
|
+
export { OAUTH_SCOPES, PERMISSION_GROUPS, ZONE_SCOPED_PERMISSIONS, isPermissionKey, isZoneScopedPermission, oauthScopesFor, resolvePermissionKeys };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import type { Capability
|
|
3
|
+
import type { Capability } from "@pithy-sh/core/src/capability/capability";
|
|
4
4
|
import { type TokenPermission } from "./accountTokensManager";
|
|
5
5
|
import { type PermissionKey } from "./permissions";
|
|
6
6
|
/**
|
|
7
7
|
* Where a minted token's value is written.
|
|
8
8
|
*
|
|
9
|
-
* - `secrets-store` — the CF Secrets Store; a Worker reads it via its binding. The destination for
|
|
10
|
-
*
|
|
9
|
+
* - `secrets-store` — the CF Secrets Store; a Worker reads it via its binding. The destination for the
|
|
10
|
+
* `runtime` token; resolved from the token's declared secret (its registry backend) unless a
|
|
11
11
|
* profile or `--store` names it directly.
|
|
12
12
|
* - `dev-vars` — `<config>/<project>/tokens.json`, keyed by environment and outside every checkout.
|
|
13
13
|
* **Read by an operator, never by a command** — nothing resolves a credential from it. The store for
|
|
@@ -37,21 +37,11 @@ export interface TokenProfile {
|
|
|
37
37
|
resources?: "account" | Record<string, string>;
|
|
38
38
|
/** The secret-registry name the minted value is stored under; its declared backend is the destination. */
|
|
39
39
|
secret: string;
|
|
40
|
-
/**
|
|
41
|
-
* Whether the value behind {@link secret} differs per environment (the default) or is one value every
|
|
42
|
-
* environment shares. It decides the environment segment of the **CF Secrets Store entry name** the
|
|
43
|
-
* minted value is written to, and nothing else — never the variable key, which is a variable name
|
|
44
|
-
* and stays verbatim. A `global` profile writes one entry with the literal `global` in that slot,
|
|
45
|
-
* matching what provisioning wrote; an `environment` profile writes one entry per environment.
|
|
46
|
-
*/
|
|
47
|
-
secretScope?: TokenSecretScope;
|
|
48
40
|
/** A built-in destination override (`dev-vars`/`ephemeral`/`secrets-store`); else the secret's backend. */
|
|
49
41
|
defaultStore?: TokenStore;
|
|
50
42
|
/** Why this profile exists / what job consumes it. */
|
|
51
43
|
description: string;
|
|
52
44
|
}
|
|
53
|
-
/** Whether a profile's secret is one value per environment or one value shared by all of them. */
|
|
54
|
-
export type TokenSecretScope = "environment" | "global";
|
|
55
45
|
/** An adopter's override of a profile's defaults (from `pithy.config.ts` or CLI flags). Every field optional. */
|
|
56
46
|
export interface ProfileOverride {
|
|
57
47
|
/** Replace the profile's permission keys. */
|
|
@@ -69,23 +59,9 @@ export declare function tokenSecretName(profile: string): string;
|
|
|
69
59
|
*/
|
|
70
60
|
export declare function defineTokenProfile(profile: TokenProfile): TokenProfile;
|
|
71
61
|
/**
|
|
72
|
-
* The
|
|
73
|
-
* capability declares
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* Exported so a capability can type its `tokenProfiles` entry against it. Declaring `secretScope` as an
|
|
77
|
-
* inline object literal directly in `tokenProfiles` still trips TypeScript's excess-property check
|
|
78
|
-
* against core's narrower `TokenProfileSeam`; declare the entry as its own `const` (with `as const` or
|
|
79
|
-
* `satisfies TokenProfileSeamInput`) and it flows through.
|
|
80
|
-
*/
|
|
81
|
-
export interface TokenProfileSeamInput extends TokenProfileSeam {
|
|
82
|
-
readonly secretScope?: string;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* The project's token-profile registry: the aggregating `ci-system` profile plus every composed
|
|
86
|
-
* capability's worker-consumer {@link Capability.tokenProfiles} slice. A capability profile that
|
|
87
|
-
* clashes with `ci-system` (or another capability's name) fails loudly at assembly. This is the one
|
|
88
|
-
* registry `pithy token` reads.
|
|
62
|
+
* The project's token-profile registry: the aggregating `ci-system` profile and, when any composed
|
|
63
|
+
* capability declares {@link Capability.runtimePermissions}, the aggregating `runtime` profile. Nothing
|
|
64
|
+
* else — a capability cannot add a profile. This is the one registry `pithy token` reads.
|
|
89
65
|
*/
|
|
90
66
|
export declare function resolveTokenProfiles(capabilities: readonly Capability[]): Record<string, TokenProfile>;
|
|
91
67
|
/**
|
|
@@ -105,6 +81,10 @@ export declare function permissionsForKeys(keys: PermissionKey[], accountId: str
|
|
|
105
81
|
* it is a policy of its own rather than a permission on the profile because its resources are zones,
|
|
106
82
|
* which a profile cannot name: they are resolved from the domains at mint time.
|
|
107
83
|
*
|
|
84
|
+
* **This limits the route, not the token.** The account policy beside it carries Workers Scripts Write,
|
|
85
|
+
* which attaches a custom domain on any zone the account holds (measured, #709; `docs/TOKENS.md`). Only
|
|
86
|
+
* the route calls are held to the declared zones.
|
|
87
|
+
*
|
|
108
88
|
* **Empty for an empty zone list, deliberately.** A project that declares no domain attaches no route,
|
|
109
89
|
* so it mints exactly the token it minted before this existed. No new permission for a project that
|
|
110
90
|
* needs none.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/tokens/profiles.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/tokens/profiles.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E,OAAO,EAAmB,KAAK,eAAe,EAAiB,MAAM,wBAAwB,CAAC;AAC9F,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,eAAe,CAAC;AAGvB;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,UAAU,GAAG,WAAW,CAAC;AAEpE,uFAAuF;AACvF,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAA+C,CAAC;AAE9F,4GAA4G;AAC5G,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAe7C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,oGAAoG;IACpG,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,oFAAoF;IACpF,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,0GAA0G;IAC1G,MAAM,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,iHAAiH;AACjH,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,WAAW,CAAC,EAAE,aAAa,EAAE,CAAC;IAC9B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,sDAAsD;IACtD,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAGtE;AAiFD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAMtG;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,eAAe,GACzB,YAAY,CAgBd;AAsBD,uGAAuG;AACvG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CAG9F;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,EAAE,CAG9E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CAK9F"}
|
package/dist/tokens/profiles.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { CloudflareNotConfiguredError } from "../client/errors.js";
|
|
4
4
|
import { accountResource, zoneResources } from "./accountTokensManager.js";
|
|
5
5
|
import { PERMISSION_GROUPS, isPermissionKey, isZoneScopedPermission, resolvePermissionKeys } from "./permissions.js";
|
|
6
|
+
import { RUNTIME_PROFILE, RUNTIME_TOKEN_SECRET } from "./runtime.js";
|
|
6
7
|
//#region src/tokens/profiles.ts
|
|
7
8
|
/** The valid stores, for validating a `--store` flag or a profile's `defaultStore`. */
|
|
8
9
|
const TOKEN_STORES = [
|
|
@@ -24,8 +25,6 @@ const CI_SYSTEM_BASE = [
|
|
|
24
25
|
"secrets:read",
|
|
25
26
|
"secrets:write"
|
|
26
27
|
];
|
|
27
|
-
/** The valid secret scopes, for validating a capability's declaration. */
|
|
28
|
-
const TOKEN_SECRET_SCOPES = ["environment", "global"];
|
|
29
28
|
/** The env-var / secret name a profile's token is stored under: `CF_TOKEN_<PROFILE>`. */
|
|
30
29
|
function tokenSecretName(profile) {
|
|
31
30
|
return `CF_TOKEN_${profile.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}`;
|
|
@@ -42,14 +41,23 @@ function defineTokenProfile(profile) {
|
|
|
42
41
|
function assertPermissionKeys(keys, where) {
|
|
43
42
|
for (const key of keys) if (!isPermissionKey(key)) throw new CloudflareNotConfiguredError({
|
|
44
43
|
message: `${where} declares an unknown permission key: ${key}.`,
|
|
45
|
-
action:
|
|
44
|
+
action: `Use a known permission key: ${Object.keys(PERMISSION_GROUPS).join(", ")}.`
|
|
46
45
|
});
|
|
47
46
|
return keys;
|
|
48
47
|
}
|
|
49
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Build the aggregating `ci-system` profile: the base permissions ∪ every capability's `ciPermissions` ∪
|
|
50
|
+
* `queues:write` where a composed capability binds a queue ∪ `artifacts:write` where one binds Artifacts.
|
|
51
|
+
*
|
|
52
|
+
* **The queue permission is derived from the binding, not declared (#662).** `pithy provision` creates the queue
|
|
53
|
+
* behind every `queue` binding it composes, the adopter's own app's included, and the app has no `ciPermissions`
|
|
54
|
+
* anyone would think to write. A permission read off the binding cannot be forgotten by whoever declares it.
|
|
55
|
+
*/
|
|
50
56
|
function ciSystemProfile(capabilities) {
|
|
51
57
|
const permissions = [...CI_SYSTEM_BASE];
|
|
52
58
|
for (const capability of capabilities) for (const key of assertPermissionKeys(capability.ciPermissions ?? [], `Capability "${capability.name}"`)) if (!permissions.includes(key)) permissions.push(key);
|
|
59
|
+
if (capabilities.some((capability) => capability.requiredBindings.some((binding) => binding.type === "queue")) && !permissions.includes("queues:write")) permissions.push("queues:write");
|
|
60
|
+
if (capabilities.some((capability) => capability.requiredBindings.some((binding) => binding.type === "artifacts")) && !permissions.includes("artifacts:write")) permissions.push("artifacts:write");
|
|
53
61
|
return {
|
|
54
62
|
name: CI_SYSTEM_PROFILE,
|
|
55
63
|
permissions,
|
|
@@ -58,44 +66,44 @@ function ciSystemProfile(capabilities) {
|
|
|
58
66
|
description: "The one CI pipeline credential: deploy Workers, migrate, and every composed capability's CI needs. Set it as CI's CLOUDFLARE_API_TOKEN."
|
|
59
67
|
};
|
|
60
68
|
}
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Build the aggregating `runtime` profile: every composed capability's `runtimePermissions`, each once,
|
|
71
|
+
* in first-declared order — or nothing, when the composition declares none.
|
|
72
|
+
*
|
|
73
|
+
* **One token per deployed application, the way `ci-system` is one per pipeline.** A capability names
|
|
74
|
+
* what its Workers call Cloudflare with and never a token of its own, so a second credential is not
|
|
75
|
+
* something a capability can ask for. The token is minted per environment into the Secrets Store, so
|
|
76
|
+
* revoking staging's leaves production reading. A key whose group is zone-scoped is refused here: a
|
|
77
|
+
* Worker's token is account-scoped, and its zones would have to be chosen by hand.
|
|
78
|
+
*/
|
|
79
|
+
function runtimeProfile(capabilities) {
|
|
80
|
+
const permissions = [];
|
|
81
|
+
for (const capability of capabilities) {
|
|
82
|
+
const where = `Capability "${capability.name}"`;
|
|
83
|
+
const keys = assertPermissionKeys(capability.runtimePermissions ?? [], where);
|
|
84
|
+
assertAccountScoped(keys, `${where}'s runtimePermissions`);
|
|
85
|
+
for (const key of keys) if (!permissions.includes(key)) permissions.push(key);
|
|
86
|
+
}
|
|
87
|
+
if (permissions.length === 0) return void 0;
|
|
73
88
|
return {
|
|
74
|
-
name,
|
|
75
|
-
permissions
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
defaultStore: store,
|
|
80
|
-
description: seam.description ?? name
|
|
89
|
+
name: RUNTIME_PROFILE,
|
|
90
|
+
permissions,
|
|
91
|
+
secret: RUNTIME_TOKEN_SECRET,
|
|
92
|
+
defaultStore: "secrets-store",
|
|
93
|
+
description: "The one Cloudflare API token a deployed application holds: every composed capability's runtime needs, per environment. Analytics reads are account-wide by Cloudflare's grain."
|
|
81
94
|
};
|
|
82
95
|
}
|
|
83
96
|
/**
|
|
84
|
-
* The project's token-profile registry: the aggregating `ci-system` profile
|
|
85
|
-
* capability
|
|
86
|
-
*
|
|
87
|
-
* registry `pithy token` reads.
|
|
97
|
+
* The project's token-profile registry: the aggregating `ci-system` profile and, when any composed
|
|
98
|
+
* capability declares {@link Capability.runtimePermissions}, the aggregating `runtime` profile. Nothing
|
|
99
|
+
* else — a capability cannot add a profile. This is the one registry `pithy token` reads.
|
|
88
100
|
*/
|
|
89
101
|
function resolveTokenProfiles(capabilities) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
profiles[name] = profileFromSeam(name, seam, capability.name);
|
|
97
|
-
}
|
|
98
|
-
return profiles;
|
|
102
|
+
const runtime = runtimeProfile(capabilities);
|
|
103
|
+
return {
|
|
104
|
+
[CI_SYSTEM_PROFILE]: ciSystemProfile(capabilities),
|
|
105
|
+
...runtime ? { [RUNTIME_PROFILE]: runtime } : {}
|
|
106
|
+
};
|
|
99
107
|
}
|
|
100
108
|
/**
|
|
101
109
|
* Resolve a profile by name from the aggregated registry, applying an adopter override. An unknown
|
|
@@ -152,6 +160,10 @@ function permissionsForKeys(keys, accountId) {
|
|
|
152
160
|
* it is a policy of its own rather than a permission on the profile because its resources are zones,
|
|
153
161
|
* which a profile cannot name: they are resolved from the domains at mint time.
|
|
154
162
|
*
|
|
163
|
+
* **This limits the route, not the token.** The account policy beside it carries Workers Scripts Write,
|
|
164
|
+
* which attaches a custom domain on any zone the account holds (measured, #709; `docs/TOKENS.md`). Only
|
|
165
|
+
* the route calls are held to the declared zones.
|
|
166
|
+
*
|
|
155
167
|
* **Empty for an empty zone list, deliberately.** A project that declares no domain attaches no route,
|
|
156
168
|
* so it mints exactly the token it minted before this existed. No new permission for a project that
|
|
157
169
|
* needs none.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* **The names of the one Cloudflare API token a deployed application holds (#728)**, in a module of their
|
|
5
|
+
* own. A Worker reads the token by {@link RUNTIME_TOKEN_SECRET}, and importing the profile registry to
|
|
6
|
+
* learn a string would put the token-minting client on every Worker's bundle. Nothing here imports anything.
|
|
7
|
+
*/
|
|
8
|
+
/** The name of the one runtime token — the single Cloudflare API token a deployed application holds (#728). */
|
|
9
|
+
export declare const RUNTIME_PROFILE = "runtime";
|
|
10
|
+
/**
|
|
11
|
+
* The secret the runtime token's value is stored under, and the binding every Worker that calls Cloudflare
|
|
12
|
+
* reads it through. Unscoped, because it is a binding name: the Secrets Store entry it resolves to carries
|
|
13
|
+
* the project and environment (`<project>-<env>-runtime-cf-api-token`).
|
|
14
|
+
*/
|
|
15
|
+
export declare const RUNTIME_TOKEN_SECRET = "RUNTIME_CF_API_TOKEN";
|
|
16
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/tokens/runtime.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,+GAA+G;AAC/G,eAAO,MAAM,eAAe,YAAY,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
//#region src/tokens/runtime.ts
|
|
4
|
+
/**
|
|
5
|
+
* **The names of the one Cloudflare API token a deployed application holds (#728)**, in a module of their
|
|
6
|
+
* own. A Worker reads the token by {@link RUNTIME_TOKEN_SECRET}, and importing the profile registry to
|
|
7
|
+
* learn a string would put the token-minting client on every Worker's bundle. Nothing here imports anything.
|
|
8
|
+
*/
|
|
9
|
+
/** The name of the one runtime token — the single Cloudflare API token a deployed application holds (#728). */
|
|
10
|
+
const RUNTIME_PROFILE = "runtime";
|
|
11
|
+
/**
|
|
12
|
+
* The secret the runtime token's value is stored under, and the binding every Worker that calls Cloudflare
|
|
13
|
+
* reads it through. Unscoped, because it is a binding name: the Secrets Store entry it resolves to carries
|
|
14
|
+
* the project and environment (`<project>-<env>-runtime-cf-api-token`).
|
|
15
|
+
*/
|
|
16
|
+
const RUNTIME_TOKEN_SECRET = "RUNTIME_CF_API_TOKEN";
|
|
17
|
+
//#endregion
|
|
18
|
+
export { RUNTIME_PROFILE, RUNTIME_TOKEN_SECRET };
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import { CloudflareInvalidResponseError, cloudflareApiErrors, cloudflareRefusal, cloudflareRequest, decodeResponse, messageOf } from "../client/errors.js";
|
|
4
4
|
import { CloudflareManager } from "../client/manager.js";
|
|
5
5
|
import { CfBuild, CfBuildLog, CfBuildToken, CfBuildTrigger, CfRepoConnection } from "./buildsTypes.js";
|
|
6
|
-
import { z } from "zod";
|
|
7
6
|
import { PithyError } from "@pithy-sh/core/src/error/pithyError";
|
|
7
|
+
import { z } from "zod";
|
|
8
8
|
//#region src/workers/buildsManager.ts
|
|
9
9
|
/** The Cloudflare REST API v4 base URL. The Builds endpoints live under `/accounts/{id}/builds`. */
|
|
10
10
|
const CF_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { z } from "zod";
|
|
4
3
|
import { PithyError } from "@pithy-sh/core/src/error/pithyError";
|
|
4
|
+
import { z } from "zod";
|
|
5
5
|
import { MAX_WORKFLOW_STEP_TEXT, decodeWorkflowStepMessage, splitWorkflowStepCode } from "@pithy-sh/core/src/workflow/stepMessage";
|
|
6
6
|
import { guaranteedErrorParams } from "@pithy-sh/core/src/error/messageParams";
|
|
7
7
|
import { ErrorPayload, kitErrorStatus } from "@pithy-sh/core/src/error/payload";
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import { CloudflareInvalidResponseError, cloudflareRequest, isNotFoundError } from "../client/errors.js";
|
|
4
4
|
import { CloudflareManager } from "../client/manager.js";
|
|
5
5
|
import { stepFailure, terminalWorkflowError } from "./stepFailure.js";
|
|
6
|
-
import { z } from "zod";
|
|
7
6
|
import { UpstreamTimeoutError } from "@pithy-sh/core/src/error/pithyError";
|
|
7
|
+
import { z } from "zod";
|
|
8
8
|
//#region src/workflows/workflowsClient.ts
|
|
9
9
|
const WorkflowInstanceStatus = z.enum([
|
|
10
10
|
"queued",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pithy-sh/cloudflare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "tsdown && tsc -p tsconfig.build.json && bun ../../tooling/build/src/stampDeclarations.ts",
|
|
28
|
-
"typecheck": "
|
|
28
|
+
"typecheck": "bun check -p tsconfig.json",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"test:node": "vitest run --project=node",
|
|
31
31
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"js-base64": "^3.9.2"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@pithy-sh/core": "^0.
|
|
43
|
+
"@pithy-sh/core": "^0.11.0",
|
|
44
44
|
"zod": "^4.4.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/ai/aiManager.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { ValidationError } from "@pithy-sh/core/src/error/pithyError";
|
|
5
|
+
import type { AIRunResponse } from "cloudflare/resources/ai/ai";
|
|
5
6
|
import type { ModelListResponse } from "cloudflare/resources/ai/models/models";
|
|
6
7
|
import { fromUint8Array } from "js-base64";
|
|
7
8
|
import { z } from "zod";
|
|
@@ -128,6 +129,39 @@ export interface AnalyzeImageOptions {
|
|
|
128
129
|
|
|
129
130
|
const DEFAULT_IMAGE_PROMPT = "Describe this image in a concise sentence suitable for alt text.";
|
|
130
131
|
|
|
132
|
+
/**
|
|
133
|
+
* What a Workers AI model name may look like: `@<namespace>/` and then one to three segments, each made of
|
|
134
|
+
* letters, digits, `.`, `_` and `-` and starting with a letter or digit — `@cf/baai/bge-m3`,
|
|
135
|
+
* `@cf/llava-hf/llava-1.5-7b-hf`, `@hf/thebloke/deepseek-coder-6.7b-instruct-awq`.
|
|
136
|
+
*
|
|
137
|
+
* **The name goes into the request path unencoded, so this pattern is the whole of the defense.** A segment
|
|
138
|
+
* cannot be `.` or `..` (it must start with a letter or digit), and nothing that ends a path, opens a query
|
|
139
|
+
* or a fragment, or escapes a character (`?`, `#`, `%`, `\`, whitespace) is in the alphabet. A name outside
|
|
140
|
+
* it is refused before a request exists.
|
|
141
|
+
*/
|
|
142
|
+
const AI_MODEL_NAME = /^@[a-z0-9][a-z0-9-]*(?:\/[A-Za-z0-9][A-Za-z0-9._-]*){1,3}$/;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The REST path that runs `model`: `/accounts/<id>/ai/run/@cf/baai/bge-m3`, the model's slashes literal.
|
|
146
|
+
*
|
|
147
|
+
* **Not the SDK's `ai.run` (#709).** cloudflare 7.0.0 builds that path with its `path` tag, which
|
|
148
|
+
* percent-encodes every path parameter, the `/`s inside a model name included. Cloudflare routes on the
|
|
149
|
+
* literal slashes, so `/ai/run/@cf%2Fbaai%2Fbge-m3` answers 7000 "No route for that URI" for every model.
|
|
150
|
+
* The path is built here instead, and a name that could change its shape is refused by {@link AI_MODEL_NAME}.
|
|
151
|
+
*
|
|
152
|
+
* @throws ValidationError (`validation/invalid_input`) when `model` is not a Workers AI model name.
|
|
153
|
+
*/
|
|
154
|
+
export function aiRunPath(accountId: string, model: string): string {
|
|
155
|
+
if (!AI_MODEL_NAME.test(model)) {
|
|
156
|
+
throw new ValidationError({
|
|
157
|
+
message: `"${model.slice(0, 120)}" is not a Workers AI model name.`,
|
|
158
|
+
action: "Pass a model name such as @cf/baai/bge-m3. The Workers AI model catalog lists them.",
|
|
159
|
+
detail: `model name ${JSON.stringify(model)} does not match ${AI_MODEL_NAME}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return `/accounts/${encodeURIComponent(accountId)}/ai/run/${model}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
131
165
|
/**
|
|
132
166
|
* Out-of-Worker Workers AI access over the REST API: run any model, generate text, generate
|
|
133
167
|
* embeddings, analyze an image, and list models from a CLI/CI/provisioning context. Inside a
|
|
@@ -138,12 +172,17 @@ const DEFAULT_IMAGE_PROMPT = "Describe this image in a concise sentence suitable
|
|
|
138
172
|
export class CloudflareAIManager extends CloudflareManager {
|
|
139
173
|
/**
|
|
140
174
|
* Run a Cloudflare AI model by name with an arbitrary JSON input. The single seam every typed
|
|
141
|
-
* helper builds on. The account
|
|
175
|
+
* helper builds on. The account is addressed for you; `input` is the model's body.
|
|
176
|
+
*
|
|
177
|
+
* Through the SDK's raw `post` to {@link aiRunPath}, not `ai.run`, whose path encoding Cloudflare cannot
|
|
178
|
+
* route. A malformed model name throws `validation/invalid_input` before any request is sent.
|
|
142
179
|
*/
|
|
143
180
|
async runModel(model: string, input: Record<string, unknown>): Promise<AIRunResponse> {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
181
|
+
const path = aiRunPath(this.accountId, model);
|
|
182
|
+
return cloudflareRequest(`AI run model '${model}'`, async () => {
|
|
183
|
+
const envelope = await this.getClient().post<{ result: AIRunResponse }>(path, { body: input });
|
|
184
|
+
return envelope.result;
|
|
185
|
+
});
|
|
147
186
|
}
|
|
148
187
|
|
|
149
188
|
/**
|