alchemy 0.92.2 → 0.93.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.
Files changed (53) hide show
  1. package/bin/alchemy.js +1 -1
  2. package/lib/cloudflare/ai-search-namespace.d.ts +150 -0
  3. package/lib/cloudflare/ai-search-namespace.d.ts.map +1 -0
  4. package/lib/cloudflare/ai-search-namespace.js +281 -0
  5. package/lib/cloudflare/ai-search-namespace.js.map +1 -0
  6. package/lib/cloudflare/ai-search-token.d.ts +11 -1
  7. package/lib/cloudflare/ai-search-token.d.ts.map +1 -1
  8. package/lib/cloudflare/ai-search-token.js +35 -7
  9. package/lib/cloudflare/ai-search-token.js.map +1 -1
  10. package/lib/cloudflare/ai-search.d.ts +123 -18
  11. package/lib/cloudflare/ai-search.d.ts.map +1 -1
  12. package/lib/cloudflare/ai-search.js +239 -68
  13. package/lib/cloudflare/ai-search.js.map +1 -1
  14. package/lib/cloudflare/bindings.d.ts +29 -2
  15. package/lib/cloudflare/bindings.d.ts.map +1 -1
  16. package/lib/cloudflare/bindings.js.map +1 -1
  17. package/lib/cloudflare/bound.d.ts +4 -2
  18. package/lib/cloudflare/bound.d.ts.map +1 -1
  19. package/lib/cloudflare/container.d.ts +79 -6
  20. package/lib/cloudflare/container.d.ts.map +1 -1
  21. package/lib/cloudflare/container.js +117 -2
  22. package/lib/cloudflare/container.js.map +1 -1
  23. package/lib/cloudflare/index.d.ts +1 -0
  24. package/lib/cloudflare/index.d.ts.map +1 -1
  25. package/lib/cloudflare/index.js +1 -0
  26. package/lib/cloudflare/index.js.map +1 -1
  27. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  28. package/lib/cloudflare/miniflare/build-worker-options.js +37 -0
  29. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  30. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  31. package/lib/cloudflare/worker-metadata.js +25 -1
  32. package/lib/cloudflare/worker-metadata.js.map +1 -1
  33. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  34. package/lib/cloudflare/wrangler.json.js +32 -0
  35. package/lib/cloudflare/wrangler.json.js.map +1 -1
  36. package/lib/docker/api.d.ts +4 -1
  37. package/lib/docker/api.d.ts.map +1 -1
  38. package/lib/docker/api.js +8 -2
  39. package/lib/docker/api.js.map +1 -1
  40. package/package.json +2 -2
  41. package/src/cloudflare/ai-search-namespace.ts +439 -0
  42. package/src/cloudflare/ai-search-token.ts +43 -9
  43. package/src/cloudflare/ai-search.ts +353 -73
  44. package/src/cloudflare/bindings.ts +33 -0
  45. package/src/cloudflare/bound.ts +87 -74
  46. package/src/cloudflare/container.ts +228 -11
  47. package/src/cloudflare/index.ts +1 -0
  48. package/src/cloudflare/miniflare/build-worker-options.ts +39 -0
  49. package/src/cloudflare/worker-metadata.ts +25 -1
  50. package/src/cloudflare/wrangler.json.ts +32 -0
  51. package/src/docker/api.ts +11 -2
  52. package/workers/cloudflare-state-store.js +57 -57
  53. package/workers/tunnel-proxy.js +1 -1
@@ -5,6 +5,8 @@
5
5
  */
6
6
  import type { Secret } from "../secret.ts";
7
7
  import type { Ai } from "./ai.ts";
8
+ import type { AiSearchNamespace } from "./ai-search-namespace.ts";
9
+ import type { AiSearch } from "./ai-search.ts";
8
10
  import type { AnalyticsEngineDataset } from "./analytics-engine.ts";
9
11
  import type { Assets } from "./assets.ts";
10
12
  import type { Bound } from "./bound.ts";
