@genkit-ai/fetch 0.1.3 → 0.2.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 +42 -9
- package/lib/index.d.mts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +7 -3
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +7 -3
- package/lib/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +11 -3
- package/tests/web_test.ts +109 -0
package/README.md
CHANGED
|
@@ -60,7 +60,11 @@ Use a context provider (e.g. for auth) and attach it to an action with `withActi
|
|
|
60
60
|
```ts
|
|
61
61
|
import { UserFacingError } from 'genkit';
|
|
62
62
|
import type { ContextProvider, RequestData } from 'genkit/context';
|
|
63
|
-
import {
|
|
63
|
+
import {
|
|
64
|
+
fetchHandler,
|
|
65
|
+
fetchHandlers,
|
|
66
|
+
withActionOptions,
|
|
67
|
+
} from '@genkit-ai/fetch';
|
|
64
68
|
|
|
65
69
|
const authContext: ContextProvider<{ userId: string }> = (req: RequestData) => {
|
|
66
70
|
if (req.headers['authorization'] !== 'Bearer open-sesame') {
|
|
@@ -90,7 +94,11 @@ Provide a `streamManager` in the options. For development, use `InMemoryStreamMa
|
|
|
90
94
|
|
|
91
95
|
```ts
|
|
92
96
|
import { InMemoryStreamManager } from 'genkit/beta';
|
|
93
|
-
import {
|
|
97
|
+
import {
|
|
98
|
+
fetchHandler,
|
|
99
|
+
fetchHandlers,
|
|
100
|
+
withActionOptions,
|
|
101
|
+
} from '@genkit-ai/fetch';
|
|
94
102
|
|
|
95
103
|
app.all('/myDurableFlow', (c) =>
|
|
96
104
|
fetchHandler(myFlow, {
|
|
@@ -159,15 +167,40 @@ for await (const chunk of result.stream) {
|
|
|
159
167
|
console.log(await result.output);
|
|
160
168
|
```
|
|
161
169
|
|
|
170
|
+
### Initialization Data
|
|
171
|
+
|
|
172
|
+
If your flow or action accepts initialization data (defined via `initSchema`), you can pass it using the `init` option in the client:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const result = await runFlow({
|
|
176
|
+
url: 'http://localhost:3780/api/myFlow',
|
|
177
|
+
input: 'say hello',
|
|
178
|
+
init: { sessionId: 'abc123', config: { temperature: 0.7 } },
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Also works with streaming
|
|
182
|
+
const streamed = streamFlow({
|
|
183
|
+
url: 'http://localhost:3780/api/myFlow',
|
|
184
|
+
input: 'say hello',
|
|
185
|
+
init: { sessionId: 'abc123' },
|
|
186
|
+
});
|
|
187
|
+
for await (const chunk of streamed.stream) {
|
|
188
|
+
console.log(chunk);
|
|
189
|
+
}
|
|
190
|
+
console.log(await streamed.output);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The `init` data is sent in the request body alongside `data` and is validated against the action's `initSchema` on the server side. If the `init` data does not conform to the `initSchema`, the request fails with a `400 INVALID_ARGUMENT` error before the flow runs.
|
|
194
|
+
|
|
162
195
|
## API summary
|
|
163
196
|
|
|
164
|
-
| Export
|
|
165
|
-
|
|
166
|
-
| `fetchHandler(action, options?)`
|
|
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`
|
|
170
|
-
| `FetchHandlerOptions`
|
|
197
|
+
| Export | Description |
|
|
198
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
199
|
+
| `fetchHandler(action, options?)` | Returns a handler `(request) => Promise<Response>` for a single action (flow, model, etc.). |
|
|
200
|
+
| `fetchHandlers(actions, pathPrefix?)` | Returns a handler that dispatches by path to one of the given actions. |
|
|
201
|
+
| `withActionOptions(action, options)` | Wraps an action with `contextProvider`, `streamManager`, or custom `path`. |
|
|
202
|
+
| `ActionWithOptions` | Type for an action plus options. |
|
|
203
|
+
| `FetchHandlerOptions` | Options for `fetchHandler`: `contextProvider`, `streamManager`. |
|
|
171
204
|
|
|
172
205
|
Request body must be JSON with a `data` field: `{ "data": <input> }`. For streaming, use `Accept: text/event-stream` or query `?stream=true`.
|
|
173
206
|
|
package/lib/index.d.mts
CHANGED
|
@@ -43,7 +43,7 @@ declare function withActionOptions<I extends z.ZodTypeAny, O extends z.ZodTypeAn
|
|
|
43
43
|
streamManager?: StreamManager;
|
|
44
44
|
path?: string;
|
|
45
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>;
|
|
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, Init extends z.ZodTypeAny = z.ZodTypeAny>(action: Action<I, O, S, any, Init>, options?: FetchHandlerOptions<C, I>): (request: Request) => Promise<Response>;
|
|
47
47
|
/**
|
|
48
48
|
* Returns a Fetch handler for multiple actions with path-based routing.
|
|
49
49
|
* Express-like API: pass actions and optional path prefix, then call the returned handler with the request.
|
package/lib/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ declare function withActionOptions<I extends z.ZodTypeAny, O extends z.ZodTypeAn
|
|
|
43
43
|
streamManager?: StreamManager;
|
|
44
44
|
path?: string;
|
|
45
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>;
|
|
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, Init extends z.ZodTypeAny = z.ZodTypeAny>(action: Action<I, O, S, any, Init>, options?: FetchHandlerOptions<C, I>): (request: Request) => Promise<Response>;
|
|
47
47
|
/**
|
|
48
48
|
* Returns a Fetch handler for multiple actions with path-based routing.
|
|
49
49
|
* Express-like API: pass actions and optional path prefix, then call the returned handler with the request.
|
package/lib/index.js
CHANGED
|
@@ -129,7 +129,7 @@ ${(0, import_utils.getErrorStack)(err)}`
|
|
|
129
129
|
throw e;
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
-
async function runActionWithDurableStreaming(action, streamManager, streamId, input, context, writer, abortSignal) {
|
|
132
|
+
async function runActionWithDurableStreaming(action, streamManager, streamId, input, init, context, writer, abortSignal) {
|
|
133
133
|
const encoder = new TextEncoder();
|
|
134
134
|
let taskQueue;
|
|
135
135
|
let durableStream;
|
|
@@ -155,7 +155,8 @@ async function runActionWithDurableStreaming(action, streamManager, streamId, in
|
|
|
155
155
|
const result = await action.run(input, {
|
|
156
156
|
onChunk,
|
|
157
157
|
context,
|
|
158
|
-
abortSignal
|
|
158
|
+
abortSignal,
|
|
159
|
+
init
|
|
159
160
|
});
|
|
160
161
|
if (streamManager && durableStream) {
|
|
161
162
|
taskQueue.enqueue(() => durableStream.done(result.result));
|
|
@@ -212,6 +213,7 @@ async function handleActionRequest(request, action, options) {
|
|
|
212
213
|
);
|
|
213
214
|
}
|
|
214
215
|
const input = body.data;
|
|
216
|
+
const init = body.init;
|
|
215
217
|
let context;
|
|
216
218
|
try {
|
|
217
219
|
context = await getContext(request, input, options?.contextProvider);
|
|
@@ -243,6 +245,7 @@ ${e.stack}`
|
|
|
243
245
|
options?.streamManager,
|
|
244
246
|
streamIdToUse,
|
|
245
247
|
input,
|
|
248
|
+
init,
|
|
246
249
|
context,
|
|
247
250
|
writer,
|
|
248
251
|
request.signal
|
|
@@ -266,7 +269,8 @@ ${e.stack}`
|
|
|
266
269
|
try {
|
|
267
270
|
const result = await action.run(input, {
|
|
268
271
|
context,
|
|
269
|
-
abortSignal: request.signal
|
|
272
|
+
abortSignal: request.signal,
|
|
273
|
+
init
|
|
270
274
|
});
|
|
271
275
|
const headers = {
|
|
272
276
|
"x-genkit-trace-id": result.telemetry.traceId,
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Action,\n ActionStreamInput,\n AsyncTaskQueue,\n StreamNotFoundError,\n type ActionContext,\n type StreamManager,\n type z,\n} from 'genkit/beta';\nimport {\n getCallableJSON,\n getHttpStatus,\n type ContextProvider,\n type RequestData,\n} from 'genkit/context';\nimport { logger } from 'genkit/logging';\nimport { getErrorMessage, getErrorStack } from './utils.js';\n\nconst streamDelimiter = '\\n\\n';\n\n// Let the runtime provide the randomUUID function.\nconst randomUUID = () => globalThis.crypto.randomUUID();\n\n/**\n * A wrapper object containing an action with its associated options.\n */\nexport type ActionWithOptions<\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n> = {\n action: Action<I, O, S>;\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n };\n};\n\n/**\n * Options for a fetch handler (context provider, stream manager).\n */\nexport interface FetchHandlerOptions<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n> {\n contextProvider?: ContextProvider<C, I>;\n streamManager?: StreamManager;\n}\n\n/**\n * Wraps an action (flow, model, etc.) with options for use with {@link fetchHandlers}.\n */\nexport function withActionOptions<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n action: Action<I, O, S>,\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n }\n): ActionWithOptions<I, O, S> {\n return {\n action,\n options,\n };\n}\n\n/**\n * Converts Headers object to a plain object with lowercase keys.\n */\nfunction headersToObject(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n headers.forEach((value, key) => {\n result[key.toLowerCase()] = value;\n });\n return result;\n}\n\n/**\n * Gets context from the request using the context provider if available.\n */\nasync function getContext<C extends ActionContext, I extends z.ZodTypeAny>(\n request: Request,\n input: z.infer<I>,\n provider?: ContextProvider<C, I>\n): Promise<C> {\n const context = {} as C;\n if (!provider) {\n return context;\n }\n\n const requestData: RequestData = {\n method: request.method as RequestData['method'],\n headers: headersToObject(request.headers),\n input,\n };\n\n return await provider(requestData);\n}\n\n/**\n * Subscribes to an existing stream using StreamManager.\n */\nasync function subscribeToStream(\n streamManager: StreamManager,\n streamId: string\n): Promise<Response | null> {\n try {\n const encoder = new TextEncoder();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n\n await streamManager.subscribe(streamId, {\n onChunk: (chunk) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n )\n );\n },\n onDone: (output) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ result: output }) + streamDelimiter\n )\n );\n writer.close();\n },\n onError: (err) => {\n logger.error(\n `Streaming request failed with error: ${getErrorMessage(err)}\\n${getErrorStack(err)}`\n );\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(err),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n },\n });\n\n return new Response(readable, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n 'x-genkit-stream-id': streamId,\n },\n });\n } catch (e: any) {\n if (e instanceof StreamNotFoundError) {\n return new Response(null, { status: 204 });\n }\n if (e.status === 'DEADLINE_EXCEEDED') {\n const encoder = new TextEncoder();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n return new Response(readable, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n },\n });\n }\n throw e;\n }\n}\n\n/**\n * Runs an action with durable streaming support.\n */\nasync function runActionWithDurableStreaming<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n action: Action<I, O, S>,\n streamManager: StreamManager | undefined,\n streamId: string,\n input: z.infer<I>,\n context: ActionContext,\n writer: WritableStreamDefaultWriter<Uint8Array>,\n abortSignal: AbortSignal\n): Promise<void> {\n const encoder = new TextEncoder();\n let taskQueue: AsyncTaskQueue | undefined;\n let durableStream: ActionStreamInput<any, any> | undefined;\n\n if (streamManager) {\n taskQueue = new AsyncTaskQueue();\n durableStream = await streamManager.open(streamId);\n }\n\n try {\n let onChunk = (chunk: z.infer<S>) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n )\n );\n };\n\n if (streamManager && durableStream) {\n const originalOnChunk = onChunk;\n onChunk = (chunk: z.infer<S>) => {\n originalOnChunk(chunk);\n taskQueue!.enqueue(() => durableStream!.write(chunk));\n };\n }\n\n const result = await action.run(input, {\n onChunk,\n context,\n abortSignal,\n });\n\n if (streamManager && durableStream) {\n taskQueue!.enqueue(() => durableStream!.done(result.result));\n await taskQueue!.merge();\n }\n\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ result: result.result }) + streamDelimiter\n )\n );\n writer.close();\n } catch (e) {\n if (durableStream) {\n taskQueue!.enqueue(() => durableStream!.error(e));\n await taskQueue!.merge();\n }\n logger.error(\n `Streaming request failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n }\n}\n\n/**\n * Returns a Fetch handler for a single action (flow, model, or any Genkit action).\n * Express-like API: pass the action first, then call the returned handler with the request.\n *\n * @param action - The Genkit action to execute (flow, model, etc.)\n * @param options - Optional configuration including contextProvider and streamManager\n * @returns A handler function that takes a Request and returns a Promise<Response>\n *\n * @example\n * ```typescript\n * import { fetchHandler } from '@genkit-ai/fetch';\n *\n * // Flow or model\n * app.post('/myFlow', (c) => fetchHandler(myFlow)(c.req.raw));\n * app.post('/models/gpt5', (c) => fetchHandler(gpt5)(c.req.raw));\n *\n * // With options\n * app.post('/secure', (c) => fetchHandler(secureFlow, { contextProvider })(c.req.raw));\n * ```\n */\nasync function handleActionRequest<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n request: Request,\n action: Action<I, O, S>,\n options?: FetchHandlerOptions<C, I>\n): Promise<Response> {\n const url = new URL(request.url);\n const streamParam = url.searchParams.get('stream');\n const shouldStream = streamParam === 'true';\n\n const streamIdHeader = request.headers.get('x-genkit-stream-id');\n const streamId = streamIdHeader || undefined;\n\n let body: any;\n try {\n body = await request.json();\n } catch (e) {\n const errMsg =\n `Error: Failed to parse request body as JSON. ` +\n `Make sure the request has 'Content-Type: application/json' header.`;\n logger.error(errMsg);\n return new Response(\n JSON.stringify({ message: errMsg, status: 'INVALID_ARGUMENT' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n if (!body || typeof body !== 'object' || !('data' in body)) {\n const errMsg =\n `Error: Request body must be a JSON object with a 'data' field. ` +\n `Expected format: {\"data\": ...}`;\n logger.error(errMsg);\n return new Response(\n JSON.stringify({ message: errMsg, status: 'INVALID_ARGUMENT' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n const input = body.data as z.infer<I>;\n\n let context: C;\n try {\n context = await getContext(request, input, options?.contextProvider);\n } catch (e: any) {\n logger.error(\n `Context provider failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n return new Response(JSON.stringify(getCallableJSON(e)), {\n status: getHttpStatus(e),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n const acceptHeader = request.headers.get('Accept') || '';\n const isStreaming =\n acceptHeader.toLowerCase().includes('text/event-stream') || shouldStream;\n\n if (isStreaming) {\n const streamManager = options?.streamManager;\n if (streamManager && streamId) {\n const response = await subscribeToStream(streamManager, streamId);\n if (response) {\n return response;\n }\n }\n\n const streamIdToUse = randomUUID();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n\n runActionWithDurableStreaming(\n action,\n options?.streamManager,\n streamIdToUse,\n input,\n context,\n writer,\n request.signal\n ).catch((err) => {\n logger.error(`Error in streaming handler: ${err}`);\n });\n\n const headers: Record<string, string> = {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n };\n\n if (options?.streamManager) {\n headers['x-genkit-stream-id'] = streamIdToUse;\n }\n\n return new Response(readable, {\n status: 200,\n headers,\n });\n }\n\n try {\n const result = await action.run(input, {\n context,\n abortSignal: request.signal,\n });\n\n const headers: Record<string, string> = {\n 'x-genkit-trace-id': result.telemetry.traceId,\n 'x-genkit-span-id': result.telemetry.spanId,\n };\n\n return new Response(JSON.stringify({ result: result.result }), {\n status: 200,\n headers: { 'Content-Type': 'application/json', ...headers },\n });\n } catch (e) {\n logger.error(\n `Non-streaming request failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n return new Response(JSON.stringify(getCallableJSON(e)), {\n status: getHttpStatus(e),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n}\n\nexport function fetchHandler<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n action: Action<I, O, S>,\n options?: FetchHandlerOptions<C, I>\n): (request: Request) => Promise<Response> {\n return (request: Request) => handleActionRequest(request, action, options);\n}\n\n/**\n * Returns a Fetch handler for multiple actions with path-based routing.\n * Express-like API: pass actions and optional path prefix, then call the returned handler with the request.\n *\n * @param actions - Array of actions (or ActionWithOptions) to expose; each is routed by path (action name or options.path)\n * @param pathPrefix - Optional path prefix to strip from the URL (e.g., '/api')\n * @returns A handler function that takes a Request and returns a Promise<Response>\n *\n * @example\n * ```typescript\n * import { fetchHandlers } from '@genkit-ai/fetch';\n *\n * // Mount all actions under /api - routes by path (e.g. POST /api/hello, POST /api/models/gpt5)\n * app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));\n * ```\n */\nexport function fetchHandlers(\n actions: (Action<any, any, any> | ActionWithOptions<any, any, any>)[],\n pathPrefix?: string\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n const url = new URL(request.url);\n let pathname = url.pathname;\n\n if (pathPrefix) {\n const prefix = pathPrefix.endsWith('/') ? pathPrefix : pathPrefix + '/';\n if (pathname === pathPrefix) {\n pathname = '';\n } else if (pathname.startsWith(prefix)) {\n pathname = pathname.slice(prefix.length);\n } else {\n return new Response(\n JSON.stringify({\n status: 'NOT_FOUND',\n message: 'No action matched the request path.',\n }),\n { status: 404, headers: { 'Content-Type': 'application/json' } }\n );\n }\n }\n\n pathname = pathname.replace(/^\\//, '');\n\n let matchedAction: Action<any, any, any> | null = null;\n let handlerOptions: FetchHandlerOptions<any, any> | undefined = undefined;\n\n for (const item of actions) {\n if ('action' in item) {\n const options = item.options;\n const actionPath = options.path ?? item.action.__action.name;\n if (pathname === actionPath) {\n matchedAction = item.action;\n handlerOptions = {\n contextProvider: options.contextProvider,\n streamManager: options.streamManager,\n };\n break;\n }\n } else {\n if (pathname === item.__action.name) {\n matchedAction = item;\n break;\n }\n }\n }\n\n if (!matchedAction) {\n return new Response(\n JSON.stringify({\n status: 'NOT_FOUND',\n message: 'No action matched the request path.',\n }),\n { status: 404, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n return handleActionRequest(request, matchedAction, handlerOptions);\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Action,\n ActionStreamInput,\n AsyncTaskQueue,\n StreamNotFoundError,\n type ActionContext,\n type StreamManager,\n type z,\n} from 'genkit/beta';\nimport {\n getCallableJSON,\n getHttpStatus,\n type ContextProvider,\n type RequestData,\n} from 'genkit/context';\nimport { logger } from 'genkit/logging';\nimport { getErrorMessage, getErrorStack } from './utils.js';\n\nconst streamDelimiter = '\\n\\n';\n\n// Let the runtime provide the randomUUID function.\nconst randomUUID = () => globalThis.crypto.randomUUID();\n\n/**\n * A wrapper object containing an action with its associated options.\n */\nexport type ActionWithOptions<\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n> = {\n action: Action<I, O, S>;\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n };\n};\n\n/**\n * Options for a fetch handler (context provider, stream manager).\n */\nexport interface FetchHandlerOptions<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n> {\n contextProvider?: ContextProvider<C, I>;\n streamManager?: StreamManager;\n}\n\n/**\n * Wraps an action (flow, model, etc.) with options for use with {@link fetchHandlers}.\n */\nexport function withActionOptions<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n action: Action<I, O, S>,\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n }\n): ActionWithOptions<I, O, S> {\n return {\n action,\n options,\n };\n}\n\n/**\n * Converts Headers object to a plain object with lowercase keys.\n */\nfunction headersToObject(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n headers.forEach((value, key) => {\n result[key.toLowerCase()] = value;\n });\n return result;\n}\n\n/**\n * Gets context from the request using the context provider if available.\n */\nasync function getContext<C extends ActionContext, I extends z.ZodTypeAny>(\n request: Request,\n input: z.infer<I>,\n provider?: ContextProvider<C, I>\n): Promise<C> {\n const context = {} as C;\n if (!provider) {\n return context;\n }\n\n const requestData: RequestData = {\n method: request.method as RequestData['method'],\n headers: headersToObject(request.headers),\n input,\n };\n\n return await provider(requestData);\n}\n\n/**\n * Subscribes to an existing stream using StreamManager.\n */\nasync function subscribeToStream(\n streamManager: StreamManager,\n streamId: string\n): Promise<Response | null> {\n try {\n const encoder = new TextEncoder();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n\n await streamManager.subscribe(streamId, {\n onChunk: (chunk) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n )\n );\n },\n onDone: (output) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ result: output }) + streamDelimiter\n )\n );\n writer.close();\n },\n onError: (err) => {\n logger.error(\n `Streaming request failed with error: ${getErrorMessage(err)}\\n${getErrorStack(err)}`\n );\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(err),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n },\n });\n\n return new Response(readable, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n 'x-genkit-stream-id': streamId,\n },\n });\n } catch (e: any) {\n if (e instanceof StreamNotFoundError) {\n return new Response(null, { status: 204 });\n }\n if (e.status === 'DEADLINE_EXCEEDED') {\n const encoder = new TextEncoder();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n return new Response(readable, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n },\n });\n }\n throw e;\n }\n}\n\n/**\n * Runs an action with durable streaming support.\n */\nasync function runActionWithDurableStreaming<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n Init extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n action: Action<I, O, S, any, Init>,\n streamManager: StreamManager | undefined,\n streamId: string,\n input: z.infer<I>,\n init: z.infer<Init> | undefined,\n context: ActionContext,\n writer: WritableStreamDefaultWriter<Uint8Array>,\n abortSignal: AbortSignal\n): Promise<void> {\n const encoder = new TextEncoder();\n let taskQueue: AsyncTaskQueue | undefined;\n let durableStream: ActionStreamInput<any, any> | undefined;\n\n if (streamManager) {\n taskQueue = new AsyncTaskQueue();\n durableStream = await streamManager.open(streamId);\n }\n\n try {\n let onChunk = (chunk: z.infer<S>) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n )\n );\n };\n\n if (streamManager && durableStream) {\n const originalOnChunk = onChunk;\n onChunk = (chunk: z.infer<S>) => {\n originalOnChunk(chunk);\n taskQueue!.enqueue(() => durableStream!.write(chunk));\n };\n }\n\n const result = await action.run(input, {\n onChunk,\n context,\n abortSignal,\n init,\n });\n\n if (streamManager && durableStream) {\n taskQueue!.enqueue(() => durableStream!.done(result.result));\n await taskQueue!.merge();\n }\n\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ result: result.result }) + streamDelimiter\n )\n );\n writer.close();\n } catch (e) {\n if (durableStream) {\n taskQueue!.enqueue(() => durableStream!.error(e));\n await taskQueue!.merge();\n }\n logger.error(\n `Streaming request failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n }\n}\n\n/**\n * Returns a Fetch handler for a single action (flow, model, or any Genkit action).\n * Express-like API: pass the action first, then call the returned handler with the request.\n *\n * @param action - The Genkit action to execute (flow, model, etc.)\n * @param options - Optional configuration including contextProvider and streamManager\n * @returns A handler function that takes a Request and returns a Promise<Response>\n *\n * @example\n * ```typescript\n * import { fetchHandler } from '@genkit-ai/fetch';\n *\n * // Flow or model\n * app.post('/myFlow', (c) => fetchHandler(myFlow)(c.req.raw));\n * app.post('/models/gpt5', (c) => fetchHandler(gpt5)(c.req.raw));\n *\n * // With options\n * app.post('/secure', (c) => fetchHandler(secureFlow, { contextProvider })(c.req.raw));\n * ```\n */\nasync function handleActionRequest<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n Init extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n request: Request,\n action: Action<I, O, S, any, Init>,\n options?: FetchHandlerOptions<C, I>\n): Promise<Response> {\n const url = new URL(request.url);\n const streamParam = url.searchParams.get('stream');\n const shouldStream = streamParam === 'true';\n\n const streamIdHeader = request.headers.get('x-genkit-stream-id');\n const streamId = streamIdHeader || undefined;\n\n let body: any;\n try {\n body = await request.json();\n } catch (e) {\n const errMsg =\n `Error: Failed to parse request body as JSON. ` +\n `Make sure the request has 'Content-Type: application/json' header.`;\n logger.error(errMsg);\n return new Response(\n JSON.stringify({ message: errMsg, status: 'INVALID_ARGUMENT' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n if (!body || typeof body !== 'object' || !('data' in body)) {\n const errMsg =\n `Error: Request body must be a JSON object with a 'data' field. ` +\n `Expected format: {\"data\": ...}`;\n logger.error(errMsg);\n return new Response(\n JSON.stringify({ message: errMsg, status: 'INVALID_ARGUMENT' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n const input = body.data as z.infer<I>;\n const init = body.init as z.infer<Init> | undefined;\n\n let context: C;\n try {\n context = await getContext(request, input, options?.contextProvider);\n } catch (e: any) {\n logger.error(\n `Context provider failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n return new Response(JSON.stringify(getCallableJSON(e)), {\n status: getHttpStatus(e),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n const acceptHeader = request.headers.get('Accept') || '';\n const isStreaming =\n acceptHeader.toLowerCase().includes('text/event-stream') || shouldStream;\n\n if (isStreaming) {\n const streamManager = options?.streamManager;\n if (streamManager && streamId) {\n const response = await subscribeToStream(streamManager, streamId);\n if (response) {\n return response;\n }\n }\n\n const streamIdToUse = randomUUID();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n\n runActionWithDurableStreaming(\n action,\n options?.streamManager,\n streamIdToUse,\n input,\n init,\n context,\n writer,\n request.signal\n ).catch((err) => {\n logger.error(`Error in streaming handler: ${err}`);\n });\n\n const headers: Record<string, string> = {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n };\n\n if (options?.streamManager) {\n headers['x-genkit-stream-id'] = streamIdToUse;\n }\n\n return new Response(readable, {\n status: 200,\n headers,\n });\n }\n\n try {\n const result = await action.run(input, {\n context,\n abortSignal: request.signal,\n init,\n });\n\n const headers: Record<string, string> = {\n 'x-genkit-trace-id': result.telemetry.traceId,\n 'x-genkit-span-id': result.telemetry.spanId,\n };\n\n return new Response(JSON.stringify({ result: result.result }), {\n status: 200,\n headers: { 'Content-Type': 'application/json', ...headers },\n });\n } catch (e) {\n logger.error(\n `Non-streaming request failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n return new Response(JSON.stringify(getCallableJSON(e)), {\n status: getHttpStatus(e),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n}\n\nexport function fetchHandler<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n Init extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n action: Action<I, O, S, any, Init>,\n options?: FetchHandlerOptions<C, I>\n): (request: Request) => Promise<Response> {\n return (request: Request) => handleActionRequest(request, action, options);\n}\n\n/**\n * Returns a Fetch handler for multiple actions with path-based routing.\n * Express-like API: pass actions and optional path prefix, then call the returned handler with the request.\n *\n * @param actions - Array of actions (or ActionWithOptions) to expose; each is routed by path (action name or options.path)\n * @param pathPrefix - Optional path prefix to strip from the URL (e.g., '/api')\n * @returns A handler function that takes a Request and returns a Promise<Response>\n *\n * @example\n * ```typescript\n * import { fetchHandlers } from '@genkit-ai/fetch';\n *\n * // Mount all actions under /api - routes by path (e.g. POST /api/hello, POST /api/models/gpt5)\n * app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));\n * ```\n */\nexport function fetchHandlers(\n actions: (Action<any, any, any> | ActionWithOptions<any, any, any>)[],\n pathPrefix?: string\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n const url = new URL(request.url);\n let pathname = url.pathname;\n\n if (pathPrefix) {\n const prefix = pathPrefix.endsWith('/') ? pathPrefix : pathPrefix + '/';\n if (pathname === pathPrefix) {\n pathname = '';\n } else if (pathname.startsWith(prefix)) {\n pathname = pathname.slice(prefix.length);\n } else {\n return new Response(\n JSON.stringify({\n status: 'NOT_FOUND',\n message: 'No action matched the request path.',\n }),\n { status: 404, headers: { 'Content-Type': 'application/json' } }\n );\n }\n }\n\n pathname = pathname.replace(/^\\//, '');\n\n let matchedAction: Action<any, any, any> | null = null;\n let handlerOptions: FetchHandlerOptions<any, any> | undefined = undefined;\n\n for (const item of actions) {\n if ('action' in item) {\n const options = item.options;\n const actionPath = options.path ?? item.action.__action.name;\n if (pathname === actionPath) {\n matchedAction = item.action;\n handlerOptions = {\n contextProvider: options.contextProvider,\n streamManager: options.streamManager,\n };\n break;\n }\n } else {\n if (pathname === item.__action.name) {\n matchedAction = item;\n break;\n }\n }\n }\n\n if (!matchedAction) {\n return new Response(\n JSON.stringify({\n status: 'NOT_FOUND',\n message: 'No action matched the request path.',\n }),\n { status: 404, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n return handleActionRequest(request, matchedAction, handlerOptions);\n };\n}\n"],"mappings":"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","names":[]}
|
package/lib/index.mjs
CHANGED
|
@@ -110,7 +110,7 @@ ${getErrorStack(err)}`
|
|
|
110
110
|
throw e;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
async function runActionWithDurableStreaming(action, streamManager, streamId, input, context, writer, abortSignal) {
|
|
113
|
+
async function runActionWithDurableStreaming(action, streamManager, streamId, input, init, context, writer, abortSignal) {
|
|
114
114
|
const encoder = new TextEncoder();
|
|
115
115
|
let taskQueue;
|
|
116
116
|
let durableStream;
|
|
@@ -136,7 +136,8 @@ async function runActionWithDurableStreaming(action, streamManager, streamId, in
|
|
|
136
136
|
const result = await action.run(input, {
|
|
137
137
|
onChunk,
|
|
138
138
|
context,
|
|
139
|
-
abortSignal
|
|
139
|
+
abortSignal,
|
|
140
|
+
init
|
|
140
141
|
});
|
|
141
142
|
if (streamManager && durableStream) {
|
|
142
143
|
taskQueue.enqueue(() => durableStream.done(result.result));
|
|
@@ -193,6 +194,7 @@ async function handleActionRequest(request, action, options) {
|
|
|
193
194
|
);
|
|
194
195
|
}
|
|
195
196
|
const input = body.data;
|
|
197
|
+
const init = body.init;
|
|
196
198
|
let context;
|
|
197
199
|
try {
|
|
198
200
|
context = await getContext(request, input, options?.contextProvider);
|
|
@@ -224,6 +226,7 @@ ${e.stack}`
|
|
|
224
226
|
options?.streamManager,
|
|
225
227
|
streamIdToUse,
|
|
226
228
|
input,
|
|
229
|
+
init,
|
|
227
230
|
context,
|
|
228
231
|
writer,
|
|
229
232
|
request.signal
|
|
@@ -247,7 +250,8 @@ ${e.stack}`
|
|
|
247
250
|
try {
|
|
248
251
|
const result = await action.run(input, {
|
|
249
252
|
context,
|
|
250
|
-
abortSignal: request.signal
|
|
253
|
+
abortSignal: request.signal,
|
|
254
|
+
init
|
|
251
255
|
});
|
|
252
256
|
const headers = {
|
|
253
257
|
"x-genkit-trace-id": result.telemetry.traceId,
|
package/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Action,\n ActionStreamInput,\n AsyncTaskQueue,\n StreamNotFoundError,\n type ActionContext,\n type StreamManager,\n type z,\n} from 'genkit/beta';\nimport {\n getCallableJSON,\n getHttpStatus,\n type ContextProvider,\n type RequestData,\n} from 'genkit/context';\nimport { logger } from 'genkit/logging';\nimport { getErrorMessage, getErrorStack } from './utils.mjs';\n\nconst streamDelimiter = '\\n\\n';\n\n// Let the runtime provide the randomUUID function.\nconst randomUUID = () => globalThis.crypto.randomUUID();\n\n/**\n * A wrapper object containing an action with its associated options.\n */\nexport type ActionWithOptions<\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n> = {\n action: Action<I, O, S>;\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n };\n};\n\n/**\n * Options for a fetch handler (context provider, stream manager).\n */\nexport interface FetchHandlerOptions<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n> {\n contextProvider?: ContextProvider<C, I>;\n streamManager?: StreamManager;\n}\n\n/**\n * Wraps an action (flow, model, etc.) with options for use with {@link fetchHandlers}.\n */\nexport function withActionOptions<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n action: Action<I, O, S>,\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n }\n): ActionWithOptions<I, O, S> {\n return {\n action,\n options,\n };\n}\n\n/**\n * Converts Headers object to a plain object with lowercase keys.\n */\nfunction headersToObject(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n headers.forEach((value, key) => {\n result[key.toLowerCase()] = value;\n });\n return result;\n}\n\n/**\n * Gets context from the request using the context provider if available.\n */\nasync function getContext<C extends ActionContext, I extends z.ZodTypeAny>(\n request: Request,\n input: z.infer<I>,\n provider?: ContextProvider<C, I>\n): Promise<C> {\n const context = {} as C;\n if (!provider) {\n return context;\n }\n\n const requestData: RequestData = {\n method: request.method as RequestData['method'],\n headers: headersToObject(request.headers),\n input,\n };\n\n return await provider(requestData);\n}\n\n/**\n * Subscribes to an existing stream using StreamManager.\n */\nasync function subscribeToStream(\n streamManager: StreamManager,\n streamId: string\n): Promise<Response | null> {\n try {\n const encoder = new TextEncoder();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n\n await streamManager.subscribe(streamId, {\n onChunk: (chunk) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n )\n );\n },\n onDone: (output) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ result: output }) + streamDelimiter\n )\n );\n writer.close();\n },\n onError: (err) => {\n logger.error(\n `Streaming request failed with error: ${getErrorMessage(err)}\\n${getErrorStack(err)}`\n );\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(err),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n },\n });\n\n return new Response(readable, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n 'x-genkit-stream-id': streamId,\n },\n });\n } catch (e: any) {\n if (e instanceof StreamNotFoundError) {\n return new Response(null, { status: 204 });\n }\n if (e.status === 'DEADLINE_EXCEEDED') {\n const encoder = new TextEncoder();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n return new Response(readable, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n },\n });\n }\n throw e;\n }\n}\n\n/**\n * Runs an action with durable streaming support.\n */\nasync function runActionWithDurableStreaming<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n action: Action<I, O, S>,\n streamManager: StreamManager | undefined,\n streamId: string,\n input: z.infer<I>,\n context: ActionContext,\n writer: WritableStreamDefaultWriter<Uint8Array>,\n abortSignal: AbortSignal\n): Promise<void> {\n const encoder = new TextEncoder();\n let taskQueue: AsyncTaskQueue | undefined;\n let durableStream: ActionStreamInput<any, any> | undefined;\n\n if (streamManager) {\n taskQueue = new AsyncTaskQueue();\n durableStream = await streamManager.open(streamId);\n }\n\n try {\n let onChunk = (chunk: z.infer<S>) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n )\n );\n };\n\n if (streamManager && durableStream) {\n const originalOnChunk = onChunk;\n onChunk = (chunk: z.infer<S>) => {\n originalOnChunk(chunk);\n taskQueue!.enqueue(() => durableStream!.write(chunk));\n };\n }\n\n const result = await action.run(input, {\n onChunk,\n context,\n abortSignal,\n });\n\n if (streamManager && durableStream) {\n taskQueue!.enqueue(() => durableStream!.done(result.result));\n await taskQueue!.merge();\n }\n\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ result: result.result }) + streamDelimiter\n )\n );\n writer.close();\n } catch (e) {\n if (durableStream) {\n taskQueue!.enqueue(() => durableStream!.error(e));\n await taskQueue!.merge();\n }\n logger.error(\n `Streaming request failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n }\n}\n\n/**\n * Returns a Fetch handler for a single action (flow, model, or any Genkit action).\n * Express-like API: pass the action first, then call the returned handler with the request.\n *\n * @param action - The Genkit action to execute (flow, model, etc.)\n * @param options - Optional configuration including contextProvider and streamManager\n * @returns A handler function that takes a Request and returns a Promise<Response>\n *\n * @example\n * ```typescript\n * import { fetchHandler } from '@genkit-ai/fetch';\n *\n * // Flow or model\n * app.post('/myFlow', (c) => fetchHandler(myFlow)(c.req.raw));\n * app.post('/models/gpt5', (c) => fetchHandler(gpt5)(c.req.raw));\n *\n * // With options\n * app.post('/secure', (c) => fetchHandler(secureFlow, { contextProvider })(c.req.raw));\n * ```\n */\nasync function handleActionRequest<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n request: Request,\n action: Action<I, O, S>,\n options?: FetchHandlerOptions<C, I>\n): Promise<Response> {\n const url = new URL(request.url);\n const streamParam = url.searchParams.get('stream');\n const shouldStream = streamParam === 'true';\n\n const streamIdHeader = request.headers.get('x-genkit-stream-id');\n const streamId = streamIdHeader || undefined;\n\n let body: any;\n try {\n body = await request.json();\n } catch (e) {\n const errMsg =\n `Error: Failed to parse request body as JSON. ` +\n `Make sure the request has 'Content-Type: application/json' header.`;\n logger.error(errMsg);\n return new Response(\n JSON.stringify({ message: errMsg, status: 'INVALID_ARGUMENT' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n if (!body || typeof body !== 'object' || !('data' in body)) {\n const errMsg =\n `Error: Request body must be a JSON object with a 'data' field. ` +\n `Expected format: {\"data\": ...}`;\n logger.error(errMsg);\n return new Response(\n JSON.stringify({ message: errMsg, status: 'INVALID_ARGUMENT' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n const input = body.data as z.infer<I>;\n\n let context: C;\n try {\n context = await getContext(request, input, options?.contextProvider);\n } catch (e: any) {\n logger.error(\n `Context provider failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n return new Response(JSON.stringify(getCallableJSON(e)), {\n status: getHttpStatus(e),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n const acceptHeader = request.headers.get('Accept') || '';\n const isStreaming =\n acceptHeader.toLowerCase().includes('text/event-stream') || shouldStream;\n\n if (isStreaming) {\n const streamManager = options?.streamManager;\n if (streamManager && streamId) {\n const response = await subscribeToStream(streamManager, streamId);\n if (response) {\n return response;\n }\n }\n\n const streamIdToUse = randomUUID();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n\n runActionWithDurableStreaming(\n action,\n options?.streamManager,\n streamIdToUse,\n input,\n context,\n writer,\n request.signal\n ).catch((err) => {\n logger.error(`Error in streaming handler: ${err}`);\n });\n\n const headers: Record<string, string> = {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n };\n\n if (options?.streamManager) {\n headers['x-genkit-stream-id'] = streamIdToUse;\n }\n\n return new Response(readable, {\n status: 200,\n headers,\n });\n }\n\n try {\n const result = await action.run(input, {\n context,\n abortSignal: request.signal,\n });\n\n const headers: Record<string, string> = {\n 'x-genkit-trace-id': result.telemetry.traceId,\n 'x-genkit-span-id': result.telemetry.spanId,\n };\n\n return new Response(JSON.stringify({ result: result.result }), {\n status: 200,\n headers: { 'Content-Type': 'application/json', ...headers },\n });\n } catch (e) {\n logger.error(\n `Non-streaming request failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n return new Response(JSON.stringify(getCallableJSON(e)), {\n status: getHttpStatus(e),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n}\n\nexport function fetchHandler<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n action: Action<I, O, S>,\n options?: FetchHandlerOptions<C, I>\n): (request: Request) => Promise<Response> {\n return (request: Request) => handleActionRequest(request, action, options);\n}\n\n/**\n * Returns a Fetch handler for multiple actions with path-based routing.\n * Express-like API: pass actions and optional path prefix, then call the returned handler with the request.\n *\n * @param actions - Array of actions (or ActionWithOptions) to expose; each is routed by path (action name or options.path)\n * @param pathPrefix - Optional path prefix to strip from the URL (e.g., '/api')\n * @returns A handler function that takes a Request and returns a Promise<Response>\n *\n * @example\n * ```typescript\n * import { fetchHandlers } from '@genkit-ai/fetch';\n *\n * // Mount all actions under /api - routes by path (e.g. POST /api/hello, POST /api/models/gpt5)\n * app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));\n * ```\n */\nexport function fetchHandlers(\n actions: (Action<any, any, any> | ActionWithOptions<any, any, any>)[],\n pathPrefix?: string\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n const url = new URL(request.url);\n let pathname = url.pathname;\n\n if (pathPrefix) {\n const prefix = pathPrefix.endsWith('/') ? pathPrefix : pathPrefix + '/';\n if (pathname === pathPrefix) {\n pathname = '';\n } else if (pathname.startsWith(prefix)) {\n pathname = pathname.slice(prefix.length);\n } else {\n return new Response(\n JSON.stringify({\n status: 'NOT_FOUND',\n message: 'No action matched the request path.',\n }),\n { status: 404, headers: { 'Content-Type': 'application/json' } }\n );\n }\n }\n\n pathname = pathname.replace(/^\\//, '');\n\n let matchedAction: Action<any, any, any> | null = null;\n let handlerOptions: FetchHandlerOptions<any, any> | undefined = undefined;\n\n for (const item of actions) {\n if ('action' in item) {\n const options = item.options;\n const actionPath = options.path ?? item.action.__action.name;\n if (pathname === actionPath) {\n matchedAction = item.action;\n handlerOptions = {\n contextProvider: options.contextProvider,\n streamManager: options.streamManager,\n };\n break;\n }\n } else {\n if (pathname === item.__action.name) {\n matchedAction = item;\n break;\n }\n }\n }\n\n if (!matchedAction) {\n return new Response(\n JSON.stringify({\n status: 'NOT_FOUND',\n message: 'No action matched the request path.',\n }),\n { status: 404, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n return handleActionRequest(request, matchedAction, handlerOptions);\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Action,\n ActionStreamInput,\n AsyncTaskQueue,\n StreamNotFoundError,\n type ActionContext,\n type StreamManager,\n type z,\n} from 'genkit/beta';\nimport {\n getCallableJSON,\n getHttpStatus,\n type ContextProvider,\n type RequestData,\n} from 'genkit/context';\nimport { logger } from 'genkit/logging';\nimport { getErrorMessage, getErrorStack } from './utils.mjs';\n\nconst streamDelimiter = '\\n\\n';\n\n// Let the runtime provide the randomUUID function.\nconst randomUUID = () => globalThis.crypto.randomUUID();\n\n/**\n * A wrapper object containing an action with its associated options.\n */\nexport type ActionWithOptions<\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n> = {\n action: Action<I, O, S>;\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n };\n};\n\n/**\n * Options for a fetch handler (context provider, stream manager).\n */\nexport interface FetchHandlerOptions<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n> {\n contextProvider?: ContextProvider<C, I>;\n streamManager?: StreamManager;\n}\n\n/**\n * Wraps an action (flow, model, etc.) with options for use with {@link fetchHandlers}.\n */\nexport function withActionOptions<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n action: Action<I, O, S>,\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n }\n): ActionWithOptions<I, O, S> {\n return {\n action,\n options,\n };\n}\n\n/**\n * Converts Headers object to a plain object with lowercase keys.\n */\nfunction headersToObject(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n headers.forEach((value, key) => {\n result[key.toLowerCase()] = value;\n });\n return result;\n}\n\n/**\n * Gets context from the request using the context provider if available.\n */\nasync function getContext<C extends ActionContext, I extends z.ZodTypeAny>(\n request: Request,\n input: z.infer<I>,\n provider?: ContextProvider<C, I>\n): Promise<C> {\n const context = {} as C;\n if (!provider) {\n return context;\n }\n\n const requestData: RequestData = {\n method: request.method as RequestData['method'],\n headers: headersToObject(request.headers),\n input,\n };\n\n return await provider(requestData);\n}\n\n/**\n * Subscribes to an existing stream using StreamManager.\n */\nasync function subscribeToStream(\n streamManager: StreamManager,\n streamId: string\n): Promise<Response | null> {\n try {\n const encoder = new TextEncoder();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n\n await streamManager.subscribe(streamId, {\n onChunk: (chunk) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n )\n );\n },\n onDone: (output) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ result: output }) + streamDelimiter\n )\n );\n writer.close();\n },\n onError: (err) => {\n logger.error(\n `Streaming request failed with error: ${getErrorMessage(err)}\\n${getErrorStack(err)}`\n );\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(err),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n },\n });\n\n return new Response(readable, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n 'x-genkit-stream-id': streamId,\n },\n });\n } catch (e: any) {\n if (e instanceof StreamNotFoundError) {\n return new Response(null, { status: 204 });\n }\n if (e.status === 'DEADLINE_EXCEEDED') {\n const encoder = new TextEncoder();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n return new Response(readable, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n },\n });\n }\n throw e;\n }\n}\n\n/**\n * Runs an action with durable streaming support.\n */\nasync function runActionWithDurableStreaming<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n Init extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n action: Action<I, O, S, any, Init>,\n streamManager: StreamManager | undefined,\n streamId: string,\n input: z.infer<I>,\n init: z.infer<Init> | undefined,\n context: ActionContext,\n writer: WritableStreamDefaultWriter<Uint8Array>,\n abortSignal: AbortSignal\n): Promise<void> {\n const encoder = new TextEncoder();\n let taskQueue: AsyncTaskQueue | undefined;\n let durableStream: ActionStreamInput<any, any> | undefined;\n\n if (streamManager) {\n taskQueue = new AsyncTaskQueue();\n durableStream = await streamManager.open(streamId);\n }\n\n try {\n let onChunk = (chunk: z.infer<S>) => {\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n )\n );\n };\n\n if (streamManager && durableStream) {\n const originalOnChunk = onChunk;\n onChunk = (chunk: z.infer<S>) => {\n originalOnChunk(chunk);\n taskQueue!.enqueue(() => durableStream!.write(chunk));\n };\n }\n\n const result = await action.run(input, {\n onChunk,\n context,\n abortSignal,\n init,\n });\n\n if (streamManager && durableStream) {\n taskQueue!.enqueue(() => durableStream!.done(result.result));\n await taskQueue!.merge();\n }\n\n writer.write(\n encoder.encode(\n 'data: ' + JSON.stringify({ result: result.result }) + streamDelimiter\n )\n );\n writer.close();\n } catch (e) {\n if (durableStream) {\n taskQueue!.enqueue(() => durableStream!.error(e));\n await taskQueue!.merge();\n }\n logger.error(\n `Streaming request failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n writer.write(\n encoder.encode(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n )\n );\n writer.close();\n }\n}\n\n/**\n * Returns a Fetch handler for a single action (flow, model, or any Genkit action).\n * Express-like API: pass the action first, then call the returned handler with the request.\n *\n * @param action - The Genkit action to execute (flow, model, etc.)\n * @param options - Optional configuration including contextProvider and streamManager\n * @returns A handler function that takes a Request and returns a Promise<Response>\n *\n * @example\n * ```typescript\n * import { fetchHandler } from '@genkit-ai/fetch';\n *\n * // Flow or model\n * app.post('/myFlow', (c) => fetchHandler(myFlow)(c.req.raw));\n * app.post('/models/gpt5', (c) => fetchHandler(gpt5)(c.req.raw));\n *\n * // With options\n * app.post('/secure', (c) => fetchHandler(secureFlow, { contextProvider })(c.req.raw));\n * ```\n */\nasync function handleActionRequest<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n Init extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n request: Request,\n action: Action<I, O, S, any, Init>,\n options?: FetchHandlerOptions<C, I>\n): Promise<Response> {\n const url = new URL(request.url);\n const streamParam = url.searchParams.get('stream');\n const shouldStream = streamParam === 'true';\n\n const streamIdHeader = request.headers.get('x-genkit-stream-id');\n const streamId = streamIdHeader || undefined;\n\n let body: any;\n try {\n body = await request.json();\n } catch (e) {\n const errMsg =\n `Error: Failed to parse request body as JSON. ` +\n `Make sure the request has 'Content-Type: application/json' header.`;\n logger.error(errMsg);\n return new Response(\n JSON.stringify({ message: errMsg, status: 'INVALID_ARGUMENT' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n if (!body || typeof body !== 'object' || !('data' in body)) {\n const errMsg =\n `Error: Request body must be a JSON object with a 'data' field. ` +\n `Expected format: {\"data\": ...}`;\n logger.error(errMsg);\n return new Response(\n JSON.stringify({ message: errMsg, status: 'INVALID_ARGUMENT' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n const input = body.data as z.infer<I>;\n const init = body.init as z.infer<Init> | undefined;\n\n let context: C;\n try {\n context = await getContext(request, input, options?.contextProvider);\n } catch (e: any) {\n logger.error(\n `Context provider failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n return new Response(JSON.stringify(getCallableJSON(e)), {\n status: getHttpStatus(e),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n const acceptHeader = request.headers.get('Accept') || '';\n const isStreaming =\n acceptHeader.toLowerCase().includes('text/event-stream') || shouldStream;\n\n if (isStreaming) {\n const streamManager = options?.streamManager;\n if (streamManager && streamId) {\n const response = await subscribeToStream(streamManager, streamId);\n if (response) {\n return response;\n }\n }\n\n const streamIdToUse = randomUUID();\n const { readable, writable } = new TransformStream();\n const writer = writable.getWriter();\n\n runActionWithDurableStreaming(\n action,\n options?.streamManager,\n streamIdToUse,\n input,\n init,\n context,\n writer,\n request.signal\n ).catch((err) => {\n logger.error(`Error in streaming handler: ${err}`);\n });\n\n const headers: Record<string, string> = {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'Transfer-Encoding': 'chunked',\n };\n\n if (options?.streamManager) {\n headers['x-genkit-stream-id'] = streamIdToUse;\n }\n\n return new Response(readable, {\n status: 200,\n headers,\n });\n }\n\n try {\n const result = await action.run(input, {\n context,\n abortSignal: request.signal,\n init,\n });\n\n const headers: Record<string, string> = {\n 'x-genkit-trace-id': result.telemetry.traceId,\n 'x-genkit-span-id': result.telemetry.spanId,\n };\n\n return new Response(JSON.stringify({ result: result.result }), {\n status: 200,\n headers: { 'Content-Type': 'application/json', ...headers },\n });\n } catch (e) {\n logger.error(\n `Non-streaming request failed with error: ${(e as Error).message}\\n${\n (e as Error).stack\n }`\n );\n return new Response(JSON.stringify(getCallableJSON(e)), {\n status: getHttpStatus(e),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n}\n\nexport function fetchHandler<\n C extends ActionContext = ActionContext,\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n Init extends z.ZodTypeAny = z.ZodTypeAny,\n>(\n action: Action<I, O, S, any, Init>,\n options?: FetchHandlerOptions<C, I>\n): (request: Request) => Promise<Response> {\n return (request: Request) => handleActionRequest(request, action, options);\n}\n\n/**\n * Returns a Fetch handler for multiple actions with path-based routing.\n * Express-like API: pass actions and optional path prefix, then call the returned handler with the request.\n *\n * @param actions - Array of actions (or ActionWithOptions) to expose; each is routed by path (action name or options.path)\n * @param pathPrefix - Optional path prefix to strip from the URL (e.g., '/api')\n * @returns A handler function that takes a Request and returns a Promise<Response>\n *\n * @example\n * ```typescript\n * import { fetchHandlers } from '@genkit-ai/fetch';\n *\n * // Mount all actions under /api - routes by path (e.g. POST /api/hello, POST /api/models/gpt5)\n * app.all('/api/*', (c) => fetchHandlers(actions, '/api')(c.req.raw));\n * ```\n */\nexport function fetchHandlers(\n actions: (Action<any, any, any> | ActionWithOptions<any, any, any>)[],\n pathPrefix?: string\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n const url = new URL(request.url);\n let pathname = url.pathname;\n\n if (pathPrefix) {\n const prefix = pathPrefix.endsWith('/') ? pathPrefix : pathPrefix + '/';\n if (pathname === pathPrefix) {\n pathname = '';\n } else if (pathname.startsWith(prefix)) {\n pathname = pathname.slice(prefix.length);\n } else {\n return new Response(\n JSON.stringify({\n status: 'NOT_FOUND',\n message: 'No action matched the request path.',\n }),\n { status: 404, headers: { 'Content-Type': 'application/json' } }\n );\n }\n }\n\n pathname = pathname.replace(/^\\//, '');\n\n let matchedAction: Action<any, any, any> | null = null;\n let handlerOptions: FetchHandlerOptions<any, any> | undefined = undefined;\n\n for (const item of actions) {\n if ('action' in item) {\n const options = item.options;\n const actionPath = options.path ?? item.action.__action.name;\n if (pathname === actionPath) {\n matchedAction = item.action;\n handlerOptions = {\n contextProvider: options.contextProvider,\n streamManager: options.streamManager,\n };\n break;\n }\n } else {\n if (pathname === item.__action.name) {\n matchedAction = item;\n break;\n }\n }\n }\n\n if (!matchedAction) {\n return new Response(\n JSON.stringify({\n status: 'NOT_FOUND',\n message: 'No action matched the request path.',\n }),\n { status: 404, headers: { 'Content-Type': 'application/json' } }\n );\n }\n\n return handleActionRequest(request, matchedAction, handlerOptions);\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"bun",
|
|
17
17
|
"deno"
|
|
18
18
|
],
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.2.0",
|
|
20
20
|
"type": "commonjs",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"author": "genkit",
|
|
27
27
|
"license": "Apache-2.0",
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"genkit": "^1.
|
|
29
|
+
"genkit": "^1.39.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"get-port": "^5.1.0",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"tsup": "^8.3.5",
|
|
37
37
|
"tsx": "^4.19.2",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
|
-
"genkit": "1.
|
|
39
|
+
"genkit": "1.39.0"
|
|
40
40
|
},
|
|
41
41
|
"types": "./lib/index.d.ts",
|
|
42
42
|
"exports": {
|
package/src/index.ts
CHANGED
|
@@ -208,11 +208,13 @@ async function runActionWithDurableStreaming<
|
|
|
208
208
|
I extends z.ZodTypeAny,
|
|
209
209
|
O extends z.ZodTypeAny,
|
|
210
210
|
S extends z.ZodTypeAny,
|
|
211
|
+
Init extends z.ZodTypeAny = z.ZodTypeAny,
|
|
211
212
|
>(
|
|
212
|
-
action: Action<I, O, S>,
|
|
213
|
+
action: Action<I, O, S, any, Init>,
|
|
213
214
|
streamManager: StreamManager | undefined,
|
|
214
215
|
streamId: string,
|
|
215
216
|
input: z.infer<I>,
|
|
217
|
+
init: z.infer<Init> | undefined,
|
|
216
218
|
context: ActionContext,
|
|
217
219
|
writer: WritableStreamDefaultWriter<Uint8Array>,
|
|
218
220
|
abortSignal: AbortSignal
|
|
@@ -247,6 +249,7 @@ async function runActionWithDurableStreaming<
|
|
|
247
249
|
onChunk,
|
|
248
250
|
context,
|
|
249
251
|
abortSignal,
|
|
252
|
+
init,
|
|
250
253
|
});
|
|
251
254
|
|
|
252
255
|
if (streamManager && durableStream) {
|
|
@@ -306,9 +309,10 @@ async function handleActionRequest<
|
|
|
306
309
|
I extends z.ZodTypeAny = z.ZodTypeAny,
|
|
307
310
|
O extends z.ZodTypeAny = z.ZodTypeAny,
|
|
308
311
|
S extends z.ZodTypeAny = z.ZodTypeAny,
|
|
312
|
+
Init extends z.ZodTypeAny = z.ZodTypeAny,
|
|
309
313
|
>(
|
|
310
314
|
request: Request,
|
|
311
|
-
action: Action<I, O, S>,
|
|
315
|
+
action: Action<I, O, S, any, Init>,
|
|
312
316
|
options?: FetchHandlerOptions<C, I>
|
|
313
317
|
): Promise<Response> {
|
|
314
318
|
const url = new URL(request.url);
|
|
@@ -344,6 +348,7 @@ async function handleActionRequest<
|
|
|
344
348
|
}
|
|
345
349
|
|
|
346
350
|
const input = body.data as z.infer<I>;
|
|
351
|
+
const init = body.init as z.infer<Init> | undefined;
|
|
347
352
|
|
|
348
353
|
let context: C;
|
|
349
354
|
try {
|
|
@@ -382,6 +387,7 @@ async function handleActionRequest<
|
|
|
382
387
|
options?.streamManager,
|
|
383
388
|
streamIdToUse,
|
|
384
389
|
input,
|
|
390
|
+
init,
|
|
385
391
|
context,
|
|
386
392
|
writer,
|
|
387
393
|
request.signal
|
|
@@ -410,6 +416,7 @@ async function handleActionRequest<
|
|
|
410
416
|
const result = await action.run(input, {
|
|
411
417
|
context,
|
|
412
418
|
abortSignal: request.signal,
|
|
419
|
+
init,
|
|
413
420
|
});
|
|
414
421
|
|
|
415
422
|
const headers: Record<string, string> = {
|
|
@@ -439,8 +446,9 @@ export function fetchHandler<
|
|
|
439
446
|
I extends z.ZodTypeAny = z.ZodTypeAny,
|
|
440
447
|
O extends z.ZodTypeAny = z.ZodTypeAny,
|
|
441
448
|
S extends z.ZodTypeAny = z.ZodTypeAny,
|
|
449
|
+
Init extends z.ZodTypeAny = z.ZodTypeAny,
|
|
442
450
|
>(
|
|
443
|
-
action: Action<I, O, S>,
|
|
451
|
+
action: Action<I, O, S, any, Init>,
|
|
444
452
|
options?: FetchHandlerOptions<C, I>
|
|
445
453
|
): (request: Request) => Promise<Response> {
|
|
446
454
|
return (request: Request) => handleActionRequest(request, action, options);
|
package/tests/web_test.ts
CHANGED
|
@@ -314,6 +314,115 @@ describe('fetchHandler (single action)', () => {
|
|
|
314
314
|
assert.ok(json.message?.includes('not authorized'));
|
|
315
315
|
});
|
|
316
316
|
|
|
317
|
+
it('should pass init data to the action', async () => {
|
|
318
|
+
const ai = genkit({});
|
|
319
|
+
const flow = ai.defineFlow(
|
|
320
|
+
{ name: 'flowWithInit', inputSchema: z.string() },
|
|
321
|
+
async (input) => `input: ${input}`
|
|
322
|
+
);
|
|
323
|
+
// Monkey-patch the run method to capture and return init data.
|
|
324
|
+
const originalRun = flow.run.bind(flow);
|
|
325
|
+
flow.run = async (input: any, options: any) => {
|
|
326
|
+
const result = await originalRun(input, options);
|
|
327
|
+
result.result = `input: ${input}, init: ${JSON.stringify(options?.init)}`;
|
|
328
|
+
return result;
|
|
329
|
+
};
|
|
330
|
+
const request = new Request('http://localhost/flowWithInit', {
|
|
331
|
+
method: 'POST',
|
|
332
|
+
headers: { 'Content-Type': 'application/json' },
|
|
333
|
+
body: JSON.stringify({
|
|
334
|
+
data: 'hello',
|
|
335
|
+
init: { sessionId: 'abc123', temperature: 0.7 },
|
|
336
|
+
}),
|
|
337
|
+
});
|
|
338
|
+
const response = await fetchHandler(flow)(request);
|
|
339
|
+
assert.strictEqual(response.status, 200);
|
|
340
|
+
const json = (await response.json()) as { result: string };
|
|
341
|
+
assert.strictEqual(
|
|
342
|
+
json.result,
|
|
343
|
+
'input: hello, init: {"sessionId":"abc123","temperature":0.7}'
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should pass undefined init when not provided', async () => {
|
|
348
|
+
const ai = genkit({});
|
|
349
|
+
const flow = ai.defineFlow(
|
|
350
|
+
{ name: 'flowWithInit', inputSchema: z.string() },
|
|
351
|
+
async (input) => `input: ${input}`
|
|
352
|
+
);
|
|
353
|
+
const originalRun = flow.run.bind(flow);
|
|
354
|
+
flow.run = async (input: any, options: any) => {
|
|
355
|
+
const result = await originalRun(input, options);
|
|
356
|
+
result.result = `input: ${input}, init: ${JSON.stringify(options?.init)}`;
|
|
357
|
+
return result;
|
|
358
|
+
};
|
|
359
|
+
const request = new Request('http://localhost/flowWithInit', {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers: { 'Content-Type': 'application/json' },
|
|
362
|
+
body: JSON.stringify({ data: 'hello' }),
|
|
363
|
+
});
|
|
364
|
+
const response = await fetchHandler(flow)(request);
|
|
365
|
+
assert.strictEqual(response.status, 200);
|
|
366
|
+
const json = (await response.json()) as { result: string };
|
|
367
|
+
assert.strictEqual(json.result, 'input: hello, init: undefined');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('should validate init against initSchema and pass it to the action', async () => {
|
|
371
|
+
const ai = genkit({});
|
|
372
|
+
const flow = ai.defineFlow(
|
|
373
|
+
{
|
|
374
|
+
name: 'flowWithInitSchema',
|
|
375
|
+
inputSchema: z.string(),
|
|
376
|
+
initSchema: z.object({ sessionId: z.string() }),
|
|
377
|
+
},
|
|
378
|
+
async (input, { init }) =>
|
|
379
|
+
`input: ${input}, sessionId: ${(init as { sessionId: string }).sessionId}`
|
|
380
|
+
);
|
|
381
|
+
const request = new Request('http://localhost/flowWithInitSchema', {
|
|
382
|
+
method: 'POST',
|
|
383
|
+
headers: { 'Content-Type': 'application/json' },
|
|
384
|
+
body: JSON.stringify({
|
|
385
|
+
data: 'hello',
|
|
386
|
+
init: { sessionId: 'abc123' },
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
const response = await fetchHandler(flow)(request);
|
|
390
|
+
assert.strictEqual(response.status, 200);
|
|
391
|
+
const json = (await response.json()) as { result: string };
|
|
392
|
+
assert.strictEqual(json.result, 'input: hello, sessionId: abc123');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should reject init that does not conform to initSchema', async () => {
|
|
396
|
+
const ai = genkit({});
|
|
397
|
+
const flow = ai.defineFlow(
|
|
398
|
+
{
|
|
399
|
+
name: 'flowWithInitSchema',
|
|
400
|
+
inputSchema: z.string(),
|
|
401
|
+
initSchema: z.object({ sessionId: z.string() }),
|
|
402
|
+
},
|
|
403
|
+
async (input, { init }) =>
|
|
404
|
+
`input: ${input}, sessionId: ${(init as { sessionId: string }).sessionId}`
|
|
405
|
+
);
|
|
406
|
+
const request = new Request('http://localhost/flowWithInitSchema', {
|
|
407
|
+
method: 'POST',
|
|
408
|
+
headers: { 'Content-Type': 'application/json' },
|
|
409
|
+
body: JSON.stringify({
|
|
410
|
+
data: 'hello',
|
|
411
|
+
// sessionId should be a string, not a number.
|
|
412
|
+
init: { sessionId: 123 },
|
|
413
|
+
}),
|
|
414
|
+
});
|
|
415
|
+
const response = await fetchHandler(flow)(request);
|
|
416
|
+
assert.strictEqual(response.status, 400);
|
|
417
|
+
const json = (await response.json()) as {
|
|
418
|
+
status?: string;
|
|
419
|
+
message?: string;
|
|
420
|
+
};
|
|
421
|
+
assert.ok(
|
|
422
|
+
json.status === 'INVALID_ARGUMENT' || json.message?.includes('INVALID')
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
|
|
317
426
|
it('should set x-genkit-trace-id and x-genkit-span-id headers', async () => {
|
|
318
427
|
const ai = genkit({});
|
|
319
428
|
const flow = ai.defineFlow('traceFlow', async () => 'ok');
|