alchemy 2.0.0-beta.28 → 2.0.0-beta.29
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/bin/exec.js +98 -6
- package/bin/exec.js.map +1 -1
- package/lib/AWS/IAM/SAMLProvider.d.ts +2 -1
- package/lib/AWS/IAM/SAMLProvider.d.ts.map +1 -1
- package/lib/AWS/IAM/SAMLProvider.js +30 -9
- package/lib/AWS/IAM/SAMLProvider.js.map +1 -1
- package/lib/Apply.d.ts.map +1 -1
- package/lib/Apply.js +55 -11
- package/lib/Apply.js.map +1 -1
- package/lib/Cli/commands/bootstrap.d.ts +1 -1
- package/lib/Cli/commands/bootstrap.d.ts.map +1 -1
- package/lib/Cli/commands/bootstrap.js +2 -1
- package/lib/Cli/commands/bootstrap.js.map +1 -1
- package/lib/Cloudflare/Access.d.ts +19 -0
- package/lib/Cloudflare/Access.d.ts.map +1 -0
- package/lib/Cloudflare/Access.js +64 -0
- package/lib/Cloudflare/Access.js.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGateway.d.ts +244 -0
- package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGateway.js +269 -0
- package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts +103 -0
- package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGatewayBinding.js +99 -0
- package/lib/Cloudflare/AiGateway/AiGatewayBinding.js.map +1 -0
- package/lib/Cloudflare/AiGateway/index.d.ts +3 -0
- package/lib/Cloudflare/AiGateway/index.d.ts.map +1 -0
- package/lib/Cloudflare/AiGateway/index.js +3 -0
- package/lib/Cloudflare/AiGateway/index.js.map +1 -0
- package/lib/Cloudflare/EdgeSession.d.ts +2 -1
- package/lib/Cloudflare/EdgeSession.d.ts.map +1 -1
- package/lib/Cloudflare/EdgeSession.js +8 -4
- package/lib/Cloudflare/EdgeSession.js.map +1 -1
- package/lib/Cloudflare/Providers.d.ts +2 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +5 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +10 -0
- package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Api.js +12 -1
- package/lib/Cloudflare/StateStore/Api.js.map +1 -1
- package/lib/Cloudflare/StateStore/State.d.ts +4 -3
- package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/State.js +103 -13
- package/lib/Cloudflare/StateStore/State.js.map +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +2 -1
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +40 -31
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/index.d.ts +1 -0
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +1 -0
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/Plan.d.ts +8 -0
- package/lib/Plan.d.ts.map +1 -1
- package/lib/Plan.js +12 -5
- package/lib/Plan.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +19 -1
- package/lib/State/HttpStateApi.d.ts.map +1 -1
- package/lib/State/HttpStateApi.js +18 -1
- package/lib/State/HttpStateApi.js.map +1 -1
- package/lib/Util/cached-function.d.ts +38 -0
- package/lib/Util/cached-function.d.ts.map +1 -0
- package/lib/Util/cached-function.js +53 -0
- package/lib/Util/cached-function.js.map +1 -0
- package/lib/Util/scc.d.ts +12 -0
- package/lib/Util/scc.d.ts.map +1 -0
- package/lib/Util/scc.js +81 -0
- package/lib/Util/scc.js.map +1 -0
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/AWS/IAM/SAMLProvider.ts +47 -18
- package/src/Apply.ts +72 -8
- package/src/Cli/commands/bootstrap.ts +2 -0
- package/src/Cloudflare/Access.ts +110 -0
- package/src/Cloudflare/AiGateway/AiGateway.ts +526 -0
- package/src/Cloudflare/AiGateway/AiGatewayBinding.ts +190 -0
- package/src/Cloudflare/AiGateway/index.ts +2 -0
- package/src/Cloudflare/EdgeSession.ts +15 -5
- package/src/Cloudflare/Providers.ts +7 -0
- package/src/Cloudflare/StateStore/Api.ts +18 -0
- package/src/Cloudflare/StateStore/State.ts +130 -12
- package/src/Cloudflare/Workers/InferEnv.ts +7 -5
- package/src/Cloudflare/Workers/Worker.ts +44 -33
- package/src/Cloudflare/index.ts +1 -0
- package/src/Plan.ts +21 -5
- package/src/State/HttpStateApi.ts +22 -1
- package/src/Util/cached-function.ts +75 -0
- package/src/Util/scc.ts +89 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import * as aiGateway from "@distilled.cloud/cloudflare/ai-gateway";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import { deepEqual, isResolved } from "../../Diff.ts";
|
|
4
|
+
import { createPhysicalName } from "../../PhysicalName.ts";
|
|
5
|
+
import * as Provider from "../../Provider.ts";
|
|
6
|
+
import { Resource } from "../../Resource.ts";
|
|
7
|
+
import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
|
|
8
|
+
import type { Providers } from "../Providers.ts";
|
|
9
|
+
import { AiGatewayBinding } from "./AiGatewayBinding.ts";
|
|
10
|
+
|
|
11
|
+
export type AiGatewayRateLimitingTechnique = "fixed" | "sliding";
|
|
12
|
+
|
|
13
|
+
export type AiGatewayLogManagementStrategy = "STOP_INSERTING" | "DELETE_OLDEST";
|
|
14
|
+
|
|
15
|
+
export type AiGatewayDlp =
|
|
16
|
+
| {
|
|
17
|
+
/**
|
|
18
|
+
* Action to take when a DLP profile matches.
|
|
19
|
+
*/
|
|
20
|
+
action: "BLOCK" | "FLAG";
|
|
21
|
+
/**
|
|
22
|
+
* Whether DLP is enabled.
|
|
23
|
+
*/
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* DLP profile identifiers to apply.
|
|
27
|
+
*/
|
|
28
|
+
profiles: string[];
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
/**
|
|
32
|
+
* Whether DLP is enabled.
|
|
33
|
+
*/
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* DLP policies to apply.
|
|
37
|
+
*/
|
|
38
|
+
policies: {
|
|
39
|
+
/**
|
|
40
|
+
* DLP policy identifier.
|
|
41
|
+
*/
|
|
42
|
+
id: string;
|
|
43
|
+
/**
|
|
44
|
+
* Action to take when the policy matches.
|
|
45
|
+
*/
|
|
46
|
+
action: "FLAG" | "BLOCK";
|
|
47
|
+
/**
|
|
48
|
+
* Request or response phases checked by the policy.
|
|
49
|
+
*/
|
|
50
|
+
check: ("REQUEST" | "RESPONSE")[];
|
|
51
|
+
/**
|
|
52
|
+
* Whether the policy is enabled.
|
|
53
|
+
*/
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* DLP profile identifiers to apply.
|
|
57
|
+
*/
|
|
58
|
+
profiles: string[];
|
|
59
|
+
}[];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type AiGatewayOtel = {
|
|
63
|
+
/**
|
|
64
|
+
* Authorization header value for the OpenTelemetry endpoint.
|
|
65
|
+
*/
|
|
66
|
+
authorization: string;
|
|
67
|
+
/**
|
|
68
|
+
* Additional headers sent to the OpenTelemetry endpoint.
|
|
69
|
+
*/
|
|
70
|
+
headers: Record<string, unknown>;
|
|
71
|
+
/**
|
|
72
|
+
* OpenTelemetry endpoint URL.
|
|
73
|
+
*/
|
|
74
|
+
url: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type AiGatewayStripe = {
|
|
78
|
+
/**
|
|
79
|
+
* Authorization header value for Stripe usage events.
|
|
80
|
+
*/
|
|
81
|
+
authorization: string;
|
|
82
|
+
/**
|
|
83
|
+
* Stripe usage event payload definitions.
|
|
84
|
+
*/
|
|
85
|
+
usageEvents: {
|
|
86
|
+
/**
|
|
87
|
+
* Usage event payload.
|
|
88
|
+
*/
|
|
89
|
+
payload: string;
|
|
90
|
+
}[];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type AiGatewayProps = {
|
|
94
|
+
/**
|
|
95
|
+
* Gateway identifier. If omitted, a unique ID will be generated.
|
|
96
|
+
*
|
|
97
|
+
* Must be 1-64 characters and match Cloudflare's AI Gateway ID pattern:
|
|
98
|
+
* lowercase letters, numbers, underscores, and hyphens.
|
|
99
|
+
*
|
|
100
|
+
* @default ${app}-${stage}-${id}
|
|
101
|
+
*/
|
|
102
|
+
id?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Whether cached responses are invalidated when a request changes.
|
|
105
|
+
*
|
|
106
|
+
* @default false
|
|
107
|
+
*/
|
|
108
|
+
cacheInvalidateOnUpdate?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Cache time-to-live in seconds. Set to `null` to disable caching.
|
|
111
|
+
*
|
|
112
|
+
* @default null
|
|
113
|
+
*/
|
|
114
|
+
cacheTtl?: number | null;
|
|
115
|
+
/**
|
|
116
|
+
* Whether AI Gateway stores request logs.
|
|
117
|
+
*
|
|
118
|
+
* @default true
|
|
119
|
+
*/
|
|
120
|
+
collectLogs?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Rate limiting interval in seconds. Set to `null` to disable rate limiting.
|
|
123
|
+
*
|
|
124
|
+
* @default null
|
|
125
|
+
*/
|
|
126
|
+
rateLimitingInterval?: number | null;
|
|
127
|
+
/**
|
|
128
|
+
* Maximum requests allowed during the rate limiting interval. Set to `null`
|
|
129
|
+
* to disable rate limiting.
|
|
130
|
+
*
|
|
131
|
+
* @default null
|
|
132
|
+
*/
|
|
133
|
+
rateLimitingLimit?: number | null;
|
|
134
|
+
/**
|
|
135
|
+
* Rate limiting algorithm.
|
|
136
|
+
*
|
|
137
|
+
* @default "fixed"
|
|
138
|
+
*/
|
|
139
|
+
rateLimitingTechnique?: AiGatewayRateLimitingTechnique;
|
|
140
|
+
/**
|
|
141
|
+
* Whether gateway authentication is enabled.
|
|
142
|
+
*/
|
|
143
|
+
authentication?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* DLP configuration. The installed distilled Cloudflare client applies this
|
|
146
|
+
* through the update API after gateway creation.
|
|
147
|
+
*/
|
|
148
|
+
dlp?: AiGatewayDlp;
|
|
149
|
+
/**
|
|
150
|
+
* Whether this gateway is the account default.
|
|
151
|
+
*/
|
|
152
|
+
isDefault?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Maximum number of log entries to retain.
|
|
155
|
+
*/
|
|
156
|
+
logManagement?: number | null;
|
|
157
|
+
/**
|
|
158
|
+
* Strategy used when retained logs reach `logManagement`.
|
|
159
|
+
*/
|
|
160
|
+
logManagementStrategy?: AiGatewayLogManagementStrategy | null;
|
|
161
|
+
/**
|
|
162
|
+
* Whether Logpush is enabled for this gateway.
|
|
163
|
+
*/
|
|
164
|
+
logpush?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Public key used for Logpush encryption.
|
|
167
|
+
*/
|
|
168
|
+
logpushPublicKey?: string | null;
|
|
169
|
+
/**
|
|
170
|
+
* OpenTelemetry export configuration.
|
|
171
|
+
*/
|
|
172
|
+
otel?: AiGatewayOtel[] | null;
|
|
173
|
+
/**
|
|
174
|
+
* Store identifier used by the gateway.
|
|
175
|
+
*/
|
|
176
|
+
storeId?: string | null;
|
|
177
|
+
/**
|
|
178
|
+
* Stripe usage export configuration.
|
|
179
|
+
*/
|
|
180
|
+
stripe?: AiGatewayStripe | null;
|
|
181
|
+
/**
|
|
182
|
+
* Whether Zero Data Retention is enabled.
|
|
183
|
+
*/
|
|
184
|
+
zdr?: boolean;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export type AiGateway = Resource<
|
|
188
|
+
"Cloudflare.AiGateway",
|
|
189
|
+
AiGatewayProps,
|
|
190
|
+
{
|
|
191
|
+
gatewayId: string;
|
|
192
|
+
accountId: string;
|
|
193
|
+
accountTag: string | undefined;
|
|
194
|
+
internalId: string | undefined;
|
|
195
|
+
cacheInvalidateOnUpdate: boolean;
|
|
196
|
+
cacheTtl: number | null;
|
|
197
|
+
collectLogs: boolean;
|
|
198
|
+
createdAt: string;
|
|
199
|
+
modifiedAt: string;
|
|
200
|
+
rateLimitingInterval: number | null;
|
|
201
|
+
rateLimitingLimit: number | null;
|
|
202
|
+
rateLimitingTechnique: AiGatewayRateLimitingTechnique;
|
|
203
|
+
authentication: boolean | undefined;
|
|
204
|
+
dlp: AiGatewayDlp | null | undefined;
|
|
205
|
+
isDefault: boolean | undefined;
|
|
206
|
+
logManagement: number | null | undefined;
|
|
207
|
+
logManagementStrategy: AiGatewayLogManagementStrategy | null | undefined;
|
|
208
|
+
logpush: boolean | undefined;
|
|
209
|
+
logpushPublicKey: string | null | undefined;
|
|
210
|
+
otel: AiGatewayOtel[] | null | undefined;
|
|
211
|
+
storeId: string | null | undefined;
|
|
212
|
+
stripe: AiGatewayStripe | null | undefined;
|
|
213
|
+
zdr: boolean | undefined;
|
|
214
|
+
},
|
|
215
|
+
never,
|
|
216
|
+
Providers
|
|
217
|
+
>;
|
|
218
|
+
|
|
219
|
+
// Cloudflare's AI Gateway API uses 0 to mean "disabled" for cache TTL and
|
|
220
|
+
// rate limiting fields. Normalize back to null so user-facing semantics
|
|
221
|
+
// match what was passed in.
|
|
222
|
+
const nullIfZero = (value: number | null | undefined): number | null =>
|
|
223
|
+
value == null || value === 0 ? null : value;
|
|
224
|
+
|
|
225
|
+
export const isAiGateway = (value: unknown): value is AiGateway =>
|
|
226
|
+
typeof value === "object" &&
|
|
227
|
+
value !== null &&
|
|
228
|
+
"Type" in value &&
|
|
229
|
+
(value as AiGateway).Type === "Cloudflare.AiGateway";
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A Cloudflare AI Gateway for observability, caching, rate limiting, and
|
|
233
|
+
* governance across AI provider requests.
|
|
234
|
+
*
|
|
235
|
+
* AI Gateway gives your application a stable gateway ID and account-scoped
|
|
236
|
+
* endpoint that can route model requests through Cloudflare.
|
|
237
|
+
*
|
|
238
|
+
* @section Creating a Gateway
|
|
239
|
+
* @example Basic gateway
|
|
240
|
+
* ```typescript
|
|
241
|
+
* const gateway = yield* Cloudflare.AiGateway("Gateway");
|
|
242
|
+
* ```
|
|
243
|
+
*
|
|
244
|
+
* @example Gateway with caching and rate limiting
|
|
245
|
+
* ```typescript
|
|
246
|
+
* const gateway = yield* Cloudflare.AiGateway("Gateway", {
|
|
247
|
+
* id: "my-gateway",
|
|
248
|
+
* cacheTtl: 300,
|
|
249
|
+
* cacheInvalidateOnUpdate: true,
|
|
250
|
+
* rateLimitingInterval: 60,
|
|
251
|
+
* rateLimitingLimit: 100,
|
|
252
|
+
* rateLimitingTechnique: "sliding",
|
|
253
|
+
* });
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* @section Logging
|
|
257
|
+
* @example Gateway with log retention
|
|
258
|
+
* ```typescript
|
|
259
|
+
* const gateway = yield* Cloudflare.AiGateway("Gateway", {
|
|
260
|
+
* collectLogs: true,
|
|
261
|
+
* logManagement: 10000,
|
|
262
|
+
* logManagementStrategy: "STOP_INSERTING",
|
|
263
|
+
* });
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export const AiGateway = Resource<AiGateway>("Cloudflare.AiGateway")({
|
|
267
|
+
/**
|
|
268
|
+
* Bind this gateway to the surrounding Worker, returning an Effect-native
|
|
269
|
+
* client for the runtime AI Gateway binding.
|
|
270
|
+
*/
|
|
271
|
+
bind: AiGatewayBinding.bind,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
export const AiGatewayProvider = () =>
|
|
275
|
+
Provider.effect(
|
|
276
|
+
AiGateway,
|
|
277
|
+
Effect.gen(function* () {
|
|
278
|
+
const { accountId } = yield* CloudflareEnvironment;
|
|
279
|
+
const createAiGateway = yield* aiGateway.createAiGateway;
|
|
280
|
+
const getAiGateway = yield* aiGateway.getAiGateway;
|
|
281
|
+
const updateAiGateway = yield* aiGateway.updateAiGateway;
|
|
282
|
+
const deleteAiGateway = yield* aiGateway.deleteAiGateway;
|
|
283
|
+
|
|
284
|
+
const createGatewayId = (id: string, gatewayId: string | undefined) =>
|
|
285
|
+
Effect.gen(function* () {
|
|
286
|
+
if (gatewayId) return gatewayId;
|
|
287
|
+
return yield* createPhysicalName({
|
|
288
|
+
id,
|
|
289
|
+
maxLength: 64,
|
|
290
|
+
lowercase: true,
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const desired = (id: string, props: AiGatewayProps | undefined) =>
|
|
295
|
+
Effect.gen(function* () {
|
|
296
|
+
return {
|
|
297
|
+
gatewayId: yield* createGatewayId(id, props?.id),
|
|
298
|
+
cacheInvalidateOnUpdate: props?.cacheInvalidateOnUpdate ?? false,
|
|
299
|
+
cacheTtl: props?.cacheTtl ?? null,
|
|
300
|
+
collectLogs: props?.collectLogs ?? true,
|
|
301
|
+
rateLimitingInterval: props?.rateLimitingInterval ?? null,
|
|
302
|
+
rateLimitingLimit: props?.rateLimitingLimit ?? null,
|
|
303
|
+
rateLimitingTechnique: props?.rateLimitingTechnique ?? "fixed",
|
|
304
|
+
authentication: props?.authentication,
|
|
305
|
+
dlp: props?.dlp,
|
|
306
|
+
isDefault: props?.isDefault,
|
|
307
|
+
logManagement: props?.logManagement,
|
|
308
|
+
logManagementStrategy: props?.logManagementStrategy,
|
|
309
|
+
logpush: props?.logpush,
|
|
310
|
+
logpushPublicKey: props?.logpushPublicKey,
|
|
311
|
+
otel: props?.otel,
|
|
312
|
+
storeId: props?.storeId,
|
|
313
|
+
stripe: props?.stripe,
|
|
314
|
+
zdr: props?.zdr,
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const mapGateway = (
|
|
319
|
+
gateway:
|
|
320
|
+
| aiGateway.GetAiGatewayResponse
|
|
321
|
+
| aiGateway.CreateAiGatewayResponse
|
|
322
|
+
| aiGateway.UpdateAiGatewayResponse,
|
|
323
|
+
accountId: string,
|
|
324
|
+
): AiGateway["Attributes"] => ({
|
|
325
|
+
gatewayId: gateway.id,
|
|
326
|
+
accountId,
|
|
327
|
+
accountTag: gateway.accountTag ?? undefined,
|
|
328
|
+
internalId: gateway.internalId ?? undefined,
|
|
329
|
+
cacheInvalidateOnUpdate: gateway.cacheInvalidateOnUpdate,
|
|
330
|
+
cacheTtl: nullIfZero(gateway.cacheTtl),
|
|
331
|
+
collectLogs: gateway.collectLogs,
|
|
332
|
+
createdAt: gateway.createdAt,
|
|
333
|
+
modifiedAt: gateway.modifiedAt,
|
|
334
|
+
rateLimitingInterval: nullIfZero(gateway.rateLimitingInterval),
|
|
335
|
+
rateLimitingLimit: nullIfZero(gateway.rateLimitingLimit),
|
|
336
|
+
rateLimitingTechnique: gateway.rateLimitingTechnique ?? "fixed",
|
|
337
|
+
authentication: gateway.authentication ?? undefined,
|
|
338
|
+
dlp: gateway.dlp,
|
|
339
|
+
isDefault: gateway.isDefault ?? undefined,
|
|
340
|
+
logManagement: gateway.logManagement,
|
|
341
|
+
logManagementStrategy: gateway.logManagementStrategy,
|
|
342
|
+
logpush: gateway.logpush ?? undefined,
|
|
343
|
+
logpushPublicKey: gateway.logpushPublicKey,
|
|
344
|
+
otel: gateway.otel,
|
|
345
|
+
storeId: gateway.storeId,
|
|
346
|
+
stripe: gateway.stripe,
|
|
347
|
+
zdr: gateway.zdr ?? undefined,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const mutable = (gateway: AiGateway["Attributes"]) => ({
|
|
351
|
+
cacheInvalidateOnUpdate: gateway.cacheInvalidateOnUpdate,
|
|
352
|
+
cacheTtl: gateway.cacheTtl,
|
|
353
|
+
collectLogs: gateway.collectLogs,
|
|
354
|
+
rateLimitingInterval: gateway.rateLimitingInterval,
|
|
355
|
+
rateLimitingLimit: gateway.rateLimitingLimit,
|
|
356
|
+
rateLimitingTechnique: gateway.rateLimitingTechnique,
|
|
357
|
+
authentication: gateway.authentication,
|
|
358
|
+
dlp: gateway.dlp ?? undefined,
|
|
359
|
+
isDefault: gateway.isDefault,
|
|
360
|
+
logManagement: gateway.logManagement,
|
|
361
|
+
logManagementStrategy: gateway.logManagementStrategy,
|
|
362
|
+
logpush: gateway.logpush,
|
|
363
|
+
logpushPublicKey: gateway.logpushPublicKey,
|
|
364
|
+
otel: gateway.otel,
|
|
365
|
+
storeId: gateway.storeId,
|
|
366
|
+
stripe: gateway.stripe,
|
|
367
|
+
zdr: gateway.zdr,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const createRequest = Effect.fn(function* (
|
|
371
|
+
id: string,
|
|
372
|
+
props: AiGatewayProps | undefined,
|
|
373
|
+
) {
|
|
374
|
+
const next = yield* desired(id, props);
|
|
375
|
+
return {
|
|
376
|
+
accountId,
|
|
377
|
+
id: next.gatewayId,
|
|
378
|
+
cacheInvalidateOnUpdate: next.cacheInvalidateOnUpdate,
|
|
379
|
+
cacheTtl: next.cacheTtl,
|
|
380
|
+
collectLogs: next.collectLogs,
|
|
381
|
+
rateLimitingInterval: next.rateLimitingInterval,
|
|
382
|
+
rateLimitingLimit: next.rateLimitingLimit,
|
|
383
|
+
rateLimitingTechnique: next.rateLimitingTechnique,
|
|
384
|
+
authentication: next.authentication,
|
|
385
|
+
isDefault: next.isDefault,
|
|
386
|
+
logManagement: next.logManagement,
|
|
387
|
+
logManagementStrategy: next.logManagementStrategy,
|
|
388
|
+
logpush: next.logpush,
|
|
389
|
+
logpushPublicKey: next.logpushPublicKey,
|
|
390
|
+
zdr: next.zdr,
|
|
391
|
+
} satisfies aiGateway.CreateAiGatewayRequest;
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const updateRequest = Effect.fn(function* (
|
|
395
|
+
id: string,
|
|
396
|
+
props: AiGatewayProps | undefined,
|
|
397
|
+
accountId: string,
|
|
398
|
+
) {
|
|
399
|
+
const next = yield* desired(id, props);
|
|
400
|
+
return {
|
|
401
|
+
accountId,
|
|
402
|
+
id: next.gatewayId,
|
|
403
|
+
cacheInvalidateOnUpdate: next.cacheInvalidateOnUpdate,
|
|
404
|
+
cacheTtl: next.cacheTtl,
|
|
405
|
+
collectLogs: next.collectLogs,
|
|
406
|
+
rateLimitingInterval: next.rateLimitingInterval,
|
|
407
|
+
rateLimitingLimit: next.rateLimitingLimit,
|
|
408
|
+
rateLimitingTechnique: next.rateLimitingTechnique,
|
|
409
|
+
authentication: next.authentication,
|
|
410
|
+
dlp: next.dlp,
|
|
411
|
+
isDefault: next.isDefault,
|
|
412
|
+
logManagement: next.logManagement,
|
|
413
|
+
logManagementStrategy: next.logManagementStrategy,
|
|
414
|
+
logpush: next.logpush,
|
|
415
|
+
logpushPublicKey: next.logpushPublicKey,
|
|
416
|
+
otel: next.otel,
|
|
417
|
+
storeId: next.storeId,
|
|
418
|
+
stripe: next.stripe,
|
|
419
|
+
zdr: next.zdr,
|
|
420
|
+
} satisfies aiGateway.UpdateAiGatewayRequest;
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
stables: ["gatewayId", "accountId"],
|
|
425
|
+
diff: Effect.fn(function* ({ id, olds = {}, news = {}, output }) {
|
|
426
|
+
if (!isResolved(news)) return undefined;
|
|
427
|
+
const next = yield* desired(id, news);
|
|
428
|
+
const oldGatewayId =
|
|
429
|
+
output?.gatewayId ?? (yield* createGatewayId(id, olds.id));
|
|
430
|
+
if (
|
|
431
|
+
(output?.accountId ?? accountId) !== accountId ||
|
|
432
|
+
oldGatewayId !== next.gatewayId
|
|
433
|
+
) {
|
|
434
|
+
return { action: "replace" } as const;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const oldMutable = output
|
|
438
|
+
? mutable(output)
|
|
439
|
+
: yield* desired(id, olds).pipe(
|
|
440
|
+
Effect.map((old) => ({
|
|
441
|
+
cacheInvalidateOnUpdate: old.cacheInvalidateOnUpdate,
|
|
442
|
+
cacheTtl: old.cacheTtl,
|
|
443
|
+
collectLogs: old.collectLogs,
|
|
444
|
+
rateLimitingInterval: old.rateLimitingInterval,
|
|
445
|
+
rateLimitingLimit: old.rateLimitingLimit,
|
|
446
|
+
rateLimitingTechnique: old.rateLimitingTechnique,
|
|
447
|
+
authentication: old.authentication,
|
|
448
|
+
dlp: old.dlp,
|
|
449
|
+
isDefault: old.isDefault,
|
|
450
|
+
logManagement: old.logManagement,
|
|
451
|
+
logManagementStrategy: old.logManagementStrategy,
|
|
452
|
+
logpush: old.logpush,
|
|
453
|
+
logpushPublicKey: old.logpushPublicKey,
|
|
454
|
+
otel: old.otel,
|
|
455
|
+
storeId: old.storeId,
|
|
456
|
+
stripe: old.stripe,
|
|
457
|
+
zdr: old.zdr,
|
|
458
|
+
})),
|
|
459
|
+
);
|
|
460
|
+
const nextMutable = {
|
|
461
|
+
cacheInvalidateOnUpdate: next.cacheInvalidateOnUpdate,
|
|
462
|
+
cacheTtl: next.cacheTtl,
|
|
463
|
+
collectLogs: next.collectLogs,
|
|
464
|
+
rateLimitingInterval: next.rateLimitingInterval,
|
|
465
|
+
rateLimitingLimit: next.rateLimitingLimit,
|
|
466
|
+
rateLimitingTechnique: next.rateLimitingTechnique,
|
|
467
|
+
authentication: next.authentication,
|
|
468
|
+
dlp: next.dlp,
|
|
469
|
+
isDefault: next.isDefault,
|
|
470
|
+
logManagement: next.logManagement,
|
|
471
|
+
logManagementStrategy: next.logManagementStrategy,
|
|
472
|
+
logpush: next.logpush,
|
|
473
|
+
logpushPublicKey: next.logpushPublicKey,
|
|
474
|
+
otel: next.otel,
|
|
475
|
+
storeId: next.storeId,
|
|
476
|
+
stripe: next.stripe,
|
|
477
|
+
zdr: next.zdr,
|
|
478
|
+
};
|
|
479
|
+
if (!deepEqual(oldMutable, nextMutable)) {
|
|
480
|
+
return { action: "update" } as const;
|
|
481
|
+
}
|
|
482
|
+
}),
|
|
483
|
+
create: Effect.fn(function* ({ id, news = {} }) {
|
|
484
|
+
const request = yield* createRequest(id, news);
|
|
485
|
+
const gateway = yield* createAiGateway(request).pipe(
|
|
486
|
+
Effect.catchTag("GatewayAlreadyExists", () =>
|
|
487
|
+
getAiGateway({
|
|
488
|
+
accountId,
|
|
489
|
+
id: request.id,
|
|
490
|
+
}),
|
|
491
|
+
),
|
|
492
|
+
);
|
|
493
|
+
const update = yield* updateRequest(id, news, accountId);
|
|
494
|
+
return yield* updateAiGateway(update).pipe(
|
|
495
|
+
Effect.map((gateway) => mapGateway(gateway, accountId)),
|
|
496
|
+
Effect.catchTag("GatewayNotFound", () =>
|
|
497
|
+
Effect.succeed(mapGateway(gateway, accountId)),
|
|
498
|
+
),
|
|
499
|
+
);
|
|
500
|
+
}),
|
|
501
|
+
update: Effect.fn(function* ({ id, news = {}, output }) {
|
|
502
|
+
const request = yield* updateRequest(id, news, output.accountId);
|
|
503
|
+
const gateway = yield* updateAiGateway(request);
|
|
504
|
+
return mapGateway(gateway, output.accountId);
|
|
505
|
+
}),
|
|
506
|
+
delete: Effect.fn(function* ({ output }) {
|
|
507
|
+
yield* deleteAiGateway({
|
|
508
|
+
accountId: output.accountId,
|
|
509
|
+
id: output.gatewayId,
|
|
510
|
+
}).pipe(Effect.catchTag("GatewayNotFound", () => Effect.void));
|
|
511
|
+
}),
|
|
512
|
+
read: Effect.fn(function* ({ id, olds, output }) {
|
|
513
|
+
const gatewayId =
|
|
514
|
+
output?.gatewayId ?? (yield* createGatewayId(id, olds?.id));
|
|
515
|
+
const acct = output?.accountId ?? accountId;
|
|
516
|
+
return yield* getAiGateway({
|
|
517
|
+
accountId: acct,
|
|
518
|
+
id: gatewayId,
|
|
519
|
+
}).pipe(
|
|
520
|
+
Effect.map((gateway) => mapGateway(gateway, acct)),
|
|
521
|
+
Effect.catchTag("GatewayNotFound", () => Effect.succeed(undefined)),
|
|
522
|
+
);
|
|
523
|
+
}),
|
|
524
|
+
};
|
|
525
|
+
}),
|
|
526
|
+
);
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
|
|
3
|
+
import * as Data from "effect/Data";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
import * as Option from "effect/Option";
|
|
7
|
+
import * as Binding from "../../Binding.ts";
|
|
8
|
+
import type { ResourceLike } from "../../Resource.ts";
|
|
9
|
+
import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
|
|
10
|
+
import type { AiGateway as AiGatewayResource } from "./AiGateway.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Error raised by AI Gateway runtime operations.
|
|
14
|
+
*/
|
|
15
|
+
export class AiGatewayError extends Data.TaggedError("AiGatewayError")<{
|
|
16
|
+
/**
|
|
17
|
+
* Human-readable runtime error message.
|
|
18
|
+
*/
|
|
19
|
+
message: string;
|
|
20
|
+
/**
|
|
21
|
+
* Original error thrown by the Cloudflare runtime binding.
|
|
22
|
+
*/
|
|
23
|
+
cause: unknown;
|
|
24
|
+
}> {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Effect-native client for a Cloudflare AI Gateway Worker binding.
|
|
28
|
+
*
|
|
29
|
+
* Wraps the runtime {@link AiGateway} binding so each operation returns an
|
|
30
|
+
* Effect tagged with {@link AiGatewayError}. Use
|
|
31
|
+
* `Cloudflare.AiGatewayBinding.bind(gateway)` inside a Worker's init phase.
|
|
32
|
+
*/
|
|
33
|
+
export interface AiGatewayClient {
|
|
34
|
+
/**
|
|
35
|
+
* Effect resolving to the raw Workers AI binding.
|
|
36
|
+
*/
|
|
37
|
+
raw: Effect.Effect<Ai, never, WorkerEnvironment>;
|
|
38
|
+
/**
|
|
39
|
+
* Effect resolving to the raw AI Gateway runtime binding.
|
|
40
|
+
*/
|
|
41
|
+
gateway: Effect.Effect<AiGateway, never, WorkerEnvironment>;
|
|
42
|
+
/**
|
|
43
|
+
* Update metadata on an existing AI Gateway log entry.
|
|
44
|
+
*/
|
|
45
|
+
patchLog(
|
|
46
|
+
logId: string,
|
|
47
|
+
data: Parameters<AiGateway["patchLog"]>[1],
|
|
48
|
+
): Effect.Effect<void, AiGatewayError, WorkerEnvironment>;
|
|
49
|
+
/**
|
|
50
|
+
* Read an AI Gateway log entry by ID.
|
|
51
|
+
*/
|
|
52
|
+
getLog(
|
|
53
|
+
logId: string,
|
|
54
|
+
): Effect.Effect<AiGatewayLog, AiGatewayError, WorkerEnvironment>;
|
|
55
|
+
/**
|
|
56
|
+
* Build a provider URL routed through this gateway.
|
|
57
|
+
*/
|
|
58
|
+
getUrl(
|
|
59
|
+
provider?: Parameters<AiGateway["getUrl"]>[0],
|
|
60
|
+
): Effect.Effect<string, AiGatewayError, WorkerEnvironment>;
|
|
61
|
+
/**
|
|
62
|
+
* Run an AI Gateway request through the Cloudflare runtime binding.
|
|
63
|
+
*/
|
|
64
|
+
run(
|
|
65
|
+
data: Parameters<AiGateway["run"]>[0],
|
|
66
|
+
options?: Parameters<AiGateway["run"]>[1],
|
|
67
|
+
): Effect.Effect<Response, AiGatewayError, WorkerEnvironment>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Binding service that turns an {@link AiGatewayResource} resource into a typed
|
|
72
|
+
* {@link AiGatewayClient} for Worker runtime code.
|
|
73
|
+
*
|
|
74
|
+
* @section Calling AI Gateway
|
|
75
|
+
* Bind the gateway during the Worker's init phase, then use `run` or `getUrl`
|
|
76
|
+
* from request handlers.
|
|
77
|
+
*
|
|
78
|
+
* @example Run through a gateway
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const aiGateway = yield* Cloudflare.AiGatewayBinding.bind(gateway);
|
|
81
|
+
*
|
|
82
|
+
* return {
|
|
83
|
+
* fetch: Effect.gen(function* () {
|
|
84
|
+
* return yield* aiGateway.run({
|
|
85
|
+
* provider: "workers-ai",
|
|
86
|
+
* endpoint: "@cf/meta/llama-3.1-8b-instruct",
|
|
87
|
+
* headers: { "content-type": "application/json" },
|
|
88
|
+
* query: { prompt: "Write a concise status update" },
|
|
89
|
+
* });
|
|
90
|
+
* }),
|
|
91
|
+
* };
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* Provide {@link AiGatewayBindingLive} in the worker's runtime layer to
|
|
95
|
+
* resolve the underlying Cloudflare AI binding at request time.
|
|
96
|
+
*/
|
|
97
|
+
export class AiGatewayBinding extends Binding.Service<
|
|
98
|
+
AiGatewayBinding,
|
|
99
|
+
(gateway: AiGatewayResource) => Effect.Effect<AiGatewayClient>
|
|
100
|
+
>()("Cloudflare.AiGateway.Binding") {}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Runtime layer for {@link AiGatewayBinding}.
|
|
104
|
+
*/
|
|
105
|
+
export const AiGatewayBindingLive = Layer.effect(
|
|
106
|
+
AiGatewayBinding,
|
|
107
|
+
Effect.gen(function* () {
|
|
108
|
+
const Policy = yield* AiGatewayBindingPolicy;
|
|
109
|
+
|
|
110
|
+
return Effect.fn(function* (gateway: AiGatewayResource) {
|
|
111
|
+
yield* Policy(gateway);
|
|
112
|
+
// Capture the gatewayId accessor (which requires WorkerEnvironment) but
|
|
113
|
+
// don't resolve it here — that happens lazily at runtime when each
|
|
114
|
+
// method is invoked. Resolving eagerly would require WorkerEnvironment
|
|
115
|
+
// at deploy time, where it's intentionally not provided.
|
|
116
|
+
const gatewayIdAccessor = yield* gateway.gatewayId;
|
|
117
|
+
const ai = yield* Effect.serviceOption(WorkerEnvironment).pipe(
|
|
118
|
+
Effect.map(Option.getOrUndefined),
|
|
119
|
+
Effect.map((env) => env?.[gateway.LogicalId]! as Ai),
|
|
120
|
+
Effect.cached,
|
|
121
|
+
);
|
|
122
|
+
const runtimeGateway = yield* Effect.zip(ai, gatewayIdAccessor).pipe(
|
|
123
|
+
Effect.map(([ai, gatewayId]) => ai.gateway(gatewayId)),
|
|
124
|
+
Effect.cached,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const use = <T>(
|
|
128
|
+
fn: (gateway: AiGateway) => Promise<T>,
|
|
129
|
+
): Effect.Effect<T, AiGatewayError, WorkerEnvironment> =>
|
|
130
|
+
runtimeGateway.pipe(
|
|
131
|
+
Effect.flatMap((gateway) => tryPromise(() => fn(gateway))),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
raw: ai,
|
|
136
|
+
gateway: runtimeGateway,
|
|
137
|
+
patchLog: (logId, data) =>
|
|
138
|
+
use((gateway) => gateway.patchLog(logId, data)),
|
|
139
|
+
getLog: (logId) => use((gateway) => gateway.getLog(logId)),
|
|
140
|
+
getUrl: (provider) => use((gateway) => gateway.getUrl(provider)),
|
|
141
|
+
run: (data, options) => use((gateway) => gateway.run(data, options)),
|
|
142
|
+
} satisfies AiGatewayClient;
|
|
143
|
+
});
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Deploy-time policy service that attaches an AI binding to Workers.
|
|
149
|
+
*/
|
|
150
|
+
export class AiGatewayBindingPolicy extends Binding.Policy<
|
|
151
|
+
AiGatewayBindingPolicy,
|
|
152
|
+
(gateway: AiGatewayResource) => Effect.Effect<void>
|
|
153
|
+
>()("Cloudflare.AiGateway.Binding") {}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Live deploy-time policy layer for {@link AiGatewayBindingPolicy}.
|
|
157
|
+
*/
|
|
158
|
+
export const AiGatewayBindingPolicyLive = AiGatewayBindingPolicy.layer.succeed(
|
|
159
|
+
Effect.fn(function* (host: ResourceLike, gateway: AiGatewayResource) {
|
|
160
|
+
if (isWorker(host)) {
|
|
161
|
+
yield* host.bind(gateway.LogicalId, {
|
|
162
|
+
bindings: [
|
|
163
|
+
{
|
|
164
|
+
type: "ai",
|
|
165
|
+
name: gateway.LogicalId,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
return yield* Effect.die(
|
|
171
|
+
new Error(`AiGatewayBinding does not support runtime '${host.Type}'`),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const tryPromise = <T>(
|
|
178
|
+
fn: () => Promise<T>,
|
|
179
|
+
): Effect.Effect<T, AiGatewayError> =>
|
|
180
|
+
Effect.tryPromise({
|
|
181
|
+
try: fn,
|
|
182
|
+
catch: (error) =>
|
|
183
|
+
new AiGatewayError({
|
|
184
|
+
message:
|
|
185
|
+
error instanceof Error
|
|
186
|
+
? error.message
|
|
187
|
+
: "Unknown AI Gateway runtime error",
|
|
188
|
+
cause: error,
|
|
189
|
+
}),
|
|
190
|
+
});
|