1import * as Effect from "effect/Effect";
2import * as Layer from "effect/Layer";
3import type * as HttpClient from "effect/http/HttpClient";
4import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
5import type { Credentials } from "../Credentials.ts";
6import { QuerySearch } from "./QuerySearch.ts";
7import { makeLocalSearchClient, type SearchAuth } from "./SearchHttpClient.ts";
8import type { SearchInstance } from "./SearchInstance.ts";
9
10/**
11 * Local implementation of the {@link QuerySearch} binding — queries an AI
12 * Search instance over the Cloudflare HTTP API using the **current
13 * credentials** instead of a native Worker binding (`QuerySearchBinding`).
14 *
15 * Provide it on an {@link Action} (or any deploy-time Effect) to run
16 * `search` / `chatCompletions` / `info` / `stats` against an instance with the
17 * same client you'd use inside a Worker. The instance id and namespace are
18 * resolved at apply time through the ambient RuntimeContext, so
19 * `QuerySearch(instance)` works even when the instance is created in the same
20 * deploy.
21 *
22 * `raw` (the native `AiSearchInstance` runtime handle) is unavailable outside a
23 * deployed Worker and dies if forced.
24 *
25 * @example Read an instance's status from an Action
26 * ```typescript
27 * const Probe = Alchemy.Action(
28 * "Probe",
29 * Effect.gen(function* () {
30 * const search = yield* Cloudflare.AI.QuerySearch(instance);
31 * return Effect.fn(function* () {
32 * const info = yield* search.info();
33 * const stats = yield* search.stats();
34 * return { status: info.status, indexed: stats.completed };
35 * });
36 * }).pipe(Effect.provide(Cloudflare.AI.QuerySearchLocal)),
37 * );
38 * ```
39 */
40export const QuerySearchLocal = Layer.effect(
41 QuerySearch,
42 Effect.gen(function* () {
43 // Account + credentials are ambient during stack-eval (the stack's
44 // providers layer). Capture the full context so each HTTP op can be run
45 // with the current credentials; no `host.bind`, no minted token.
46 const { accountId } = yield* yield* CloudflareEnvironment;
47 const context = yield* Effect.context<
48 Credentials | HttpClient.HttpClient
49 >();
50
51 const auth: SearchAuth = {
52 authorize: (eff) => eff.pipe(Effect.provideContext(context)),
53 accountId,
54 };
55
56 return Effect.fn(function* (instance: SearchInstance) {
57 // Deferred accessors — resolved against the tracker at apply time.
58 const instanceId = yield* instance.instanceId;
59 const namespace = yield* instance.namespace;
60 return makeLocalSearchClient(
61 auth,
62 Effect.all({ id: instanceId, name: namespace }),
63 );
64 });
65 }),
66);