@@ -49,6 +51,8 @@ export declare namespace Bindings {
49
51
  */
50
52
  export type Binding =
51
53
  | Ai
54
+ | AiSearch
55
+ | AiSearchNamespace
52
56
  | Assets
53
57
  | Container
54
58
  | CloudflareSecret
@@ -120,6 +124,8 @@ export function Json<const T>(value: T): Json<T> {
120
124
  */
121
125
  export type WorkerBindingSpec =
122
126
  | WorkerBindingAI
127
+ | WorkerBindingAiSearch
128
+ | WorkerBindingAiSearchNamespace
123
129
  | WorkerBindingAnalyticsEngine
124
130
  | WorkerBindingAssets
125
131
  | WorkerBindingBrowserRendering
@@ -161,6 +167,33 @@ export interface WorkerBindingAI {
161
167
  type: "ai";
162
168
  }
163
169
 
170
+ /**
171
+ * AI Search single instance binding type.
172
+ * Binds directly to one specific AI Search instance (always scoped to default namespace).
173
+ */
174
+ export interface WorkerBindingAiSearch {
175
+ /** The name of the binding */
176
+ name: string;
177
+ /** Type identifier for AI Search single instance binding */
178
+ type: "ai_search";
179
+ /** The AI Search instance name */
180
+ instance_name: string;
181
+ }
182
+
183
+ /**
184
+ * AI Search namespace binding type.
185
+ * Scoped to a user-defined namespace of AI Search instances.
186
+ * Grants full access (CRUD + search + chat) to all instances within the namespace.
187
+ */
188
+ export interface WorkerBindingAiSearchNamespace {
189
+ /** The name of the binding */
190
+ name: string;
191
+ /** Type identifier for AI Search namespace binding */
192
+ type: "ai_search_namespace";
193
+ /** The namespace name */
194
+ namespace: string;
195
+ }
196
+
164
197
  /**
165
198
  * Analytics Engine binding type
166
199
  */
@@ -1,6 +1,9 @@
1
+ /// <reference types="@cloudflare/workers-types" />
1
2
  import type { Pipeline } from "cloudflare:pipelines";
2
3
  import type { Secret } from "../secret.ts";
3
4
  import type { Ai as _Ai } from "./ai.ts";
5
+ import type { AiSearchNamespace as _AiSearchNamespace } from "./ai-search-namespace.ts";
6
+ import type { AiSearch as _AiSearch } from "./ai-search.ts";
4
7
  import type { AnalyticsEngineDataset as _AnalyticsEngineDataset } from "./analytics-engine.ts";
5
8
  import type { Assets } from "./assets.ts";
6
9
  import type { Binding, Json, Self } from "./bindings.ts";
@@ -9,6 +12,7 @@ import type { R2Bucket as _R2Bucket } from "./bucket.ts";
9
12
  import type { Container as _Container } from "./container.ts";
10
13
  import type { D1Database as _D1Database } from "./d1-database.ts";
11
14
  import type { DurableObjectNamespace as _DurableObjectNamespace } from "./durable-object-namespace.ts";
15
+ import type { EmailSender as _EmailSender } from "./email-sender.ts";
12
16
  import type { HyperdriveRef } from "./hyperdrive-ref.ts";
13
17
  import type { Hyperdrive as _Hyperdrive } from "./hyperdrive.ts";
14
18
  import type { Images as _Images } from "./images.ts";
@@ -18,7 +22,6 @@ import type { RateLimit as _RateLimit } from "./rate-limit.ts";
18
22
  import type { SecretKey } from "./secret-key.ts";
19
23
  import type { SecretRef as CloudflareSecretRef } from "./secret-ref.ts";
20
24
  import type { Secret as CloudflareSecret } from "./secret.ts";
21
- import type { EmailSender as _EmailSender } from "./email-sender.ts";
22
25
  import type { VectorizeIndex as _VectorizeIndex } from "./vectorize-index.ts";
23
26
  import type { VersionMetadata as _VersionMetadata } from "./version-metadata.ts";
24
27
  import type { VpcService as _VpcService } from "./vpc-service.ts";
@@ -39,78 +42,88 @@ type BoundWorker<
39
42
  >]: Rpc.Provider<RPC, "fetch" | "connect">[property];
40
43
  };
41
44
 
