@genkit-ai/fetch 0.1.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 ADDED
@@ -0,0 +1,182 @@
1
+ # Genkit Web Fetch Plugin
2
+
3
+ This plugin provides utilities for exposing Genkit actions (flows, models, etc.) over the **Web Fetch API** (`Request` / `Response`). Use it with any runtime or framework that supports the standard Fetch API (such as Hono, Bun, Cloudflare Workers, Deno, Node (18+), Vercel Edge, Netlify Edge, Elysia, SvelteKit, etc). Express-like API: pass the action first, then call the returned handler with the request.
4
+
5
+ No framework-specific dependencies; only `genkit` and the standard Web APIs.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm i @genkit-ai/fetch
11
+ ```
12
+
13
+ ## Usage (Hono)
14
+
15
+ ### Single action with `fetchHandler`
16
+
17
+ ```ts
18
+ import { fetchHandler } from '@genkit-ai/fetch';
19
+ import { Hono } from 'hono';
20
+
21
+ const simpleFlow = ai.defineFlow('simpleFlow', async (input, { sendChunk }) => {
22
+ const { text } = await ai.generate({
23
+ model: googleAI.model('gemini-2.0-flash'),
24
+ prompt: input,
25
+ onChunk: (c) => sendChunk(c.text),
26
+ });
27
+ return text;
28
+ });
29
+
30
+ const app = new Hono();
31
+ app.all('/simpleFlow', (c) => fetchHandler(simpleFlow)(c.req.raw));
32
+ ```
33
+
34
+ For a model, resolve it from the plugin then pass to `fetchHandler`:
35
+
36
+ ```ts
37
+ const gai = googleAI();
38
+ const model = await gai.model('gemini-2.0-flash');
39
+ app.post('/models/gemini-flash', (c) => fetchHandler(model)(c.req.raw));
40
+ ```
41
+
42
+ ### Multiple actions with `fetchHandlers`
43
+
44
+ Mount several actions (flows, models, etc.) under one path; the action is selected by the request path (e.g. `/api/hello` runs the action named `hello`):
45
+
46
+ ```ts
47
+ import { fetchHandlers } from '@genkit-ai/fetch';
48
+
49
+ const actions = [helloFlow, greetingFlow, streamingFlow];
50
+
51
+ app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));
52
+ ```
53
+
54
+ Clients call `POST /api/<actionName>` with body `{ "data": <input> }`.
55
+
56
+ ### Auth with context providers
57
+
58
+ Use a context provider (e.g. for auth) and attach it to an action with `withActionOptions`:
59
+
60
+ ```ts
61
+ import { UserFacingError } from 'genkit';
62
+ import type { ContextProvider, RequestData } from 'genkit/context';
63
+ import { fetchHandler, fetchHandlers, withActionOptions } from '@genkit-ai/fetch';
64
+
65
+ const authContext: ContextProvider<{ userId: string }> = (req: RequestData) => {
66
+ if (req.headers['authorization'] !== 'Bearer open-sesame') {
67
+ throw new UserFacingError('PERMISSION_DENIED', 'not authorized');
68
+ }
69
+ return { userId: 'authenticated-user' };
70
+ };
71
+
72
+ // Single action with auth
73
+ app.all('/secureFlow', (c) =>
74
+ fetchHandler(secureFlow, { contextProvider: authContext })(c.req.raw)
75
+ );
76
+
77
+ // Or wrap the action for use with fetchHandlers
78
+ const actions = [
79
+ publicFlow,
80
+ withActionOptions(secureFlow, { contextProvider: authContext }),
81
+ ];
82
+ app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));
83
+ ```
84
+
85
+ ### Durable streaming (Beta)
86
+
87
+ You can configure actions to use a `StreamManager` so stream state is persisted. Clients can disconnect and reconnect without losing the stream.
88
+
89
+ Provide a `streamManager` in the options. For development, use `InMemoryStreamManager`:
90
+
91
+ ```ts
92
+ import { InMemoryStreamManager } from 'genkit/beta';
93
+ import { fetchHandler, fetchHandlers, withActionOptions } from '@genkit-ai/fetch';
94
+
95
+ app.all('/myDurableFlow', (c) =>
96
+ fetchHandler(myFlow, {
97
+ streamManager: new InMemoryStreamManager(),
98
+ })(c.req.raw)
99
+ );
100
+
101
+ // Or with fetchHandlers
102
+ const actions = [
103
+ withActionOptions(myFlow, {
104
+ streamManager: new InMemoryStreamManager(),
105
+ }),
106
+ ];
107
+ app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));
108
+ ```
109
+
110
+ For production, use a durable implementation such as `FirestoreStreamManager` or `RtdbStreamManager` from `@genkit-ai/firebase`, or a custom `StreamManager`.
111
+
112
+ Clients can reconnect using the `streamId`:
113
+
114
+ ```ts
115
+ import { streamFlow } from 'genkit/beta/client';
116
+
117
+ // Start a new stream
118
+ const result = streamFlow({
119
+ url: 'http://localhost:3780/api/myDurableFlow',
120
+ input: 'tell me a long story',
121
+ });
122
+ const streamId = await result.streamId; // save for reconnect
123
+
124
+ // Reconnect later
125
+ const reconnected = streamFlow({
126
+ url: 'http://localhost:3780/api/myDurableFlow',
127
+ streamId,
128
+ });
129
+ ```
130
+
131
+ ### Calling actions from the client
132
+
133
+ Use `runFlow` and `streamFlow` from `genkit/beta/client` (same protocol as the Express plugin):
134
+
135
+ ```ts
136
+ import { runFlow, streamFlow } from 'genkit/beta/client';
137
+
138
+ const result = await runFlow({
139
+ url: 'http://localhost:3780/api/hello',
140
+ input: 'world',
141
+ });
142
+ console.log(result);
143
+
144
+ // With auth headers
145
+ const result = await runFlow({
146
+ url: 'http://localhost:3780/api/secureGreeting',
147
+ headers: { Authorization: 'Bearer open-sesame' },
148
+ input: { name: 'Alex' },
149
+ });
150
+
151
+ // Streaming
152
+ const result = streamFlow({
153
+ url: 'http://localhost:3780/api/streaming',
154
+ input: { prompt: 'Say hello in chunks' },
155
+ });
156
+ for await (const chunk of result.stream) {
157
+ console.log(chunk);
158
+ }
159
+ console.log(await result.output);
160
+ ```
161
+
162
+ ## API summary
163
+
164
+ | Export | Description |
165
+ |---------------------|-----------------------------------------------------------------------------|
166
+ | `fetchHandler(action, options?)` | Returns a handler `(request) => Promise<Response>` for a single action (flow, model, etc.). |
167
+ | `fetchHandlers(actions, pathPrefix?)` | Returns a handler that dispatches by path to one of the given actions. |
168
+ | `withActionOptions(action, options)` | Wraps an action with `contextProvider`, `streamManager`, or custom `path`. |
169
+ | `ActionWithOptions` | Type for an action plus options. |
170
+ | `FetchHandlerOptions` | Options for `fetchHandler`: `contextProvider`, `streamManager`. |
171
+
172
+ Request body must be JSON with a `data` field: `{ "data": <input> }`. For streaming, use `Accept: text/event-stream` or query `?stream=true`.
173
+
174
+ ## Contributing
175
+
176
+ The sources for this package are in the main [Genkit](https://github.com/firebase/genkit) repo. Please file issues and pull requests there.
177
+
178
+ More details are in the [Genkit documentation](https://genkit.dev/docs/get-started/).
179
+
180
+ ## License
181
+
182
+ Licensed under the Apache 2.0 License.
@@ -0,0 +1,65 @@
1
+ import { z, Action, StreamManager, ActionContext } from 'genkit/beta';
2
+ import { ContextProvider } from 'genkit/context';
3
+
4
+ /**
5
+ * Copyright 2025 Google LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+ /**
21
+ * A wrapper object containing an action with its associated options.
22
+ */
23
+ type ActionWithOptions<I extends z.ZodTypeAny = z.ZodTypeAny, O extends z.ZodTypeAny = z.ZodTypeAny, S extends z.ZodTypeAny = z.ZodTypeAny> = {
24
+ action: Action<I, O, S>;
25
+ options: {
26
+ contextProvider?: ContextProvider<any, I>;
27
+ streamManager?: StreamManager;
28
+ path?: string;
29
+ };
30
+ };
31
+ /**
32
+ * Options for a fetch handler (context provider, stream manager).
33
+ */
34
+ interface FetchHandlerOptions<C extends ActionContext = ActionContext, I extends z.ZodTypeAny = z.ZodTypeAny> {
35
+ contextProvider?: ContextProvider<C, I>;
36
+ streamManager?: StreamManager;
37
+ }
38
+ /**
39
+ * Wraps an action (flow, model, etc.) with options for use with {@link fetchHandlers}.
40
+ */
41
+ declare function withActionOptions<I extends z.ZodTypeAny, O extends z.ZodTypeAny, S extends z.ZodTypeAny>(action: Action<I, O, S>, options: {
42
+ contextProvider?: ContextProvider<any, I>;
43
+ streamManager?: StreamManager;
44
+ path?: string;
45
+ }): ActionWithOptions<I, O, S>;
46
+ declare function fetchHandler<C extends ActionContext = ActionContext, I extends z.ZodTypeAny = z.ZodTypeAny, O extends z.ZodTypeAny = z.ZodTypeAny, S extends z.ZodTypeAny = z.ZodTypeAny>(action: Action<I, O, S>, options?: FetchHandlerOptions<C, I>): (request: Request) => Promise<Response>;
47
+ /**
48
+ * Returns a Fetch handler for multiple actions with path-based routing.
49
+ * Express-like API: pass actions and optional path prefix, then call the returned handler with the request.
50
+ *
51
+ * @param actions - Array of actions (or ActionWithOptions) to expose; each is routed by path (action name or options.path)
52
+ * @param pathPrefix - Optional path prefix to strip from the URL (e.g., '/api')
53
+ * @returns A handler function that takes a Request and returns a Promise<Response>
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { fetchHandlers } from '@genkit-ai/fetch';
58
+ *
59
+ * // Mount all actions under /api - routes by path (e.g. POST /api/hello, POST /api/models/gpt5)
60
+ * app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));
61
+ * ```
62
+ */
63
+ declare function fetchHandlers(actions: (Action<any, any, any> | ActionWithOptions<any, any, any>)[], pathPrefix?: string): (request: Request) => Promise<Response>;
64
+
65
+ export { type ActionWithOptions, type FetchHandlerOptions, fetchHandler, fetchHandlers, withActionOptions };
package/lib/index.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { z, Action, StreamManager, ActionContext } from 'genkit/beta';
2
+ import { ContextProvider } from 'genkit/context';
3
+
4
+ /**
5
+ * Copyright 2025 Google LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+ /**
21
+ * A wrapper object containing an action with its associated options.
22
+ */
23
+ type ActionWithOptions<I extends z.ZodTypeAny = z.ZodTypeAny, O extends z.ZodTypeAny = z.ZodTypeAny, S extends z.ZodTypeAny = z.ZodTypeAny> = {
24
+ action: Action<I, O, S>;
25
+ options: {
26
+ contextProvider?: ContextProvider<any, I>;
27
+ streamManager?: StreamManager;
28
+ path?: string;
29
+ };
30
+ };
31
+ /**
32
+ * Options for a fetch handler (context provider, stream manager).
33
+ */
34
+ interface FetchHandlerOptions<C extends ActionContext = ActionContext, I extends z.ZodTypeAny = z.ZodTypeAny> {
35
+ contextProvider?: ContextProvider<C, I>;
36
+ streamManager?: StreamManager;
37
+ }
38
+ /**
39
+ * Wraps an action (flow, model, etc.) with options for use with {@link fetchHandlers}.
40
+ */
41
+ declare function withActionOptions<I extends z.ZodTypeAny, O extends z.ZodTypeAny, S extends z.ZodTypeAny>(action: Action<I, O, S>, options: {
42
+ contextProvider?: ContextProvider<any, I>;
43
+ streamManager?: StreamManager;
44
+ path?: string;
45
+ }): ActionWithOptions<I, O, S>;
46
+ declare function fetchHandler<C extends ActionContext = ActionContext, I extends z.ZodTypeAny = z.ZodTypeAny, O extends z.ZodTypeAny = z.ZodTypeAny, S extends z.ZodTypeAny = z.ZodTypeAny>(action: Action<I, O, S>, options?: FetchHandlerOptions<C, I>): (request: Request) => Promise<Response>;
47
+ /**
48
+ * Returns a Fetch handler for multiple actions with path-based routing.
49
+ * Express-like API: pass actions and optional path prefix, then call the returned handler with the request.
50
+ *
51
+ * @param actions - Array of actions (or ActionWithOptions) to expose; each is routed by path (action name or options.path)
52
+ * @param pathPrefix - Optional path prefix to strip from the URL (e.g., '/api')
53
+ * @returns A handler function that takes a Request and returns a Promise<Response>
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { fetchHandlers } from '@genkit-ai/fetch';
58
+ *
59
+ * // Mount all actions under /api - routes by path (e.g. POST /api/hello, POST /api/models/gpt5)
60
+ * app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));
61
+ * ```
62
+ */
63
+ declare function fetchHandlers(actions: (Action<any, any, any> | ActionWithOptions<any, any, any>)[], pathPrefix?: string): (request: Request) => Promise<Response>;
64
+
65
+ export { type ActionWithOptions, type FetchHandlerOptions, fetchHandler, fetchHandlers, withActionOptions };
package/lib/index.js ADDED
@@ -0,0 +1,353 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var index_exports = {};
20
+ __export(index_exports, {
21
+ fetchHandler: () => fetchHandler,
22
+ fetchHandlers: () => fetchHandlers,
23
+ withActionOptions: () => withActionOptions
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_beta = require("genkit/beta");
27
+ var import_context = require("genkit/context");
28
+ var import_logging = require("genkit/logging");
29
+ var import_utils = require("./utils.js");
30
+ const streamDelimiter = "\n\n";
31
+ const randomUUID = () => globalThis.crypto.randomUUID();
32
+ function withActionOptions(action, options) {
33
+ return {
34
+ action,
35
+ options
36
+ };
37
+ }
38
+ function headersToObject(headers) {
39
+ const result = {};
40
+ headers.forEach((value, key) => {
41
+ result[key.toLowerCase()] = value;
42
+ });
43
+ return result;
44
+ }
45
+ async function getContext(request, input, provider) {
46
+ const context = {};
47
+ if (!provider) {
48
+ return context;
49
+ }
50
+ const requestData = {
51
+ method: request.method,
52
+ headers: headersToObject(request.headers),
53
+ input
54
+ };
55
+ return await provider(requestData);
56
+ }
57
+ async function subscribeToStream(streamManager, streamId) {
58
+ try {
59
+ const encoder = new TextEncoder();
60
+ const { readable, writable } = new TransformStream();
61
+ const writer = writable.getWriter();
62
+ await streamManager.subscribe(streamId, {
63
+ onChunk: (chunk) => {
64
+ writer.write(
65
+ encoder.encode(
66
+ "data: " + JSON.stringify({ message: chunk }) + streamDelimiter
67
+ )
68
+ );
69
+ },
70
+ onDone: (output) => {
71
+ writer.write(
72
+ encoder.encode(
73
+ "data: " + JSON.stringify({ result: output }) + streamDelimiter
74
+ )
75
+ );
76
+ writer.close();
77
+ },
78
+ onError: (err) => {
79
+ import_logging.logger.error(
80
+ `Streaming request failed with error: ${(0, import_utils.getErrorMessage)(err)}
81
+ ${(0, import_utils.getErrorStack)(err)}`
82
+ );
83
+ writer.write(
84
+ encoder.encode(
85
+ `error: ${JSON.stringify({
86
+ error: (0, import_context.getCallableJSON)(err)
87
+ })}${streamDelimiter}`
88
+ )
89
+ );
90
+ writer.close();
91
+ }
92
+ });
93
+ return new Response(readable, {
94
+ status: 200,
95
+ headers: {
96
+ "Content-Type": "text/event-stream",
97
+ "Cache-Control": "no-cache",
98
+ Connection: "keep-alive",
99
+ "Transfer-Encoding": "chunked",
100
+ "x-genkit-stream-id": streamId
101
+ }
102
+ });
103
+ } catch (e) {
104
+ if (e instanceof import_beta.StreamNotFoundError) {
105
+ return new Response(null, { status: 204 });
106
+ }
107
+ if (e.status === "DEADLINE_EXCEEDED") {
108
+ const encoder = new TextEncoder();
109
+ const { readable, writable } = new TransformStream();
110
+ const writer = writable.getWriter();
111
+ writer.write(
112
+ encoder.encode(
113
+ `error: ${JSON.stringify({
114
+ error: (0, import_context.getCallableJSON)(e)
115
+ })}${streamDelimiter}`
116
+ )
117
+ );
118
+ writer.close();
119
+ return new Response(readable, {
120
+ status: 200,
121
+ headers: {
122
+ "Content-Type": "text/event-stream",
123
+ "Cache-Control": "no-cache",
124
+ Connection: "keep-alive",
125
+ "Transfer-Encoding": "chunked"
126
+ }
127
+ });
128
+ }
129
+ throw e;
130
+ }
131
+ }
132
+ async function runActionWithDurableStreaming(action, streamManager, streamId, input, context, writer, abortSignal) {
133
+ const encoder = new TextEncoder();
134
+ let taskQueue;
135
+ let durableStream;
136
+ if (streamManager) {
137
+ taskQueue = new import_beta.AsyncTaskQueue();
138
+ durableStream = await streamManager.open(streamId);
139
+ }
140
+ try {
141
+ let onChunk = (chunk) => {
142
+ writer.write(
143
+ encoder.encode(
144
+ "data: " + JSON.stringify({ message: chunk }) + streamDelimiter
145
+ )
146
+ );
147
+ };
148
+ if (streamManager && durableStream) {
149
+ const originalOnChunk = onChunk;
150
+ onChunk = (chunk) => {
151
+ originalOnChunk(chunk);
152
+ taskQueue.enqueue(() => durableStream.write(chunk));
153
+ };
154
+ }
155
+ const result = await action.run(input, {
156
+ onChunk,
157
+ context,
158
+ abortSignal
159
+ });
160
+ if (streamManager && durableStream) {
161
+ taskQueue.enqueue(() => durableStream.done(result.result));
162
+ await taskQueue.merge();
163
+ }
164
+ writer.write(
165
+ encoder.encode(
166
+ "data: " + JSON.stringify({ result: result.result }) + streamDelimiter
167
+ )
168
+ );
169
+ writer.close();
170
+ } catch (e) {
171
+ if (durableStream) {
172
+ taskQueue.enqueue(() => durableStream.error(e));
173
+ await taskQueue.merge();
174
+ }
175
+ import_logging.logger.error(
176
+ `Streaming request failed with error: ${e.message}
177
+ ${e.stack}`
178
+ );
179
+ writer.write(
180
+ encoder.encode(
181
+ `error: ${JSON.stringify({
182
+ error: (0, import_context.getCallableJSON)(e)
183
+ })}${streamDelimiter}`
184
+ )
185
+ );
186
+ writer.close();
187
+ }
188
+ }
189
+ async function handleActionRequest(request, action, options) {
190
+ const url = new URL(request.url);
191
+ const streamParam = url.searchParams.get("stream");
192
+ const shouldStream = streamParam === "true";
193
+ const streamIdHeader = request.headers.get("x-genkit-stream-id");
194
+ const streamId = streamIdHeader || void 0;
195
+ let body;
196
+ try {
197
+ body = await request.json();
198
+ } catch (e) {
199
+ const errMsg = `Error: Failed to parse request body as JSON. Make sure the request has 'Content-Type: application/json' header.`;
200
+ import_logging.logger.error(errMsg);
201
+ return new Response(
202
+ JSON.stringify({ message: errMsg, status: "INVALID_ARGUMENT" }),
203
+ { status: 400, headers: { "Content-Type": "application/json" } }
204
+ );
205
+ }
206
+ if (!body || typeof body !== "object" || !("data" in body)) {
207
+ const errMsg = `Error: Request body must be a JSON object with a 'data' field. Expected format: {"data": ...}`;
208
+ import_logging.logger.error(errMsg);
209
+ return new Response(
210
+ JSON.stringify({ message: errMsg, status: "INVALID_ARGUMENT" }),
211
+ { status: 400, headers: { "Content-Type": "application/json" } }
212
+ );
213
+ }
214
+ const input = body.data;
215
+ let context;
216
+ try {
217
+ context = await getContext(request, input, options?.contextProvider);
218
+ } catch (e) {
219
+ import_logging.logger.error(
220
+ `Context provider failed with error: ${e.message}
221
+ ${e.stack}`
222
+ );
223
+ return new Response(JSON.stringify((0, import_context.getCallableJSON)(e)), {
224
+ status: (0, import_context.getHttpStatus)(e),
225
+ headers: { "Content-Type": "application/json" }
226
+ });
227
+ }
228
+ const acceptHeader = request.headers.get("Accept") || "";
229
+ const isStreaming = acceptHeader === "text/event-stream" || shouldStream;
230
+ if (isStreaming) {
231
+ const streamManager = options?.streamManager;
232
+ if (streamManager && streamId) {
233
+ const response = await subscribeToStream(streamManager, streamId);
234
+ if (response) {
235
+ return response;
236
+ }
237
+ }
238
+ const streamIdToUse = randomUUID();
239
+ const { readable, writable } = new TransformStream();
240
+ const writer = writable.getWriter();
241
+ runActionWithDurableStreaming(
242
+ action,
243
+ options?.streamManager,
244
+ streamIdToUse,
245
+ input,
246
+ context,
247
+ writer,
248
+ request.signal
249
+ ).catch((err) => {
250
+ import_logging.logger.error(`Error in streaming handler: ${err}`);
251
+ });
252
+ const headers = {
253
+ "Content-Type": "text/event-stream",
254
+ "Cache-Control": "no-cache",
255
+ Connection: "keep-alive",
256
+ "Transfer-Encoding": "chunked"
257
+ };
258
+ if (options?.streamManager) {
259
+ headers["x-genkit-stream-id"] = streamIdToUse;
260
+ }
261
+ return new Response(readable, {
262
+ status: 200,
263
+ headers
264
+ });
265
+ }
266
+ try {
267
+ const result = await action.run(input, {
268
+ context,
269
+ abortSignal: request.signal
270
+ });
271
+ const headers = {
272
+ "x-genkit-trace-id": result.telemetry.traceId,
273
+ "x-genkit-span-id": result.telemetry.spanId
274
+ };
275
+ return new Response(JSON.stringify({ result: result.result }), {
276
+ status: 200,
277
+ headers: { "Content-Type": "application/json", ...headers }
278
+ });
279
+ } catch (e) {
280
+ import_logging.logger.error(
281
+ `Non-streaming request failed with error: ${e.message}
282
+ ${e.stack}`
283
+ );
284
+ return new Response(JSON.stringify((0, import_context.getCallableJSON)(e)), {
285
+ status: (0, import_context.getHttpStatus)(e),
286
+ headers: { "Content-Type": "application/json" }
287
+ });
288
+ }
289
+ }
290
+ function fetchHandler(action, options) {
291
+ return (request) => handleActionRequest(request, action, options);
292
+ }
293
+ function fetchHandlers(actions, pathPrefix) {
294
+ return async (request) => {
295
+ const url = new URL(request.url);
296
+ let pathname = url.pathname;
297
+ if (pathPrefix) {
298
+ const prefix = pathPrefix.endsWith("/") ? pathPrefix : pathPrefix + "/";
299
+ if (pathname === pathPrefix) {
300
+ pathname = "";
301
+ } else if (pathname.startsWith(prefix)) {
302
+ pathname = pathname.slice(prefix.length);
303
+ } else {
304
+ return new Response(
305
+ JSON.stringify({
306
+ status: "NOT_FOUND",
307
+ message: "No action matched the request path."
308
+ }),
309
+ { status: 404, headers: { "Content-Type": "application/json" } }
310
+ );
311
+ }
312
+ }
313
+ pathname = pathname.replace(/^\//, "");
314
+ let matchedAction = null;
315
+ let handlerOptions = void 0;
316
+ for (const item of actions) {
317
+ if ("action" in item) {
318
+ const options = item.options;
319
+ const actionPath = options.path ?? item.action.__action.name;
320
+ if (pathname === actionPath) {
321
+ matchedAction = item.action;
322
+ handlerOptions = {
323
+ contextProvider: options.contextProvider,
324
+ streamManager: options.streamManager
325
+ };
326
+ break;
327
+ }
328
+ } else {
329
+ if (pathname === item.__action.name) {
330
+ matchedAction = item;
331
+ break;
332
+ }
333
+ }
334
+ }
335
+ if (!matchedAction) {
336
+ return new Response(
337
+ JSON.stringify({
338
+ status: "NOT_FOUND",
339
+ message: "No action matched the request path."
340
+ }),
341
+ { status: 404, headers: { "Content-Type": "application/json" } }
342
+ );
343
+ }
344
+ return handleActionRequest(request, matchedAction, handlerOptions);
345
+ };
346
+ }
347
+ // Annotate the CommonJS export names for ESM import in node:
348
+ 0 && (module.exports = {
349
+ fetchHandler,
350
+ fetchHandlers,
351
+ withActionOptions
352
+ });
353
+ //# sourceMappingURL=index.js.map