@genkit-ai/express 1.38.0 → 1.39.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 CHANGED
@@ -131,6 +131,31 @@ for await (const chunk of result.stream) {
131
131
  console.log(await result.output);
132
132
  ```
133
133
 
134
+ ### Initialization Data
135
+
136
+ If your flow or action accepts initialization data (defined via `initSchema`), you can pass it using the `init` option in the client:
137
+
138
+ ```ts
139
+ const result = await runFlow({
140
+ url: `http://localhost:${port}/myFlow`,
141
+ input: 'say hello',
142
+ init: { sessionId: 'abc123', config: { temperature: 0.7 } },
143
+ });
144
+
145
+ // Also works with streaming
146
+ const streamed = streamFlow({
147
+ url: `http://localhost:${port}/myFlow`,
148
+ input: 'say hello',
149
+ init: { sessionId: 'abc123' },
150
+ });
151
+ for await (const chunk of streamed.stream) {
152
+ console.log(chunk);
153
+ }
154
+ console.log(await streamed.output);
155
+ ```
156
+
157
+ 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.
158
+
134
159
  You can also use `startFlowServer` to quickly expose multiple flows and actions:
135
160
 
136
161
  ```ts
package/lib/index.d.mts CHANGED
@@ -23,7 +23,7 @@ import { ContextProvider } from 'genkit/context';
23
23
  /**
24
24
  * Exposes provided flow or an action as express handler.
25
25
  */
26
- declare function expressHandler<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>, opts?: {
26
+ declare function expressHandler<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>, opts?: {
27
27
  contextProvider?: ContextProvider<C, I>;
28
28
  streamManager?: StreamManager;
29
29
  }): express.RequestHandler;
package/lib/index.d.ts CHANGED
@@ -23,7 +23,7 @@ import { ContextProvider } from 'genkit/context';
23
23
  /**
24
24
  * Exposes provided flow or an action as express handler.
25
25
  */
26
- declare function expressHandler<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>, opts?: {
26
+ declare function expressHandler<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>, opts?: {
27
27
  contextProvider?: ContextProvider<C, I>;
28
28
  streamManager?: StreamManager;
29
29
  }): express.RequestHandler;
package/lib/index.js CHANGED
@@ -55,6 +55,7 @@ function expressHandler(action, opts) {
55
55
  return;
56
56
  }
57
57
  const input = request.body.data;
58
+ const init = request.body.init;
58
59
  let context;
59
60
  try {
60
61
  context = await opts?.contextProvider?.({
@@ -103,6 +104,7 @@ ${e.stack}`
103
104
  streamManager,
104
105
  streamIdToUse,
105
106
  input,
107
+ init,
106
108
  context,
107
109
  response,
108
110
  abortController.signal
@@ -111,7 +113,8 @@ ${e.stack}`
111
113
  try {
112
114
  const result = await action.run(input, {
113
115
  context,
114
- abortSignal: abortController.signal
116
+ abortSignal: abortController.signal,
117
+ init
115
118
  });
116
119
  response.setHeader("x-genkit-trace-id", result.telemetry.traceId);
117
120
  response.setHeader("x-genkit-span-id", result.telemetry.spanId);
@@ -128,7 +131,7 @@ ${e.stack}`
128
131
  }
129
132
  };
130
133
  }
131
- async function runActionWithDurableStreaming(action, streamManager, streamId, input, context, response, abortSignal) {
134
+ async function runActionWithDurableStreaming(action, streamManager, streamId, input, init, context, response, abortSignal) {
132
135
  let taskQueue;
133
136
  let durableStream;
134
137
  if (streamManager) {
@@ -152,7 +155,8 @@ async function runActionWithDurableStreaming(action, streamManager, streamId, in
152
155
  const result = await action.run(input, {
153
156
  onChunk,
154
157
  context,
155
- abortSignal
158
+ abortSignal,
159
+ init
156
160
  });
157
161
  if (streamManager) {
158
162
  taskQueue.enqueue(() => durableStream.done(result.result));
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2024 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 bodyParser from 'body-parser';\nimport cors, { type CorsOptions } from 'cors';\nimport { randomUUID } from 'crypto';\nimport express from 'express';\nimport {\n Action,\n ActionStreamInput,\n AsyncTaskQueue,\n Flow,\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 type { Server } from 'http';\n\nconst streamDelimiter = '\\n\\n';\n\n/**\n * Exposes provided flow or an action as express handler.\n */\nexport function expressHandler<\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 opts?: {\n contextProvider?: ContextProvider<C, I>;\n streamManager?: StreamManager;\n }\n): express.RequestHandler {\n return async (\n request: express.Request,\n response: express.Response\n ): Promise<void> => {\n const { stream } = request.query;\n const streamIdHeader = request.headers['x-genkit-stream-id'];\n const streamId = Array.isArray(streamIdHeader)\n ? streamIdHeader[0]\n : streamIdHeader;\n\n if (!request.body) {\n const errMsg =\n `Error: request.body is undefined. ` +\n `Possible reasons: missing 'content-type: application/json' in request ` +\n `headers or misconfigured JSON middleware ('app.use(express.json()')? `;\n logger.error(errMsg);\n response\n .status(400)\n .json({ message: errMsg, status: 'INVALID ARGUMENT' })\n .end();\n return;\n }\n\n const input = request.body.data as z.infer<I>;\n let context: Record<string, any>;\n\n try {\n context =\n (await opts?.contextProvider?.({\n method: request.method as RequestData['method'],\n headers: Object.fromEntries(\n Object.entries(request.headers)\n // Skip headers explicitly set to undefined so they don't become\n // the literal string \"undefined\" via String(value).\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [\n key.toLowerCase(),\n // RFC 9110 5.3: combine repeated field lines with a comma.\n Array.isArray(value) ? value.join(', ') : String(value),\n ])\n ),\n input,\n })) || {};\n } catch (e: any) {\n logger.error(\n `Auth policy failed with error: ${(e as Error).message}\\n${(e as Error).stack}`\n );\n response.status(getHttpStatus(e)).json(getCallableJSON(e)).end();\n return;\n }\n\n const abortController = new AbortController();\n request.on('close', () => {\n abortController.abort();\n });\n // when/if using timeout middleware, it will emit 'timeout' event.\n request.on('timeout', () => {\n abortController.abort();\n });\n\n if (\n request.get('Accept')?.toLowerCase().includes('text/event-stream') ||\n stream === 'true'\n ) {\n const streamManager = opts?.streamManager;\n if (streamManager && streamId) {\n await subscribeToStream(streamManager, streamId, response);\n return;\n }\n const streamIdToUse = randomUUID();\n const headers = {\n 'Content-Type': 'text/plain',\n 'Transfer-Encoding': 'chunked',\n };\n if (streamManager) {\n headers['x-genkit-stream-id'] = streamIdToUse;\n }\n response.writeHead(200, headers);\n runActionWithDurableStreaming(\n action,\n streamManager,\n streamIdToUse,\n input,\n context,\n response,\n abortController.signal\n );\n } else {\n try {\n const result = await action.run(input, {\n context,\n abortSignal: abortController.signal,\n });\n response.setHeader('x-genkit-trace-id', result.telemetry.traceId);\n response.setHeader('x-genkit-span-id', result.telemetry.spanId);\n // Responses for non-streaming flows are passed back with the flow result stored in a field called \"result.\"\n response\n .status(200)\n .json({\n result: result.result,\n })\n .end();\n } catch (e) {\n // Errors for non-streaming flows are passed back as standard API errors.\n logger.error(\n `Non-streaming request failed with error: ${(e as Error).message}\\n${(e as Error).stack}`\n );\n response.status(getHttpStatus(e)).json(getCallableJSON(e)).end();\n }\n }\n };\n}\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 response: express.Response,\n abortSignal: AbortSignal\n) {\n let taskQueue: AsyncTaskQueue | undefined;\n let durableStream: ActionStreamInput<any, any> | undefined;\n if (streamManager) {\n taskQueue = new AsyncTaskQueue();\n durableStream = await streamManager.open(streamId);\n }\n try {\n let onChunk = (chunk: z.infer<S>) => {\n // The client may have disconnected mid-stream; writing to a destroyed\n // response would throw.\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n );\n };\n if (streamManager) {\n const originalOnChunk = onChunk;\n onChunk = (chunk: z.infer<S>) => {\n originalOnChunk(chunk);\n taskQueue!.enqueue(() => durableStream!.write(chunk));\n };\n }\n const result = await action.run(input, {\n onChunk,\n context,\n abortSignal,\n });\n if (streamManager) {\n taskQueue!.enqueue(() => durableStream!.done(result.result));\n await taskQueue!.merge();\n }\n if (!response.destroyed) {\n response.write(\n 'data: ' + JSON.stringify({ result: result.result }) + streamDelimiter\n );\n response.end();\n }\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 if (!response.destroyed) {\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n );\n response.end();\n }\n }\n}\n\nasync function subscribeToStream(\n streamManager: StreamManager,\n streamId: string,\n response: express.Response\n): Promise<void> {\n try {\n await streamManager.subscribe(streamId, {\n onChunk: (chunk) => {\n // The subscribing client may have disconnected; skip writes to a\n // destroyed response to avoid throwing.\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n );\n },\n onDone: (output) => {\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ result: output }) + streamDelimiter\n );\n response.end();\n },\n onError: (err) => {\n logger.error(\n `Streaming request failed with error: ${(err as Error).message}\\n${\n (err as Error).stack\n }`\n );\n if (response.destroyed) return;\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(err),\n })}${streamDelimiter}`\n );\n response.end();\n },\n });\n } catch (e: any) {\n // The subscribing client may have disconnected; skip writes to a\n // destroyed response to avoid throwing.\n if (response.destroyed) return;\n if (e instanceof StreamNotFoundError) {\n response.status(204).end();\n return;\n }\n if (e.status === 'DEADLINE_EXCEEDED') {\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n );\n response.end();\n return;\n }\n throw e;\n }\n}\n\n/**\n * A wrapper object containing a flow with its associated auth policy.\n * @deprecated Use `withFlowOptions` instead.\n */\nexport type FlowWithContextProvider<\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 flow: Flow<I, O, S>;\n context: ContextProvider<C, I>;\n};\n\n/**\n * A wrapper object containing a flow with its associated options.\n */\nexport type FlowWithOptions<\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n> = {\n flow: Flow<I, O, S>;\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n };\n};\n\n/**\n * Adds an auth policy to the flow.\n * @deprecated Use `withFlowOptions` instead.\n */\nexport function withContextProvider<\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 flow: Flow<I, O, S>,\n context: ContextProvider<C, I>\n): FlowWithContextProvider<C, I, O, S> {\n return {\n flow,\n context,\n };\n}\n\n/**\n * Adds an auth policy to the flow.\n */\nexport function withFlowOptions<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n flow: Flow<I, O, S>,\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n }\n): FlowWithOptions<I, O, S> {\n return {\n flow,\n options,\n };\n}\n\n/**\n * Options to configure the flow server.\n */\nexport interface FlowServerOptions {\n /** List of flows to expose via the flow server. */\n flows: (\n | Flow<any, any, any>\n | FlowWithContextProvider<any, any, any>\n | FlowWithOptions<any, any, any>\n )[];\n /** Port to run the server on. Defaults to env.PORT or 3400. */\n port?: number;\n /** CORS options for the server. */\n cors?: CorsOptions;\n /** HTTP method path prefix for the exposed flows. */\n pathPrefix?: string;\n /** JSON body parser options. */\n jsonParserOptions?: bodyParser.OptionsJson;\n}\n\n/**\n * Starts an express server with the provided flows and options.\n */\nexport function startFlowServer(options: FlowServerOptions): FlowServer {\n const server = new FlowServer(options);\n server.start();\n return server;\n}\n\n/**\n * Flow server exposes registered flows as HTTP endpoints.\n *\n * This is for use in production environments.\n *\n * @hidden\n */\nexport class FlowServer {\n /** List of all running servers needed to be cleaned up on process exit. */\n private static RUNNING_SERVERS: FlowServer[] = [];\n\n /** Options for the flow server configured by the developer. */\n private options: FlowServerOptions;\n /** Port the server is actually running on. This may differ from `options.port` if the original was occupied. Null is server is not running. */\n private port: number | null = null;\n /** Express server instance. Null if server is not running. */\n private server: Server | null = null;\n\n constructor(options: FlowServerOptions) {\n this.options = {\n ...options,\n };\n }\n\n /**\n * Starts the server and adds it to the list of running servers to clean up on exit.\n */\n async start() {\n const server = express();\n\n server.use(bodyParser.json(this.options.jsonParserOptions));\n server.use(cors(this.options.cors));\n\n logger.debug('Running flow server with flow paths:');\n const pathPrefix = this.options.pathPrefix ?? '';\n this.options.flows?.forEach((flow) => {\n if ('flow' in flow) {\n const flowPath = `/${pathPrefix}${\n ('options' in flow && flow.options.path) || flow.flow.__action.name\n }`;\n logger.debug(` - ${flowPath}`);\n const options =\n 'options' in flow ? flow.options : { contextProvider: flow.context };\n server.post(flowPath, expressHandler(flow.flow, options));\n } else {\n const flowPath = `/${pathPrefix}${flow.__action.name}`;\n logger.debug(` - ${flowPath}`);\n server.post(flowPath, expressHandler(flow));\n }\n });\n this.port =\n this.options?.port ||\n (process.env.PORT ? Number.parseInt(process.env.PORT) : 0) ||\n 3400;\n this.server = server.listen(this.port, () => {\n logger.debug(`Flow server running on http://localhost:${this.port}`);\n FlowServer.RUNNING_SERVERS.push(this);\n });\n }\n\n /**\n * Stops the server and removes it from the list of running servers to clean up on exit.\n */\n async stop(): Promise<void> {\n if (!this.server) {\n return;\n }\n return new Promise<void>((resolve, reject) => {\n this.server!.close((err) => {\n if (err) {\n logger.error(\n `Error shutting down flow server on port ${this.port}: ${err}`\n );\n reject(err);\n }\n const index = FlowServer.RUNNING_SERVERS.indexOf(this);\n if (index > -1) {\n FlowServer.RUNNING_SERVERS.splice(index, 1);\n }\n logger.debug(\n `Flow server on port ${this.port} has successfully shut down.`\n );\n this.port = null;\n this.server = null;\n resolve();\n });\n });\n }\n\n /**\n * Stops all running servers.\n */\n static async stopAll() {\n return Promise.all(\n FlowServer.RUNNING_SERVERS.map((server) => server.stop())\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,yBAAuB;AACvB,kBAAuC;AACvC,oBAA2B;AAC3B,qBAAoB;AACpB,kBASO;AACP,qBAKO;AACP,qBAAuB;AAGvB,MAAM,kBAAkB;AAKjB,SAAS,eAMd,QACA,MAIwB;AACxB,SAAO,OACL,SACA,aACkB;AAClB,UAAM,EAAE,OAAO,IAAI,QAAQ;AAC3B,UAAM,iBAAiB,QAAQ,QAAQ,oBAAoB;AAC3D,UAAM,WAAW,MAAM,QAAQ,cAAc,IACzC,eAAe,CAAC,IAChB;AAEJ,QAAI,CAAC,QAAQ,MAAM;AACjB,YAAM,SACJ;AAGF,4BAAO,MAAM,MAAM;AACnB,eACG,OAAO,GAAG,EACV,KAAK,EAAE,SAAS,QAAQ,QAAQ,mBAAmB,CAAC,EACpD,IAAI;AACP;AAAA,IACF;AAEA,UAAM,QAAQ,QAAQ,KAAK;AAC3B,QAAI;AAEJ,QAAI;AACF,gBACG,MAAM,MAAM,kBAAkB;AAAA,QAC7B,QAAQ,QAAQ;AAAA,QAChB,SAAS,OAAO;AAAA,UACd,OAAO,QAAQ,QAAQ,OAAO,EAG3B,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,UAAU,MAAS,EACzC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAAA,YACrB,IAAI,YAAY;AAAA;AAAA,YAEhB,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK;AAAA,UACxD,CAAC;AAAA,QACL;AAAA,QACA;AAAA,MACF,CAAC,KAAM,CAAC;AAAA,IACZ,SAAS,GAAQ;AACf,4BAAO;AAAA,QACL,kCAAmC,EAAY,OAAO;AAAA,EAAM,EAAY,KAAK;AAAA,MAC/E;AACA,eAAS,WAAO,8BAAc,CAAC,CAAC,EAAE,SAAK,gCAAgB,CAAC,CAAC,EAAE,IAAI;AAC/D;AAAA,IACF;AAEA,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,YAAQ,GAAG,SAAS,MAAM;AACxB,sBAAgB,MAAM;AAAA,IACxB,CAAC;AAED,YAAQ,GAAG,WAAW,MAAM;AAC1B,sBAAgB,MAAM;AAAA,IACxB,CAAC;AAED,QACE,QAAQ,IAAI,QAAQ,GAAG,YAAY,EAAE,SAAS,mBAAmB,KACjE,WAAW,QACX;AACA,YAAM,gBAAgB,MAAM;AAC5B,UAAI,iBAAiB,UAAU;AAC7B,cAAM,kBAAkB,eAAe,UAAU,QAAQ;AACzD;AAAA,MACF;AACA,YAAM,oBAAgB,0BAAW;AACjC,YAAM,UAAU;AAAA,QACd,gBAAgB;AAAA,QAChB,qBAAqB;AAAA,MACvB;AACA,UAAI,eAAe;AACjB,gBAAQ,oBAAoB,IAAI;AAAA,MAClC;AACA,eAAS,UAAU,KAAK,OAAO;AAC/B;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,MAClB;AAAA,IACF,OAAO;AACL,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,IAAI,OAAO;AAAA,UACrC;AAAA,UACA,aAAa,gBAAgB;AAAA,QAC/B,CAAC;AACD,iBAAS,UAAU,qBAAqB,OAAO,UAAU,OAAO;AAChE,iBAAS,UAAU,oBAAoB,OAAO,UAAU,MAAM;AAE9D,iBACG,OAAO,GAAG,EACV,KAAK;AAAA,UACJ,QAAQ,OAAO;AAAA,QACjB,CAAC,EACA,IAAI;AAAA,MACT,SAAS,GAAG;AAEV,8BAAO;AAAA,UACL,4CAA6C,EAAY,OAAO;AAAA,EAAM,EAAY,KAAK;AAAA,QACzF;AACA,iBAAS,WAAO,8BAAc,CAAC,CAAC,EAAE,SAAK,gCAAgB,CAAC,CAAC,EAAE,IAAI;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,8BAKb,QACA,eACA,UACA,OACA,SACA,UACA,aACA;AACA,MAAI;AACJ,MAAI;AACJ,MAAI,eAAe;AACjB,gBAAY,IAAI,2BAAe;AAC/B,oBAAgB,MAAM,cAAc,KAAK,QAAQ;AAAA,EACnD;AACA,MAAI;AACF,QAAI,UAAU,CAAC,UAAsB;AAGnC,UAAI,SAAS,UAAW;AACxB,eAAS;AAAA,QACP,WAAW,KAAK,UAAU,EAAE,SAAS,MAAM,CAAC,IAAI;AAAA,MAClD;AAAA,IACF;AACA,QAAI,eAAe;AACjB,YAAM,kBAAkB;AACxB,gBAAU,CAAC,UAAsB;AAC/B,wBAAgB,KAAK;AACrB,kBAAW,QAAQ,MAAM,cAAe,MAAM,KAAK,CAAC;AAAA,MACtD;AAAA,IACF;AACA,UAAM,SAAS,MAAM,OAAO,IAAI,OAAO;AAAA,MACrC;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,eAAe;AACjB,gBAAW,QAAQ,MAAM,cAAe,KAAK,OAAO,MAAM,CAAC;AAC3D,YAAM,UAAW,MAAM;AAAA,IACzB;AACA,QAAI,CAAC,SAAS,WAAW;AACvB,eAAS;AAAA,QACP,WAAW,KAAK,UAAU,EAAE,QAAQ,OAAO,OAAO,CAAC,IAAI;AAAA,MACzD;AACA,eAAS,IAAI;AAAA,IACf;AAAA,EACF,SAAS,GAAG;AACV,QAAI,eAAe;AACjB,gBAAW,QAAQ,MAAM,cAAe,MAAM,CAAC,CAAC;AAChD,YAAM,UAAW,MAAM;AAAA,IACzB;AACA,0BAAO;AAAA,MACL,wCAAyC,EAAY,OAAO;AAAA,EACzD,EAAY,KACf;AAAA,IACF;AACA,QAAI,CAAC,SAAS,WAAW;AACvB,eAAS;AAAA,QACP,UAAU,KAAK,UAAU;AAAA,UACvB,WAAO,gCAAgB,CAAC;AAAA,QAC1B,CAAC,CAAC,GAAG,eAAe;AAAA,MACtB;AACA,eAAS,IAAI;AAAA,IACf;AAAA,EACF;AACF;AAEA,eAAe,kBACb,eACA,UACA,UACe;AACf,MAAI;AACF,UAAM,cAAc,UAAU,UAAU;AAAA,MACtC,SAAS,CAAC,UAAU;AAGlB,YAAI,SAAS,UAAW;AACxB,iBAAS;AAAA,UACP,WAAW,KAAK,UAAU,EAAE,SAAS,MAAM,CAAC,IAAI;AAAA,QAClD;AAAA,MACF;AAAA,MACA,QAAQ,CAAC,WAAW;AAClB,YAAI,SAAS,UAAW;AACxB,iBAAS;AAAA,UACP,WAAW,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC,IAAI;AAAA,QAClD;AACA,iBAAS,IAAI;AAAA,MACf;AAAA,MACA,SAAS,CAAC,QAAQ;AAChB,8BAAO;AAAA,UACL,wCAAyC,IAAc,OAAO;AAAA,EAC3D,IAAc,KACjB;AAAA,QACF;AACA,YAAI,SAAS,UAAW;AACxB,iBAAS;AAAA,UACP,UAAU,KAAK,UAAU;AAAA,YACvB,WAAO,gCAAgB,GAAG;AAAA,UAC5B,CAAC,CAAC,GAAG,eAAe;AAAA,QACtB;AACA,iBAAS,IAAI;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH,SAAS,GAAQ;AAGf,QAAI,SAAS,UAAW;AACxB,QAAI,aAAa,iCAAqB;AACpC,eAAS,OAAO,GAAG,EAAE,IAAI;AACzB;AAAA,IACF;AACA,QAAI,EAAE,WAAW,qBAAqB;AACpC,eAAS;AAAA,QACP,UAAU,KAAK,UAAU;AAAA,UACvB,WAAO,gCAAgB,CAAC;AAAA,QAC1B,CAAC,CAAC,GAAG,eAAe;AAAA,MACtB;AACA,eAAS,IAAI;AACb;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;AAoCO,SAAS,oBAMd,MACA,SACqC;AACrC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAKO,SAAS,gBAKd,MACA,SAK0B;AAC1B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAyBO,SAAS,gBAAgB,SAAwC;AACtE,QAAM,SAAS,IAAI,WAAW,OAAO;AACrC,SAAO,MAAM;AACb,SAAO;AACT;AASO,MAAM,WAAW;AAAA;AAAA,EAEtB,OAAe,kBAAgC,CAAC;AAAA;AAAA,EAGxC;AAAA;AAAA,EAEA,OAAsB;AAAA;AAAA,EAEtB,SAAwB;AAAA,EAEhC,YAAY,SAA4B;AACtC,SAAK,UAAU;AAAA,MACb,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ;AACZ,UAAM,aAAS,eAAAA,SAAQ;AAEvB,WAAO,IAAI,mBAAAC,QAAW,KAAK,KAAK,QAAQ,iBAAiB,CAAC;AAC1D,WAAO,QAAI,YAAAC,SAAK,KAAK,QAAQ,IAAI,CAAC;AAElC,0BAAO,MAAM,sCAAsC;AACnD,UAAM,aAAa,KAAK,QAAQ,cAAc;AAC9C,SAAK,QAAQ,OAAO,QAAQ,CAAC,SAAS;AACpC,UAAI,UAAU,MAAM;AAClB,cAAM,WAAW,IAAI,UAAU,GAC5B,aAAa,QAAQ,KAAK,QAAQ,QAAS,KAAK,KAAK,SAAS,IACjE;AACA,8BAAO,MAAM,MAAM,QAAQ,EAAE;AAC7B,cAAM,UACJ,aAAa,OAAO,KAAK,UAAU,EAAE,iBAAiB,KAAK,QAAQ;AACrE,eAAO,KAAK,UAAU,eAAe,KAAK,MAAM,OAAO,CAAC;AAAA,MAC1D,OAAO;AACL,cAAM,WAAW,IAAI,UAAU,GAAG,KAAK,SAAS,IAAI;AACpD,8BAAO,MAAM,MAAM,QAAQ,EAAE;AAC7B,eAAO,KAAK,UAAU,eAAe,IAAI,CAAC;AAAA,MAC5C;AAAA,IACF,CAAC;AACD,SAAK,OACH,KAAK,SAAS,SACb,QAAQ,IAAI,OAAO,OAAO,SAAS,QAAQ,IAAI,IAAI,IAAI,MACxD;AACF,SAAK,SAAS,OAAO,OAAO,KAAK,MAAM,MAAM;AAC3C,4BAAO,MAAM,2CAA2C,KAAK,IAAI,EAAE;AACnE,iBAAW,gBAAgB,KAAK,IAAI;AAAA,IACtC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AACA,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAK,OAAQ,MAAM,CAAC,QAAQ;AAC1B,YAAI,KAAK;AACP,gCAAO;AAAA,YACL,2CAA2C,KAAK,IAAI,KAAK,GAAG;AAAA,UAC9D;AACA,iBAAO,GAAG;AAAA,QACZ;AACA,cAAM,QAAQ,WAAW,gBAAgB,QAAQ,IAAI;AACrD,YAAI,QAAQ,IAAI;AACd,qBAAW,gBAAgB,OAAO,OAAO,CAAC;AAAA,QAC5C;AACA,8BAAO;AAAA,UACL,uBAAuB,KAAK,IAAI;AAAA,QAClC;AACA,aAAK,OAAO;AACZ,aAAK,SAAS;AACd,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,UAAU;AACrB,WAAO,QAAQ;AAAA,MACb,WAAW,gBAAgB,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;","names":["express","bodyParser","cors"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2024 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 bodyParser from 'body-parser';\nimport cors, { type CorsOptions } from 'cors';\nimport { randomUUID } from 'crypto';\nimport express from 'express';\nimport {\n Action,\n ActionStreamInput,\n AsyncTaskQueue,\n Flow,\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 type { Server } from 'http';\n\nconst streamDelimiter = '\\n\\n';\n\n/**\n * Exposes provided flow or an action as express handler.\n */\nexport function expressHandler<\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 opts?: {\n contextProvider?: ContextProvider<C, I>;\n streamManager?: StreamManager;\n }\n): express.RequestHandler {\n return async (\n request: express.Request,\n response: express.Response\n ): Promise<void> => {\n const { stream } = request.query;\n const streamIdHeader = request.headers['x-genkit-stream-id'];\n const streamId = Array.isArray(streamIdHeader)\n ? streamIdHeader[0]\n : streamIdHeader;\n\n if (!request.body) {\n const errMsg =\n `Error: request.body is undefined. ` +\n `Possible reasons: missing 'content-type: application/json' in request ` +\n `headers or misconfigured JSON middleware ('app.use(express.json()')? `;\n logger.error(errMsg);\n response\n .status(400)\n .json({ message: errMsg, status: 'INVALID ARGUMENT' })\n .end();\n return;\n }\n\n const input = request.body.data as z.infer<I>;\n const init = request.body.init as z.infer<Init> | undefined;\n let context: Record<string, any>;\n\n try {\n context =\n (await opts?.contextProvider?.({\n method: request.method as RequestData['method'],\n headers: Object.fromEntries(\n Object.entries(request.headers)\n // Skip headers explicitly set to undefined so they don't become\n // the literal string \"undefined\" via String(value).\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [\n key.toLowerCase(),\n // RFC 9110 5.3: combine repeated field lines with a comma.\n Array.isArray(value) ? value.join(', ') : String(value),\n ])\n ),\n input,\n })) || {};\n } catch (e: any) {\n logger.error(\n `Auth policy failed with error: ${(e as Error).message}\\n${(e as Error).stack}`\n );\n response.status(getHttpStatus(e)).json(getCallableJSON(e)).end();\n return;\n }\n\n const abortController = new AbortController();\n request.on('close', () => {\n abortController.abort();\n });\n // when/if using timeout middleware, it will emit 'timeout' event.\n request.on('timeout', () => {\n abortController.abort();\n });\n\n if (\n request.get('Accept')?.toLowerCase().includes('text/event-stream') ||\n stream === 'true'\n ) {\n const streamManager = opts?.streamManager;\n if (streamManager && streamId) {\n await subscribeToStream(streamManager, streamId, response);\n return;\n }\n const streamIdToUse = randomUUID();\n const headers = {\n 'Content-Type': 'text/plain',\n 'Transfer-Encoding': 'chunked',\n };\n if (streamManager) {\n headers['x-genkit-stream-id'] = streamIdToUse;\n }\n response.writeHead(200, headers);\n runActionWithDurableStreaming(\n action,\n streamManager,\n streamIdToUse,\n input,\n init,\n context,\n response,\n abortController.signal\n );\n } else {\n try {\n const result = await action.run(input, {\n context,\n abortSignal: abortController.signal,\n init,\n });\n response.setHeader('x-genkit-trace-id', result.telemetry.traceId);\n response.setHeader('x-genkit-span-id', result.telemetry.spanId);\n // Responses for non-streaming flows are passed back with the flow result stored in a field called \"result.\"\n response\n .status(200)\n .json({\n result: result.result,\n })\n .end();\n } catch (e) {\n // Errors for non-streaming flows are passed back as standard API errors.\n logger.error(\n `Non-streaming request failed with error: ${(e as Error).message}\\n${(e as Error).stack}`\n );\n response.status(getHttpStatus(e)).json(getCallableJSON(e)).end();\n }\n }\n };\n}\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 response: express.Response,\n abortSignal: AbortSignal\n) {\n let taskQueue: AsyncTaskQueue | undefined;\n let durableStream: ActionStreamInput<any, any> | undefined;\n if (streamManager) {\n taskQueue = new AsyncTaskQueue();\n durableStream = await streamManager.open(streamId);\n }\n try {\n let onChunk = (chunk: z.infer<S>) => {\n // The client may have disconnected mid-stream; writing to a destroyed\n // response would throw.\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n );\n };\n if (streamManager) {\n const originalOnChunk = onChunk;\n onChunk = (chunk: z.infer<S>) => {\n originalOnChunk(chunk);\n taskQueue!.enqueue(() => durableStream!.write(chunk));\n };\n }\n const result = await action.run(input, {\n onChunk,\n context,\n abortSignal,\n init,\n });\n if (streamManager) {\n taskQueue!.enqueue(() => durableStream!.done(result.result));\n await taskQueue!.merge();\n }\n if (!response.destroyed) {\n response.write(\n 'data: ' + JSON.stringify({ result: result.result }) + streamDelimiter\n );\n response.end();\n }\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 if (!response.destroyed) {\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n );\n response.end();\n }\n }\n}\n\nasync function subscribeToStream(\n streamManager: StreamManager,\n streamId: string,\n response: express.Response\n): Promise<void> {\n try {\n await streamManager.subscribe(streamId, {\n onChunk: (chunk) => {\n // The subscribing client may have disconnected; skip writes to a\n // destroyed response to avoid throwing.\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n );\n },\n onDone: (output) => {\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ result: output }) + streamDelimiter\n );\n response.end();\n },\n onError: (err) => {\n logger.error(\n `Streaming request failed with error: ${(err as Error).message}\\n${\n (err as Error).stack\n }`\n );\n if (response.destroyed) return;\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(err),\n })}${streamDelimiter}`\n );\n response.end();\n },\n });\n } catch (e: any) {\n // The subscribing client may have disconnected; skip writes to a\n // destroyed response to avoid throwing.\n if (response.destroyed) return;\n if (e instanceof StreamNotFoundError) {\n response.status(204).end();\n return;\n }\n if (e.status === 'DEADLINE_EXCEEDED') {\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n );\n response.end();\n return;\n }\n throw e;\n }\n}\n\n/**\n * A wrapper object containing a flow with its associated auth policy.\n * @deprecated Use `withFlowOptions` instead.\n */\nexport type FlowWithContextProvider<\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 flow: Flow<I, O, S>;\n context: ContextProvider<C, I>;\n};\n\n/**\n * A wrapper object containing a flow with its associated options.\n */\nexport type FlowWithOptions<\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n> = {\n flow: Flow<I, O, S>;\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n };\n};\n\n/**\n * Adds an auth policy to the flow.\n * @deprecated Use `withFlowOptions` instead.\n */\nexport function withContextProvider<\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 flow: Flow<I, O, S>,\n context: ContextProvider<C, I>\n): FlowWithContextProvider<C, I, O, S> {\n return {\n flow,\n context,\n };\n}\n\n/**\n * Adds an auth policy to the flow.\n */\nexport function withFlowOptions<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n flow: Flow<I, O, S>,\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n }\n): FlowWithOptions<I, O, S> {\n return {\n flow,\n options,\n };\n}\n\n/**\n * Options to configure the flow server.\n */\nexport interface FlowServerOptions {\n /** List of flows to expose via the flow server. */\n flows: (\n | Flow<any, any, any>\n | FlowWithContextProvider<any, any, any>\n | FlowWithOptions<any, any, any>\n )[];\n /** Port to run the server on. Defaults to env.PORT or 3400. */\n port?: number;\n /** CORS options for the server. */\n cors?: CorsOptions;\n /** HTTP method path prefix for the exposed flows. */\n pathPrefix?: string;\n /** JSON body parser options. */\n jsonParserOptions?: bodyParser.OptionsJson;\n}\n\n/**\n * Starts an express server with the provided flows and options.\n */\nexport function startFlowServer(options: FlowServerOptions): FlowServer {\n const server = new FlowServer(options);\n server.start();\n return server;\n}\n\n/**\n * Flow server exposes registered flows as HTTP endpoints.\n *\n * This is for use in production environments.\n *\n * @hidden\n */\nexport class FlowServer {\n /** List of all running servers needed to be cleaned up on process exit. */\n private static RUNNING_SERVERS: FlowServer[] = [];\n\n /** Options for the flow server configured by the developer. */\n private options: FlowServerOptions;\n /** Port the server is actually running on. This may differ from `options.port` if the original was occupied. Null is server is not running. */\n private port: number | null = null;\n /** Express server instance. Null if server is not running. */\n private server: Server | null = null;\n\n constructor(options: FlowServerOptions) {\n this.options = {\n ...options,\n };\n }\n\n /**\n * Starts the server and adds it to the list of running servers to clean up on exit.\n */\n async start() {\n const server = express();\n\n server.use(bodyParser.json(this.options.jsonParserOptions));\n server.use(cors(this.options.cors));\n\n logger.debug('Running flow server with flow paths:');\n const pathPrefix = this.options.pathPrefix ?? '';\n this.options.flows?.forEach((flow) => {\n if ('flow' in flow) {\n const flowPath = `/${pathPrefix}${\n ('options' in flow && flow.options.path) || flow.flow.__action.name\n }`;\n logger.debug(` - ${flowPath}`);\n const options =\n 'options' in flow ? flow.options : { contextProvider: flow.context };\n server.post(flowPath, expressHandler(flow.flow, options));\n } else {\n const flowPath = `/${pathPrefix}${flow.__action.name}`;\n logger.debug(` - ${flowPath}`);\n server.post(flowPath, expressHandler(flow));\n }\n });\n this.port =\n this.options?.port ||\n (process.env.PORT ? Number.parseInt(process.env.PORT) : 0) ||\n 3400;\n this.server = server.listen(this.port, () => {\n logger.debug(`Flow server running on http://localhost:${this.port}`);\n FlowServer.RUNNING_SERVERS.push(this);\n });\n }\n\n /**\n * Stops the server and removes it from the list of running servers to clean up on exit.\n */\n async stop(): Promise<void> {\n if (!this.server) {\n return;\n }\n return new Promise<void>((resolve, reject) => {\n this.server!.close((err) => {\n if (err) {\n logger.error(\n `Error shutting down flow server on port ${this.port}: ${err}`\n );\n reject(err);\n }\n const index = FlowServer.RUNNING_SERVERS.indexOf(this);\n if (index > -1) {\n FlowServer.RUNNING_SERVERS.splice(index, 1);\n }\n logger.debug(\n `Flow server on port ${this.port} has successfully shut down.`\n );\n this.port = null;\n this.server = null;\n resolve();\n });\n });\n }\n\n /**\n * Stops all running servers.\n */\n static async stopAll() {\n return Promise.all(\n FlowServer.RUNNING_SERVERS.map((server) => server.stop())\n );\n }\n}\n"],"mappings":"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","names":["express","bodyParser","cors"]}
package/lib/index.mjs CHANGED
@@ -24,6 +24,7 @@ function expressHandler(action, opts) {
24
24
  return;
25
25
  }
26
26
  const input = request.body.data;
27
+ const init = request.body.init;
27
28
  let context;
28
29
  try {
29
30
  context = await opts?.contextProvider?.({
@@ -72,6 +73,7 @@ ${e.stack}`
72
73
  streamManager,
73
74
  streamIdToUse,
74
75
  input,
76
+ init,
75
77
  context,
76
78
  response,
77
79
  abortController.signal
@@ -80,7 +82,8 @@ ${e.stack}`
80
82
  try {
81
83
  const result = await action.run(input, {
82
84
  context,
83
- abortSignal: abortController.signal
85
+ abortSignal: abortController.signal,
86
+ init
84
87
  });
85
88
  response.setHeader("x-genkit-trace-id", result.telemetry.traceId);
86
89
  response.setHeader("x-genkit-span-id", result.telemetry.spanId);
@@ -97,7 +100,7 @@ ${e.stack}`
97
100
  }
98
101
  };
99
102
  }
100
- async function runActionWithDurableStreaming(action, streamManager, streamId, input, context, response, abortSignal) {
103
+ async function runActionWithDurableStreaming(action, streamManager, streamId, input, init, context, response, abortSignal) {
101
104
  let taskQueue;
102
105
  let durableStream;
103
106
  if (streamManager) {
@@ -121,7 +124,8 @@ async function runActionWithDurableStreaming(action, streamManager, streamId, in
121
124
  const result = await action.run(input, {
122
125
  onChunk,
123
126
  context,
124
- abortSignal
127
+ abortSignal,
128
+ init
125
129
  });
126
130
  if (streamManager) {
127
131
  taskQueue.enqueue(() => durableStream.done(result.result));
package/lib/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2024 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 bodyParser from 'body-parser';\nimport cors, { type CorsOptions } from 'cors';\nimport { randomUUID } from 'crypto';\nimport express from 'express';\nimport {\n Action,\n ActionStreamInput,\n AsyncTaskQueue,\n Flow,\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 type { Server } from 'http';\n\nconst streamDelimiter = '\\n\\n';\n\n/**\n * Exposes provided flow or an action as express handler.\n */\nexport function expressHandler<\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 opts?: {\n contextProvider?: ContextProvider<C, I>;\n streamManager?: StreamManager;\n }\n): express.RequestHandler {\n return async (\n request: express.Request,\n response: express.Response\n ): Promise<void> => {\n const { stream } = request.query;\n const streamIdHeader = request.headers['x-genkit-stream-id'];\n const streamId = Array.isArray(streamIdHeader)\n ? streamIdHeader[0]\n : streamIdHeader;\n\n if (!request.body) {\n const errMsg =\n `Error: request.body is undefined. ` +\n `Possible reasons: missing 'content-type: application/json' in request ` +\n `headers or misconfigured JSON middleware ('app.use(express.json()')? `;\n logger.error(errMsg);\n response\n .status(400)\n .json({ message: errMsg, status: 'INVALID ARGUMENT' })\n .end();\n return;\n }\n\n const input = request.body.data as z.infer<I>;\n let context: Record<string, any>;\n\n try {\n context =\n (await opts?.contextProvider?.({\n method: request.method as RequestData['method'],\n headers: Object.fromEntries(\n Object.entries(request.headers)\n // Skip headers explicitly set to undefined so they don't become\n // the literal string \"undefined\" via String(value).\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [\n key.toLowerCase(),\n // RFC 9110 5.3: combine repeated field lines with a comma.\n Array.isArray(value) ? value.join(', ') : String(value),\n ])\n ),\n input,\n })) || {};\n } catch (e: any) {\n logger.error(\n `Auth policy failed with error: ${(e as Error).message}\\n${(e as Error).stack}`\n );\n response.status(getHttpStatus(e)).json(getCallableJSON(e)).end();\n return;\n }\n\n const abortController = new AbortController();\n request.on('close', () => {\n abortController.abort();\n });\n // when/if using timeout middleware, it will emit 'timeout' event.\n request.on('timeout', () => {\n abortController.abort();\n });\n\n if (\n request.get('Accept')?.toLowerCase().includes('text/event-stream') ||\n stream === 'true'\n ) {\n const streamManager = opts?.streamManager;\n if (streamManager && streamId) {\n await subscribeToStream(streamManager, streamId, response);\n return;\n }\n const streamIdToUse = randomUUID();\n const headers = {\n 'Content-Type': 'text/plain',\n 'Transfer-Encoding': 'chunked',\n };\n if (streamManager) {\n headers['x-genkit-stream-id'] = streamIdToUse;\n }\n response.writeHead(200, headers);\n runActionWithDurableStreaming(\n action,\n streamManager,\n streamIdToUse,\n input,\n context,\n response,\n abortController.signal\n );\n } else {\n try {\n const result = await action.run(input, {\n context,\n abortSignal: abortController.signal,\n });\n response.setHeader('x-genkit-trace-id', result.telemetry.traceId);\n response.setHeader('x-genkit-span-id', result.telemetry.spanId);\n // Responses for non-streaming flows are passed back with the flow result stored in a field called \"result.\"\n response\n .status(200)\n .json({\n result: result.result,\n })\n .end();\n } catch (e) {\n // Errors for non-streaming flows are passed back as standard API errors.\n logger.error(\n `Non-streaming request failed with error: ${(e as Error).message}\\n${(e as Error).stack}`\n );\n response.status(getHttpStatus(e)).json(getCallableJSON(e)).end();\n }\n }\n };\n}\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 response: express.Response,\n abortSignal: AbortSignal\n) {\n let taskQueue: AsyncTaskQueue | undefined;\n let durableStream: ActionStreamInput<any, any> | undefined;\n if (streamManager) {\n taskQueue = new AsyncTaskQueue();\n durableStream = await streamManager.open(streamId);\n }\n try {\n let onChunk = (chunk: z.infer<S>) => {\n // The client may have disconnected mid-stream; writing to a destroyed\n // response would throw.\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n );\n };\n if (streamManager) {\n const originalOnChunk = onChunk;\n onChunk = (chunk: z.infer<S>) => {\n originalOnChunk(chunk);\n taskQueue!.enqueue(() => durableStream!.write(chunk));\n };\n }\n const result = await action.run(input, {\n onChunk,\n context,\n abortSignal,\n });\n if (streamManager) {\n taskQueue!.enqueue(() => durableStream!.done(result.result));\n await taskQueue!.merge();\n }\n if (!response.destroyed) {\n response.write(\n 'data: ' + JSON.stringify({ result: result.result }) + streamDelimiter\n );\n response.end();\n }\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 if (!response.destroyed) {\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n );\n response.end();\n }\n }\n}\n\nasync function subscribeToStream(\n streamManager: StreamManager,\n streamId: string,\n response: express.Response\n): Promise<void> {\n try {\n await streamManager.subscribe(streamId, {\n onChunk: (chunk) => {\n // The subscribing client may have disconnected; skip writes to a\n // destroyed response to avoid throwing.\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n );\n },\n onDone: (output) => {\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ result: output }) + streamDelimiter\n );\n response.end();\n },\n onError: (err) => {\n logger.error(\n `Streaming request failed with error: ${(err as Error).message}\\n${\n (err as Error).stack\n }`\n );\n if (response.destroyed) return;\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(err),\n })}${streamDelimiter}`\n );\n response.end();\n },\n });\n } catch (e: any) {\n // The subscribing client may have disconnected; skip writes to a\n // destroyed response to avoid throwing.\n if (response.destroyed) return;\n if (e instanceof StreamNotFoundError) {\n response.status(204).end();\n return;\n }\n if (e.status === 'DEADLINE_EXCEEDED') {\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n );\n response.end();\n return;\n }\n throw e;\n }\n}\n\n/**\n * A wrapper object containing a flow with its associated auth policy.\n * @deprecated Use `withFlowOptions` instead.\n */\nexport type FlowWithContextProvider<\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 flow: Flow<I, O, S>;\n context: ContextProvider<C, I>;\n};\n\n/**\n * A wrapper object containing a flow with its associated options.\n */\nexport type FlowWithOptions<\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n> = {\n flow: Flow<I, O, S>;\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n };\n};\n\n/**\n * Adds an auth policy to the flow.\n * @deprecated Use `withFlowOptions` instead.\n */\nexport function withContextProvider<\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 flow: Flow<I, O, S>,\n context: ContextProvider<C, I>\n): FlowWithContextProvider<C, I, O, S> {\n return {\n flow,\n context,\n };\n}\n\n/**\n * Adds an auth policy to the flow.\n */\nexport function withFlowOptions<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n flow: Flow<I, O, S>,\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n }\n): FlowWithOptions<I, O, S> {\n return {\n flow,\n options,\n };\n}\n\n/**\n * Options to configure the flow server.\n */\nexport interface FlowServerOptions {\n /** List of flows to expose via the flow server. */\n flows: (\n | Flow<any, any, any>\n | FlowWithContextProvider<any, any, any>\n | FlowWithOptions<any, any, any>\n )[];\n /** Port to run the server on. Defaults to env.PORT or 3400. */\n port?: number;\n /** CORS options for the server. */\n cors?: CorsOptions;\n /** HTTP method path prefix for the exposed flows. */\n pathPrefix?: string;\n /** JSON body parser options. */\n jsonParserOptions?: bodyParser.OptionsJson;\n}\n\n/**\n * Starts an express server with the provided flows and options.\n */\nexport function startFlowServer(options: FlowServerOptions): FlowServer {\n const server = new FlowServer(options);\n server.start();\n return server;\n}\n\n/**\n * Flow server exposes registered flows as HTTP endpoints.\n *\n * This is for use in production environments.\n *\n * @hidden\n */\nexport class FlowServer {\n /** List of all running servers needed to be cleaned up on process exit. */\n private static RUNNING_SERVERS: FlowServer[] = [];\n\n /** Options for the flow server configured by the developer. */\n private options: FlowServerOptions;\n /** Port the server is actually running on. This may differ from `options.port` if the original was occupied. Null is server is not running. */\n private port: number | null = null;\n /** Express server instance. Null if server is not running. */\n private server: Server | null = null;\n\n constructor(options: FlowServerOptions) {\n this.options = {\n ...options,\n };\n }\n\n /**\n * Starts the server and adds it to the list of running servers to clean up on exit.\n */\n async start() {\n const server = express();\n\n server.use(bodyParser.json(this.options.jsonParserOptions));\n server.use(cors(this.options.cors));\n\n logger.debug('Running flow server with flow paths:');\n const pathPrefix = this.options.pathPrefix ?? '';\n this.options.flows?.forEach((flow) => {\n if ('flow' in flow) {\n const flowPath = `/${pathPrefix}${\n ('options' in flow && flow.options.path) || flow.flow.__action.name\n }`;\n logger.debug(` - ${flowPath}`);\n const options =\n 'options' in flow ? flow.options : { contextProvider: flow.context };\n server.post(flowPath, expressHandler(flow.flow, options));\n } else {\n const flowPath = `/${pathPrefix}${flow.__action.name}`;\n logger.debug(` - ${flowPath}`);\n server.post(flowPath, expressHandler(flow));\n }\n });\n this.port =\n this.options?.port ||\n (process.env.PORT ? Number.parseInt(process.env.PORT) : 0) ||\n 3400;\n this.server = server.listen(this.port, () => {\n logger.debug(`Flow server running on http://localhost:${this.port}`);\n FlowServer.RUNNING_SERVERS.push(this);\n });\n }\n\n /**\n * Stops the server and removes it from the list of running servers to clean up on exit.\n */\n async stop(): Promise<void> {\n if (!this.server) {\n return;\n }\n return new Promise<void>((resolve, reject) => {\n this.server!.close((err) => {\n if (err) {\n logger.error(\n `Error shutting down flow server on port ${this.port}: ${err}`\n );\n reject(err);\n }\n const index = FlowServer.RUNNING_SERVERS.indexOf(this);\n if (index > -1) {\n FlowServer.RUNNING_SERVERS.splice(index, 1);\n }\n logger.debug(\n `Flow server on port ${this.port} has successfully shut down.`\n );\n this.port = null;\n this.server = null;\n resolve();\n });\n });\n }\n\n /**\n * Stops all running servers.\n */\n static async stopAll() {\n return Promise.all(\n FlowServer.RUNNING_SERVERS.map((server) => server.stop())\n );\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2024 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 bodyParser from 'body-parser';\nimport cors, { type CorsOptions } from 'cors';\nimport { randomUUID } from 'crypto';\nimport express from 'express';\nimport {\n Action,\n ActionStreamInput,\n AsyncTaskQueue,\n Flow,\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 type { Server } from 'http';\n\nconst streamDelimiter = '\\n\\n';\n\n/**\n * Exposes provided flow or an action as express handler.\n */\nexport function expressHandler<\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 opts?: {\n contextProvider?: ContextProvider<C, I>;\n streamManager?: StreamManager;\n }\n): express.RequestHandler {\n return async (\n request: express.Request,\n response: express.Response\n ): Promise<void> => {\n const { stream } = request.query;\n const streamIdHeader = request.headers['x-genkit-stream-id'];\n const streamId = Array.isArray(streamIdHeader)\n ? streamIdHeader[0]\n : streamIdHeader;\n\n if (!request.body) {\n const errMsg =\n `Error: request.body is undefined. ` +\n `Possible reasons: missing 'content-type: application/json' in request ` +\n `headers or misconfigured JSON middleware ('app.use(express.json()')? `;\n logger.error(errMsg);\n response\n .status(400)\n .json({ message: errMsg, status: 'INVALID ARGUMENT' })\n .end();\n return;\n }\n\n const input = request.body.data as z.infer<I>;\n const init = request.body.init as z.infer<Init> | undefined;\n let context: Record<string, any>;\n\n try {\n context =\n (await opts?.contextProvider?.({\n method: request.method as RequestData['method'],\n headers: Object.fromEntries(\n Object.entries(request.headers)\n // Skip headers explicitly set to undefined so they don't become\n // the literal string \"undefined\" via String(value).\n .filter(([, value]) => value !== undefined)\n .map(([key, value]) => [\n key.toLowerCase(),\n // RFC 9110 5.3: combine repeated field lines with a comma.\n Array.isArray(value) ? value.join(', ') : String(value),\n ])\n ),\n input,\n })) || {};\n } catch (e: any) {\n logger.error(\n `Auth policy failed with error: ${(e as Error).message}\\n${(e as Error).stack}`\n );\n response.status(getHttpStatus(e)).json(getCallableJSON(e)).end();\n return;\n }\n\n const abortController = new AbortController();\n request.on('close', () => {\n abortController.abort();\n });\n // when/if using timeout middleware, it will emit 'timeout' event.\n request.on('timeout', () => {\n abortController.abort();\n });\n\n if (\n request.get('Accept')?.toLowerCase().includes('text/event-stream') ||\n stream === 'true'\n ) {\n const streamManager = opts?.streamManager;\n if (streamManager && streamId) {\n await subscribeToStream(streamManager, streamId, response);\n return;\n }\n const streamIdToUse = randomUUID();\n const headers = {\n 'Content-Type': 'text/plain',\n 'Transfer-Encoding': 'chunked',\n };\n if (streamManager) {\n headers['x-genkit-stream-id'] = streamIdToUse;\n }\n response.writeHead(200, headers);\n runActionWithDurableStreaming(\n action,\n streamManager,\n streamIdToUse,\n input,\n init,\n context,\n response,\n abortController.signal\n );\n } else {\n try {\n const result = await action.run(input, {\n context,\n abortSignal: abortController.signal,\n init,\n });\n response.setHeader('x-genkit-trace-id', result.telemetry.traceId);\n response.setHeader('x-genkit-span-id', result.telemetry.spanId);\n // Responses for non-streaming flows are passed back with the flow result stored in a field called \"result.\"\n response\n .status(200)\n .json({\n result: result.result,\n })\n .end();\n } catch (e) {\n // Errors for non-streaming flows are passed back as standard API errors.\n logger.error(\n `Non-streaming request failed with error: ${(e as Error).message}\\n${(e as Error).stack}`\n );\n response.status(getHttpStatus(e)).json(getCallableJSON(e)).end();\n }\n }\n };\n}\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 response: express.Response,\n abortSignal: AbortSignal\n) {\n let taskQueue: AsyncTaskQueue | undefined;\n let durableStream: ActionStreamInput<any, any> | undefined;\n if (streamManager) {\n taskQueue = new AsyncTaskQueue();\n durableStream = await streamManager.open(streamId);\n }\n try {\n let onChunk = (chunk: z.infer<S>) => {\n // The client may have disconnected mid-stream; writing to a destroyed\n // response would throw.\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n );\n };\n if (streamManager) {\n const originalOnChunk = onChunk;\n onChunk = (chunk: z.infer<S>) => {\n originalOnChunk(chunk);\n taskQueue!.enqueue(() => durableStream!.write(chunk));\n };\n }\n const result = await action.run(input, {\n onChunk,\n context,\n abortSignal,\n init,\n });\n if (streamManager) {\n taskQueue!.enqueue(() => durableStream!.done(result.result));\n await taskQueue!.merge();\n }\n if (!response.destroyed) {\n response.write(\n 'data: ' + JSON.stringify({ result: result.result }) + streamDelimiter\n );\n response.end();\n }\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 if (!response.destroyed) {\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n );\n response.end();\n }\n }\n}\n\nasync function subscribeToStream(\n streamManager: StreamManager,\n streamId: string,\n response: express.Response\n): Promise<void> {\n try {\n await streamManager.subscribe(streamId, {\n onChunk: (chunk) => {\n // The subscribing client may have disconnected; skip writes to a\n // destroyed response to avoid throwing.\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ message: chunk }) + streamDelimiter\n );\n },\n onDone: (output) => {\n if (response.destroyed) return;\n response.write(\n 'data: ' + JSON.stringify({ result: output }) + streamDelimiter\n );\n response.end();\n },\n onError: (err) => {\n logger.error(\n `Streaming request failed with error: ${(err as Error).message}\\n${\n (err as Error).stack\n }`\n );\n if (response.destroyed) return;\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(err),\n })}${streamDelimiter}`\n );\n response.end();\n },\n });\n } catch (e: any) {\n // The subscribing client may have disconnected; skip writes to a\n // destroyed response to avoid throwing.\n if (response.destroyed) return;\n if (e instanceof StreamNotFoundError) {\n response.status(204).end();\n return;\n }\n if (e.status === 'DEADLINE_EXCEEDED') {\n response.write(\n `error: ${JSON.stringify({\n error: getCallableJSON(e),\n })}${streamDelimiter}`\n );\n response.end();\n return;\n }\n throw e;\n }\n}\n\n/**\n * A wrapper object containing a flow with its associated auth policy.\n * @deprecated Use `withFlowOptions` instead.\n */\nexport type FlowWithContextProvider<\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 flow: Flow<I, O, S>;\n context: ContextProvider<C, I>;\n};\n\n/**\n * A wrapper object containing a flow with its associated options.\n */\nexport type FlowWithOptions<\n I extends z.ZodTypeAny = z.ZodTypeAny,\n O extends z.ZodTypeAny = z.ZodTypeAny,\n S extends z.ZodTypeAny = z.ZodTypeAny,\n> = {\n flow: Flow<I, O, S>;\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n };\n};\n\n/**\n * Adds an auth policy to the flow.\n * @deprecated Use `withFlowOptions` instead.\n */\nexport function withContextProvider<\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 flow: Flow<I, O, S>,\n context: ContextProvider<C, I>\n): FlowWithContextProvider<C, I, O, S> {\n return {\n flow,\n context,\n };\n}\n\n/**\n * Adds an auth policy to the flow.\n */\nexport function withFlowOptions<\n I extends z.ZodTypeAny,\n O extends z.ZodTypeAny,\n S extends z.ZodTypeAny,\n>(\n flow: Flow<I, O, S>,\n options: {\n contextProvider?: ContextProvider<any, I>;\n streamManager?: StreamManager;\n path?: string;\n }\n): FlowWithOptions<I, O, S> {\n return {\n flow,\n options,\n };\n}\n\n/**\n * Options to configure the flow server.\n */\nexport interface FlowServerOptions {\n /** List of flows to expose via the flow server. */\n flows: (\n | Flow<any, any, any>\n | FlowWithContextProvider<any, any, any>\n | FlowWithOptions<any, any, any>\n )[];\n /** Port to run the server on. Defaults to env.PORT or 3400. */\n port?: number;\n /** CORS options for the server. */\n cors?: CorsOptions;\n /** HTTP method path prefix for the exposed flows. */\n pathPrefix?: string;\n /** JSON body parser options. */\n jsonParserOptions?: bodyParser.OptionsJson;\n}\n\n/**\n * Starts an express server with the provided flows and options.\n */\nexport function startFlowServer(options: FlowServerOptions): FlowServer {\n const server = new FlowServer(options);\n server.start();\n return server;\n}\n\n/**\n * Flow server exposes registered flows as HTTP endpoints.\n *\n * This is for use in production environments.\n *\n * @hidden\n */\nexport class FlowServer {\n /** List of all running servers needed to be cleaned up on process exit. */\n private static RUNNING_SERVERS: FlowServer[] = [];\n\n /** Options for the flow server configured by the developer. */\n private options: FlowServerOptions;\n /** Port the server is actually running on. This may differ from `options.port` if the original was occupied. Null is server is not running. */\n private port: number | null = null;\n /** Express server instance. Null if server is not running. */\n private server: Server | null = null;\n\n constructor(options: FlowServerOptions) {\n this.options = {\n ...options,\n };\n }\n\n /**\n * Starts the server and adds it to the list of running servers to clean up on exit.\n */\n async start() {\n const server = express();\n\n server.use(bodyParser.json(this.options.jsonParserOptions));\n server.use(cors(this.options.cors));\n\n logger.debug('Running flow server with flow paths:');\n const pathPrefix = this.options.pathPrefix ?? '';\n this.options.flows?.forEach((flow) => {\n if ('flow' in flow) {\n const flowPath = `/${pathPrefix}${\n ('options' in flow && flow.options.path) || flow.flow.__action.name\n }`;\n logger.debug(` - ${flowPath}`);\n const options =\n 'options' in flow ? flow.options : { contextProvider: flow.context };\n server.post(flowPath, expressHandler(flow.flow, options));\n } else {\n const flowPath = `/${pathPrefix}${flow.__action.name}`;\n logger.debug(` - ${flowPath}`);\n server.post(flowPath, expressHandler(flow));\n }\n });\n this.port =\n this.options?.port ||\n (process.env.PORT ? Number.parseInt(process.env.PORT) : 0) ||\n 3400;\n this.server = server.listen(this.port, () => {\n logger.debug(`Flow server running on http://localhost:${this.port}`);\n FlowServer.RUNNING_SERVERS.push(this);\n });\n }\n\n /**\n * Stops the server and removes it from the list of running servers to clean up on exit.\n */\n async stop(): Promise<void> {\n if (!this.server) {\n return;\n }\n return new Promise<void>((resolve, reject) => {\n this.server!.close((err) => {\n if (err) {\n logger.error(\n `Error shutting down flow server on port ${this.port}: ${err}`\n );\n reject(err);\n }\n const index = FlowServer.RUNNING_SERVERS.indexOf(this);\n if (index > -1) {\n FlowServer.RUNNING_SERVERS.splice(index, 1);\n }\n logger.debug(\n `Flow server on port ${this.port} has successfully shut down.`\n );\n this.port = null;\n this.server = null;\n resolve();\n });\n });\n }\n\n /**\n * Stops all running servers.\n */\n static async stopAll() {\n return Promise.all(\n FlowServer.RUNNING_SERVERS.map((server) => server.stop())\n );\n }\n}\n"],"mappings":"AAgBA,OAAO,gBAAgB;AACvB,OAAO,UAAgC;AACvC,SAAS,kBAAkB;AAC3B,OAAO,aAAa;AACpB;AAAA,EAGE;AAAA,EAEA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,cAAc;AAGvB,MAAM,kBAAkB;AAKjB,SAAS,eAOd,QACA,MAIwB;AACxB,SAAO,OACL,SACA,aACkB;AAClB,UAAM,EAAE,OAAO,IAAI,QAAQ;AAC3B,UAAM,iBAAiB,QAAQ,QAAQ,oBAAoB;AAC3D,UAAM,WAAW,MAAM,QAAQ,cAAc,IACzC,eAAe,CAAC,IAChB;AAEJ,QAAI,CAAC,QAAQ,MAAM;AACjB,YAAM,SACJ;AAGF,aAAO,MAAM,MAAM;AACnB,eACG,OAAO,GAAG,EACV,KAAK,EAAE,SAAS,QAAQ,QAAQ,mBAAmB,CAAC,EACpD,IAAI;AACP;AAAA,IACF;AAEA,UAAM,QAAQ,QAAQ,KAAK;AAC3B,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI;AAEJ,QAAI;AACF,gBACG,MAAM,MAAM,kBAAkB;AAAA,QAC7B,QAAQ,QAAQ;AAAA,QAChB,SAAS,OAAO;AAAA,UACd,OAAO,QAAQ,QAAQ,OAAO,EAG3B,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,UAAU,MAAS,EACzC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAAA,YACrB,IAAI,YAAY;AAAA;AAAA,YAEhB,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK;AAAA,UACxD,CAAC;AAAA,QACL;AAAA,QACA;AAAA,MACF,CAAC,KAAM,CAAC;AAAA,IACZ,SAAS,GAAQ;AACf,aAAO;AAAA,QACL,kCAAmC,EAAY,OAAO;AAAA,EAAM,EAAY,KAAK;AAAA,MAC/E;AACA,eAAS,OAAO,cAAc,CAAC,CAAC,EAAE,KAAK,gBAAgB,CAAC,CAAC,EAAE,IAAI;AAC/D;AAAA,IACF;AAEA,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,YAAQ,GAAG,SAAS,MAAM;AACxB,sBAAgB,MAAM;AAAA,IACxB,CAAC;AAED,YAAQ,GAAG,WAAW,MAAM;AAC1B,sBAAgB,MAAM;AAAA,IACxB,CAAC;AAED,QACE,QAAQ,IAAI,QAAQ,GAAG,YAAY,EAAE,SAAS,mBAAmB,KACjE,WAAW,QACX;AACA,YAAM,gBAAgB,MAAM;AAC5B,UAAI,iBAAiB,UAAU;AAC7B,cAAM,kBAAkB,eAAe,UAAU,QAAQ;AACzD;AAAA,MACF;AACA,YAAM,gBAAgB,WAAW;AACjC,YAAM,UAAU;AAAA,QACd,gBAAgB;AAAA,QAChB,qBAAqB;AAAA,MACvB;AACA,UAAI,eAAe;AACjB,gBAAQ,oBAAoB,IAAI;AAAA,MAClC;AACA,eAAS,UAAU,KAAK,OAAO;AAC/B;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,MAClB;AAAA,IACF,OAAO;AACL,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,IAAI,OAAO;AAAA,UACrC;AAAA,UACA,aAAa,gBAAgB;AAAA,UAC7B;AAAA,QACF,CAAC;AACD,iBAAS,UAAU,qBAAqB,OAAO,UAAU,OAAO;AAChE,iBAAS,UAAU,oBAAoB,OAAO,UAAU,MAAM;AAE9D,iBACG,OAAO,GAAG,EACV,KAAK;AAAA,UACJ,QAAQ,OAAO;AAAA,QACjB,CAAC,EACA,IAAI;AAAA,MACT,SAAS,GAAG;AAEV,eAAO;AAAA,UACL,4CAA6C,EAAY,OAAO;AAAA,EAAM,EAAY,KAAK;AAAA,QACzF;AACA,iBAAS,OAAO,cAAc,CAAC,CAAC,EAAE,KAAK,gBAAgB,CAAC,CAAC,EAAE,IAAI;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,8BAMb,QACA,eACA,UACA,OACA,MACA,SACA,UACA,aACA;AACA,MAAI;AACJ,MAAI;AACJ,MAAI,eAAe;AACjB,gBAAY,IAAI,eAAe;AAC/B,oBAAgB,MAAM,cAAc,KAAK,QAAQ;AAAA,EACnD;AACA,MAAI;AACF,QAAI,UAAU,CAAC,UAAsB;AAGnC,UAAI,SAAS,UAAW;AACxB,eAAS;AAAA,QACP,WAAW,KAAK,UAAU,EAAE,SAAS,MAAM,CAAC,IAAI;AAAA,MAClD;AAAA,IACF;AACA,QAAI,eAAe;AACjB,YAAM,kBAAkB;AACxB,gBAAU,CAAC,UAAsB;AAC/B,wBAAgB,KAAK;AACrB,kBAAW,QAAQ,MAAM,cAAe,MAAM,KAAK,CAAC;AAAA,MACtD;AAAA,IACF;AACA,UAAM,SAAS,MAAM,OAAO,IAAI,OAAO;AAAA,MACrC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,eAAe;AACjB,gBAAW,QAAQ,MAAM,cAAe,KAAK,OAAO,MAAM,CAAC;AAC3D,YAAM,UAAW,MAAM;AAAA,IACzB;AACA,QAAI,CAAC,SAAS,WAAW;AACvB,eAAS;AAAA,QACP,WAAW,KAAK,UAAU,EAAE,QAAQ,OAAO,OAAO,CAAC,IAAI;AAAA,MACzD;AACA,eAAS,IAAI;AAAA,IACf;AAAA,EACF,SAAS,GAAG;AACV,QAAI,eAAe;AACjB,gBAAW,QAAQ,MAAM,cAAe,MAAM,CAAC,CAAC;AAChD,YAAM,UAAW,MAAM;AAAA,IACzB;AACA,WAAO;AAAA,MACL,wCAAyC,EAAY,OAAO;AAAA,EACzD,EAAY,KACf;AAAA,IACF;AACA,QAAI,CAAC,SAAS,WAAW;AACvB,eAAS;AAAA,QACP,UAAU,KAAK,UAAU;AAAA,UACvB,OAAO,gBAAgB,CAAC;AAAA,QAC1B,CAAC,CAAC,GAAG,eAAe;AAAA,MACtB;AACA,eAAS,IAAI;AAAA,IACf;AAAA,EACF;AACF;AAEA,eAAe,kBACb,eACA,UACA,UACe;AACf,MAAI;AACF,UAAM,cAAc,UAAU,UAAU;AAAA,MACtC,SAAS,CAAC,UAAU;AAGlB,YAAI,SAAS,UAAW;AACxB,iBAAS;AAAA,UACP,WAAW,KAAK,UAAU,EAAE,SAAS,MAAM,CAAC,IAAI;AAAA,QAClD;AAAA,MACF;AAAA,MACA,QAAQ,CAAC,WAAW;AAClB,YAAI,SAAS,UAAW;AACxB,iBAAS;AAAA,UACP,WAAW,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC,IAAI;AAAA,QAClD;AACA,iBAAS,IAAI;AAAA,MACf;AAAA,MACA,SAAS,CAAC,QAAQ;AAChB,eAAO;AAAA,UACL,wCAAyC,IAAc,OAAO;AAAA,EAC3D,IAAc,KACjB;AAAA,QACF;AACA,YAAI,SAAS,UAAW;AACxB,iBAAS;AAAA,UACP,UAAU,KAAK,UAAU;AAAA,YACvB,OAAO,gBAAgB,GAAG;AAAA,UAC5B,CAAC,CAAC,GAAG,eAAe;AAAA,QACtB;AACA,iBAAS,IAAI;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH,SAAS,GAAQ;AAGf,QAAI,SAAS,UAAW;AACxB,QAAI,aAAa,qBAAqB;AACpC,eAAS,OAAO,GAAG,EAAE,IAAI;AACzB;AAAA,IACF;AACA,QAAI,EAAE,WAAW,qBAAqB;AACpC,eAAS;AAAA,QACP,UAAU,KAAK,UAAU;AAAA,UACvB,OAAO,gBAAgB,CAAC;AAAA,QAC1B,CAAC,CAAC,GAAG,eAAe;AAAA,MACtB;AACA,eAAS,IAAI;AACb;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;AAoCO,SAAS,oBAMd,MACA,SACqC;AACrC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAKO,SAAS,gBAKd,MACA,SAK0B;AAC1B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAyBO,SAAS,gBAAgB,SAAwC;AACtE,QAAM,SAAS,IAAI,WAAW,OAAO;AACrC,SAAO,MAAM;AACb,SAAO;AACT;AASO,MAAM,WAAW;AAAA;AAAA,EAEtB,OAAe,kBAAgC,CAAC;AAAA;AAAA,EAGxC;AAAA;AAAA,EAEA,OAAsB;AAAA;AAAA,EAEtB,SAAwB;AAAA,EAEhC,YAAY,SAA4B;AACtC,SAAK,UAAU;AAAA,MACb,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ;AACZ,UAAM,SAAS,QAAQ;AAEvB,WAAO,IAAI,WAAW,KAAK,KAAK,QAAQ,iBAAiB,CAAC;AAC1D,WAAO,IAAI,KAAK,KAAK,QAAQ,IAAI,CAAC;AAElC,WAAO,MAAM,sCAAsC;AACnD,UAAM,aAAa,KAAK,QAAQ,cAAc;AAC9C,SAAK,QAAQ,OAAO,QAAQ,CAAC,SAAS;AACpC,UAAI,UAAU,MAAM;AAClB,cAAM,WAAW,IAAI,UAAU,GAC5B,aAAa,QAAQ,KAAK,QAAQ,QAAS,KAAK,KAAK,SAAS,IACjE;AACA,eAAO,MAAM,MAAM,QAAQ,EAAE;AAC7B,cAAM,UACJ,aAAa,OAAO,KAAK,UAAU,EAAE,iBAAiB,KAAK,QAAQ;AACrE,eAAO,KAAK,UAAU,eAAe,KAAK,MAAM,OAAO,CAAC;AAAA,MAC1D,OAAO;AACL,cAAM,WAAW,IAAI,UAAU,GAAG,KAAK,SAAS,IAAI;AACpD,eAAO,MAAM,MAAM,QAAQ,EAAE;AAC7B,eAAO,KAAK,UAAU,eAAe,IAAI,CAAC;AAAA,MAC5C;AAAA,IACF,CAAC;AACD,SAAK,OACH,KAAK,SAAS,SACb,QAAQ,IAAI,OAAO,OAAO,SAAS,QAAQ,IAAI,IAAI,IAAI,MACxD;AACF,SAAK,SAAS,OAAO,OAAO,KAAK,MAAM,MAAM;AAC3C,aAAO,MAAM,2CAA2C,KAAK,IAAI,EAAE;AACnE,iBAAW,gBAAgB,KAAK,IAAI;AAAA,IACtC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AACA,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAK,OAAQ,MAAM,CAAC,QAAQ;AAC1B,YAAI,KAAK;AACP,iBAAO;AAAA,YACL,2CAA2C,KAAK,IAAI,KAAK,GAAG;AAAA,UAC9D;AACA,iBAAO,GAAG;AAAA,QACZ;AACA,cAAM,QAAQ,WAAW,gBAAgB,QAAQ,IAAI;AACrD,YAAI,QAAQ,IAAI;AACd,qBAAW,gBAAgB,OAAO,OAAO,CAAC;AAAA,QAC5C;AACA,eAAO;AAAA,UACL,uBAAuB,KAAK,IAAI;AAAA,QAClC;AACA,aAAK,OAAO;AACZ,aAAK,SAAS;AACd,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,UAAU;AACrB,WAAO,QAAQ;AAAA,MACb,WAAW,gBAAgB,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "genai",
10
10
  "generative-ai"
11
11
  ],
