@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 +182 -0
- package/lib/index.d.mts +65 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.js +353 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +333 -0
- package/lib/index.mjs.map +1 -0
- package/lib/utils.d.mts +25 -0
- package/lib/utils.d.ts +25 -0
- package/lib/utils.js +42 -0
- package/lib/utils.js.map +1 -0
- package/lib/utils.mjs +17 -0
- package/lib/utils.mjs.map +1 -0
- package/package.json +59 -0
- package/src/index.ts +526 -0
- package/src/utils.ts +33 -0
- package/tests/web_test.ts +802 -0
- package/typedoc.json +3 -0
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.
|
package/lib/index.d.mts
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.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
|