Spaces:
Paused
Paused
| import { Hono } from "hono"; | |
| import { ChatCompletionRequestSchema } from "../types/openai.js"; | |
| import type { AccountPool } from "../auth/account-pool.js"; | |
| import type { CookieJar } from "../proxy/cookie-jar.js"; | |
| import type { ProxyPool } from "../proxy/proxy-pool.js"; | |
| import { translateToCodexRequest } from "../translation/openai-to-codex.js"; | |
| import { | |
| streamCodexToOpenAI, | |
| collectCodexResponse, | |
| } from "../translation/codex-to-openai.js"; | |
| import { getConfig } from "../config.js"; | |
| import { parseModelName, buildDisplayModelName } from "../models/model-store.js"; | |
| import { | |
| handleProxyRequest, | |
| type FormatAdapter, | |
| } from "./shared/proxy-handler.js"; | |
| function makeOpenAIFormat(wantReasoning: boolean): FormatAdapter { | |
| return { | |
| tag: "Chat", | |
| noAccountStatus: 503, | |
| formatNoAccount: () => ({ | |
| error: { | |
| message: | |
| "No available accounts. All accounts are expired or rate-limited.", | |
| type: "server_error", | |
| param: null, | |
| code: "no_available_accounts", | |
| }, | |
| }), | |
| format429: (msg) => ({ | |
| error: { | |
| message: msg, | |
| type: "rate_limit_error", | |
| param: null, | |
| code: "rate_limit_exceeded", | |
| }, | |
| }), | |
| formatError: (_status, msg) => ({ | |
| error: { | |
| message: msg, | |
| type: "server_error", | |
| param: null, | |
| code: "codex_api_error", | |
| }, | |
| }), | |
| streamTranslator: (api, response, model, onUsage, onResponseId, tupleSchema) => | |
| streamCodexToOpenAI(api, response, model, onUsage, onResponseId, wantReasoning, tupleSchema), | |
| collectTranslator: (api, response, model, tupleSchema) => | |
| collectCodexResponse(api, response, model, wantReasoning, tupleSchema), | |
| }; | |
| } | |
| export function createChatRoutes( | |
| accountPool: AccountPool, | |
| cookieJar?: CookieJar, | |
| proxyPool?: ProxyPool, | |
| ): Hono { | |
| const app = new Hono(); | |
| app.post("/v1/chat/completions", async (c) => { | |
| // Auth check | |
| if (!accountPool.isAuthenticated()) { | |
| c.status(401); | |
| return c.json({ | |
| error: { | |
| message: "Not authenticated. Please login first at /", | |
| type: "invalid_request_error", | |
| param: null, | |
| code: "invalid_api_key", | |
| }, | |
| }); | |
| } | |
| // Optional proxy API key check | |
| const config = getConfig(); | |
| if (config.server.proxy_api_key) { | |
| const authHeader = c.req.header("Authorization"); | |
| const providedKey = authHeader?.replace("Bearer ", ""); | |
| if ( | |
| !providedKey || | |
| !accountPool.validateProxyApiKey(providedKey) | |
| ) { | |
| c.status(401); | |
| return c.json({ | |
| error: { | |
| message: "Invalid proxy API key", | |
| type: "invalid_request_error", | |
| param: null, | |
| code: "invalid_api_key", | |
| }, | |
| }); | |
| } | |
| } | |
| // Parse request | |
| let body: unknown; | |
| try { | |
| body = await c.req.json(); | |
| } catch { | |
| c.status(400); | |
| return c.json({ | |
| error: { | |
| message: "Malformed JSON request body", | |
| type: "invalid_request_error", | |
| param: null, | |
| code: "invalid_json", | |
| }, | |
| }); | |
| } | |
| const parsed = ChatCompletionRequestSchema.safeParse(body); | |
| if (!parsed.success) { | |
| c.status(400); | |
| return c.json({ | |
| error: { | |
| message: `Invalid request: ${parsed.error.message}`, | |
| type: "invalid_request_error", | |
| param: null, | |
| code: "invalid_request", | |
| }, | |
| }); | |
| } | |
| const req = parsed.data; | |
| const { codexRequest, tupleSchema } = translateToCodexRequest(req); | |
| const displayModel = buildDisplayModelName(parseModelName(req.model)); | |
| const wantReasoning = !!req.reasoning_effort; | |
| return handleProxyRequest( | |
| c, | |
| accountPool, | |
| cookieJar, | |
| { | |
| codexRequest, | |
| model: displayModel, | |
| isStreaming: req.stream, | |
| tupleSchema, | |
| }, | |
| makeOpenAIFormat(wantReasoning), | |
| proxyPool, | |
| ); | |
| }); | |
| return app; | |
| } | |