1import type * as runtime from "@cloudflare/workers-types";
2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";
4import * as Binding from "../../Binding.ts";
5import type { RuntimeContext } from "../../RuntimeContext.ts";
6import type { SearchInstance } from "./SearchInstance.ts";
7
8/**
9 * Bind a {@link SearchInstance} to a Worker and obtain the Effect-native
10 * AI Search client (`search`, `chatCompletions`, `info`, `stats`). The
11 * single-instance `ai_search` binding resolves directly to a runtime
12 * `SearchInstance`.
13 *
14 * `QuerySearch` is a single identifier that is simultaneously the binding's Context
15 * tag, its type, and the callable — `yield* Cloudflare.AI.QuerySearch(instance)`.
16 *
17 * Provide {@link QuerySearchBinding} in the Worker's runtime layer.
18 *
19 *
20 * ### Querying AI Search
21 * **Example:** Retrieve and generate from a Worker
22 * Bind the instance during the Worker's init phase, then use `search`
23 * (retrieval only) or `chatCompletions` (retrieval + generation) from request
24 * handlers.
25 * ```typescript
26 * const search = yield* Cloudflare.AI.QuerySearch(instance);
27 *
28 * return {
29 * fetch: Effect.gen(function* () {
30 * const answer = yield* search.chatCompletions({
31 * messages: [{ role: "user", content: "How do I deploy?" }],
32 * });
33 * return yield* HttpServerResponse.json(answer);
34 * }),
35 * };
36 * ```
37 *
38 * @binding
39 * @category AI
40 */
41export interface QuerySearch extends Binding.Service<
42 QuerySearch,
43 "Cloudflare.AI.QuerySearch",
44 (instance: SearchInstance) => Effect.Effect<QuerySearchClient>
45> {}
46
47export const QuerySearch = Binding.Service<QuerySearch>(
48 "Cloudflare.AI.QuerySearch",
49);
50
51/**
52 * Error raised by AI Search runtime binding operations.
53 */
54export class SearchError extends Data.TaggedError("AiSearchError")<{
55 /**
56 * Human-readable runtime error message.
57 */
58 message: string;
59 /**
60 * Original error thrown by the Cloudflare.AI. Search runtime binding.
61 */
62 cause: unknown;
63}> {}
64
65/**
66 * Effect-native client for a Cloudflare.AI. Search Worker binding.
67 *
68 * Wraps the standalone `SearchInstance` runtime binding (the binding the
69 * `ai_search` type resolves to — the deprecated `AutoRAG` shape is gone) so
70 * each operation returns an Effect tagged with {@link SearchError}. Obtain
71 * one from `Cloudflare.AI.QuerySearch(instance)` (or a namespace client's
72 * `.get(instanceName)`) during the Worker's init phase.
73 */
74export interface QuerySearchClient {
75 /**
76 * Effect resolving to the raw underlying Cloudflare `SearchInstance`
77 * binding. Use this for operations not surfaced below (`items`, `jobs`,
78 * `update`, or streaming `chatCompletions`).
79 */
80 raw: Effect.Effect<runtime.AiSearchInstance, never, RuntimeContext>;
81 /**
82 * Retrieve the chunks most relevant to a query without generation.
83 */
84 search(
85 params: runtime.AiSearchSearchRequest,
86 ): Effect.Effect<runtime.AiSearchSearchResponse, SearchError, RuntimeContext>;
87 /**
88 * Run retrieval-augmented generation: retrieve relevant chunks and answer
89 * with the configured model (non-streaming).
90 */
91 chatCompletions(
92 params: runtime.AiSearchChatCompletionsRequest,
93 ): Effect.Effect<
94 runtime.AiSearchChatCompletionsResponse,
95 SearchError,
96 RuntimeContext
97 >;
98 /**
99 * Metadata about this instance (id, models, source, status, …).
100 */
101 info(): Effect.Effect<
102 runtime.AiSearchInstanceInfo,
103 SearchError,
104 RuntimeContext
105 >;
106 /**
107 * Indexing statistics (item counts per status, last activity, engine).
108 */
109 stats(): Effect.Effect<
110 runtime.AiSearchStatsResponse,
111 SearchError,
112 RuntimeContext
113 >;
114}
115
116export const tryAiSearch = <A>(
117 fn: () => Promise<A>,
118): Effect.Effect<A, SearchError> =>
119 Effect.tryPromise({
120 try: fn,
121 catch: (cause) =>
122 new SearchError({
123 message:
124 cause instanceof Error
125 ? cause.message
126 : "Unknown AI Search runtime error",
127 cause,
128 }),
129 });
130
131/**
132 * Build a {@link QuerySearchClient} from an Effect that lazily resolves the raw
133 * `SearchInstance` runtime binding.
134 */
135export const makeClient = (
136 rawEff: Effect.Effect<runtime.AiSearchInstance>,
137): QuerySearchClient => {
138 const use = <A>(fn: (raw: runtime.AiSearchInstance) => Promise<A>) =>
139 Effect.flatMap(rawEff, (raw) => tryAiSearch(() => fn(raw)));
140 return {
141 raw: rawEff,
142 search: (params) => use((raw) => raw.search(params)),
143 chatCompletions: (params) => use((raw) => raw.chatCompletions(params)),
144 info: () => use((raw) => raw.info()),
145 stats: () => use((raw) => raw.stats()),
146 } satisfies QuerySearchClient;
147};