{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { randomUUID } from 'crypto';\nimport {\n  Action,\n  AsyncTaskQueue,\n  StreamNotFoundError,\n  type ActionContext,\n  type ActionStreamInput,\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 { NextRequest, NextResponse } from 'next/server.js';\nexport { NextRequest, NextResponse, z, type Action, type ActionContext };\n\nconst delimiter = '\\n\\n';\n\nasync function subscribeToStream<S, O>(\n  streamManager: StreamManager,\n  streamId: string\n): Promise<NextResponse | null> {\n  try {\n    const encoder = new TextEncoder();\n    const { readable, writable } = new TransformStream();\n    const writer = writable.getWriter();\n    await streamManager.subscribe(streamId, {\n      onChunk: (chunk) => {\n        writer.write(\n          encoder.encode(\n            'data: ' + JSON.stringify({ message: chunk }) + delimiter\n          )\n        );\n      },\n      onDone: (output) => {\n        writer.write(\n          encoder.encode(\n            'data: ' + JSON.stringify({ result: output }) + delimiter\n          )\n        );\n        writer.write(encoder.encode('END'));\n        writer.close();\n      },\n      onError: (err) => {\n        console.error(\n          `Streaming request failed with error: ${(err as Error).message}\\n${\n            (err as Error).stack\n          }`\n        );\n        writer.write(\n          encoder.encode(\n            `error: ${JSON.stringify({\n              error: getCallableJSON(err),\n            })}${delimiter}`\n          )\n        );\n        writer.write(encoder.encode('END'));\n        writer.close();\n      },\n    });\n    return new NextResponse(readable, {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/event-stream',\n        'Cache-Control': 'no-cache',\n        Connection: 'keep-alive',\n        'Transfer-Encoding': 'chunked',\n        'x-genkit-stream-id': streamId,\n      },\n    });\n  } catch (e: any) {\n    if (e instanceof StreamNotFoundError) {\n      return new NextResponse(null, { status: 204 });\n    }\n    if (e.status === 'DEADLINE_EXCEEDED') {\n      const encoder = new TextEncoder();\n      const { readable, writable } = new TransformStream();\n      const writer = writable.getWriter();\n      writer.write(\n        encoder.encode(\n          `error: ${JSON.stringify({\n            error: getCallableJSON(e),\n          })}${delimiter}`\n        )\n      );\n      writer.write(encoder.encode('END'));\n      writer.close();\n      return new NextResponse(readable, {\n        status: 200,\n        headers: {\n          'Content-Type': 'text/event-stream',\n          'Cache-Control': 'no-cache',\n          Connection: 'keep-alive',\n          'Transfer-Encoding': 'chunked',\n        },\n      });\n    }\n    throw e;\n  }\n}\n\nasync function getContext<C extends ActionContext, T>(\n  request: NextRequest,\n  input: T,\n  provider: ContextProvider<C, T> | undefined\n): Promise<C> {\n  // Type cast is necessary because there is no runtime way to generate a context if C is provided to appRoute\n  // but contextProvider is missing. When I'm less sleepy/busy I'll see if I can make this a type error.\n  const context = {} as C;\n  if (!provider) {\n    return context;\n  }\n\n  const r: RequestData = {\n    method: request.method as RequestData['method'],\n    headers: {},\n    input,\n  };\n  request.headers.forEach((val, key) => {\n    r.headers[key.toLowerCase()] = val;\n  });\n  return await provider(r);\n}\n\nfunction appRoute<\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) {\n  return async (req: NextRequest): Promise<NextResponse> => {\n    let context: C = {} as C;\n    const { data: input, init } = (await req.json()) as {\n      data: z.infer<I>;\n      init?: z.infer<Init>;\n    };\n    const streamId = req.headers.get('x-genkit-stream-id');\n    if (req.headers.get('accept') !== 'text/event-stream') {\n      try {\n        context = await getContext(req, input, opts?.contextProvider);\n      } catch (e) {\n        console.error('Error gathering context for running action:', e);\n        return NextResponse.json(\n          { error: getCallableJSON(e) },\n          { status: getHttpStatus(e) }\n        );\n      }\n      try {\n        const resp = await action.run(input, {\n          context,\n          abortSignal: req.signal,\n          init,\n        });\n        const response = NextResponse.json({ result: resp.result });\n        if (opts?.streamManager && streamId) {\n          response.headers.set('x-genkit-stream-id', streamId);\n        }\n        return response;\n      } catch (e) {\n        // For security reasons, log the error rather than responding with it.\n        console.error('Error calling action:', e);\n        return NextResponse.json(\n          { error: getCallableJSON(e) },\n          { status: getHttpStatus(e) }\n        );\n      }\n    }\n\n    try {\n      context = await getContext(req, input, opts?.contextProvider);\n    } catch (e) {\n      console.error('Error gathering context for streaming action:', e);\n      return new NextResponse(\n        `error: ${JSON.stringify(getCallableJSON(e))}${delimiter}END`,\n        { status: getHttpStatus(e) }\n      );\n    }\n    const streamManager = opts?.streamManager;\n    if (streamManager && streamId) {\n      const response = await subscribeToStream(streamManager, streamId);\n      if (response) {\n        return response;\n      }\n    }\n\n    const streamIdToUse = randomUUID();\n    const encoder = new TextEncoder();\n    const { readable, writable } = new TransformStream();\n\n    // Not using a dangling promise causes this closure to block on the stream being drained,\n    // which doesn't happen until the NextResponse is consumed later in the cosure.\n    // TODO: Add ping comments at regular intervals between streaming responses to mitigate\n    // timeouts.\n    (async (): Promise<void> => {\n      const writer = writable.getWriter();\n      const taskQueue = new AsyncTaskQueue();\n      let durableStream: ActionStreamInput<S, O> | undefined = undefined;\n      if (streamManager) {\n        durableStream = await streamManager.open(streamIdToUse);\n      }\n      try {\n        const output = action.run(input, {\n          context,\n          abortSignal: req.signal,\n          init,\n          onChunk: (chunk) => {\n            if (durableStream) {\n              taskQueue.enqueue(() => durableStream!.write(chunk));\n            }\n            taskQueue.enqueue(() =>\n              writer.write(\n                encoder.encode(\n                  `data: ${JSON.stringify({ message: chunk })}${delimiter}`\n                )\n              )\n            );\n          },\n        });\n        const finalOutput = await output;\n        if (durableStream) {\n          taskQueue.enqueue(() => durableStream!.done(finalOutput.result));\n        }\n        taskQueue.enqueue(() =>\n          writer.write(\n            encoder.encode(\n              `data: ${JSON.stringify({ result: finalOutput.result })}${delimiter}`\n            )\n          )\n        );\n        taskQueue.enqueue(() => writer.write(encoder.encode('END')));\n      } catch (err) {\n        if (durableStream) {\n          taskQueue.enqueue(() => durableStream!.error(err));\n        }\n        console.error('Error streaming action:', err);\n        taskQueue.enqueue(() =>\n          writer.write(\n            encoder.encode(\n              `error: ${JSON.stringify(getCallableJSON(err))}` + '\\n\\n'\n            )\n          )\n        );\n        taskQueue.enqueue(() => writer.write(encoder.encode('END')));\n      } finally {\n        await taskQueue.merge();\n        await writer.close();\n      }\n    })();\n\n    const headers = {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n      Connection: 'keep-alive',\n      'Transfer-Encoding': 'chunked',\n    };\n    if (streamManager) {\n      headers['x-genkit-stream-id'] = streamIdToUse;\n    }\n\n    return new NextResponse(readable, {\n      status: 200,\n      headers,\n    });\n  };\n}\n\nexport default appRoute;\nexport { appRoute };\n"],"mappings":"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","names":[]}