42
- export type Bound<T extends Binding> =
43
- T extends _DurableObjectNamespace<infer O>
44
- ? DurableObjectNamespace<O & Rpc.DurableObjectBranded>
45
- : T extends { type: "kv_namespace" }
46
- ? KVNamespace
47
- : T extends WorkerStub<infer RPC>
48
- ? BoundWorker<RPC>
49
- : T extends _Worker<any, infer RPC> | WorkerRef<infer RPC>
45
+ // NOTE: AiSearch / AiSearchNamespace MUST come FIRST in this conditional
46
+ // chain, before any structural discriminator like `{ type: "kv_namespace" }`.
47
+ // `AiSearch.type` is the data-source type (`"r2" | "web-crawler"`), NOT a
48
+ // binding-type discriminator so we match by *resource-type import* via the
49
+ // `_AiSearch` / `_AiSearchNamespace` aliases (underscore-prefixed to avoid
50
+ // colliding with the identically-named Cloudflare runtime classes on the
51
+ // right-hand side of the conditional).
52
+ export type Bound<T extends Binding> = T extends _AiSearch
53
+ ? AiSearchInstance
54
+ : T extends _AiSearchNamespace
55
+ ? AiSearchNamespace
56
+ : T extends _DurableObjectNamespace<infer O>
57
+ ? DurableObjectNamespace<O & Rpc.DurableObjectBranded>
58
+ : T extends { type: "kv_namespace" }
59
+ ? KVNamespace
60
+ : T extends WorkerStub<infer RPC>
50
61
  ? BoundWorker<RPC>
51
- : T extends { type: "service" }
52
- ? Service
53
- : T extends _R2Bucket
54
- ? R2Bucket
55
- : T extends _Hyperdrive | HyperdriveRef
56
- ? Hyperdrive
57
- : T extends Secret
58
- ? string
59
- : T extends CloudflareSecret | CloudflareSecretRef
60
- ? SecretsStoreSecret
61
- : T extends _EmailSender
62
- ? SendEmail
63
- : T extends SecretKey
64
- ? CryptoKey
65
- : T extends Assets
66
- ? Service
67
- : T extends _Workflow<infer P>
68
- ? Workflow<P>
69
- : T extends _D1Database
70
- ? D1Database
71
- : T extends DispatchNamespace
72
- ? DispatchNamespace
73
- : T extends _WorkerLoader
74
- ? WorkerLoader
75
- : T extends _VectorizeIndex
76
- ? VectorizeIndex
77
- : T extends _Queue<infer Body>
78
- ? Queue<Body>
79
- : T extends _AnalyticsEngineDataset
80
- ? AnalyticsEngineDataset
81
- : T extends _Pipeline<infer R>
82
- ? Pipeline<R>
83
- : T extends _RateLimit
84
- ? RateLimit
85
- : T extends string
86
- ? T
87
- : T extends BrowserRendering
88
- ? Fetcher
89
- : T extends _Ai<infer M>
90
- ? Ai<M>
91
- : T extends _Images
92
- ? ImagesBinding
93
- : T extends _VersionMetadata
94
- ? WorkerVersionMetadata
95
- : T extends
96
- | _Worker.DevDomain
97
- | _Worker.DevUrl
98
- ? string
99
- : T extends Self
100
- ? Service
101
- : T extends Json<
102
- infer T
103
- >
104
- ? T
105
- : T extends _Container<
106
- infer Obj
107
- >
108
- ? DurableObjectNamespace<
109
- Obj &
110
- Rpc.DurableObjectBranded
62
+ : T extends _Worker<any, infer RPC> | WorkerRef<infer RPC>
63
+ ? BoundWorker<RPC>
64
+ : T extends { type: "service" }
65
+ ? Service
66
+ : T extends _R2Bucket
67
+ ? R2Bucket
68
+ : T extends _Hyperdrive | HyperdriveRef
69
+ ? Hyperdrive
70
+ : T extends Secret
71
+ ? string
72
+ : T extends CloudflareSecret | CloudflareSecretRef
73
+ ? SecretsStoreSecret
74
+ : T extends _EmailSender
75
+ ? SendEmail
76
+ : T extends SecretKey
77
+ ? CryptoKey
78
+ : T extends Assets
79
+ ? Service
80
+ : T extends _Workflow<infer P>
81
+ ? Workflow<P>
82
+ : T extends _D1Database
83
+ ? D1Database
84
+ : T extends DispatchNamespace
85
+ ? DispatchNamespace
86
+ : T extends _WorkerLoader
87
+ ? WorkerLoader
88
+ : T extends _VectorizeIndex
89
+ ? VectorizeIndex
90
+ : T extends _Queue<infer Body>
91
+ ? Queue<Body>
92
+ : T extends _AnalyticsEngineDataset
93
+ ? AnalyticsEngineDataset
94
+ : T extends _Pipeline<infer R>
95
+ ? Pipeline<R>
96
+ : T extends _RateLimit
97
+ ? RateLimit
98
+ : T extends string
99
+ ? T
100
+ : T extends BrowserRendering
101
+ ? Fetcher
102
+ : T extends _Ai<infer M>
103
+ ? Ai<M>
104
+ : T extends _Images
105
+ ? ImagesBinding
106
+ : T extends _VersionMetadata
107
+ ? WorkerVersionMetadata
108
+ : T extends
109
+ | _Worker.DevDomain
110
+ | _Worker.DevUrl
111
+ ? string
112
+ : T extends Self
113
+ ? Service
114
+ : T extends Json<
115
+ infer T
111
116
  >