12
- "version": "1.38.0",
12
+ "version": "1.39.0",
13
13
  "type": "commonjs",
14
14
  "repository": {
15
15
  "type": "git",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "peerDependencies": {
26
26
  "express": "^4.21.1 || ^5",
27
- "genkit": "^1.38.0"
27
+ "genkit": "^1.39.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "get-port": "^5.1.0",
package/src/index.ts CHANGED
@@ -47,8 +47,9 @@ export function expressHandler<
47
47
  I extends z.ZodTypeAny = z.ZodTypeAny,
48
48
  O extends z.ZodTypeAny = z.ZodTypeAny,
49
49
  S extends z.ZodTypeAny = z.ZodTypeAny,
50
+ Init extends z.ZodTypeAny = z.ZodTypeAny,
50
51
  >(
51
- action: Action<I, O, S>,
52
+ action: Action<I, O, S, any, Init>,
52
53
  opts?: {
53
54
  contextProvider?: ContextProvider<C, I>;
54
55
  streamManager?: StreamManager;
@@ -78,6 +79,7 @@ export function expressHandler<
78
79
  }
79
80
 
80
81
  const input = request.body.data as z.infer<I>;
82
+ const init = request.body.init as z.infer<Init> | undefined;
81
83
  let context: Record<string, any>;
82
84
 
83
85
  try {
@@ -137,6 +139,7 @@ export function expressHandler<
137
139
  streamManager,
138
140
  streamIdToUse,
139
141
  input,
142
+ init,
140
143
  context,
141
144
  response,
142
145
  abortController.signal
@@ -146,6 +149,7 @@ export function expressHandler<
146
149
  const result = await action.run(input, {
147
150
  context,
148
151
  abortSignal: abortController.signal,
152
+ init,
149
153
  });
150
154
  response.setHeader('x-genkit-trace-id', result.telemetry.traceId);
151
155
  response.setHeader('x-genkit-span-id', result.telemetry.spanId);
@@ -171,11 +175,13 @@ async function runActionWithDurableStreaming<
171
175
  I extends z.ZodTypeAny,
172
176
  O extends z.ZodTypeAny,
173
177
  S extends z.ZodTypeAny,
178
+ Init extends z.ZodTypeAny = z.ZodTypeAny,
174
179
  >(
175
- action: Action<I, O, S>,
180
+ action: Action<I, O, S, any, Init>,
176
181
  streamManager: StreamManager | undefined,
177
182
  streamId: string,
178
183
  input: z.infer<I>,
184
+ init: z.infer<Init> | undefined,
179
185
  context: ActionContext,
180
186
  response: express.Response,
181
187
  abortSignal: AbortSignal
@@ -206,6 +212,7 @@ async function runActionWithDurableStreaming<
206
212
  onChunk,
207
213
  context,
208
214
  abortSignal,
215
+ init,
209
216
  });
210
217
  if (streamManager) {
211
218
  taskQueue!.enqueue(() => durableStream!.done(result.result));
@@ -161,6 +161,38 @@ describe('expressHandler', async () => {
161
161
  '/echoModelWithAuth',
162
162
  expressHandler(echoModel, { contextProvider })
163
163
  );
164
+ // A flow that echoes back the init data to verify it was received.
165
+ const flowWithInit = ai.defineFlow(
166
+ {
167
+ name: 'flowWithInit',
168
+ inputSchema: z.string(),
169
+ },
170
+ async (input) => {
171
+ return `input: ${input}`;
172
+ }
173
+ );
174
+ // Monkey-patch the run method to capture and return init data.
175
+ const originalRun = flowWithInit.run.bind(flowWithInit);
176
+ flowWithInit.run = async (input: any, options: any) => {
177
+ const result = await originalRun(input, options);
178
+ // Embed init in the result so we can verify it was passed through.
179
+ result.result = `input: ${input}, init: ${JSON.stringify(options?.init)}`;
180
+ return result;
181
+ };
182
+
183
+ // A flow with an initSchema to exercise real init validation.
184
+ const flowWithInitSchema = ai.defineFlow(
185
+ {
186
+ name: 'flowWithInitSchema',
187
+ inputSchema: z.string(),
188
+ initSchema: z.object({ sessionId: z.string() }),
189
+ },
190
+ async (input, { init }) =>
191
+ `input: ${input}, sessionId: ${(init as { sessionId: string }).sessionId}`
192
+ );
193
+
194
+ app.post('/flowWithInit', expressHandler(flowWithInit));
195
+ app.post('/flowWithInitSchema', expressHandler(flowWithInitSchema));
164
196
  app.post('/abortableFlow', expressHandler(abortableFlow));
165
197
 
166
198
  server = app.listen(port, () => {
@@ -289,6 +321,47 @@ describe('expressHandler', async () => {
289
321
  });
290
322
  });
291
323
 
324
+ it('should pass init data to the action', async () => {
325
+ const result = await runFlow<string>({
326
+ url: `http://localhost:${port}/flowWithInit`,
327
+ input: 'hello',
328
+ init: { sessionId: 'abc123', temperature: 0.7 },
329
+ });
330
+ assert.strictEqual(
331
+ result,
332
+ 'input: hello, init: {"sessionId":"abc123","temperature":0.7}'
333
+ );
334
+ });
335
+
336
+ it('should pass undefined init when not provided', async () => {
337
+ const result = await runFlow<string>({
338
+ url: `http://localhost:${port}/flowWithInit`,
339
+ input: 'hello',
340
+ });
341
+ assert.strictEqual(result, 'input: hello, init: undefined');
342
+ });
343
+
344
+ it('should validate init against initSchema and pass it to the action', async () => {
345
+ const result = await runFlow<string>({
346
+ url: `http://localhost:${port}/flowWithInitSchema`,
347
+ input: 'hello',
348
+ init: { sessionId: 'abc123' },
349
+ });
350
+ assert.strictEqual(result, 'input: hello, sessionId: abc123');
351
+ });
352
+
353
+ it('should reject init that does not conform to initSchema', async () => {
354
+ const result = runFlow<string>({
355
+ url: `http://localhost:${port}/flowWithInitSchema`,
356
+ input: 'hello',
357
+ // sessionId should be a string, not a number.
358
+ init: { sessionId: 123 },
359
+ });
360
+ await assert.rejects(result, (err: Error) => {
361
+ return err.message.includes('INVALID_ARGUMENT');
362
+ });
363
+ });
364
+
292
365
  // TODO: This test is flaky, skipping until fixed.
293
366
  it.skip('should abort a flow with auth', async () => {
294
367
  const controller = new AbortController();