UNPKG

@genkit-ai/next

Version:
235 lines (234 loc) 6.76 kB
import { randomUUID } from "crypto"; import { AsyncTaskQueue, StreamNotFoundError } from "genkit/beta"; import { getCallableJSON, getHttpStatus } from "genkit/context"; import { NextRequest, NextResponse } from "next/server.js"; const delimiter = "\n\n"; async function subscribeToStream(streamManager, streamId) { try { const encoder = new TextEncoder(); const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); await streamManager.subscribe(streamId, { onChunk: (chunk) => { writer.write( encoder.encode( "data: " + JSON.stringify({ message: chunk }) + delimiter ) ); }, onDone: (output) => { writer.write( encoder.encode( "data: " + JSON.stringify({ result: output }) + delimiter ) ); writer.write(encoder.encode("END")); writer.close(); }, onError: (err) => { console.error( `Streaming request failed with error: ${err.message} ${err.stack}` ); writer.write( encoder.encode( `error: ${JSON.stringify({ error: getCallableJSON(err) })}${delimiter}` ) ); writer.write(encoder.encode("END")); writer.close(); } }); return new NextResponse(readable, { status: 200, headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", "Transfer-Encoding": "chunked", "x-genkit-stream-id": streamId } }); } catch (e) { if (e instanceof StreamNotFoundError) { return new NextResponse(null, { status: 204 }); } if (e.status === "DEADLINE_EXCEEDED") { const encoder = new TextEncoder(); const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); writer.write( encoder.encode( `error: ${JSON.stringify({ error: getCallableJSON(e) })}${delimiter}` ) ); writer.write(encoder.encode("END")); writer.close(); return new NextResponse(readable, { status: 200, headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", "Transfer-Encoding": "chunked" } }); } throw e; } } async function getContext(request, input, provider) { const context = {}; if (!provider) { return context; } const r = { method: request.method, headers: {}, input }; request.headers.forEach((val, key) => { r.headers[key.toLowerCase()] = val; }); return await provider(r); } function appRoute(action, opts) { return async (req) => { let context = {}; const { data: input } = await req.json(); const streamId = req.headers.get("x-genkit-stream-id"); if (req.headers.get("accept") !== "text/event-stream") { try { context = await getContext(req, input, opts?.contextProvider); } catch (e) { console.error("Error gathering context for running action:", e); return NextResponse.json( { error: getCallableJSON(e) }, { status: getHttpStatus(e) } ); } try { const resp = await action.run(input, { context, abortSignal: req.signal }); const response = NextResponse.json({ result: resp.result }); if (opts?.streamManager && streamId) { response.headers.set("x-genkit-stream-id", streamId); } return response; } catch (e) { console.error("Error calling action:", e); return NextResponse.json( { error: getCallableJSON(e) }, { status: getHttpStatus(e) } ); } } try { context = await getContext(req, input, opts?.contextProvider); } catch (e) { console.error("Error gathering context for streaming action:", e); return new NextResponse( `error: ${JSON.stringify(getCallableJSON(e))}${delimiter}END`, { status: getHttpStatus(e) } ); } const streamManager = opts?.streamManager; if (streamManager && streamId) { const response = await subscribeToStream(streamManager, streamId); if (response) { return response; } } const streamIdToUse = randomUUID(); const encoder = new TextEncoder(); const { readable, writable } = new TransformStream(); (async () => { const writer = writable.getWriter(); const taskQueue = new AsyncTaskQueue(); let durableStream = void 0; if (streamManager) { durableStream = await streamManager.open(streamIdToUse); } try { const output = action.run(input, { context, abortSignal: req.signal, onChunk: (chunk) => { if (durableStream) { taskQueue.enqueue(() => durableStream.write(chunk)); } taskQueue.enqueue( () => writer.write( encoder.encode( `data: ${JSON.stringify({ message: chunk })}${delimiter}` ) ) ); } }); const finalOutput = await output; if (durableStream) { taskQueue.enqueue(() => durableStream.done(finalOutput.result)); } taskQueue.enqueue( () => writer.write( encoder.encode( `data: ${JSON.stringify({ result: finalOutput.result })}${delimiter}` ) ) ); taskQueue.enqueue(() => writer.write(encoder.encode("END"))); } catch (err) { if (durableStream) { taskQueue.enqueue(() => durableStream.error(err)); } console.error("Error streaming action:", err); taskQueue.enqueue( () => writer.write( encoder.encode( `error: ${JSON.stringify(getCallableJSON(err))} ` ) ) ); taskQueue.enqueue(() => writer.write(encoder.encode("END"))); } finally { await taskQueue.merge(); await writer.close(); } })(); const headers = { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", "Transfer-Encoding": "chunked" }; if (streamManager) { headers["x-genkit-stream-id"] = streamIdToUse; } return new NextResponse(readable, { status: 200, headers }); }; } var index_default = appRoute; export { NextRequest, NextResponse, appRoute, index_default as default }; //# sourceMappingURL=index.mjs.map