@h-ai/serv 0.1.0-alpha.46 → 0.1.0-alpha.47
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 +61 -64
- package/dist/chunk-KIWHKWLD.js +317 -0
- package/dist/chunk-KIWHKWLD.js.map +1 -0
- package/dist/features/ai.d.ts +20 -21
- package/dist/features/ai.js +14 -31
- package/dist/features/ai.js.map +1 -1
- package/dist/features/iam.d.ts +58 -58
- package/dist/features/iam.js +38 -97
- package/dist/features/iam.js.map +1 -1
- package/dist/features/storage.d.ts +18 -19
- package/dist/features/storage.js +39 -44
- package/dist/features/storage.js.map +1 -1
- package/dist/index.d.ts +102 -122
- package/dist/index.js +3 -19
- package/dist/index.js.map +1 -1
- package/dist/{serv-context-BUrtWoFC.d.ts → serv-context-jc3rZxbj.d.ts} +2 -2
- package/package.json +10 -10
- package/dist/chunk-4QBF4PUG.js +0 -128
- package/dist/chunk-4QBF4PUG.js.map +0 -1
- package/dist/chunk-YQV2OMQK.js +0 -16
- package/dist/chunk-YQV2OMQK.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/serv-adapter-fetch.ts","../src/features/serv-feature-audio.ts","../src/serv-validation.ts","../src/pipelines/serv-pipeline-require-internal-rpc.ts","../src/pipelines/serv-pipeline-security-headers.ts","../src/serv-config.ts","../src/serv-context.ts","../src/serv-cookie-auth.ts","../src/serv-openapi.ts","../src/serv-transport.ts","../src/serv-app.ts","../src/adapters/serv-adapter-node.ts","../src/pipelines/serv-pipeline-require-role.ts","../src/serv-cors.ts","../src/serv-runtime-security.ts","../src/serv-storage-assets.ts","../src/serv-main.ts"],"names":["core","ok","err","HaiCommonError","cors","process","logger","HonoApp"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @h-ai/serv — Fetch 适配器\n *\n * 将 ServHttpApp 包装为标准 Web `fetch(Request)` 处理函数,\n * 适用于 Cloudflare Workers、Deno、Bun 等 Fetch-first 运行时。\n * @module adapters/serv-adapter-fetch\n */\n\nimport type { ServHttpApp } from '../serv-app.js'\n\n/**\n * Fetch runtime handler。\n *\n * 兼容标准 Web `fetch` 签名 `(input, init)` —— 既可作为 Cloudflare Workers / Bun\n * 的 `fetch` 导出,也可作为 oRPC client `fetch` 选项的替代实现,让客户端在测试\n * 中直连 in-process 服务。\n */\nexport type ServFetchHandler = typeof fetch\n\n/**\n * 将 ServHttpApp 转成 Fetch handler。\n *\n * @param app - `serv.createApp()` 返回的 HTTP app\n * @returns fetch-compatible handler\n *\n * @example\n * ```ts\n * // Cloudflare Workers\n * export default { fetch: serv.toFetch(app) }\n *\n * // Bun\n * Bun.serve({ fetch: serv.toFetch(app), port: 3000 })\n *\n * // 测试:把服务端 app 当作 fetch 注入到 client,免去 HTTP 监听\n * const client = apiClient.create(contract)\n * await client.init({ baseUrl: 'http://test', fetch: serv.toFetch(app) })\n * ```\n */\nexport function toFetch(app: ServHttpApp): ServFetchHandler {\n // 包装为标准 fetch 签名:接受 `Request | URL | string` + `RequestInit?`,\n // 内部统一转为 `Request` 再委托给 ServHttpApp。\n const handler = (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = input instanceof Request\n ? input\n : new Request(typeof input === 'string' ? input : input.toString(), init)\n return Promise.resolve(app.fetch(request))\n }\n return handler as ServFetchHandler\n}\n","/**\n * @h-ai/serv — 统一语音 WebSocket 接入\n *\n * 基于 `@h-ai/ai` 的 `ai.audio` 提供开箱即用的语音 WebSocket 端点,同时支持非实时 / 实时\n * 的语音识别与语音合成。传输细节(WebSocket 帧、二进制/JSON 编解码)在此内部完成,\n * 客户端只使用统一的领域协议,不接触任何厂商原生事件。\n *\n * 安全与健壮性:\n * - 连接建立后立即用一次性 ticket 完成鉴权(独立预鉴权超时),未鉴权不处理任何业务帧;\n * - 每一帧经运行时 Zod 校验,按严格状态机接受(识别只收音频、合成只收文本、start/done 各一次);\n * - 输入队列、消息积压、发送缓冲均有上限,超限即以领域错误关闭,取消时级联中止。\n * @module features/serv-feature-audio\n */\n\nimport type { AIFunctions, AudioWsClientMessage, AudioWsServerMessage, AudioWsStartMessage, SynthesisTextSegment } from '@h-ai/ai'\nimport type { HaiError, HaiResult } from '@h-ai/core'\nimport type { Hono } from 'hono'\nimport type { UpgradeWebSocket, WSContext } from 'hono/ws'\nimport type { AudioTicketGrant, AudioTicketVerification, AuthorizedAudioRequest } from '../serv-app.js'\nimport type { ServSession } from '../serv-context.js'\nimport { Buffer } from 'node:buffer'\nimport { AudioWsClientMessageSchema } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\n\nconst logger = core.logger.child({ module: 'serv', scope: 'audio-ws' })\n\n/** 语音 WebSocket 接入依赖。 */\nexport interface AudioWsDeps {\n /** AI 服务对象的语音能力(`ai.audio`)。 */\n readonly ai: Pick<AIFunctions, 'audio'>\n /** 校验并原子消费短期、一次性的 Audio ticket,返回会话与可选授权。 */\n readonly verifyTicket: (ticket: string) => Promise<HaiResult<AudioTicketVerification>>\n /** 基于会话授权操作、模型、音色、格式和配额;第三参为票据绑定授权(如有)。 */\n readonly authorize?: (session: ServSession, request: AudioWsStartMessage, grant?: AudioTicketGrant) => Promise<HaiResult<AuthorizedAudioRequest>>\n /** 会话结束时释放应用侧并发占用。 */\n readonly onSessionEnd?: (session: ServSession, request: AuthorizedAudioRequest) => void | Promise<void>\n /** 单条消息字节上限(默认 1 MiB,防止内存放大)。 */\n readonly maxMessageBytes?: number\n /** 单连接累计接收音频字节上限(默认 10 MiB,防止非流式缓冲无限增长)。 */\n readonly maxBufferedBytes?: number\n /** 单连接累计接收文本字节上限(默认 1 MiB,防止 TTS 文本输入无限增长与上游费用失控)。 */\n readonly maxTextBytes?: number\n /** 单连接最长持续时间(毫秒,默认 5 分钟,防止恶意长连接)。 */\n readonly maxSessionMs?: number\n /** 预鉴权超时(毫秒,默认 5000):连接建立后未完成鉴权并收到 start 即关闭。 */\n readonly preAuthTimeoutMs?: number\n /** 未处理消息积压上限(默认 256):串行处理链积压超过此数立即关闭。 */\n readonly maxPendingMessages?: number\n /** 发送缓冲高水位(字节,默认 8 MiB):bufferedAmount 超过即关闭慢客户端。 */\n readonly maxSendBufferBytes?: number\n}\n\nconst DEFAULT_MAX_MESSAGE_BYTES = 1_048_576\nconst DEFAULT_MAX_BUFFERED_BYTES = 10 * 1024 * 1024\nconst DEFAULT_MAX_TEXT_BYTES = 1 * 1024 * 1024\nconst DEFAULT_MAX_SESSION_MS = 5 * 60 * 1000\nconst DEFAULT_PRE_AUTH_TIMEOUT_MS = 5000\nconst DEFAULT_MAX_PENDING_MESSAGES = 256\nconst DEFAULT_MAX_SEND_BUFFER_BYTES = 8 * 1024 * 1024\n/** 输入队列单向最大缓存帧数(背压:超过即以领域错误关闭)。 */\nconst MAX_QUEUE_ITEMS = 4096\n\n// 领域错误码(对齐 @h-ai/ai audio 错误段 hai:ai:05x)\nconst CODE_PROTOCOL = 'hai:ai:050'\nconst CODE_TICKET = 'hai:ai:054'\nconst CODE_TOO_LARGE = 'hai:ai:058'\nconst CODE_TIMEOUT = 'hai:ai:057'\n\n/** 连接状态机 */\ntype ConnectionState\n = | 'connecting'\n | 'authenticated'\n | 'awaiting_start'\n | 'transcribing'\n | 'synthesizing'\n | 'input_completed'\n | 'completed'\n | 'failed'\n | 'cancelled'\n\n/** 连接级限额。 */\ninterface ConnectionLimits {\n readonly maxMessageBytes: number\n readonly maxBufferedBytes: number\n readonly maxTextBytes: number\n readonly maxPendingMessages: number\n readonly maxSendBufferBytes: number\n}\n\n/**\n * 在 Hono app 上注册统一语音 WebSocket 路由。\n *\n * @param app - Hono app\n * @param path - 绝对路径(已与 apiPrefix 拼接)\n * @param upgradeWebSocket - 由 `createNodeWebSocket` 提供的升级处理器\n * @param deps - 语音接入依赖\n */\nexport function registerAudioWsRoute(\n app: Hono,\n path: string,\n upgradeWebSocket: UpgradeWebSocket,\n deps: AudioWsDeps,\n): void {\n const limits: ConnectionLimits = {\n maxMessageBytes: deps.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES,\n maxBufferedBytes: deps.maxBufferedBytes ?? DEFAULT_MAX_BUFFERED_BYTES,\n maxTextBytes: deps.maxTextBytes ?? DEFAULT_MAX_TEXT_BYTES,\n maxPendingMessages: deps.maxPendingMessages ?? DEFAULT_MAX_PENDING_MESSAGES,\n maxSendBufferBytes: deps.maxSendBufferBytes ?? DEFAULT_MAX_SEND_BUFFER_BYTES,\n }\n const maxSessionMs = deps.maxSessionMs ?? DEFAULT_MAX_SESSION_MS\n const preAuthTimeoutMs = deps.preAuthTimeoutMs ?? DEFAULT_PRE_AUTH_TIMEOUT_MS\n\n app.get(path, upgradeWebSocket((c) => {\n const ticket = c.req.query('ticket')\n const conn = new AudioConnection(deps, ticket, limits)\n let sessionTimer: ReturnType<typeof setTimeout> | undefined\n\n return {\n onOpen(_evt, ws) {\n // 连接建立即启动鉴权与预鉴权超时;会话超时另行计时。\n conn.begin(ws, preAuthTimeoutMs)\n sessionTimer = setTimeout(() => {\n conn.fail(ws, CODE_TIMEOUT, 'audio session timeout')\n }, maxSessionMs)\n },\n onMessage(evt, ws) {\n // 串行处理同一连接的消息,鉴权完成前不处理业务帧(付费 ASR/TTS)\n conn.enqueue(ws, evt.data)\n },\n onClose() {\n if (sessionTimer)\n clearTimeout(sessionTimer)\n conn.dispose()\n },\n onError() {\n if (sessionTimer)\n clearTimeout(sessionTimer)\n conn.dispose()\n },\n }\n }))\n}\n\n/** 背压友好的有界异步队列(桥接 WebSocket 事件 → ai.audio 的 AsyncIterable 输入)。 */\nclass AsyncQueue<T> {\n private items: T[] = []\n private waiting: ((result: IteratorResult<T>) => void) | null = null\n private ended = false\n\n constructor(private readonly maxItems: number) {}\n\n /** 入队;超过上限返回 false(背压信号,调用方据此关闭连接)。 */\n push(item: T): boolean {\n if (this.ended)\n return true\n if (this.waiting) {\n const w = this.waiting\n this.waiting = null\n w({ value: item, done: false })\n return true\n }\n if (this.items.length >= this.maxItems)\n return false\n this.items.push(item)\n return true\n }\n\n end(): void {\n if (this.ended)\n return\n this.ended = true\n if (this.waiting) {\n const w = this.waiting\n this.waiting = null\n w({ value: undefined as never, done: true })\n }\n }\n\n async* iterate(): AsyncIterableIterator<T> {\n while (true) {\n if (this.items.length > 0) {\n yield this.items.shift() as T\n continue\n }\n if (this.ended)\n return\n const next = await new Promise<IteratorResult<T>>((res) => {\n this.waiting = res\n })\n if (next.done)\n return\n yield next.value\n }\n }\n}\n\n/** 单个语音 WebSocket 连接的状态机。 */\nclass AudioConnection {\n private state: ConnectionState = 'connecting'\n private start?: AudioWsStartMessage\n private session?: ServSession\n private grant?: AudioTicketGrant\n private authorizedRequest?: AuthorizedAudioRequest\n private doneReceived = false\n private readonly audioQueue: AsyncQueue<Uint8Array>\n private readonly textQueue: AsyncQueue<SynthesisTextSegment>\n private readonly audioBuffer: Uint8Array[] = []\n private receivedBytes = 0\n private receivedTextBytes = 0\n /** 合成文本段 ID 去重(同一会话内唯一)。 */\n private readonly seenSegmentIds = new Set<string>()\n private readonly controller = new AbortController()\n private closed = false\n private preAuthTimer?: ReturnType<typeof setTimeout>\n /** 待处理消息数(含鉴权任务):积压过多即关闭。 */\n private pending = 0\n /** 消息串行处理链:确保同一连接的消息按到达顺序处理,鉴权完成前不处理业务帧。 */\n private queue: Promise<void> = Promise.resolve()\n\n constructor(\n private readonly deps: AudioWsDeps,\n private readonly ticket: string | undefined,\n private readonly limits: ConnectionLimits,\n ) {\n this.audioQueue = new AsyncQueue<Uint8Array>(MAX_QUEUE_ITEMS)\n this.textQueue = new AsyncQueue<SynthesisTextSegment>(MAX_QUEUE_ITEMS)\n }\n\n /** 连接建立:立即鉴权(串行链首个任务)并启动预鉴权超时。 */\n begin(ws: WSContext, preAuthTimeoutMs: number): void {\n this.preAuthTimer = setTimeout(() => {\n // 鉴权完成并进入业务态前未收到 start:以预鉴权超时关闭(独立于会话超时)\n if (this.state === 'connecting' || this.state === 'authenticated' || this.state === 'awaiting_start')\n this.fail(ws, CODE_TIMEOUT, 'pre-auth timeout: authenticate and send start in time')\n }, preAuthTimeoutMs)\n this.chain(ws, () => this.authenticate(ws))\n }\n\n /** 将任务追加到串行处理链(含积压上限保护)。 */\n private chain(ws: WSContext, task: () => Promise<void>): void {\n if (this.closed)\n return\n this.pending++\n if (this.pending > this.limits.maxPendingMessages) {\n // 积压超限:立即以领域错误关闭,避免 Promise 链无界增长\n this.pending--\n this.fail(ws, CODE_TOO_LARGE, 'too many pending messages')\n return\n }\n this.queue = this.queue\n .then(() => task())\n .catch((error: unknown) => {\n logger.debug('audio ws task failed', { error: error instanceof Error ? error.message : String(error) })\n if (!this.closed)\n this.failFromError(ws, error)\n })\n .finally(() => {\n this.pending--\n })\n }\n\n /** 将消息追加到串行处理链,保证按序处理(鉴权完成前不处理业务帧)。 */\n enqueue(ws: WSContext, data: unknown): void {\n this.chain(ws, () => this.handleMessage(ws, data))\n }\n\n /** 连接建立即执行:校验并原子消费 ticket,成功后进入 awaiting_start。 */\n private async authenticate(ws: WSContext): Promise<void> {\n if (this.closed)\n return\n if (!this.ticket) {\n this.fail(ws, CODE_TICKET, 'missing audio ticket')\n return\n }\n const verified = await this.deps.verifyTicket(this.ticket)\n if (this.closed)\n return\n if (!verified.success) {\n this.fail(ws, String(verified.error.code), verified.error.message)\n return\n }\n this.session = verified.data.session\n this.grant = verified.data.grant\n this.state = 'awaiting_start'\n }\n\n async handleMessage(ws: WSContext, data: unknown): Promise<void> {\n if (this.closed)\n return\n\n // 二进制帧 → 音频分片\n const bytes = toBytes(data)\n if (bytes) {\n this.handleAudioFrame(ws, bytes)\n return\n }\n\n // 文本帧 → JSON 控制消息(UTF-8 实际字节计,非 JS 字符数)\n if (typeof data !== 'string')\n return\n if (Buffer.byteLength(data, 'utf8') > this.limits.maxMessageBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'message too large')\n return\n }\n\n let raw: unknown\n try {\n raw = JSON.parse(data)\n }\n catch {\n this.fail(ws, CODE_PROTOCOL, 'invalid json message')\n return\n }\n\n // 运行时结构校验:非法帧在进入业务逻辑前拒绝(TypeScript 类型在网络运行时不生效)\n const parsed = AudioWsClientMessageSchema.safeParse(raw)\n if (!parsed.success) {\n this.fail(ws, CODE_PROTOCOL, `invalid message: ${parsed.error.issues[0]?.message ?? 'schema validation failed'}`)\n return\n }\n const message = parsed.data as AudioWsClientMessage\n\n if (message.type === 'start') {\n await this.onStart(ws, message)\n return\n }\n\n // 非 start 帧必须在鉴权且已 start 之后\n if (this.state === 'connecting' || this.state === 'authenticated' || this.state === 'awaiting_start') {\n this.fail(ws, CODE_PROTOCOL, 'message before start')\n return\n }\n\n if (message.type === 'text')\n this.handleTextFrame(ws, message)\n else if (message.type === 'done')\n this.handleDone(ws)\n }\n\n /** 处理音频二进制帧(仅识别操作、输入未结束时接受)。 */\n private handleAudioFrame(ws: WSContext, bytes: Uint8Array): void {\n if (this.state !== 'transcribing') {\n // 合成会话或未 start / 已结束时收到音频帧:协议违规\n this.fail(ws, CODE_PROTOCOL, 'unexpected audio frame')\n return\n }\n if (bytes.length > this.limits.maxMessageBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'audio frame too large')\n return\n }\n this.receivedBytes += bytes.length\n if (this.receivedBytes > this.limits.maxBufferedBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'buffered audio exceeds limit')\n return\n }\n if (this.start?.stream) {\n if (!this.audioQueue.push(bytes))\n this.fail(ws, CODE_TOO_LARGE, 'audio backpressure limit exceeded')\n }\n else {\n this.audioBuffer.push(bytes)\n }\n }\n\n /** 处理文本控制帧(仅合成操作、输入未结束时接受;segmentId 唯一)。 */\n private handleTextFrame(ws: WSContext, message: { segmentId: string, text: string }): void {\n if (this.state !== 'synthesizing') {\n this.fail(ws, CODE_PROTOCOL, 'unexpected text frame')\n return\n }\n if (this.seenSegmentIds.has(message.segmentId)) {\n this.fail(ws, CODE_PROTOCOL, `duplicate segmentId: ${message.segmentId}`)\n return\n }\n this.receivedTextBytes += Buffer.byteLength(message.text, 'utf8')\n if (this.receivedTextBytes > this.limits.maxTextBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'buffered text exceeds limit')\n return\n }\n this.seenSegmentIds.add(message.segmentId)\n if (!this.textQueue.push({ id: message.segmentId, text: message.text }))\n this.fail(ws, CODE_TOO_LARGE, 'text backpressure limit exceeded')\n }\n\n /** 处理输入结束帧(只允许一次)。 */\n private handleDone(ws: WSContext): void {\n if (this.doneReceived) {\n this.fail(ws, CODE_PROTOCOL, 'duplicate done')\n return\n }\n this.doneReceived = true\n this.audioQueue.end()\n this.textQueue.end()\n if (this.state === 'transcribing' || this.state === 'synthesizing')\n this.state = 'input_completed'\n if (this.start && !this.start.stream && this.start.operation === 'transcribe')\n void this.runBufferedTranscribe(ws)\n }\n\n private async onStart(ws: WSContext, message: AudioWsStartMessage): Promise<void> {\n // 必须已鉴权且尚未开始\n if (this.state === 'connecting' || this.state === 'authenticated') {\n this.fail(ws, CODE_TICKET, 'not authenticated')\n return\n }\n if (this.state !== 'awaiting_start') {\n this.fail(ws, CODE_PROTOCOL, 'session already started')\n return\n }\n const session = this.session\n if (!session) {\n this.fail(ws, CODE_TICKET, 'not authenticated')\n return\n }\n\n const authorized = this.deps.authorize\n ? await this.deps.authorize(session, message, this.grant)\n : this.defaultAuthorize(message)\n if (this.closed)\n return\n if (!authorized.success) {\n this.fail(ws, String(authorized.error.code), authorized.error.message)\n return\n }\n\n const confirmed: AudioWsStartMessage = {\n ...message,\n operation: authorized.data.operation,\n model: authorized.data.model,\n voice: authorized.data.voice,\n instruction: authorized.data.instruction,\n format: authorized.data.format,\n sampleRate: authorized.data.sampleRate,\n }\n\n this.start = confirmed\n this.authorizedRequest = authorized.data\n // 进入业务态并清除预鉴权超时\n this.state = confirmed.operation === 'synthesize' ? 'synthesizing' : 'transcribing'\n if (this.preAuthTimer) {\n clearTimeout(this.preAuthTimer)\n this.preAuthTimer = undefined\n }\n\n if (confirmed.operation === 'synthesize')\n void this.runSynthesize(ws, confirmed)\n else if (confirmed.operation === 'transcribe' && confirmed.stream)\n void this.runStreamTranscribe(ws, confirmed)\n // 非流式识别在收到 done 后由 runBufferedTranscribe 处理\n }\n\n /** 无 authorize 回调时的默认授权:仅保留操作与格式,并交叉校验票据 grant。 */\n private defaultAuthorize(message: AudioWsStartMessage): HaiResult<AuthorizedAudioRequest> {\n // 票据绑定了操作但与请求不符:拒绝(防止越权复用票据)\n if (this.grant?.operation && this.grant.operation !== message.operation) {\n return err(HaiCommonError.FORBIDDEN, 'operation not permitted by ticket')\n }\n return ok({\n operation: message.operation,\n // 无 authorize 回调时不透传客户端 model/voice/instruction;仅采用票据 grant 绑定的 model\n model: this.grant?.model,\n format: message.format,\n })\n }\n\n /** 流式识别:桥接持续音频输入,流式返回临时/最终结果与语音起止事件 */\n private async runStreamTranscribe(ws: WSContext, start: AudioWsStartMessage): Promise<void> {\n try {\n const stream = this.deps.ai.audio.transcribeStream({\n audio: {\n chunks: this.audioQueue.iterate(),\n format: start.format ?? 'pcm16',\n sampleRate: start.sampleRate ?? 16000,\n channels: start.channels,\n },\n language: start.language,\n contextHints: start.contextHints,\n model: start.model,\n signal: this.controller.signal,\n })\n for await (const event of stream) {\n if (this.closed)\n return\n if (event.type === 'transcript')\n this.send(ws, { type: 'transcript', text: event.text, final: event.final })\n else if (event.type === 'speech_started')\n this.send(ws, { type: 'speech_started' })\n else if (event.type === 'speech_stopped')\n this.send(ws, { type: 'speech_stopped' })\n }\n this.end(ws)\n }\n catch (error) {\n this.failFromError(ws, error)\n }\n }\n\n /** 非流式识别:缓冲完整音频后返回单条最终结果 */\n private async runBufferedTranscribe(ws: WSContext): Promise<void> {\n const start = this.start\n if (!start)\n return\n try {\n const result = await this.deps.ai.audio.transcribe({\n audio: {\n data: concat(this.audioBuffer),\n format: start.format ?? 'wav',\n sampleRate: start.sampleRate,\n channels: start.channels,\n },\n language: start.language,\n contextHints: start.contextHints,\n model: start.model,\n signal: this.controller.signal,\n })\n if (this.closed)\n return\n if (!result.success) {\n this.fail(ws, String(result.error.code), result.error.message)\n return\n }\n this.send(ws, { type: 'transcript', text: result.data.text, final: true })\n this.end(ws)\n }\n catch (error) {\n this.failFromError(ws, error)\n }\n }\n\n /** 语音合成:桥接文本输入,流式返回音频二进制帧(带发送缓冲背压) */\n private async runSynthesize(ws: WSContext, start: AudioWsStartMessage): Promise<void> {\n try {\n const stream = this.deps.ai.audio.synthesizeStream({\n text: this.textQueue.iterate(),\n voice: start.voice,\n instruction: start.instruction,\n format: start.format,\n sampleRate: start.sampleRate,\n model: start.model,\n signal: this.controller.signal,\n })\n for await (const event of stream) {\n if (this.closed)\n return\n if (event.type === 'audio') {\n // 发送缓冲高水位保护:慢客户端导致底层缓冲膨胀时以领域错误关闭\n if (sendBufferedAmount(ws) > this.limits.maxSendBufferBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'client send buffer exceeded high-water mark')\n return\n }\n ws.send(toArrayBuffer(event.data))\n }\n else {\n this.send(ws, event)\n }\n }\n this.end(ws)\n }\n catch (error) {\n this.failFromError(ws, error)\n }\n }\n\n fail(ws: WSContext, code: string, message: string): void {\n if (this.closed)\n return\n this.state = 'failed'\n this.send(ws, { type: 'error', code, message } satisfies AudioWsServerMessage)\n this.dispose()\n ws.close()\n }\n\n private failFromError(ws: WSContext, error: unknown): void {\n if (this.closed)\n return\n const hai = error as Partial<HaiError>\n const code = typeof hai?.code === 'string' ? hai.code : CODE_TICKET\n const message = error instanceof Error ? error.message : String(error)\n this.fail(ws, code, message)\n }\n\n private end(ws: WSContext): void {\n if (this.closed)\n return\n this.state = 'completed'\n this.send(ws, { type: 'end' })\n this.dispose()\n ws.close()\n }\n\n private send(ws: WSContext, message: AudioWsServerMessage): void {\n if (!this.closed)\n ws.send(JSON.stringify(message))\n }\n\n dispose(): void {\n if (this.closed)\n return\n this.closed = true\n if (this.state !== 'completed' && this.state !== 'failed')\n this.state = 'cancelled'\n if (this.preAuthTimer) {\n clearTimeout(this.preAuthTimer)\n this.preAuthTimer = undefined\n }\n this.audioQueue.end()\n this.textQueue.end()\n // 级联取消:中止上游 provider 流与所有等待\n this.controller.abort()\n if (this.session && this.authorizedRequest && this.deps.onSessionEnd) {\n void Promise.resolve(this.deps.onSessionEnd(this.session, this.authorizedRequest)).catch((error: unknown) => {\n logger.warn('audio ws session end hook failed', { error: error instanceof Error ? error.message : String(error) })\n })\n }\n }\n}\n\n/** 读取 WebSocket 底层发送缓冲字节数(不可用时返回 0)。 */\nfunction sendBufferedAmount(ws: WSContext): number {\n const raw = (ws as { raw?: { bufferedAmount?: number } }).raw\n return typeof raw?.bufferedAmount === 'number' ? raw.bufferedAmount : 0\n}\n\n/** 将 WebSocket 消息数据转换为二进制字节(非二进制返回 undefined)。 */\nfunction toBytes(data: unknown): Uint8Array | undefined {\n if (typeof data === 'string')\n return undefined\n if (data instanceof ArrayBuffer)\n return new Uint8Array(data)\n if (ArrayBuffer.isView(data))\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\n if (Buffer.isBuffer(data))\n return new Uint8Array(data)\n return undefined\n}\n\n/** 顺序拼接音频分片。 */\nfunction concat(chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((sum, c) => sum + c.length, 0)\n const out = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n out.set(chunk, offset)\n offset += chunk.length\n }\n return out\n}\n\n/** 将 Uint8Array 转为独立 ArrayBuffer(供 WSContext.send 二进制发送)。 */\nfunction toArrayBuffer(data: Uint8Array): ArrayBuffer {\n const buffer = new ArrayBuffer(data.length)\n new Uint8Array(buffer).set(data)\n return buffer\n}\n","/**\n * @h-ai/serv — 校验工具\n *\n * 为 API service 提供与 kit 对等的校验 i18n 能力:\n * - {@link resolveRequestLocale}:从请求头(`x-hai-locale` / `Accept-Language`)解析语言。\n * - {@link validateInputOrFail}:在 procedure 内显式校验数据,失败返回 `HaiResult` 错误。\n * - oRPC contract 输入校验失败的自动本地化由 `serv-app.ts` 中的 oRPC 响应拦截器处理。\n *\n * @module serv-validation\n */\n\nimport type { HaiResult, ValidationFormError } from '@h-ai/core'\nimport type { ZodType } from 'zod'\nimport type { ServMessageKey } from './serv-i18n.js'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { normalizeServLocale, servM } from './serv-i18n.js'\n\nconst DEFAULT_LOCALE = 'zh-CN'\n\n/**\n * 校验失败响应体(含 i18n 错误列表)。\n *\n * 复用 `HaiResult` 失败分支结构。字段错误同时在两处出现:\n * - `error.cause`:与 `validateInputOrFail` 输出对齐,HaiResult 标准位置\n * - 顶层 `errors`:保留以兼容已有客户端\n */\nexport interface ServValidationFailureBody {\n readonly success: false\n readonly error: {\n readonly code: string | number\n readonly message: string\n readonly httpStatus: number\n readonly system: string\n readonly module: string\n readonly cause?: readonly ValidationFormError[]\n }\n readonly errors: readonly ValidationFormError[]\n}\n\n/**\n * 从请求头解析客户端期望 locale。\n *\n * 优先级:`x-hai-locale` > `Accept-Language` 第一个匹配项 > 默认值(`zh-CN`)。\n * 返回值会被规范化为 serv 当前支持的 locale(如 `en` → `en-US`)。\n *\n * @param headers - 请求头(`Headers` 或与之等价的对象)\n * @returns 解析得到的 locale\n */\nexport function resolveRequestLocale(headers: Headers | Record<string, string | undefined>): string {\n const get = (name: string): string | undefined => {\n if (headers instanceof Headers)\n return headers.get(name) ?? undefined\n return headers[name] ?? headers[name.toLowerCase()]\n }\n\n const explicit = get('x-hai-locale')\n if (explicit)\n return normalizeServLocale(explicit)\n\n return normalizeServLocale(get('accept-language') ?? get('Accept-Language') ?? DEFAULT_LOCALE)\n}\n\n/**\n * 把 Zod 错误转为本地化的扁平错误列表。\n *\n * @param zodError - Zod `SafeParseError` 或 `ZodError`\n * @param locale - 期望 locale\n */\nexport function localizeZodError(zodError: unknown, locale: string): ValidationFormError[] {\n const resolvedLocale = normalizeServLocale(locale)\n return core.zodValidation.mapZodErrorToFormErrors(\n zodError,\n core.zodValidation.createPrefixedZodMessageGetter<ServMessageKey>(\n 'serv',\n (messageKey, params) => servM(messageKey, { locale: resolvedLocale, params }),\n ),\n )\n}\n\n/**\n * 在 procedure handler 内显式校验数据。\n *\n * 与 oRPC contract 自动校验不同,本函数用于业务层二次校验(例如对从数据库读到的对象再校验)。\n * 失败时返回 `HaiResult` 错误分支,错误的 `cause` 字段携带 `ValidationFormError[]`。\n *\n * 推荐使用方式:\n * ```ts\n * import { z } from 'zod'\n * import { serv } from '@h-ai/serv'\n *\n * const Schema = z.object({ name: z.string().min(1) })\n *\n * export const handler = serv.mapHaiError(async ({ input, context }) => {\n * const result = serv.validateInputOrFail(Schema, input, context.locale)\n * if (!result.success) return result\n * const { name } = result.data\n * // ...\n * })\n * ```\n *\n * @param schema - Zod schema\n * @param input - 待校验数据\n * @param locale - 期望 locale(通常来自请求上下文)\n */\nexport function validateInputOrFail<TOutput>(\n schema: ZodType<TOutput>,\n input: unknown,\n locale: string = DEFAULT_LOCALE,\n): HaiResult<TOutput> {\n const resolvedLocale = normalizeServLocale(locale)\n const result = schema.safeParse(input)\n if (result.success)\n return ok(result.data)\n const errors = localizeZodError(result.error, resolvedLocale)\n return err(HaiCommonError.VALIDATION_ERROR, servM('serv_validationFailed', { locale: resolvedLocale }), errors)\n}\n","/**\n * @h-ai/serv — 内部 RPC 访问控制 middleware\n * @module pipelines/serv-pipeline-require-internal-rpc\n */\n\nimport type { Context } from 'hono'\nimport type { ServRpcHttpConfig } from '../serv-config.js'\nimport type { ServMiddleware } from './serv-pipeline-types.js'\nimport { core, HaiCommonError } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { resolveRequestLocale } from '../serv-validation.js'\nimport { buildHaiErrorBody } from './serv-pipeline-helper.js'\n\nconst pipelineLogger = core.logger.child({ module: 'serv', scope: 'pipeline' })\n\n/**\n * 懒加载 `@hono/node-server/conninfo`:仅在 Node 运行时可用。\n *\n * 在 Bun / Workers / Deno 等 Fetch runtime 下,该 sub-path 可能不存在;\n * 用 try/catch 包住模块解析,让模块在非 Node 平台也能加载而不抛错。\n * 解析失败时 `loopback` / `private-network` 模式会因取不到 IP 而 fail closed(403)。\n */\ntype GetConnInfoFn = (c: Context) => { remote: { address?: string } }\nlet cachedGetConnInfo: GetConnInfoFn | null | undefined\n\nasync function resolveGetConnInfo(): Promise<GetConnInfoFn | null> {\n if (cachedGetConnInfo !== undefined)\n return cachedGetConnInfo\n try {\n const mod = await import('@hono/node-server/conninfo')\n cachedGetConnInfo = mod.getConnInfo as GetConnInfoFn\n }\n catch (cause) {\n pipelineLogger.warn('getConnInfo unavailable in this runtime; loopback/private-network access modes will fail closed', { error: cause })\n cachedGetConnInfo = null\n }\n return cachedGetConnInfo\n}\n\nconst LOOPBACK_IPS = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1', 'localhost'])\n\n/**\n * 限制内部 RPC 入口来源。\n *\n * 应用层兜底校验;生产仍应优先依赖内网、服务网格或网关策略。\n *\n * @param config - RPC 访问控制配置\n * @returns Hono middleware\n *\n * @example\n * ```ts\n * app.use('/rpc/*', requireInternalRPC({ prefix: '/rpc', access: 'loopback' }))\n * ```\n */\nexport function requireInternalRPC(config: ServRpcHttpConfig): ServMiddleware {\n return async (c, next) => {\n // Step 1:先解析 locale,确保拒绝访问时也能返回本地化错误消息。\n const locale = resolveRequestLocale(c.req.raw.headers)\n\n // Step 2:若是 gateway-only 模式,直接校验网关注入的共享密钥 header。\n if (config.access === 'gateway-only') {\n const headerName = config.gatewayHeader ?? 'x-hai-internal-rpc'\n const received = c.req.header(headerName)\n if (!config.gatewaySecret || !received || !core.string.constantTimeEqual(received, config.gatewaySecret))\n return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale })), 403)\n return next()\n }\n\n // Step 3:其余模式依赖远端 IP 判定来源是否合法;取不到 IP 时按 fail closed 处理。\n const ip = await getRequestRemoteAddress(c) ?? ''\n\n // Step 4:loopback 模式只允许本机来源。\n if (config.access === 'loopback' && !isLoopbackIP(ip))\n return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale })), 403)\n\n // Step 5:private-network 模式允许内网来源(含 loopback)。\n if (config.access === 'private-network' && !isPrivateNetworkIP(ip))\n return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale })), 403)\n\n // Step 6:通过校验后,请求才会真正进入内部 RPC handler。\n return next()\n }\n}\n\nasync function getRequestRemoteAddress(c: Context): Promise<string | undefined> {\n const getConnInfo = await resolveGetConnInfo()\n if (!getConnInfo)\n return undefined\n try {\n return getConnInfo(c).remote.address\n }\n catch {\n return undefined\n }\n}\n\nfunction normalizeIpAddress(ip: string): string {\n return ip.startsWith('::ffff:') ? ip.slice('::ffff:'.length) : ip\n}\n\nfunction isLoopbackIP(ip: string): boolean {\n const normalized = normalizeIpAddress(ip)\n return LOOPBACK_IPS.has(ip) || LOOPBACK_IPS.has(normalized)\n}\n\nfunction isPrivateNetworkIP(ip: string): boolean {\n const normalized = normalizeIpAddress(ip)\n if (isLoopbackIP(ip))\n return true\n if (normalized.startsWith('10.') || normalized.startsWith('192.168.'))\n return true\n const second = Number(normalized.split('.')[1])\n return normalized.startsWith('172.') && Number.isInteger(second) && second >= 16 && second <= 31\n}\n","/**\n * @h-ai/serv — 默认安全响应头 middleware\n * @module pipelines/serv-pipeline-security-headers\n */\n\nimport type { ServMiddleware } from './serv-pipeline-types.js'\n\n/**\n * 添加基础安全响应头:\n * `X-Content-Type-Options: nosniff`、`X-Frame-Options: DENY`、`Referrer-Policy: no-referrer`。\n *\n * @returns Hono middleware\n */\nexport function securityHeaders(): ServMiddleware {\n return async (c, next) => {\n c.header('X-Content-Type-Options', 'nosniff')\n c.header('X-Frame-Options', 'DENY')\n c.header('Referrer-Policy', 'no-referrer')\n await next()\n }\n}\n","/**\n * @h-ai/serv — HTTP 配置\n *\n * 提供用于控制 API 前缀、OpenAPI、docs、健康检查、内部 RPC 与传输加密策略的配置结构,\n * 并由 zod 负责默认值填充与边界校验。\n * @module serv-config\n */\n\nimport { z } from 'zod'\n\nconst ApiPrefixSchema = z.custom<`/api/${string}`>(\n value => typeof value === 'string' && value.startsWith('/api/'),\n 'apiPrefix must start with /api/',\n)\n\nconst AbsolutePathSchema = z.custom<`/${string}`>(\n value => typeof value === 'string' && value.startsWith('/'),\n 'path must start with /',\n)\n\n/** OpenAPI JSON endpoint 配置。 */\nexport interface ServOpenAPIHttpConfig {\n readonly path: `/${string}`\n}\n\n/** 文档页面 endpoint 配置。 */\nexport interface ServDocsHttpConfig {\n readonly path: `/${string}`\n readonly requireAuth?: boolean\n}\n\n/** 健康检查 endpoint 配置。 */\nexport interface ServHealthHttpConfig {\n readonly path: `/${string}`\n readonly readyPath?: `/${string}`\n}\n\n/** 内部 RPC endpoint 配置。 */\nexport interface ServRpcHttpConfig {\n readonly prefix: `/${string}`\n readonly access: 'loopback' | 'private-network' | 'gateway-only'\n readonly gatewayHeader?: string\n readonly gatewaySecret?: string\n}\n\n/** 传输加密运行时配置(对应 `config/_serv.yml`)。 */\nexport interface ServTransportRuntimeConfig {\n readonly keyExchangePath: `/${string}`\n readonly excludePaths: readonly `/${string}`[]\n readonly maxClients: number\n}\n\n/** `config/_serv.yml` 中可序列化的跨端 CORS 配置。 */\nexport interface ServCorsRuntimeConfig {\n readonly origin?: string\n readonly nativeOrigins?: string\n readonly allowedHeaders: readonly string[]\n readonly exposedHeaders: readonly string[]\n readonly credentials: boolean\n}\n\n/** ServHttpApp 的 HTTP 挂载配置。 */\nexport interface ServHttpConfig {\n readonly apiPrefix: `/api/${string}`\n readonly openapi: false | ServOpenAPIHttpConfig\n readonly docs: false | ServDocsHttpConfig\n readonly health: false | ServHealthHttpConfig\n readonly rpc: false | ServRpcHttpConfig\n}\n\n/** `config/_serv.yml` 的顶层配置结构。 */\nexport interface ServConfig {\n readonly http: ServHttpConfig\n readonly cors: ServCorsRuntimeConfig\n readonly transport: false | ServTransportRuntimeConfig\n}\n\n/** `config/_serv.yml` 中的 CORS 输入类型。 */\nexport type ServCorsRuntimeConfigInput = Partial<ServCorsRuntimeConfig>\n\n/** `config/_serv.yml` 中的传输加密输入类型。 */\nexport type ServTransportRuntimeConfigInput = Partial<{\n readonly keyExchangePath: `/${string}`\n readonly excludePaths: readonly `/${string}`[]\n readonly maxClients: number\n}>\n\n/** `serv.createApp` 接受的 HTTP 配置入参类型。 */\nexport type ServHttpConfigInput = Partial<{\n readonly apiPrefix: `/api/${string}`\n readonly openapi: false | Partial<ServOpenAPIHttpConfig>\n readonly docs: false | Partial<ServDocsHttpConfig>\n readonly health: false | Partial<ServHealthHttpConfig>\n readonly rpc: false | Partial<ServRpcHttpConfig>\n}>\n\n/** `config/_serv.yml` 的顶层输入类型。 */\nexport interface ServConfigInput {\n readonly http?: ServHttpConfigInput\n readonly cors?: ServCorsRuntimeConfigInput\n readonly transport?: false | ServTransportRuntimeConfigInput\n}\n\nconst OpenAPIHttpConfigInputSchema = z.union([\n z.literal(false),\n z.object({ path: AbsolutePathSchema.default('/openapi.json') }),\n])\n\nconst DocsHttpConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n path: AbsolutePathSchema.default('/docs'),\n requireAuth: z.boolean().optional(),\n }),\n])\n\nconst HealthHttpConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n path: AbsolutePathSchema.default('/health'),\n readyPath: AbsolutePathSchema.optional().default('/ready'),\n }),\n])\n\nconst RpcHttpConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n prefix: AbsolutePathSchema.default('/rpc'),\n access: z.enum(['loopback', 'private-network', 'gateway-only']).default('loopback'),\n gatewayHeader: z.string().optional(),\n gatewaySecret: z.string().optional(),\n }),\n])\n\nconst TransportRuntimeConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n keyExchangePath: AbsolutePathSchema.default('/_hai/key-exchange'),\n excludePaths: z.array(AbsolutePathSchema).default([]),\n maxClients: z.number().int().positive().default(10000),\n }),\n])\n\nconst CorsRuntimeConfigInputSchema = z.object({\n origin: z.string().optional(),\n nativeOrigins: z.string().optional(),\n allowedHeaders: z.array(z.string()).default(['Authorization', 'Content-Type', 'X-Requested-With']),\n exposedHeaders: z.array(z.string()).default([]),\n credentials: z.boolean().default(false),\n})\n\nconst ServHttpConfigInputSchema = z.object({\n apiPrefix: ApiPrefixSchema.default('/api/v1'),\n openapi: OpenAPIHttpConfigInputSchema.default(false),\n docs: DocsHttpConfigInputSchema.default(false),\n health: HealthHttpConfigInputSchema.default({ path: '/health', readyPath: '/ready' }),\n rpc: RpcHttpConfigInputSchema.default(false),\n})\n\n/** `config/_serv.yml` 对应的配置 Schema。 */\nexport const ServConfigSchema = z.object({\n http: ServHttpConfigInputSchema.optional(),\n cors: CorsRuntimeConfigInputSchema.optional(),\n transport: TransportRuntimeConfigInputSchema.optional(),\n}).transform(({ http, cors, transport }) => ({\n http: resolveServHttpConfig(http ?? {}),\n cors: resolveServCorsRuntimeConfig(cors ?? {}),\n transport: resolveServTransportRuntimeConfig(transport ?? false),\n}))\n\n/**\n * 解析 HTTP 配置并补齐默认值。\n *\n * @param input - 用户传入的部分 HTTP 配置\n * @returns 完整 HTTP 配置\n */\nexport function resolveServHttpConfig(input: ServHttpConfigInput = {}): ServHttpConfig {\n return ServHttpConfigInputSchema.parse(input)\n}\n\n/** 解析配置文件中的跨端 CORS Header 与凭据策略。 */\nexport function resolveServCorsRuntimeConfig(input: ServCorsRuntimeConfigInput = {}): ServCorsRuntimeConfig {\n return CorsRuntimeConfigInputSchema.parse(input)\n}\n\n/**\n * 解析传输加密配置并补齐默认值。\n *\n * @param input - `config/_serv.yml` 中的 transport 配置\n * @returns 完整 transport 配置;`false` 表示关闭\n */\nexport function resolveServTransportRuntimeConfig(\n input: false | ServTransportRuntimeConfigInput = false,\n): false | ServTransportRuntimeConfig {\n return TransportRuntimeConfigInputSchema.parse(input)\n}\n\n/**\n * 解析 `config/_serv.yml` 顶层配置并补齐默认值。\n *\n * 典型用法:`core.config.validate('serv', ServConfigSchema)`。\n */\nexport function resolveServConfig(input: ServConfigInput = {}): ServConfig {\n return ServConfigSchema.parse(input)\n}\n","/**\n * @h-ai/serv — 请求上下文与认证工厂\n *\n * 定义 oRPC procedures 共享的统一上下文结构(requestId / accessToken / session / logger)。\n *\n * 上下文构造分两层(职责分明):\n * - `parseRequestContext`:**纯同步元数据解析**,从 HTTP header 提取 requestId/locale/ip/userAgent\n * 与 Bearer accessToken。**不会**填充 `session`。\n * - `buildAuthContextFactory(verifyToken)`:**带认证的上下文工厂**。在 `parseRequestContext` 基础上\n * 异步调用 `verifyToken(accessToken)` 校验访问令牌,若成功则把会话写入 `context.session`。\n * 失败或 token 不存在均使 `session = undefined`,由下游 `requireAuth` 统一返回 401。\n *\n * 应用层通常无需直接调用上述函数:`serv.createApp({ iam })` 内部自动选择最合适的工厂。\n * @module serv-context\n */\n\nimport type { HaiResult, Logger } from '@h-ai/core'\nimport type { RefreshTokenPair } from './serv-cookie-auth.js'\nimport { core } from '@h-ai/core'\nimport { resolveRequestLocale } from './serv-validation.js'\n\n/** 当前请求解析出的会话摘要。 */\nexport interface ServSession {\n readonly userId: string\n readonly username?: string\n readonly roles: string[]\n readonly permissions: string[]\n}\n\n/**\n * serv 与 IAM 模块交互所需的**最小**接口。\n *\n * 结构上是 `@h-ai/iam` 的 `iam` 对象的子集 —— 直接传入 `iam` 即可(TypeScript 结构类型)。\n * 单一出处:在 `serv.createApp({ iam })` 提供后,serv 自动派生:\n * - access token 校验(用于填充 `context.session`)\n * - refresh token 轮换(仅当启用 `refreshCookie` 时使用)\n *\n * 模块消费者**无需**知道 IAM 内部 Session 类型与 ServSession 的差异:结构类型确保兼容。\n */\nexport interface ServIam {\n readonly session: {\n /** 校验访问令牌,成功返回会话快照(结构上是 ServSession 的超集即可)。 */\n verifyToken: (accessToken: string) => Promise<HaiResult<ServSession>>\n /** 使用 refresh token 换发新的 token 对。仅 `refreshCookie` 启用时被调用。 */\n refresh: (refreshToken: string) => Promise<HaiResult<RefreshTokenPair>>\n }\n}\n\n/** `@h-ai/serv` 在 procedures 中传递的统一上下文。 */\nexport interface ServContext {\n readonly requestId: string\n readonly locale: string\n readonly ip?: string\n readonly userAgent?: string\n readonly accessToken?: string\n readonly session?: ServSession\n readonly request: Request\n readonly logger: Logger\n}\n\n/** 创建上下文所需的输入。 */\nexport interface CreateServContextInput {\n readonly request: Request\n}\n\n/** 应用可传入的上下文工厂。 */\nexport type CreateServContext = (input: CreateServContextInput) => ServContext | Promise<ServContext>\n\nconst HEADER_REQUEST_ID = 'x-request-id'\nconst HEADER_FORWARDED_FOR = 'x-forwarded-for'\nconst HEADER_REAL_IP = 'x-real-ip'\nconst HEADER_AUTHORIZATION = 'authorization'\nconst HEADER_USER_AGENT = 'user-agent'\n\n/**\n * 从 Authorization header 中提取 Bearer token。\n *\n * 这里只处理 **access token**。\n * 若启用了 `refreshCookie`,refresh token 不会出现在 Authorization header 中,\n * 而是通过浏览器 httpOnly cookie 或受信原生请求的 JSON body 传输,\n * 并只在 `/auth/refresh` 路由里读取。\n *\n * @param value - Authorization header 原始值\n * @returns token 或 undefined\n */\nexport function extractBearerToken(value: string | null): string | undefined {\n if (!value)\n return undefined\n const [scheme, token] = value.split(' ')\n if (scheme?.toLowerCase() !== 'bearer' || !token)\n return undefined\n return token\n}\n\n/**\n * 解析 HTTP 请求元数据并构造基础 ServContext(**同步**,**不**填充 session)。\n *\n * - 提取 `x-request-id` / `accept-language` / `x-forwarded-for` / `x-real-ip` / `user-agent`\n * - 从 `Authorization: Bearer <token>` 提取 `accessToken`\n * - **不会**读取 refresh token:refresh token 若启用 `refreshCookie`,只走专属刷新路由\n *\n * `session` 字段始终为 `undefined`:若需会话填充,请使用 {@link buildAuthContextFactory}\n * 或直接通过 `serv.createApp({ iam })` 让框架自动选择合适工厂。\n *\n * @param input - 上下文输入\n * @returns 仅含元数据的 ServContext\n */\nexport function parseRequestContext(input: CreateServContextInput): ServContext {\n const headers = input.request.headers\n const requestId = headers.get(HEADER_REQUEST_ID) ?? core.id.uuid()\n const forwardedFor = headers.get(HEADER_FORWARDED_FOR)\n const ip = headers.get(HEADER_REAL_IP) ?? forwardedFor?.split(',')[0]?.trim()\n const locale = resolveRequestLocale(headers)\n const accessToken = extractBearerToken(headers.get(HEADER_AUTHORIZATION))\n\n return {\n requestId,\n locale,\n ip,\n userAgent: headers.get(HEADER_USER_AGENT) ?? undefined,\n accessToken,\n request: input.request,\n logger: core.logger.child({ module: 'serv', requestId }),\n }\n}\n\n/**\n * 构造**带认证的上下文工厂**:在 {@link parseRequestContext} 基础上异步填充 `context.session`。\n *\n * 工作流程(每个请求执行一次):\n * 1. 调用 `parseRequestContext` 提取元数据 + accessToken(仅来自 Authorization Bearer)\n * 2. 若 `accessToken` 存在 → 调用 `verifyToken(accessToken)`\n * 3. `verifyToken` 成功 → `session = result.data`;失败或 token 缺失 → `session = undefined`\n * 4. 下游 `requireAuth` / `requirePermission` / `requireRole` 据此判断 401/403\n *\n * refresh token **不参与**这里的认证流程:它只在 access token 过期后,被 `/auth/refresh`\n * 从浏览器 httpOnly cookie 或受信原生 JSON body 中读出,用于换发新的 access token。\n *\n * **安全保证**:\n * - 每个请求都会重新调用 `verifyToken`(不缓存),用户被撤权后立即生效\n * - `verifyToken` 失败时**不**抛错,统一收敛为 `session=undefined` → 401\n * - 异常通过 try/catch 兜底,避免向 HTTP 层泄漏堆栈\n *\n * @param verifyToken - 访问令牌校验回调(通常是 `token => iam.session.verifyToken(token)` 或自定义函数)\n * @returns 异步上下文工厂\n */\nexport function buildAuthContextFactory(\n verifyToken: (accessToken: string) => Promise<HaiResult<ServSession>>,\n): CreateServContext {\n return async (input) => {\n // Step 1:先同步解析请求头,拿到 requestId / locale / accessToken 等基础元数据。\n const base = parseRequestContext(input)\n // Step 2:没有 access token 时,直接返回基础上下文;后续由 requireAuth 统一判 401。\n if (!base.accessToken)\n return base\n try {\n // Step 3:有 token 时才触发异步校验;成功则把 session 注入上下文。\n const result = await verifyToken(base.accessToken)\n if (!result.success)\n return base\n // Step 4:返回带 session 的上下文,供 requireAuth / requirePermission / requireRole 使用。\n return { ...base, session: result.data }\n }\n catch {\n // Step 5:verifyToken 抛错(不规范)也视为未认证;fail closed,不向上传播。\n return base\n }\n }\n}\n","/**\n * @h-ai/serv — Refresh Token 浏览器 / 原生传输\n *\n * **职责边界**:本文件 **仅** 负责 `refresh token` 的传输,不涉及 access token 校验。\n * - access token 校验由 `serv-context.ts` 的 `buildAuthContextFactory` 统一处理\n * - 浏览器使用 httpOnly cookie,避免 refresh token 进入 JS 可读存储\n * - 经过应用可信来源校验的原生客户端可改用 JSON body,交由系统安全存储持久化\n *\n * **挂载的路由**:\n * - login / register / loginWithOtp:浏览器写 Cookie 并擦除响应 token;受信原生请求保留响应 token\n * - logout:浏览器清除 Cookie(Max-Age=0);原生客户端自行清理安全存储\n * - refresh:**专属路由**(不走 oRPC),按请求来源从 Cookie 或 JSON body 读取并轮换 token\n *\n * **Cookie 规格**:`HttpOnly; SameSite=Strict; Secure`(生产)/ 无 Secure(开发)\n * Cookie Path 限制为 `{apiPrefix}/auth/refresh`,最小化浏览器自动发送范围(避免 CSRF 攻击面)。\n * @module serv-cookie-auth\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { Hono } from 'hono'\nimport type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport type { ServIam } from './serv-context.js'\nimport process from 'node:process'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, HaiCommonError } from '@h-ai/core'\nimport { getCookie } from 'hono/cookie'\nimport { buildHaiErrorBody } from './pipelines/serv-pipeline-helper.js'\nimport { servM } from './serv-i18n.js'\nimport { resolveRequestLocale } from './serv-validation.js'\n\n// ─── 公共类型 ─────────────────────────────────────────────────────────────────\n\n/**\n * Refresh 流程返回的 Token 对结构。\n * 与 `@h-ai/iam` 的 `TokenPair` 结构兼容;此处独立定义以避免对可选 peer dep 的强依赖。\n */\nexport interface RefreshTokenPair {\n readonly accessToken: string\n readonly refreshToken: string\n readonly expiresIn?: number\n readonly tokenType?: string\n}\n\ntype SafeRefreshTokenPair = Omit<RefreshTokenPair, 'refreshToken'>\n\n/**\n * 原生客户端的 refresh token body 传输配置。\n *\n * 浏览器继续使用 httpOnly cookie;只有经过应用可信来源校验的原生请求,才允许在\n * JSON body 中接收和返回 refresh token。`isRequest` 必须同时校验客户端标识与 Origin,\n * 不能仅信任可伪造的单个请求头。\n */\nexport interface NativeRefreshTokenTransportConfig {\n /** 判断当前请求是否来自受信原生客户端。 */\n readonly isRequest: (request: Request) => boolean\n}\n\n/**\n * Refresh Token 传输配置。\n *\n * 传入 `serv.createApp({ refreshCookie: ... })` 后自动管理浏览器 cookie,并可选启用受信原生 body 通道。\n * access token 仍然走 `Authorization: Bearer <token>`;本配置只改变 refresh token 的传输通道。\n *\n * 刷新回调来源(优先级从高到低):\n * 1. `onRefresh`(显式覆盖)\n * 2. 顶层 `iam.session.refresh`(推荐:`createApp({ iam, refreshCookie: {} })` 即开箱可用)\n */\nexport interface RefreshCookieConfig {\n /** Cookie 名,默认 `hai_refresh_token`。 */\n readonly cookieName?: string\n /**\n * Cookie Max-Age(秒),默认 `30 * 24 * 3600`(30 天)。\n * 应与 IAM 模块的 refresh token TTL 对齐。\n */\n readonly maxAge?: number\n /**\n * 强制 Secure 属性。\n * - 未传时:`NODE_ENV === 'production'` 时自动开启。\n * - 本地 HTTP 开发设为 `false` 可让 Cookie 在 HTTP 下正常工作。\n */\n readonly secure?: boolean\n /**\n * 为受信原生客户端启用 JSON body 传输。\n *\n * 启用后,匹配请求的登录、注册与刷新响应保留 `refreshToken`,刷新请求从\n * `{ refreshToken }` body 读取凭证,且不读写浏览器 Cookie。\n */\n readonly nativeTokenTransport?: NativeRefreshTokenTransportConfig\n /**\n * 自定义刷新回调(高级用法)。\n * 未提供时使用顶层 `iam.session.refresh`;两者均缺失时 `createApp` 会抛错。\n */\n readonly onRefresh?: (refreshToken: string) => Promise<HaiResult<RefreshTokenPair>>\n}\n\n// ─── 内部解析 ─────────────────────────────────────────────────────────────────\n\ninterface ResolvedRefreshCookieConfig {\n cookieName: string\n maxAge: number\n isSecure: boolean\n refreshCookiePath: string\n onRefresh: (refreshToken: string) => Promise<HaiResult<RefreshTokenPair>>\n nativeTokenTransport: NativeRefreshTokenTransportConfig | undefined\n}\n\nconst IAM_AUTH_PATHS = {\n login: apiContract.pathOf(apiContract.iam.auth.login),\n loginWithOtp: apiContract.pathOf(apiContract.iam.auth.loginWithOtp),\n register: apiContract.pathOf(apiContract.iam.auth.register),\n logout: apiContract.pathOf(apiContract.iam.auth.logout),\n refresh: apiContract.pathOf(apiContract.iam.auth.refresh),\n}\n\nfunction resolveRefreshCookieConfig(\n apiPrefix: string,\n config: RefreshCookieConfig,\n iam: ServIam | undefined,\n): ResolvedRefreshCookieConfig {\n // ⚠️ 不 `.bind(iam.session)`:IAM NotInitializedKit Proxy 在 init 后才切换为真实实现,\n // 提前 bind 会捕获未初始化 Proxy。用闭包延迟到调用点再解析。\n const onRefresh = config.onRefresh\n ?? (iam ? (refreshToken: string) => iam.session.refresh(refreshToken) : undefined)\n if (!onRefresh) {\n // 抛 HaiError 实例而非裸 Error:保留 code/system/module,便于上层错误处理识别这是\n // 配置缺失而非业务异常。该错误只在 createApp 装配阶段出现,不会泄漏到 HTTP 响应。\n throw core.error.buildHaiErrorInst(\n HaiCommonError.INTERNAL_ERROR,\n '[serv] refreshCookie requires either top-level `iam` (recommended) or `refreshCookie.onRefresh` callback',\n )\n }\n return {\n cookieName: config.cookieName ?? 'hai_refresh_token',\n maxAge: config.maxAge ?? 30 * 24 * 3600,\n isSecure: config.secure ?? (process.env.NODE_ENV === 'production'),\n refreshCookiePath: `${apiPrefix}${IAM_AUTH_PATHS.refresh}`,\n onRefresh,\n nativeTokenTransport: config.nativeTokenTransport,\n }\n}\n\n// ─── Cookie 字符串构建 ────────────────────────────────────────────────────────\n\nfunction buildSetCookieHeader(\n name: string,\n value: string,\n path: string,\n maxAge: number,\n secure: boolean,\n): string {\n const parts = [\n `${name}=${encodeURIComponent(value)}`,\n `Path=${path}`,\n `Max-Age=${maxAge}`,\n 'HttpOnly',\n 'SameSite=Strict',\n ]\n if (secure)\n parts.push('Secure')\n return parts.join('; ')\n}\n\nfunction buildClearCookieHeader(name: string, path: string): string {\n return `${name}=; Path=${path}; Max-Age=0; HttpOnly; SameSite=Strict`\n}\n\n// ─── 响应体解析 ───────────────────────────────────────────────────────────────\n\n/**\n * 从 oRPC 成功响应的 `data` 字段中提取 refresh token。\n * 兼容两种结构:\n * - login/register: `{ tokens: { refreshToken, ... }, user, ... }`\n * - (保留)直接含 `{ tokens: { refreshToken } }` 的 data\n */\nfunction extractRefreshTokenFromData(data: unknown): string | undefined {\n if (!data || typeof data !== 'object')\n return undefined\n const d = data as Record<string, unknown>\n const tokens = d.tokens\n if (!tokens || typeof tokens !== 'object')\n return undefined\n const t = tokens as Record<string, unknown>\n return typeof t.refreshToken === 'string' && t.refreshToken ? t.refreshToken : undefined\n}\n\n/**\n * 擦除响应体中的 refreshToken,确保 httpOnly 模式下 JS 无法读取长期凭证。\n *\n * 这与 `Set-Cookie` 写入 refresh token **不是重复操作**,而是刻意把两条信道分开:\n * - `Set-Cookie`:交给浏览器保存 refresh token(httpOnly,JS 不可读)\n * - JSON 响应体:交给前端 JS 消费,因此必须删除 `refreshToken`\n */\nfunction stripRefreshTokenFromData(data: unknown): unknown {\n if (!data || typeof data !== 'object' || Array.isArray(data))\n return data\n const d = data as Record<string, unknown>\n const tokens = d.tokens\n if (!tokens || typeof tokens !== 'object' || Array.isArray(tokens))\n return data\n\n const safeTokens: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(tokens)) {\n if (key !== 'refreshToken')\n safeTokens[key] = value\n }\n return { ...d, tokens: safeTokens }\n}\n\nfunction stripRefreshTokenFromPair(tokens: RefreshTokenPair): SafeRefreshTokenPair {\n return {\n accessToken: tokens.accessToken,\n ...(tokens.expiresIn === undefined ? {} : { expiresIn: tokens.expiresIn }),\n ...(tokens.tokenType === undefined ? {} : { tokenType: tokens.tokenType }),\n }\n}\n\n// ─── 路由挂载 ─────────────────────────────────────────────────────────────────\n\n/* 由 createApp 在 mountOpenAPIRoutes 之前调用,在 Hono app 上挂载\n * cookie 认证相关路由。Hono 按注册顺序匹配路由,因此必须在 oRPC 通配符路由之前注册。 */\nexport function mountRefreshCookieRoutes(\n app: Hono,\n apiPrefix: string,\n config: RefreshCookieConfig,\n iam: ServIam | undefined,\n): void {\n const {\n cookieName,\n maxAge,\n isSecure,\n refreshCookiePath,\n onRefresh,\n nativeTokenTransport,\n } = resolveRefreshCookieConfig(apiPrefix, config, iam)\n\n // ─── login / register / loginWithOtp:oRPC 处理后,按客户端类型选择 token 传输通道 ──\n // 路径直接从 apiContract.iam 的 route 元数据读取,保持 contract 为唯一来源。\n const loginPaths = [\n `${apiPrefix}${IAM_AUTH_PATHS.login}`,\n `${apiPrefix}${IAM_AUTH_PATHS.loginWithOtp}`,\n `${apiPrefix}${IAM_AUTH_PATHS.register}`,\n ]\n for (const path of loginPaths) {\n app.post(path, async (c, next) => {\n const useNativeTokenTransport = nativeTokenTransport?.isRequest(c.req.raw) === true\n await next()\n if (c.res.status !== 200)\n return\n // 消费响应体后必须重建 Response(ReadableStream 只能消费一次)\n const bodyText = await c.res.text()\n const headers = new Headers(c.res.headers)\n let responseBody = bodyText\n try {\n const body = JSON.parse(bodyText) as { success?: boolean, data?: unknown }\n if (body.success) {\n const refreshToken = extractRefreshTokenFromData(body.data)\n if (!useNativeTokenTransport) {\n // 刻意保留“写 cookie + 擦响应体”两步:\n // - cookie 让浏览器持久保存 refresh token,用于后续 /auth/refresh\n // - body 中删掉 refreshToken,避免任何前端 JS 直接读到长期凭证\n if (refreshToken)\n headers.append('Set-Cookie', buildSetCookieHeader(cookieName, refreshToken, refreshCookiePath, maxAge, isSecure))\n body.data = stripRefreshTokenFromData(body.data)\n responseBody = JSON.stringify(body)\n headers.delete('Content-Length')\n }\n }\n }\n catch { /* 非 JSON 响应,跳过 cookie 写入 */ }\n c.res = new Response(responseBody, { status: c.res.status, statusText: c.res.statusText, headers })\n })\n }\n\n // ─── logout:oRPC 处理后,拦截成功响应清除 cookie ────────────────────────────\n app.post(`${apiPrefix}${IAM_AUTH_PATHS.logout}`, async (c, next) => {\n const useNativeTokenTransport = nativeTokenTransport?.isRequest(c.req.raw) === true\n await next()\n if (c.res.status !== 200)\n return\n const bodyText = await c.res.text()\n const headers = new Headers(c.res.headers)\n try {\n const body = JSON.parse(bodyText) as { success?: boolean }\n if (body.success && !useNativeTokenTransport)\n headers.append('Set-Cookie', buildClearCookieHeader(cookieName, refreshCookiePath))\n }\n catch { /* 非 JSON,跳过 */ }\n c.res = new Response(bodyText, { status: c.res.status, statusText: c.res.statusText, headers })\n })\n\n // ─── refresh:专属路由,绕过 oRPC,直接从 cookie 读取 token 并刷新 ─────────\n // 此路由须在 oRPC 通配符之前注册,Hono 才能正确匹配。\n app.post(`${apiPrefix}${IAM_AUTH_PATHS.refresh}`, async (c) => {\n // access token 认证不走这里;这里只有“用 refresh token 换新 access token”这一件事。\n const useNativeTokenTransport = nativeTokenTransport?.isRequest(c.req.raw) === true\n const refreshToken = useNativeTokenTransport\n ? await readRefreshTokenFromBody(c.req.raw)\n : getCookie(c, cookieName)\n if (!refreshToken) {\n const locale = resolveRequestLocale(c.req.raw.headers)\n return c.json(buildHaiErrorBody(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale })), 401)\n }\n\n const result = await onRefresh(refreshToken)\n if (!result.success) {\n const status = (result.error.httpStatus ?? 401) as ContentfulStatusCode\n // 浏览器刷新失败时清除失效 cookie,避免客户端循环重试;原生客户端自行清理安全存储。\n const headers = new Headers({ 'Content-Type': 'application/json' })\n if (!useNativeTokenTransport)\n headers.set('Set-Cookie', buildClearCookieHeader(cookieName, refreshCookiePath))\n return new Response(JSON.stringify({ success: false, error: result.error }), {\n status,\n headers,\n })\n }\n\n const tokens = result.data\n if (useNativeTokenTransport) {\n return new Response(JSON.stringify({ success: true, data: { tokens } }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n const setCookie = buildSetCookieHeader(cookieName, tokens.refreshToken, refreshCookiePath, maxAge, isSecure)\n return new Response(JSON.stringify({ success: true, data: { tokens: stripRefreshTokenFromPair(tokens) } }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Set-Cookie': setCookie,\n },\n })\n })\n}\n\n/**\n * 从受信原生客户端的 JSON body 读取 refresh token。\n *\n * 非 JSON、空 body 与空字符串统一视为未提供凭证,由调用方返回 401。\n */\nasync function readRefreshTokenFromBody(request: Request): Promise<string | undefined> {\n const body = await request.json().catch(() => undefined) as { refreshToken?: unknown } | undefined\n return typeof body?.refreshToken === 'string' && body.refreshToken.length > 0\n ? body.refreshToken\n : undefined\n}\n","/**\n * @h-ai/serv — OpenAPI 文档\n *\n * 由 oRPC contract 生成 OpenAPI 3.1 spec,并集中处理 Scalar 文档页 HTML\n * 与本地脚本资源读取逻辑。\n * @module serv-openapi\n */\n\nimport type { AnyContractRouter, OpenAPI } from '@orpc/contract'\nimport { readFile } from 'node:fs/promises'\nimport { OpenAPIGenerator } from '@orpc/openapi'\nimport { ZodToJsonSchemaConverter } from '@orpc/zod/zod4'\n\n/** Scalar UI 脚本本地路由,由 createApp 自动挂载,无需外网 CDN。 */\nexport const SCALAR_ROUTE = '/_hai/scalar.js'\n\n/** Scalar browser bundle 兜底路径;优先使用 package.json 的 browser 字段。 */\nconst SCALAR_BROWSER_ENTRY_FALLBACK = './dist/browser/standalone.js'\n\n/** 缓存 Scalar UI 脚本内容(启动后不变,缓存避免重复读取)。 */\nlet scalarScriptCache: string | undefined\n\n/** OpenAPI spec 生成配置。 */\nexport interface GenerateOpenAPISpecOptions {\n readonly title?: string\n readonly version?: string\n readonly apiPrefix?: string\n readonly description?: string\n}\n\n/**\n * 由应用级 contract 生成 OpenAPI 3.1 spec。\n *\n * @param contract - 已组合完成的应用级 oRPC contract\n * @param options - 文档元信息\n * @returns OpenAPI 文档对象\n *\n * @example\n * ```ts\n * const spec = await serv.generateSpec(contract, {\n * title: 'My API',\n * version: '1.0.0',\n * apiPrefix: '/api/v1',\n * })\n * ```\n */\nexport async function generateSpec(\n contract: AnyContractRouter,\n options: GenerateOpenAPISpecOptions = {},\n): Promise<OpenAPI.Document> {\n const generator = new OpenAPIGenerator({\n schemaConverters: [new ZodToJsonSchemaConverter()],\n })\n\n return generator.generate(contract, {\n info: {\n title: options.title ?? 'hai-framework API',\n version: options.version ?? '0.1.0',\n description: options.description,\n },\n servers: options.apiPrefix ? [{ url: options.apiPrefix }] : undefined,\n security: [{ bearerAuth: [] }],\n components: {\n securitySchemes: {\n bearerAuth: { type: 'http', scheme: 'bearer' },\n },\n },\n })\n}\n\n/** 文档页面创建配置。 */\nexport interface CreateDocsPageOptions {\n readonly title?: string\n readonly specUrl?: string\n /**\n * Scalar UI 脚本 URL。\n *\n * 默认为 `/_hai/scalar.js`(由 `createApp` 自动挂载的本地路由),无需访问外网 CDN。\n * 如需指向 CDN,可设为 `https://cdn.jsdelivr.net/npm/@scalar/api-reference`。\n */\n readonly scriptUrl?: string\n}\n\n/**\n * 读取本地 Scalar browser bundle。\n *\n * 供 `createApp()` 在 docs 路由中按需挂载 `/_hai/scalar.js` 使用。\n */\nexport async function getScalarScript(): Promise<string | undefined> {\n if (scalarScriptCache !== undefined)\n return scalarScriptCache || undefined\n try {\n const fileUrl = await resolveScalarBrowserScriptUrl()\n scalarScriptCache = await readFile(fileUrl, 'utf8')\n }\n catch {\n scalarScriptCache = ''\n }\n return scalarScriptCache || undefined\n}\n\n/**\n * 创建轻量 API 文档 HTML。\n *\n * 默认使用本地路由 `/_hai/scalar.js` 加载 Scalar UI 脚本,无 CDN 依赖。\n *\n * @param spec - OpenAPI spec,可用于离线内嵌\n * @param options - 文档页选项\n * @returns HTML 字符串\n */\nexport function createDocsPage(spec: OpenAPI.Document, options: CreateDocsPageOptions = {}): string {\n const title = escapeHtml(options.title ?? spec.info.title)\n const specUrl = options.specUrl\n const scriptUrl = escapeHtml(options.scriptUrl ?? SCALAR_ROUTE)\n\n if (specUrl) {\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${title}</title></head><body><script id=\"api-reference\" data-url=\"${escapeHtml(specUrl)}\"></script><script src=\"${scriptUrl}\"></script></body></html>`\n }\n\n const encodedSpec = escapeScript(JSON.stringify(spec))\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${title}</title></head><body><script id=\"api-reference\" type=\"application/json\">${encodedSpec}</script><script src=\"${scriptUrl}\"></script></body></html>`\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(String.fromCharCode(39), ''')\n}\n\nfunction escapeScript(value: string): string {\n return value.replaceAll('</script', '<\\\\/script')\n}\n\nasync function resolveScalarBrowserScriptUrl(): Promise<URL> {\n const packageEntryUrl = import.meta.resolve('@scalar/api-reference')\n const packageJsonUrl = new URL('../package.json', packageEntryUrl)\n const packageInfo: unknown = JSON.parse(await readFile(packageJsonUrl, 'utf8'))\n const browserEntry = readScalarBrowserEntry(packageInfo) ?? SCALAR_BROWSER_ENTRY_FALLBACK\n return new URL(browserEntry, packageJsonUrl)\n}\n\nfunction readScalarBrowserEntry(packageInfo: unknown): string | undefined {\n if (typeof packageInfo !== 'object' || packageInfo === null || Array.isArray(packageInfo))\n return undefined\n const browser = (packageInfo as Record<string, unknown>).browser\n return typeof browser === 'string' ? browser : undefined\n}\n","/**\n * @h-ai/serv — 传输加密集成\n *\n * 提供 Hono 中间件 + 密钥协商端点,直接依赖 `@h-ai/crypto` 的\n * `TransportEncryptionManager` 与 `TRANSPORT_PROTOCOL` 协议常量,\n * 确保前后端协议不漂移。\n *\n * 单次请求的处理顺序:\n * 1. 特判密钥协商端点\n * 2. 跳过不参与加解密的路径\n * 3. 校验 clientId 与已注册公钥\n * 4. 解密请求体并把明文 request 回写给下游\n * 5. 执行业务逻辑\n * 6. 尝试加密 JSON 响应\n *\n * @module serv-transport\n */\n\nimport type { CryptoFunctions, EncryptedPayload, TransportEncryptionManager, TransportKeyStore } from '@h-ai/crypto'\nimport type { MiddlewareHandler } from 'hono'\nimport { core } from '@h-ai/core'\nimport { TRANSPORT_PROTOCOL } from '@h-ai/crypto'\nimport { servM } from './serv-i18n.js'\n\nconst logger = core.logger.child({ module: 'serv', scope: 'transport' })\n\n/** 单次响应加密的体积上限(1 MiB)。超过则 fail-closed,避免内存放大和明文泄露。 */\nconst MAX_ENCRYPTED_BODY = 1_048_576\nconst ENCRYPTABLE_RESPONSE_CONTENT_TYPES = ['application/json']\n\n/** {@link createApp} 的 `transport` 配置项。 */\nexport interface ServTransportConfig {\n /**\n * crypto 服务实例。serv 通过 `crypto.transport.createServer()` 创建\n * 传输加密管理器,并使用 `crypto.transport.protocol` 中的协议常量。\n */\n readonly crypto: CryptoFunctions\n /**\n * 密钥协商端点路径(相对于 `apiPrefix`)。默认 `/_hai/key-exchange`。\n */\n readonly keyExchangePath?: string\n /**\n * 不参与加解密的路径白名单(绝对路径完整匹配或前缀匹配)。\n * 健康检查、文档页等公共路由通常无需加密。\n */\n readonly excludePaths?: readonly string[]\n /** 共享客户端公钥存储;适用于多节点部署。 */\n readonly keyStore?: TransportKeyStore\n /** 服务端可缓存的客户端公钥数量上限。默认 10000。 */\n readonly maxClients?: number\n}\n\n/**\n * 创建传输加密 Hono 中间件。\n *\n * 行为:\n * - `${keyExchangePath}` POST → 密钥协商\n * - 其他请求必须携带 `X-Client-Id` 且加密\n * - JSON 响应自动加密\n *\n * @param manager - 由 `crypto.transport.createServer()` 创建的管理器\n * @param keyExchangePath - 密钥协商绝对路径(已与 apiPrefix 拼接)\n * @param excludePaths - 不参与加解密的路径\n */\nexport function createTransportMiddleware(\n manager: TransportEncryptionManager,\n keyExchangePath: string,\n excludePaths: readonly string[] = [],\n): MiddlewareHandler {\n return async (c, next) => {\n const pathname = new URL(c.req.url).pathname\n\n // Step 1:密钥协商端点直接短路处理,不进入下游业务逻辑。\n if (pathname === keyExchangePath && c.req.method === 'POST')\n return handleKeyExchange(c.req.raw, manager)\n\n // Step 2:健康检查 / 文档页等可配置排除路径直接跳过加解密。\n if (shouldExclude(pathname, excludePaths, keyExchangePath))\n return next()\n\n // Step 3:普通业务请求必须先确认 clientId 存在,且服务端已经保存过该客户端公钥。\n const clientId = c.req.header(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER)\n if (!clientId)\n return jsonError(servM('serv_transportClientIdRequired'), 400)\n\n const clientPublicKey = await manager.getClientPublicKey(clientId)\n if (!clientPublicKey)\n return jsonError(servM('serv_transportClientKeyNotFound'), 400)\n\n // Step 4:对带 body 的请求先解密;解密后下游看到的是普通 JSON Request。\n // 空 body 请求(如无 input 的 POST 过程)无需解密;下游 Zod 校验会拒绝缺字段输入。\n if (hasBody(c.req.method) && !isEmptyBody(c.req.raw)) {\n const decErr = await decryptRequestInPlace(c, manager)\n if (decErr)\n return decErr\n }\n\n // Step 5:明文请求进入后续 Hono / oRPC 流程。\n await next()\n const response = c.res\n if (!response)\n return\n // Step 6:受保护路由响应必须加密;无法加密时 fail-closed,禁止明文透传。\n const contentType = response.headers.get('Content-Type') ?? ''\n if (isEmptyResponse(response))\n return\n if (!canEncryptResponseContentType(contentType)) {\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n const contentLength = response.headers.get('Content-Length')\n if (contentLength && Number.parseInt(contentLength, 10) > MAX_ENCRYPTED_BODY) {\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n\n let bodyText: string\n try {\n bodyText = await response.clone().text()\n }\n catch (cause) {\n logger.warn('Failed to read response body for encryption', { error: cause })\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n if (!bodyText)\n return\n // 二次防御:分块传输(chunked)等场景没有 Content-Length 头,需在读取后再校验体积。\n // 超过上限则 fail-closed,避免大 body 加密导致内存放大且禁止明文泄露。\n if (bodyText.length > MAX_ENCRYPTED_BODY) {\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n\n // Step 7:把下游返回的明文 JSON 重新加密后替换回响应对象。\n const encResult = await manager.encryptResponse(clientId, bodyText)\n if (!encResult.success) {\n logger.warn('Failed to encrypt response', { error: encResult.error })\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n\n const headers = new Headers(response.headers)\n headers.set('Content-Type', 'application/json; charset=utf-8')\n headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE)\n headers.delete('Content-Length')\n c.res = new Response(JSON.stringify(encResult.data), {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n\n// ─── 内部 ───\n\nasync function handleKeyExchange(request: Request, manager: TransportEncryptionManager): Promise<Response> {\n let body: { clientPublicKey?: unknown }\n try {\n // Step 1:读取客户端提交的公钥。\n body = await request.json() as { clientPublicKey?: unknown }\n }\n catch {\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n }\n if (typeof body.clientPublicKey !== 'string' || body.clientPublicKey.length === 0)\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n\n try {\n // Step 2:注册客户端公钥,换取稳定的 clientId;并返回服务端公钥给客户端完成协商。\n const clientId = await manager.registerClientKey(body.clientPublicKey)\n const serverPublicKey = manager.getServerPublicKey()\n return new Response(JSON.stringify({ serverPublicKey, clientId }), {\n status: 200,\n headers: { 'Content-Type': 'application/json; charset=utf-8' },\n })\n }\n catch (cause) {\n logger.error('Key exchange failed', { error: cause })\n return jsonError(servM('serv_transportKeyExchangeFailed'), 500)\n }\n}\n\nasync function decryptRequestInPlace(\n c: Parameters<MiddlewareHandler>[0],\n manager: TransportEncryptionManager,\n): Promise<Response | undefined> {\n // Step A:请求必须显式声明自己是加密 payload,避免把普通 JSON 误当密文解析。\n const isEncrypted = c.req.header(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) === TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE\n if (!isEncrypted)\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n\n let payload: unknown\n try {\n // Step B:先以 JSON 读取密文载荷。\n payload = await c.req.raw.clone().json()\n }\n catch {\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n }\n if (!isEncryptedPayloadShape(payload))\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n\n // Step C:调用 transport manager 做真正的解密。\n const decResult = manager.decryptRequest(payload)\n if (!decResult.success) {\n logger.warn('Failed to decrypt request', { error: decResult.error })\n return jsonError(servM('serv_transportDecryptFailed'), 400)\n }\n\n // Step D:重建一个“明文 Request”回写到 `c.req.raw`,下游业务完全不需要感知加密协议。\n const original = c.req.raw\n const newHeaders = new Headers(original.headers)\n newHeaders.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER)\n newHeaders.delete('Content-Length')\n newHeaders.set('Content-Type', 'application/json; charset=utf-8')\n const newRequest = new Request(original.url, {\n method: original.method,\n headers: newHeaders,\n body: decResult.data,\n // @ts-expect-error Node fetch 需要 duplex 才能携带流式 body。\n duplex: 'half',\n })\n ;(c.req as unknown as { raw: Request }).raw = newRequest\n return undefined\n}\n\nfunction shouldExclude(pathname: string, excludePaths: readonly string[], keyExchangePath: string): boolean {\n if (pathname === keyExchangePath)\n return true\n return excludePaths.some(p => pathname === p || pathname.startsWith(`${p}/`))\n}\n\nfunction canEncryptResponseContentType(contentType: string): boolean {\n return ENCRYPTABLE_RESPONSE_CONTENT_TYPES.some(type => contentType.includes(type))\n}\n\nfunction isEmptyResponse(response: Response): boolean {\n if ([204, 304].includes(response.status))\n return true\n return response.headers.get('Content-Length') === '0'\n}\n\nfunction hasBody(method: string): boolean {\n return ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method.toUpperCase())\n}\n\n/** 判断请求是否携带 body:Content-Length=0 或缺失 Content-Type 视为空 body。 */\nfunction isEmptyBody(req: Request): boolean {\n if (req.body === null)\n return true\n const cl = req.headers.get('Content-Length')\n if (cl !== null)\n return Number.parseInt(cl, 10) === 0\n // 无 Content-Length 且无 Content-Type 的 body-bearing 请求按空 body 处理。\n return !req.headers.get('Content-Type')\n}\n\nfunction jsonError(message: string, status: number): Response {\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: { 'Content-Type': 'application/json; charset=utf-8' },\n })\n}\n\nfunction isEncryptedPayloadShape(value: unknown): value is EncryptedPayload {\n if (!value || typeof value !== 'object')\n return false\n const p = value as Record<string, unknown>\n return typeof p.encryptedKey === 'string' && typeof p.ciphertext === 'string' && typeof p.iv === 'string'\n}\n","/**\n * @h-ai/serv — HTTP app 装配\n *\n * 将 oRPC contract + procedures + http 配置组合为可运行的 ServHttpApp,\n * 自动挂载安全响应头、健康检查、OpenAPI JSON、Scalar 文档页与 oRPC handler。\n *\n * 关键装配顺序:\n * 1. 解析 HTTP 配置\n * 2. 选择请求上下文工厂\n * 3. 挂基础安全 middleware\n * 4. 挂自定义 middleware(若提供)\n * 5. (可选)挂传输加密 middleware\n * 6. 挂基础路由(health / refresh-cookie)\n * 7. 挂业务路由(oRPC / RPC)\n * 8. 挂文档路由(openapi / docs)\n * @module serv-app\n */\n\nimport type { AudioFormat, AudioWsStartMessage } from '@h-ai/ai'\nimport type { HaiResult } from '@h-ai/core'\nimport type { ServerType } from '@hono/node-server'\nimport type { AnyContractRouter, OpenAPI } from '@orpc/contract'\nimport type { Router } from '@orpc/server'\nimport type { Hono, Context as HonoContext, Next } from 'hono'\nimport type { AudioWsDeps } from './features/serv-feature-audio.js'\nimport type { ServMiddleware } from './pipelines/serv-pipeline-types.js'\nimport type { ServHealthHttpConfig, ServHttpConfigInput } from './serv-config.js'\nimport type { CreateServContext, ServContext, ServIam, ServSession } from './serv-context.js'\nimport type { RefreshCookieConfig } from './serv-cookie-auth.js'\nimport type { ServTransportConfig } from './serv-transport.js'\nimport type { ServValidationFailureBody } from './serv-validation.js'\nimport { AUDIO_WS_PATH } from '@h-ai/api-contract'\nimport { core, HaiCommonError } from '@h-ai/core'\nimport { createNodeWebSocket } from '@hono/node-ws'\nimport { OpenAPIHandler } from '@orpc/openapi/fetch'\nimport { RPCHandler } from '@orpc/server/fetch'\nimport { Hono as HonoApp } from 'hono'\nimport { registerAudioWsRoute } from './features/serv-feature-audio.js'\nimport { buildHaiErrorBody } from './pipelines/serv-pipeline-helper.js'\nimport { requireInternalRPC } from './pipelines/serv-pipeline-require-internal-rpc.js'\nimport { securityHeaders } from './pipelines/serv-pipeline-security-headers.js'\nimport { resolveServHttpConfig } from './serv-config.js'\nimport { buildAuthContextFactory, parseRequestContext } from './serv-context.js'\nimport { mountRefreshCookieRoutes } from './serv-cookie-auth.js'\nimport { servM } from './serv-i18n.js'\nimport { createDocsPage, generateSpec, getScalarScript, SCALAR_ROUTE } from './serv-openapi.js'\nimport { createTransportMiddleware } from './serv-transport.js'\nimport { localizeZodError, resolveRequestLocale } from './serv-validation.js'\n\n/** 允许通过 oRPC OpenAPIHandler 转发的 HTTP 方法。 */\nconst API_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const\n\n/**\n * `serv.createApp()` 的自定义 middleware 挂载项。\n *\n * - `path` 省略时默认挂到 `'*'`\n * - 按数组顺序注册\n * - 注册位置固定在内置 `securityHeaders` 之后、`transport` / 业务路由之前\n */\nexport interface ServMiddlewareMount {\n readonly path?: string\n readonly middleware: ServMiddleware\n}\n\n/**\n * `@h-ai/serv` 对外暴露的 HTTP app 抽象。\n *\n * Hono 是当前内部实现细节,应用代码只应依赖此最小接口:\n * - `fetch`:部署到 Fetch-first 运行时或交给 `serv.listen()`。\n * - `request`:测试 / 本地直连时发起 in-process 请求,避免消费方 import Hono。\n */\nexport interface ServHttpApp {\n readonly fetch: (request: Request) => Response | Promise<Response>\n readonly request: (input: RequestInfo | URL, init?: RequestInit) => Response | Promise<Response>\n}\n\n/**\n * 创建 ServHttpApp 的配置。\n *\n * **认证 / 会话填充设计**(遵循最小知识原则):\n *\n * - **`iam`**(推荐):单一顶层引用。提供后 serv 自动:\n * 1. 使用 `iam.session.verifyToken` 填充 `context.session` → `requireAuth/Permission/Role` 生效\n * 2. 若同时启用 `refreshCookie`,使用 `iam.session.refresh` 实现 refresh token 轮换\n *\n * - **`refreshCookie`**(可选):浏览器默认用 httpOnly cookie;受信原生客户端可配置 body 通道。与 `iam` 正交:\n * `iam` 决定 *谁* 验证 token,`refreshCookie` 决定 refresh token *怎么* 传输。\n *\n * - **`verifyToken`**(高级):不使用 IAM 模块的逆脱口。优先于 `iam.session.verifyToken`。\n *\n * - **`createContext`**(高级):完全接管上下文构造,设置后 serv **不再**自动填充 session,\n * 由调用方负责。\n */\nexport interface CreateServAppOptions<\n TContract extends AnyContractRouter,\n TProcedures extends Router<AnyContractRouter, ServContext>,\n> {\n readonly contract: TContract\n readonly procedures: TProcedures\n readonly http?: ServHttpConfigInput\n /**\n * 自定义 HTTP middleware(当前内部由 Hono 承载,但应用 API 不暴露 Hono app)。\n *\n * 典型用途:请求日志、trace、指标、CORS、限流、租户头校验,以及不适合放入\n * JSON/oRPC contract 的 WebSocket、文件或其它二进制 HTTP 端点。\n * 这些 middleware 会在内置安全头之后、传输加密与业务路由之前注册;\n * 因此 CORS preflight 或二进制端点可以直接短路返回。短路响应仍包含内置安全头,\n * 但不会经过 transport;若需要读取解密后的业务 body,应改用 context / procedure 层扩展。\n */\n readonly middlewares?: readonly ServMiddlewareMount[]\n /**\n * IAM 模块引用(推荐)。提供后 serv 自动使用 `iam.session.verifyToken` 填充 session,\n * 以及在 `refreshCookie` 启用时使用 `iam.session.refresh` 实现 cookie 刷新。\n *\n * 类型上要求 {@link ServIam}(`iam` 可以直接传入,结构类型保证兼容)。\n */\n readonly iam?: ServIam\n /**\n * 启用 refresh token 传输(opt-in):浏览器使用 httpOnly Cookie,可选配置受信原生 body 通道。\n * 需配合 `iam` 或 `refreshCookie.onRefresh` 使用。\n */\n readonly refreshCookie?: RefreshCookieConfig\n /**\n * 高级:自定义访问令牌校验回调,覆盖 `iam.session.verifyToken`。\n * 不使用 IAM 模块的场景下使用。\n */\n readonly verifyToken?: (token: string) => Promise<HaiResult<ServSession>>\n /**\n * 高级:完全接管上下文工厂。设置后上述 `iam` / `verifyToken` 均不生效(session 不会被自动填充)。\n */\n readonly createContext?: CreateServContext\n /**\n * 启用传输加密(opt-in)。提供后 serv 自动:\n * 1. 调用 `crypto.transport.createServer()` 创建管理器\n * 2. 在 oRPC 路由之前挂载中间件,自动解密请求 / 加密响应\n * 3. 在 `${apiPrefix}${keyExchangePath}` 暴露 POST 密钥协商端点\n *\n * @example\n * ```ts\n * serv.createApp({ contract, procedures, transport: { crypto } })\n * ```\n */\n readonly transport?: ServTransportConfig\n /**\n * 启用统一语音 WebSocket 入口(opt-in)。提供后 serv 自动:\n * 1. 在 `${apiPrefix}/ai/audio`(可自定义 `path`)注册语音 WebSocket 路由\n * 2. 基于 `ai.audio` 提供非实时 / 实时的语音识别与合成\n * 3. 通过一次性 Audio ticket 验证身份,再由授权回调确认付费参数\n *\n * 需配合 `serv.listen()`(Node 适配器会自动完成 WebSocket 升级注入)。\n *\n * @example\n * ```ts\n * serv.createApp({ contract, procedures, iam, audio: { ai, verifyTicket, authorize } })\n * ```\n */\n readonly audio?: ServAudioConfig\n}\n\n/** `serv.createApp()` 的语音 WebSocket 接入配置。 */\nexport interface ServAudioConfig {\n /** AI 服务对象(提供 `ai.audio` 领域能力)。 */\n readonly ai: AudioWsDeps['ai']\n /**\n * 校验并原子消费短期、一次性的 Audio ticket,返回会话与可选的能力授权(grant)。\n *\n * ticket 的签发和存储由应用决定(推荐 `iam.ticket.issue` / `iam.ticket.consume`);\n * 成功结果必须返回建立授权所需的 IAM 会话摘要,可选返回签发时绑定的 `grant`\n * (操作 / 模型 / 会话),供服务端交叉校验 `start` 帧与票据一致。\n */\n readonly verifyTicket: (ticket: string) => Promise<HaiResult<AudioTicketVerification>>\n /**\n * 基于已验证会话授权本次操作,并返回服务端确认后的付费参数。\n *\n * 未提供时只保留操作及音频格式,客户端提交的 model / voice / instruction 不会生效。\n * 这里适合完成 IAM 权限、Persona、套餐配额和并发会话的检查/占用。\n * 第三参 `grant` 为票据绑定的能力(如有),可用于确认 `start` 未越权。\n */\n readonly authorize?: (session: ServSession, request: AudioWsStartMessage, grant?: AudioTicketGrant) => Promise<HaiResult<AuthorizedAudioRequest>>\n /** 会话结束钩子,供应用释放并发占用;无论成功、失败或断连都至多调用一次。 */\n readonly onSessionEnd?: (session: ServSession, request: AuthorizedAudioRequest) => void | Promise<void>\n /** 语音入口路径(相对 apiPrefix,默认 `/ai/audio`)。 */\n readonly path?: string\n /** 单条消息字节上限(默认 1 MiB)。 */\n readonly maxMessageBytes?: number\n /** 单连接累计接收音频字节上限(默认 10 MiB)。 */\n readonly maxBufferedBytes?: number\n /** 单连接累计接收文本字节上限(默认 1 MiB)。 */\n readonly maxTextBytes?: number\n /** 单连接最长持续时间(毫秒,默认 5 分钟)。 */\n readonly maxSessionMs?: number\n /** 预鉴权超时(毫秒,默认 5000):连接建立后未完成鉴权并收到 `start` 即关闭,独立于会话超时。 */\n readonly preAuthTimeoutMs?: number\n /** 未处理消息积压上限(默认 256):串行处理链积压超过此数立即关闭,防止内存放大。 */\n readonly maxPendingMessages?: number\n /** 发送缓冲高水位(字节,默认 8 MiB):`ws.bufferedAmount` 超过即以领域错误关闭慢客户端。 */\n readonly maxSendBufferBytes?: number\n}\n\n/** Audio ticket 绑定的能力授权(消费时原样返回,供服务端交叉校验 start 帧)。 */\nexport interface AudioTicketGrant {\n /** 允许的操作 */\n readonly operation?: 'transcribe' | 'synthesize'\n /** 允许的模型 ID */\n readonly model?: string\n /** 绑定的会话 ID */\n readonly sessionId?: string\n /** 应用自定义绑定字段 */\n readonly [key: string]: unknown\n}\n\n/** Audio ticket 验证结果:IAM 会话 + 可选能力授权。 */\nexport interface AudioTicketVerification {\n /** IAM 会话摘要 */\n readonly session: ServSession\n /** 票据绑定的能力授权(如有) */\n readonly grant?: AudioTicketGrant\n}\n\n/** Audio WebSocket 经应用授权后允许生效的服务端参数。 */\nexport interface AuthorizedAudioRequest {\n readonly operation: 'transcribe' | 'synthesize'\n /** 服务端允许的模型 ID;不传时使用 `ai.audio` 对应操作的默认模型。 */\n readonly model?: string\n readonly voice?: string\n readonly instruction?: string\n readonly format?: AudioFormat\n readonly sampleRate?: number\n}\n\n/**\n * 语音 WebSocket 升级注入器注册表\n *\n * `createApp` 启用 audio 时将 `injectWebSocket` 写入此 WeakMap,`serv.listen()` 在创建\n * Node 服务器后取出并注入,从而不向 `ServHttpApp` 公共类型泄露 Hono / WebSocket 细节。\n */\nexport const audioWsInjectors = new WeakMap<ServHttpApp, (server: ServerType) => void>()\n\n/**\n * 创建并装配 HTTP API app。\n *\n * @param options - contract/procedures/http 三段式配置\n * @returns 可被 `serv.listen()` / `serv.toFetch()` 使用的 ServHttpApp\n *\n * @example\n * ```ts\n * const app = serv.createApp({\n * contract,\n * procedures,\n * http: {\n * apiPrefix: '/api/v1',\n * openapi: { path: '/openapi.json' },\n * docs: { path: '/docs' },\n * },\n * })\n * serv.listen(app, { port: 3000, host: '0.0.0.0' })\n * ```\n */\nexport function createApp<\n TContract extends AnyContractRouter,\n TProcedures extends Router<AnyContractRouter, ServContext>,\n>(options: CreateServAppOptions<TContract, TProcedures>): ServHttpApp {\n // Step 1:先把用户输入的 HTTP 配置收敛成完整配置,后续挂载逻辑只处理一种形态。\n const http = resolveServHttpConfig(options.http)\n // Step 2:创建 Hono app 外壳,后续所有 middleware / route 都按顺序向这个实例注册。\n const app = new HonoApp()\n\n // Step 3:决定请求上下文工厂。这个选择直接决定后续 procedure 是否能拿到 `context.session`。\n // 上下文工厂选择优先级(顶位优先):\n // 1. options.createContext\n // 2. options.verifyToken\n // 3. options.iam.session.verifyToken\n // 4. 都未提供 → parseRequestContext(仅解析元数据,session 始终 undefined)\n //\n // ⚠️ 对 `iam.session.verifyToken` **不**做 `.bind()`:IAM 模块使用 NotInitializedKit Proxy 模式,\n // `iam.session` 的方法实现会在 `init()` 后切换。提前 bind 会捕获未初始化 Proxy 的方法引用。\n // 用闭包函数延迟到请求处理时再读取,确保始终调用最新实现。\n const iamRef = options.iam\n const verifyToken = options.verifyToken\n ?? (iamRef ? (token: string) => iamRef.session.verifyToken(token) : undefined)\n const createContext: CreateServContext = options.createContext\n ?? (verifyToken ? buildAuthContextFactory(verifyToken) : parseRequestContext)\n\n // Step 4:准备文档 spec 的懒缓存。只有真正访问 openapi/docs 时才生成,避免启动时做重活。\n // OpenAPI spec 是相对昂贵的同步生成;延迟到第一次访问 `/openapi.json` 或 `/docs` 时再算一次。\n let cachedSpec: Promise<OpenAPI.Document> | undefined\n const getSpec = (): Promise<OpenAPI.Document> => {\n cachedSpec ??= generateSpec(options.contract, { apiPrefix: http.apiPrefix })\n return cachedSpec\n }\n\n // Step 5:最先挂全局安全响应头,让后续所有响应(包括错误响应)都自动带上保护性 header。\n app.use('*', securityHeaders())\n\n // Step 6:先注册调用方自定义 middleware。位置固定在内置安全头之后、传输加密/业务路由之前,\n // 让 CORS preflight 等 HTTP 层横切逻辑可以优先短路,同时保持 transport 仍然位于业务路由之前。\n if (options.middlewares)\n mountCustomMiddlewares(app, options.middlewares)\n\n // Step 7:若启用传输加密,必须在业务路由之前挂载。\n // 这样请求能先被解密,响应也能在离开应用前被重新加密。\n // 传输加密必须在所有业务路由之前装载:\n // - 它需在 oRPC 读取 body 前完成解密\n // - 在下游响应返回后加密\n if (options.transport) {\n const mgr = options.transport.crypto.transport.createServer({\n keyStore: options.transport.keyStore,\n maxClients: options.transport.maxClients,\n })\n if (!mgr.success) {\n // 直接抛已存在的 HaiError 实例,保留 code/system/module/cause,便于上层定位到\n // \"transport 初始化失败\" 这一类配置错误,而非被 `new Error(message)` 抹平为字符串。\n throw core.error.buildHaiErrorInst(\n HaiCommonError.INTERNAL_ERROR,\n mgr.error.message,\n mgr.error,\n )\n }\n const keyExchangePath = `${http.apiPrefix}${options.transport.keyExchangePath ?? '/_hai/key-exchange'}`\n app.use('*', createTransportMiddleware(mgr.data, keyExchangePath, options.transport.excludePaths))\n }\n\n // Step 8:挂基础探活路由。健康检查放在业务路由之外,便于基础设施直接探测。\n if (http.health !== false)\n mountHealthRoutes(app, http.health)\n\n // Step 9:refresh-cookie 路由要抢在 oRPC 通配符之前注册,否则会被 `${apiPrefix}/*` 吞掉。\n // refresh-cookie 路由必须在 oRPC 通配符路由之前注册,Hono 按注册顺序匹配。\n if (options.refreshCookie)\n mountRefreshCookieRoutes(app, http.apiPrefix, options.refreshCookie, options.iam)\n\n // Step 9.5:语音 WebSocket 入口同样要在 oRPC 通配符之前注册,避免 GET 升级请求被 `${apiPrefix}/*` 吞掉。\n if (options.audio) {\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })\n const audioPath = `${http.apiPrefix}${options.audio.path ?? AUDIO_WS_PATH}`\n registerAudioWsRoute(app, audioPath, upgradeWebSocket, {\n ai: options.audio.ai,\n verifyTicket: options.audio.verifyTicket,\n authorize: options.audio.authorize,\n onSessionEnd: options.audio.onSessionEnd,\n maxMessageBytes: options.audio.maxMessageBytes,\n maxBufferedBytes: options.audio.maxBufferedBytes,\n maxTextBytes: options.audio.maxTextBytes,\n maxSessionMs: options.audio.maxSessionMs,\n preAuthTimeoutMs: options.audio.preAuthTimeoutMs,\n maxPendingMessages: options.audio.maxPendingMessages,\n maxSendBufferBytes: options.audio.maxSendBufferBytes,\n })\n audioWsInjectors.set(app, injectWebSocket)\n }\n\n // Step 10:挂主业务 API 路由。所有 `${apiPrefix}/*` 请求都会在这里进入 contract + procedure 分发。\n mountOpenAPIRoutes(app, options.procedures, http.apiPrefix, createContext)\n\n // Step 11:按需挂内部 RPC 路由,并在入口处限制访问来源。\n if (http.rpc !== false)\n mountRPCRoutes(app, options.procedures, http.rpc, createContext)\n\n // Step 12:按需挂 OpenAPI JSON 文档端点。\n if (http.openapi !== false) {\n app.get(http.openapi.path, async c => c.json(await getSpec()))\n }\n\n // Step 13:按需挂 docs 页面;若要求登录,则重用同一套 context/session 判定逻辑。\n if (http.docs !== false) {\n const docs = http.docs\n // 挂载 Scalar UI 脚本本地路由,避免访问外网 CDN\n app.get(SCALAR_ROUTE, async (c) => {\n const content = await getScalarScript()\n if (!content)\n return c.notFound()\n c.header('Content-Type', 'application/javascript; charset=utf-8')\n c.header('Cache-Control', 'public, max-age=86400, immutable')\n return c.body(content)\n })\n\n app.get(docs.path, async (c) => {\n if (docs.requireAuth) {\n const context = await createContext({ request: c.req.raw })\n // 与 procedure 层 requireAuth 语义保持一致:必须是已验证 session,\n // 不能只检查 Bearer 字符串是否存在,避免任意伪造 Token 访问受保护文档。\n if (!context.session)\n return c.json(buildHaiErrorBody(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: resolveRequestLocale(c.req.raw.headers) })), 401)\n }\n const specUrl = http.openapi === false ? undefined : http.openapi.path\n return c.html(createDocsPage(await getSpec(), { specUrl }))\n })\n }\n\n // Step 14:所有运行时能力都已装配完毕,返回对外稳定的 HTTP app 抽象。\n return app\n}\n\nfunction mountCustomMiddlewares(app: Hono, middlewares: readonly ServMiddlewareMount[]): void {\n for (const entry of middlewares)\n app.use(entry.path ?? '*', entry.middleware)\n}\n\nfunction mountHealthRoutes(app: Hono, config: ServHealthHttpConfig): void {\n app.get(config.path, c => c.json({ status: 'ok' }))\n if (config.readyPath)\n app.get(config.readyPath, c => c.json({ status: 'ready' }))\n}\n\nfunction mountOpenAPIRoutes(\n app: Hono,\n procedures: Router<AnyContractRouter, ServContext>,\n apiPrefix: `/${string}`,\n createContext: CreateServContext,\n): void {\n const handler = new OpenAPIHandler(procedures)\n // 这里只为 `${apiPrefix}/*` 这个挂载前缀注册一次 multi-method catch-all。\n // 它不是“为 contract 中的每个 endpoint 各注册一遍 GET/POST/...”,\n // 真实的路由命中与输入校验都在下游 oRPC handler 中完成。\n app.on([...API_METHODS], `${apiPrefix}/*`, (c, next) => handleORPC(c, next, apiPrefix, handler, createContext))\n}\n\nfunction mountRPCRoutes(\n app: Hono,\n procedures: Router<AnyContractRouter, ServContext>,\n rpcConfig: { readonly prefix: `/${string}` } & Parameters<typeof requireInternalRPC>[0],\n createContext: CreateServContext,\n): void {\n const handler = new RPCHandler(procedures)\n const prefix = rpcConfig.prefix\n app.use(`${prefix}/*`, requireInternalRPC(rpcConfig))\n app.on([...API_METHODS], `${prefix}/*`, (c, next) => handleORPC(c, next, prefix, handler, createContext))\n}\n\nasync function handleORPC(\n c: HonoContext,\n next: Next,\n prefix: `/${string}`,\n handler: OpenAPIHandler<ServContext> | RPCHandler<ServContext>,\n createContext: CreateServContext,\n): Promise<Response | void> {\n // Step A:为本次请求创建 ServContext(可能只含元数据,也可能已带 session)。\n const context = await createContext({ request: c.req.raw })\n // Step B:交给 oRPC handler 做 contract 匹配 + procedure 执行。\n const { matched, response } = await handler.handle(c.req.raw, { prefix, context })\n if (!matched) {\n // Step C:当前前缀下没有匹配路由,继续交给 Hono 后续路由处理。\n await next()\n return\n }\n // Step D:若命中的是输入校验错误,这里统一改写成本地化的 HaiResult 失败体。\n return localizeValidationResponse(response, c.req.raw.headers)\n}\n\n/**\n * 拦截 oRPC 输入校验失败响应,重写为带 i18n 与 `errors[]` 的标准 HaiResult 失败体。\n *\n * 触发条件(保守判断,避免误伤业务自定义错误):\n * - status === 400\n * - body 为 oRPC ORPCError JSON 形态:`{ defined, code, status, message, data }`\n * - `code === 'BAD_REQUEST'` 且 `data.issues` 存在\n *\n * 不符合上述条件的响应原样透传,确保业务层主动抛出的 BAD_REQUEST 不被改写。\n */\nasync function localizeValidationResponse(response: Response, requestHeaders: Headers): Promise<Response> {\n if (response.status !== 400)\n return response\n const contentType = response.headers.get('content-type') ?? ''\n if (!contentType.includes('application/json'))\n return response\n\n // 克隆响应再读取,避免破坏调用方对 response 的潜在二次消费。\n let payload: unknown\n try {\n payload = await response.clone().json()\n }\n catch {\n return response\n }\n\n if (!isOrpcValidationErrorPayload(payload))\n return response\n\n const issues = payload.data.issues\n const locale = resolveRequestLocale(requestHeaders)\n const errors = localizeZodError({ issues }, locale)\n // 与 `validateInputOrFail` 输出对齐:errors 同时挂在 `error.cause` 上,\n // 使两条校验路径的客户端解析逻辑一致(都从 `error.cause` 读取字段错误)。\n // 顶层 `errors` 字段保留,兼容已有客户端读法。\n const body: ServValidationFailureBody = {\n success: false,\n error: {\n code: HaiCommonError.VALIDATION_ERROR.code,\n message: servM('serv_validationFailed', { locale }),\n httpStatus: HaiCommonError.VALIDATION_ERROR.httpStatus,\n system: HaiCommonError.VALIDATION_ERROR.system,\n module: HaiCommonError.VALIDATION_ERROR.module,\n cause: errors,\n },\n errors,\n }\n\n // 保留原响应头(其中可能包含安全头),仅替换 body 与 status。\n const headers = new Headers(response.headers)\n headers.set('content-type', 'application/json; charset=utf-8')\n return new Response(JSON.stringify(body), {\n status: HaiCommonError.VALIDATION_ERROR.httpStatus,\n headers,\n })\n}\n\ninterface OrpcValidationErrorPayload {\n readonly defined: boolean\n readonly code: string\n readonly status: number\n readonly message: string\n readonly data: { readonly issues: unknown[] }\n}\n\nfunction isOrpcValidationErrorPayload(value: unknown): value is OrpcValidationErrorPayload {\n if (!value || typeof value !== 'object')\n return false\n const obj = value as Record<string, unknown>\n if (obj.code !== 'BAD_REQUEST')\n return false\n if (typeof obj.status !== 'number' || obj.status !== 400)\n return false\n const data = obj.data\n if (!data || typeof data !== 'object')\n return false\n const issues = (data as Record<string, unknown>).issues\n return Array.isArray(issues)\n}\n","/**\n * @h-ai/serv — Node.js 适配器\n *\n * 封装 `@hono/node-server`,将 ServHttpApp 以 Node.js HTTP 服务器形式启动。\n * 默认监听 `127.0.0.1`(仅本机可达);如需对外暴露,需显式设置 `host: '0.0.0.0'` 或指定 IP。\n * @module adapters/serv-adapter-node\n */\n\nimport type { ServerType } from '@hono/node-server'\nimport type { AddressInfo } from 'node:net'\nimport type { ServHttpApp } from '../serv-app.js'\nimport process from 'node:process'\nimport { serve } from '@hono/node-server'\nimport { audioWsInjectors } from '../serv-app.js'\n\n/** `serv.listen` 默认监听地址。仅本机可达,避免误暴露到公网。 */\nexport const DEFAULT_SERV_HOST = '127.0.0.1'\n\n/** `serv.listen` 默认监听端口。 */\nexport const DEFAULT_SERV_PORT = 3000\n\n/** Node 服务器启动配置。 */\nexport interface ServListenOptions {\n /**\n * 监听端口。\n * 优先级:显式值 > `PORT` 环境变量 > 默认 `3000`。\n */\n readonly port?: number\n /**\n * 监听地址。\n * 优先级:显式值 > `HOST` 环境变量 > 默认 `'127.0.0.1'`(仅本机可达)。\n *\n * - `'127.0.0.1'`:仅本机(推荐用于本地开发、容器内 sidecar、反向代理后端)\n * - `'0.0.0.0'`:监听所有 IPv4 网卡(容器对外暴露、需公网/内网访问)\n * - 指定 IP:仅监听该网卡(多网卡精确绑定)\n */\n readonly host?: string\n /** 服务启动就绪后的回调。 */\n readonly onListening?: (info: AddressInfo) => void\n /**\n * 优雅关闭回调(释放业务模块资源)。\n *\n * 传入后 `serv.listen` 自动监听 `SIGINT` / `SIGTERM`,按以下顺序关闭:\n * 1. 停止接受新 HTTP 连接\n * 2. 调用此回调释放业务资源\n *\n * 应用层无需手动注册 `process.once('SIGINT', ...)` 或维护 `shuttingDown` 标志。\n */\n readonly onClose?: () => Promise<void>\n}\n\n/** Node 服务运行句柄。 */\nexport interface ServNodeServer {\n /** 底层 Node.js HTTP Server 实例。 */\n readonly server: ServerType\n /** 实际监听地址(host 与解析后的 port)。 */\n readonly address: { readonly host: string, readonly port: number }\n /** 优雅关闭服务器并等待已有连接断开。 */\n readonly close: () => Promise<void>\n}\n\n/**\n * 在 Node.js 中启动 ServHttpApp。\n *\n * 默认监听 `127.0.0.1:3000`(可通过 `PORT` / `HOST` 环境变量覆盖)。\n * 传入 `onClose` 后自动注册 SIGINT/SIGTERM 优雅关闭,应用层无需手动处理信号。\n *\n * @param app - `serv.createApp()` 返回的 HTTP app\n * @param options - 监听配置(port / host / onListening / onClose)\n * @returns 运行句柄\n *\n * @example\n * ```ts\n * // 最简:读取 PORT / HOST 环境变量,自动优雅关闭\n * serv.listen(app, {\n * onListening: info => logger.info('listening', { port: info.port }),\n * onClose: closeApp,\n * })\n *\n * // 容器对外暴露\n * serv.listen(app, { host: '0.0.0.0', onClose: closeApp })\n * ```\n */\nexport function listen(app: ServHttpApp, options: ServListenOptions = {}): ServNodeServer {\n const host = options.host ?? process.env.HOST ?? DEFAULT_SERV_HOST\n const port = options.port ?? (process.env.PORT ? Number(process.env.PORT) : DEFAULT_SERV_PORT)\n\n const server = serve(\n { fetch: app.fetch, hostname: host, port },\n options.onListening,\n )\n\n // 若启用了语音 WebSocket 入口,注入升级处理器以支持 ws 握手\n const injectWebSocket = audioWsInjectors.get(app)\n if (injectWebSocket)\n injectWebSocket(server)\n\n const handle: ServNodeServer = {\n server,\n address: { host, port },\n close: () => new Promise((resolve, reject) => {\n server.close((error) => {\n if (error)\n reject(error)\n else resolve()\n })\n }),\n }\n\n // 注册 SIGINT / SIGTERM 优雅关闭(仅在传入 onClose 时启用)。\n if (options.onClose) {\n const onClose = options.onClose\n let shuttingDown = false\n const shutdown = async (): Promise<void> => {\n if (shuttingDown)\n return\n shuttingDown = true\n try {\n await handle.close()\n }\n catch { /* 忽略 HTTP server 关闭错误 */ }\n try {\n await onClose()\n }\n catch { /* 忽略业务模块关闭错误 */ }\n }\n process.once('SIGINT', () => void shutdown())\n process.once('SIGTERM', () => void shutdown())\n }\n\n return handle\n}\n","/**\n * @h-ai/serv — requireRole 默认实现\n * @module pipelines/serv-pipeline-require-role\n */\n\nimport type { ServProcedureHandler } from './serv-pipeline-types.js'\nimport { err, HaiCommonError } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { requireAuth } from './serv-pipeline-require-auth.js'\n\n/**\n * 通配符角色:拥有此角色的用户自动通过所有 {@link requireRole} 检查。\n * 用于超级管理员场景。**禁止**普通用户被分配此角色。\n */\nexport const WILDCARD_ROLE = '*'\n\n/**\n * 需要指定角色的 procedure 包装器。\n *\n * 内置调用 `requireAuth`,在验证 Token 后进一步匹配 `session.roles`。\n * 拥有 {@link WILDCARD_ROLE}(`'*'`)的用户自动通过所有角色检查。\n *\n * **使用建议**:优先使用 {@link requirePermission}(基于行为的细粒度授权);\n * `requireRole` 适用于按角色分流的粗粒度路由(如 `'admin'` 专属入口)。\n *\n * @param role - 所需角色字符串(如 `'admin'`)\n * @param handler - 被包装的 procedure handler\n * @returns 带角色检查的新 handler\n *\n * @example\n * ```ts\n * const adminOnly = serv.requireRole('admin', myHandler)\n * ```\n */\nexport function requireRole<TInput, TOutput>(\n role: string,\n handler: ServProcedureHandler<TInput, TOutput>,\n): ServProcedureHandler<TInput, TOutput> {\n return requireAuth(async (options) => {\n // 先认证,再检查角色;通配符角色保留给超级管理员场景。\n const roles = options.context.session?.roles ?? []\n if (!roles.includes(role) && !roles.includes(WILDCARD_ROLE))\n return err(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale: options.context.locale }))\n return handler(options)\n })\n}\n","/**\n * @h-ai/serv — CORS middleware\n *\n * 统一处理 OPTIONS 预检与常规响应的 CORS 头注入,让应用层只保留 origin 白名单策略。\n * @module serv-cors\n */\n\nimport type { ServMiddleware } from './pipelines/serv-pipeline-types.js'\n\n/** serv CORS 配置。 */\nexport interface ServCorsConfig {\n /** 允许的 origin:`'*'`、单个字符串、数组或自定义匹配函数。默认 `'*'`。 */\n readonly origin?: '*' | string | readonly string[] | ((origin: string) => boolean)\n /** 允许的方法。默认 `GET/HEAD/PUT/PATCH/POST/DELETE/OPTIONS`。 */\n readonly methods?: readonly string[]\n /** 允许的请求头;若预检请求带 `Access-Control-Request-Headers`,优先回显该值。 */\n readonly allowedHeaders?: readonly string[]\n /** 暴露给浏览器 JS 的响应头。 */\n readonly exposedHeaders?: readonly string[]\n /** 是否允许携带 credentials(cookie / Authorization)。默认 `false`。 */\n readonly credentials?: boolean\n /** 预检缓存秒数。默认 `86400`。 */\n readonly maxAge?: number\n}\n\nconst DEFAULT_CORS_METHODS = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS']\nconst DEFAULT_CORS_ALLOWED_HEADERS = ['Authorization', 'Content-Type', 'X-Requested-With']\nconst DEFAULT_CORS_MAX_AGE = 86_400\n\n/** 创建可复用的 CORS middleware。 */\nexport function cors(config: ServCorsConfig = {}): ServMiddleware {\n const methods = [...(config.methods ?? DEFAULT_CORS_METHODS)]\n const allowedHeaders = [...(config.allowedHeaders ?? DEFAULT_CORS_ALLOWED_HEADERS)]\n const exposedHeaders = [...(config.exposedHeaders ?? [])]\n const credentials = config.credentials ?? false\n const maxAge = config.maxAge ?? DEFAULT_CORS_MAX_AGE\n const originConfig = config.origin ?? '*'\n\n return async (c, next) => {\n const allowedOrigin = resolveCorsOrigin(c.req.header('Origin'), originConfig)\n\n if (c.req.method === 'OPTIONS') {\n return new Response(null, {\n status: 204,\n headers: buildCorsHeaders({\n allowedOrigin,\n allowedHeaders,\n credentials,\n exposedHeaders,\n maxAge,\n methods,\n request: c.req.raw,\n }),\n })\n }\n\n await next()\n\n const corsHeaders = buildCorsHeaders({\n allowedOrigin,\n allowedHeaders,\n credentials,\n exposedHeaders,\n maxAge,\n methods,\n request: c.req.raw,\n })\n for (const [key, value] of corsHeaders) {\n if (key.toLowerCase() === 'vary') {\n c.res.headers.append(key, value)\n continue\n }\n c.res.headers.set(key, value)\n }\n\n return undefined\n }\n}\n\nfunction resolveCorsOrigin(\n origin: string | undefined,\n configOrigin: NonNullable<ServCorsConfig['origin']>,\n): string | null {\n if (!origin)\n return null\n\n if (configOrigin === '*')\n return '*'\n\n if (typeof configOrigin === 'string')\n return origin === configOrigin ? origin : null\n\n if (Array.isArray(configOrigin))\n return configOrigin.includes(origin) ? origin : null\n\n if (typeof configOrigin === 'function')\n return configOrigin(origin) ? origin : null\n\n return null\n}\n\nfunction buildCorsHeaders(options: {\n allowedOrigin: string | null\n allowedHeaders: readonly string[]\n credentials: boolean\n exposedHeaders: readonly string[]\n maxAge: number\n methods: readonly string[]\n request: Request\n}): Headers {\n const headers = new Headers()\n if (!options.allowedOrigin)\n return headers\n\n const requestedHeaders = options.request.headers.get('Access-Control-Request-Headers')\n\n headers.set('Access-Control-Allow-Origin', options.allowedOrigin)\n if (options.credentials)\n headers.set('Access-Control-Allow-Credentials', 'true')\n\n if (options.allowedOrigin !== '*') {\n headers.set(\n 'Vary',\n requestedHeaders ? 'Origin, Access-Control-Request-Headers' : 'Origin',\n )\n }\n\n if (options.exposedHeaders.length > 0)\n headers.set('Access-Control-Expose-Headers', options.exposedHeaders.join(', '))\n\n if (options.request.method === 'OPTIONS') {\n headers.set('Access-Control-Allow-Methods', options.methods.join(', '))\n headers.set('Access-Control-Allow-Headers', requestedHeaders ?? options.allowedHeaders.join(', '))\n headers.set('Access-Control-Max-Age', String(options.maxAge))\n }\n\n return headers\n}\n","/**\n * @h-ai/serv — 部署环境 HTTP 安全策略\n *\n * 将环境与 CORS 配置收敛为 fail-closed 的运行时策略,避免每个业务应用重复实现\n * Origin 解析、生产通配符保护、Cookie Secure 与 API 文档暴露规则。\n * @module serv-runtime-security\n */\n\n/** 运行时安全策略输入;通常来自 `_core.yml.env` 与 `_serv.yml.cors`。 */\nexport interface ServRuntimeSecurityPolicyInput {\n readonly environment?: string\n readonly corsOrigin?: string\n readonly nativeOrigins?: string\n}\n\n/** 可直接装配到 CORS、refresh cookie 与 HTTP 文档配置的安全策略。 */\nexport interface ServRuntimeSecurityPolicy {\n readonly allowOrigin: (origin: string) => boolean\n readonly isNativeOrigin: (origin: string) => boolean\n readonly secureRefreshCookie: boolean\n readonly exposeApiDocs: boolean\n}\n\n/**\n * 创建部署环境安全策略。\n *\n * 生产环境必须显式配置 HTTP(S) Origin,禁止缺省或 `*`;开发环境保留通配符便利性。\n */\nexport function createRuntimeSecurityPolicy(\n input: ServRuntimeSecurityPolicyInput,\n): ServRuntimeSecurityPolicy {\n const production = input.environment?.trim().toLowerCase() === 'production'\n const configuredOrigins = parseOrigins(input.corsOrigin)\n const configuredNativeOrigins = parseNativeOrigins(input.nativeOrigins)\n const allowsEveryOrigin = configuredOrigins === '*'\n\n if (production && (allowsEveryOrigin || configuredOrigins.length === 0))\n throw new Error('serv.cors.origin must contain explicit HTTP(S) origins in production')\n\n const allowedOrigins = allowsEveryOrigin ? undefined : new Set(configuredOrigins)\n const allowedNativeOrigins = new Set(configuredNativeOrigins)\n return {\n allowOrigin: (origin) => {\n const normalizedNativeOrigin = tryNormalizeNativeOrigin(origin)\n if (normalizedNativeOrigin !== undefined && allowedNativeOrigins.has(normalizedNativeOrigin))\n return true\n if (allowedOrigins === undefined)\n return true\n const normalizedWebOrigin = tryNormalizeWebOrigin(origin)\n return normalizedWebOrigin !== undefined && allowedOrigins.has(normalizedWebOrigin)\n },\n isNativeOrigin: origin => allowedNativeOrigins.has(tryNormalizeNativeOrigin(origin) ?? ''),\n secureRefreshCookie: production,\n exposeApiDocs: !production,\n }\n}\n\nfunction parseOrigins(value: string | undefined): '*' | string[] {\n const entries = (value ?? '*').split(',').map(origin => origin.trim()).filter(Boolean)\n if (entries.includes('*'))\n return '*'\n return entries.map(normalizeWebOrigin)\n}\n\nfunction parseNativeOrigins(value: string | undefined): string[] {\n return (value ?? '').split(',').map(origin => origin.trim()).filter(Boolean).map(normalizeNativeOrigin)\n}\n\nfunction normalizeWebOrigin(value: string): string {\n const normalized = tryNormalizeWebOrigin(value)\n if (normalized === undefined)\n throw new Error(`serv.cors.origin contains an invalid origin: ${value}`)\n return normalized\n}\n\nfunction normalizeNativeOrigin(value: string): string {\n const normalized = tryNormalizeNativeOrigin(value)\n if (normalized === undefined)\n throw new Error(`serv.cors.nativeOrigins contains an invalid origin: ${value}`)\n return normalized\n}\n\nfunction tryNormalizeWebOrigin(value: string): string | undefined {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n return undefined\n }\n if (!['http:', 'https:'].includes(url.protocol) || url.origin === 'null' || url.pathname !== '/' || url.search || url.hash)\n return undefined\n return url.origin\n}\n\nfunction tryNormalizeNativeOrigin(value: string): string | undefined {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n return undefined\n }\n if (!['http:', 'https:', 'capacitor:', 'tauri:'].includes(url.protocol) || !url.hostname || url.username || url.password || !['', '/'].includes(url.pathname) || url.search || url.hash)\n return undefined\n return ['capacitor:', 'tauri:'].includes(url.protocol) ? `${url.protocol}//${url.host}` : url.origin\n}\n","/**\n * @h-ai/serv — Storage 只读资源 middleware\n *\n * 将经过路径与 MIME 白名单校验的 `@h-ai/storage` 文件作为 HTTP 资源返回,\n * 统一处理 GET/HEAD、ETag 与缓存响应头,不包含头像等业务领域概念。\n * @module serv-storage-assets\n */\n\nimport type { StorageFunctions } from '@h-ai/storage'\nimport type { ServMiddleware } from './pipelines/serv-pipeline-types.js'\n\n/** 可由只读资源 middleware 使用的最小 Storage 能力。 */\ninterface StorageAssetSource {\n readonly file: Pick<StorageFunctions['file'], 'get' | 'head'>\n}\n\n/** Storage 只读资源 middleware 配置。 */\nexport interface ServStorageAssetsConfig {\n /** 已初始化的 Storage 模块或等价文件读取能力。 */\n readonly storage: StorageAssetSource\n /**\n * 从请求 pathname 中移除的路由前缀,例如 `/api/v1/assets/`。\n * 是否挂载通配符仍由 `createApp({ middlewares })` 的 `path` 决定。\n */\n readonly pathPrefix: `/${string}`\n /** 允许访问的完整 Storage key 白名单;框架仍会先拒绝路径穿越与非法 key。 */\n readonly keyPattern: RegExp\n /** 允许返回的 MIME 白名单。 */\n readonly allowedContentTypes: readonly string[]\n /** Cache-Control 响应头,默认 `no-store`。 */\n readonly cacheControl?: string\n /** 可选的 Cross-Origin-Resource-Policy 响应头。 */\n readonly crossOriginResourcePolicy?: 'same-origin' | 'same-site' | 'cross-origin'\n}\n\n/**\n * 创建由 Storage 提供内容的只读资源 middleware。\n *\n * 仅允许 GET/HEAD;非法路径、非白名单 MIME 与 Storage 读取失败统一返回 404,\n * 避免向外暴露文件是否存在或后端错误细节。\n *\n * @param config - Storage、路径、key、MIME 与缓存策略。\n * @returns 可挂载到 `serv.createApp({ middlewares })` 的 middleware。\n * @example\n * ```ts\n * const middleware = serv.storageAssets({\n * storage,\n * pathPrefix: '/api/v1/avatar-assets/',\n * keyPattern: /^avatars\\/[\\w-]+\\/[\\w-]+\\.webp$/,\n * allowedContentTypes: ['image/webp'],\n * cacheControl: 'public, max-age=31536000, immutable',\n * })\n * ```\n */\nexport function storageAssets(config: ServStorageAssetsConfig): ServMiddleware {\n const pathPrefix = config.pathPrefix.endsWith('/') ? config.pathPrefix : `${config.pathPrefix}/`\n const allowedKeyPattern = new RegExp(config.keyPattern.source, config.keyPattern.flags.replace(/[gy]/g, ''))\n const allowedContentTypes = new Set(config.allowedContentTypes)\n\n return async (c) => {\n const request = c.req.raw\n const path = new URL(request.url).pathname\n if (request.method !== 'GET' && request.method !== 'HEAD')\n return c.body(null, 405, { Allow: 'GET, HEAD' })\n if (!path.startsWith(pathPrefix))\n return notFound(c)\n\n const key = decodeStorageAssetKey(path.slice(pathPrefix.length), allowedKeyPattern)\n if (!key)\n return notFound(c)\n\n const metadata = await config.storage.file.head(key)\n if (!metadata.success || !allowedContentTypes.has(metadata.data.contentType))\n return notFound(c)\n\n const headers: Record<string, string> = {\n 'Cache-Control': config.cacheControl ?? 'no-store',\n 'Content-Length': String(metadata.data.size),\n 'Content-Type': metadata.data.contentType,\n ...(config.crossOriginResourcePolicy\n ? { 'Cross-Origin-Resource-Policy': config.crossOriginResourcePolicy }\n : {}),\n ...(metadata.data.etag ? { ETag: metadata.data.etag } : {}),\n }\n if (metadata.data.etag && matchesEtag(request.headers.get('if-none-match'), metadata.data.etag))\n return c.body(null, 304, headers)\n if (request.method === 'HEAD')\n return c.body(null, 200, headers)\n\n const file = await config.storage.file.get(key)\n if (!file.success)\n return notFound(c)\n return c.body(Uint8Array.from(file.data).buffer, 200, headers)\n }\n}\n\n/** 解码并校验资源 key,拒绝绝对路径、反斜杠、NUL 与点路径段。 */\nfunction decodeStorageAssetKey(value: string, allowedKeyPattern: RegExp): string | undefined {\n try {\n const key = decodeURIComponent(value)\n if (\n key.length === 0\n || key.length > 1024\n || key.startsWith('/')\n || key.includes('\\\\')\n || key.includes('\\0')\n || key.split('/').some(segment => segment === '.' || segment === '..')\n ) {\n return undefined\n }\n const match = allowedKeyPattern.exec(key)\n return match?.[0] === key ? key : undefined\n }\n catch {\n return undefined\n }\n}\n\n/** 按 HTTP 弱比较语义匹配 If-None-Match。 */\nfunction matchesEtag(ifNoneMatch: string | null, etag: string): boolean {\n if (!ifNoneMatch)\n return false\n if (ifNoneMatch.trim() === '*')\n return true\n const expected = normalizeEtag(etag)\n return ifNoneMatch.split(',').some(candidate => normalizeEtag(candidate) === expected)\n}\n\nfunction normalizeEtag(value: string): string {\n return value.trim().replace(/^W\\//i, '')\n}\n\nfunction notFound(c: Parameters<ServMiddleware>[0]): Response {\n return c.body(null, 404, { 'Cache-Control': 'no-store' })\n}\n","/**\n * @h-ai/serv — API service runtime 统一入口\n *\n * 将 createApp、parseRequestContext、buildAuthContextFactory、listen、toFetch、openapi、pipeline 工具集合为扁平\n * 的 `serv` 命名空间,应用层通过这个模块访问所有 runtime 能力而无需直接导入内部文件。\n *\n * 设计原则:最小知识 — 调用方只需 `serv.listen(app, { port })`、`serv.requireAuth(...)`\n * 等扁平 API,不需要感知子模块层级(adapters/pipeline 等内部组织)。\n *\n * @module serv-main\n *\n * @example\n * ```ts\n * import { serv } from '@h-ai/serv'\n *\n * const app = serv.createApp({ contract, procedures })\n * await serv.listen(app, { port: 3000, host: '0.0.0.0' })\n * ```\n */\n\nimport { implement } from '@orpc/server'\nimport { toFetch } from './adapters/serv-adapter-fetch.js'\nimport { listen } from './adapters/serv-adapter-node.js'\nimport { mapHaiError } from './pipelines/serv-pipeline-helper.js'\nimport { requireAuth } from './pipelines/serv-pipeline-require-auth.js'\nimport { requirePermission, WILDCARD_PERMISSION } from './pipelines/serv-pipeline-require-permission.js'\nimport { requireRole, WILDCARD_ROLE } from './pipelines/serv-pipeline-require-role.js'\nimport { createApp } from './serv-app.js'\nimport { ServConfigSchema } from './serv-config.js'\nimport { buildAuthContextFactory, parseRequestContext } from './serv-context.js'\nimport { cors } from './serv-cors.js'\nimport { servM } from './serv-i18n.js'\nimport { generateSpec } from './serv-openapi.js'\nimport { createRuntimeSecurityPolicy } from './serv-runtime-security.js'\nimport { storageAssets } from './serv-storage-assets.js'\nimport {\n resolveRequestLocale,\n validateInputOrFail,\n} from './serv-validation.js'\n\nexport { ServConfigSchema }\n\n/** hai-framework API service runtime 统一入口(扁平 API)。 */\nexport const serv = {\n // 应用与上下文\n createApp,\n parseRequestContext,\n buildAuthContextFactory,\n resolveRequestLocale,\n\n // 运行时适配器\n listen,\n toFetch,\n cors,\n storageAssets,\n createRuntimeSecurityPolicy,\n\n // OpenAPI / 导出\n generateSpec,\n\n // Procedure 装配(contract 路由定义统一使用 `apiContract.route`)。\n /** 基于 contract 创建可注入 context 的 procedure builder(封装 `@orpc/server` 的 `implement`)。 */\n implement,\n\n // oRPC procedure 包装器\n requireAuth,\n requirePermission,\n requireRole,\n mapHaiError,\n validateInputOrFail,\n\n // 通配符常量\n WILDCARD_PERMISSION,\n WILDCARD_ROLE,\n\n // Serv 模块 i18n 消息获取器(支持 `options.locale` 单次调用本地化)。\n m: servM,\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapters/serv-adapter-fetch.ts","../src/features/serv-feature-audio.ts","../src/serv-validation.ts","../src/pipelines/serv-pipeline-require-internal-rpc.ts","../src/pipelines/serv-pipeline-security-headers.ts","../src/serv-config.ts","../src/serv-context.ts","../src/serv-cookie-auth.ts","../src/serv-openapi.ts","../src/serv-transport.ts","../src/serv-app.ts","../src/adapters/serv-adapter-node.ts","../src/serv-cors.ts","../src/serv-runtime-security.ts","../src/serv-storage-assets.ts","../src/serv-main.ts"],"names":["core","ok","err","HaiCommonError","cors","process","logger","HonoApp"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @h-ai/serv — Fetch 适配器\n *\n * 将 ServHttpApp 包装为标准 Web `fetch(Request)` 处理函数,\n * 适用于 Cloudflare Workers、Deno、Bun 等 Fetch-first 运行时。\n * @module adapters/serv-adapter-fetch\n */\n\nimport type { ServHttpApp } from '../serv-app.js'\n\n/**\n * Fetch runtime handler。\n *\n * 兼容标准 Web `fetch` 签名 `(input, init)` —— 既可作为 Cloudflare Workers / Bun\n * 的 `fetch` 导出,也可作为 oRPC client `fetch` 选项的替代实现,让客户端在测试\n * 中直连 in-process 服务。\n */\nexport type ServFetchHandler = typeof fetch\n\n/**\n * 将 ServHttpApp 转成 Fetch handler。\n *\n * @param app - `serv.createApp()` 返回的 HTTP app\n * @returns fetch-compatible handler\n *\n * @example\n * ```ts\n * // Cloudflare Workers\n * export default { fetch: serv.toFetch(app) }\n *\n * // Bun\n * Bun.serve({ fetch: serv.toFetch(app), port: 3000 })\n *\n * // 测试:把服务端 app 当作 fetch 注入到 client,免去 HTTP 监听\n * const client = apiClient.create(contract)\n * await client.init({ baseUrl: 'http://test', fetch: serv.toFetch(app) })\n * ```\n */\nexport function toFetch(app: ServHttpApp): ServFetchHandler {\n // 包装为标准 fetch 签名:接受 `Request | URL | string` + `RequestInit?`,\n // 内部统一转为 `Request` 再委托给 ServHttpApp。\n const handler = (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = input instanceof Request\n ? input\n : new Request(typeof input === 'string' ? input : input.toString(), init)\n return Promise.resolve(app.fetch(request))\n }\n return handler as ServFetchHandler\n}\n","/**\n * @h-ai/serv — 统一语音 WebSocket 接入\n *\n * 基于 `@h-ai/ai` 的 `ai.audio` 提供开箱即用的语音 WebSocket 端点,同时支持非实时 / 实时\n * 的语音识别与语音合成。传输细节(WebSocket 帧、二进制/JSON 编解码)在此内部完成,\n * 客户端只使用统一的领域协议,不接触任何厂商原生事件。\n *\n * 安全与健壮性:\n * - 连接建立后立即用一次性 ticket 完成鉴权(独立预鉴权超时),未鉴权不处理任何业务帧;\n * - 每一帧经运行时 Zod 校验,按严格状态机接受(识别只收音频、合成只收文本、start/done 各一次);\n * - 输入队列、消息积压、发送缓冲均有上限,超限即以领域错误关闭,取消时级联中止。\n * @module features/serv-feature-audio\n */\n\nimport type { AIFunctions, AudioWsClientMessage, AudioWsServerMessage, AudioWsStartMessage, SynthesisTextSegment } from '@h-ai/ai'\nimport type { HaiError, HaiResult } from '@h-ai/core'\nimport type { Hono } from 'hono'\nimport type { UpgradeWebSocket, WSContext } from 'hono/ws'\nimport type { AudioTicketGrant, AudioTicketVerification, AuthorizedAudioRequest } from '../serv-app.js'\nimport type { ServSession } from '../serv-context.js'\nimport { Buffer } from 'node:buffer'\nimport { AudioWsClientMessageSchema } from '@h-ai/api-contract'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\n\nconst logger = core.logger.child({ module: 'serv', scope: 'audio-ws' })\n\n/** 语音 WebSocket 接入依赖。 */\nexport interface AudioWsDeps {\n /** AI 服务对象的语音能力(`ai.audio`)。 */\n readonly ai: Pick<AIFunctions, 'audio'>\n /** 校验并原子消费短期、一次性的 Audio ticket,返回会话与可选授权。 */\n readonly verifyTicket: (ticket: string) => Promise<HaiResult<AudioTicketVerification>>\n /** 基于会话授权操作、模型、音色、格式和配额;第三参为票据绑定授权(如有)。 */\n readonly authorize?: (session: ServSession, request: AudioWsStartMessage, grant?: AudioTicketGrant) => Promise<HaiResult<AuthorizedAudioRequest>>\n /** 会话结束时释放应用侧并发占用。 */\n readonly onSessionEnd?: (session: ServSession, request: AuthorizedAudioRequest) => void | Promise<void>\n /** 单条消息字节上限(默认 1 MiB,防止内存放大)。 */\n readonly maxMessageBytes?: number\n /** 单连接累计接收音频字节上限(默认 10 MiB,防止非流式缓冲无限增长)。 */\n readonly maxBufferedBytes?: number\n /** 单连接累计接收文本字节上限(默认 1 MiB,防止 TTS 文本输入无限增长与上游费用失控)。 */\n readonly maxTextBytes?: number\n /** 单连接最长持续时间(毫秒,默认 5 分钟,防止恶意长连接)。 */\n readonly maxSessionMs?: number\n /** 预鉴权超时(毫秒,默认 5000):连接建立后未完成鉴权并收到 start 即关闭。 */\n readonly preAuthTimeoutMs?: number\n /** 未处理消息积压上限(默认 256):串行处理链积压超过此数立即关闭。 */\n readonly maxPendingMessages?: number\n /** 发送缓冲高水位(字节,默认 8 MiB):bufferedAmount 超过即关闭慢客户端。 */\n readonly maxSendBufferBytes?: number\n}\n\nconst DEFAULT_MAX_MESSAGE_BYTES = 1_048_576\nconst DEFAULT_MAX_BUFFERED_BYTES = 10 * 1024 * 1024\nconst DEFAULT_MAX_TEXT_BYTES = 1 * 1024 * 1024\nconst DEFAULT_MAX_SESSION_MS = 5 * 60 * 1000\nconst DEFAULT_PRE_AUTH_TIMEOUT_MS = 5000\nconst DEFAULT_MAX_PENDING_MESSAGES = 256\nconst DEFAULT_MAX_SEND_BUFFER_BYTES = 8 * 1024 * 1024\n/** 输入队列单向最大缓存帧数(背压:超过即以领域错误关闭)。 */\nconst MAX_QUEUE_ITEMS = 4096\n\n// 领域错误码(对齐 @h-ai/ai audio 错误段 hai:ai:05x)\nconst CODE_PROTOCOL = 'hai:ai:050'\nconst CODE_TICKET = 'hai:ai:054'\nconst CODE_TOO_LARGE = 'hai:ai:058'\nconst CODE_TIMEOUT = 'hai:ai:057'\n\n/** 连接状态机 */\ntype ConnectionState\n = | 'connecting'\n | 'authenticated'\n | 'awaiting_start'\n | 'transcribing'\n | 'synthesizing'\n | 'input_completed'\n | 'completed'\n | 'failed'\n | 'cancelled'\n\n/** 连接级限额。 */\ninterface ConnectionLimits {\n readonly maxMessageBytes: number\n readonly maxBufferedBytes: number\n readonly maxTextBytes: number\n readonly maxPendingMessages: number\n readonly maxSendBufferBytes: number\n}\n\n/**\n * 在 Hono app 上注册统一语音 WebSocket 路由。\n *\n * @param app - Hono app\n * @param path - 绝对路径(已与 apiPrefix 拼接)\n * @param upgradeWebSocket - 由 `createNodeWebSocket` 提供的升级处理器\n * @param deps - 语音接入依赖\n */\nexport function registerAudioWsRoute(\n app: Hono,\n path: string,\n upgradeWebSocket: UpgradeWebSocket,\n deps: AudioWsDeps,\n): void {\n const limits: ConnectionLimits = {\n maxMessageBytes: deps.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES,\n maxBufferedBytes: deps.maxBufferedBytes ?? DEFAULT_MAX_BUFFERED_BYTES,\n maxTextBytes: deps.maxTextBytes ?? DEFAULT_MAX_TEXT_BYTES,\n maxPendingMessages: deps.maxPendingMessages ?? DEFAULT_MAX_PENDING_MESSAGES,\n maxSendBufferBytes: deps.maxSendBufferBytes ?? DEFAULT_MAX_SEND_BUFFER_BYTES,\n }\n const maxSessionMs = deps.maxSessionMs ?? DEFAULT_MAX_SESSION_MS\n const preAuthTimeoutMs = deps.preAuthTimeoutMs ?? DEFAULT_PRE_AUTH_TIMEOUT_MS\n\n app.get(path, upgradeWebSocket((c) => {\n const ticket = c.req.query('ticket')\n const conn = new AudioConnection(deps, ticket, limits)\n let sessionTimer: ReturnType<typeof setTimeout> | undefined\n\n return {\n onOpen(_evt, ws) {\n // 连接建立即启动鉴权与预鉴权超时;会话超时另行计时。\n conn.begin(ws, preAuthTimeoutMs)\n sessionTimer = setTimeout(() => {\n conn.fail(ws, CODE_TIMEOUT, 'audio session timeout')\n }, maxSessionMs)\n },\n onMessage(evt, ws) {\n // 串行处理同一连接的消息,鉴权完成前不处理业务帧(付费 ASR/TTS)\n conn.enqueue(ws, evt.data)\n },\n onClose() {\n if (sessionTimer)\n clearTimeout(sessionTimer)\n conn.dispose()\n },\n onError() {\n if (sessionTimer)\n clearTimeout(sessionTimer)\n conn.dispose()\n },\n }\n }))\n}\n\n/** 背压友好的有界异步队列(桥接 WebSocket 事件 → ai.audio 的 AsyncIterable 输入)。 */\nclass AsyncQueue<T> {\n private items: T[] = []\n private waiting: ((result: IteratorResult<T>) => void) | null = null\n private ended = false\n\n constructor(private readonly maxItems: number) {}\n\n /** 入队;超过上限返回 false(背压信号,调用方据此关闭连接)。 */\n push(item: T): boolean {\n if (this.ended)\n return true\n if (this.waiting) {\n const w = this.waiting\n this.waiting = null\n w({ value: item, done: false })\n return true\n }\n if (this.items.length >= this.maxItems)\n return false\n this.items.push(item)\n return true\n }\n\n end(): void {\n if (this.ended)\n return\n this.ended = true\n if (this.waiting) {\n const w = this.waiting\n this.waiting = null\n w({ value: undefined as never, done: true })\n }\n }\n\n async* iterate(): AsyncIterableIterator<T> {\n while (true) {\n if (this.items.length > 0) {\n yield this.items.shift() as T\n continue\n }\n if (this.ended)\n return\n const next = await new Promise<IteratorResult<T>>((res) => {\n this.waiting = res\n })\n if (next.done)\n return\n yield next.value\n }\n }\n}\n\n/** 单个语音 WebSocket 连接的状态机。 */\nclass AudioConnection {\n private state: ConnectionState = 'connecting'\n private start?: AudioWsStartMessage\n private session?: ServSession\n private grant?: AudioTicketGrant\n private authorizedRequest?: AuthorizedAudioRequest\n private doneReceived = false\n private readonly audioQueue: AsyncQueue<Uint8Array>\n private readonly textQueue: AsyncQueue<SynthesisTextSegment>\n private readonly audioBuffer: Uint8Array[] = []\n private receivedBytes = 0\n private receivedTextBytes = 0\n /** 合成文本段 ID 去重(同一会话内唯一)。 */\n private readonly seenSegmentIds = new Set<string>()\n private readonly controller = new AbortController()\n private closed = false\n private preAuthTimer?: ReturnType<typeof setTimeout>\n /** 待处理消息数(含鉴权任务):积压过多即关闭。 */\n private pending = 0\n /** 消息串行处理链:确保同一连接的消息按到达顺序处理,鉴权完成前不处理业务帧。 */\n private queue: Promise<void> = Promise.resolve()\n\n constructor(\n private readonly deps: AudioWsDeps,\n private readonly ticket: string | undefined,\n private readonly limits: ConnectionLimits,\n ) {\n this.audioQueue = new AsyncQueue<Uint8Array>(MAX_QUEUE_ITEMS)\n this.textQueue = new AsyncQueue<SynthesisTextSegment>(MAX_QUEUE_ITEMS)\n }\n\n /** 连接建立:立即鉴权(串行链首个任务)并启动预鉴权超时。 */\n begin(ws: WSContext, preAuthTimeoutMs: number): void {\n this.preAuthTimer = setTimeout(() => {\n // 鉴权完成并进入业务态前未收到 start:以预鉴权超时关闭(独立于会话超时)\n if (this.state === 'connecting' || this.state === 'authenticated' || this.state === 'awaiting_start')\n this.fail(ws, CODE_TIMEOUT, 'pre-auth timeout: authenticate and send start in time')\n }, preAuthTimeoutMs)\n this.chain(ws, () => this.authenticate(ws))\n }\n\n /** 将任务追加到串行处理链(含积压上限保护)。 */\n private chain(ws: WSContext, task: () => Promise<void>): void {\n if (this.closed)\n return\n this.pending++\n if (this.pending > this.limits.maxPendingMessages) {\n // 积压超限:立即以领域错误关闭,避免 Promise 链无界增长\n this.pending--\n this.fail(ws, CODE_TOO_LARGE, 'too many pending messages')\n return\n }\n this.queue = this.queue\n .then(() => task())\n .catch((error: unknown) => {\n logger.debug('audio ws task failed', { error: error instanceof Error ? error.message : String(error) })\n if (!this.closed)\n this.failFromError(ws, error)\n })\n .finally(() => {\n this.pending--\n })\n }\n\n /** 将消息追加到串行处理链,保证按序处理(鉴权完成前不处理业务帧)。 */\n enqueue(ws: WSContext, data: unknown): void {\n this.chain(ws, () => this.handleMessage(ws, data))\n }\n\n /** 连接建立即执行:校验并原子消费 ticket,成功后进入 awaiting_start。 */\n private async authenticate(ws: WSContext): Promise<void> {\n if (this.closed)\n return\n if (!this.ticket) {\n this.fail(ws, CODE_TICKET, 'missing audio ticket')\n return\n }\n const verified = await this.deps.verifyTicket(this.ticket)\n if (this.closed)\n return\n if (!verified.success) {\n this.fail(ws, String(verified.error.code), verified.error.message)\n return\n }\n this.session = verified.data.session\n this.grant = verified.data.grant\n this.state = 'awaiting_start'\n }\n\n async handleMessage(ws: WSContext, data: unknown): Promise<void> {\n if (this.closed)\n return\n\n // 二进制帧 → 音频分片\n const bytes = toBytes(data)\n if (bytes) {\n this.handleAudioFrame(ws, bytes)\n return\n }\n\n // 文本帧 → JSON 控制消息(UTF-8 实际字节计,非 JS 字符数)\n if (typeof data !== 'string')\n return\n if (Buffer.byteLength(data, 'utf8') > this.limits.maxMessageBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'message too large')\n return\n }\n\n let raw: unknown\n try {\n raw = JSON.parse(data)\n }\n catch {\n this.fail(ws, CODE_PROTOCOL, 'invalid json message')\n return\n }\n\n // 运行时结构校验:非法帧在进入业务逻辑前拒绝(TypeScript 类型在网络运行时不生效)\n const parsed = AudioWsClientMessageSchema.safeParse(raw)\n if (!parsed.success) {\n this.fail(ws, CODE_PROTOCOL, `invalid message: ${parsed.error.issues[0]?.message ?? 'schema validation failed'}`)\n return\n }\n const message = parsed.data as AudioWsClientMessage\n\n if (message.type === 'start') {\n await this.onStart(ws, message)\n return\n }\n\n // 非 start 帧必须在鉴权且已 start 之后\n if (this.state === 'connecting' || this.state === 'authenticated' || this.state === 'awaiting_start') {\n this.fail(ws, CODE_PROTOCOL, 'message before start')\n return\n }\n\n if (message.type === 'text')\n this.handleTextFrame(ws, message)\n else if (message.type === 'done')\n this.handleDone(ws)\n }\n\n /** 处理音频二进制帧(仅识别操作、输入未结束时接受)。 */\n private handleAudioFrame(ws: WSContext, bytes: Uint8Array): void {\n if (this.state !== 'transcribing') {\n // 合成会话或未 start / 已结束时收到音频帧:协议违规\n this.fail(ws, CODE_PROTOCOL, 'unexpected audio frame')\n return\n }\n if (bytes.length > this.limits.maxMessageBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'audio frame too large')\n return\n }\n this.receivedBytes += bytes.length\n if (this.receivedBytes > this.limits.maxBufferedBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'buffered audio exceeds limit')\n return\n }\n if (this.start?.stream) {\n if (!this.audioQueue.push(bytes))\n this.fail(ws, CODE_TOO_LARGE, 'audio backpressure limit exceeded')\n }\n else {\n this.audioBuffer.push(bytes)\n }\n }\n\n /** 处理文本控制帧(仅合成操作、输入未结束时接受;segmentId 唯一)。 */\n private handleTextFrame(ws: WSContext, message: { segmentId: string, text: string }): void {\n if (this.state !== 'synthesizing') {\n this.fail(ws, CODE_PROTOCOL, 'unexpected text frame')\n return\n }\n if (this.seenSegmentIds.has(message.segmentId)) {\n this.fail(ws, CODE_PROTOCOL, `duplicate segmentId: ${message.segmentId}`)\n return\n }\n this.receivedTextBytes += Buffer.byteLength(message.text, 'utf8')\n if (this.receivedTextBytes > this.limits.maxTextBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'buffered text exceeds limit')\n return\n }\n this.seenSegmentIds.add(message.segmentId)\n if (!this.textQueue.push({ id: message.segmentId, text: message.text }))\n this.fail(ws, CODE_TOO_LARGE, 'text backpressure limit exceeded')\n }\n\n /** 处理输入结束帧(只允许一次)。 */\n private handleDone(ws: WSContext): void {\n if (this.doneReceived) {\n this.fail(ws, CODE_PROTOCOL, 'duplicate done')\n return\n }\n this.doneReceived = true\n this.audioQueue.end()\n this.textQueue.end()\n if (this.state === 'transcribing' || this.state === 'synthesizing')\n this.state = 'input_completed'\n if (this.start && !this.start.stream && this.start.operation === 'transcribe')\n void this.runBufferedTranscribe(ws)\n }\n\n private async onStart(ws: WSContext, message: AudioWsStartMessage): Promise<void> {\n // 必须已鉴权且尚未开始\n if (this.state === 'connecting' || this.state === 'authenticated') {\n this.fail(ws, CODE_TICKET, 'not authenticated')\n return\n }\n if (this.state !== 'awaiting_start') {\n this.fail(ws, CODE_PROTOCOL, 'session already started')\n return\n }\n const session = this.session\n if (!session) {\n this.fail(ws, CODE_TICKET, 'not authenticated')\n return\n }\n\n const authorized = this.deps.authorize\n ? await this.deps.authorize(session, message, this.grant)\n : this.defaultAuthorize(message)\n if (this.closed)\n return\n if (!authorized.success) {\n this.fail(ws, String(authorized.error.code), authorized.error.message)\n return\n }\n\n const confirmed: AudioWsStartMessage = {\n ...message,\n operation: authorized.data.operation,\n model: authorized.data.model,\n voice: authorized.data.voice,\n instruction: authorized.data.instruction,\n format: authorized.data.format,\n sampleRate: authorized.data.sampleRate,\n }\n\n this.start = confirmed\n this.authorizedRequest = authorized.data\n // 进入业务态并清除预鉴权超时\n this.state = confirmed.operation === 'synthesize' ? 'synthesizing' : 'transcribing'\n if (this.preAuthTimer) {\n clearTimeout(this.preAuthTimer)\n this.preAuthTimer = undefined\n }\n\n if (confirmed.operation === 'synthesize')\n void this.runSynthesize(ws, confirmed)\n else if (confirmed.operation === 'transcribe' && confirmed.stream)\n void this.runStreamTranscribe(ws, confirmed)\n // 非流式识别在收到 done 后由 runBufferedTranscribe 处理\n }\n\n /** 无 authorize 回调时的默认授权:仅保留操作与格式,并交叉校验票据 grant。 */\n private defaultAuthorize(message: AudioWsStartMessage): HaiResult<AuthorizedAudioRequest> {\n // 票据绑定了操作但与请求不符:拒绝(防止越权复用票据)\n if (this.grant?.operation && this.grant.operation !== message.operation) {\n return err(HaiCommonError.FORBIDDEN, 'operation not permitted by ticket')\n }\n return ok({\n operation: message.operation,\n // 无 authorize 回调时不透传客户端 model/voice/instruction;仅采用票据 grant 绑定的 model\n model: this.grant?.model,\n format: message.format,\n })\n }\n\n /** 流式识别:桥接持续音频输入,流式返回临时/最终结果与语音起止事件 */\n private async runStreamTranscribe(ws: WSContext, start: AudioWsStartMessage): Promise<void> {\n try {\n const stream = this.deps.ai.audio.transcribeStream({\n audio: {\n chunks: this.audioQueue.iterate(),\n format: start.format ?? 'pcm16',\n sampleRate: start.sampleRate ?? 16000,\n channels: start.channels,\n },\n language: start.language,\n contextHints: start.contextHints,\n model: start.model,\n signal: this.controller.signal,\n })\n for await (const event of stream) {\n if (this.closed)\n return\n if (event.type === 'transcript')\n this.send(ws, { type: 'transcript', text: event.text, final: event.final })\n else if (event.type === 'speech_started')\n this.send(ws, { type: 'speech_started' })\n else if (event.type === 'speech_stopped')\n this.send(ws, { type: 'speech_stopped' })\n }\n this.end(ws)\n }\n catch (error) {\n this.failFromError(ws, error)\n }\n }\n\n /** 非流式识别:缓冲完整音频后返回单条最终结果 */\n private async runBufferedTranscribe(ws: WSContext): Promise<void> {\n const start = this.start\n if (!start)\n return\n try {\n const result = await this.deps.ai.audio.transcribe({\n audio: {\n data: concat(this.audioBuffer),\n format: start.format ?? 'wav',\n sampleRate: start.sampleRate,\n channels: start.channels,\n },\n language: start.language,\n contextHints: start.contextHints,\n model: start.model,\n signal: this.controller.signal,\n })\n if (this.closed)\n return\n if (!result.success) {\n this.fail(ws, String(result.error.code), result.error.message)\n return\n }\n this.send(ws, { type: 'transcript', text: result.data.text, final: true })\n this.end(ws)\n }\n catch (error) {\n this.failFromError(ws, error)\n }\n }\n\n /** 语音合成:桥接文本输入,流式返回音频二进制帧(带发送缓冲背压) */\n private async runSynthesize(ws: WSContext, start: AudioWsStartMessage): Promise<void> {\n try {\n const stream = this.deps.ai.audio.synthesizeStream({\n text: this.textQueue.iterate(),\n voice: start.voice,\n instruction: start.instruction,\n format: start.format,\n sampleRate: start.sampleRate,\n model: start.model,\n signal: this.controller.signal,\n })\n for await (const event of stream) {\n if (this.closed)\n return\n if (event.type === 'audio') {\n // 发送缓冲高水位保护:慢客户端导致底层缓冲膨胀时以领域错误关闭\n if (sendBufferedAmount(ws) > this.limits.maxSendBufferBytes) {\n this.fail(ws, CODE_TOO_LARGE, 'client send buffer exceeded high-water mark')\n return\n }\n ws.send(toArrayBuffer(event.data))\n }\n else {\n this.send(ws, event)\n }\n }\n this.end(ws)\n }\n catch (error) {\n this.failFromError(ws, error)\n }\n }\n\n fail(ws: WSContext, code: string, message: string): void {\n if (this.closed)\n return\n this.state = 'failed'\n this.send(ws, { type: 'error', code, message } satisfies AudioWsServerMessage)\n this.dispose()\n ws.close()\n }\n\n private failFromError(ws: WSContext, error: unknown): void {\n if (this.closed)\n return\n const hai = error as Partial<HaiError>\n const code = typeof hai?.code === 'string' ? hai.code : CODE_TICKET\n const message = error instanceof Error ? error.message : String(error)\n this.fail(ws, code, message)\n }\n\n private end(ws: WSContext): void {\n if (this.closed)\n return\n this.state = 'completed'\n this.send(ws, { type: 'end' })\n this.dispose()\n ws.close()\n }\n\n private send(ws: WSContext, message: AudioWsServerMessage): void {\n if (!this.closed)\n ws.send(JSON.stringify(message))\n }\n\n dispose(): void {\n if (this.closed)\n return\n this.closed = true\n if (this.state !== 'completed' && this.state !== 'failed')\n this.state = 'cancelled'\n if (this.preAuthTimer) {\n clearTimeout(this.preAuthTimer)\n this.preAuthTimer = undefined\n }\n this.audioQueue.end()\n this.textQueue.end()\n // 级联取消:中止上游 provider 流与所有等待\n this.controller.abort()\n if (this.session && this.authorizedRequest && this.deps.onSessionEnd) {\n void Promise.resolve(this.deps.onSessionEnd(this.session, this.authorizedRequest)).catch((error: unknown) => {\n logger.warn('audio ws session end hook failed', { error: error instanceof Error ? error.message : String(error) })\n })\n }\n }\n}\n\n/** 读取 WebSocket 底层发送缓冲字节数(不可用时返回 0)。 */\nfunction sendBufferedAmount(ws: WSContext): number {\n const raw = (ws as { raw?: { bufferedAmount?: number } }).raw\n return typeof raw?.bufferedAmount === 'number' ? raw.bufferedAmount : 0\n}\n\n/** 将 WebSocket 消息数据转换为二进制字节(非二进制返回 undefined)。 */\nfunction toBytes(data: unknown): Uint8Array | undefined {\n if (typeof data === 'string')\n return undefined\n if (data instanceof ArrayBuffer)\n return new Uint8Array(data)\n if (ArrayBuffer.isView(data))\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\n if (Buffer.isBuffer(data))\n return new Uint8Array(data)\n return undefined\n}\n\n/** 顺序拼接音频分片。 */\nfunction concat(chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((sum, c) => sum + c.length, 0)\n const out = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n out.set(chunk, offset)\n offset += chunk.length\n }\n return out\n}\n\n/** 将 Uint8Array 转为独立 ArrayBuffer(供 WSContext.send 二进制发送)。 */\nfunction toArrayBuffer(data: Uint8Array): ArrayBuffer {\n const buffer = new ArrayBuffer(data.length)\n new Uint8Array(buffer).set(data)\n return buffer\n}\n","/**\n * @h-ai/serv — 校验工具\n *\n * 为 API service 提供与 kit 对等的校验 i18n 能力:\n * - {@link resolveRequestLocale}:从请求头(`x-hai-locale` / `Accept-Language`)解析语言。\n * - {@link validateInputOrFail}:在 procedure 内显式校验数据,失败返回 `HaiResult` 错误。\n * - oRPC contract 输入校验失败的自动本地化由 `serv-app.ts` 中的 oRPC 响应拦截器处理。\n *\n * @module serv-validation\n */\n\nimport type { HaiResult, ValidationFormError } from '@h-ai/core'\nimport type { ZodType } from 'zod'\nimport type { ServMessageKey } from './serv-i18n.js'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { normalizeServLocale, servM } from './serv-i18n.js'\n\nconst DEFAULT_LOCALE = 'zh-CN'\n\n/**\n * 校验失败响应体(含 i18n 错误列表)。\n *\n * 复用 `HaiResult` 失败分支结构。字段错误同时在两处出现:\n * - `error.cause`:与 `validateInputOrFail` 输出对齐,HaiResult 标准位置\n * - 顶层 `errors`:保留以兼容已有客户端\n */\nexport interface ServValidationFailureBody {\n readonly success: false\n readonly error: {\n readonly code: string | number\n readonly message: string\n readonly httpStatus: number\n readonly system: string\n readonly module: string\n readonly cause?: readonly ValidationFormError[]\n }\n readonly errors: readonly ValidationFormError[]\n}\n\n/**\n * 从请求头解析客户端期望 locale。\n *\n * 优先级:`x-hai-locale` > `Accept-Language` 第一个匹配项 > 默认值(`zh-CN`)。\n * 返回值会被规范化为 serv 当前支持的 locale(如 `en` → `en-US`)。\n *\n * @param headers - 请求头(`Headers` 或与之等价的对象)\n * @returns 解析得到的 locale\n */\nexport function resolveRequestLocale(headers: Headers | Record<string, string | undefined>): string {\n const get = (name: string): string | undefined => {\n if (headers instanceof Headers)\n return headers.get(name) ?? undefined\n return headers[name] ?? headers[name.toLowerCase()]\n }\n\n const explicit = get('x-hai-locale')\n if (explicit)\n return normalizeServLocale(explicit)\n\n return normalizeServLocale(get('accept-language') ?? get('Accept-Language') ?? DEFAULT_LOCALE)\n}\n\n/**\n * 把 Zod 错误转为本地化的扁平错误列表。\n *\n * @param zodError - Zod `SafeParseError` 或 `ZodError`\n * @param locale - 期望 locale\n */\nexport function localizeZodError(zodError: unknown, locale: string): ValidationFormError[] {\n const resolvedLocale = normalizeServLocale(locale)\n return core.zodValidation.mapZodErrorToFormErrors(\n zodError,\n core.zodValidation.createPrefixedZodMessageGetter<ServMessageKey>(\n 'serv',\n (messageKey, params) => servM(messageKey, { locale: resolvedLocale, params }),\n ),\n )\n}\n\n/**\n * 在 procedure handler 内显式校验数据。\n *\n * 与 oRPC contract 自动校验不同,本函数用于业务层二次校验(例如对从数据库读到的对象再校验)。\n * 失败时返回 `HaiResult` 错误分支,错误的 `cause` 字段携带 `ValidationFormError[]`。\n *\n * 推荐使用方式:\n * ```ts\n * import { z } from 'zod'\n * import { serv } from '@h-ai/serv'\n *\n * const Schema = z.object({ name: z.string().min(1) })\n *\n * export const procedures = serv\n * .implement(contract)\n * .context<ServContext>()\n * .route('profile.update', ({ input, context }) => {\n * const result = serv.validateInputOrFail(Schema, input, context.locale)\n * if (!result.success) return result\n * return profileService.update(result.data)\n * })\n * .build()\n * ```\n *\n * @param schema - Zod schema\n * @param input - 待校验数据\n * @param locale - 期望 locale(通常来自请求上下文)\n */\nexport function validateInputOrFail<TOutput>(\n schema: ZodType<TOutput>,\n input: unknown,\n locale: string = DEFAULT_LOCALE,\n): HaiResult<TOutput> {\n const resolvedLocale = normalizeServLocale(locale)\n const result = schema.safeParse(input)\n if (result.success)\n return ok(result.data)\n const errors = localizeZodError(result.error, resolvedLocale)\n return err(HaiCommonError.VALIDATION_ERROR, servM('serv_validationFailed', { locale: resolvedLocale }), errors)\n}\n","/**\n * @h-ai/serv — 内部 RPC 访问控制 middleware\n * @module pipelines/serv-pipeline-require-internal-rpc\n */\n\nimport type { Context } from 'hono'\nimport type { ServRpcHttpConfig } from '../serv-config.js'\nimport type { ServMiddleware } from './serv-pipeline-types.js'\nimport { core, HaiCommonError } from '@h-ai/core'\nimport { servM } from '../serv-i18n.js'\nimport { resolveRequestLocale } from '../serv-validation.js'\nimport { buildHaiErrorBody } from './serv-pipeline-helper.js'\n\nconst pipelineLogger = core.logger.child({ module: 'serv', scope: 'pipeline' })\n\n/**\n * 懒加载 `@hono/node-server/conninfo`:仅在 Node 运行时可用。\n *\n * 在 Bun / Workers / Deno 等 Fetch runtime 下,该 sub-path 可能不存在;\n * 用 try/catch 包住模块解析,让模块在非 Node 平台也能加载而不抛错。\n * 解析失败时 `loopback` / `private-network` 模式会因取不到 IP 而 fail closed(403)。\n */\ntype GetConnInfoFn = (c: Context) => { remote: { address?: string } }\nlet cachedGetConnInfo: GetConnInfoFn | null | undefined\n\nasync function resolveGetConnInfo(): Promise<GetConnInfoFn | null> {\n if (cachedGetConnInfo !== undefined)\n return cachedGetConnInfo\n try {\n const mod = await import('@hono/node-server/conninfo')\n cachedGetConnInfo = mod.getConnInfo as GetConnInfoFn\n }\n catch (cause) {\n pipelineLogger.warn('getConnInfo unavailable in this runtime; loopback/private-network access modes will fail closed', { error: cause })\n cachedGetConnInfo = null\n }\n return cachedGetConnInfo\n}\n\nconst LOOPBACK_IPS = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1', 'localhost'])\n\n/**\n * 限制内部 RPC 入口来源。\n *\n * 应用层兜底校验;生产仍应优先依赖内网、服务网格或网关策略。\n *\n * @param config - RPC 访问控制配置\n * @returns Hono middleware\n *\n * @example\n * ```ts\n * app.use('/rpc/*', requireInternalRPC({ prefix: '/rpc', access: 'loopback' }))\n * ```\n */\nexport function requireInternalRPC(config: ServRpcHttpConfig): ServMiddleware {\n return async (c, next) => {\n // Step 1:先解析 locale,确保拒绝访问时也能返回本地化错误消息。\n const locale = resolveRequestLocale(c.req.raw.headers)\n\n // Step 2:若是 gateway-only 模式,直接校验网关注入的共享密钥 header。\n if (config.access === 'gateway-only') {\n const headerName = config.gatewayHeader ?? 'x-hai-internal-rpc'\n const received = c.req.header(headerName)\n if (!config.gatewaySecret || !received || !core.string.constantTimeEqual(received, config.gatewaySecret))\n return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale })), 403)\n return next()\n }\n\n // Step 3:其余模式依赖远端 IP 判定来源是否合法;取不到 IP 时按 fail closed 处理。\n const ip = await getRequestRemoteAddress(c) ?? ''\n\n // Step 4:loopback 模式只允许本机来源。\n if (config.access === 'loopback' && !isLoopbackIP(ip))\n return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale })), 403)\n\n // Step 5:private-network 模式允许内网来源(含 loopback)。\n if (config.access === 'private-network' && !isPrivateNetworkIP(ip))\n return c.json(buildHaiErrorBody(HaiCommonError.FORBIDDEN, servM('serv_errorForbidden', { locale })), 403)\n\n // Step 6:通过校验后,请求才会真正进入内部 RPC handler。\n return next()\n }\n}\n\nasync function getRequestRemoteAddress(c: Context): Promise<string | undefined> {\n const getConnInfo = await resolveGetConnInfo()\n if (!getConnInfo)\n return undefined\n try {\n return getConnInfo(c).remote.address\n }\n catch {\n return undefined\n }\n}\n\nfunction normalizeIpAddress(ip: string): string {\n return ip.startsWith('::ffff:') ? ip.slice('::ffff:'.length) : ip\n}\n\nfunction isLoopbackIP(ip: string): boolean {\n const normalized = normalizeIpAddress(ip)\n return LOOPBACK_IPS.has(ip) || LOOPBACK_IPS.has(normalized)\n}\n\nfunction isPrivateNetworkIP(ip: string): boolean {\n const normalized = normalizeIpAddress(ip)\n if (isLoopbackIP(ip))\n return true\n if (normalized.startsWith('10.') || normalized.startsWith('192.168.'))\n return true\n const second = Number(normalized.split('.')[1])\n return normalized.startsWith('172.') && Number.isInteger(second) && second >= 16 && second <= 31\n}\n","/**\n * @h-ai/serv — 默认安全响应头 middleware\n * @module pipelines/serv-pipeline-security-headers\n */\n\nimport type { ServMiddleware } from './serv-pipeline-types.js'\n\n/**\n * 添加基础安全响应头:\n * `X-Content-Type-Options: nosniff`、`X-Frame-Options: DENY`、`Referrer-Policy: no-referrer`。\n *\n * @returns Hono middleware\n */\nexport function securityHeaders(): ServMiddleware {\n return async (c, next) => {\n c.header('X-Content-Type-Options', 'nosniff')\n c.header('X-Frame-Options', 'DENY')\n c.header('Referrer-Policy', 'no-referrer')\n await next()\n }\n}\n","/**\n * @h-ai/serv — HTTP 配置\n *\n * 提供用于控制 API 前缀、OpenAPI、docs、健康检查、内部 RPC 与传输加密策略的配置结构,\n * 并由 zod 负责默认值填充与边界校验。\n * @module serv-config\n */\n\nimport { z } from 'zod'\n\nconst ApiPrefixSchema = z.custom<`/api/${string}`>(\n value => typeof value === 'string' && value.startsWith('/api/'),\n 'apiPrefix must start with /api/',\n)\n\nconst AbsolutePathSchema = z.custom<`/${string}`>(\n value => typeof value === 'string' && value.startsWith('/'),\n 'path must start with /',\n)\n\n/** OpenAPI JSON endpoint 配置。 */\nexport interface ServOpenAPIHttpConfig {\n readonly path: `/${string}`\n}\n\n/** 文档页面 endpoint 配置。 */\nexport interface ServDocsHttpConfig {\n readonly path: `/${string}`\n readonly requireAuth?: boolean\n}\n\n/** 健康检查 endpoint 配置。 */\nexport interface ServHealthHttpConfig {\n readonly path: `/${string}`\n readonly readyPath?: `/${string}`\n}\n\n/** 内部 RPC endpoint 配置。 */\nexport interface ServRpcHttpConfig {\n readonly prefix: `/${string}`\n readonly access: 'loopback' | 'private-network' | 'gateway-only'\n readonly gatewayHeader?: string\n readonly gatewaySecret?: string\n}\n\n/** 传输加密运行时配置(对应 `config/_serv.yml`)。 */\nexport interface ServTransportRuntimeConfig {\n readonly keyExchangePath: `/${string}`\n readonly excludePaths: readonly `/${string}`[]\n readonly maxClients: number\n}\n\n/** `config/_serv.yml` 中可序列化的跨端 CORS 配置。 */\nexport interface ServCorsRuntimeConfig {\n readonly origin?: string\n readonly nativeOrigins?: string\n readonly allowedHeaders: readonly string[]\n readonly exposedHeaders: readonly string[]\n readonly credentials: boolean\n}\n\n/** ServHttpApp 的 HTTP 挂载配置。 */\nexport interface ServHttpConfig {\n readonly apiPrefix: `/api/${string}`\n readonly openapi: false | ServOpenAPIHttpConfig\n readonly docs: false | ServDocsHttpConfig\n readonly health: false | ServHealthHttpConfig\n readonly rpc: false | ServRpcHttpConfig\n}\n\n/** `config/_serv.yml` 的顶层配置结构。 */\nexport interface ServConfig {\n readonly http: ServHttpConfig\n readonly cors: ServCorsRuntimeConfig\n readonly transport: false | ServTransportRuntimeConfig\n}\n\n/** `config/_serv.yml` 中的 CORS 输入类型。 */\nexport type ServCorsRuntimeConfigInput = Partial<ServCorsRuntimeConfig>\n\n/** `config/_serv.yml` 中的传输加密输入类型。 */\nexport type ServTransportRuntimeConfigInput = Partial<{\n readonly keyExchangePath: `/${string}`\n readonly excludePaths: readonly `/${string}`[]\n readonly maxClients: number\n}>\n\n/** `serv.createApp` 接受的 HTTP 配置入参类型。 */\nexport type ServHttpConfigInput = Partial<{\n readonly apiPrefix: `/api/${string}`\n readonly openapi: false | Partial<ServOpenAPIHttpConfig>\n readonly docs: false | Partial<ServDocsHttpConfig>\n readonly health: false | Partial<ServHealthHttpConfig>\n readonly rpc: false | Partial<ServRpcHttpConfig>\n}>\n\n/** `config/_serv.yml` 的顶层输入类型。 */\nexport interface ServConfigInput {\n readonly http?: ServHttpConfigInput\n readonly cors?: ServCorsRuntimeConfigInput\n readonly transport?: false | ServTransportRuntimeConfigInput\n}\n\nconst OpenAPIHttpConfigInputSchema = z.union([\n z.literal(false),\n z.object({ path: AbsolutePathSchema.default('/openapi.json') }),\n])\n\nconst DocsHttpConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n path: AbsolutePathSchema.default('/docs'),\n requireAuth: z.boolean().optional(),\n }),\n])\n\nconst HealthHttpConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n path: AbsolutePathSchema.default('/health'),\n readyPath: AbsolutePathSchema.optional().default('/ready'),\n }),\n])\n\nconst RpcHttpConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n prefix: AbsolutePathSchema.default('/rpc'),\n access: z.enum(['loopback', 'private-network', 'gateway-only']).default('loopback'),\n gatewayHeader: z.string().optional(),\n gatewaySecret: z.string().optional(),\n }),\n])\n\nconst TransportRuntimeConfigInputSchema = z.union([\n z.literal(false),\n z.object({\n keyExchangePath: AbsolutePathSchema.default('/_hai/key-exchange'),\n excludePaths: z.array(AbsolutePathSchema).default([]),\n maxClients: z.number().int().positive().default(10000),\n }),\n])\n\nconst CorsRuntimeConfigInputSchema = z.object({\n origin: z.string().optional(),\n nativeOrigins: z.string().optional(),\n allowedHeaders: z.array(z.string()).default(['Authorization', 'Content-Type', 'X-Requested-With']),\n exposedHeaders: z.array(z.string()).default([]),\n credentials: z.boolean().default(false),\n})\n\nconst ServHttpConfigInputSchema = z.object({\n apiPrefix: ApiPrefixSchema.default('/api/v1'),\n openapi: OpenAPIHttpConfigInputSchema.default(false),\n docs: DocsHttpConfigInputSchema.default(false),\n health: HealthHttpConfigInputSchema.default({ path: '/health', readyPath: '/ready' }),\n rpc: RpcHttpConfigInputSchema.default(false),\n})\n\n/** `config/_serv.yml` 对应的配置 Schema。 */\nexport const ServConfigSchema = z.object({\n http: ServHttpConfigInputSchema.optional(),\n cors: CorsRuntimeConfigInputSchema.optional(),\n transport: TransportRuntimeConfigInputSchema.optional(),\n}).transform(({ http, cors, transport }) => ({\n http: resolveServHttpConfig(http ?? {}),\n cors: resolveServCorsRuntimeConfig(cors ?? {}),\n transport: resolveServTransportRuntimeConfig(transport ?? false),\n}))\n\n/**\n * 解析 HTTP 配置并补齐默认值。\n *\n * @param input - 用户传入的部分 HTTP 配置\n * @returns 完整 HTTP 配置\n */\nexport function resolveServHttpConfig(input: ServHttpConfigInput = {}): ServHttpConfig {\n return ServHttpConfigInputSchema.parse(input)\n}\n\n/** 解析配置文件中的跨端 CORS Header 与凭据策略。 */\nexport function resolveServCorsRuntimeConfig(input: ServCorsRuntimeConfigInput = {}): ServCorsRuntimeConfig {\n return CorsRuntimeConfigInputSchema.parse(input)\n}\n\n/**\n * 解析传输加密配置并补齐默认值。\n *\n * @param input - `config/_serv.yml` 中的 transport 配置\n * @returns 完整 transport 配置;`false` 表示关闭\n */\nexport function resolveServTransportRuntimeConfig(\n input: false | ServTransportRuntimeConfigInput = false,\n): false | ServTransportRuntimeConfig {\n return TransportRuntimeConfigInputSchema.parse(input)\n}\n\n/**\n * 解析 `config/_serv.yml` 顶层配置并补齐默认值。\n *\n * 典型用法:`core.config.validate('serv', ServConfigSchema)`。\n */\nexport function resolveServConfig(input: ServConfigInput = {}): ServConfig {\n return ServConfigSchema.parse(input)\n}\n","/**\n * @h-ai/serv — 请求上下文与认证工厂\n *\n * 定义 oRPC procedures 共享的统一上下文结构(requestId / accessToken / session / logger)。\n *\n * 上下文构造分两层(职责分明):\n * - `parseRequestContext`:**纯同步元数据解析**,从 HTTP header 提取 requestId/locale/ip/userAgent\n * 与 Bearer accessToken。**不会**填充 `session`。\n * - `buildAuthContextFactory(verifyToken)`:**带认证的上下文工厂**。在 `parseRequestContext` 基础上\n * 异步调用 `verifyToken(accessToken)` 校验访问令牌,若成功则把会话写入 `context.session`。\n * 失败或 token 不存在均使 `session = undefined`,由下游 `.auth()` guard 统一返回 401。\n *\n * 应用层通常无需直接调用上述函数:`serv.createApp({ iam })` 内部自动选择最合适的工厂。\n * @module serv-context\n */\n\nimport type { HaiResult, Logger } from '@h-ai/core'\nimport type { RefreshTokenPair } from './serv-cookie-auth.js'\nimport { core } from '@h-ai/core'\nimport { resolveRequestLocale } from './serv-validation.js'\n\n/** 当前请求解析出的会话摘要。 */\nexport interface ServSession {\n readonly userId: string\n readonly username?: string\n readonly roles: string[]\n readonly permissions: string[]\n}\n\n/**\n * serv 与 IAM 模块交互所需的**最小**接口。\n *\n * 结构上是 `@h-ai/iam` 的 `iam` 对象的子集 —— 直接传入 `iam` 即可(TypeScript 结构类型)。\n * 单一出处:在 `serv.createApp({ iam })` 提供后,serv 自动派生:\n * - access token 校验(用于填充 `context.session`)\n * - refresh token 轮换(仅当启用 `refreshCookie` 时使用)\n *\n * 模块消费者**无需**知道 IAM 内部 Session 类型与 ServSession 的差异:结构类型确保兼容。\n */\nexport interface ServIam {\n readonly session: {\n /** 校验访问令牌,成功返回会话快照(结构上是 ServSession 的超集即可)。 */\n verifyToken: (accessToken: string) => Promise<HaiResult<ServSession>>\n /** 使用 refresh token 换发新的 token 对。仅 `refreshCookie` 启用时被调用。 */\n refresh: (refreshToken: string) => Promise<HaiResult<RefreshTokenPair>>\n }\n}\n\n/** `@h-ai/serv` 在 procedures 中传递的统一上下文。 */\nexport interface ServContext {\n readonly requestId: string\n readonly locale: string\n readonly ip?: string\n readonly userAgent?: string\n readonly accessToken?: string\n readonly session?: ServSession\n readonly request: Request\n readonly logger: Logger\n}\n\n/** 创建上下文所需的输入。 */\nexport interface CreateServContextInput {\n readonly request: Request\n}\n\n/** 应用可传入的上下文工厂。 */\nexport type CreateServContext = (input: CreateServContextInput) => ServContext | Promise<ServContext>\n\nconst HEADER_REQUEST_ID = 'x-request-id'\nconst HEADER_FORWARDED_FOR = 'x-forwarded-for'\nconst HEADER_REAL_IP = 'x-real-ip'\nconst HEADER_AUTHORIZATION = 'authorization'\nconst HEADER_USER_AGENT = 'user-agent'\n\n/**\n * 从 Authorization header 中提取 Bearer token。\n *\n * 这里只处理 **access token**。\n * 若启用了 `refreshCookie`,refresh token 不会出现在 Authorization header 中,\n * 而是通过浏览器 httpOnly cookie 或受信原生请求的 JSON body 传输,\n * 并只在 `/auth/refresh` 路由里读取。\n *\n * @param value - Authorization header 原始值\n * @returns token 或 undefined\n */\nexport function extractBearerToken(value: string | null): string | undefined {\n if (!value)\n return undefined\n const [scheme, token] = value.split(' ')\n if (scheme?.toLowerCase() !== 'bearer' || !token)\n return undefined\n return token\n}\n\n/**\n * 解析 HTTP 请求元数据并构造基础 ServContext(**同步**,**不**填充 session)。\n *\n * - 提取 `x-request-id` / `accept-language` / `x-forwarded-for` / `x-real-ip` / `user-agent`\n * - 从 `Authorization: Bearer <token>` 提取 `accessToken`\n * - **不会**读取 refresh token:refresh token 若启用 `refreshCookie`,只走专属刷新路由\n *\n * `session` 字段始终为 `undefined`:若需会话填充,请使用 {@link buildAuthContextFactory}\n * 或直接通过 `serv.createApp({ iam })` 让框架自动选择合适工厂。\n *\n * @param input - 上下文输入\n * @returns 仅含元数据的 ServContext\n */\nexport function parseRequestContext(input: CreateServContextInput): ServContext {\n const headers = input.request.headers\n const requestId = headers.get(HEADER_REQUEST_ID) ?? core.id.uuid()\n const forwardedFor = headers.get(HEADER_FORWARDED_FOR)\n const ip = headers.get(HEADER_REAL_IP) ?? forwardedFor?.split(',')[0]?.trim()\n const locale = resolveRequestLocale(headers)\n const accessToken = extractBearerToken(headers.get(HEADER_AUTHORIZATION))\n\n return {\n requestId,\n locale,\n ip,\n userAgent: headers.get(HEADER_USER_AGENT) ?? undefined,\n accessToken,\n request: input.request,\n logger: core.logger.child({ module: 'serv', requestId }),\n }\n}\n\n/**\n * 构造**带认证的上下文工厂**:在 {@link parseRequestContext} 基础上异步填充 `context.session`。\n *\n * 工作流程(每个请求执行一次):\n * 1. 调用 `parseRequestContext` 提取元数据 + accessToken(仅来自 Authorization Bearer)\n * 2. 若 `accessToken` 存在 → 调用 `verifyToken(accessToken)`\n * 3. `verifyToken` 成功 → `session = result.data`;失败或 token 缺失 → `session = undefined`\n * 4. 下游 `.auth()` / `.permission()` / `.role()` guard 据此判断 401/403\n *\n * refresh token **不参与**这里的认证流程:它只在 access token 过期后,被 `/auth/refresh`\n * 从浏览器 httpOnly cookie 或受信原生 JSON body 中读出,用于换发新的 access token。\n *\n * **安全保证**:\n * - 每个请求都会重新调用 `verifyToken`(不缓存),用户被撤权后立即生效\n * - `verifyToken` 失败时**不**抛错,统一收敛为 `session=undefined` → 401\n * - 异常通过 try/catch 兜底,避免向 HTTP 层泄漏堆栈\n *\n * @param verifyToken - 访问令牌校验回调(通常是 `token => iam.session.verifyToken(token)` 或自定义函数)\n * @returns 异步上下文工厂\n */\nexport function buildAuthContextFactory(\n verifyToken: (accessToken: string) => Promise<HaiResult<ServSession>>,\n): CreateServContext {\n return async (input) => {\n // Step 1:先同步解析请求头,拿到 requestId / locale / accessToken 等基础元数据。\n const base = parseRequestContext(input)\n // Step 2:没有 access token 时,直接返回基础上下文;后续由 `.auth()` guard 统一判 401。\n if (!base.accessToken)\n return base\n try {\n // Step 3:有 token 时才触发异步校验;成功则把 session 注入上下文。\n const result = await verifyToken(base.accessToken)\n if (!result.success)\n return base\n // Step 4:返回带 session 的上下文,供 route guard 使用。\n return { ...base, session: result.data }\n }\n catch {\n // Step 5:verifyToken 抛错(不规范)也视为未认证;fail closed,不向上传播。\n return base\n }\n }\n}\n","/**\n * @h-ai/serv — Refresh Token 浏览器 / 原生传输\n *\n * **职责边界**:本文件 **仅** 负责 `refresh token` 的传输,不涉及 access token 校验。\n * - access token 校验由 `serv-context.ts` 的 `buildAuthContextFactory` 统一处理\n * - 浏览器使用 httpOnly cookie,避免 refresh token 进入 JS 可读存储\n * - 经过应用可信来源校验的原生客户端可改用 JSON body,交由系统安全存储持久化\n *\n * **挂载的路由**:\n * - login / register / loginWithOtp:浏览器写 Cookie 并擦除响应 token;受信原生请求保留响应 token\n * - logout:浏览器清除 Cookie(Max-Age=0);原生客户端自行清理安全存储\n * - refresh:**专属路由**(不走 oRPC),按请求来源从 Cookie 或 JSON body 读取并轮换 token\n *\n * **Cookie 规格**:`HttpOnly; SameSite=Strict; Secure`(生产)/ 无 Secure(开发)\n * Cookie Path 限制为 `{apiPrefix}/auth/refresh`,最小化浏览器自动发送范围(避免 CSRF 攻击面)。\n * @module serv-cookie-auth\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { Hono } from 'hono'\nimport type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport type { ServIam } from './serv-context.js'\nimport process from 'node:process'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, HaiCommonError } from '@h-ai/core'\nimport { getCookie } from 'hono/cookie'\nimport { buildHaiErrorBody } from './pipelines/serv-pipeline-helper.js'\nimport { servM } from './serv-i18n.js'\nimport { resolveRequestLocale } from './serv-validation.js'\n\n// ─── 公共类型 ─────────────────────────────────────────────────────────────────\n\n/**\n * Refresh 流程返回的 Token 对结构。\n * 与 `@h-ai/iam` 的 `TokenPair` 结构兼容;此处独立定义以避免对可选 peer dep 的强依赖。\n */\nexport interface RefreshTokenPair {\n readonly accessToken: string\n readonly refreshToken: string\n readonly expiresIn?: number\n readonly tokenType?: string\n}\n\ntype SafeRefreshTokenPair = Omit<RefreshTokenPair, 'refreshToken'>\n\n/**\n * 原生客户端的 refresh token body 传输配置。\n *\n * 浏览器继续使用 httpOnly cookie;只有经过应用可信来源校验的原生请求,才允许在\n * JSON body 中接收和返回 refresh token。`isRequest` 必须同时校验客户端标识与 Origin,\n * 不能仅信任可伪造的单个请求头。\n */\nexport interface NativeRefreshTokenTransportConfig {\n /** 判断当前请求是否来自受信原生客户端。 */\n readonly isRequest: (request: Request) => boolean\n}\n\n/**\n * Refresh Token 传输配置。\n *\n * 传入 `serv.createApp({ refreshCookie: ... })` 后自动管理浏览器 cookie,并可选启用受信原生 body 通道。\n * access token 仍然走 `Authorization: Bearer <token>`;本配置只改变 refresh token 的传输通道。\n *\n * 刷新回调来源(优先级从高到低):\n * 1. `onRefresh`(显式覆盖)\n * 2. 顶层 `iam.session.refresh`(推荐:`createApp({ iam, refreshCookie: {} })` 即开箱可用)\n */\nexport interface RefreshCookieConfig {\n /** Cookie 名,默认 `hai_refresh_token`。 */\n readonly cookieName?: string\n /**\n * Cookie Max-Age(秒),默认 `30 * 24 * 3600`(30 天)。\n * 应与 IAM 模块的 refresh token TTL 对齐。\n */\n readonly maxAge?: number\n /**\n * 强制 Secure 属性。\n * - 未传时:`NODE_ENV === 'production'` 时自动开启。\n * - 本地 HTTP 开发设为 `false` 可让 Cookie 在 HTTP 下正常工作。\n */\n readonly secure?: boolean\n /**\n * 为受信原生客户端启用 JSON body 传输。\n *\n * 启用后,匹配请求的登录、注册与刷新响应保留 `refreshToken`,刷新请求从\n * `{ refreshToken }` body 读取凭证,且不读写浏览器 Cookie。\n */\n readonly nativeTokenTransport?: NativeRefreshTokenTransportConfig\n /**\n * 自定义刷新回调(高级用法)。\n * 未提供时使用顶层 `iam.session.refresh`;两者均缺失时 `createApp` 会抛错。\n */\n readonly onRefresh?: (refreshToken: string) => Promise<HaiResult<RefreshTokenPair>>\n}\n\n// ─── 内部解析 ─────────────────────────────────────────────────────────────────\n\ninterface ResolvedRefreshCookieConfig {\n cookieName: string\n maxAge: number\n isSecure: boolean\n refreshCookiePath: string\n onRefresh: (refreshToken: string) => Promise<HaiResult<RefreshTokenPair>>\n nativeTokenTransport: NativeRefreshTokenTransportConfig | undefined\n}\n\nconst IAM_AUTH_PATHS = {\n login: apiContract.pathOf(apiContract.iam.auth.login),\n loginWithOtp: apiContract.pathOf(apiContract.iam.auth.loginWithOtp),\n register: apiContract.pathOf(apiContract.iam.auth.register),\n logout: apiContract.pathOf(apiContract.iam.auth.logout),\n refresh: apiContract.pathOf(apiContract.iam.auth.refresh),\n}\n\nfunction resolveRefreshCookieConfig(\n apiPrefix: string,\n config: RefreshCookieConfig,\n iam: ServIam | undefined,\n): ResolvedRefreshCookieConfig {\n // ⚠️ 不 `.bind(iam.session)`:IAM NotInitializedKit Proxy 在 init 后才切换为真实实现,\n // 提前 bind 会捕获未初始化 Proxy。用闭包延迟到调用点再解析。\n const onRefresh = config.onRefresh\n ?? (iam ? (refreshToken: string) => iam.session.refresh(refreshToken) : undefined)\n if (!onRefresh) {\n // 抛 HaiError 实例而非裸 Error:保留 code/system/module,便于上层错误处理识别这是\n // 配置缺失而非业务异常。该错误只在 createApp 装配阶段出现,不会泄漏到 HTTP 响应。\n throw core.error.buildHaiErrorInst(\n HaiCommonError.INTERNAL_ERROR,\n '[serv] refreshCookie requires either top-level `iam` (recommended) or `refreshCookie.onRefresh` callback',\n )\n }\n return {\n cookieName: config.cookieName ?? 'hai_refresh_token',\n maxAge: config.maxAge ?? 30 * 24 * 3600,\n isSecure: config.secure ?? (process.env.NODE_ENV === 'production'),\n refreshCookiePath: `${apiPrefix}${IAM_AUTH_PATHS.refresh}`,\n onRefresh,\n nativeTokenTransport: config.nativeTokenTransport,\n }\n}\n\n// ─── Cookie 字符串构建 ────────────────────────────────────────────────────────\n\nfunction buildSetCookieHeader(\n name: string,\n value: string,\n path: string,\n maxAge: number,\n secure: boolean,\n): string {\n const parts = [\n `${name}=${encodeURIComponent(value)}`,\n `Path=${path}`,\n `Max-Age=${maxAge}`,\n 'HttpOnly',\n 'SameSite=Strict',\n ]\n if (secure)\n parts.push('Secure')\n return parts.join('; ')\n}\n\nfunction buildClearCookieHeader(name: string, path: string): string {\n return `${name}=; Path=${path}; Max-Age=0; HttpOnly; SameSite=Strict`\n}\n\n// ─── 响应体解析 ───────────────────────────────────────────────────────────────\n\n/**\n * 从 oRPC 成功响应的 `data` 字段中提取 refresh token。\n * 兼容两种结构:\n * - login/register: `{ tokens: { refreshToken, ... }, user, ... }`\n * - (保留)直接含 `{ tokens: { refreshToken } }` 的 data\n */\nfunction extractRefreshTokenFromData(data: unknown): string | undefined {\n if (!data || typeof data !== 'object')\n return undefined\n const d = data as Record<string, unknown>\n const tokens = d.tokens\n if (!tokens || typeof tokens !== 'object')\n return undefined\n const t = tokens as Record<string, unknown>\n return typeof t.refreshToken === 'string' && t.refreshToken ? t.refreshToken : undefined\n}\n\n/**\n * 擦除响应体中的 refreshToken,确保 httpOnly 模式下 JS 无法读取长期凭证。\n *\n * 这与 `Set-Cookie` 写入 refresh token **不是重复操作**,而是刻意把两条信道分开:\n * - `Set-Cookie`:交给浏览器保存 refresh token(httpOnly,JS 不可读)\n * - JSON 响应体:交给前端 JS 消费,因此必须删除 `refreshToken`\n */\nfunction stripRefreshTokenFromData(data: unknown): unknown {\n if (!data || typeof data !== 'object' || Array.isArray(data))\n return data\n const d = data as Record<string, unknown>\n const tokens = d.tokens\n if (!tokens || typeof tokens !== 'object' || Array.isArray(tokens))\n return data\n\n const safeTokens: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(tokens)) {\n if (key !== 'refreshToken')\n safeTokens[key] = value\n }\n return { ...d, tokens: safeTokens }\n}\n\nfunction stripRefreshTokenFromPair(tokens: RefreshTokenPair): SafeRefreshTokenPair {\n return {\n accessToken: tokens.accessToken,\n ...(tokens.expiresIn === undefined ? {} : { expiresIn: tokens.expiresIn }),\n ...(tokens.tokenType === undefined ? {} : { tokenType: tokens.tokenType }),\n }\n}\n\n// ─── 路由挂载 ─────────────────────────────────────────────────────────────────\n\n/* 由 createApp 在 mountOpenAPIRoutes 之前调用,在 Hono app 上挂载\n * cookie 认证相关路由。Hono 按注册顺序匹配路由,因此必须在 oRPC 通配符路由之前注册。 */\nexport function mountRefreshCookieRoutes(\n app: Hono,\n apiPrefix: string,\n config: RefreshCookieConfig,\n iam: ServIam | undefined,\n): void {\n const {\n cookieName,\n maxAge,\n isSecure,\n refreshCookiePath,\n onRefresh,\n nativeTokenTransport,\n } = resolveRefreshCookieConfig(apiPrefix, config, iam)\n\n // ─── login / register / loginWithOtp:oRPC 处理后,按客户端类型选择 token 传输通道 ──\n // 路径直接从 apiContract.iam 的 route 元数据读取,保持 contract 为唯一来源。\n const loginPaths = [\n `${apiPrefix}${IAM_AUTH_PATHS.login}`,\n `${apiPrefix}${IAM_AUTH_PATHS.loginWithOtp}`,\n `${apiPrefix}${IAM_AUTH_PATHS.register}`,\n ]\n for (const path of loginPaths) {\n app.post(path, async (c, next) => {\n const useNativeTokenTransport = nativeTokenTransport?.isRequest(c.req.raw) === true\n await next()\n if (c.res.status !== 200)\n return\n // 消费响应体后必须重建 Response(ReadableStream 只能消费一次)\n const bodyText = await c.res.text()\n const headers = new Headers(c.res.headers)\n let responseBody = bodyText\n try {\n const body = JSON.parse(bodyText) as { success?: boolean, data?: unknown }\n if (body.success) {\n const refreshToken = extractRefreshTokenFromData(body.data)\n if (!useNativeTokenTransport) {\n // 刻意保留“写 cookie + 擦响应体”两步:\n // - cookie 让浏览器持久保存 refresh token,用于后续 /auth/refresh\n // - body 中删掉 refreshToken,避免任何前端 JS 直接读到长期凭证\n if (refreshToken)\n headers.append('Set-Cookie', buildSetCookieHeader(cookieName, refreshToken, refreshCookiePath, maxAge, isSecure))\n body.data = stripRefreshTokenFromData(body.data)\n responseBody = JSON.stringify(body)\n headers.delete('Content-Length')\n }\n }\n }\n catch { /* 非 JSON 响应,跳过 cookie 写入 */ }\n c.res = new Response(responseBody, { status: c.res.status, statusText: c.res.statusText, headers })\n })\n }\n\n // ─── logout:oRPC 处理后,拦截成功响应清除 cookie ────────────────────────────\n app.post(`${apiPrefix}${IAM_AUTH_PATHS.logout}`, async (c, next) => {\n const useNativeTokenTransport = nativeTokenTransport?.isRequest(c.req.raw) === true\n await next()\n if (c.res.status !== 200)\n return\n const bodyText = await c.res.text()\n const headers = new Headers(c.res.headers)\n try {\n const body = JSON.parse(bodyText) as { success?: boolean }\n if (body.success && !useNativeTokenTransport)\n headers.append('Set-Cookie', buildClearCookieHeader(cookieName, refreshCookiePath))\n }\n catch { /* 非 JSON,跳过 */ }\n c.res = new Response(bodyText, { status: c.res.status, statusText: c.res.statusText, headers })\n })\n\n // ─── refresh:专属路由,绕过 oRPC,直接从 cookie 读取 token 并刷新 ─────────\n // 此路由须在 oRPC 通配符之前注册,Hono 才能正确匹配。\n app.post(`${apiPrefix}${IAM_AUTH_PATHS.refresh}`, async (c) => {\n // access token 认证不走这里;这里只有“用 refresh token 换新 access token”这一件事。\n const useNativeTokenTransport = nativeTokenTransport?.isRequest(c.req.raw) === true\n const refreshToken = useNativeTokenTransport\n ? await readRefreshTokenFromBody(c.req.raw)\n : getCookie(c, cookieName)\n if (!refreshToken) {\n const locale = resolveRequestLocale(c.req.raw.headers)\n return c.json(buildHaiErrorBody(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale })), 401)\n }\n\n const result = await onRefresh(refreshToken)\n if (!result.success) {\n const status = (result.error.httpStatus ?? 401) as ContentfulStatusCode\n // 浏览器刷新失败时清除失效 cookie,避免客户端循环重试;原生客户端自行清理安全存储。\n const headers = new Headers({ 'Content-Type': 'application/json' })\n if (!useNativeTokenTransport)\n headers.set('Set-Cookie', buildClearCookieHeader(cookieName, refreshCookiePath))\n return new Response(JSON.stringify({ success: false, error: result.error }), {\n status,\n headers,\n })\n }\n\n const tokens = result.data\n if (useNativeTokenTransport) {\n return new Response(JSON.stringify({ success: true, data: { tokens } }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n const setCookie = buildSetCookieHeader(cookieName, tokens.refreshToken, refreshCookiePath, maxAge, isSecure)\n return new Response(JSON.stringify({ success: true, data: { tokens: stripRefreshTokenFromPair(tokens) } }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Set-Cookie': setCookie,\n },\n })\n })\n}\n\n/**\n * 从受信原生客户端的 JSON body 读取 refresh token。\n *\n * 非 JSON、空 body 与空字符串统一视为未提供凭证,由调用方返回 401。\n */\nasync function readRefreshTokenFromBody(request: Request): Promise<string | undefined> {\n const body = await request.json().catch(() => undefined) as { refreshToken?: unknown } | undefined\n return typeof body?.refreshToken === 'string' && body.refreshToken.length > 0\n ? body.refreshToken\n : undefined\n}\n","/**\n * @h-ai/serv — OpenAPI 文档\n *\n * 由 oRPC contract 生成 OpenAPI 3.1 spec,并集中处理 Scalar 文档页 HTML\n * 与本地脚本资源读取逻辑。\n * @module serv-openapi\n */\n\nimport type { AnyContractRouter, OpenAPI } from '@orpc/contract'\nimport { readFile } from 'node:fs/promises'\nimport { OpenAPIGenerator } from '@orpc/openapi'\nimport { ZodToJsonSchemaConverter } from '@orpc/zod/zod4'\n\n/** Scalar UI 脚本本地路由,由 createApp 自动挂载,无需外网 CDN。 */\nexport const SCALAR_ROUTE = '/_hai/scalar.js'\n\n/** Scalar browser bundle 兜底路径;优先使用 package.json 的 browser 字段。 */\nconst SCALAR_BROWSER_ENTRY_FALLBACK = './dist/browser/standalone.js'\n\n/** 缓存 Scalar UI 脚本内容(启动后不变,缓存避免重复读取)。 */\nlet scalarScriptCache: string | undefined\n\n/** OpenAPI spec 生成配置。 */\nexport interface GenerateOpenAPISpecOptions {\n readonly title?: string\n readonly version?: string\n readonly apiPrefix?: string\n readonly description?: string\n}\n\n/**\n * 由应用级 contract 生成 OpenAPI 3.1 spec。\n *\n * @param contract - 已组合完成的应用级 oRPC contract\n * @param options - 文档元信息\n * @returns OpenAPI 文档对象\n *\n * @example\n * ```ts\n * const spec = await serv.generateSpec(contract, {\n * title: 'My API',\n * version: '1.0.0',\n * apiPrefix: '/api/v1',\n * })\n * ```\n */\nexport async function generateSpec(\n contract: AnyContractRouter,\n options: GenerateOpenAPISpecOptions = {},\n): Promise<OpenAPI.Document> {\n const generator = new OpenAPIGenerator({\n schemaConverters: [new ZodToJsonSchemaConverter()],\n })\n\n return generator.generate(contract, {\n info: {\n title: options.title ?? 'hai-framework API',\n version: options.version ?? '0.1.0',\n description: options.description,\n },\n servers: options.apiPrefix ? [{ url: options.apiPrefix }] : undefined,\n security: [{ bearerAuth: [] }],\n components: {\n securitySchemes: {\n bearerAuth: { type: 'http', scheme: 'bearer' },\n },\n },\n })\n}\n\n/** 文档页面创建配置。 */\nexport interface CreateDocsPageOptions {\n readonly title?: string\n readonly specUrl?: string\n /**\n * Scalar UI 脚本 URL。\n *\n * 默认为 `/_hai/scalar.js`(由 `createApp` 自动挂载的本地路由),无需访问外网 CDN。\n * 如需指向 CDN,可设为 `https://cdn.jsdelivr.net/npm/@scalar/api-reference`。\n */\n readonly scriptUrl?: string\n}\n\n/**\n * 读取本地 Scalar browser bundle。\n *\n * 供 `createApp()` 在 docs 路由中按需挂载 `/_hai/scalar.js` 使用。\n */\nexport async function getScalarScript(): Promise<string | undefined> {\n if (scalarScriptCache !== undefined)\n return scalarScriptCache || undefined\n try {\n const fileUrl = await resolveScalarBrowserScriptUrl()\n scalarScriptCache = await readFile(fileUrl, 'utf8')\n }\n catch {\n scalarScriptCache = ''\n }\n return scalarScriptCache || undefined\n}\n\n/**\n * 创建轻量 API 文档 HTML。\n *\n * 默认使用本地路由 `/_hai/scalar.js` 加载 Scalar UI 脚本,无 CDN 依赖。\n *\n * @param spec - OpenAPI spec,可用于离线内嵌\n * @param options - 文档页选项\n * @returns HTML 字符串\n */\nexport function createDocsPage(spec: OpenAPI.Document, options: CreateDocsPageOptions = {}): string {\n const title = escapeHtml(options.title ?? spec.info.title)\n const specUrl = options.specUrl\n const scriptUrl = escapeHtml(options.scriptUrl ?? SCALAR_ROUTE)\n\n if (specUrl) {\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${title}</title></head><body><script id=\"api-reference\" data-url=\"${escapeHtml(specUrl)}\"></script><script src=\"${scriptUrl}\"></script></body></html>`\n }\n\n const encodedSpec = escapeScript(JSON.stringify(spec))\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${title}</title></head><body><script id=\"api-reference\" type=\"application/json\">${encodedSpec}</script><script src=\"${scriptUrl}\"></script></body></html>`\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(String.fromCharCode(39), ''')\n}\n\nfunction escapeScript(value: string): string {\n return value.replaceAll('</script', '<\\\\/script')\n}\n\nasync function resolveScalarBrowserScriptUrl(): Promise<URL> {\n const packageEntryUrl = import.meta.resolve('@scalar/api-reference')\n const packageJsonUrl = new URL('../package.json', packageEntryUrl)\n const packageInfo: unknown = JSON.parse(await readFile(packageJsonUrl, 'utf8'))\n const browserEntry = readScalarBrowserEntry(packageInfo) ?? SCALAR_BROWSER_ENTRY_FALLBACK\n return new URL(browserEntry, packageJsonUrl)\n}\n\nfunction readScalarBrowserEntry(packageInfo: unknown): string | undefined {\n if (typeof packageInfo !== 'object' || packageInfo === null || Array.isArray(packageInfo))\n return undefined\n const browser = (packageInfo as Record<string, unknown>).browser\n return typeof browser === 'string' ? browser : undefined\n}\n","/**\n * @h-ai/serv — 传输加密集成\n *\n * 提供 Hono 中间件 + 密钥协商端点,直接依赖 `@h-ai/crypto` 的\n * `TransportEncryptionManager` 与 `TRANSPORT_PROTOCOL` 协议常量,\n * 确保前后端协议不漂移。\n *\n * 单次请求的处理顺序:\n * 1. 特判密钥协商端点\n * 2. 跳过不参与加解密的路径\n * 3. 校验 clientId 与已注册公钥\n * 4. 解密请求体并把明文 request 回写给下游\n * 5. 执行业务逻辑\n * 6. 尝试加密 JSON 响应\n *\n * @module serv-transport\n */\n\nimport type { CryptoFunctions, EncryptedPayload, TransportEncryptionManager, TransportKeyStore } from '@h-ai/crypto'\nimport type { MiddlewareHandler } from 'hono'\nimport { core } from '@h-ai/core'\nimport { TRANSPORT_PROTOCOL } from '@h-ai/crypto'\nimport { servM } from './serv-i18n.js'\n\nconst logger = core.logger.child({ module: 'serv', scope: 'transport' })\n\n/** 单次响应加密的体积上限(1 MiB)。超过则 fail-closed,避免内存放大和明文泄露。 */\nconst MAX_ENCRYPTED_BODY = 1_048_576\nconst ENCRYPTABLE_RESPONSE_CONTENT_TYPES = ['application/json']\n\n/** {@link createApp} 的 `transport` 配置项。 */\nexport interface ServTransportConfig {\n /**\n * crypto 服务实例。serv 通过 `crypto.transport.createServer()` 创建\n * 传输加密管理器,并使用 `crypto.transport.protocol` 中的协议常量。\n */\n readonly crypto: CryptoFunctions\n /**\n * 密钥协商端点路径(相对于 `apiPrefix`)。默认 `/_hai/key-exchange`。\n */\n readonly keyExchangePath?: string\n /**\n * 不参与加解密的路径白名单(绝对路径完整匹配或前缀匹配)。\n * 健康检查、文档页等公共路由通常无需加密。\n */\n readonly excludePaths?: readonly string[]\n /** 共享客户端公钥存储;适用于多节点部署。 */\n readonly keyStore?: TransportKeyStore\n /** 服务端可缓存的客户端公钥数量上限。默认 10000。 */\n readonly maxClients?: number\n}\n\n/**\n * 创建传输加密 Hono 中间件。\n *\n * 行为:\n * - `${keyExchangePath}` POST → 密钥协商\n * - 其他请求必须携带 `X-Client-Id` 且加密\n * - JSON 响应自动加密\n *\n * @param manager - 由 `crypto.transport.createServer()` 创建的管理器\n * @param keyExchangePath - 密钥协商绝对路径(已与 apiPrefix 拼接)\n * @param excludePaths - 不参与加解密的路径\n */\nexport function createTransportMiddleware(\n manager: TransportEncryptionManager,\n keyExchangePath: string,\n excludePaths: readonly string[] = [],\n): MiddlewareHandler {\n return async (c, next) => {\n const pathname = new URL(c.req.url).pathname\n\n // Step 1:密钥协商端点直接短路处理,不进入下游业务逻辑。\n if (pathname === keyExchangePath && c.req.method === 'POST')\n return handleKeyExchange(c.req.raw, manager)\n\n // Step 2:健康检查 / 文档页等可配置排除路径直接跳过加解密。\n if (shouldExclude(pathname, excludePaths, keyExchangePath))\n return next()\n\n // Step 3:普通业务请求必须先确认 clientId 存在,且服务端已经保存过该客户端公钥。\n const clientId = c.req.header(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER)\n if (!clientId)\n return jsonError(servM('serv_transportClientIdRequired'), 400)\n\n const clientPublicKey = await manager.getClientPublicKey(clientId)\n if (!clientPublicKey)\n return jsonError(servM('serv_transportClientKeyNotFound'), 400)\n\n // Step 4:对带 body 的请求先解密;解密后下游看到的是普通 JSON Request。\n // 空 body 请求(如无 input 的 POST 过程)无需解密;下游 Zod 校验会拒绝缺字段输入。\n if (hasBody(c.req.method) && !isEmptyBody(c.req.raw)) {\n const decErr = await decryptRequestInPlace(c, manager)\n if (decErr)\n return decErr\n }\n\n // Step 5:明文请求进入后续 Hono / oRPC 流程。\n await next()\n const response = c.res\n if (!response)\n return\n // Step 6:受保护路由响应必须加密;无法加密时 fail-closed,禁止明文透传。\n const contentType = response.headers.get('Content-Type') ?? ''\n if (isEmptyResponse(response))\n return\n if (!canEncryptResponseContentType(contentType)) {\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n const contentLength = response.headers.get('Content-Length')\n if (contentLength && Number.parseInt(contentLength, 10) > MAX_ENCRYPTED_BODY) {\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n\n let bodyText: string\n try {\n bodyText = await response.clone().text()\n }\n catch (cause) {\n logger.warn('Failed to read response body for encryption', { error: cause })\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n if (!bodyText)\n return\n // 二次防御:分块传输(chunked)等场景没有 Content-Length 头,需在读取后再校验体积。\n // 超过上限则 fail-closed,避免大 body 加密导致内存放大且禁止明文泄露。\n if (bodyText.length > MAX_ENCRYPTED_BODY) {\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n\n // Step 7:把下游返回的明文 JSON 重新加密后替换回响应对象。\n const encResult = await manager.encryptResponse(clientId, bodyText)\n if (!encResult.success) {\n logger.warn('Failed to encrypt response', { error: encResult.error })\n c.res = jsonError(servM('serv_transportEncryptFailed'), 500)\n return\n }\n\n const headers = new Headers(response.headers)\n headers.set('Content-Type', 'application/json; charset=utf-8')\n headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE)\n headers.delete('Content-Length')\n c.res = new Response(JSON.stringify(encResult.data), {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n\n// ─── 内部 ───\n\nasync function handleKeyExchange(request: Request, manager: TransportEncryptionManager): Promise<Response> {\n let body: { clientPublicKey?: unknown }\n try {\n // Step 1:读取客户端提交的公钥。\n body = await request.json() as { clientPublicKey?: unknown }\n }\n catch {\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n }\n if (typeof body.clientPublicKey !== 'string' || body.clientPublicKey.length === 0)\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n\n try {\n // Step 2:注册客户端公钥,换取稳定的 clientId;并返回服务端公钥给客户端完成协商。\n const clientId = await manager.registerClientKey(body.clientPublicKey)\n const serverPublicKey = manager.getServerPublicKey()\n return new Response(JSON.stringify({ serverPublicKey, clientId }), {\n status: 200,\n headers: { 'Content-Type': 'application/json; charset=utf-8' },\n })\n }\n catch (cause) {\n logger.error('Key exchange failed', { error: cause })\n return jsonError(servM('serv_transportKeyExchangeFailed'), 500)\n }\n}\n\nasync function decryptRequestInPlace(\n c: Parameters<MiddlewareHandler>[0],\n manager: TransportEncryptionManager,\n): Promise<Response | undefined> {\n // Step A:请求必须显式声明自己是加密 payload,避免把普通 JSON 误当密文解析。\n const isEncrypted = c.req.header(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) === TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE\n if (!isEncrypted)\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n\n let payload: unknown\n try {\n // Step B:先以 JSON 读取密文载荷。\n payload = await c.req.raw.clone().json()\n }\n catch {\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n }\n if (!isEncryptedPayloadShape(payload))\n return jsonError(servM('serv_transportInvalidPayload'), 400)\n\n // Step C:调用 transport manager 做真正的解密。\n const decResult = manager.decryptRequest(payload)\n if (!decResult.success) {\n logger.warn('Failed to decrypt request', { error: decResult.error })\n return jsonError(servM('serv_transportDecryptFailed'), 400)\n }\n\n // Step D:重建一个“明文 Request”回写到 `c.req.raw`,下游业务完全不需要感知加密协议。\n const original = c.req.raw\n const newHeaders = new Headers(original.headers)\n newHeaders.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER)\n newHeaders.delete('Content-Length')\n newHeaders.set('Content-Type', 'application/json; charset=utf-8')\n const newRequest = new Request(original.url, {\n method: original.method,\n headers: newHeaders,\n body: decResult.data,\n // @ts-expect-error Node fetch 需要 duplex 才能携带流式 body。\n duplex: 'half',\n })\n ;(c.req as unknown as { raw: Request }).raw = newRequest\n return undefined\n}\n\nfunction shouldExclude(pathname: string, excludePaths: readonly string[], keyExchangePath: string): boolean {\n if (pathname === keyExchangePath)\n return true\n return excludePaths.some(p => pathname === p || pathname.startsWith(`${p}/`))\n}\n\nfunction canEncryptResponseContentType(contentType: string): boolean {\n return ENCRYPTABLE_RESPONSE_CONTENT_TYPES.some(type => contentType.includes(type))\n}\n\nfunction isEmptyResponse(response: Response): boolean {\n if ([204, 304].includes(response.status))\n return true\n return response.headers.get('Content-Length') === '0'\n}\n\nfunction hasBody(method: string): boolean {\n return ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method.toUpperCase())\n}\n\n/** 判断请求是否携带 body:Content-Length=0 或缺失 Content-Type 视为空 body。 */\nfunction isEmptyBody(req: Request): boolean {\n if (req.body === null)\n return true\n const cl = req.headers.get('Content-Length')\n if (cl !== null)\n return Number.parseInt(cl, 10) === 0\n // 无 Content-Length 且无 Content-Type 的 body-bearing 请求按空 body 处理。\n return !req.headers.get('Content-Type')\n}\n\nfunction jsonError(message: string, status: number): Response {\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: { 'Content-Type': 'application/json; charset=utf-8' },\n })\n}\n\nfunction isEncryptedPayloadShape(value: unknown): value is EncryptedPayload {\n if (!value || typeof value !== 'object')\n return false\n const p = value as Record<string, unknown>\n return typeof p.encryptedKey === 'string' && typeof p.ciphertext === 'string' && typeof p.iv === 'string'\n}\n","/**\n * @h-ai/serv — HTTP app 装配\n *\n * 将 oRPC contract + procedures + http 配置组合为可运行的 ServHttpApp,\n * 自动挂载安全响应头、健康检查、OpenAPI JSON、Scalar 文档页与 oRPC handler。\n *\n * 关键装配顺序:\n * 1. 解析 HTTP 配置\n * 2. 选择请求上下文工厂\n * 3. 挂基础安全 middleware\n * 4. 挂自定义 middleware(若提供)\n * 5. (可选)挂传输加密 middleware\n * 6. 挂基础路由(health / refresh-cookie)\n * 7. 挂业务路由(oRPC / RPC)\n * 8. 挂文档路由(openapi / docs)\n * @module serv-app\n */\n\nimport type { AudioFormat, AudioWsStartMessage } from '@h-ai/ai'\nimport type { HaiResult } from '@h-ai/core'\nimport type { ServerType } from '@hono/node-server'\nimport type { AnyContractRouter, OpenAPI } from '@orpc/contract'\nimport type { Router } from '@orpc/server'\nimport type { Hono, Context as HonoContext, Next } from 'hono'\nimport type { AudioWsDeps } from './features/serv-feature-audio.js'\nimport type { ServMiddleware } from './pipelines/serv-pipeline-types.js'\nimport type { ServHealthHttpConfig, ServHttpConfigInput } from './serv-config.js'\nimport type { CreateServContext, ServContext, ServIam, ServSession } from './serv-context.js'\nimport type { RefreshCookieConfig } from './serv-cookie-auth.js'\nimport type { ServTransportConfig } from './serv-transport.js'\nimport type { ServValidationFailureBody } from './serv-validation.js'\nimport { AUDIO_WS_PATH } from '@h-ai/api-contract'\nimport { core, HaiCommonError } from '@h-ai/core'\nimport { createNodeWebSocket } from '@hono/node-ws'\nimport { OpenAPIHandler } from '@orpc/openapi/fetch'\nimport { RPCHandler } from '@orpc/server/fetch'\nimport { Hono as HonoApp } from 'hono'\nimport { registerAudioWsRoute } from './features/serv-feature-audio.js'\nimport { buildHaiErrorBody } from './pipelines/serv-pipeline-helper.js'\nimport { requireInternalRPC } from './pipelines/serv-pipeline-require-internal-rpc.js'\nimport { securityHeaders } from './pipelines/serv-pipeline-security-headers.js'\nimport { resolveServHttpConfig } from './serv-config.js'\nimport { buildAuthContextFactory, parseRequestContext } from './serv-context.js'\nimport { mountRefreshCookieRoutes } from './serv-cookie-auth.js'\nimport { servM } from './serv-i18n.js'\nimport { createDocsPage, generateSpec, getScalarScript, SCALAR_ROUTE } from './serv-openapi.js'\nimport { createTransportMiddleware } from './serv-transport.js'\nimport { localizeZodError, resolveRequestLocale } from './serv-validation.js'\n\n/** 允许通过 oRPC OpenAPIHandler 转发的 HTTP 方法。 */\nconst API_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const\n\n/**\n * `serv.createApp()` 的自定义 middleware 挂载项。\n *\n * - `path` 省略时默认挂到 `'*'`\n * - 按数组顺序注册\n * - 注册位置固定在内置 `securityHeaders` 之后、`transport` / 业务路由之前\n */\nexport interface ServMiddlewareMount {\n readonly path?: string\n readonly middleware: ServMiddleware\n}\n\n/**\n * `@h-ai/serv` 对外暴露的 HTTP app 抽象。\n *\n * Hono 是当前内部实现细节,应用代码只应依赖此最小接口:\n * - `fetch`:部署到 Fetch-first 运行时或交给 `serv.listen()`。\n * - `request`:测试 / 本地直连时发起 in-process 请求,避免消费方 import Hono。\n */\nexport interface ServHttpApp {\n readonly fetch: (request: Request) => Response | Promise<Response>\n readonly request: (input: RequestInfo | URL, init?: RequestInit) => Response | Promise<Response>\n}\n\n/**\n * 创建 ServHttpApp 的配置。\n *\n * **认证 / 会话填充设计**(遵循最小知识原则):\n *\n * - **`iam`**(推荐):单一顶层引用。提供后 serv 自动:\n * 1. 使用 `iam.session.verifyToken` 填充 `context.session` → route guard 生效\n * 2. 若同时启用 `refreshCookie`,使用 `iam.session.refresh` 实现 refresh token 轮换\n *\n * - **`refreshCookie`**(可选):浏览器默认用 httpOnly cookie;受信原生客户端可配置 body 通道。与 `iam` 正交:\n * `iam` 决定 *谁* 验证 token,`refreshCookie` 决定 refresh token *怎么* 传输。\n *\n * - **`verifyToken`**(高级):不使用 IAM 模块的逆脱口。优先于 `iam.session.verifyToken`。\n *\n * - **`createContext`**(高级):完全接管上下文构造,设置后 serv **不再**自动填充 session,\n * 由调用方负责。\n */\nexport interface CreateServAppOptions<\n TContract extends AnyContractRouter,\n TProcedures extends Router<AnyContractRouter, ServContext>,\n> {\n readonly contract: TContract\n readonly procedures: TProcedures\n readonly http?: ServHttpConfigInput\n /**\n * 自定义 HTTP middleware(当前内部由 Hono 承载,但应用 API 不暴露 Hono app)。\n *\n * 典型用途:请求日志、trace、指标、CORS、限流、租户头校验,以及不适合放入\n * JSON/oRPC contract 的 WebSocket、文件或其它二进制 HTTP 端点。\n * 这些 middleware 会在内置安全头之后、传输加密与业务路由之前注册;\n * 因此 CORS preflight 或二进制端点可以直接短路返回。短路响应仍包含内置安全头,\n * 但不会经过 transport;若需要读取解密后的业务 body,应改用 context / procedure 层扩展。\n */\n readonly middlewares?: readonly ServMiddlewareMount[]\n /**\n * IAM 模块引用(推荐)。提供后 serv 自动使用 `iam.session.verifyToken` 填充 session,\n * 以及在 `refreshCookie` 启用时使用 `iam.session.refresh` 实现 cookie 刷新。\n *\n * 类型上要求 {@link ServIam}(`iam` 可以直接传入,结构类型保证兼容)。\n */\n readonly iam?: ServIam\n /**\n * 启用 refresh token 传输(opt-in):浏览器使用 httpOnly Cookie,可选配置受信原生 body 通道。\n * 需配合 `iam` 或 `refreshCookie.onRefresh` 使用。\n */\n readonly refreshCookie?: RefreshCookieConfig\n /**\n * 高级:自定义访问令牌校验回调,覆盖 `iam.session.verifyToken`。\n * 不使用 IAM 模块的场景下使用。\n */\n readonly verifyToken?: (token: string) => Promise<HaiResult<ServSession>>\n /**\n * 高级:完全接管上下文工厂。设置后上述 `iam` / `verifyToken` 均不生效(session 不会被自动填充)。\n */\n readonly createContext?: CreateServContext\n /**\n * 启用传输加密(opt-in)。提供后 serv 自动:\n * 1. 调用 `crypto.transport.createServer()` 创建管理器\n * 2. 在 oRPC 路由之前挂载中间件,自动解密请求 / 加密响应\n * 3. 在 `${apiPrefix}${keyExchangePath}` 暴露 POST 密钥协商端点\n *\n * @example\n * ```ts\n * serv.createApp({ contract, procedures, transport: { crypto } })\n * ```\n */\n readonly transport?: ServTransportConfig\n /**\n * 启用统一语音 WebSocket 入口(opt-in)。提供后 serv 自动:\n * 1. 在 `${apiPrefix}/ai/audio`(可自定义 `path`)注册语音 WebSocket 路由\n * 2. 基于 `ai.audio` 提供非实时 / 实时的语音识别与合成\n * 3. 通过一次性 Audio ticket 验证身份,再由授权回调确认付费参数\n *\n * 需配合 `serv.listen()`(Node 适配器会自动完成 WebSocket 升级注入)。\n *\n * @example\n * ```ts\n * serv.createApp({ contract, procedures, iam, audio: { ai, verifyTicket, authorize } })\n * ```\n */\n readonly audio?: ServAudioConfig\n}\n\n/** `serv.createApp()` 的语音 WebSocket 接入配置。 */\nexport interface ServAudioConfig {\n /** AI 服务对象(提供 `ai.audio` 领域能力)。 */\n readonly ai: AudioWsDeps['ai']\n /**\n * 校验并原子消费短期、一次性的 Audio ticket,返回会话与可选的能力授权(grant)。\n *\n * ticket 的签发和存储由应用决定(推荐 `iam.ticket.issue` / `iam.ticket.consume`);\n * 成功结果必须返回建立授权所需的 IAM 会话摘要,可选返回签发时绑定的 `grant`\n * (操作 / 模型 / 会话),供服务端交叉校验 `start` 帧与票据一致。\n */\n readonly verifyTicket: (ticket: string) => Promise<HaiResult<AudioTicketVerification>>\n /**\n * 基于已验证会话授权本次操作,并返回服务端确认后的付费参数。\n *\n * 未提供时只保留操作及音频格式,客户端提交的 model / voice / instruction 不会生效。\n * 这里适合完成 IAM 权限、Persona、套餐配额和并发会话的检查/占用。\n * 第三参 `grant` 为票据绑定的能力(如有),可用于确认 `start` 未越权。\n */\n readonly authorize?: (session: ServSession, request: AudioWsStartMessage, grant?: AudioTicketGrant) => Promise<HaiResult<AuthorizedAudioRequest>>\n /** 会话结束钩子,供应用释放并发占用;无论成功、失败或断连都至多调用一次。 */\n readonly onSessionEnd?: (session: ServSession, request: AuthorizedAudioRequest) => void | Promise<void>\n /** 语音入口路径(相对 apiPrefix,默认 `/ai/audio`)。 */\n readonly path?: string\n /** 单条消息字节上限(默认 1 MiB)。 */\n readonly maxMessageBytes?: number\n /** 单连接累计接收音频字节上限(默认 10 MiB)。 */\n readonly maxBufferedBytes?: number\n /** 单连接累计接收文本字节上限(默认 1 MiB)。 */\n readonly maxTextBytes?: number\n /** 单连接最长持续时间(毫秒,默认 5 分钟)。 */\n readonly maxSessionMs?: number\n /** 预鉴权超时(毫秒,默认 5000):连接建立后未完成鉴权并收到 `start` 即关闭,独立于会话超时。 */\n readonly preAuthTimeoutMs?: number\n /** 未处理消息积压上限(默认 256):串行处理链积压超过此数立即关闭,防止内存放大。 */\n readonly maxPendingMessages?: number\n /** 发送缓冲高水位(字节,默认 8 MiB):`ws.bufferedAmount` 超过即以领域错误关闭慢客户端。 */\n readonly maxSendBufferBytes?: number\n}\n\n/** Audio ticket 绑定的能力授权(消费时原样返回,供服务端交叉校验 start 帧)。 */\nexport interface AudioTicketGrant {\n /** 允许的操作 */\n readonly operation?: 'transcribe' | 'synthesize'\n /** 允许的模型 ID */\n readonly model?: string\n /** 绑定的会话 ID */\n readonly sessionId?: string\n /** 应用自定义绑定字段 */\n readonly [key: string]: unknown\n}\n\n/** Audio ticket 验证结果:IAM 会话 + 可选能力授权。 */\nexport interface AudioTicketVerification {\n /** IAM 会话摘要 */\n readonly session: ServSession\n /** 票据绑定的能力授权(如有) */\n readonly grant?: AudioTicketGrant\n}\n\n/** Audio WebSocket 经应用授权后允许生效的服务端参数。 */\nexport interface AuthorizedAudioRequest {\n readonly operation: 'transcribe' | 'synthesize'\n /** 服务端允许的模型 ID;不传时使用 `ai.audio` 对应操作的默认模型。 */\n readonly model?: string\n readonly voice?: string\n readonly instruction?: string\n readonly format?: AudioFormat\n readonly sampleRate?: number\n}\n\n/**\n * 语音 WebSocket 升级注入器注册表\n *\n * `createApp` 启用 audio 时将 `injectWebSocket` 写入此 WeakMap,`serv.listen()` 在创建\n * Node 服务器后取出并注入,从而不向 `ServHttpApp` 公共类型泄露 Hono / WebSocket 细节。\n */\nexport const audioWsInjectors = new WeakMap<ServHttpApp, (server: ServerType) => void>()\n\n/**\n * 创建并装配 HTTP API app。\n *\n * @param options - contract/procedures/http 三段式配置\n * @returns 可被 `serv.listen()` / `serv.toFetch()` 使用的 ServHttpApp\n *\n * @example\n * ```ts\n * const app = serv.createApp({\n * contract,\n * procedures,\n * http: {\n * apiPrefix: '/api/v1',\n * openapi: { path: '/openapi.json' },\n * docs: { path: '/docs' },\n * },\n * })\n * serv.listen(app, { port: 3000, host: '0.0.0.0' })\n * ```\n */\nexport function createApp<\n TContract extends AnyContractRouter,\n TProcedures extends Router<AnyContractRouter, ServContext>,\n>(options: CreateServAppOptions<TContract, TProcedures>): ServHttpApp {\n // Step 1:先把用户输入的 HTTP 配置收敛成完整配置,后续挂载逻辑只处理一种形态。\n const http = resolveServHttpConfig(options.http)\n // Step 2:创建 Hono app 外壳,后续所有 middleware / route 都按顺序向这个实例注册。\n const app = new HonoApp()\n\n // Step 3:决定请求上下文工厂。这个选择直接决定后续 procedure 是否能拿到 `context.session`。\n // 上下文工厂选择优先级(顶位优先):\n // 1. options.createContext\n // 2. options.verifyToken\n // 3. options.iam.session.verifyToken\n // 4. 都未提供 → parseRequestContext(仅解析元数据,session 始终 undefined)\n //\n // ⚠️ 对 `iam.session.verifyToken` **不**做 `.bind()`:IAM 模块使用 NotInitializedKit Proxy 模式,\n // `iam.session` 的方法实现会在 `init()` 后切换。提前 bind 会捕获未初始化 Proxy 的方法引用。\n // 用闭包函数延迟到请求处理时再读取,确保始终调用最新实现。\n const iamRef = options.iam\n const verifyToken = options.verifyToken\n ?? (iamRef ? (token: string) => iamRef.session.verifyToken(token) : undefined)\n const createContext: CreateServContext = options.createContext\n ?? (verifyToken ? buildAuthContextFactory(verifyToken) : parseRequestContext)\n\n // Step 4:准备文档 spec 的懒缓存。只有真正访问 openapi/docs 时才生成,避免启动时做重活。\n // OpenAPI spec 是相对昂贵的同步生成;延迟到第一次访问 `/openapi.json` 或 `/docs` 时再算一次。\n let cachedSpec: Promise<OpenAPI.Document> | undefined\n const getSpec = (): Promise<OpenAPI.Document> => {\n cachedSpec ??= generateSpec(options.contract, { apiPrefix: http.apiPrefix })\n return cachedSpec\n }\n\n // Step 5:最先挂全局安全响应头,让后续所有响应(包括错误响应)都自动带上保护性 header。\n app.use('*', securityHeaders())\n\n // Step 6:先注册调用方自定义 middleware。位置固定在内置安全头之后、传输加密/业务路由之前,\n // 让 CORS preflight 等 HTTP 层横切逻辑可以优先短路,同时保持 transport 仍然位于业务路由之前。\n if (options.middlewares)\n mountCustomMiddlewares(app, options.middlewares)\n\n // Step 7:若启用传输加密,必须在业务路由之前挂载。\n // 这样请求能先被解密,响应也能在离开应用前被重新加密。\n // 传输加密必须在所有业务路由之前装载:\n // - 它需在 oRPC 读取 body 前完成解密\n // - 在下游响应返回后加密\n if (options.transport) {\n const mgr = options.transport.crypto.transport.createServer({\n keyStore: options.transport.keyStore,\n maxClients: options.transport.maxClients,\n })\n if (!mgr.success) {\n // 直接抛已存在的 HaiError 实例,保留 code/system/module/cause,便于上层定位到\n // \"transport 初始化失败\" 这一类配置错误,而非被 `new Error(message)` 抹平为字符串。\n throw core.error.buildHaiErrorInst(\n HaiCommonError.INTERNAL_ERROR,\n mgr.error.message,\n mgr.error,\n )\n }\n const keyExchangePath = `${http.apiPrefix}${options.transport.keyExchangePath ?? '/_hai/key-exchange'}`\n app.use('*', createTransportMiddleware(mgr.data, keyExchangePath, options.transport.excludePaths))\n }\n\n // Step 8:挂基础探活路由。健康检查放在业务路由之外,便于基础设施直接探测。\n if (http.health !== false)\n mountHealthRoutes(app, http.health)\n\n // Step 9:refresh-cookie 路由要抢在 oRPC 通配符之前注册,否则会被 `${apiPrefix}/*` 吞掉。\n // refresh-cookie 路由必须在 oRPC 通配符路由之前注册,Hono 按注册顺序匹配。\n if (options.refreshCookie)\n mountRefreshCookieRoutes(app, http.apiPrefix, options.refreshCookie, options.iam)\n\n // Step 9.5:语音 WebSocket 入口同样要在 oRPC 通配符之前注册,避免 GET 升级请求被 `${apiPrefix}/*` 吞掉。\n if (options.audio) {\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })\n const audioPath = `${http.apiPrefix}${options.audio.path ?? AUDIO_WS_PATH}`\n registerAudioWsRoute(app, audioPath, upgradeWebSocket, {\n ai: options.audio.ai,\n verifyTicket: options.audio.verifyTicket,\n authorize: options.audio.authorize,\n onSessionEnd: options.audio.onSessionEnd,\n maxMessageBytes: options.audio.maxMessageBytes,\n maxBufferedBytes: options.audio.maxBufferedBytes,\n maxTextBytes: options.audio.maxTextBytes,\n maxSessionMs: options.audio.maxSessionMs,\n preAuthTimeoutMs: options.audio.preAuthTimeoutMs,\n maxPendingMessages: options.audio.maxPendingMessages,\n maxSendBufferBytes: options.audio.maxSendBufferBytes,\n })\n audioWsInjectors.set(app, injectWebSocket)\n }\n\n // Step 10:挂主业务 API 路由。所有 `${apiPrefix}/*` 请求都会在这里进入 contract + procedure 分发。\n mountOpenAPIRoutes(app, options.procedures, http.apiPrefix, createContext)\n\n // Step 11:按需挂内部 RPC 路由,并在入口处限制访问来源。\n if (http.rpc !== false)\n mountRPCRoutes(app, options.procedures, http.rpc, createContext)\n\n // Step 12:按需挂 OpenAPI JSON 文档端点。\n if (http.openapi !== false) {\n app.get(http.openapi.path, async c => c.json(await getSpec()))\n }\n\n // Step 13:按需挂 docs 页面;若要求登录,则重用同一套 context/session 判定逻辑。\n if (http.docs !== false) {\n const docs = http.docs\n // 挂载 Scalar UI 脚本本地路由,避免访问外网 CDN\n app.get(SCALAR_ROUTE, async (c) => {\n const content = await getScalarScript()\n if (!content)\n return c.notFound()\n c.header('Content-Type', 'application/javascript; charset=utf-8')\n c.header('Cache-Control', 'public, max-age=86400, immutable')\n return c.body(content)\n })\n\n app.get(docs.path, async (c) => {\n if (docs.requireAuth) {\n const context = await createContext({ request: c.req.raw })\n // 与 procedure 层 `.auth()` 语义保持一致:必须是已验证 session,\n // 不能只检查 Bearer 字符串是否存在,避免任意伪造 Token 访问受保护文档。\n if (!context.session)\n return c.json(buildHaiErrorBody(HaiCommonError.UNAUTHORIZED, servM('serv_errorUnauthorized', { locale: resolveRequestLocale(c.req.raw.headers) })), 401)\n }\n const specUrl = http.openapi === false ? undefined : http.openapi.path\n return c.html(createDocsPage(await getSpec(), { specUrl }))\n })\n }\n\n // Step 14:所有运行时能力都已装配完毕,返回对外稳定的 HTTP app 抽象。\n return app\n}\n\nfunction mountCustomMiddlewares(app: Hono, middlewares: readonly ServMiddlewareMount[]): void {\n for (const entry of middlewares)\n app.use(entry.path ?? '*', entry.middleware)\n}\n\nfunction mountHealthRoutes(app: Hono, config: ServHealthHttpConfig): void {\n app.get(config.path, c => c.json({ status: 'ok' }))\n if (config.readyPath)\n app.get(config.readyPath, c => c.json({ status: 'ready' }))\n}\n\nfunction mountOpenAPIRoutes(\n app: Hono,\n procedures: Router<AnyContractRouter, ServContext>,\n apiPrefix: `/${string}`,\n createContext: CreateServContext,\n): void {\n const handler = new OpenAPIHandler(procedures)\n // 这里只为 `${apiPrefix}/*` 这个挂载前缀注册一次 multi-method catch-all。\n // 它不是“为 contract 中的每个 endpoint 各注册一遍 GET/POST/...”,\n // 真实的路由命中与输入校验都在下游 oRPC handler 中完成。\n app.on([...API_METHODS], `${apiPrefix}/*`, (c, next) => handleORPC(c, next, apiPrefix, handler, createContext))\n}\n\nfunction mountRPCRoutes(\n app: Hono,\n procedures: Router<AnyContractRouter, ServContext>,\n rpcConfig: { readonly prefix: `/${string}` } & Parameters<typeof requireInternalRPC>[0],\n createContext: CreateServContext,\n): void {\n const handler = new RPCHandler(procedures)\n const prefix = rpcConfig.prefix\n app.use(`${prefix}/*`, requireInternalRPC(rpcConfig))\n app.on([...API_METHODS], `${prefix}/*`, (c, next) => handleORPC(c, next, prefix, handler, createContext))\n}\n\nasync function handleORPC(\n c: HonoContext,\n next: Next,\n prefix: `/${string}`,\n handler: OpenAPIHandler<ServContext> | RPCHandler<ServContext>,\n createContext: CreateServContext,\n): Promise<Response | void> {\n // Step A:为本次请求创建 ServContext(可能只含元数据,也可能已带 session)。\n const context = await createContext({ request: c.req.raw })\n // Step B:交给 oRPC handler 做 contract 匹配 + procedure 执行。\n const { matched, response } = await handler.handle(c.req.raw, { prefix, context })\n if (!matched) {\n // Step C:当前前缀下没有匹配路由,继续交给 Hono 后续路由处理。\n await next()\n return\n }\n // Step D:若命中的是输入校验错误,这里统一改写成本地化的 HaiResult 失败体。\n return localizeValidationResponse(response, c.req.raw.headers)\n}\n\n/**\n * 拦截 oRPC 输入校验失败响应,重写为带 i18n 与 `errors[]` 的标准 HaiResult 失败体。\n *\n * 触发条件(保守判断,避免误伤业务自定义错误):\n * - status === 400\n * - body 为 oRPC ORPCError JSON 形态:`{ defined, code, status, message, data }`\n * - `code === 'BAD_REQUEST'` 且 `data.issues` 存在\n *\n * 不符合上述条件的响应原样透传,确保业务层主动抛出的 BAD_REQUEST 不被改写。\n */\nasync function localizeValidationResponse(response: Response, requestHeaders: Headers): Promise<Response> {\n if (response.status !== 400)\n return response\n const contentType = response.headers.get('content-type') ?? ''\n if (!contentType.includes('application/json'))\n return response\n\n // 克隆响应再读取,避免破坏调用方对 response 的潜在二次消费。\n let payload: unknown\n try {\n payload = await response.clone().json()\n }\n catch {\n return response\n }\n\n if (!isOrpcValidationErrorPayload(payload))\n return response\n\n const issues = payload.data.issues\n const locale = resolveRequestLocale(requestHeaders)\n const errors = localizeZodError({ issues }, locale)\n // 与 `validateInputOrFail` 输出对齐:errors 同时挂在 `error.cause` 上,\n // 使两条校验路径的客户端解析逻辑一致(都从 `error.cause` 读取字段错误)。\n // 顶层 `errors` 字段保留,兼容已有客户端读法。\n const body: ServValidationFailureBody = {\n success: false,\n error: {\n code: HaiCommonError.VALIDATION_ERROR.code,\n message: servM('serv_validationFailed', { locale }),\n httpStatus: HaiCommonError.VALIDATION_ERROR.httpStatus,\n system: HaiCommonError.VALIDATION_ERROR.system,\n module: HaiCommonError.VALIDATION_ERROR.module,\n cause: errors,\n },\n errors,\n }\n\n // 保留原响应头(其中可能包含安全头),仅替换 body 与 status。\n const headers = new Headers(response.headers)\n headers.set('content-type', 'application/json; charset=utf-8')\n return new Response(JSON.stringify(body), {\n status: HaiCommonError.VALIDATION_ERROR.httpStatus,\n headers,\n })\n}\n\ninterface OrpcValidationErrorPayload {\n readonly defined: boolean\n readonly code: string\n readonly status: number\n readonly message: string\n readonly data: { readonly issues: unknown[] }\n}\n\nfunction isOrpcValidationErrorPayload(value: unknown): value is OrpcValidationErrorPayload {\n if (!value || typeof value !== 'object')\n return false\n const obj = value as Record<string, unknown>\n if (obj.code !== 'BAD_REQUEST')\n return false\n if (typeof obj.status !== 'number' || obj.status !== 400)\n return false\n const data = obj.data\n if (!data || typeof data !== 'object')\n return false\n const issues = (data as Record<string, unknown>).issues\n return Array.isArray(issues)\n}\n","/**\n * @h-ai/serv — Node.js 适配器\n *\n * 封装 `@hono/node-server`,将 ServHttpApp 以 Node.js HTTP 服务器形式启动。\n * 默认监听 `127.0.0.1`(仅本机可达);如需对外暴露,需显式设置 `host: '0.0.0.0'` 或指定 IP。\n * @module adapters/serv-adapter-node\n */\n\nimport type { ServerType } from '@hono/node-server'\nimport type { AddressInfo } from 'node:net'\nimport type { ServHttpApp } from '../serv-app.js'\nimport process from 'node:process'\nimport { serve } from '@hono/node-server'\nimport { audioWsInjectors } from '../serv-app.js'\n\n/** `serv.listen` 默认监听地址。仅本机可达,避免误暴露到公网。 */\nexport const DEFAULT_SERV_HOST = '127.0.0.1'\n\n/** `serv.listen` 默认监听端口。 */\nexport const DEFAULT_SERV_PORT = 3000\n\n/** Node 服务器启动配置。 */\nexport interface ServListenOptions {\n /**\n * 监听端口。\n * 优先级:显式值 > `PORT` 环境变量 > 默认 `3000`。\n */\n readonly port?: number\n /**\n * 监听地址。\n * 优先级:显式值 > `HOST` 环境变量 > 默认 `'127.0.0.1'`(仅本机可达)。\n *\n * - `'127.0.0.1'`:仅本机(推荐用于本地开发、容器内 sidecar、反向代理后端)\n * - `'0.0.0.0'`:监听所有 IPv4 网卡(容器对外暴露、需公网/内网访问)\n * - 指定 IP:仅监听该网卡(多网卡精确绑定)\n */\n readonly host?: string\n /** 服务启动就绪后的回调。 */\n readonly onListening?: (info: AddressInfo) => void\n /**\n * 优雅关闭回调(释放业务模块资源)。\n *\n * 传入后 `serv.listen` 自动监听 `SIGINT` / `SIGTERM`,按以下顺序关闭:\n * 1. 停止接受新 HTTP 连接\n * 2. 调用此回调释放业务资源\n *\n * 应用层无需手动注册 `process.once('SIGINT', ...)` 或维护 `shuttingDown` 标志。\n */\n readonly onClose?: () => Promise<void>\n}\n\n/** Node 服务运行句柄。 */\nexport interface ServNodeServer {\n /** 底层 Node.js HTTP Server 实例。 */\n readonly server: ServerType\n /** 实际监听地址(host 与解析后的 port)。 */\n readonly address: { readonly host: string, readonly port: number }\n /** 优雅关闭服务器并等待已有连接断开。 */\n readonly close: () => Promise<void>\n}\n\n/**\n * 在 Node.js 中启动 ServHttpApp。\n *\n * 默认监听 `127.0.0.1:3000`(可通过 `PORT` / `HOST` 环境变量覆盖)。\n * 传入 `onClose` 后自动注册 SIGINT/SIGTERM 优雅关闭,应用层无需手动处理信号。\n *\n * @param app - `serv.createApp()` 返回的 HTTP app\n * @param options - 监听配置(port / host / onListening / onClose)\n * @returns 运行句柄\n *\n * @example\n * ```ts\n * // 最简:读取 PORT / HOST 环境变量,自动优雅关闭\n * serv.listen(app, {\n * onListening: info => logger.info('listening', { port: info.port }),\n * onClose: closeApp,\n * })\n *\n * // 容器对外暴露\n * serv.listen(app, { host: '0.0.0.0', onClose: closeApp })\n * ```\n */\nexport function listen(app: ServHttpApp, options: ServListenOptions = {}): ServNodeServer {\n const host = options.host ?? process.env.HOST ?? DEFAULT_SERV_HOST\n const port = options.port ?? (process.env.PORT ? Number(process.env.PORT) : DEFAULT_SERV_PORT)\n\n const server = serve(\n { fetch: app.fetch, hostname: host, port },\n options.onListening,\n )\n\n // 若启用了语音 WebSocket 入口,注入升级处理器以支持 ws 握手\n const injectWebSocket = audioWsInjectors.get(app)\n if (injectWebSocket)\n injectWebSocket(server)\n\n const handle: ServNodeServer = {\n server,\n address: { host, port },\n close: () => new Promise((resolve, reject) => {\n server.close((error) => {\n if (error)\n reject(error)\n else resolve()\n })\n }),\n }\n\n // 注册 SIGINT / SIGTERM 优雅关闭(仅在传入 onClose 时启用)。\n if (options.onClose) {\n const onClose = options.onClose\n let shuttingDown = false\n const shutdown = async (): Promise<void> => {\n if (shuttingDown)\n return\n shuttingDown = true\n try {\n await handle.close()\n }\n catch { /* 忽略 HTTP server 关闭错误 */ }\n try {\n await onClose()\n }\n catch { /* 忽略业务模块关闭错误 */ }\n }\n process.once('SIGINT', () => void shutdown())\n process.once('SIGTERM', () => void shutdown())\n }\n\n return handle\n}\n","/**\n * @h-ai/serv — CORS middleware\n *\n * 统一处理 OPTIONS 预检与常规响应的 CORS 头注入,让应用层只保留 origin 白名单策略。\n * @module serv-cors\n */\n\nimport type { ServMiddleware } from './pipelines/serv-pipeline-types.js'\n\n/** serv CORS 配置。 */\nexport interface ServCorsConfig {\n /** 允许的 origin:`'*'`、单个字符串、数组或自定义匹配函数。默认 `'*'`。 */\n readonly origin?: '*' | string | readonly string[] | ((origin: string) => boolean)\n /** 允许的方法。默认 `GET/HEAD/PUT/PATCH/POST/DELETE/OPTIONS`。 */\n readonly methods?: readonly string[]\n /** 允许的请求头;若预检请求带 `Access-Control-Request-Headers`,优先回显该值。 */\n readonly allowedHeaders?: readonly string[]\n /** 暴露给浏览器 JS 的响应头。 */\n readonly exposedHeaders?: readonly string[]\n /** 是否允许携带 credentials(cookie / Authorization)。默认 `false`。 */\n readonly credentials?: boolean\n /** 预检缓存秒数。默认 `86400`。 */\n readonly maxAge?: number\n}\n\nconst DEFAULT_CORS_METHODS = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS']\nconst DEFAULT_CORS_ALLOWED_HEADERS = ['Authorization', 'Content-Type', 'X-Requested-With']\nconst DEFAULT_CORS_MAX_AGE = 86_400\n\n/** 创建可复用的 CORS middleware。 */\nexport function cors(config: ServCorsConfig = {}): ServMiddleware {\n const methods = [...(config.methods ?? DEFAULT_CORS_METHODS)]\n const allowedHeaders = [...(config.allowedHeaders ?? DEFAULT_CORS_ALLOWED_HEADERS)]\n const exposedHeaders = [...(config.exposedHeaders ?? [])]\n const credentials = config.credentials ?? false\n const maxAge = config.maxAge ?? DEFAULT_CORS_MAX_AGE\n const originConfig = config.origin ?? '*'\n\n return async (c, next) => {\n const allowedOrigin = resolveCorsOrigin(c.req.header('Origin'), originConfig)\n\n if (c.req.method === 'OPTIONS') {\n return new Response(null, {\n status: 204,\n headers: buildCorsHeaders({\n allowedOrigin,\n allowedHeaders,\n credentials,\n exposedHeaders,\n maxAge,\n methods,\n request: c.req.raw,\n }),\n })\n }\n\n await next()\n\n const corsHeaders = buildCorsHeaders({\n allowedOrigin,\n allowedHeaders,\n credentials,\n exposedHeaders,\n maxAge,\n methods,\n request: c.req.raw,\n })\n for (const [key, value] of corsHeaders) {\n if (key.toLowerCase() === 'vary') {\n c.res.headers.append(key, value)\n continue\n }\n c.res.headers.set(key, value)\n }\n\n return undefined\n }\n}\n\nfunction resolveCorsOrigin(\n origin: string | undefined,\n configOrigin: NonNullable<ServCorsConfig['origin']>,\n): string | null {\n if (!origin)\n return null\n\n if (configOrigin === '*')\n return '*'\n\n if (typeof configOrigin === 'string')\n return origin === configOrigin ? origin : null\n\n if (Array.isArray(configOrigin))\n return configOrigin.includes(origin) ? origin : null\n\n if (typeof configOrigin === 'function')\n return configOrigin(origin) ? origin : null\n\n return null\n}\n\nfunction buildCorsHeaders(options: {\n allowedOrigin: string | null\n allowedHeaders: readonly string[]\n credentials: boolean\n exposedHeaders: readonly string[]\n maxAge: number\n methods: readonly string[]\n request: Request\n}): Headers {\n const headers = new Headers()\n if (!options.allowedOrigin)\n return headers\n\n const requestedHeaders = options.request.headers.get('Access-Control-Request-Headers')\n\n headers.set('Access-Control-Allow-Origin', options.allowedOrigin)\n if (options.credentials)\n headers.set('Access-Control-Allow-Credentials', 'true')\n\n if (options.allowedOrigin !== '*') {\n headers.set(\n 'Vary',\n requestedHeaders ? 'Origin, Access-Control-Request-Headers' : 'Origin',\n )\n }\n\n if (options.exposedHeaders.length > 0)\n headers.set('Access-Control-Expose-Headers', options.exposedHeaders.join(', '))\n\n if (options.request.method === 'OPTIONS') {\n headers.set('Access-Control-Allow-Methods', options.methods.join(', '))\n headers.set('Access-Control-Allow-Headers', requestedHeaders ?? options.allowedHeaders.join(', '))\n headers.set('Access-Control-Max-Age', String(options.maxAge))\n }\n\n return headers\n}\n","/**\n * @h-ai/serv — 部署环境 HTTP 安全策略\n *\n * 将环境与 CORS 配置收敛为 fail-closed 的运行时策略,避免每个业务应用重复实现\n * Origin 解析、生产通配符保护、Cookie Secure 与 API 文档暴露规则。\n * @module serv-runtime-security\n */\n\n/** 运行时安全策略输入;通常来自 `_core.yml.env` 与 `_serv.yml.cors`。 */\nexport interface ServRuntimeSecurityPolicyInput {\n readonly environment?: string\n readonly corsOrigin?: string\n readonly nativeOrigins?: string\n}\n\n/** 可直接装配到 CORS、refresh cookie 与 HTTP 文档配置的安全策略。 */\nexport interface ServRuntimeSecurityPolicy {\n readonly allowOrigin: (origin: string) => boolean\n readonly isNativeOrigin: (origin: string) => boolean\n readonly secureRefreshCookie: boolean\n readonly exposeApiDocs: boolean\n}\n\n/**\n * 创建部署环境安全策略。\n *\n * 生产环境必须显式配置 HTTP(S) Origin,禁止缺省或 `*`;开发环境保留通配符便利性。\n */\nexport function createRuntimeSecurityPolicy(\n input: ServRuntimeSecurityPolicyInput,\n): ServRuntimeSecurityPolicy {\n const production = input.environment?.trim().toLowerCase() === 'production'\n const configuredOrigins = parseOrigins(input.corsOrigin)\n const configuredNativeOrigins = parseNativeOrigins(input.nativeOrigins)\n const allowsEveryOrigin = configuredOrigins === '*'\n\n if (production && (allowsEveryOrigin || configuredOrigins.length === 0))\n throw new Error('serv.cors.origin must contain explicit HTTP(S) origins in production')\n\n const allowedOrigins = allowsEveryOrigin ? undefined : new Set(configuredOrigins)\n const allowedNativeOrigins = new Set(configuredNativeOrigins)\n return {\n allowOrigin: (origin) => {\n const normalizedNativeOrigin = tryNormalizeNativeOrigin(origin)\n if (normalizedNativeOrigin !== undefined && allowedNativeOrigins.has(normalizedNativeOrigin))\n return true\n if (allowedOrigins === undefined)\n return true\n const normalizedWebOrigin = tryNormalizeWebOrigin(origin)\n return normalizedWebOrigin !== undefined && allowedOrigins.has(normalizedWebOrigin)\n },\n isNativeOrigin: origin => allowedNativeOrigins.has(tryNormalizeNativeOrigin(origin) ?? ''),\n secureRefreshCookie: production,\n exposeApiDocs: !production,\n }\n}\n\nfunction parseOrigins(value: string | undefined): '*' | string[] {\n const entries = (value ?? '*').split(',').map(origin => origin.trim()).filter(Boolean)\n if (entries.includes('*'))\n return '*'\n return entries.map(normalizeWebOrigin)\n}\n\nfunction parseNativeOrigins(value: string | undefined): string[] {\n return (value ?? '').split(',').map(origin => origin.trim()).filter(Boolean).map(normalizeNativeOrigin)\n}\n\nfunction normalizeWebOrigin(value: string): string {\n const normalized = tryNormalizeWebOrigin(value)\n if (normalized === undefined)\n throw new Error(`serv.cors.origin contains an invalid origin: ${value}`)\n return normalized\n}\n\nfunction normalizeNativeOrigin(value: string): string {\n const normalized = tryNormalizeNativeOrigin(value)\n if (normalized === undefined)\n throw new Error(`serv.cors.nativeOrigins contains an invalid origin: ${value}`)\n return normalized\n}\n\nfunction tryNormalizeWebOrigin(value: string): string | undefined {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n return undefined\n }\n if (!['http:', 'https:'].includes(url.protocol) || url.origin === 'null' || url.pathname !== '/' || url.search || url.hash)\n return undefined\n return url.origin\n}\n\nfunction tryNormalizeNativeOrigin(value: string): string | undefined {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n return undefined\n }\n if (!['http:', 'https:', 'capacitor:', 'tauri:'].includes(url.protocol) || !url.hostname || url.username || url.password || !['', '/'].includes(url.pathname) || url.search || url.hash)\n return undefined\n return ['capacitor:', 'tauri:'].includes(url.protocol) ? `${url.protocol}//${url.host}` : url.origin\n}\n","/**\n * @h-ai/serv — Storage 只读资源 middleware\n *\n * 将经过路径与 MIME 白名单校验的 `@h-ai/storage` 文件作为 HTTP 资源返回,\n * 统一处理 GET/HEAD、ETag 与缓存响应头,不包含头像等业务领域概念。\n * @module serv-storage-assets\n */\n\nimport type { StorageFunctions } from '@h-ai/storage'\nimport type { ServMiddleware } from './pipelines/serv-pipeline-types.js'\n\n/** 可由只读资源 middleware 使用的最小 Storage 能力。 */\ninterface StorageAssetSource {\n readonly file: Pick<StorageFunctions['file'], 'get' | 'head'>\n}\n\n/** Storage 只读资源 middleware 配置。 */\nexport interface ServStorageAssetsConfig {\n /** 已初始化的 Storage 模块或等价文件读取能力。 */\n readonly storage: StorageAssetSource\n /**\n * 从请求 pathname 中移除的路由前缀,例如 `/api/v1/assets/`。\n * 是否挂载通配符仍由 `createApp({ middlewares })` 的 `path` 决定。\n */\n readonly pathPrefix: `/${string}`\n /** 允许访问的完整 Storage key 白名单;框架仍会先拒绝路径穿越与非法 key。 */\n readonly keyPattern: RegExp\n /** 允许返回的 MIME 白名单。 */\n readonly allowedContentTypes: readonly string[]\n /** Cache-Control 响应头,默认 `no-store`。 */\n readonly cacheControl?: string\n /** 可选的 Cross-Origin-Resource-Policy 响应头。 */\n readonly crossOriginResourcePolicy?: 'same-origin' | 'same-site' | 'cross-origin'\n}\n\n/**\n * 创建由 Storage 提供内容的只读资源 middleware。\n *\n * 仅允许 GET/HEAD;非法路径、非白名单 MIME 与 Storage 读取失败统一返回 404,\n * 避免向外暴露文件是否存在或后端错误细节。\n *\n * @param config - Storage、路径、key、MIME 与缓存策略。\n * @returns 可挂载到 `serv.createApp({ middlewares })` 的 middleware。\n * @example\n * ```ts\n * const middleware = serv.storageAssets({\n * storage,\n * pathPrefix: '/api/v1/avatar-assets/',\n * keyPattern: /^avatars\\/[\\w-]+\\/[\\w-]+\\.webp$/,\n * allowedContentTypes: ['image/webp'],\n * cacheControl: 'public, max-age=31536000, immutable',\n * })\n * ```\n */\nexport function storageAssets(config: ServStorageAssetsConfig): ServMiddleware {\n const pathPrefix = config.pathPrefix.endsWith('/') ? config.pathPrefix : `${config.pathPrefix}/`\n const allowedKeyPattern = new RegExp(config.keyPattern.source, config.keyPattern.flags.replace(/[gy]/g, ''))\n const allowedContentTypes = new Set(config.allowedContentTypes)\n\n return async (c) => {\n const request = c.req.raw\n const path = new URL(request.url).pathname\n if (request.method !== 'GET' && request.method !== 'HEAD')\n return c.body(null, 405, { Allow: 'GET, HEAD' })\n if (!path.startsWith(pathPrefix))\n return notFound(c)\n\n const key = decodeStorageAssetKey(path.slice(pathPrefix.length), allowedKeyPattern)\n if (!key)\n return notFound(c)\n\n const metadata = await config.storage.file.head(key)\n if (!metadata.success || !allowedContentTypes.has(metadata.data.contentType))\n return notFound(c)\n\n const headers: Record<string, string> = {\n 'Cache-Control': config.cacheControl ?? 'no-store',\n 'Content-Length': String(metadata.data.size),\n 'Content-Type': metadata.data.contentType,\n ...(config.crossOriginResourcePolicy\n ? { 'Cross-Origin-Resource-Policy': config.crossOriginResourcePolicy }\n : {}),\n ...(metadata.data.etag ? { ETag: metadata.data.etag } : {}),\n }\n if (metadata.data.etag && matchesEtag(request.headers.get('if-none-match'), metadata.data.etag))\n return c.body(null, 304, headers)\n if (request.method === 'HEAD')\n return c.body(null, 200, headers)\n\n const file = await config.storage.file.get(key)\n if (!file.success)\n return notFound(c)\n return c.body(Uint8Array.from(file.data).buffer, 200, headers)\n }\n}\n\n/** 解码并校验资源 key,拒绝绝对路径、反斜杠、NUL 与点路径段。 */\nfunction decodeStorageAssetKey(value: string, allowedKeyPattern: RegExp): string | undefined {\n try {\n const key = decodeURIComponent(value)\n if (\n key.length === 0\n || key.length > 1024\n || key.startsWith('/')\n || key.includes('\\\\')\n || key.includes('\\0')\n || key.split('/').some(segment => segment === '.' || segment === '..')\n ) {\n return undefined\n }\n const match = allowedKeyPattern.exec(key)\n return match?.[0] === key ? key : undefined\n }\n catch {\n return undefined\n }\n}\n\n/** 按 HTTP 弱比较语义匹配 If-None-Match。 */\nfunction matchesEtag(ifNoneMatch: string | null, etag: string): boolean {\n if (!ifNoneMatch)\n return false\n if (ifNoneMatch.trim() === '*')\n return true\n const expected = normalizeEtag(etag)\n return ifNoneMatch.split(',').some(candidate => normalizeEtag(candidate) === expected)\n}\n\nfunction normalizeEtag(value: string): string {\n return value.trim().replace(/^W\\//i, '')\n}\n\nfunction notFound(c: Parameters<ServMiddleware>[0]): Response {\n return c.body(null, 404, { 'Cache-Control': 'no-store' })\n}\n","/**\n * @h-ai/serv — API service runtime 统一入口\n *\n * 将 createApp、parseRequestContext、buildAuthContextFactory、listen、toFetch、openapi、pipeline 工具集合为扁平\n * 的 `serv` 命名空间,应用层通过这个模块访问所有 runtime 能力而无需直接导入内部文件。\n *\n * @module serv-main\n *\n * @example\n * ```ts\n * import { serv } from '@h-ai/serv'\n *\n * const procedures = serv\n * .implement(contract)\n * .context<ServContext>()\n * .route('health', handler)\n * .build()\n * ```\n */\n\nimport { toFetch } from './adapters/serv-adapter-fetch.js'\nimport { listen } from './adapters/serv-adapter-node.js'\nimport {\n WILDCARD_PERMISSION,\n WILDCARD_ROLE,\n} from './pipelines/serv-pipeline-guard.js'\nimport { createApp } from './serv-app.js'\nimport { ServConfigSchema } from './serv-config.js'\nimport { buildAuthContextFactory, parseRequestContext } from './serv-context.js'\nimport { cors } from './serv-cors.js'\nimport { servM } from './serv-i18n.js'\nimport { generateSpec } from './serv-openapi.js'\nimport { implement } from './serv-router.js'\nimport { createRuntimeSecurityPolicy } from './serv-runtime-security.js'\nimport { storageAssets } from './serv-storage-assets.js'\nimport {\n resolveRequestLocale,\n validateInputOrFail,\n} from './serv-validation.js'\n\nexport { ServConfigSchema }\nexport type {\n AuthenticatedServContext,\n ServImplementer,\n ServRouteHandler,\n ServRoutePath,\n ServRouterBuilder,\n} from './serv-router.js'\n\n/** hai-framework API service runtime 统一入口(扁平 API)。 */\nexport const serv = {\n // 应用与上下文\n createApp,\n parseRequestContext,\n buildAuthContextFactory,\n resolveRequestLocale,\n\n // 运行时适配器\n listen,\n toFetch,\n cors,\n storageAssets,\n createRuntimeSecurityPolicy,\n\n // OpenAPI / 导出\n generateSpec,\n\n // Contract procedure 装配。\n implement,\n\n // Procedure 工具;认证与授权统一由 route 链声明。\n validateInputOrFail,\n\n // 通配符常量\n WILDCARD_PERMISSION,\n WILDCARD_ROLE,\n\n // Serv 模块 i18n 消息获取器(支持 `options.locale` 单次调用本地化)。\n m: servM,\n}\n"]}
|