{"version":3,"file":"serve.mjs","names":[],"sources":["../../../../../../../ai/src/serve/serve.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { streamToSSE, type StreamLike } from \"./stream-to-sse\";\n\n/**\n * Anything `serve` can expose: a primitive whose `stream(input, options)`\n * returns a {@link StreamLike}. Agents, supervisors, and orchestrators all\n * satisfy it.\n */\nexport type ServableExecutable<TInput = unknown> = {\n  stream(input: TInput, options?: Record<string, unknown>): StreamLike<{ type: string }, unknown>;\n};\n\n/** Options for {@link serve}. */\nexport type ServeOptions<TInput = unknown> = {\n  /**\n   * Bearer token required on every request. When set, a request must send\n   * `Authorization: Bearer <token>`, else `401` (S4-style auth, the same\n   * control the dashboard uses — fold this in for a production deploy).\n   */\n  authToken?: string;\n  /**\n   * Map the parsed JSON request body to the executable's input. Default:\n   * `body.input`. Override to accept a different request shape.\n   */\n  toInput?: (body: Record<string, unknown>) => TInput;\n  /**\n   * Map the parsed body to per-call stream options (e.g. an orchestrator\n   * `{ sessionId, history }` so a turn resumes the right session — A3\n   * wiring). Default: pass `sessionId` / `history` straight through.\n   */\n  toOptions?: (body: Record<string, unknown>) => Record<string, unknown>;\n};\n\nconst SECURITY_HEADERS: Record<string, string> = {\n  \"x-content-type-options\": \"nosniff\",\n  \"x-frame-options\": \"DENY\",\n  \"referrer-policy\": \"no-referrer\",\n};\n\n/**\n * Turn an executable into a `node:http` request handler that streams its\n * run to the client as Server-Sent Events (A3) — the production-serving\n * primitive. POST a JSON body (`{ input, sessionId?, history? }`); the\n * response is an `text/event-stream` of the primitive's events, the final\n * `result`, then `[DONE]`. Absorbs the auth-token control; pair with a\n * `sessionLock` + an orchestrator for durable multi-turn serving.\n *\n * @example\n * import { createServer } from \"node:http\";\n * createServer(ai.serve(myAgent, { authToken: process.env.TOKEN })).listen(8787);\n */\nexport function serve<TInput = unknown>(\n  executable: ServableExecutable<TInput>,\n  options: ServeOptions<TInput> = {},\n): (req: IncomingMessage, res: ServerResponse) => void {\n  const toInput = options.toInput ?? ((body) => body.input as TInput);\n  const toOptions =\n    options.toOptions ??\n    ((body) => {\n      const opts: Record<string, unknown> = {};\n      if (body.sessionId !== undefined) opts.sessionId = body.sessionId;\n      if (body.history !== undefined) opts.history = body.history;\n      return opts;\n    });\n\n  return function handle(req: IncomingMessage, res: ServerResponse): void {\n    void (async () => {\n      if (req.method !== \"POST\") {\n        sendJson(res, 405, { error: \"method_not_allowed\" });\n        return;\n      }\n\n      if (options.authToken && req.headers.authorization !== `Bearer ${options.authToken}`) {\n        sendJson(res, 401, { error: \"unauthorized\" });\n        return;\n      }\n\n      let body: Record<string, unknown>;\n      try {\n        body = await readJsonBody(req);\n      } catch {\n        sendJson(res, 400, { error: \"invalid_json\" });\n        return;\n      }\n\n      res.writeHead(200, {\n        \"content-type\": \"text/event-stream; charset=utf-8\",\n        \"cache-control\": \"no-cache\",\n        connection: \"keep-alive\",\n        ...SECURITY_HEADERS,\n      });\n\n      try {\n        const stream = executable.stream(toInput(body), toOptions(body));\n        for await (const frame of streamToSSE(stream)) {\n          res.write(frame);\n        }\n      } catch (error) {\n        res.write(\n          `event: error\\ndata: ${JSON.stringify({\n            message: error instanceof Error ? error.message : String(error),\n          })}\\n\\n`,\n        );\n      } finally {\n        res.end();\n      }\n    })();\n  };\n}\n\n/** Read and JSON-parse a request body. */\nfunction readJsonBody(req: IncomingMessage): Promise<Record<string, unknown>> {\n  return new Promise((resolve, reject) => {\n    let raw = \"\";\n    req.on(\"data\", (chunk: Buffer | string) => {\n      raw += chunk.toString();\n    });\n    req.on(\"end\", () => {\n      try {\n        resolve(raw ? (JSON.parse(raw) as Record<string, unknown>) : {});\n      } catch (error) {\n        reject(error);\n      }\n    });\n    req.on(\"error\", reject);\n  });\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n  res.writeHead(status, { \"content-type\": \"application/json; charset=utf-8\", ...SECURITY_HEADERS });\n  res.end(JSON.stringify(body));\n}\n"],"mappings":";;;AAiCA,MAAM,mBAA2C;CAC/C,0BAA0B;CAC1B,mBAAmB;CACnB,mBAAmB;AACrB;;;;;;;;;;;;;AAcA,SAAgB,MACd,YACA,UAAgC,CAAC,GACoB;CACrD,MAAM,UAAU,QAAQ,aAAa,SAAS,KAAK;CACnD,MAAM,YACJ,QAAQ,eACN,SAAS;EACT,MAAM,OAAgC,CAAC;EACvC,IAAI,KAAK,cAAc,QAAW,KAAK,YAAY,KAAK;EACxD,IAAI,KAAK,YAAY,QAAW,KAAK,UAAU,KAAK;EACpD,OAAO;CACT;CAEF,OAAO,SAAS,OAAO,KAAsB,KAA2B;EACtE,CAAM,YAAY;GAChB,IAAI,IAAI,WAAW,QAAQ;IACzB,SAAS,KAAK,KAAK,EAAE,OAAO,qBAAqB,CAAC;IAClD;GACF;GAEA,IAAI,QAAQ,aAAa,IAAI,QAAQ,kBAAkB,UAAU,QAAQ,aAAa;IACpF,SAAS,KAAK,KAAK,EAAE,OAAO,eAAe,CAAC;IAC5C;GACF;GAEA,IAAI;GACJ,IAAI;IACF,OAAO,MAAM,aAAa,GAAG;GAC/B,QAAQ;IACN,SAAS,KAAK,KAAK,EAAE,OAAO,eAAe,CAAC;IAC5C;GACF;GAEA,IAAI,UAAU,KAAK;IACjB,gBAAgB;IAChB,iBAAiB;IACjB,YAAY;IACZ,GAAG;GACL,CAAC;GAED,IAAI;IACF,MAAM,SAAS,WAAW,OAAO,QAAQ,IAAI,GAAG,UAAU,IAAI,CAAC;IAC/D,WAAW,MAAM,SAAS,YAAY,MAAM,GAC1C,IAAI,MAAM,KAAK;GAEnB,SAAS,OAAO;IACd,IAAI,MACF,uBAAuB,KAAK,UAAU,EACpC,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAChE,CAAC,EAAE,KACL;GACF,UAAU;IACR,IAAI,IAAI;GACV;EACF,EAAC,CAAE;CACL;AACF;;AAGA,SAAS,aAAa,KAAwD;CAC5E,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,MAAM;EACV,IAAI,GAAG,SAAS,UAA2B;GACzC,OAAO,MAAM,SAAS;EACxB,CAAC;EACD,IAAI,GAAG,aAAa;GAClB,IAAI;IACF,QAAQ,MAAO,KAAK,MAAM,GAAG,IAAgC,CAAC,CAAC;GACjE,SAAS,OAAO;IACd,OAAO,KAAK;GACd;EACF,CAAC;EACD,IAAI,GAAG,SAAS,MAAM;CACxB,CAAC;AACH;AAEA,SAAS,SAAS,KAAqB,QAAgB,MAAqB;CAC1E,IAAI,UAAU,QAAQ;EAAE,gBAAgB;EAAmC,GAAG;CAAiB,CAAC;CAChG,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAC9B"}