112
- : T extends _VpcService
113
- ? Fetcher
114
- : T extends undefined
115
- ? undefined
116
- : Service;
117
+ ? T
118
+ : T extends _Container<
119
+ infer Obj
120
+ >
121
+ ? DurableObjectNamespace<
122
+ Obj &
123
+ Rpc.DurableObjectBranded
124
+ >
125
+ : T extends _VpcService
126
+ ? Fetcher
127
+ : T extends undefined
128
+ ? undefined
129
+ : Service;
@@ -1,30 +1,35 @@
1
1
  import type { Context } from "../context.ts";
2
- import { Image, type ImageProps } from "../docker/image.ts";
2
+ import { DockerApi } from "../docker/api.ts";
3
+ import {
4
+ Image,
5
+ type DockerBuildOptions,
6
+ type ImageProps,
7
+ } from "../docker/image.ts";
8
+ import type { RemoteImage } from "../docker/remote-image.ts";
3
9
  import { Resource } from "../resource.ts";
4
10
  import { Scope } from "../scope.ts";
5
11
  import { secret } from "../secret.ts";
6
12
  import {
13
+ createCloudflareApi,
7
14
  type CloudflareApi,
8
15
  type CloudflareApiOptions,
9
- createCloudflareApi,
10
16
  } from "./api.ts";
11
17
 
12
18
  /**
13
- * Properties for creating a Container binding or ContainerApplication
14
- *
15
- * Extends ImageProps for Docker image configuration and CloudflareApiOptions
16
- * for Cloudflare API authentication.
19
+ * Common properties shared between build and image container configurations
17
20
  */
18
- export interface ContainerProps
19
- extends
20
- Omit<ImageProps, "registry" | "skipPush">,
21
- Partial<CloudflareApiOptions> {
21
+ interface ContainerPropsBase extends Partial<CloudflareApiOptions> {
22
22
  /**
23
23
  * The class name for the container binding.
24
24
  * This is used to identify the container class in Worker bindings.
25
25
  */
26
26
  className: string;
27
27
 
28
+ /**
29
+ * Tag for the image (e.g., "latest")
30
+ */
31
+ tag?: string;
32
+
28
33
  /**
29
34
  * Maximum number of container instances that can be running.
30
35
  * Controls horizontal scaling limits.
@@ -92,6 +97,75 @@ export interface ContainerProps
92
97
  rollout?: ContainerApplicationRollout;
93
98
  }
94
99
 
100
+ /**
101
+ * Container configuration using an existing image reference
102
+ */
103
+ interface ContainerPropsWithImage extends ContainerPropsBase {
104
+ /**
105
+ * Name for the container application.
106
+ *
107
+ * @default ${app}-${stage}-${id}
108
+ */
109
+ name?: string;
110
+
111
+ /**
112
+ * Image name or reference (e.g., "nginx:alpine")
113
+ *
114
+ * Use this when you want to deploy an existing image rather than building one.
115
+ * Cannot be used together with `build`.
116
+ */
117
+ image: string | Image | RemoteImage;
118
+
119
+ build?: never;
120
+ }
121
+
122
+ /**
123
+ * Container configuration that builds a Docker image
124
+ */
125
+ interface ContainerPropsWithBuild extends ContainerPropsBase {
126
+ /**
127
+ * Name for the container application and image repository.
128
+ *
129
+ * @default ${app}-${stage}-${id}
130
+ */
131
+ name?: string;
132
+
133
+ /**
134
+ * Build configuration for the Docker image.
135
+ *
136
+ * Use this when you want to build an image from a Dockerfile.
137
+ * Cannot be used together with `image`.
138
+ */
139
+ build: DockerBuildOptions;
140
+
141
+ image?: never;
142
+ }
143
+
144
+ /**
145
+ * Properties for creating a Container binding or ContainerApplication
146
+ *
147
+ * Either provide `image` to use an existing image, or `build` to build from a Dockerfile.
148
+ * These options are mutually exclusive.
149
+ *
150
+ * @example
151
+ * // Using an existing image
152
+ * const container = await Container("my-container", {
153
+ * className: "MyContainer",
154
+ * image: "nginx:alpine"
155
+ * });
156
+ *
157
+ * @example
158
+ * // Building from a Dockerfile
159
+ * const container = await Container("my-container", {
160
+ * className: "MyContainer",
161
+ * build: {
162
+ * context: "./app",
163
+ * dockerfile: "Dockerfile"
164
+ * }
165
+ * });
166
+ */
167
+ export type ContainerProps = ContainerPropsWithImage | ContainerPropsWithBuild;
168
+
95
169
  /**
96
170
  * Instance types for Cloudflare Container deployments.
97
171
  *
@@ -202,10 +276,60 @@ export type Container<T = any> = {
202
276
  __phantom?: T;
203
277
  };
204
278
 
279
+ /**
280
+ * Normalize an image reference for Cloudflare Container deployments.
281
+ *
282
+ * Follows wrangler's resolveImageName logic:
283
+ * - Short names like "myapp:v1" → "registry.cloudflare.com/{accountId}/myapp:v1"
284
+ * - CF registry without accountId like "registry.cloudflare.com/myapp:v1" → adds accountId
285
+ * - External registries like "docker.io/nginx:1.25" → pass through unchanged
286
+ */
287
+ export function resolveImageName(accountId: string, image: string): string {
288
+ const cfRegistry = getCloudflareContainerRegistry();
289
+
290
+ // Check if image has a registry prefix (contains a dot in the first segment)
291
+ const segments = image.split("/");
292
+ const hasRegistryPrefix = segments.length > 1 && segments[0].includes(".");
293
+
294
+ if (!hasRegistryPrefix) {
295
+ // Short name like "myapp:v1" → add CF registry + accountId
296
+ return `${cfRegistry}/${accountId}/${image}`;
297
+ }
298
+
299
+ if (image.startsWith(`${cfRegistry}/`)) {
300
+ // CF registry image - check if accountId is present
301
+ const afterRegistry = image.slice(`${cfRegistry}/`.length);
302
+ const segments = afterRegistry.split("/");
303
+
304
+ // If only one segment (e.g., "myapp:tag"), add accountId
305
+ // If first segment doesn't look like an accountId (32 hex chars), add it
306
+ if (segments.length === 1) {
307
+ return `${cfRegistry}/${accountId}/${afterRegistry}`;
308
+ }
309
+
310
+ // Check if first segment is the accountId (32 hex chars)
311
+ const possibleAccountId = segments[0];
312
+ const isAccountId = /^[a-f0-9]{32}$/.test(possibleAccountId);
313
+
314
+ if (!isAccountId) {
315
+ // First segment is not an accountId, prepend it
316
+ return `${cfRegistry}/${accountId}/${afterRegistry}`;
317
+ }
318
+ }
319
+
320
+ // External registry or already fully-qualified CF registry → pass through
321
+ return image;
322
+ }
323
+
205
324
  export async function Container<T>(
206
325
  id: string,
207
326
  props: ContainerProps,
208
327
  ): Promise<Container<T>> {
328
+ // Validate that build and image are mutually exclusive
329
+ if (props.build && props.image) {
330
+ throw new Error("Container: specify either `build` or `image`, not both.");
331
+ }
332
+
209
333
  const scope = Scope.current;
210
334
  const name = props.name ?? scope.createPhysicalName(id);
211
335
  const tag =
@@ -230,6 +354,100 @@ export async function Container<T>(
230
354
  };
231
355
 
232
356
  const isDev = scope.local && !props.dev?.remote;
357
+
358
+ // Prebuilt image path: use as-is, no Docker pull/push
359
+ // This matches wrangler's behavior where registry URIs are passed directly
360
+ // to the Cloudflare API without any local Docker operations
361
+ if (props.image && !props.build) {
362
+ const rawImageRef =
363
+ typeof props.image === "string" ? props.image : props.image.imageRef;
364
+
365
+ if (isDev) {
366
+ // For local dev with prebuilt images, we need to pull and re-tag
367
+ const dockerApi = new DockerApi();
368
+ const devImageRef = `cloudflare-dev/${name}:${tag}`;
369
+
370
+ if (isCloudflareRegistryLink(rawImageRef)) {
371
+ // For CF registry images, authenticate before pulling
372
+ const api = await createCloudflareApi(props);
373
+ const credentials = await getContainerCredentials(api);
374
+ const cfRegistry = getCloudflareContainerRegistry();
375
+
376
+ await dockerApi.login(
377
+ cfRegistry,
378
+ credentials.username || credentials.user!,
379
+ credentials.password,
380
+ );
381
+
382
+ try {
383
+ // CF Containers run on linux/amd64, so we need to pull that platform
384
+ await dockerApi.pullImage(rawImageRef, { platform: "linux/amd64" });
385
+ await dockerApi.tagImage(rawImageRef, devImageRef);
386
+ } finally {
387
+ await dockerApi.logout(cfRegistry);
388
+ }
389
+
390
+ return {
391
+ ...output,
392
+ image: {
393
+ kind: "Image",
394
+ name: `cloudflare-dev/${name}`,
395
+ imageRef: devImageRef,
396
+ tag,
397
+ builtAt: Date.now(),
398
+ build: undefined,
399
+ },
400
+ };
401
+ }
402
+
403
+ // For external registry images in dev mode, pull and re-tag for Miniflare
404
+ const image = await Image(id, {
405
+ image: props.image,
406
+ tag,
407
+ });
408
+
409
+ await dockerApi.tagImage(image.imageRef, devImageRef);
410
+
411
+ return {
412
+ ...output,
413
+ image: {
414
+ ...image,
415
+ name: `cloudflare-dev/${name}`,
416
+ imageRef: devImageRef,
417
+ },
418
+ };
419
+ }
420
+
421
+ // Non-dev mode: normalize the image reference and use directly
422
+ const api = await createCloudflareApi(props);
423
+ const imageRef = resolveImageName(api.accountId, rawImageRef);
424
+
425
+ // Extract name and tag from the resolved reference
426
+ const [refWithoutDigest] = imageRef.split("@");
427
+ const lastColonIndex = refWithoutDigest.lastIndexOf(":");
428
+ const namePart =
429
+ lastColonIndex > -1
430
+ ? refWithoutDigest.slice(0, lastColonIndex)
431
+ : refWithoutDigest;
432
+ const tagPart =
433
+ lastColonIndex > -1 ? refWithoutDigest.slice(lastColonIndex + 1) : tag;
434
+
435
+ const image: Image = {
436
+ kind: "Image",
437
+ name: namePart,
438
+ imageRef,
439
+ tag: tagPart,
440
+ builtAt: Date.now(),
441
+ build: undefined,
442
+ };
443
+
444
+ return {
445
+ ...output,
446
+ image,
447
+ };
448
+ }
449
+
450
+ // Build path: build locally and push to Cloudflare registry
233
451
  if (isDev) {
234
452
  const image = await Image(id, {
235
453
  ...props,
@@ -264,7 +482,6 @@ export async function Container<T>(
264
482
  platform: "linux/amd64",
265
483
  context: process.cwd(),
266
484
  },
267
- image: props.image,
268
485
  registry: {
269
486
  server: "registry.cloudflare.com",
270
487
  username: credentials.username || credentials.user!,
@@ -4,6 +4,7 @@ export * from "./account-api-token.ts";
4
4
  export * from "./account-id.ts";
5
5
  export * from "./ai-crawler.ts";
6
6
  export * from "./ai-gateway.ts";
7
+ export * from "./ai-search-namespace.ts";
7
8
  export * from "./ai-search-token.ts";
8
9
  export * from "./ai-search.ts";
9
10
  export * from "./ai.ts";
@@ -4,6 +4,8 @@ import { assertNever } from "../../util/assert-never.ts";
4
4
  import { reservePort } from "../../util/find-open-port.ts";
5
5
  import type { HTTPServer } from "../../util/http.ts";
6
6
  import { logger } from "../../util/logger.ts";
7
+ import { isAiSearchNamespace } from "../ai-search-namespace.ts";
8
+ import { isAiSearch } from "../ai-search.ts";
7
9
  import type { CloudflareApi } from "../api.ts";
8
10
  import type {
9
11
  Binding,
@@ -38,6 +40,8 @@ type RemoteBinding =
38
40
  {
39
41
  type:
40
42
  | "ai"
43
+ | "ai_search"
44
+ | "ai_search_namespace"
41
45
  | "browser"
42
46
  | "dispatch_namespace"
43
47
  | "mtls_certificate"
@@ -90,6 +94,29 @@ export const buildWorkerOptions = async (
90
94
  (options.bindings ??= {})[key] = binding;
91
95
  continue;
92
96
  }
97
+ if (isAiSearch(binding)) {
98
+ // AI Search instance bindings are not supported natively by Miniflare;
99
+ // proxy them to the deployed instance (same mechanism used by `ai`,
100
+ // `vectorize`, etc.). Instance bindings are always scoped to the
101
+ // default namespace on the CF side, so the namespace need not be
102
+ // surfaced in the remote-proxy metadata.
103
+ remoteBindings.push({
104
+ type: "ai_search",
105
+ name: key,
106
+ instance_name: binding.name,
107
+ raw: true,
108
+ });
109
+ continue;
110
+ }
111
+ if (isAiSearchNamespace(binding)) {
112
+ remoteBindings.push({
113
+ type: "ai_search_namespace",
114
+ name: key,
115
+ namespace: binding.namespace,
116
+ raw: true,
117
+ });
118
+ continue;
119
+ }
93
120
  if (binding.type === "cloudflare::Worker::Self") {
94
121
  (options.serviceBindings ??= {})[key] = miniflare.kCurrentWorker;
95
122
  continue;
@@ -527,6 +554,18 @@ export const buildWorkerOptions = async (
527
554
  remoteProxyConnectionString: remoteProxy.connectionString,
528
555
  };
529
556
  break;
557
+ case "ai_search":
558
+ (options.aiSearchInstances ??= {})[binding.name] = {
559
+ instance_name: binding.instance_name,
560
+ remoteProxyConnectionString: remoteProxy.connectionString,
561
+ };
562
+ break;
563
+ case "ai_search_namespace":
564
+ (options.aiSearchNamespaces ??= {})[binding.name] = {
565
+ namespace: binding.namespace,
566
+ remoteProxyConnectionString: remoteProxy.connectionString,
567
+ };
568
+ break;
530
569
  default: {
531
570
  assertNever(binding);
532
571
  }
@@ -2,6 +2,8 @@ import { assertNever } from "../util/assert-never.ts";
2
2
  import { camelToSnakeObjectDeep } from "../util/camel-to-snake.ts";
3
3
  import { logger } from "../util/logger.ts";
4
4
  import { memoize } from "../util/memoize.ts";
5
+ import { isAiSearchNamespace } from "./ai-search-namespace.ts";
6
+ import { isAiSearch } from "./ai-search.ts";
5
7
  import { extractCloudflareResult } from "./api-response.ts";
6
8
  import type { CloudflareApi } from "./api.ts";
7
9
  import type {
@@ -418,7 +420,29 @@ export async function prepareWorkerMetadata(
418
420
  for (const [bindingName, binding] of Object.entries(bindings)) {
419
421
  // Create a copy of the binding to avoid modifying the original
420
422
 
421
- if (typeof binding === "string") {
423
+ if (isAiSearch(binding)) {
424
+ // Single-instance bindings (ai_search) are always scoped to the `default` namespace.
425
+ if (binding.namespace !== undefined && binding.namespace !== "default") {
426
+ throw new Error(
427
+ `Worker binding "${bindingName}" uses a single-instance AiSearch binding (type: "ai_search"), ` +
428
+ `but the bound AiSearch instance "${binding.name}" is in namespace "${binding.namespace}". ` +
429
+ `Single-instance bindings only support the "default" namespace.\n` +
430
+ `Fix: either (1) create the AiSearch without a custom namespace, or ` +
431
+ `(2) bind the enclosing AiSearchNamespace and use \`env.${bindingName}.get("${binding.name}")\`.`,
432
+ );
433
+ }
434
+ meta.bindings.push({
435
+ type: "ai_search",
436
+ name: bindingName,
437
+ instance_name: binding.name,
438
+ });
439
+ } else if (isAiSearchNamespace(binding)) {
440
+ meta.bindings.push({
441
+ type: "ai_search_namespace",
442
+ name: bindingName,
443
+ namespace: binding.namespace,
444
+ });
445
+ } else if (typeof binding === "string") {
422
446
  meta.bindings.push({
423
447
  type: "plain_text",
424
448
  name: bindingName,