#!/usr/bin/env node import http from "node:http"; import { spawn } from "node:child_process"; import { createHash } from "node:crypto"; import { chmod, copyFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"; import fs from "node:fs"; import path from "node:path"; import { DatabaseSync } from "node:sqlite"; import zlib from "node:zlib"; import { TextDecoder } from "node:util"; import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const ADMIN_BASE_PATH = "/__codex_retry_gateway"; const UI_PATH = `${ADMIN_BASE_PATH}/ui`; const UI_STATIC_ROOT = path.join(__dirname, "public", "ui"); const STATUS_API_PATH = `${ADMIN_BASE_PATH}/api/status`; const CONFIG_API_PATH = `${ADMIN_BASE_PATH}/api/config`; const LOGS_API_PATH = `${ADMIN_BASE_PATH}/api/logs`; const REQUESTS_API_PATH = `${ADMIN_BASE_PATH}/api/requests`; const THREAD_RULES_API_PATH = `${ADMIN_BASE_PATH}/api/thread-rules`; const THREAD_RULE_ITEM_API_PREFIX = `${THREAD_RULES_API_PATH}/`; const PROFILES_API_PATH = `${ADMIN_BASE_PATH}/api/profiles`; const PROFILE_PROBE_API_PATH = `${ADMIN_BASE_PATH}/api/profiles/probe`; const PROFILE_SWITCH_API_PATH = `${ADMIN_BASE_PATH}/api/profiles/switch`; const PROFILE_ITEM_API_PREFIX = `${ADMIN_BASE_PATH}/api/profiles/`; const IMAGE_PROFILES_API_PATH = `${ADMIN_BASE_PATH}/api/image-profiles`; const IMAGE_PROFILE_PROBE_API_PATH = `${ADMIN_BASE_PATH}/api/image-profiles/probe`; const IMAGE_PROFILE_SWITCH_API_PATH = `${ADMIN_BASE_PATH}/api/image-profiles/switch`; const IMAGE_PROFILE_ITEM_API_PREFIX = `${ADMIN_BASE_PATH}/api/image-profiles/`; const RESTORE_API_PATH = `${ADMIN_BASE_PATH}/api/restore`; const STATUS_REASONING_COUNT_LIMIT = 24; const MANAGEMENT_ACCESS_COOKIE = "codex_retry_gateway_access"; const RESPONSES_REASONING_RETRY_PATHS = new Set(["/responses", "/v1/responses"]); const REASONING_RETRY_ABORT_CLIENT = "reasoning_retry_client_disconnected"; const REASONING_RETRY_ABORT_WINNER = "reasoning_retry_winner_selected"; const DEFAULT_CONFIG = { profile_name: "default", image_profile_name: "", listen_host: "127.0.0.1", listen_port: 4610, upstream_base_url: "", upstream_auth_mode: "passthrough", upstream_auth_env: "CODEX_RETRY_GATEWAY_UPSTREAM_API_KEY", upstream_auth_file: "", upstream_auth_json_path: "", upstream_auth_json_key: "OPENAI_API_KEY", image_base_url: "", image_auth_mode: "fixed_bearer", image_auth_env: "CODEX_RETRY_GATEWAY_IMAGE_API_KEY", image_auth_file: "", image_auth_json_path: "", image_auth_json_key: "OPENAI_API_KEY", request_body_limit_bytes: 1024 * 1024 * 1024, request_history_limit: 0, model_remap: "", endpoints: ["/responses", "/chat/completions", "/v1/responses", "/v1/chat/completions"], reasoning_match_mode: "formula_518n_minus_2", reasoning_equals: [516, 1034, 1552], retryable_status_codes: [429, 503], retryable_error_messages: [ "Selected model is at capacity. Please try a different model.", "stream disconnected before completion: Concurrency limit exceeded for account, please retry later", ], management_access_key: "", upstream_fetch_retry_attempts: 5, upstream_fetch_retry_backoff_ms: 350, non_stream_status_code: 502, stream_action: "strict_502", log_match: true, health_path: "/__codex_retry_gateway/health", }; const REASONING_POINTERS = [ "/usage/output_tokens_details/reasoning_tokens", "/usage/completion_tokens_details/reasoning_tokens", "/response/usage/output_tokens_details/reasoning_tokens", "/response/usage/completion_tokens_details/reasoning_tokens", ]; const REQUEST_THREAD_ID_POINTERS = [ "/thread_id", "/client_metadata/thread_id", "/client_metadata/x-codex-thread-id", "/thread", "/thread/id", "/threadId", "/client_metadata/threadId", "/conversation_id", "/conversationId", "/conversation", "/conversation/id", "/metadata/thread_id", "/metadata/threadId", "/metadata/conversation_id", "/metadata/conversationId", "/x-codex-turn-metadata/thread_id", "/x-codex-turn-metadata/threadId", "/x-codex-turn-metadata/conversation_id", "/x-codex-turn-metadata/conversationId", "/previous_response_id", ]; const REQUEST_REASONING_EFFORT_POINTERS = [ "/reasoning/effort", "/reasoning_effort", "/reasoningEffort", ]; const REQUEST_REASONING_SUMMARY_POINTERS = [ "/reasoning/summary", "/reasoning_summary", "/reasoningSummary", ]; const RESPONSE_THREAD_ID_POINTERS = [ "/thread_id", "/client_metadata/thread_id", "/client_metadata/x-codex-thread-id", "/thread", "/thread/id", "/threadId", "/client_metadata/threadId", "/conversation_id", "/conversationId", "/conversation", "/conversation/id", "/response/thread_id", "/response/threadId", "/response/thread", "/response/thread/id", "/response/conversation_id", "/response/conversationId", "/response/conversation", "/response/conversation/id", ]; const NON_STREAM_RESPONSE_ID_POINTERS = [ "/id", "/response_id", "/response/id", ]; const STREAM_RESPONSE_ID_POINTERS = [ "/response_id", "/response/id", ]; function parseArgs(argv) { const args = { config: null, log: null }; for (let i = 2; i < argv.length; i += 1) { const current = argv[i]; if (current === "--config") { args.config = argv[i + 1]; i += 1; } else if (current === "--log") { args.log = argv[i + 1]; i += 1; } else if (current === "--help" || current === "-h") { printHelp(); process.exit(0); } } return args; } function printHelp() { process.stdout.write( [ "用法:", " node gateway.mjs --config [--log ]", "", "说明:", " 独立 Codex 本地重试网关。", " 默认按 518n-2 公式拦截 reasoning_tokens(516, 1034, 1552, ...),非流式返回 502。", " 流式命中时默认缓存并返回 502,避免半截流返回。", "", ].join("\n"), ); } function normalizePath(inputPath) { const [withoutQuery] = `${inputPath || "/"}`.split("?"); const trimmed = withoutQuery.length > 1 ? withoutQuery.replace(/\/+$/, "") : withoutQuery; return trimmed || "/"; } function expandEscapedLineBreaks(value) { return `${value ?? ""}` .replace(/\\r\\n/g, "\n") .replace(/\\n/g, "\n") .replace(/\\r/g, "\n"); } function flattenValues(value) { if (Array.isArray(value)) { return value.flatMap((item) => flattenValues(item)); } return [value]; } function isJsonContentType(contentType) { return `${contentType || ""}`.toLowerCase().includes("application/json"); } function isSseContentType(contentType) { return `${contentType || ""}`.toLowerCase().includes("text/event-stream"); } function jsonPointerGet(value, pointer) { if (!pointer.startsWith("/")) { return undefined; } return pointer .slice(1) .split("/") .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")) .reduce((current, segment) => { if (current === null || current === undefined) { return undefined; } return current[segment]; }, value); } function extractReasoningTokens(payload) { for (const pointer of REASONING_POINTERS) { const raw = jsonPointerGet(payload, pointer); if (Number.isInteger(raw)) { return raw; } } return null; } function firstInteger(...values) { for (const value of values) { if (Number.isInteger(value)) { return value; } } return null; } function firstNonEmptyString(...values) { for (const value of values) { if (typeof value !== "string") { continue; } const trimmed = value.trim(); if (trimmed) { return trimmed; } } return null; } function timingSafeEquals(left, right) { const leftText = Buffer.from(`${left || ""}`); const rightText = Buffer.from(`${right || ""}`); if (leftText.length !== rightText.length) { return false; } let mismatch = 0; for (let i = 0; i < leftText.length; i += 1) { mismatch |= leftText[i] ^ rightText[i]; } return mismatch === 0; } function parseCookieHeader(cookieHeader) { const cookies = {}; for (const part of `${cookieHeader || ""}`.split(";")) { const trimmed = part.trim(); if (!trimmed) { continue; } const separatorIndex = trimmed.indexOf("="); if (separatorIndex <= 0) { continue; } const key = trimmed.slice(0, separatorIndex).trim(); const value = trimmed.slice(separatorIndex + 1).trim(); cookies[key] = value; } return cookies; } function normalizeManagementAccessKey(value) { return `${value || ""}`.trim(); } function currentManagementAccessKey(config) { return normalizeManagementAccessKey(config?.management_access_key); } function managementAccessEnabled(config) { return Boolean(currentManagementAccessKey(config)); } function requestManagementAccessKey(req, requestUrl) { const headerKey = firstNonEmptyString( req.headers["x-codex-retry-gateway-key"], req.headers["x-codex-retry-gateway-access-key"], req.headers.authorization?.startsWith?.("Bearer ") ? req.headers.authorization.slice("Bearer ".length) : null, ); if (headerKey) { return headerKey; } const queryKey = firstNonEmptyString( requestUrl?.searchParams?.get("key"), requestUrl?.searchParams?.get("access_key"), ); if (queryKey) { return queryKey; } const cookies = parseCookieHeader(req.headers.cookie || ""); return firstNonEmptyString(cookies[MANAGEMENT_ACCESS_COOKIE]); } function hasManagementAccess(req, requestUrl, config) { const expected = currentManagementAccessKey(config); if (!expected) { return true; } const provided = requestManagementAccessKey(req, requestUrl); return Boolean(provided) && timingSafeEquals(provided, expected); } function clearManagementAccessCookieHeaders() { return [ `${MANAGEMENT_ACCESS_COOKIE}=; Path=${ADMIN_BASE_PATH}; HttpOnly; SameSite=Lax; Max-Age=0`, ]; } function buildManagementAccessCookieHeaders(config, accessKey) { const expected = currentManagementAccessKey(config); if (!expected || !accessKey || !timingSafeEquals(accessKey, expected)) { return clearManagementAccessCookieHeaders(); } return [ `${MANAGEMENT_ACCESS_COOKIE}=${accessKey}; Path=${ADMIN_BASE_PATH}; HttpOnly; SameSite=Lax`, ]; } function managementUnauthorizedPayload() { return { error: { message: "management access key required", code: "management_access_key_required", }, }; } function extractStringByPointers(payload, pointers) { for (const pointer of pointers) { const raw = jsonPointerGet(payload, pointer); if (typeof raw !== "string") { continue; } const trimmed = raw.trim(); if (trimmed) { return trimmed; } } return null; } function extractRequestThreadId(payload) { return extractStringByPointers(payload, REQUEST_THREAD_ID_POINTERS); } function extractRequestReasoningEffort(payload) { return extractStringByPointers(payload, REQUEST_REASONING_EFFORT_POINTERS); } function extractRequestReasoningSummary(payload) { return extractStringByPointers(payload, REQUEST_REASONING_SUMMARY_POINTERS); } function extractResponseThreadId(payload) { return extractStringByPointers(payload, RESPONSE_THREAD_ID_POINTERS); } function extractHeaderThreadId(headers) { if (!headers || typeof headers !== "object") { return null; } const candidates = [ "thread-id", "x-client-request-id", "conversation_id", "conversation-id", "session_id", "session-id", "x-codex-parent-thread-id", ]; for (const key of candidates) { const raw = headers[key]; if (Array.isArray(raw)) { for (const item of raw) { const text = firstNonEmptyString(item); if (text) { return text; } } continue; } const text = firstNonEmptyString(raw); if (text) { return text; } } return null; } function extractNonStreamingResponseId(payload) { return extractStringByPointers(payload, NON_STREAM_RESPONSE_ID_POINTERS); } function extractStreamingResponseId(payload) { return extractStringByPointers(payload, STREAM_RESPONSE_ID_POINTERS); } function normalizeUsageSnapshot(payload) { const usage = payload?.usage || payload?.response?.usage || null; if (!usage || typeof usage !== "object") { return null; } const inputTokens = firstInteger(usage.input_tokens, usage.prompt_tokens); const outputTokens = firstInteger(usage.output_tokens, usage.completion_tokens); const totalTokens = firstInteger( usage.total_tokens, inputTokens !== null && outputTokens !== null ? inputTokens + outputTokens : null, ); const reasoningTokens = firstInteger( usage.output_tokens_details?.reasoning_tokens, usage.completion_tokens_details?.reasoning_tokens, payload?.response?.usage?.output_tokens_details?.reasoning_tokens, payload?.response?.usage?.completion_tokens_details?.reasoning_tokens, ); const cachedTokens = firstInteger( usage.input_tokens_details?.cached_tokens, usage.prompt_tokens_details?.cached_tokens, payload?.response?.usage?.input_tokens_details?.cached_tokens, payload?.response?.usage?.prompt_tokens_details?.cached_tokens, ); if ( inputTokens === null && outputTokens === null && totalTokens === null && reasoningTokens === null && cachedTokens === null ) { return null; } return { input_tokens: inputTokens, output_tokens: outputTokens, total_tokens: totalTokens, reasoning_tokens: reasoningTokens, cached_tokens: cachedTokens, }; } function mergeUsageSnapshots(current, next) { if (!next) { return current || null; } return { input_tokens: next.input_tokens ?? current?.input_tokens ?? null, output_tokens: next.output_tokens ?? current?.output_tokens ?? null, total_tokens: next.total_tokens ?? current?.total_tokens ?? null, reasoning_tokens: next.reasoning_tokens ?? current?.reasoning_tokens ?? null, cached_tokens: next.cached_tokens ?? current?.cached_tokens ?? null, }; } function sumUsageSnapshots(current, next) { if (!next) { return current || null; } const result = { ...(current || {}) }; for (const key of ["input_tokens", "output_tokens", "total_tokens", "reasoning_tokens", "cached_tokens"]) { const nextValue = next[key]; if (!Number.isInteger(nextValue)) { continue; } result[key] = (Number.isInteger(result[key]) ? result[key] : 0) + nextValue; } return Object.keys(result).length > 0 ? result : null; } function denormalizeUsageSnapshotToResponsesUsage(usage) { if (!usage || typeof usage !== "object") { return null; } const payload = {}; if (Number.isInteger(usage.input_tokens)) { payload.input_tokens = usage.input_tokens; } if (Number.isInteger(usage.output_tokens)) { payload.output_tokens = usage.output_tokens; } if (Number.isInteger(usage.total_tokens)) { payload.total_tokens = usage.total_tokens; } else if (Number.isInteger(usage.input_tokens) && Number.isInteger(usage.output_tokens)) { payload.total_tokens = usage.input_tokens + usage.output_tokens; } if (Number.isInteger(usage.cached_tokens)) { payload.input_tokens_details = { cached_tokens: usage.cached_tokens, }; } if (Number.isInteger(usage.reasoning_tokens)) { payload.output_tokens_details = { reasoning_tokens: usage.reasoning_tokens, }; } return Object.keys(payload).length > 0 ? payload : null; } function cloneJsonLike(value) { if (value === null || value === undefined) { return value; } return JSON.parse(JSON.stringify(value)); } function buildSseBlock(eventName, payloadText) { const lines = []; if (`${eventName || ""}`.trim()) { lines.push(`event: ${`${eventName}`.trim()}`); } lines.push(`data: ${payloadText}`); return Buffer.from(`${lines.join("\n")}\n\n`); } const RESPONSES_MODEL_HEADER_NAMES = new Set(["openai-model", "x-openai-model"]); function normalizeResponsesHeaderSubset(...sources) { const headers = {}; for (const source of sources) { if (!source || typeof source !== "object" || Array.isArray(source)) { continue; } for (const [key, value] of Object.entries(source)) { if (!RESPONSES_MODEL_HEADER_NAMES.has(`${key}`.toLowerCase())) { continue; } headers[key] = cloneJsonLike(value); } } return Object.keys(headers).length > 0 ? headers : null; } function createEmptySseInspectionResult() { return { reasoning: null, usage: null, response_id: null, thread_id: null, retryable_upstream_error: null, }; } function mergeSseInspectionResult(target, next) { if (Number.isInteger(next.reasoning)) { target.reasoning = next.reasoning; } target.usage = mergeUsageSnapshots(target.usage, next.usage); target.response_id = target.response_id || next.response_id || null; target.thread_id = target.thread_id || next.thread_id || null; target.retryable_upstream_error = next.retryable_upstream_error || target.retryable_upstream_error || null; return target; } function inspectSseBlocks(blocks, config) { const result = createEmptySseInspectionResult(); for (const block of blocks) { const lines = `${block || ""}` .split(/\r?\n/) .map((line) => line.trimEnd()) .filter(Boolean); const eventName = lines .filter((line) => line.startsWith("event:")) .map((line) => line.replace(/^event:\s?/, "").trim()) .find(Boolean) || ""; const dataLines = lines .filter((line) => line.startsWith("data:")) .map((line) => line.replace(/^data:\s?/, "")); if (dataLines.length === 0) { continue; } const payloadText = dataLines.join("\n"); if (payloadText === "[DONE]") { continue; } let parsed = null; try { parsed = JSON.parse(payloadText); const reasoning = extractReasoningTokens(parsed); if (reasoning !== null) { result.reasoning = reasoning; } result.usage = mergeUsageSnapshots(result.usage, normalizeUsageSnapshot(parsed)); result.response_id = result.response_id || extractStreamingResponseId(parsed) || extractNonStreamingResponseId(parsed); result.thread_id = result.thread_id || extractResponseThreadId(parsed); } catch { // ignore malformed SSE payloads } const retryableUpstreamError = findRetryableStreamErrorMatch( config, parsed, payloadText, eventName, ); if (retryableUpstreamError) { result.retryable_upstream_error = retryableUpstreamError; } } return result; } function flushSseInspectionRemainder(state, config) { if (!state?.buffer || !state.buffer.trim()) { state.buffer = ""; return createEmptySseInspectionResult(); } const remainder = state.buffer; state.buffer = ""; return inspectSseBlocks([remainder], config); } function createResponsesCodexSseState() { return { decoder: new TextDecoder("utf8"), buffer: "", saw_response_completed: false, saw_terminal_failure: false, response_id: null, usage: null, }; } function normalizeResponsesFailedPayloadForCodex(parsed, eventName = "", fallbackResponseId = null) { const eventType = firstNonEmptyString(parsed?.type, eventName); if (eventType !== "error" && eventType !== "response.failed") { return null; } const normalized = cloneJsonLike(parsed) || {}; normalized.type = "response.failed"; if (!normalized.response || typeof normalized.response !== "object" || Array.isArray(normalized.response)) { normalized.response = {}; } if (!firstNonEmptyString(normalized.response.status)) { normalized.response.status = "failed"; } const responseId = firstNonEmptyString( normalized.response.id, normalized.id, fallbackResponseId, ); if (responseId && !firstNonEmptyString(normalized.response.id)) { normalized.response.id = responseId; } if (!normalized.response.error || typeof normalized.response.error !== "object" || Array.isArray(normalized.response.error)) { if (normalized.error && typeof normalized.error === "object" && !Array.isArray(normalized.error)) { normalized.response.error = cloneJsonLike(normalized.error); } else { const fallbackError = {}; const message = firstNonEmptyString(normalized.message, normalized.error_message); const code = firstNonEmptyString(normalized.code, normalized.error_code); const errorType = firstNonEmptyString(normalized.error_type, normalized.type); if (message) { fallbackError.message = message; } if (code) { fallbackError.code = code; } if (errorType) { fallbackError.type = errorType; } if (Object.keys(fallbackError).length > 0) { normalized.response.error = fallbackError; } } } for (const key of ["usage", "output", "incomplete_details", "metadata"]) { if (normalized.response[key] === undefined && normalized[key] !== undefined) { normalized.response[key] = cloneJsonLike(normalized[key]); } } return normalized; } function normalizeResponsesLifecyclePayloadForCodex(parsed, eventName = "", fallbackResponseId = null) { const eventType = firstNonEmptyString(parsed?.type, eventName); if ( eventType !== "response.created" && eventType !== "response.in_progress" && eventType !== "response.completed" && eventType !== "response.done" ) { return null; } const normalizedType = eventType === "response.done" ? "response.completed" : eventType; const normalized = { type: normalizedType, response: {}, }; const responseId = firstNonEmptyString( parsed?.response?.id, parsed?.id, fallbackResponseId, ); if (responseId) { normalized.response.id = responseId; } const status = firstNonEmptyString(parsed?.response?.status); if (status) { normalized.response.status = status; } const headers = normalizeResponsesHeaderSubset(parsed?.response?.headers, parsed?.headers); if (headers) { normalized.response.headers = headers; } if (normalizedType === "response.completed") { const usage = cloneJsonLike(parsed?.response?.usage ?? parsed?.usage); if (usage !== undefined) { normalized.response.usage = usage; } if (parsed?.response?.end_turn !== undefined) { normalized.response.end_turn = parsed.response.end_turn; } else if (parsed?.end_turn !== undefined) { normalized.response.end_turn = parsed.end_turn; } } return normalized; } function normalizeResponsesCompletedPayloadForCodex(parsed, eventName = "", fallbackResponseId = null) { const eventType = firstNonEmptyString(parsed?.type, eventName); if (eventType !== "response.done" && eventType !== "response.completed") { return null; } return normalizeResponsesLifecyclePayloadForCodex(parsed, eventName, fallbackResponseId); } function processResponsesSseBlockForCodex(state, blockText, fallbackResponseId = null) { const lines = `${blockText || ""}` .split(/\r?\n/) .map((line) => line.trimEnd()); const eventName = lines .filter((line) => line.startsWith("event:")) .map((line) => line.replace(/^event:\s?/, "").trim()) .find(Boolean) || ""; const dataLines = lines .filter((line) => line.startsWith("data:")) .map((line) => line.replace(/^data:\s?/, "")); if (dataLines.length === 0) { return Buffer.from(`${lines.join("\n")}\n\n`); } const payloadText = dataLines.join("\n"); if (payloadText === "[DONE]") { return buildSseBlock(eventName, payloadText); } let parsed = null; try { parsed = JSON.parse(payloadText); } catch { return Buffer.from(`${lines.join("\n")}\n\n`); } state.usage = mergeUsageSnapshots(state.usage, normalizeUsageSnapshot(parsed)); state.response_id = state.response_id || extractStreamingResponseId(parsed) || extractNonStreamingResponseId(parsed) || firstNonEmptyString(parsed?.response?.id, fallbackResponseId); let rewritten = normalizeResponsesLifecyclePayloadForCodex( parsed, eventName, state.response_id || fallbackResponseId, ); if (!rewritten) { rewritten = normalizeResponsesCompletedPayloadForCodex(parsed, eventName, state.response_id || fallbackResponseId); } if (!rewritten) { rewritten = normalizeResponsesFailedPayloadForCodex(parsed, eventName, state.response_id || fallbackResponseId); } const outputPayload = rewritten || parsed; const outputEventName = firstNonEmptyString(outputPayload?.type, eventName); state.response_id = state.response_id || extractStreamingResponseId(outputPayload) || extractNonStreamingResponseId(outputPayload) || firstNonEmptyString(outputPayload?.response?.id, fallbackResponseId); state.usage = mergeUsageSnapshots(state.usage, normalizeUsageSnapshot(outputPayload)); if (outputEventName === "response.completed") { state.saw_response_completed = true; } if (outputEventName === "response.failed" || outputEventName === "response.incomplete") { state.saw_terminal_failure = true; } if (!rewritten) { return Buffer.from(`${lines.join("\n")}\n\n`); } return buildSseBlock(outputEventName, JSON.stringify(outputPayload)); } function drainResponsesSseForCodex(state, chunk, fallbackResponseId = null) { const decoded = state.decoder.decode(chunk, { stream: true }); state.buffer += decoded; const blocks = state.buffer.split(/\r?\n\r?\n/); state.buffer = blocks.pop() ?? ""; return blocks .filter((block) => block.length > 0) .map((block) => processResponsesSseBlockForCodex(state, block, fallbackResponseId)) .filter((chunkBuffer) => chunkBuffer && chunkBuffer.length > 0); } function flushResponsesSseForCodex(state, fallbackResponseId = null, fallbackUsage = null) { const flushed = state.decoder.decode(); if (flushed) { state.buffer += flushed; } const outputs = []; if (state.buffer.trim()) { outputs.push(processResponsesSseBlockForCodex(state, state.buffer, fallbackResponseId)); } state.buffer = ""; if (!state.saw_response_completed && !state.saw_terminal_failure) { const responseId = firstNonEmptyString(state.response_id, fallbackResponseId); if (responseId) { const usage = denormalizeUsageSnapshotToResponsesUsage(mergeUsageSnapshots(state.usage, fallbackUsage)); const payload = { type: "response.completed", response: { id: responseId, }, }; if (usage) { payload.response.usage = usage; } outputs.push(buildSseBlock("response.completed", JSON.stringify(payload))); state.saw_response_completed = true; } } return outputs.filter((chunkBuffer) => chunkBuffer && chunkBuffer.length > 0); } function normalizeIntegerList(values, fallback = []) { const source = values === undefined || values === null ? fallback : values; const normalized = flattenValues(source) .flatMap((value) => { if (typeof value === "string") { return value.split(/[\s,]+/).filter(Boolean); } return [value]; }) .map((value) => Number.parseInt(`${value}`, 10)) .filter((value) => Number.isInteger(value)); return [...new Set(normalized)]; } function normalizeStringList(values, fallback = []) { const source = values === undefined || values === null ? fallback : values; const normalized = flattenValues(source) .flatMap((value) => `${value ?? ""}`.split(/[\s,]+/)) .map((value) => value.trim()) .filter(Boolean); return [...new Set(normalized)]; } function normalizePhraseList(values, fallback = []) { const source = values === undefined || values === null ? fallback : values; const normalized = flattenValues(source) .flatMap((value) => { if (typeof value === "string") { return expandEscapedLineBreaks(value).split(/\r?\n/); } return [value]; }) .map((value) => `${value ?? ""}`.trim()) .filter(Boolean); return [...new Set(normalized)]; } function normalizeReasoningMatchMode(value) { const mode = `${value || DEFAULT_CONFIG.reasoning_match_mode}`.trim().toLowerCase(); if (["formula_518n_minus_2", "manual"].includes(mode)) { return mode; } return DEFAULT_CONFIG.reasoning_match_mode; } function normalizeReasoningEquals(values, fallback = DEFAULT_CONFIG.reasoning_equals) { const normalized = normalizeIntegerList(values, fallback); return normalized.length > 0 ? normalized : [...fallback]; } function normalizePositiveInteger(value, fallback) { const parsed = Number.parseInt(`${value ?? ""}`, 10); if (Number.isInteger(parsed) && parsed > 0) { return parsed; } return fallback; } function normalizeNonNegativeInteger(value, fallback) { const parsed = Number.parseInt(`${value ?? ""}`, 10); if (Number.isInteger(parsed) && parsed >= 0) { return parsed; } return fallback; } function sleep(ms) { if (!Number.isFinite(ms) || ms <= 0) { return Promise.resolve(); } return new Promise((resolve) => setTimeout(resolve, ms)); } function parseModelRemapMap(value) { const map = {}; for (const rawEntry of `${value || ""}`.split(/\r?\n|[;,]/)) { const entry = rawEntry.trim(); if (!entry) { continue; } const separatorIndex = entry.indexOf("="); if (separatorIndex <= 0) { continue; } const from = entry.slice(0, separatorIndex).trim(); const to = entry.slice(separatorIndex + 1).trim(); if (!from || !to) { continue; } map[from] = to; } return map; } function canHotSwapProfile(currentConfig, nextConfig) { return ( `${currentConfig?.listen_host || ""}` === `${nextConfig?.listen_host || ""}` && Number.parseInt(`${currentConfig?.listen_port || ""}`, 10) === Number.parseInt(`${nextConfig?.listen_port || ""}`, 10) ); } function parseEnvText(content) { const values = {}; for (const rawLine of `${content || ""}`.split(/\r?\n/)) { const line = rawLine.trim(); if (!line || line.startsWith("#")) { continue; } const separatorIndex = line.indexOf("="); if (separatorIndex <= 0) { continue; } const key = line.slice(0, separatorIndex).trim(); let value = line.slice(separatorIndex + 1).trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } values[key] = value; } return values; } function isSecretLikeKey(key) { return /TOKEN|SECRET|PASSWORD|API_KEY|AUTH|BEARER|KEY/i.test(`${key || ""}`); } function redactProfileValue(key, value) { if (!value) { return ""; } if (isSecretLikeKey(key) || /^sk-[A-Za-z0-9_-]+/.test(value)) { return "[configured]"; } return value; } function getProfileNameFromFile(fileName) { if (!fileName.endsWith(".env")) { return null; } const profileName = fileName.slice(0, -4); return /^[A-Za-z0-9_.-]+$/.test(profileName) ? profileName : null; } function validateProfileName(profileName) { if (!/^[A-Za-z0-9_.-]+$/.test(profileName)) { throw new Error("profile 名称只能包含字母、数字、下划线、点和短横线"); } } function buildBlockedBody(pathname, reasoning, statusCode) { return JSON.stringify({ error: { message: `codex retry gateway blocked suspicious reasoning response on ${pathname}`, type: "codex_retry_gateway", code: "reasoning_guard_triggered", reasoning_tokens: reasoning, status_code: statusCode, }, }); } function buildRetryableUpstreamErrorBody(pathname, upstreamStatusCode, upstreamMessage, statusCode) { return JSON.stringify({ error: { message: `codex retry gateway converted retryable upstream error on ${pathname}`, type: "codex_retry_gateway", code: "upstream_error_retry_triggered", upstream_status_code: upstreamStatusCode, upstream_error_message: upstreamMessage, status_code: statusCode, }, }); } function buildGatewayErrorBody(message) { return JSON.stringify({ error: { message, type: "codex_retry_gateway_error", code: "gateway_error", }, }); } function createMonitor() { return { started_at: new Date().toISOString(), persistent_since: null, next_log_seq: 1, next_request_seq: 1, log_entries: [], request_entries: [], total_proxy_request_count: 0, inspected_response_count: 0, matched_response_count: 0, token_totals: { input_tokens: 0, output_tokens: 0, total_tokens: 0, reasoning_tokens: 0, cached_tokens: 0, }, observed_reasoning_counts: {}, }; } function buildLogEntry(seq, at, message) { return { seq, at, message, }; } function parseLogLine(line, seq) { const match = `${line || ""}`.match(/^(\S+)\s([\s\S]*)$/); if (!match) { return buildLogEntry(seq, null, line); } return buildLogEntry(seq, match[1], match[2]); } function parseJsonLine(line) { if (!line.trim()) { return null; } try { return JSON.parse(line); } catch { return null; } } async function readJsonlFile(filePath) { const text = await readOptionalText(filePath); if (!text) { return []; } return text .split(/\r?\n/) .map(parseJsonLine) .filter(Boolean); } async function appendJsonl(filePath, value) { await mkdir(path.dirname(filePath), { recursive: true }); await fs.promises.appendFile(filePath, `${JSON.stringify(value)}\n`, "utf8"); } function openRequestsDatabase(dbPath) { fs.mkdirSync(path.dirname(dbPath), { recursive: true }); const db = new DatabaseSync(dbPath); db.exec(` PRAGMA journal_mode = WAL; CREATE TABLE IF NOT EXISTS requests ( seq INTEGER PRIMARY KEY, request_id TEXT, response_id TEXT, thread_id TEXT, started_at TEXT, finished_at TEXT, duration_ms INTEGER, profile_name TEXT, method TEXT, path TEXT, model TEXT, requested_model TEXT, forwarded_model TEXT, reasoning_effort TEXT, reasoning_summary TEXT, request_stream INTEGER, response_stream INTEGER, inspected INTEGER, matched INTEGER, status_code INTEGER, upstream_status_code INTEGER, reasoning_tokens INTEGER, input_tokens INTEGER, output_tokens INTEGER, total_tokens INTEGER, cached_tokens INTEGER, error TEXT, upstream_origin TEXT, upstream_path TEXT, upstream_auth_mode TEXT, upstream_auth_source TEXT, payload_json TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_requests_started_at ON requests(started_at DESC); CREATE INDEX IF NOT EXISTS idx_requests_profile_name ON requests(profile_name); CREATE INDEX IF NOT EXISTS idx_requests_status_code ON requests(status_code); CREATE INDEX IF NOT EXISTS idx_requests_matched ON requests(matched); CREATE INDEX IF NOT EXISTS idx_requests_response_stream ON requests(response_stream); `); const requestColumns = db.prepare("PRAGMA table_info(requests)").all(); const requestColumnNames = new Set(requestColumns.map((column) => column.name)); if (!requestColumnNames.has("request_id")) { db.exec("ALTER TABLE requests ADD COLUMN request_id TEXT"); } if (!requestColumnNames.has("response_id")) { db.exec("ALTER TABLE requests ADD COLUMN response_id TEXT"); } if (!requestColumnNames.has("thread_id")) { db.exec("ALTER TABLE requests ADD COLUMN thread_id TEXT"); } if (!requestColumnNames.has("reasoning_effort")) { db.exec("ALTER TABLE requests ADD COLUMN reasoning_effort TEXT"); } if (!requestColumnNames.has("reasoning_summary")) { db.exec("ALTER TABLE requests ADD COLUMN reasoning_summary TEXT"); } db.exec("CREATE INDEX IF NOT EXISTS idx_requests_request_id ON requests(request_id)"); db.exec("CREATE INDEX IF NOT EXISTS idx_requests_response_id ON requests(response_id)"); db.exec("CREATE INDEX IF NOT EXISTS idx_requests_thread_id ON requests(thread_id)"); db.exec("CREATE INDEX IF NOT EXISTS idx_requests_reasoning_effort ON requests(reasoning_effort)"); return db; } function boolToInt(value) { return value ? 1 : 0; } function usageField(entry, key) { const value = entry?.usage?.[key]; return Number.isInteger(value) ? value : null; } function buildPersistedRequestPayload(entry) { return Object.fromEntries( Object.entries(entry || {}).filter(([key]) => !key.startsWith("_")), ); } function computeRequestId(pathname, rawBody) { const hash = createHash("sha256"); hash.update(normalizePath(pathname)); hash.update("\n"); hash.update(Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(rawBody || "")); return `req_${hash.digest("hex").slice(0, 16)}`; } function requestRowFromEntry(entry) { const payload = buildPersistedRequestPayload(entry); return { seq: entry.seq, request_id: entry.request_id || null, response_id: entry.response_id || null, thread_id: entry.thread_id || null, started_at: entry.started_at || null, finished_at: entry.finished_at || null, duration_ms: Number.isInteger(entry.duration_ms) ? entry.duration_ms : null, profile_name: entry.profile_name || null, method: entry.method || null, path: entry.path || null, model: entry.model || null, requested_model: entry.requested_model || null, forwarded_model: entry.forwarded_model || null, reasoning_effort: entry.reasoning_effort || null, reasoning_summary: entry.reasoning_summary || null, request_stream: boolToInt(Boolean(entry.request_stream)), response_stream: boolToInt(Boolean(entry.response_stream)), inspected: boolToInt(Boolean(entry.inspected)), matched: boolToInt(Boolean(entry.matched)), status_code: Number.isInteger(entry.status_code) ? entry.status_code : null, upstream_status_code: Number.isInteger(entry.upstream_status_code) ? entry.upstream_status_code : null, reasoning_tokens: Number.isInteger(entry.reasoning_tokens) ? entry.reasoning_tokens : usageField(entry, "reasoning_tokens"), input_tokens: usageField(entry, "input_tokens"), output_tokens: usageField(entry, "output_tokens"), total_tokens: usageField(entry, "total_tokens"), cached_tokens: usageField(entry, "cached_tokens"), error: entry.error || null, upstream_origin: entry.upstream?.origin || null, upstream_path: entry.upstream?.path || null, upstream_auth_mode: entry.upstream?.auth_mode || null, upstream_auth_source: entry.upstream?.auth_source || null, payload_json: JSON.stringify(payload), }; } function insertRequestRow(db, row) { db.prepare(` INSERT OR REPLACE INTO requests ( seq, request_id, response_id, thread_id, started_at, finished_at, duration_ms, profile_name, method, path, model, requested_model, forwarded_model, reasoning_effort, reasoning_summary, request_stream, response_stream, inspected, matched, status_code, upstream_status_code, reasoning_tokens, input_tokens, output_tokens, total_tokens, cached_tokens, error, upstream_origin, upstream_path, upstream_auth_mode, upstream_auth_source, payload_json ) VALUES ( @seq, @request_id, @response_id, @thread_id, @started_at, @finished_at, @duration_ms, @profile_name, @method, @path, @model, @requested_model, @forwarded_model, @reasoning_effort, @reasoning_summary, @request_stream, @response_stream, @inspected, @matched, @status_code, @upstream_status_code, @reasoning_tokens, @input_tokens, @output_tokens, @total_tokens, @cached_tokens, @error, @upstream_origin, @upstream_path, @upstream_auth_mode, @upstream_auth_source, @payload_json ) `).run(row); } async function importRequestsJsonlToDb(db, filePath) { const rows = await readJsonlFile(filePath); if (rows.length === 0) { return 0; } const countRow = db.prepare("SELECT COUNT(*) AS count FROM requests").get(); if ((countRow?.count || 0) >= rows.length) { return 0; } db.exec("BEGIN"); try { for (const entry of rows) { if (!Number.isInteger(entry?.seq)) { continue; } insertRequestRow(db, requestRowFromEntry(entry)); } db.exec("COMMIT"); } catch (error) { try { db.exec("ROLLBACK"); } catch { // Ignore rollback failures so the original insert error is preserved. } throw error; } return rows.length; } function parseRequestRowPayload(row) { if (!row?.payload_json) { return null; } try { return JSON.parse(row.payload_json); } catch { return null; } } function buildRequestQueryFilters({ query, filter }) { const clauses = []; const params = {}; const trimmedQuery = `${query || ""}`.trim().toLowerCase(); if (trimmedQuery) { clauses.push(` ( lower(coalesce(profile_name, '')) LIKE @query OR lower(coalesce(method, '')) LIKE @query OR lower(coalesce(path, '')) LIKE @query OR lower(coalesce(request_id, '')) LIKE @query OR lower(coalesce(response_id, '')) LIKE @query OR lower(coalesce(thread_id, '')) LIKE @query OR lower(coalesce(model, '')) LIKE @query OR lower(coalesce(requested_model, '')) LIKE @query OR lower(coalesce(forwarded_model, '')) LIKE @query OR lower(coalesce(reasoning_effort, '')) LIKE @query OR lower(coalesce(reasoning_summary, '')) LIKE @query OR lower(coalesce(error, '')) LIKE @query OR lower(coalesce(upstream_origin, '')) LIKE @query OR lower(coalesce(upstream_path, '')) LIKE @query OR CAST(coalesce(status_code, '') AS TEXT) LIKE @query OR CAST(coalesce(upstream_status_code, '') AS TEXT) LIKE @query OR CAST(coalesce(input_tokens, '') AS TEXT) LIKE @query OR CAST(coalesce(output_tokens, '') AS TEXT) LIKE @query OR CAST(coalesce(total_tokens, '') AS TEXT) LIKE @query OR CAST(coalesce(cached_tokens, '') AS TEXT) LIKE @query OR CAST(coalesce(reasoning_tokens, '') AS TEXT) LIKE @query ) `); params.query = `%${trimmedQuery}%`; } if (filter === "matched") { clauses.push("matched = 1"); } else if (filter === "stream") { clauses.push("response_stream = 1"); } else if (filter === "error") { clauses.push("(error IS NOT NULL AND error != '')"); } return { whereSql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "", params, }; } async function hydrateMonitorFromDisk(monitor, paths, requestHistoryLimit, requestsDb = null) { monitor.request_entries = []; if (requestsDb) { const totalsRow = requestsDb.prepare(` SELECT COUNT(*) AS total_proxy_request_count, COALESCE(SUM(CASE WHEN inspected = 1 THEN 1 ELSE 0 END), 0) AS inspected_response_count, COALESCE(SUM(CASE WHEN matched = 1 THEN 1 ELSE 0 END), 0) AS matched_response_count, COALESCE(SUM(COALESCE(input_tokens, 0)), 0) AS input_tokens, COALESCE(SUM(COALESCE(output_tokens, 0)), 0) AS output_tokens, COALESCE(SUM(COALESCE(total_tokens, 0)), 0) AS total_tokens, COALESCE(SUM(COALESCE(reasoning_tokens, 0)), 0) AS reasoning_tokens, COALESCE(SUM(COALESCE(cached_tokens, 0)), 0) AS cached_tokens, MIN(NULLIF(started_at, '')) AS persistent_since, MAX(seq) AS max_seq FROM requests `).get(); monitor.total_proxy_request_count = totalsRow?.total_proxy_request_count || 0; monitor.inspected_response_count = totalsRow?.inspected_response_count || 0; monitor.matched_response_count = totalsRow?.matched_response_count || 0; monitor.token_totals = { input_tokens: totalsRow?.input_tokens || 0, output_tokens: totalsRow?.output_tokens || 0, total_tokens: totalsRow?.total_tokens || 0, reasoning_tokens: totalsRow?.reasoning_tokens || 0, cached_tokens: totalsRow?.cached_tokens || 0, }; monitor.persistent_since = totalsRow?.persistent_since || null; monitor.next_request_seq = Number.isInteger(totalsRow?.max_seq) ? totalsRow.max_seq + 1 : 1; monitor.observed_reasoning_counts = {}; const reasoningRows = requestsDb.prepare(` SELECT reasoning_tokens, COUNT(*) AS count FROM requests WHERE reasoning_tokens IS NOT NULL GROUP BY reasoning_tokens `).all(); for (const row of reasoningRows) { if (!Number.isInteger(row?.reasoning_tokens)) { continue; } monitor.observed_reasoning_counts[`${row.reasoning_tokens}`] = row.count || 0; } const persistedRows = requestsDb.prepare("SELECT payload_json FROM requests").all(); for (const row of persistedRows) { const entry = parseRequestRowPayload(row); if (!entry) { continue; } applyPersistedRetryExtras(monitor, entry); } return; } const requestEntries = await readJsonlFile(paths.requestsPath); monitor.next_request_seq = requestEntries.reduce((maxSeq, entry) => { return Math.max(maxSeq, Number.isInteger(entry.seq) ? entry.seq + 1 : maxSeq); }, 1); for (const entry of requestEntries) { monitor.total_proxy_request_count += 1; if (entry.inspected) { recordInspectedResponse(monitor, entry.reasoning_tokens ?? entry.usage?.reasoning_tokens ?? null, Boolean(entry.matched)); } applyPersistedRetryExtras(monitor, entry); addTokenTotals(monitor, entry.usage); } const firstStartedAt = requestEntries .map((entry) => `${entry?.started_at || ""}`.trim()) .filter(Boolean) .sort()[0]; monitor.persistent_since = firstStartedAt || null; } function createMonitorRecorder(monitor) { return (message) => { const entry = { seq: monitor.next_log_seq, at: new Date().toISOString(), message, }; monitor.next_log_seq += 1; monitor.log_entries.push(entry); return entry; }; } function createLogger(logPath, recordEntry) { if (!logPath) { return (message) => { const entry = recordEntry ? recordEntry(message) : { at: new Date().toISOString(), message }; process.stdout.write(`${entry.at} ${entry.message}\n`); }; } const stream = fs.createWriteStream(logPath, { flags: "a" }); return (message) => { const entry = recordEntry ? recordEntry(message) : { at: new Date().toISOString(), message }; const line = `${entry.at} ${entry.message}\n`; stream.write(line); process.stdout.write(line); }; } function incrementReasoningCount(counter, reasoning) { if (!Number.isInteger(reasoning)) { return; } const key = `${reasoning}`; counter[key] = (counter[key] || 0) + 1; } function recordInspectedResponse(monitor, reasoning, matched) { monitor.inspected_response_count += 1; incrementReasoningCount(monitor.observed_reasoning_counts, reasoning); if (matched) { monitor.matched_response_count += 1; } } function applyPersistedRetryExtras(monitor, entry) { const extraInspectedCount = Number.isInteger(entry?.reasoning_retry_extra_inspected_count) ? entry.reasoning_retry_extra_inspected_count : 0; const extraMatchedCount = Number.isInteger(entry?.reasoning_retry_extra_matched_count) ? entry.reasoning_retry_extra_matched_count : 0; monitor.inspected_response_count += extraInspectedCount; monitor.matched_response_count += extraMatchedCount; const extraReasoningCounts = entry?.reasoning_retry_extra_reasoning_counts; if (extraReasoningCounts && typeof extraReasoningCounts === "object") { for (const [reasoningKey, count] of Object.entries(extraReasoningCounts)) { const parsedReasoning = Number.parseInt(reasoningKey, 10); if (!Number.isInteger(parsedReasoning) || !Number.isInteger(count) || count <= 0) { continue; } monitor.observed_reasoning_counts[`${parsedReasoning}`] = ( monitor.observed_reasoning_counts[`${parsedReasoning}`] || 0 ) + count; } } addTokenTotals(monitor, entry?.reasoning_retry_extra_usage || null); } function addTokenTotals(monitor, usage) { if (!usage) { return; } for (const key of ["input_tokens", "output_tokens", "total_tokens", "reasoning_tokens", "cached_tokens"]) { const value = usage[key]; if (Number.isInteger(value)) { monitor.token_totals[key] += value; } } } function upsertRequestEntry(runtime, entry, { persistJsonl = false, includeUsage = false } = {}) { if (includeUsage) { addTokenTotals(runtime.monitor, entry.usage); addTokenTotals(runtime.monitor, entry.reasoning_retry_extra_usage || null); } if (runtime.requestsDb) { insertRequestRow(runtime.requestsDb, requestRowFromEntry(entry)); } if (persistJsonl) { appendJsonl(runtime.paths.requestsPath, buildPersistedRequestPayload(entry)).catch((error) => { runtime.logger?.(`[requests] failed to persist request seq=${entry.seq}: ${error?.message || error}`); }); } } function recordRequestEntry(runtime, entry, limit = DEFAULT_CONFIG.request_history_limit) { upsertRequestEntry(runtime, entry, { persistJsonl: true, includeUsage: true, }); } function markAndPersistFirstResponse(runtime, entry, at = new Date()) { if (!markRequestFirstResponse(entry, at)) { return false; } upsertRequestEntry(runtime, entry); return true; } function markRequestEntryFirstResponse(runtime, entry, { persistEntry = true, at = new Date() } = {}) { if (!markRequestFirstResponse(entry, at)) { return false; } if (persistEntry) { upsertRequestEntry(runtime, entry); } return true; } function summarizeProfileAuthSource(env, options = {}) { const prefix = options.prefix || "CODEX_RETRY_GATEWAY_UPSTREAM"; const defaultMode = options.defaultMode || "passthrough"; const mode = env[`${prefix}_AUTH_MODE`] || defaultMode; if (mode === "auth_json") { return env[`${prefix}_AUTH_JSON_PATH`] ? "auth.json path configured" : "~/.codex/auth.json"; } if (mode === "manual_bearer") { const secretPath = env[`${prefix}_AUTH_FILE`] || ""; if (!secretPath) { return "system secret file missing"; } return fs.existsSync(secretPath) ? "system secret file configured" : "system secret file missing"; } if (mode === "fixed_bearer") { if (env[`${prefix}_AUTH_FILE`]) { return "token file configured"; } if (env[`${prefix}_AUTH_ENV`]) { return `env:${env[`${prefix}_AUTH_ENV`]}`; } } return "passthrough"; } function summarizeImageProfileAuthSource(env) { return summarizeProfileAuthSource(env, { prefix: "CODEX_RETRY_GATEWAY_IMAGE", defaultMode: DEFAULT_CONFIG.image_auth_mode, }); } function buildProfileFormModel(env) { const manualSecretFile = env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_FILE || ""; return { listen_host: env.CODEX_RETRY_GATEWAY_LISTEN_HOST || "", listen_port: env.CODEX_RETRY_GATEWAY_LISTEN_PORT || "", upstream_base_url: env.CODEX_RETRY_GATEWAY_UPSTREAM_BASE_URL || "", auth_mode: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_MODE || "passthrough", auth_env: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_ENV || "", auth_file: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_MODE === "manual_bearer" ? "" : env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_FILE || "", manual_secret_file: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_MODE === "manual_bearer" ? manualSecretFile : "", manual_secret_configured: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_MODE === "manual_bearer" && Boolean(manualSecretFile) && fs.existsSync(manualSecretFile), auth_json_path: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_PATH || "", auth_json_key: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_KEY || "", request_history_limit: env.CODEX_RETRY_GATEWAY_REQUEST_HISTORY_LIMIT || `${DEFAULT_CONFIG.request_history_limit}`, model_remap: env.CODEX_RETRY_GATEWAY_MODEL_REMAP || "", reasoning_match_mode: normalizeReasoningMatchMode(env.CODEX_RETRY_GATEWAY_REASONING_MATCH_MODE), reasoning_equals: normalizeReasoningEquals( env.CODEX_RETRY_GATEWAY_REASONING_EQUALS || DEFAULT_CONFIG.reasoning_equals, DEFAULT_CONFIG.reasoning_equals, ).join(","), retryable_status_codes: env.CODEX_RETRY_GATEWAY_RETRYABLE_STATUS_CODES || "", retryable_error_messages: normalizePhraseList( env.CODEX_RETRY_GATEWAY_RETRYABLE_ERROR_MESSAGES || DEFAULT_CONFIG.retryable_error_messages, DEFAULT_CONFIG.retryable_error_messages, ), upstream_fetch_retry_attempts: env.CODEX_RETRY_GATEWAY_UPSTREAM_FETCH_RETRY_ATTEMPTS || `${DEFAULT_CONFIG.upstream_fetch_retry_attempts}`, upstream_fetch_retry_backoff_ms: env.CODEX_RETRY_GATEWAY_UPSTREAM_FETCH_RETRY_BACKOFF_MS || `${DEFAULT_CONFIG.upstream_fetch_retry_backoff_ms}`, endpoints: normalizeStringList(env.CODEX_RETRY_GATEWAY_ENDPOINTS || DEFAULT_CONFIG.endpoints, DEFAULT_CONFIG.endpoints), }; } function buildImageProfileFormModel(env) { const manualSecretFile = env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_FILE || ""; return { base_url: env.CODEX_RETRY_GATEWAY_IMAGE_BASE_URL || "", auth_mode: env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_MODE || DEFAULT_CONFIG.image_auth_mode, auth_env: env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_ENV || DEFAULT_CONFIG.image_auth_env, auth_file: env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_MODE === "manual_bearer" ? "" : manualSecretFile, manual_secret_file: env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_MODE === "manual_bearer" ? manualSecretFile : "", manual_secret_configured: env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_MODE === "manual_bearer" && Boolean(manualSecretFile) && fs.existsSync(manualSecretFile), auth_json_path: env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_PATH || "", auth_json_key: env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_KEY || DEFAULT_CONFIG.image_auth_json_key, }; } function imageConfigFromConfig(config) { return { image_profile_name: `${config?.image_profile_name || ""}`.trim(), image_base_url: `${config?.image_base_url || ""}`.trim(), image_auth_mode: config?.image_auth_mode || DEFAULT_CONFIG.image_auth_mode, image_auth_env: config?.image_auth_env || DEFAULT_CONFIG.image_auth_env, image_auth_file: config?.image_auth_file || "", image_auth_json_path: config?.image_auth_json_path || "", image_auth_json_key: config?.image_auth_json_key || DEFAULT_CONFIG.image_auth_json_key, }; } function buildImageConfigFromProfileEnv(imageProfileName, env) { const form = buildImageProfileFormModel(env); const baseUrl = `${form.base_url || ""}`.trim(); if (baseUrl) { try { new URL(baseUrl); } catch { throw new Error("图片上游 Base URL 必须是合法 URL"); } } return { image_profile_name: imageProfileName || "", image_base_url: baseUrl, image_auth_mode: normalizeAuthMode(form.auth_mode), image_auth_env: form.auth_env || DEFAULT_CONFIG.image_auth_env, image_auth_file: env.CODEX_RETRY_GATEWAY_IMAGE_AUTH_FILE || "", image_auth_json_path: form.auth_json_path || "", image_auth_json_key: form.auth_json_key || DEFAULT_CONFIG.image_auth_json_key, }; } function buildConfigFromProfileEnv(profileName, env, imageConfig = {}) { const reasoningMatchMode = normalizeReasoningMatchMode(env.CODEX_RETRY_GATEWAY_REASONING_MATCH_MODE); const config = { ...DEFAULT_CONFIG, ...imageConfigFromConfig(imageConfig), profile_name: profileName, listen_host: env.CODEX_RETRY_GATEWAY_LISTEN_HOST || DEFAULT_CONFIG.listen_host, listen_port: env.CODEX_RETRY_GATEWAY_LISTEN_PORT ? Number.parseInt(`${env.CODEX_RETRY_GATEWAY_LISTEN_PORT}`, 10) : DEFAULT_CONFIG.listen_port, upstream_base_url: env.CODEX_RETRY_GATEWAY_UPSTREAM_BASE_URL || "", upstream_auth_mode: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_MODE || DEFAULT_CONFIG.upstream_auth_mode, upstream_auth_env: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_ENV || DEFAULT_CONFIG.upstream_auth_env, upstream_auth_file: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_FILE || "", upstream_auth_json_path: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_PATH || "", upstream_auth_json_key: env.CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_KEY || DEFAULT_CONFIG.upstream_auth_json_key, request_body_limit_bytes: env.CODEX_RETRY_GATEWAY_REQUEST_BODY_LIMIT_BYTES ? Number.parseInt(`${env.CODEX_RETRY_GATEWAY_REQUEST_BODY_LIMIT_BYTES}`, 10) : DEFAULT_CONFIG.request_body_limit_bytes, request_history_limit: env.CODEX_RETRY_GATEWAY_REQUEST_HISTORY_LIMIT ? Number.parseInt(`${env.CODEX_RETRY_GATEWAY_REQUEST_HISTORY_LIMIT}`, 10) : DEFAULT_CONFIG.request_history_limit, model_remap: env.CODEX_RETRY_GATEWAY_MODEL_REMAP || "", endpoints: normalizeStringList(env.CODEX_RETRY_GATEWAY_ENDPOINTS || DEFAULT_CONFIG.endpoints, DEFAULT_CONFIG.endpoints).map(normalizePath), reasoning_match_mode: reasoningMatchMode, reasoning_equals: normalizeReasoningEquals( env.CODEX_RETRY_GATEWAY_REASONING_EQUALS || DEFAULT_CONFIG.reasoning_equals, DEFAULT_CONFIG.reasoning_equals, ), retryable_status_codes: normalizeIntegerList( env.CODEX_RETRY_GATEWAY_RETRYABLE_STATUS_CODES || DEFAULT_CONFIG.retryable_status_codes, DEFAULT_CONFIG.retryable_status_codes, ), retryable_error_messages: normalizePhraseList( env.CODEX_RETRY_GATEWAY_RETRYABLE_ERROR_MESSAGES || DEFAULT_CONFIG.retryable_error_messages, DEFAULT_CONFIG.retryable_error_messages, ), management_access_key: normalizeManagementAccessKey( env.CODEX_RETRY_GATEWAY_MANAGEMENT_ACCESS_KEY || DEFAULT_CONFIG.management_access_key, ), upstream_fetch_retry_attempts: env.CODEX_RETRY_GATEWAY_UPSTREAM_FETCH_RETRY_ATTEMPTS ? normalizePositiveInteger( env.CODEX_RETRY_GATEWAY_UPSTREAM_FETCH_RETRY_ATTEMPTS, DEFAULT_CONFIG.upstream_fetch_retry_attempts, ) : DEFAULT_CONFIG.upstream_fetch_retry_attempts, upstream_fetch_retry_backoff_ms: env.CODEX_RETRY_GATEWAY_UPSTREAM_FETCH_RETRY_BACKOFF_MS ? normalizeNonNegativeInteger( env.CODEX_RETRY_GATEWAY_UPSTREAM_FETCH_RETRY_BACKOFF_MS, DEFAULT_CONFIG.upstream_fetch_retry_backoff_ms, ) : DEFAULT_CONFIG.upstream_fetch_retry_backoff_ms, non_stream_status_code: env.CODEX_RETRY_GATEWAY_NON_STREAM_STATUS_CODE ? Number.parseInt(`${env.CODEX_RETRY_GATEWAY_NON_STREAM_STATUS_CODE}`, 10) : DEFAULT_CONFIG.non_stream_status_code, stream_action: env.CODEX_RETRY_GATEWAY_STREAM_ACTION || DEFAULT_CONFIG.stream_action, log_match: env.CODEX_RETRY_GATEWAY_LOG_MATCH === undefined ? DEFAULT_CONFIG.log_match : ["1", "true", "yes", "on"].includes(`${env.CODEX_RETRY_GATEWAY_LOG_MATCH}`.trim().toLowerCase()), health_path: env.CODEX_RETRY_GATEWAY_HEALTH_PATH || DEFAULT_CONFIG.health_path, }; config.model_remap_map = parseModelRemapMap(config.model_remap); return config; } function assertNoInlineProfileSecret(payload, fields = [ "auth_env", "auth_file", "auth_json_path", "auth_json_key", "upstream_base_url", ]) { for (const field of fields) { const value = `${payload?.[field] || ""}`.trim(); if (/^sk-[A-Za-z0-9_-]+/.test(value) || /Bearer\s+sk-[A-Za-z0-9_-]+/i.test(value)) { throw new Error("profile 不保存明文 sk 密钥;请改用 env/file/auth.json 引用"); } } } function defaultManualSecretPath(profileName) { const homeDir = process.env.HOME || ""; return path.join(homeDir, ".codex-retry-gateway", "secrets", `${profileName}.token`); } function defaultImageManualSecretPath(profileName) { const homeDir = process.env.HOME || ""; return path.join(homeDir, ".codex-retry-gateway", "secrets", `${profileName}.images.token`); } async function writeManualSecret(profileName, secretValue) { const text = `${secretValue || ""}`.trim(); if (!text) { return null; } const secretPath = defaultManualSecretPath(profileName); await mkdir(path.dirname(secretPath), { recursive: true, mode: 0o700 }); await writeFile(secretPath, `${text}\n`, { encoding: "utf8", mode: 0o600 }); await chmod(path.dirname(secretPath), 0o700).catch(() => {}); await chmod(secretPath, 0o600).catch(() => {}); return secretPath; } async function writeImageManualSecret(profileName, secretValue) { const text = `${secretValue || ""}`.trim(); if (!text) { return null; } const secretPath = defaultImageManualSecretPath(profileName); await mkdir(path.dirname(secretPath), { recursive: true, mode: 0o700 }); await writeFile(secretPath, `${text}\n`, { encoding: "utf8", mode: 0o600 }); await chmod(path.dirname(secretPath), 0o700).catch(() => {}); await chmod(secretPath, 0o600).catch(() => {}); return secretPath; } function serializeEnvValue(value) { const text = `${value ?? ""}`; if (/^[A-Za-z0-9_./:@?&=,+-]*$/.test(text)) { return text; } return JSON.stringify(text); } async function buildProfileEnvText(payload) { assertNoInlineProfileSecret(payload); const name = `${payload?.name || ""}`.trim(); validateProfileName(name); const listenPort = Number.parseInt(`${payload.listen_port || DEFAULT_CONFIG.listen_port}`, 10); if (!Number.isInteger(listenPort) || listenPort < 1 || listenPort > 65535) { throw new Error("监听端口必须是 1-65535 的整数"); } const upstreamBaseUrl = `${payload.upstream_base_url || ""}`.trim(); if (!upstreamBaseUrl) { throw new Error("上游 Base URL 不能为空"); } try { new URL(upstreamBaseUrl); } catch { throw new Error("上游 Base URL 必须是合法 URL"); } const authMode = normalizeAuthMode(payload.auth_mode); const reasoningMatchMode = normalizeReasoningMatchMode(payload.reasoning_match_mode); const reasoningEquals = normalizeReasoningEquals(payload.reasoning_equals, DEFAULT_CONFIG.reasoning_equals); const endpoints = normalizeStringList(payload.endpoints, DEFAULT_CONFIG.endpoints).map(normalizePath); if (endpoints.length === 0) { throw new Error("endpoints 不能为空"); } const requestHistoryLimit = Number.parseInt( `${payload.request_history_limit || DEFAULT_CONFIG.request_history_limit}`, 10, ); if (!Number.isInteger(requestHistoryLimit) || requestHistoryLimit < 0) { throw new Error("History Limit 必须是 0 或正整数;0 表示不裁剪"); } const retryableStatusCodes = normalizeIntegerList( payload.retryable_status_codes, DEFAULT_CONFIG.retryable_status_codes, ); const retryableErrorMessages = normalizePhraseList( payload.retryable_error_messages, DEFAULT_CONFIG.retryable_error_messages, ); const upstreamFetchRetryAttempts = normalizePositiveInteger( payload.upstream_fetch_retry_attempts, DEFAULT_CONFIG.upstream_fetch_retry_attempts, ); const upstreamFetchRetryBackoffMs = normalizeNonNegativeInteger( payload.upstream_fetch_retry_backoff_ms, DEFAULT_CONFIG.upstream_fetch_retry_backoff_ms, ); if (retryableStatusCodes.length === 0) { throw new Error("retryable_status_codes 不能为空"); } if (retryableErrorMessages.length === 0) { throw new Error("retryable_error_messages 不能为空"); } if (upstreamFetchRetryAttempts < 1) { throw new Error("upstream_fetch_retry_attempts 必须是正整数"); } if (upstreamFetchRetryBackoffMs < 0) { throw new Error("upstream_fetch_retry_backoff_ms 不能为负数"); } const envPairs = [ ["CODEX_RETRY_GATEWAY_LISTEN_HOST", `${payload.listen_host || DEFAULT_CONFIG.listen_host}`.trim()], ["CODEX_RETRY_GATEWAY_LISTEN_PORT", `${listenPort}`], ["CODEX_RETRY_GATEWAY_UPSTREAM_BASE_URL", upstreamBaseUrl], ["CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_MODE", authMode], ["CODEX_RETRY_GATEWAY_REASONING_MATCH_MODE", reasoningMatchMode], ["CODEX_RETRY_GATEWAY_REASONING_EQUALS", reasoningEquals.join(",")], ["CODEX_RETRY_GATEWAY_RETRYABLE_STATUS_CODES", retryableStatusCodes.join(",")], ["CODEX_RETRY_GATEWAY_RETRYABLE_ERROR_MESSAGES", retryableErrorMessages.join("\n")], ["CODEX_RETRY_GATEWAY_UPSTREAM_FETCH_RETRY_ATTEMPTS", `${upstreamFetchRetryAttempts}`], ["CODEX_RETRY_GATEWAY_UPSTREAM_FETCH_RETRY_BACKOFF_MS", `${upstreamFetchRetryBackoffMs}`], ["CODEX_RETRY_GATEWAY_REQUEST_HISTORY_LIMIT", `${requestHistoryLimit}`], ["CODEX_RETRY_GATEWAY_ENDPOINTS", endpoints.join(",")], ]; const managementAccessKey = normalizeManagementAccessKey(payload.management_access_key); if (managementAccessKey) { envPairs.push(["CODEX_RETRY_GATEWAY_MANAGEMENT_ACCESS_KEY", managementAccessKey]); } const modelRemap = `${payload.model_remap || ""}`.trim(); if (modelRemap) { envPairs.push(["CODEX_RETRY_GATEWAY_MODEL_REMAP", modelRemap]); } if (authMode === "manual_bearer") { const secretPath = (await writeManualSecret(name, payload.manual_secret)) || `${payload.manual_secret_file || ""}`.trim() || defaultManualSecretPath(name); if (!fs.existsSync(secretPath)) { throw new Error("manual_bearer 需要手动填入一次 token/password 后才能保存"); } envPairs.push(["CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_FILE", secretPath]); } else if (authMode === "fixed_bearer") { envPairs.push([ "CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_ENV", `${payload.auth_env || DEFAULT_CONFIG.upstream_auth_env}`.trim(), ]); if (`${payload.auth_file || ""}`.trim()) { envPairs.push(["CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_FILE", `${payload.auth_file}`.trim()]); } } else if (authMode === "auth_json") { if (`${payload.auth_json_path || ""}`.trim()) { envPairs.push(["CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_PATH", `${payload.auth_json_path}`.trim()]); } envPairs.push([ "CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_KEY", `${payload.auth_json_key || DEFAULT_CONFIG.upstream_auth_json_key}`.trim(), ]); } const lines = [ "# Managed by codex-retry-gateway UI.", "# Do not put raw sk-* secrets here; use env/file/auth.json references.", ...envPairs.map(([key, value]) => `${key}=${serializeEnvValue(value)}`), "", ]; return { name, content: lines.join("\n"), }; } async function buildImageProfileEnvText(payload) { assertNoInlineProfileSecret(payload, [ "base_url", "auth_env", "auth_file", "auth_json_path", "auth_json_key", ]); const name = `${payload?.name || ""}`.trim(); validateProfileName(name); const baseUrl = `${payload?.base_url || ""}`.trim(); if (baseUrl) { try { new URL(baseUrl); } catch { throw new Error("图片上游 Base URL 必须是合法 URL"); } } const authMode = normalizeAuthMode(payload?.auth_mode || DEFAULT_CONFIG.image_auth_mode); const envPairs = [ ["CODEX_RETRY_GATEWAY_IMAGE_BASE_URL", baseUrl], ["CODEX_RETRY_GATEWAY_IMAGE_AUTH_MODE", authMode], ]; if (baseUrl && authMode === "manual_bearer") { const secretPath = (await writeImageManualSecret(name, payload?.manual_secret)) || `${payload?.manual_secret_file || ""}`.trim() || defaultImageManualSecretPath(name); if (!fs.existsSync(secretPath)) { throw new Error("图片 manual_bearer 需要手动填入一次 API key 后才能保存"); } envPairs.push(["CODEX_RETRY_GATEWAY_IMAGE_AUTH_FILE", secretPath]); } else if (baseUrl && authMode === "fixed_bearer") { envPairs.push([ "CODEX_RETRY_GATEWAY_IMAGE_AUTH_ENV", `${payload?.auth_env || DEFAULT_CONFIG.image_auth_env}`.trim(), ]); if (`${payload?.auth_file || ""}`.trim()) { envPairs.push(["CODEX_RETRY_GATEWAY_IMAGE_AUTH_FILE", `${payload.auth_file}`.trim()]); } } else if (baseUrl && authMode === "auth_json") { if (`${payload?.auth_json_path || ""}`.trim()) { envPairs.push(["CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_PATH", `${payload.auth_json_path}`.trim()]); } envPairs.push([ "CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_KEY", `${payload?.auth_json_key || DEFAULT_CONFIG.image_auth_json_key}`.trim(), ]); } return { name, content: [ "# Managed by codex-retry-gateway image profile UI.", "# Do not put raw sk-* secrets here; use env/file/auth.json references.", ...envPairs.map(([key, value]) => `${key}=${serializeEnvValue(value)}`), "", ].join("\n"), }; } async function writeProfile(runtime, payload) { const { name, content } = await buildProfileEnvText(payload); await mkdir(runtime.paths.profilesDir, { recursive: true }); const profilePath = path.join(runtime.paths.profilesDir, `${name}.env`); await writeFile(profilePath, content, { encoding: "utf8", mode: 0o600 }); return { name, file_path: profilePath, }; } async function writeImageProfile(runtime, payload) { const { name, content } = await buildImageProfileEnvText(payload); await mkdir(runtime.paths.imageProfilesDir, { recursive: true }); const profilePath = path.join(runtime.paths.imageProfilesDir, `${name}.env`); await writeFile(profilePath, content, { encoding: "utf8", mode: 0o600 }); return { name, file_path: profilePath, }; } function buildMetricsSnapshot(monitor) { const reasoning516Count = monitor.observed_reasoning_counts["516"] || 0; const inspectedResponseCount = monitor.inspected_response_count; const reasoningEntries = Object.entries(monitor.observed_reasoning_counts).sort((left, right) => { const countDelta = Number(right[1] || 0) - Number(left[1] || 0); if (countDelta !== 0) { return countDelta; } return Number(left[0] || 0) - Number(right[0] || 0); }); const visibleReasoningEntries = reasoningEntries.slice(0, STATUS_REASONING_COUNT_LIMIT); return { started_at: monitor.started_at, persistent_since: monitor.persistent_since, total_proxy_request_count: monitor.total_proxy_request_count, inspected_response_count: inspectedResponseCount, matched_response_count: monitor.matched_response_count, reasoning_516_count: reasoning516Count, reasoning_516_ratio: inspectedResponseCount === 0 ? 0 : reasoning516Count / inspectedResponseCount, token_totals: { ...monitor.token_totals }, observed_reasoning_counts: Object.fromEntries(visibleReasoningEntries), observed_reasoning_counts_total_keys: reasoningEntries.length, observed_reasoning_counts_omitted: Math.max( 0, reasoningEntries.length - visibleReasoningEntries.length, ), }; } function buildLogsSnapshot(monitor, sinceSeq = null) { const entries = Number.isInteger(sinceSeq) ? monitor.log_entries.filter((entry) => entry.seq > sinceSeq) : monitor.log_entries; return { total_entries: monitor.log_entries.length, latest_seq: monitor.next_log_seq - 1, entries, }; } async function buildPersistentLogsSnapshot(runtime, sinceSeq = null, limit = 500) { const safeLimit = Number.isInteger(limit) && limit > 0 ? Math.min(limit, 1000) : 500; const text = runtime.logPath ? await readOptionalText(runtime.logPath) : null; if (!text) { return buildLogsSnapshot(runtime.monitor, sinceSeq); } const allEntries = text .split(/\r?\n/) .filter(Boolean) .map((line, index) => parseLogLine(line, index + 1)); const entries = Number.isInteger(sinceSeq) ? allEntries.filter((entry) => entry.seq > sinceSeq) : allEntries.slice(-safeLimit); return { total_entries: allEntries.length, latest_seq: allEntries.length, entries, }; } function buildRequestsSnapshot(monitor, limit = 50) { const safeLimit = Number.isInteger(limit) && limit > 0 ? Math.min(limit, 500) : 50; const entries = monitor.request_entries.slice(-safeLimit).reverse(); return { total_entries: monitor.request_entries.length, latest_seq: monitor.next_request_seq - 1, entries, }; } async function buildPersistentRequestsSnapshot(runtime, { limit = 50, offset = 0, query = "", filter = "all" } = {}) { const safeLimit = Number.isInteger(limit) && limit > 0 ? Math.min(limit, 500) : 50; const safeOffset = Number.isInteger(offset) && offset > 0 ? offset : 0; if (!runtime.requestsDb) { const entries = await readJsonlFile(runtime.paths.requestsPath); return { total_entries: entries.length, latest_seq: entries.reduce((maxSeq, entry) => { return Math.max(maxSeq, Number.isInteger(entry.seq) ? entry.seq : maxSeq); }, 0), entries: entries.slice(-safeLimit).reverse().map((entry) => decorateRequestEntryWithThreadRule(runtime, entry)), }; } const { whereSql, params } = buildRequestQueryFilters({ query, filter }); const totalRow = runtime.requestsDb.prepare(`SELECT COUNT(*) AS count FROM requests ${whereSql}`).get(params); const latestRow = runtime.requestsDb.prepare("SELECT MAX(seq) AS latest_seq FROM requests").get(); const rows = runtime.requestsDb.prepare(` SELECT payload_json FROM requests ${whereSql} ORDER BY seq DESC LIMIT @limit OFFSET @offset `).all({ ...params, limit: safeLimit, offset: safeOffset, }); return { total_entries: totalRow?.count || 0, latest_seq: latestRow?.latest_seq || 0, entries: rows.map(parseRequestRowPayload).filter(Boolean).map((entry) => decorateRequestEntryWithThreadRule(runtime, entry)), }; } function buildRequestEntry({ seq, startedAt, startedMs, req, pathname, requestJson, profileName }) { return { seq, request_id: null, response_id: null, thread_id: firstNonEmptyString( extractRequestThreadId(requestJson), extractHeaderThreadId(req?.headers), ), lifecycle_state: "sent", started_at: startedAt.toISOString(), first_response_at: null, first_response_delay_ms: null, last_activity_at: null, finished_at: null, duration_ms: null, profile_name: profileName || "default", method: req.method, path: pathname, request_body_bytes: null, response_bytes_received: 0, model: requestJson?.model || null, requested_model: requestJson?.model || null, forwarded_model: requestJson?.model || null, reasoning_effort: extractRequestReasoningEffort(requestJson), reasoning_summary: extractRequestReasoningSummary(requestJson), request_stream: Boolean(requestJson?.stream), response_stream: false, stream_chunk_count: 0, usage_last_updated_at: null, upstream_attempt_count: 0, reasoning_guard_enabled: true, reasoning_guard_thread_override: "default", reasoning_retry_enabled: false, reasoning_retry_query_count: 0, reasoning_retry_round_count: 0, reasoning_retry_current_round: null, reasoning_retry_current_width: 0, reasoning_retry_current_firsts: [], reasoning_retry_winner_round: null, reasoning_retry_winner_slot: null, reasoning_retry_stop_reason: null, reasoning_retry_thread_mode: "disabled", reasoning_retry_extra_inspected_count: 0, reasoning_retry_extra_matched_count: 0, reasoning_retry_extra_usage: null, reasoning_retry_extra_reasoning_counts: {}, inspected: false, matched: false, status_code: null, upstream_status_code: null, upstream: null, reasoning_tokens: null, usage: null, error: null, _started_ms: startedMs, }; } function markRequestFirstResponse(entry, at = new Date()) { if (entry.first_response_at) { return false; } const firstAt = at instanceof Date ? at : new Date(at); const firstMs = firstAt.getTime(); entry.first_response_at = firstAt.toISOString(); entry.first_response_delay_ms = Number.isFinite(entry._started_ms) ? Math.max(0, firstMs - entry._started_ms) : null; entry.last_activity_at = firstAt.toISOString(); entry.lifecycle_state = "receive_first"; entry._first_response_observer?.({ first_response_at: entry.first_response_at, first_response_delay_ms: entry.first_response_delay_ms, }); return true; } const STREAM_PROGRESS_PERSIST_INTERVAL_MS = 1000; function updateStreamingProgress(entry, { chunkBytes = 0, usage = null, reasoning = null, at = new Date() } = {}) { const observedAt = at instanceof Date ? at : new Date(at); const observedAtIso = observedAt.toISOString(); entry.last_activity_at = observedAtIso; entry.lifecycle_state = "streaming"; if (chunkBytes > 0) { entry.stream_chunk_count = (entry.stream_chunk_count || 0) + 1; entry.response_bytes_received = (entry.response_bytes_received || 0) + chunkBytes; } if (usage) { entry.usage = mergeUsageSnapshots(entry.usage, usage); entry.usage_last_updated_at = observedAtIso; } if (Number.isInteger(reasoning)) { entry.reasoning_tokens = reasoning; } } function persistStreamingProgress(runtime, entry, { force = false, usageUpdated = false } = {}, at = new Date()) { const observedAt = at instanceof Date ? at : new Date(at); const nowMs = observedAt.getTime(); const lastPersistedMs = Number.isFinite(entry._last_stream_persisted_ms) ? entry._last_stream_persisted_ms : 0; if (!force && !usageUpdated && nowMs - lastPersistedMs < STREAM_PROGRESS_PERSIST_INTERVAL_MS) { return false; } entry._last_stream_persisted_ms = nowMs; upsertRequestEntry(runtime, entry); return true; } function finalizeRequestEntry(entry, result = {}) { const finishedAt = new Date(); const startedMs = entry._started_ms; delete entry._started_ms; delete entry._last_stream_persisted_ms; return { ...entry, ...result, lifecycle_state: "finish", finished_at: finishedAt.toISOString(), duration_ms: Number.isFinite(startedMs) ? Math.max(0, Date.now() - startedMs) : null, }; } async function loadConfig(configPath) { const content = await readFile(configPath, "utf8"); const loaded = JSON.parse(content); const config = { ...DEFAULT_CONFIG, ...loaded }; config.image_profile_name = /^[A-Za-z0-9_.-]+$/.test(`${config.image_profile_name || ""}`) ? `${config.image_profile_name}` : ""; config.model_remap_map = parseModelRemapMap(config.model_remap); config.endpoints = normalizeStringList(config.endpoints, DEFAULT_CONFIG.endpoints).map(normalizePath); config.reasoning_match_mode = normalizeReasoningMatchMode(config.reasoning_match_mode); config.reasoning_equals = normalizeReasoningEquals( config.reasoning_equals, DEFAULT_CONFIG.reasoning_equals, ); config.retryable_status_codes = normalizeIntegerList( config.retryable_status_codes, DEFAULT_CONFIG.retryable_status_codes, ); config.retryable_error_messages = normalizePhraseList( config.retryable_error_messages, DEFAULT_CONFIG.retryable_error_messages, ); config.management_access_key = normalizeManagementAccessKey(config.management_access_key); config.upstream_fetch_retry_attempts = normalizePositiveInteger( config.upstream_fetch_retry_attempts, DEFAULT_CONFIG.upstream_fetch_retry_attempts, ); config.upstream_fetch_retry_backoff_ms = normalizeNonNegativeInteger( config.upstream_fetch_retry_backoff_ms, DEFAULT_CONFIG.upstream_fetch_retry_backoff_ms, ); config.image_base_url = `${config.image_base_url || ""}`.trim(); if (config.image_base_url) { try { new URL(config.image_base_url); } catch { throw new Error("配置中的 image_base_url 必须是合法 URL"); } } if (!config.upstream_base_url) { throw new Error("配置缺少 upstream_base_url"); } return config; } function buildRuntimePaths(configPath, logPath) { const configDirectory = path.dirname(configPath); const stateRoot = path.dirname(configDirectory); const homeDir = process.env.HOME || ""; return { stateRoot, statePath: path.join(stateRoot, "state.json"), pidPath: path.join(stateRoot, "gateway.pid"), profilesDir: path.join(homeDir, ".config", "codex-retry-gateway", "profiles"), imageProfilesDir: path.join(homeDir, ".config", "codex-retry-gateway", "image-profiles"), configPath, logPath, threadRulesPath: path.join(stateRoot, "thread-rules.json"), requestsPath: path.join(stateRoot, "logs", "requests.jsonl"), requestsDbPath: path.join(stateRoot, "logs", "requests.sqlite"), }; } async function readOptionalJson(jsonPath) { try { const content = await readFile(jsonPath, "utf8"); return JSON.parse(content); } catch { return null; } } function normalizeThreadRuleEntry(value) { const threadId = firstNonEmptyString(value?.thread_id, value?.threadId); if (!threadId || value?.reasoning_intercept_enabled === undefined) { return null; } return { thread_id: threadId, reasoning_intercept_enabled: Boolean(value.reasoning_intercept_enabled), updated_at: firstNonEmptyString(value?.updated_at) || new Date().toISOString(), }; } function normalizeThreadRulesDocument(payload) { const rules = Array.isArray(payload) ? payload : Array.isArray(payload?.rules) ? payload.rules : []; const entries = []; for (const rule of rules) { const normalized = normalizeThreadRuleEntry(rule); if (normalized) { entries.push(normalized); } } return entries; } function compareThreadRuleEntries(left, right) { const leftTime = Date.parse(left?.updated_at || "") || 0; const rightTime = Date.parse(right?.updated_at || "") || 0; if (rightTime !== leftTime) { return rightTime - leftTime; } return `${left?.thread_id || ""}`.localeCompare(`${right?.thread_id || ""}`); } function buildThreadRulesMap(entries = []) { const map = new Map(); for (const entry of entries) { map.set(entry.thread_id, entry); } return map; } async function loadThreadRules(filePath) { const payload = await readOptionalJson(filePath); return buildThreadRulesMap(normalizeThreadRulesDocument(payload)); } function serializeThreadRules(threadRules) { const rules = Array.from(threadRules?.values?.() || []) .map((entry) => ({ thread_id: entry.thread_id, reasoning_intercept_enabled: Boolean(entry.reasoning_intercept_enabled), updated_at: entry.updated_at || new Date().toISOString(), })) .sort(compareThreadRuleEntries); return { version: 1, rules, }; } async function writeThreadRules(filePath, threadRules) { await mkdir(path.dirname(filePath), { recursive: true }); await writeFile(filePath, `${JSON.stringify(serializeThreadRules(threadRules), null, 2)}\n`, "utf8"); } function listThreadRules(runtime) { return serializeThreadRules(runtime.threadRules).rules; } function getThreadReasoningState(runtime, threadId) { const normalizedThreadId = firstNonEmptyString(threadId); if (!normalizedThreadId) { return { thread_id: null, reasoning_intercept_enabled: true, override: "default", updated_at: null, }; } const entry = runtime.threadRules?.get(normalizedThreadId) || null; if (!entry) { return { thread_id: normalizedThreadId, reasoning_intercept_enabled: true, override: "default", updated_at: null, }; } return { thread_id: normalizedThreadId, reasoning_intercept_enabled: Boolean(entry.reasoning_intercept_enabled), override: entry.reasoning_intercept_enabled ? "enabled" : "disabled", updated_at: entry.updated_at || null, }; } function applyThreadReasoningState(runtime, requestEntry, pathname = requestEntry?.path) { if (!requestEntry || typeof requestEntry !== "object") { return requestEntry; } const threadState = getThreadReasoningState(runtime, requestEntry.thread_id); requestEntry.reasoning_guard_enabled = threadState.reasoning_intercept_enabled; requestEntry.reasoning_guard_thread_override = threadState.override; const retryPath = isResponsesReasoningRetryPath(pathname); requestEntry.reasoning_retry_enabled = retryPath && threadState.reasoning_intercept_enabled; if (!retryPath) { requestEntry.reasoning_retry_thread_mode = "disabled"; return requestEntry; } if (!threadState.reasoning_intercept_enabled) { requestEntry.reasoning_retry_thread_mode = "thread_guard_disabled"; if ( !requestEntry.reasoning_retry_stop_reason || requestEntry.reasoning_retry_stop_reason === "missing_thread_id" ) { requestEntry.reasoning_retry_stop_reason = "thread_guard_disabled"; } return requestEntry; } if (requestEntry.thread_id) { requestEntry.reasoning_retry_thread_mode = "thread_id"; if (requestEntry.reasoning_retry_stop_reason === "thread_guard_disabled") { requestEntry.reasoning_retry_stop_reason = null; } return requestEntry; } requestEntry.reasoning_retry_thread_mode = "missing_thread_id"; if (!requestEntry.reasoning_retry_stop_reason) { requestEntry.reasoning_retry_stop_reason = "missing_thread_id"; } return requestEntry; } function decorateRequestEntryWithThreadRule(runtime, entry) { if (!entry || typeof entry !== "object") { return entry; } const threadState = getThreadReasoningState(runtime, entry.thread_id); return { ...entry, reasoning_guard_enabled: threadState.reasoning_intercept_enabled, reasoning_guard_thread_override: threadState.override, }; } function normalizeThreadRuleUpdatePayload(payload) { const threadId = firstNonEmptyString(payload?.thread_id, payload?.threadId); if (!threadId) { throw new Error("缺少 thread_id"); } if (payload?.reasoning_intercept_enabled === undefined) { throw new Error("缺少 reasoning_intercept_enabled"); } return { thread_id: threadId, reasoning_intercept_enabled: Boolean(payload.reasoning_intercept_enabled), updated_at: new Date().toISOString(), }; } async function upsertThreadRule(runtime, payload) { const entry = normalizeThreadRuleUpdatePayload(payload); runtime.threadRules.set(entry.thread_id, entry); await writeThreadRules(runtime.paths.threadRulesPath, runtime.threadRules); return entry; } async function deleteThreadRule(runtime, threadId) { const normalizedThreadId = firstNonEmptyString(threadId); if (!normalizedThreadId) { throw new Error("缺少 thread_id"); } const previous = runtime.threadRules.get(normalizedThreadId) || null; runtime.threadRules.delete(normalizedThreadId); await writeThreadRules(runtime.paths.threadRulesPath, runtime.threadRules); return previous; } async function readOptionalText(textPath) { try { return await readFile(textPath, "utf8"); } catch { return null; } } function normalizeAuthMode(value) { const mode = `${value || "passthrough"}`.trim().toLowerCase(); if (["passthrough", "fixed_bearer", "manual_bearer", "auth_json"].includes(mode)) { return mode; } return "passthrough"; } function sanitizeConfigForStatus(config) { const { management_access_key, upstream_auth_file, upstream_auth_json_path, upstream_auth_env, upstream_auth_json_key, image_auth_file, image_auth_json_path, image_auth_env, image_auth_json_key, model_remap_map, ...rest } = config; return { ...rest, management_access_key_configured: Boolean(management_access_key), upstream_auth_env: upstream_auth_env || null, upstream_auth_file: upstream_auth_file ? "[configured]" : "", upstream_auth_json_path: upstream_auth_json_path ? "[configured]" : "", upstream_auth_json_key: upstream_auth_json_key || null, image_auth_env: image_auth_env || null, image_auth_file: image_auth_file ? "[configured]" : "", image_auth_json_path: image_auth_json_path ? "[configured]" : "", image_auth_json_key: image_auth_json_key || null, }; } function remapRequestModel(config, requestJson) { if (!requestJson || typeof requestJson !== "object") { return { requestJson, remapped: false, forwardedModel: null }; } const requestedModel = `${requestJson.model || ""}`.trim(); if (!requestedModel) { return { requestJson, remapped: false, forwardedModel: null }; } const forwardedModel = config.model_remap_map?.[requestedModel]; if (!forwardedModel || forwardedModel === requestedModel) { return { requestJson, remapped: false, forwardedModel: requestedModel }; } return { requestJson: { ...requestJson, model: forwardedModel, }, remapped: true, forwardedModel, }; } async function resolveUpstreamAuth(config, authScope = "upstream") { const mode = normalizeAuthMode(config.upstream_auth_mode); if (mode === "passthrough") { return { mode, authorization: null, source: "passthrough" }; } let token = ""; let source = "missing"; if (mode === "fixed_bearer") { const envName = config.upstream_auth_env || DEFAULT_CONFIG.upstream_auth_env; token = envName ? `${process.env[envName] || ""}`.trim() : ""; source = token ? "env" : "missing"; if (!token && config.upstream_auth_file) { token = `${(await readOptionalText(config.upstream_auth_file)) || ""}`.trim(); source = token ? "file" : "file_empty"; } } else if (mode === "manual_bearer") { if (config.upstream_auth_file) { token = `${(await readOptionalText(config.upstream_auth_file)) || ""}`.trim(); source = token ? "manual_file" : "manual_file_empty"; } } else if (mode === "auth_json") { const authPath = config.upstream_auth_json_path || path.join(process.env.HOME || "", ".codex", "auth.json"); const key = config.upstream_auth_json_key || DEFAULT_CONFIG.upstream_auth_json_key; const authJson = await readOptionalJson(authPath); token = `${authJson?.[key] || ""}`.trim(); source = token ? "auth_json" : "auth_json_missing"; } if (!token) { throw new Error(`${authScope}_auth_mode=${mode} requires a configured token source`); } const authorization = token.toLowerCase().startsWith("bearer ") ? token : `Bearer ${token}`; return { mode, authorization, source }; } async function writeConfig(configPath, config) { await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); } async function updateRuntimeState(runtime, updates) { const current = await readOptionalJson(runtime.paths.statePath); if (!current) { return; } await writeFile( runtime.paths.statePath, `${JSON.stringify({ ...current, ...updates }, null, 2)}\n`, "utf8", ); } async function listProfiles(runtime) { let files = []; try { files = await readdir(runtime.paths.profilesDir, { withFileTypes: true }); } catch { files = []; } const activeProfile = runtime.config.profile_name || "default"; const profiles = []; for (const file of files) { if (!file.isFile()) { continue; } const name = getProfileNameFromFile(file.name); if (!name) { continue; } const filePath = path.join(runtime.paths.profilesDir, file.name); const env = parseEnvText((await readOptionalText(filePath)) || ""); const form = buildProfileFormModel(env); profiles.push({ name, active: name === activeProfile, file_path: filePath, summary: { listen_host: form.listen_host, listen_port: form.listen_port, upstream_base_url: form.upstream_base_url, auth_mode: form.auth_mode, auth_env: redactProfileValue( "CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_ENV", form.auth_env, ), auth_file: form.auth_file ? "[configured]" : "", auth_json_path: form.auth_json_path ? "[configured]" : "", auth_json_key: redactProfileValue( "CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_KEY", form.auth_json_key, ), request_history_limit: form.request_history_limit, model_remap: form.model_remap || "", auth_source: summarizeProfileAuthSource(env), reasoning_match_mode: form.reasoning_match_mode, reasoning_equals: form.reasoning_equals, }, form, }); } profiles.sort((left, right) => left.name.localeCompare(right.name)); return profiles; } const IMAGE_PROFILE_ENV_KEYS = [ "CODEX_RETRY_GATEWAY_IMAGE_BASE_URL", "CODEX_RETRY_GATEWAY_IMAGE_AUTH_MODE", "CODEX_RETRY_GATEWAY_IMAGE_AUTH_ENV", "CODEX_RETRY_GATEWAY_IMAGE_AUTH_FILE", "CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_PATH", "CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_KEY", ]; function hasLegacyImageProfileConfig(env) { return Boolean(`${env?.CODEX_RETRY_GATEWAY_IMAGE_BASE_URL || ""}`.trim()); } function legacyImageProfileEnvFromConfig(config) { const baseUrl = `${config?.image_base_url || ""}`.trim(); if (!baseUrl) { return {}; } return { CODEX_RETRY_GATEWAY_IMAGE_BASE_URL: baseUrl, CODEX_RETRY_GATEWAY_IMAGE_AUTH_MODE: config?.image_auth_mode || DEFAULT_CONFIG.image_auth_mode, CODEX_RETRY_GATEWAY_IMAGE_AUTH_ENV: config?.image_auth_env || "", CODEX_RETRY_GATEWAY_IMAGE_AUTH_FILE: config?.image_auth_file || "", CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_PATH: config?.image_auth_json_path || "", CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_KEY: config?.image_auth_json_key || "", }; } function buildLegacyImageProfileEnvText(env) { const pairs = IMAGE_PROFILE_ENV_KEYS .filter((key) => env?.[key] !== undefined) .map((key) => [key, env[key]]); return [ "# Migrated from a legacy text profile by codex-retry-gateway.", "# Image configuration is now independent from text profiles.", ...pairs.map(([key, value]) => `${key}=${serializeEnvValue(value)}`), "", ].join("\n"); } async function migrateLegacyImageProfile(runtime, textProfileName, textEnv = null, legacyConfig = null) { validateProfileName(textProfileName); const imageProfilePath = path.join(runtime.paths.imageProfilesDir, `${textProfileName}.env`); if (fs.existsSync(imageProfilePath)) { return { name: textProfileName, file_path: imageProfilePath, migrated: false }; } let env = textEnv; if (!env) { const textProfilePath = path.join(runtime.paths.profilesDir, `${textProfileName}.env`); if (fs.existsSync(textProfilePath)) { env = parseEnvText((await readOptionalText(textProfilePath)) || ""); } else { env = {}; } } const sourceEnv = hasLegacyImageProfileConfig(env) ? env : legacyImageProfileEnvFromConfig(legacyConfig); if (!hasLegacyImageProfileConfig(sourceEnv)) { return null; } await mkdir(runtime.paths.imageProfilesDir, { recursive: true }); await writeFile(imageProfilePath, buildLegacyImageProfileEnvText(sourceEnv), { encoding: "utf8", mode: 0o600, }); return { name: textProfileName, file_path: imageProfilePath, migrated: true }; } async function listImageProfiles(runtime) { let files = []; try { files = await readdir(runtime.paths.imageProfilesDir, { withFileTypes: true }); } catch { files = []; } const activeProfile = `${runtime.config.image_profile_name || ""}`.trim(); const profiles = []; for (const file of files) { if (!file.isFile()) { continue; } const name = getProfileNameFromFile(file.name); if (!name) { continue; } const filePath = path.join(runtime.paths.imageProfilesDir, file.name); const env = parseEnvText((await readOptionalText(filePath)) || ""); const form = buildImageProfileFormModel(env); profiles.push({ name, active: name === activeProfile, file_path: filePath, summary: { base_url: form.base_url, auth_mode: form.auth_mode, auth_env: redactProfileValue("CODEX_RETRY_GATEWAY_IMAGE_AUTH_ENV", form.auth_env), auth_file: form.auth_file ? "[configured]" : "", manual_secret_file: form.manual_secret_file ? "[configured]" : "", auth_json_path: form.auth_json_path ? "[configured]" : "", auth_json_key: redactProfileValue( "CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_KEY", form.auth_json_key, ), auth_source: form.base_url ? summarizeImageProfileAuthSource(env) : "disabled", }, form, }); } profiles.sort((left, right) => left.name.localeCompare(right.name)); return profiles; } function runDetached(command, args) { const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true, }); child.unref(); return child.pid; } async function triggerProfileSwitch(runtime, profileName) { if (!/^[A-Za-z0-9_.-]+$/.test(profileName)) { throw new Error("profile 名称只能包含字母、数字、下划线、点和短横线"); } const profilePath = path.join(runtime.paths.profilesDir, `${profileName}.env`); if (!fs.existsSync(profilePath)) { throw new Error(`profile 不存在: ${profileName}`); } const currentUnit = `codex-retry-gateway@${runtime.config.profile_name || "default"}.service`; const targetUnit = `codex-retry-gateway@${profileName}.service`; const switchScript = [ "set -e", `systemctl --user disable --now ${currentUnit}`, `systemctl --user enable --now ${targetUnit}`, ].join("\n"); const unitName = `codex-retry-gateway-switch-${Date.now()}`; const pid = runDetached("systemd-run", [ "--user", "--collect", `--unit=${unitName}`, "--on-active=1s", "/usr/bin/env", "bash", "-lc", switchScript, ]); return { profile: profileName, current_unit: currentUnit, target_unit: targetUnit, switch_unit: `${unitName}.service`, pid, }; } async function applyProfileConfig(runtime, profileName) { const { profilePath, config } = await loadProfileConfigForProbe(runtime, profileName); if (!canHotSwapProfile(runtime.config, config)) { throw new Error("该 profile 的监听地址或端口与当前实例不同,暂不支持无重启热切换"); } runtime.config = { ...config, model_remap_map: parseModelRemapMap(config.model_remap), }; await writeConfig(runtime.configPath, runtime.config); await updateRuntimeState(runtime, { profile_name: runtime.config.profile_name || "default", profile_env_path: profilePath, gateway_base_url: `http://${runtime.config.listen_host}:${runtime.config.listen_port}`, last_started_at: new Date().toISOString(), }); runtime.logger( `[profile] hot-swapped profile=${runtime.config.profile_name || "default"} auth=${normalizeAuthMode(runtime.config.upstream_auth_mode)} upstream=${runtime.config.upstream_base_url}`, ); return { profile: runtime.config.profile_name || "default", profile_env_path: profilePath, hot_swapped: true, listen: `${runtime.config.listen_host}:${runtime.config.listen_port}`, upstream_base_url: runtime.config.upstream_base_url, }; } async function loadProfileConfigForProbe(runtime, profileName) { validateProfileName(profileName); const profilePath = path.join(runtime.paths.profilesDir, `${profileName}.env`); if (!fs.existsSync(profilePath)) { throw new Error(`profile 不存在: ${profileName}`); } const env = parseEnvText((await readOptionalText(profilePath)) || ""); const config = buildConfigFromProfileEnv(profileName, env, imageConfigFromConfig(runtime.config)); if (!config.upstream_base_url) { throw new Error(`profile ${profileName} 缺少 upstream_base_url`); } return { profilePath, env, config }; } async function loadImageProfileConfigForProbe(runtime, profileName) { validateProfileName(profileName); const profilePath = path.join(runtime.paths.imageProfilesDir, `${profileName}.env`); if (!fs.existsSync(profilePath)) { throw new Error(`图片 profile 不存在: ${profileName}`); } const env = parseEnvText((await readOptionalText(profilePath)) || ""); const config = buildImageConfigFromProfileEnv(profileName, env); return { profilePath, env, config }; } async function applyImageProfileConfig(runtime, profileName) { const { profilePath, config } = await loadImageProfileConfigForProbe(runtime, profileName); runtime.config = { ...runtime.config, ...config, model_remap_map: parseModelRemapMap(runtime.config.model_remap), }; await writeConfig(runtime.configPath, runtime.config); await updateRuntimeState(runtime, { image_profile_name: runtime.config.image_profile_name || "", image_profile_env_path: profilePath, last_started_at: new Date().toISOString(), }); runtime.logger( `[image-profile] hot-swapped profile=${runtime.config.image_profile_name || "-"} auth=${normalizeAuthMode(runtime.config.image_auth_mode)} upstream=${runtime.config.image_base_url || "disabled"}`, ); return { image_profile: runtime.config.image_profile_name || "", image_profile_env_path: profilePath, hot_swapped: true, image_base_url: runtime.config.image_base_url || "", }; } async function ensureActiveImageProfile(runtime) { const textProfileName = `${runtime.config.profile_name || "default"}`.trim(); const state = await readOptionalJson(runtime.paths.statePath); const candidates = [ `${runtime.config.image_profile_name || ""}`.trim(), `${state?.image_profile_name || ""}`.trim(), textProfileName, ].filter((value, index, values) => /^[A-Za-z0-9_.-]+$/.test(value) && values.indexOf(value) === index); let selectedName = candidates.find((name) => fs.existsSync(path.join(runtime.paths.imageProfilesDir, `${name}.env`))) || ""; let migration = null; if (!selectedName && /^[A-Za-z0-9_.-]+$/.test(textProfileName)) { migration = await migrateLegacyImageProfile(runtime, textProfileName, null, runtime.config); selectedName = migration?.name || ""; } if (!selectedName) { return null; } const { profilePath, config } = await loadImageProfileConfigForProbe(runtime, selectedName); runtime.config = { ...runtime.config, ...config, model_remap_map: parseModelRemapMap(runtime.config.model_remap), }; await writeConfig(runtime.configPath, runtime.config); await updateRuntimeState(runtime, { image_profile_name: selectedName, image_profile_env_path: profilePath, }); if (migration?.migrated) { runtime.logger( `[image-profile] migrated legacy text profile=${textProfileName} to image profile=${selectedName}`, ); } return { image_profile: selectedName, image_profile_env_path: profilePath, migrated: Boolean(migration?.migrated), }; } async function deleteProfile(runtime, profileName) { validateProfileName(profileName); const activeProfile = runtime.config.profile_name || "default"; if (profileName === activeProfile) { throw new Error("不能删除当前正在运行的 profile;请先切换到其他 profile"); } const profilePath = path.join(runtime.paths.profilesDir, `${profileName}.env`); if (!fs.existsSync(profilePath)) { throw new Error(`profile 不存在: ${profileName}`); } await rm(profilePath, { force: true }); return { name: profileName, file_path: profilePath, }; } async function deleteImageProfile(runtime, profileName) { validateProfileName(profileName); const activeProfile = `${runtime.config.image_profile_name || ""}`.trim(); if (profileName === activeProfile) { throw new Error("不能删除当前正在运行的图片 profile;请先切换到其他图片 profile"); } const profilePath = path.join(runtime.paths.imageProfilesDir, `${profileName}.env`); if (!fs.existsSync(profilePath)) { throw new Error(`图片 profile 不存在: ${profileName}`); } await rm(profilePath, { force: true }); return { name: profileName, file_path: profilePath, }; } async function readProbeBodySummary(response, maxChars = 400) { const contentType = response.headers.get("content-type") || ""; const text = await response.text(); let summary = text.slice(0, maxChars); if (contentType.includes("application/json")) { try { const payload = JSON.parse(text); if (Array.isArray(payload?.data)) { summary = JSON.stringify(payload.data.slice(0, 8).map((item) => item.id ?? item.display_name ?? item), null, 2); } else if (payload?.error) { summary = JSON.stringify(payload.error); } else { summary = text.slice(0, maxChars); } } catch { summary = text.slice(0, maxChars); } } return { content_type: contentType, body_preview: summary, }; } async function probeProfile(runtime, payload) { const profileName = `${payload?.profile || ""}`.trim(); if (!profileName) { throw new Error("缺少 profile"); } const { config } = await loadProfileConfigForProbe(runtime, profileName); const upstreamAuth = await resolveUpstreamAuth(config); const result = { profile: profileName, upstream_base_url: config.upstream_base_url, auth_mode: upstreamAuth.mode, auth_source: upstreamAuth.source, authorization_configured: Boolean(upstreamAuth.authorization), model_remap: config.model_remap || "", probes: [], }; const modelsUrl = buildUpstreamUrl(config.upstream_base_url, new URL("http://local/v1/models")); const { response: modelsResponse } = await fetchUpstreamWithRetry(modelsUrl, { method: "GET", headers: cloneHeadersForUpstream({}, upstreamAuth), }, config, runtime.logger, { method: "GET", pathname: "/v1/models" }); result.probes.push({ kind: "models", target: "/v1/models", status: modelsResponse.status, ...await readProbeBodySummary(modelsResponse, 600), }); const requestedModel = `${payload?.model || ""}`.trim(); if (requestedModel) { const { requestJson, forwardedModel } = remapRequestModel(config, { model: requestedModel, input: `${payload?.input || "ping"}`, max_output_tokens: 1, stream: false, }); const responsesUrl = buildUpstreamUrl(config.upstream_base_url, new URL("http://local/v1/responses")); const { response: responseProbe } = await fetchUpstreamWithRetry(responsesUrl, { method: "POST", headers: cloneHeadersForUpstream({ "content-type": "application/json" }, upstreamAuth), body: JSON.stringify(requestJson), }, config, runtime.logger, { method: "POST", pathname: "/v1/responses" }); result.probes.push({ kind: "responses", target: "/v1/responses", requested_model: requestedModel, forwarded_model: forwardedModel || requestedModel, status: responseProbe.status, ...await readProbeBodySummary(responseProbe, 600), }); } return result; } async function probeImageProfile(runtime, payload) { const profileName = `${payload?.profile || ""}`.trim(); if (!profileName) { throw new Error("缺少图片 profile"); } const { config } = await loadImageProfileConfigForProbe(runtime, profileName); if (!config.image_base_url) { throw new Error(`图片 profile ${profileName} 未配置 Base URL`); } const probeConfig = { ...runtime.config, ...config, }; const upstreamAuth = await resolveUpstreamAuth( { upstream_auth_mode: config.image_auth_mode, upstream_auth_env: config.image_auth_env, upstream_auth_file: config.image_auth_file, upstream_auth_json_path: config.image_auth_json_path, upstream_auth_json_key: config.image_auth_json_key, }, "images", ); const modelsUrl = buildUpstreamUrl(config.image_base_url, new URL("http://local/v1/models")); const { response } = await fetchUpstreamWithRetry(modelsUrl, { method: "GET", headers: cloneHeadersForUpstream({}, upstreamAuth), }, probeConfig, runtime.logger, { method: "GET", pathname: "/v1/models" }); return { image_profile: profileName, image_base_url: config.image_base_url, auth_mode: upstreamAuth.mode, auth_source: upstreamAuth.source, authorization_configured: Boolean(upstreamAuth.authorization), probes: [{ kind: "models", target: "/v1/models", status: response.status, ...await readProbeBodySummary(response, 600), }], }; } function extractProviderBaseUrl(content, providerName) { if (!content || !providerName) { return null; } const sectionPattern = new RegExp( String.raw`^\[model_providers\.${providerName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\s*$[\s\S]*?(?=^\[|\Z)`, "m", ); const sectionMatch = content.match(sectionPattern); if (!sectionMatch) { return null; } const baseUrlMatch = sectionMatch[0].match(/^\s*base_url\s*=\s*"([^"]+)"\s*$/m); return baseUrlMatch ? baseUrlMatch[1] : null; } async function readRuntimeState(runtime) { const state = await readOptionalJson(runtime.paths.statePath); if (!state) { return null; } let codexCurrentBaseUrl = null; if (state.codex_config_path && state.provider_name) { try { const codexConfig = await readFile(state.codex_config_path, "utf8"); codexCurrentBaseUrl = extractProviderBaseUrl(codexConfig, state.provider_name); } catch { codexCurrentBaseUrl = null; } } return { ...state, codex_current_base_url: codexCurrentBaseUrl, }; } async function restoreRuntimeState(runtime, state) { const backupPath = state?.latest_backup_path; const codexConfigPath = state?.codex_config_path; if (!backupPath || !fs.existsSync(backupPath)) { throw new Error(`未找到可恢复备份: ${backupPath || "unknown"}`); } if (!codexConfigPath) { throw new Error("安装状态里缺少 codex_config_path"); } await copyFile(backupPath, codexConfigPath); await Promise.all([ rm(runtime.paths.statePath, { force: true }), rm(runtime.paths.pidPath, { force: true }), ]); } function pickContentEncoding(acceptEncoding = "") { const value = `${acceptEncoding}`.toLowerCase(); if (value.includes("br")) { return "br"; } if (value.includes("gzip")) { return "gzip"; } return null; } function shouldCompressContent(headers = {}) { const contentType = `${headers["content-type"] || headers["Content-Type"] || ""}`.toLowerCase(); return ( contentType.startsWith("text/") || contentType.includes("javascript") || contentType.includes("json") || contentType.includes("xml") || contentType.includes("svg") ); } function maybeCompressBody(body, headers = {}, acceptEncoding = "") { if (!body || body.length < 1024 || !shouldCompressContent(headers)) { return { body, encoding: null }; } const encoding = pickContentEncoding(acceptEncoding); if (encoding === "br") { return { body: zlib.brotliCompressSync(body), encoding }; } if (encoding === "gzip") { return { body: zlib.gzipSync(body), encoding }; } return { body, encoding: null }; } function respondBuffer(res, statusCode, body, headers = {}, acceptEncoding = "") { const { body: responseBody, encoding } = maybeCompressBody(body, headers, acceptEncoding); res.writeHead(statusCode, { "content-length": responseBody.length, vary: "accept-encoding", ...(encoding ? { "content-encoding": encoding } : {}), ...headers, }); res.end(responseBody); } function jsonResponse(req, res, statusCode, payload, headers = {}) { const body = Buffer.from(JSON.stringify(payload)); respondBuffer( res, statusCode, body, { "content-type": "application/json; charset=utf-8", ...headers, }, req?.headers?.["accept-encoding"] || "", ); } const STATIC_CONTENT_TYPES = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml", ".png": "image/png", ".ico": "image/x-icon", ".woff": "font/woff", ".woff2": "font/woff2", }; function contentTypeForFile(filePath) { return STATIC_CONTENT_TYPES[path.extname(filePath).toLowerCase()] || "application/octet-stream"; } function safeJoinStatic(root, requestPath) { const decoded = decodeURIComponent(requestPath); const relative = decoded.replace(/^\/+/, ""); const fullPath = path.resolve(root, relative); const rootPath = path.resolve(root); if (fullPath !== rootPath && !fullPath.startsWith(`${rootPath}${path.sep}`)) { return null; } return fullPath; } async function serveStaticFile(req, res, filePath) { try { const body = await readFile(filePath); respondBuffer( res, 200, body, { "content-type": contentTypeForFile(filePath), "cache-control": filePath.includes(`${path.sep}assets${path.sep}`) ? "public, max-age=31536000, immutable" : "no-cache", }, req?.headers?.["accept-encoding"] || "", ); return true; } catch { return false; } } async function serveManagementUi(req, res, requestPathname) { const uiPrefix = `${UI_PATH}/`; if (requestPathname === UI_PATH || requestPathname === `${UI_PATH}/`) { return serveStaticFile(req, res, path.join(UI_STATIC_ROOT, "index.html")); } if (!requestPathname.startsWith(uiPrefix)) { return false; } const staticPath = safeJoinStatic(UI_STATIC_ROOT, requestPathname.slice(uiPrefix.length)); if (!staticPath) { jsonResponse(req, res, 403, { error: { message: "invalid static path", code: "invalid_static_path", }, }); return true; } if (await serveStaticFile(req, res, staticPath)) { return true; } return serveStaticFile(req, res, path.join(UI_STATIC_ROOT, "index.html")); } async function serveManagementUiWithOptionalCookie(req, res, requestPathname, cookieHeaders) { const uiPrefix = `${UI_PATH}/`; if (requestPathname === UI_PATH || requestPathname === `${UI_PATH}/`) { try { const body = await readFile(path.join(UI_STATIC_ROOT, "index.html")); respondBuffer( res, 200, body, { "content-type": "text/html; charset=utf-8", "cache-control": "no-cache", ...(cookieHeaders?.length ? { "set-cookie": cookieHeaders } : {}), }, req?.headers?.["accept-encoding"] || "", ); return true; } catch { return false; } } if (!requestPathname.startsWith(uiPrefix)) { return false; } return serveManagementUi(req, res, requestPathname); } function renderManagementAccessPage(requestPathname, errorMessage = "") { const escapedAction = `${requestPathname || UI_PATH}`.replace(/&/g, "&").replace(/"/g, """); const escapedError = `${errorMessage || ""}` .replace(/&/g, "&") .replace(//g, ">"); return ` Codex Retry Gateway

Codex Retry Gateway

此页面已启用访问 key。输入后会在当前浏览器保存后台访问状态。

${escapedError}
`; } function buildEditableConfig(currentConfig, payload) { const nextReasoningMatchMode = normalizeReasoningMatchMode( payload.reasoning_match_mode === undefined ? currentConfig.reasoning_match_mode : payload.reasoning_match_mode, ); const nextReasoning = normalizeReasoningEquals( payload.reasoning_equals === undefined ? currentConfig.reasoning_equals : payload.reasoning_equals, currentConfig.reasoning_equals, ); const nextRetryableStatusCodes = normalizeIntegerList( payload.retryable_status_codes, currentConfig.retryable_status_codes, ); const nextRetryableErrorMessages = normalizePhraseList( payload.retryable_error_messages, currentConfig.retryable_error_messages, ); const nextEndpoints = normalizeStringList(payload.endpoints, currentConfig.endpoints).map(normalizePath); const nextUpstreamFetchRetryAttempts = payload.upstream_fetch_retry_attempts === undefined ? normalizePositiveInteger( currentConfig.upstream_fetch_retry_attempts, DEFAULT_CONFIG.upstream_fetch_retry_attempts, ) : normalizePositiveInteger( payload.upstream_fetch_retry_attempts, DEFAULT_CONFIG.upstream_fetch_retry_attempts, ); const nextUpstreamFetchRetryBackoffMs = payload.upstream_fetch_retry_backoff_ms === undefined ? normalizeNonNegativeInteger( currentConfig.upstream_fetch_retry_backoff_ms, DEFAULT_CONFIG.upstream_fetch_retry_backoff_ms, ) : normalizeNonNegativeInteger( payload.upstream_fetch_retry_backoff_ms, DEFAULT_CONFIG.upstream_fetch_retry_backoff_ms, ); const nextStatusCode = payload.non_stream_status_code === undefined ? currentConfig.non_stream_status_code : Number.parseInt(`${payload.non_stream_status_code}`, 10); if (nextRetryableStatusCodes.length === 0) { throw new Error("retryable_status_codes 不能为空"); } if (nextRetryableErrorMessages.length === 0) { throw new Error("retryable_error_messages 不能为空"); } if (nextEndpoints.length === 0) { throw new Error("endpoints 不能为空"); } if (nextUpstreamFetchRetryAttempts < 1) { throw new Error("upstream_fetch_retry_attempts 必须是正整数"); } if (nextUpstreamFetchRetryBackoffMs < 0) { throw new Error("upstream_fetch_retry_backoff_ms 不能为负数"); } if (!Number.isInteger(nextStatusCode) || nextStatusCode < 100 || nextStatusCode > 599) { throw new Error("non_stream_status_code 必须是 100-599 的整数"); } return { ...currentConfig, reasoning_match_mode: nextReasoningMatchMode, reasoning_equals: nextReasoning, retryable_status_codes: nextRetryableStatusCodes, retryable_error_messages: nextRetryableErrorMessages, management_access_key: payload.management_access_key === undefined ? currentManagementAccessKey(currentConfig) : normalizeManagementAccessKey(payload.management_access_key), endpoints: nextEndpoints, upstream_fetch_retry_attempts: nextUpstreamFetchRetryAttempts, upstream_fetch_retry_backoff_ms: nextUpstreamFetchRetryBackoffMs, non_stream_status_code: nextStatusCode, log_match: payload.log_match === undefined ? currentConfig.log_match : Boolean(payload.log_match), }; } async function handleManagementRequest(runtime, req, res, requestUrl) { const pathname = normalizePath(requestUrl.pathname); const isManagementPath = pathname === ADMIN_BASE_PATH || pathname.startsWith(`${ADMIN_BASE_PATH}/`); const accessEnabled = managementAccessEnabled(runtime.config); const accessGranted = hasManagementAccess(req, requestUrl, runtime.config); if (pathname === UI_PATH || pathname.startsWith(`${UI_PATH}/`)) { if (accessEnabled && !accessGranted) { if ( pathname === UI_PATH || pathname === `${UI_PATH}/` || pathname === `${UI_PATH}/index.html` ) { const body = Buffer.from( renderManagementAccessPage(pathname, requestUrl.searchParams.get("key") ? "access key 不正确" : ""), "utf8", ); return respondBuffer( res, 401, body, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "set-cookie": clearManagementAccessCookieHeaders(), }, req?.headers?.["accept-encoding"] || "", ); } jsonResponse(req, res, 401, managementUnauthorizedPayload(), { "cache-control": "no-store", "set-cookie": clearManagementAccessCookieHeaders(), }); return true; } const cookieHeaders = accessEnabled ? buildManagementAccessCookieHeaders(runtime.config, requestManagementAccessKey(req, requestUrl)) : []; if (!(await serveManagementUiWithOptionalCookie(req, res, requestUrl.pathname, cookieHeaders))) { jsonResponse(req, res, 503, { error: { message: "UI assets were not built. Run: npm run build:ui", code: "ui_not_built", }, }); } return true; } if (accessEnabled && !accessGranted && isManagementPath) { jsonResponse(req, res, 401, managementUnauthorizedPayload(), { "cache-control": "no-store", "set-cookie": clearManagementAccessCookieHeaders(), }); return true; } if (pathname === STATUS_API_PATH && req.method === "GET") { const state = await readRuntimeState(runtime); jsonResponse(req, res, 200, { ok: true, listen: `${runtime.config.listen_host}:${runtime.config.listen_port}`, config: sanitizeConfigForStatus(runtime.config), state, paths: { config_path: runtime.configPath, state_path: runtime.paths.statePath, state_root: runtime.paths.stateRoot, log_path: runtime.logPath, requests_path: runtime.paths.requestsPath, thread_rules_path: runtime.paths.threadRulesPath, profiles_dir: runtime.paths.profilesDir, image_profiles_dir: runtime.paths.imageProfilesDir, }, metrics: buildMetricsSnapshot(runtime.monitor), }, accessEnabled && accessGranted ? { "set-cookie": buildManagementAccessCookieHeaders(runtime.config, requestManagementAccessKey(req, requestUrl)) } : {}); return true; } if (pathname === LOGS_API_PATH && req.method === "GET") { const sinceSeqRaw = requestUrl.searchParams.get("since_seq"); const limitRaw = requestUrl.searchParams.get("limit"); const sinceSeq = sinceSeqRaw === null ? null : Number.parseInt(sinceSeqRaw, 10); const limit = limitRaw === null ? 500 : Number.parseInt(limitRaw, 10); jsonResponse(req, res, 200, { ok: true, ...await buildPersistentLogsSnapshot( runtime, Number.isInteger(sinceSeq) ? sinceSeq : null, Number.isInteger(limit) ? limit : 500, ), }); return true; } if (pathname === REQUESTS_API_PATH && req.method === "GET") { const limitRaw = requestUrl.searchParams.get("limit"); const offsetRaw = requestUrl.searchParams.get("offset"); const query = requestUrl.searchParams.get("query") || ""; const filter = requestUrl.searchParams.get("filter") || "all"; const limit = limitRaw === null ? 50 : Number.parseInt(limitRaw, 10); const offset = offsetRaw === null ? 0 : Number.parseInt(offsetRaw, 10); jsonResponse(req, res, 200, { ok: true, ...await buildPersistentRequestsSnapshot(runtime, { limit: Number.isInteger(limit) ? limit : 50, offset: Number.isInteger(offset) ? offset : 0, query, filter, }), }); return true; } if (pathname === THREAD_RULES_API_PATH && req.method === "GET") { jsonResponse(req, res, 200, { ok: true, thread_rules_path: runtime.paths.threadRulesPath, rules: listThreadRules(runtime), }); return true; } if (pathname === THREAD_RULES_API_PATH && req.method === "POST") { const body = await readRequestBody(req, runtime.config.request_body_limit_bytes); const payload = parseJsonSafely(body); if (!payload) { jsonResponse(req, res, 400, { error: { message: "thread rule 保存请求必须是有效 JSON", code: "invalid_json", }, }); return true; } let savedRule; try { savedRule = await upsertThreadRule(runtime, payload); } catch (error) { jsonResponse(req, res, 400, { error: { message: `${error?.message || error}`, code: "invalid_thread_rule", }, }); return true; } runtime.logger( `[thread-rule] thread_id=${savedRule.thread_id} reasoning_intercept_enabled=${savedRule.reasoning_intercept_enabled}`, ); jsonResponse(req, res, 200, { ok: true, message: savedRule.reasoning_intercept_enabled ? "thread 已开启 reasoning 拦截" : "thread 已关闭 reasoning 拦截", saved_rule: savedRule, thread_rules_path: runtime.paths.threadRulesPath, rules: listThreadRules(runtime), }); return true; } if (pathname.startsWith(THREAD_RULE_ITEM_API_PREFIX) && req.method === "DELETE") { const rawThreadId = pathname.slice(THREAD_RULE_ITEM_API_PREFIX.length); if (!rawThreadId || rawThreadId.includes("/")) { jsonResponse(req, res, 400, { error: { message: "无效的 thread_id", code: "invalid_thread_id", }, }); return true; } const threadId = decodeURIComponent(rawThreadId); let removedRule; try { removedRule = await deleteThreadRule(runtime, threadId); } catch (error) { jsonResponse(req, res, 400, { error: { message: `${error?.message || error}`, code: "invalid_thread_rule", }, }); return true; } runtime.logger(`[thread-rule] thread_id=${threadId} restored_to_default`); jsonResponse(req, res, 200, { ok: true, message: "thread 已恢复默认拦截策略", removed_rule: removedRule, thread_rules_path: runtime.paths.threadRulesPath, rules: listThreadRules(runtime), }); return true; } if (pathname === PROFILES_API_PATH && req.method === "GET") { jsonResponse(req, res, 200, { ok: true, profiles_dir: runtime.paths.profilesDir, active_profile: runtime.config.profile_name || "default", profiles: await listProfiles(runtime), }); return true; } if (pathname === PROFILES_API_PATH && req.method === "POST") { const body = await readRequestBody(req, runtime.config.request_body_limit_bytes); const payload = parseJsonSafely(body); if (!payload) { jsonResponse(req, res, 400, { error: { message: "profile 保存请求必须是有效 JSON", code: "invalid_json", }, }); return true; } const result = await writeProfile(runtime, payload); let applied = null; if (result.name === (runtime.config.profile_name || "default")) { applied = await applyProfileConfig(runtime, result.name); } runtime.logger(`[profile] saved name=${result.name} path=${result.file_path}`); jsonResponse(req, res, 200, { ok: true, message: applied ? "profile 已保存并已热应用" : "profile 已保存", saved_profile: result, applied_profile: applied, profiles_dir: runtime.paths.profilesDir, active_profile: runtime.config.profile_name || "default", profiles: await listProfiles(runtime), }); return true; } if (pathname === PROFILE_PROBE_API_PATH && req.method === "POST") { const body = await readRequestBody(req, runtime.config.request_body_limit_bytes); const payload = parseJsonSafely(body); if (!payload) { jsonResponse(req, res, 400, { error: { message: "profile probe 请求必须是有效 JSON", code: "invalid_json", }, }); return true; } const result = await probeProfile(runtime, payload); runtime.logger( `[profile-probe] profile=${result.profile} auth=${result.auth_mode}/${result.auth_source} upstream=${result.upstream_base_url}`, ); jsonResponse(req, res, 200, { ok: true, ...result, }); return true; } if (pathname === PROFILE_SWITCH_API_PATH && req.method === "POST") { const body = await readRequestBody(req, runtime.config.request_body_limit_bytes); const payload = parseJsonSafely(body); const profileName = `${payload?.profile || ""}`.trim(); if (!profileName) { jsonResponse(req, res, 400, { error: { message: "缺少 profile", code: "profile_required", }, }); return true; } const result = await applyProfileConfig(runtime, profileName); jsonResponse(req, res, 200, { ok: true, message: "profile 已热切换,无需重启 gateway", ...result, }); return true; } if (pathname.startsWith(PROFILE_ITEM_API_PREFIX) && req.method === "DELETE") { const rawName = pathname.slice(PROFILE_ITEM_API_PREFIX.length); if (!rawName || rawName.includes("/")) { jsonResponse(req, res, 400, { error: { message: "无效的 profile 名称", code: "invalid_profile_name", }, }); return true; } const profileName = decodeURIComponent(rawName); const result = await deleteProfile(runtime, profileName); runtime.logger(`[profile] deleted name=${result.name} path=${result.file_path}`); jsonResponse(req, res, 200, { ok: true, message: "profile 已删除", deleted_profile: result, profiles_dir: runtime.paths.profilesDir, active_profile: runtime.config.profile_name || "default", profiles: await listProfiles(runtime), }); return true; } if (pathname === IMAGE_PROFILES_API_PATH && req.method === "GET") { jsonResponse(req, res, 200, { ok: true, image_profiles_dir: runtime.paths.imageProfilesDir, active_image_profile: runtime.config.image_profile_name || "", image_profiles: await listImageProfiles(runtime), }); return true; } if (pathname === IMAGE_PROFILES_API_PATH && req.method === "POST") { const body = await readRequestBody(req, runtime.config.request_body_limit_bytes); const payload = parseJsonSafely(body); if (!payload) { jsonResponse(req, res, 400, { error: { message: "图片 profile 保存请求必须是有效 JSON", code: "invalid_json", }, }); return true; } const result = await writeImageProfile(runtime, payload); let applied = null; if (result.name === `${runtime.config.image_profile_name || ""}`.trim()) { applied = await applyImageProfileConfig(runtime, result.name); } runtime.logger(`[image-profile] saved name=${result.name} path=${result.file_path}`); jsonResponse(req, res, 200, { ok: true, message: applied ? "图片 profile 已保存并已热应用" : "图片 profile 已保存", saved_image_profile: result, applied_image_profile: applied, image_profiles_dir: runtime.paths.imageProfilesDir, active_image_profile: runtime.config.image_profile_name || "", image_profiles: await listImageProfiles(runtime), }); return true; } if (pathname === IMAGE_PROFILE_PROBE_API_PATH && req.method === "POST") { const body = await readRequestBody(req, runtime.config.request_body_limit_bytes); const payload = parseJsonSafely(body); if (!payload) { jsonResponse(req, res, 400, { error: { message: "图片 profile probe 请求必须是有效 JSON", code: "invalid_json", }, }); return true; } const result = await probeImageProfile(runtime, payload); runtime.logger( `[image-profile-probe] profile=${result.image_profile} auth=${result.auth_mode}/${result.auth_source} upstream=${result.image_base_url}`, ); jsonResponse(req, res, 200, { ok: true, ...result }); return true; } if (pathname === IMAGE_PROFILE_SWITCH_API_PATH && req.method === "POST") { const body = await readRequestBody(req, runtime.config.request_body_limit_bytes); const payload = parseJsonSafely(body); const profileName = `${payload?.profile || ""}`.trim(); if (!profileName) { jsonResponse(req, res, 400, { error: { message: "缺少图片 profile", code: "profile_required", }, }); return true; } const result = await applyImageProfileConfig(runtime, profileName); jsonResponse(req, res, 200, { ok: true, message: "图片 profile 已热切换,无需重启 gateway", ...result, }); return true; } if (pathname.startsWith(IMAGE_PROFILE_ITEM_API_PREFIX) && req.method === "DELETE") { const rawName = pathname.slice(IMAGE_PROFILE_ITEM_API_PREFIX.length); if (!rawName || rawName.includes("/")) { jsonResponse(req, res, 400, { error: { message: "无效的图片 profile 名称", code: "invalid_profile_name", }, }); return true; } const profileName = decodeURIComponent(rawName); const result = await deleteImageProfile(runtime, profileName); runtime.logger(`[image-profile] deleted name=${result.name} path=${result.file_path}`); jsonResponse(req, res, 200, { ok: true, message: "图片 profile 已删除", deleted_image_profile: result, image_profiles_dir: runtime.paths.imageProfilesDir, active_image_profile: runtime.config.image_profile_name || "", image_profiles: await listImageProfiles(runtime), }); return true; } if (pathname === CONFIG_API_PATH && req.method === "POST") { const body = await readRequestBody(req, runtime.config.request_body_limit_bytes); const payload = parseJsonSafely(body); if (!payload) { jsonResponse(req, res, 400, { error: { message: "配置保存请求必须是有效 JSON", code: "invalid_json", }, }); return true; } const nextConfig = buildEditableConfig(runtime.config, payload); await writeConfig(runtime.configPath, nextConfig); runtime.config = nextConfig; runtime.logger( `[config] updated reasoning_match_mode=${nextConfig.reasoning_match_mode} reasoning_equals=${nextConfig.reasoning_equals.join(",")} retryable_status_codes=${nextConfig.retryable_status_codes.join(",")} endpoints=${nextConfig.endpoints.join(",")}`, ); const state = await readRuntimeState(runtime); jsonResponse(req, res, 200, { ok: true, message: "配置已保存并立即生效", config: sanitizeConfigForStatus(runtime.config), state, paths: { config_path: runtime.configPath, state_path: runtime.paths.statePath, state_root: runtime.paths.stateRoot, log_path: runtime.logPath, thread_rules_path: runtime.paths.threadRulesPath, }, metrics: buildMetricsSnapshot(runtime.monitor), }); return true; } if (pathname === RESTORE_API_PATH && req.method === "POST") { const state = await readRuntimeState(runtime); if (!state) { jsonResponse(req, res, 409, { error: { message: "当前未检测到安装状态,无法恢复 Codex 原设置", code: "state_not_found", }, }); return true; } await restoreRuntimeState(runtime, state); runtime.logger(`[restore] restored via UI state_root=${runtime.paths.stateRoot}`); jsonResponse(req, res, 202, { ok: true, message: "原设置已恢复,gateway 即将关闭", }); res.on("finish", () => { const exitTimer = setTimeout(() => { if (runtime.server) { runtime.server.close(() => { process.exit(0); }); } else { process.exit(0); } const hardExitTimer = setTimeout(() => { process.exit(0); }, 600); hardExitTimer.unref(); }, 120); exitTimer.unref(); }); return true; } if (isManagementPath) { jsonResponse(req, res, 404, { error: { message: "管理面路径不存在", code: "management_endpoint_not_found", }, }); return true; } return false; } function buildUpstreamUrl(baseUrl, requestUrl) { const upstream = new URL(baseUrl); const normalizedBasePath = upstream.pathname.endsWith("/") ? upstream.pathname.slice(0, -1) : upstream.pathname; const incomingPath = requestUrl.pathname; let finalPath = incomingPath; if (normalizedBasePath && normalizedBasePath !== "/") { if (incomingPath.startsWith(`${normalizedBasePath}/`) || incomingPath === normalizedBasePath) { finalPath = incomingPath; } else if (normalizedBasePath.endsWith("/v1") && incomingPath.startsWith("/v1/")) { finalPath = `${normalizedBasePath}${incomingPath.slice(3)}`; } else { finalPath = `${normalizedBasePath}${incomingPath}`; } } upstream.pathname = finalPath; upstream.search = requestUrl.search; return upstream.toString(); } function buildUpstreamSnapshot({ upstreamUrl, upstreamAuth = null, upstreamResponse = null }) { const parsedUrl = new URL(upstreamUrl); const snapshot = { origin: parsedUrl.origin, path: parsedUrl.pathname, auth_mode: upstreamAuth?.mode || "unknown", auth_source: upstreamAuth?.source || "unknown", authorization_configured: Boolean(upstreamAuth?.authorization), }; if (upstreamResponse) { snapshot.status = upstreamResponse.status; snapshot.content_type = upstreamResponse.headers.get("content-type") || ""; } return snapshot; } function cloneHeadersForUpstream(headers, upstreamAuth = null) { const outgoing = new Headers(); for (const [key, value] of Object.entries(headers)) { if (value === undefined) { continue; } const lowerKey = key.toLowerCase(); if ( lowerKey === "host" || lowerKey === "content-length" || lowerKey === "connection" || lowerKey === "transfer-encoding" ) { continue; } if (Array.isArray(value)) { for (const item of value) { outgoing.append(key, item); } } else { outgoing.set(key, value); } } if (upstreamAuth?.authorization) { outgoing.set("authorization", upstreamAuth.authorization); } return outgoing; } function copyHeadersToClient(sourceHeaders, target) { for (const [key, value] of sourceHeaders.entries()) { const lowerKey = key.toLowerCase(); if ( lowerKey === "content-length" || lowerKey === "transfer-encoding" || lowerKey === "content-encoding" || lowerKey === "connection" ) { continue; } target.setHeader(key, value); } } function cloneResponseHeaders(sourceHeaders) { const headers = new Headers(); for (const [key, value] of sourceHeaders.entries()) { headers.set(key, value); } return headers; } const CAPTURED_STREAM_REPLAY_DELAY_MS = 5; function cloneBufferList(chunks) { if (!Array.isArray(chunks) || chunks.length === 0) { return null; } return chunks .filter((chunk) => chunk && chunk.length > 0) .map((chunk) => (Buffer.isBuffer(chunk) ? Buffer.from(chunk) : Buffer.from(chunk))); } async function writeBufferedStreamChunks(res, chunks) { const replayChunks = (chunks || []).filter((chunk) => chunk && chunk.length > 0); res.socket?.setNoDelay(true); res.flushHeaders?.(); for (let index = 0; index < replayChunks.length; index += 1) { if (res.destroyed || res.writableEnded) { break; } const chunk = replayChunks[index]; const accepted = res.write(chunk); if (!accepted) { await new Promise((resolve) => { let settled = false; const finish = () => { if (settled) { return; } settled = true; res.off("drain", onDrain); res.off("close", onClose); res.off("error", onClose); resolve(); }; const onDrain = () => finish(); const onClose = () => finish(); res.once("drain", onDrain); res.once("close", onClose); res.once("error", onClose); }); } if ( index < replayChunks.length - 1 && !res.destroyed && !res.writableEnded ) { await sleep(CAPTURED_STREAM_REPLAY_DELAY_MS); } } } async function writeCapturedResponse(res, delivery) { if (!delivery) { throw new Error("missing captured response delivery"); } copyHeadersToClient(delivery.headers, res); res.writeHead(delivery.status_code); if (Array.isArray(delivery.stream_chunks) && delivery.stream_chunks.length > 0) { await writeBufferedStreamChunks(res, delivery.stream_chunks); if (!res.writableEnded) { res.end(); } return; } res.end(delivery.body); } function buildCapturedDelivery(statusCode, headers, body, options = {}) { const delivery = { status_code: statusCode, headers: cloneResponseHeaders(headers), body: Buffer.isBuffer(body) ? body : Buffer.from(body || ""), }; const streamChunks = cloneBufferList(options.stream_chunks); if (streamChunks && streamChunks.length > 0) { delivery.stream_chunks = streamChunks; } return delivery; } function createAbortReason(code, message) { const error = new Error(message); error.name = "AbortError"; error.code = code; return error; } function abortReasonCode(value) { return value?.reason?.code || value?.code || null; } function createLinkedAbortController(signals = []) { const controller = new AbortController(); const cleanups = []; const linkSignal = (signal) => { if (!signal) { return; } const abort = () => { if (!controller.signal.aborted) { controller.abort(signal.reason || createAbortReason(REASONING_RETRY_ABORT_CLIENT, "request aborted")); } }; if (signal.aborted) { abort(); return; } signal.addEventListener("abort", abort, { once: true }); cleanups.push(() => signal.removeEventListener("abort", abort)); }; for (const signal of signals) { linkSignal(signal); if (controller.signal.aborted) { break; } } return { controller, cleanup() { for (const cleanup of cleanups) { cleanup(); } }, }; } function createClientAbortContext(req, res) { const controller = new AbortController(); const abort = () => { if (!controller.signal.aborted) { controller.abort( createAbortReason(REASONING_RETRY_ABORT_CLIENT, "client disconnected before reasoning retry completed"), ); } }; const onAborted = () => abort(); const onReqClose = () => { if (req.destroyed && !res.writableEnded) { abort(); } }; const onResClose = () => { if (!res.writableEnded) { abort(); } }; req.on("aborted", onAborted); req.on("close", onReqClose); res.on("close", onResClose); return { signal: controller.signal, cleanup() { req.off("aborted", onAborted); req.off("close", onReqClose); res.off("close", onResClose); }, }; } function isResponsesReasoningRetryPath(pathname) { return RESPONSES_REASONING_RETRY_PATHS.has(normalizePath(pathname)); } function isResponsesReasoningRetryEligible(pathname, requestEntry) { return isResponsesReasoningRetryPath(pathname) && Boolean(requestEntry?.thread_id) && Boolean(requestEntry?.reasoning_retry_enabled); } function reasoningRetryWaveWidth(round) { if (round <= 2) { return 1; } if (round <= 4) { return 2; } return 4; } async function readRequestBody(req, limitBytes) { const chunks = []; let total = 0; for await (const chunk of req) { total += chunk.length; if (total > limitBytes) { throw new Error(`请求体超过限制: ${limitBytes} bytes`); } chunks.push(chunk); } return Buffer.concat(chunks); } function parseJsonSafely(buffer) { try { return JSON.parse(buffer.toString("utf8")); } catch { return null; } } function rebuildBufferedResponse(response, bodyBuffer) { return new Response(bodyBuffer, { status: response.status, statusText: response.statusText, headers: new Headers(response.headers), }); } function matchPath(config, pathname) { return config.endpoints.includes(normalizePath(pathname)); } function isImageRequestPath(pathname) { const normalizedPath = normalizePath(pathname); return ( normalizedPath === "/images" || normalizedPath.startsWith("/images/") || normalizedPath === "/v1/images" || normalizedPath.startsWith("/v1/images/") ); } function normalizeImageRequestUrl(requestUrl) { const normalized = new URL(requestUrl.toString()); if (normalized.pathname === "/images" || normalized.pathname.startsWith("/images/")) { normalized.pathname = `/v1${normalized.pathname}`; } return normalized; } function selectUpstreamRoute(config, pathname) { if (isImageRequestPath(pathname) && `${config.image_base_url || ""}`.trim()) { return { kind: "images", baseUrl: config.image_base_url, authConfig: { upstream_auth_mode: config.image_auth_mode, upstream_auth_env: config.image_auth_env, upstream_auth_file: config.image_auth_file, upstream_auth_json_path: config.image_auth_json_path, upstream_auth_json_key: config.image_auth_json_key, }, }; } return { kind: "default", baseUrl: config.upstream_base_url, authConfig: config, }; } function reasoningMatchesFormula518nMinus2(reasoning) { return Number.isInteger(reasoning) && reasoning >= 516 && (reasoning + 2) % 518 === 0; } function reasoningMatchedByConfig(config, reasoning) { if (!Number.isInteger(reasoning)) { return false; } if (normalizeReasoningMatchMode(config.reasoning_match_mode) === "manual") { return config.reasoning_equals.includes(reasoning); } return reasoningMatchesFormula518nMinus2(reasoning); } function reasoningMatched(runtime, config, requestEntry, reasoning) { if (!getThreadReasoningState(runtime, requestEntry?.thread_id).reasoning_intercept_enabled) { return false; } return reasoningMatchedByConfig(config, reasoning); } function collectRetryableMessageCandidates(value, state = { seen: new Set(), results: [] }, depth = 0) { if (value === null || value === undefined || depth > 5 || state.results.length >= 64) { return state.results; } if (typeof value === "string") { const trimmed = value.trim(); if (trimmed) { state.results.push(trimmed); } return state.results; } if (typeof value !== "object") { return state.results; } if (state.seen.has(value)) { return state.results; } state.seen.add(value); if (Array.isArray(value)) { for (const item of value) { collectRetryableMessageCandidates(item, state, depth + 1); } return state.results; } const preferredKeys = ["message", "error", "detail", "details", "description", "title"]; for (const key of preferredKeys) { if (Object.hasOwn(value, key)) { collectRetryableMessageCandidates(value[key], state, depth + 1); } } for (const [key, nested] of Object.entries(value)) { if (preferredKeys.includes(key)) { continue; } collectRetryableMessageCandidates(nested, state, depth + 1); } return state.results; } function truncateRetryableMessage(value, maxLength = 280) { const text = `${value || ""}`.trim(); if (!text) { return ""; } return text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text; } const RETRYABLE_OVERLOAD_ERROR_CODES = new Set([ "server_is_overloaded", "slow_down", ]); function findRetryableOverloadErrorCode(value, state = { seen: new Set() }, depth = 0) { if (!value || typeof value !== "object" || depth > 6 || state.seen.has(value)) { return null; } state.seen.add(value); if (Array.isArray(value)) { for (const item of value) { const match = findRetryableOverloadErrorCode(item, state, depth + 1); if (match) { return match; } } return null; } const code = typeof value.code === "string" ? value.code.trim().toLowerCase() : ""; if (RETRYABLE_OVERLOAD_ERROR_CODES.has(code)) { return { matched_code: code, matched_pattern: `error_code:${code}`, matched_message: truncateRetryableMessage( firstNonEmptyString(value.message, value.detail, value.description, code), ), }; } for (const nested of Object.values(value)) { const match = findRetryableOverloadErrorCode(nested, state, depth + 1); if (match) { return match; } } return null; } function findRetryableUpstreamErrorMatch(config, upstreamStatusCode, parsedBody, bodyText) { const retryableMessages = normalizePhraseList( config.retryable_error_messages, DEFAULT_CONFIG.retryable_error_messages, ); if (retryableMessages.length === 0) { return null; } const retryableStatusCodes = normalizeIntegerList( config.retryable_status_codes, DEFAULT_CONFIG.retryable_status_codes, ); const statusEligible = isUpstreamErrorStatus(upstreamStatusCode) && ( retryableStatusCodes.length === 0 || retryableStatusCodes.includes(upstreamStatusCode) ); const failurePayload = isRetryableErrorPayloadShape(parsedBody); if (!statusEligible && !failurePayload) { return null; } const overloadCodeMatch = findRetryableOverloadErrorCode(parsedBody); if (overloadCodeMatch) { return overloadCodeMatch; } return matchRetryableMessage(config, parsedBody, bodyText); } function matchRetryableMessage(config, parsedBody, bodyText) { const retryableMessages = normalizePhraseList( config.retryable_error_messages, DEFAULT_CONFIG.retryable_error_messages, ); if (retryableMessages.length === 0) { return null; } const normalizedPatterns = retryableMessages.map((message) => ({ original: message, normalized: message.toLowerCase(), })); const candidateMessages = []; if (parsedBody) { candidateMessages.push(...collectRetryableMessageCandidates(parsedBody)); } const trimmedBodyText = `${bodyText || ""}`.trim(); if (trimmedBodyText) { candidateMessages.push(trimmedBodyText); } for (const candidate of candidateMessages) { const normalizedCandidate = candidate.toLowerCase(); for (const pattern of normalizedPatterns) { if (normalizedCandidate.includes(pattern.normalized)) { return { matched_pattern: pattern.original, matched_message: truncateRetryableMessage(candidate), }; } } } return null; } function hasRetryableFailureKeyword(value) { const normalized = `${value || ""}`.trim().toLowerCase(); if (!normalized) { return false; } return ( normalized.includes("error") || normalized.includes("failed") || normalized.includes("failure") || normalized.includes("cancelled") || normalized.includes("canceled") || normalized.includes("rate_limit") || normalized.includes("overloaded") || normalized.includes("unavailable") ); } function hasRetryableFailureShape(value, depth = 0) { if (!value || depth > 4) { return false; } if (Array.isArray(value)) { return value.some((item) => hasRetryableFailureShape(item, depth + 1)); } if (typeof value !== "object") { return false; } if (Object.hasOwn(value, "error")) { return true; } if (Number.isInteger(value.status) && value.status >= 400) { return true; } for (const key of ["type", "event", "status", "state", "result", "code"]) { if (typeof value[key] === "string" && hasRetryableFailureKeyword(value[key])) { return true; } } for (const key of ["response", "data", "meta", "details", "detail"]) { if (hasRetryableFailureShape(value[key], depth + 1)) { return true; } } return false; } function isRetryableErrorPayloadShape(parsedBody, eventName = "") { if (hasRetryableFailureKeyword(eventName)) { return true; } if (!parsedBody || typeof parsedBody !== "object" || Array.isArray(parsedBody)) { return false; } return hasRetryableFailureShape(parsedBody); } function findRetryableStreamErrorMatch(config, parsedBody, bodyText, eventName = "") { if (!isRetryableErrorPayloadShape(parsedBody, eventName)) { return null; } const overloadCodeMatch = findRetryableOverloadErrorCode(parsedBody); if (overloadCodeMatch) { return overloadCodeMatch; } return matchRetryableMessage(config, parsedBody, bodyText); } function findRetryableStreamTerminationMatch(config, error) { const message = `${error?.message || error || ""}`.trim(); if (!message) { return null; } return matchRetryableMessage(config, null, message); } function isExpectedStreamTermination(error) { if (!error) { return false; } const message = `${error?.message || ""}`.trim().toLowerCase(); if (error.name === "AbortError") { return true; } return error instanceof TypeError && ( message === "terminated" || message.includes("stream disconnected before completion") ); } function isRetryableUpstreamFetchError(error) { if (!error) { return false; } return error instanceof TypeError && error.message === "fetch failed"; } function isUpstreamErrorStatus(statusCode) { return Number.isInteger(statusCode) && statusCode >= 400; } function computeRetryBackoffMs(config, attempt) { const baseDelay = normalizeNonNegativeInteger( config?.upstream_fetch_retry_backoff_ms, DEFAULT_CONFIG.upstream_fetch_retry_backoff_ms, ); if (baseDelay <= 0 || attempt <= 1) { return baseDelay; } const multiplier = Math.min(attempt - 1, 4); return baseDelay * multiplier; } async function fetchUpstreamWithRetry(upstreamUrl, init, config, logger, requestContext = {}) { const maxAttempts = normalizePositiveInteger( config?.upstream_fetch_retry_attempts, DEFAULT_CONFIG.upstream_fetch_retry_attempts, ); const method = `${requestContext.method || init?.method || "GET"}`.toUpperCase(); const pathname = requestContext.pathname || ""; let lastError = null; let lastResponse = null; let retryableUpstreamError = null; for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { try { const response = await fetch(upstreamUrl, init); lastResponse = response; if (method === "GET") { return { response, attempt_count: attempt, retryable_upstream_error: null, }; } const contentType = response.headers.get("content-type"); if (!isJsonContentType(contentType)) { return { response, attempt_count: attempt, retryable_upstream_error: null, }; } const bodyBuffer = Buffer.from(await response.arrayBuffer()); const parsed = parseJsonSafely(bodyBuffer); const bodyText = bodyBuffer.toString("utf8"); retryableUpstreamError = findRetryableUpstreamErrorMatch( config, response.status, parsed, bodyText, ); if (!retryableUpstreamError) { return { response: rebuildBufferedResponse(response, bodyBuffer), attempt_count: attempt, retryable_upstream_error: null, }; } if (attempt === maxAttempts) { return { response: rebuildBufferedResponse(response, bodyBuffer), attempt_count: attempt, retryable_upstream_error: { ...retryableUpstreamError, upstream_status_code: response.status, }, }; } const backoffMs = computeRetryBackoffMs(config, attempt); logger?.( `[retry] upstream retryable error attempt=${attempt} next_attempt=${attempt + 1} status=${response.status} path=${pathname || "-"} reason=${JSON.stringify(retryableUpstreamError.matched_pattern)} backoff_ms=${backoffMs}`, ); if (backoffMs > 0) { await sleep(backoffMs); } } catch (error) { lastError = error; if (error && typeof error === "object") { error.gatewayAttemptCount = attempt; } if (!isRetryableUpstreamFetchError(error) || attempt === maxAttempts) { break; } const backoffMs = computeRetryBackoffMs(config, attempt); logger?.( `[retry] upstream fetch failed attempt=${attempt} next_attempt=${attempt + 1} method=${method} path=${pathname || "-"} url=${upstreamUrl} backoff_ms=${backoffMs}`, ); if (backoffMs > 0) { await sleep(backoffMs); } } } if (lastError) { throw lastError; } return { response: lastResponse, attempt_count: maxAttempts, retryable_upstream_error: retryableUpstreamError, }; } function inspectSseChunk(state, chunk, config) { const decoded = state.decoder.decode(chunk, { stream: true }); state.buffer += decoded; const blocks = state.buffer.split(/\r?\n\r?\n/); state.buffer = blocks.pop() ?? ""; return inspectSseBlocks(blocks, config); } async function handleNonStreaming({ runtime, config, logger, monitor, pathname, upstreamResponse, res, requestEntry, terminalRetryableUpstreamError = null, captureOnly = false, persistEntry = true, }) { markRequestEntryFirstResponse(runtime, requestEntry, { persistEntry }); const bodyBuffer = Buffer.from(await upstreamResponse.arrayBuffer()); const bodyText = bodyBuffer.toString("utf8"); const parsed = isJsonContentType(upstreamResponse.headers.get("content-type")) ? parseJsonSafely(bodyBuffer) : null; const reasoning = parsed ? extractReasoningTokens(parsed) : null; const usage = parsed ? normalizeUsageSnapshot(parsed) : null; const responseId = parsed ? extractNonStreamingResponseId(parsed) : null; const threadId = firstNonEmptyString( requestEntry.thread_id, parsed ? extractResponseThreadId(parsed) : null, ); const retryableUpstreamError = terminalRetryableUpstreamError || findRetryableUpstreamErrorMatch( config, upstreamResponse.status, parsed, bodyText, ); requestEntry.response_id = responseId || requestEntry.response_id || null; requestEntry.thread_id = threadId || requestEntry.thread_id || null; applyThreadReasoningState(runtime, requestEntry, pathname); const matched = reasoningMatched(runtime, config, requestEntry, reasoning); recordInspectedResponse(monitor, reasoning, matched || Boolean(retryableUpstreamError)); if (matched) { if (config.log_match) { logger( `[match] non-stream path=${pathname} reasoning_tokens=${reasoning} action=status_${config.non_stream_status_code}`, ); } if (!captureOnly) { const blockedBody = buildBlockedBody(pathname, reasoning, config.non_stream_status_code); res.writeHead(config.non_stream_status_code, { "content-type": "application/json; charset=utf-8", "x-codex-retry-gateway-reason": "reasoning-guard-triggered", }); res.end(blockedBody); } return { inspected: true, matched, match_reason: "reasoning_guard", status_code: config.non_stream_status_code, upstream_status_code: upstreamResponse.status, reasoning_tokens: reasoning, usage, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, }; } if (retryableUpstreamError) { if (config.log_match) { logger( `[match] non-stream path=${pathname} upstream_status=${upstreamResponse.status} retryable_error=${JSON.stringify(retryableUpstreamError.matched_pattern)} action=status_${config.non_stream_status_code}`, ); } const blockedBody = buildRetryableUpstreamErrorBody( pathname, upstreamResponse.status, retryableUpstreamError.matched_message || retryableUpstreamError.matched_pattern, config.non_stream_status_code, ); if (!captureOnly) { res.writeHead(config.non_stream_status_code, { "content-type": "application/json; charset=utf-8", "x-codex-retry-gateway-reason": "upstream-error-retry-triggered", }); res.end(blockedBody); } return { inspected: true, matched: true, status_code: config.non_stream_status_code, upstream_status_code: upstreamResponse.status, reasoning_tokens: reasoning, usage, error: `retryable upstream error: ${retryableUpstreamError.matched_pattern}`, match_reason: "retryable_upstream_error", response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, delivery: captureOnly ? buildCapturedDelivery( config.non_stream_status_code, new Headers({ "content-type": "application/json; charset=utf-8", "x-codex-retry-gateway-reason": "upstream-error-retry-triggered", }), blockedBody, ) : null, }; } if (!captureOnly) { copyHeadersToClient(upstreamResponse.headers, res); res.writeHead(upstreamResponse.status); res.end(bodyBuffer); } return { inspected: true, matched, status_code: upstreamResponse.status, upstream_status_code: upstreamResponse.status, reasoning_tokens: reasoning, usage, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, delivery: captureOnly ? buildCapturedDelivery(upstreamResponse.status, upstreamResponse.headers, bodyBuffer) : null, }; } async function handleStreaming({ runtime, config, logger, monitor, pathname, upstreamResponse, res, abortController, requestEntry, requestAbortSignal = null, captureOnly = false, persistEntry = true, }) { const strict502Mode = captureOnly || config.stream_action !== "disconnect"; const reader = upstreamResponse.body.getReader(); const sseState = { decoder: new TextDecoder("utf8"), buffer: "", }; const codexResponsesSseState = isResponsesReasoningRetryPath(pathname) && isSseContentType( upstreamResponse.headers.get("content-type"), ) ? createResponsesCodexSseState() : null; let wroteAnyChunk = false; let observedReasoning = null; let observedUsage = null; const bufferedChunks = []; if (!strict502Mode && !captureOnly) { copyHeadersToClient(upstreamResponse.headers, res); res.writeHead(upstreamResponse.status); } while (true) { let readResult; try { readResult = await reader.read(); } catch (error) { if (requestAbortSignal?.aborted) { const abortCode = abortReasonCode(requestAbortSignal); if (abortCode === REASONING_RETRY_ABORT_WINNER) { return { cancelled: true, cancel_reason: abortCode, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } throw requestAbortSignal.reason || error; } if (isExpectedStreamTermination(error)) { const retryableTerminationError = findRetryableStreamTerminationMatch(config, error); if (retryableTerminationError) { return { inspected: true, matched: true, retry_requested: strict502Mode || !wroteAnyChunk, retryable_upstream_error: retryableTerminationError, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, error: `retryable upstream error: ${retryableTerminationError.matched_pattern}`, match_reason: "retryable_upstream_error", response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } recordInspectedResponse(monitor, observedReasoning, false); if (persistEntry) { persistStreamingProgress(runtime, requestEntry, { force: true }, new Date()); } if (strict502Mode) { logger?.(`[stream] upstream terminated before completion path=${pathname} action=status_502`); const gatewayErrorBody = buildGatewayErrorBody("upstream stream terminated before completion"); if (!captureOnly) { res.writeHead(502, { "content-type": "application/json; charset=utf-8" }); res.end(gatewayErrorBody); } return { inspected: true, matched: false, status_code: 502, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, error: "upstream stream terminated before completion", response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, delivery: captureOnly ? buildCapturedDelivery( 502, new Headers({ "content-type": "application/json; charset=utf-8" }), gatewayErrorBody, ) : null, }; } else { if (!captureOnly) { res.end(); } return { inspected: true, matched: false, status_code: upstreamResponse.status, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, error: "upstream stream terminated before completion", response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } } throw error; } const { done, value } = readResult; if (done) { const finalInspection = flushSseInspectionRemainder(sseState, config); const finalCodexChunks = codexResponsesSseState ? flushResponsesSseForCodex( codexResponsesSseState, requestEntry.response_id || null, observedUsage, ) : []; if (Number.isInteger(finalInspection.reasoning)) { observedReasoning = finalInspection.reasoning; } observedUsage = mergeUsageSnapshots(observedUsage, finalInspection.usage); if (finalInspection.response_id) { requestEntry.response_id = finalInspection.response_id; } if (finalInspection.thread_id) { requestEntry.thread_id = finalInspection.thread_id; } applyThreadReasoningState(runtime, requestEntry, pathname); if (codexResponsesSseState?.response_id && !requestEntry.response_id) { requestEntry.response_id = codexResponsesSseState.response_id; } observedUsage = mergeUsageSnapshots(observedUsage, codexResponsesSseState?.usage || null); if (finalInspection.retryable_upstream_error) { return { inspected: true, matched: true, retry_requested: strict502Mode || !wroteAnyChunk, retryable_upstream_error: finalInspection.retryable_upstream_error, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, error: `retryable upstream error: ${finalInspection.retryable_upstream_error.matched_pattern}`, match_reason: "retryable_upstream_error", response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } if (reasoningMatched(runtime, config, requestEntry, observedReasoning)) { recordInspectedResponse(monitor, observedReasoning, true); if (config.log_match) { logger( `[match] stream path=${pathname} reasoning_tokens=${observedReasoning} action=${config.stream_action}`, ); } if (captureOnly) { return { inspected: true, matched: true, match_reason: "reasoning_guard", status_code: config.non_stream_status_code, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } if (strict502Mode || !wroteAnyChunk) { const blockedBody = buildBlockedBody(pathname, observedReasoning, config.non_stream_status_code); res.writeHead(config.non_stream_status_code, { "content-type": "application/json; charset=utf-8", "x-codex-retry-gateway-reason": "reasoning-guard-triggered", }); res.end(blockedBody); } else if (!captureOnly && !res.writableEnded) { res.end(); } return { inspected: true, matched: true, match_reason: "reasoning_guard", status_code: config.non_stream_status_code, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } if (finalCodexChunks.length > 0) { if (strict502Mode) { bufferedChunks.push(...finalCodexChunks); } else if (!captureOnly) { for (const chunk of finalCodexChunks) { wroteAnyChunk = true; res.write(chunk); } } } recordInspectedResponse(monitor, observedReasoning, false); if (persistEntry) { persistStreamingProgress(runtime, requestEntry, { force: true }, new Date()); } if (strict502Mode) { const replayChunks = cloneBufferList(bufferedChunks) || []; const bufferedBody = Buffer.concat(replayChunks); if (!captureOnly) { copyHeadersToClient(upstreamResponse.headers, res); res.writeHead(upstreamResponse.status); await writeBufferedStreamChunks(res, replayChunks); if (!res.writableEnded) { res.end(); } } return { inspected: true, matched: false, status_code: upstreamResponse.status, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, delivery: captureOnly ? buildCapturedDelivery(upstreamResponse.status, upstreamResponse.headers, bufferedBody, { stream_chunks: replayChunks, }) : null, }; } else { if (!captureOnly) { res.end(); } return { inspected: true, matched: false, status_code: upstreamResponse.status, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } } const chunkBuffer = Buffer.from(value); const now = new Date(); const inspection = inspectSseChunk(sseState, value, config); const retryableUpstreamError = inspection.retryable_upstream_error; if (retryableUpstreamError) { abortController.abort(); reader.cancel().catch(() => {}); return { inspected: true, matched: true, retry_requested: strict502Mode || !wroteAnyChunk, retryable_upstream_error: retryableUpstreamError, upstream_status_code: upstreamResponse.status, reasoning_tokens: observedReasoning, usage: observedUsage, error: `retryable upstream error: ${retryableUpstreamError.matched_pattern}`, match_reason: "retryable_upstream_error", response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } markRequestEntryFirstResponse(runtime, requestEntry, { persistEntry, at: now }); const reasoning = inspection.reasoning; const usageUpdated = Boolean(inspection.usage); observedUsage = mergeUsageSnapshots(observedUsage, inspection.usage); if (Number.isInteger(reasoning)) { observedReasoning = reasoning; } if (inspection.response_id) { requestEntry.response_id = inspection.response_id; } if (inspection.thread_id) { requestEntry.thread_id = inspection.thread_id; } applyThreadReasoningState(runtime, requestEntry, pathname); updateStreamingProgress(requestEntry, { chunkBytes: chunkBuffer.length, usage: inspection.usage, reasoning, at: now, }); if (persistEntry) { persistStreamingProgress( runtime, requestEntry, { usageUpdated, force: requestEntry.stream_chunk_count === 1 }, now, ); } if (reasoningMatched(runtime, config, requestEntry, reasoning)) { recordInspectedResponse(monitor, reasoning, true); if (config.log_match) { logger( `[match] stream path=${pathname} reasoning_tokens=${reasoning} action=${config.stream_action}`, ); } if (captureOnly) { abortController.abort(createAbortReason(REASONING_RETRY_ABORT_WINNER, "reasoning retry wave advanced")); reader.cancel().catch(() => {}); } else if (strict502Mode || !wroteAnyChunk) { abortController.abort(); reader.cancel().catch(() => {}); const blockedBody = buildBlockedBody(pathname, reasoning, config.non_stream_status_code); res.writeHead(config.non_stream_status_code, { "content-type": "application/json; charset=utf-8", "x-codex-retry-gateway-reason": "reasoning-guard-triggered", }); res.end(blockedBody); } else { abortController.abort(); reader.cancel().catch(() => {}); res.socket?.destroy(); } return { inspected: true, matched: true, match_reason: "reasoning_guard", status_code: config.non_stream_status_code, upstream_status_code: upstreamResponse.status, reasoning_tokens: reasoning, usage: observedUsage, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, response_bytes_received: requestEntry.response_bytes_received, stream_chunk_count: requestEntry.stream_chunk_count, }; } const outputChunks = codexResponsesSseState ? drainResponsesSseForCodex( codexResponsesSseState, value, requestEntry.response_id || null, ) : [chunkBuffer]; if (strict502Mode) { bufferedChunks.push(...outputChunks); } else { if (!captureOnly) { for (const outputChunk of outputChunks) { if (!outputChunk || outputChunk.length === 0) { continue; } wroteAnyChunk = true; res.write(outputChunk); } } } } } function buildAttemptRequestEntry(baseEntry) { return { ...baseEntry, lifecycle_state: "sent", first_response_at: null, first_response_delay_ms: null, last_activity_at: null, finished_at: null, duration_ms: null, response_bytes_received: 0, response_stream: false, stream_chunk_count: 0, usage_last_updated_at: null, upstream_attempt_count: 0, inspected: false, matched: false, status_code: null, upstream_status_code: null, reasoning_tokens: null, usage: null, error: null, _started_ms: Date.now(), }; } function classifyGatewayQueryResult(result) { if (result?.cancelled) { return "cancelled"; } if (result?.match_reason === "reasoning_guard") { return "reasoning_retry"; } if ( Number.isInteger(result?.status_code) && result.status_code >= 200 && result.status_code < 400 && !result?.error ) { return "success"; } return "fatal"; } async function executeGatewayQuery({ runtime, config, logger, monitor, pathname, req, res, requestBody, requestIsStream, upstreamUrl, upstreamAuth, requestEntry, captureOnly = false, persistEntry = true, externalAbortSignals = [], }) { const maxUpstreamAttempts = normalizePositiveInteger( config.upstream_fetch_retry_attempts, DEFAULT_CONFIG.upstream_fetch_retry_attempts, ); const shouldInspect = matchPath(config, pathname); let totalUpstreamAttempts = 0; const queryAbortLink = createLinkedAbortController(externalAbortSignals); try { while (totalUpstreamAttempts < maxUpstreamAttempts) { if (queryAbortLink.controller.signal.aborted) { throw queryAbortLink.controller.signal.reason || createAbortReason( REASONING_RETRY_ABORT_CLIENT, "request aborted", ); } const upstreamAbortLink = createLinkedAbortController([queryAbortLink.controller.signal]); try { const { response: upstreamResponse, attempt_count: upstreamAttemptCount, retryable_upstream_error: terminalRetryableUpstreamError, } = await fetchUpstreamWithRetry(upstreamUrl, { method: req.method, headers: cloneHeadersForUpstream(req.headers, upstreamAuth), body: requestBody.length > 0 ? requestBody : undefined, signal: upstreamAbortLink.controller.signal, }, { ...config, upstream_fetch_retry_attempts: Math.max(1, maxUpstreamAttempts - totalUpstreamAttempts), }, logger, { method: req.method, pathname }); totalUpstreamAttempts += upstreamAttemptCount; requestEntry.upstream_attempt_count = totalUpstreamAttempts; const responseContentType = upstreamResponse.headers.get("content-type"); const responseIsStream = isSseContentType(responseContentType) || ( requestIsStream && !isJsonContentType(responseContentType) && !isUpstreamErrorStatus(upstreamResponse.status) ); requestEntry.response_stream = responseIsStream; requestEntry.inspected = shouldInspect; requestEntry.upstream_status_code = upstreamResponse.status; const upstreamRoute = requestEntry.upstream?.route || "default"; requestEntry.upstream = { ...buildUpstreamSnapshot({ upstreamUrl, upstreamAuth, upstreamResponse }), route: upstreamRoute, }; if (persistEntry) { upsertRequestEntry(runtime, requestEntry); } if (isUpstreamErrorStatus(upstreamResponse.status)) { logger?.( `[upstream] status=${upstreamResponse.status} profile=${config.profile_name || "default"} path=${requestEntry.upstream.path} auth=${requestEntry.upstream.auth_mode}/${requestEntry.upstream.auth_source} content_type=${requestEntry.upstream.content_type || "-"}`, ); } if (!shouldInspect) { markRequestEntryFirstResponse(runtime, requestEntry, { persistEntry }); const body = Buffer.from(await upstreamResponse.arrayBuffer()); requestEntry.response_bytes_received = body.length; const parsed = isJsonContentType(responseContentType) ? parseJsonSafely(body) : null; requestEntry.response_id = requestEntry.response_id || (parsed ? extractNonStreamingResponseId(parsed) : null); requestEntry.thread_id = requestEntry.thread_id || (parsed ? extractResponseThreadId(parsed) : null); if (!captureOnly) { copyHeadersToClient(upstreamResponse.headers, res); res.writeHead(upstreamResponse.status); res.end(body); } return { response_stream: responseIsStream, inspected: false, matched: false, status_code: upstreamResponse.status, upstream_status_code: upstreamResponse.status, response_id: requestEntry.response_id, thread_id: requestEntry.thread_id, total_upstream_attempts: totalUpstreamAttempts, delivery: captureOnly ? buildCapturedDelivery(upstreamResponse.status, upstreamResponse.headers, body) : null, }; } const result = responseIsStream ? await handleStreaming({ runtime, config, logger, monitor, pathname, upstreamResponse, res, abortController: upstreamAbortLink.controller, requestEntry, requestAbortSignal: queryAbortLink.controller.signal, captureOnly, persistEntry, }) : await handleNonStreaming({ runtime, config, logger, monitor, pathname, upstreamResponse, res, requestEntry, terminalRetryableUpstreamError, captureOnly, persistEntry, }); if (result.retry_requested) { if (totalUpstreamAttempts < maxUpstreamAttempts) { const backoffMs = computeRetryBackoffMs(config, totalUpstreamAttempts); logger?.( `[retry] upstream retryable stream error attempt=${totalUpstreamAttempts} next_attempt=${totalUpstreamAttempts + 1} status=${upstreamResponse.status} path=${pathname || "-"} reason=${JSON.stringify(result.retryable_upstream_error?.matched_pattern)} backoff_ms=${backoffMs}`, ); if (backoffMs > 0) { await sleep(backoffMs); } continue; } recordInspectedResponse(runtime.monitor, result.reasoning_tokens, true); if (config.log_match) { logger?.( `[match] stream path=${pathname} upstream_status=${upstreamResponse.status} retryable_error=${JSON.stringify(result.retryable_upstream_error?.matched_pattern)} action=status_${config.non_stream_status_code}`, ); } const blockedBody = buildRetryableUpstreamErrorBody( pathname, upstreamResponse.status, result.retryable_upstream_error?.matched_message || result.retryable_upstream_error?.matched_pattern, config.non_stream_status_code, ); if (!captureOnly) { res.writeHead(config.non_stream_status_code, { "content-type": "application/json; charset=utf-8", "x-codex-retry-gateway-reason": "upstream-error-retry-triggered", }); res.end(blockedBody); } return { response_stream: true, ...result, matched: true, status_code: config.non_stream_status_code, upstream_status_code: upstreamResponse.status, total_upstream_attempts: totalUpstreamAttempts, delivery: captureOnly ? buildCapturedDelivery( config.non_stream_status_code, new Headers({ "content-type": "application/json; charset=utf-8", "x-codex-retry-gateway-reason": "upstream-error-retry-triggered", }), blockedBody, ) : null, }; } return { response_stream: responseIsStream, ...result, total_upstream_attempts: totalUpstreamAttempts, }; } finally { upstreamAbortLink.cleanup(); } } } catch (error) { const accumulatedAttempts = Number.isInteger(error?.gatewayAttemptCount) ? totalUpstreamAttempts + error.gatewayAttemptCount : totalUpstreamAttempts; if (error && typeof error === "object") { error.gatewayAttemptCount = accumulatedAttempts; } throw error; } finally { queryAbortLink.cleanup(); } throw new Error("gateway query exhausted unexpectedly"); } async function executeCapturedGatewayQuery(args) { try { const result = await executeGatewayQuery({ ...args, captureOnly: true, persistEntry: false, }); return { kind: classifyGatewayQueryResult(result), result, total_upstream_attempts: result.total_upstream_attempts || 0, }; } catch (error) { const abortCode = abortReasonCode(error); if (abortCode === REASONING_RETRY_ABORT_CLIENT) { throw error; } if (abortCode === REASONING_RETRY_ABORT_WINNER) { return { kind: "cancelled", result: { cancelled: true, cancel_reason: abortCode, }, total_upstream_attempts: Number.isInteger(error?.gatewayAttemptCount) ? error.gatewayAttemptCount : 0, }; } const gatewayErrorBody = buildGatewayErrorBody(`${error?.message || error}`); return { kind: "fatal", result: { inspected: false, matched: false, status_code: 502, upstream_status_code: null, error: `${error?.message || error}`, delivery: buildCapturedDelivery( 502, new Headers({ "content-type": "application/json; charset=utf-8" }), gatewayErrorBody, ), }, total_upstream_attempts: Number.isInteger(error?.gatewayAttemptCount) ? error.gatewayAttemptCount : 0, }; } } function createReasoningRetryExtraState() { return { inspected_count: 0, matched_count: 0, usage: null, reasoning_counts: {}, }; } function accumulateReasoningRetryExtras(state, outcomes, excludedResult = null) { for (const outcome of outcomes) { if (outcome?.kind === "cancelled") { continue; } const result = outcome?.result; if (!result?.inspected || result === excludedResult) { continue; } state.inspected_count += 1; if (result.matched) { state.matched_count += 1; } incrementReasoningCount(state.reasoning_counts, result.reasoning_tokens); state.usage = sumUsageSnapshots(state.usage, result.usage); } } function buildReasoningRetryFirstSlots(round, width) { return Array.from({ length: width }, (_, index) => ({ round, slot: index + 1, first_response_at: null, first_response_delay_ms: null, outcome: "pending", })); } function updateReasoningRetryFirstSlot(requestEntry, round, slot, fields = {}) { if (requestEntry.reasoning_retry_current_round !== round) { return false; } const currentFirsts = Array.isArray(requestEntry.reasoning_retry_current_firsts) ? requestEntry.reasoning_retry_current_firsts : []; const index = slot - 1; if (index < 0 || index >= currentFirsts.length) { return false; } const existing = currentFirsts[index] || { round, slot }; currentFirsts[index] = { ...existing, round, slot, ...fields, }; requestEntry.reasoning_retry_current_firsts = currentFirsts; return true; } function updateReasoningRetryFirstFromAttempt(requestEntry, outcome) { const attemptEntry = outcome?.requestEntry || {}; const result = outcome?.result || {}; return updateReasoningRetryFirstSlot(requestEntry, outcome.round, outcome.slot, { first_response_at: attemptEntry.first_response_at || null, first_response_delay_ms: Number.isInteger(attemptEntry.first_response_delay_ms) ? attemptEntry.first_response_delay_ms : null, outcome: outcome.kind || "unknown", status_code: Number.isInteger(result.status_code) ? result.status_code : null, upstream_status_code: Number.isInteger(result.upstream_status_code) ? result.upstream_status_code : null, reasoning_tokens: Number.isInteger(result.reasoning_tokens) ? result.reasoning_tokens : null, matched: Boolean(result.matched), }); } function markReasoningRetryCancelledSlots(requestEntry, round, slots = []) { let changed = false; for (const slot of slots) { changed = updateReasoningRetryFirstSlot(requestEntry, round, slot, { outcome: "cancelled" }) || changed; } return changed; } async function runResponsesReasoningRetry({ runtime, config, logger, monitor, pathname, req, requestBody, requestIsStream, upstreamUrl, upstreamAuth, requestEntry, clientAbortSignal, }) { let round = 1; let totalUpstreamAttempts = 0; const retryExtraState = createReasoningRetryExtraState(); while (true) { if (clientAbortSignal?.aborted) { throw clientAbortSignal.reason || createAbortReason(REASONING_RETRY_ABORT_CLIENT, "client disconnected"); } const width = reasoningRetryWaveWidth(round); requestEntry.reasoning_retry_round_count = round; requestEntry.reasoning_retry_current_round = round; requestEntry.reasoning_retry_current_width = width; requestEntry.reasoning_retry_current_firsts = buildReasoningRetryFirstSlots(round, width); requestEntry.reasoning_retry_query_count += width; if (requestEntry.reasoning_retry_enabled) { upsertRequestEntry(runtime, requestEntry); } logger?.( `[reasoning-retry] round=${round} width=${width} path=${pathname} thread_id=${requestEntry.thread_id}`, ); const slotControllers = Array.from({ length: width }, () => new AbortController()); let winner = null; const attempts = slotControllers.map((slotController, index) => { const slot = index + 1; const attemptEntry = buildAttemptRequestEntry(requestEntry); attemptEntry._first_response_observer = (firstFields) => { if (updateReasoningRetryFirstSlot(requestEntry, round, slot, firstFields)) { upsertRequestEntry(runtime, requestEntry); } }; const rawPromise = executeCapturedGatewayQuery({ runtime, config, logger, monitor, pathname, req, requestBody, requestIsStream, upstreamUrl, upstreamAuth, requestEntry: attemptEntry, externalAbortSignals: [clientAbortSignal, slotController.signal], }); return new Promise((resolve, reject) => { let settled = false; const finish = (fn, value) => { if (settled) { return; } settled = true; slotController.signal.removeEventListener("abort", onAbort); fn(value); }; const onAbort = () => { if (abortReasonCode(slotController.signal) !== REASONING_RETRY_ABORT_WINNER) { return; } finish(resolve, { kind: "cancelled", result: { cancelled: true, cancel_reason: REASONING_RETRY_ABORT_WINNER, }, total_upstream_attempts: 0, round, slot, requestEntry: attemptEntry, }); }; slotController.signal.addEventListener("abort", onAbort, { once: true }); rawPromise.then( (outcome) => { const attemptOutcome = { ...outcome, round, slot, requestEntry: attemptEntry, }; if (!winner && attemptOutcome.kind === "success") { winner = attemptOutcome; const cancelledSlots = []; slotControllers.forEach((otherController, otherIndex) => { const otherSlot = otherIndex + 1; if (otherController !== slotController && !otherController.signal.aborted) { cancelledSlots.push(otherSlot); otherController.abort( createAbortReason(REASONING_RETRY_ABORT_WINNER, "reasoning retry winner selected"), ); } }); if (cancelledSlots.length > 0) { markReasoningRetryCancelledSlots(requestEntry, round, cancelledSlots); upsertRequestEntry(runtime, requestEntry); } } finish(resolve, attemptOutcome); }, (error) => finish(reject, error), ); }); }); const outcomes = await Promise.all(attempts); totalUpstreamAttempts += outcomes.reduce((sum, outcome) => { return sum + (Number.isInteger(outcome.total_upstream_attempts) ? outcome.total_upstream_attempts : 0); }, 0); const nonCancelledOutcomes = outcomes.filter((outcome) => outcome.kind !== "cancelled"); for (const outcome of outcomes) { updateReasoningRetryFirstFromAttempt(requestEntry, outcome); } if (requestEntry.reasoning_retry_enabled) { upsertRequestEntry(runtime, requestEntry); } if (winner) { accumulateReasoningRetryExtras(retryExtraState, nonCancelledOutcomes, winner.result); requestEntry.reasoning_retry_extra_inspected_count = retryExtraState.inspected_count; requestEntry.reasoning_retry_extra_matched_count = retryExtraState.matched_count; requestEntry.reasoning_retry_extra_usage = retryExtraState.usage; requestEntry.reasoning_retry_extra_reasoning_counts = retryExtraState.reasoning_counts; requestEntry.reasoning_retry_winner_round = winner.round; requestEntry.reasoning_retry_winner_slot = winner.slot; requestEntry.reasoning_retry_stop_reason = "success"; return { ...winner.result, response_stream: winner.result.response_stream, total_upstream_attempts: totalUpstreamAttempts, }; } const allReasoningRetry = nonCancelledOutcomes.length > 0 && nonCancelledOutcomes.every( (outcome) => outcome.kind === "reasoning_retry", ); if (allReasoningRetry) { accumulateReasoningRetryExtras(retryExtraState, nonCancelledOutcomes); round += 1; continue; } const fatalOutcome = nonCancelledOutcomes.find((outcome) => outcome.kind === "fatal"); if (fatalOutcome) { accumulateReasoningRetryExtras(retryExtraState, nonCancelledOutcomes, fatalOutcome.result); requestEntry.reasoning_retry_extra_inspected_count = retryExtraState.inspected_count; requestEntry.reasoning_retry_extra_matched_count = retryExtraState.matched_count; requestEntry.reasoning_retry_extra_usage = retryExtraState.usage; requestEntry.reasoning_retry_extra_reasoning_counts = retryExtraState.reasoning_counts; requestEntry.reasoning_retry_stop_reason = fatalOutcome.result?.match_reason || "fatal"; return { ...fatalOutcome.result, response_stream: fatalOutcome.result.response_stream, total_upstream_attempts: totalUpstreamAttempts, }; } accumulateReasoningRetryExtras(retryExtraState, nonCancelledOutcomes); requestEntry.reasoning_retry_extra_inspected_count = retryExtraState.inspected_count; requestEntry.reasoning_retry_extra_matched_count = retryExtraState.matched_count; requestEntry.reasoning_retry_extra_usage = retryExtraState.usage; requestEntry.reasoning_retry_extra_reasoning_counts = retryExtraState.reasoning_counts; requestEntry.reasoning_retry_stop_reason = "exhausted_without_winner"; return { inspected: false, matched: false, status_code: 502, upstream_status_code: null, error: "reasoning retry ended without a successful response", response_stream: requestIsStream, total_upstream_attempts: totalUpstreamAttempts, delivery: buildCapturedDelivery( 502, new Headers({ "content-type": "application/json; charset=utf-8" }), buildGatewayErrorBody("reasoning retry ended without a successful response"), ), }; } } async function proxyRequest(runtime, req, res) { const { logger } = runtime; const config = runtime.config; const requestStartedAt = new Date(); const requestStartedMs = Date.now(); const incomingUrl = new URL(req.url, `http://${req.headers.host || "127.0.0.1"}`); const pathname = normalizePath(incomingUrl.pathname); if (pathname === "/favicon.ico") { res.writeHead(204, { "cache-control": "public, max-age=86400" }); res.end(); return; } if (pathname === config.health_path) { res.writeHead(200, { "content-type": "application/json; charset=utf-8" }); res.end( JSON.stringify({ ok: true, listen: `${config.listen_host}:${config.listen_port}`, upstream_base_url: config.upstream_base_url, ui_path: UI_PATH, }), ); return; } if (await handleManagementRequest(runtime, req, res, incomingUrl)) { return; } runtime.monitor.total_proxy_request_count += 1; const requestSeq = runtime.monitor.next_request_seq; runtime.monitor.next_request_seq += 1; const requestEntry = buildRequestEntry({ seq: requestSeq, startedAt: requestStartedAt, startedMs: requestStartedMs, req, pathname, requestJson: null, profileName: config.profile_name || "default", }); upsertRequestEntry(runtime, requestEntry); try { const rawRequestBody = await readRequestBody(req, config.request_body_limit_bytes); const parsedRequestJson = isJsonContentType(req.headers["content-type"]) ? parseJsonSafely(rawRequestBody) : null; const { requestJson, remapped, forwardedModel } = remapRequestModel(config, parsedRequestJson); const requestBody = remapped ? Buffer.from(JSON.stringify(requestJson)) : rawRequestBody; const requestIsStream = Boolean(requestJson?.stream); requestEntry.request_body_bytes = rawRequestBody.length; requestEntry.request_id = computeRequestId(pathname, rawRequestBody); requestEntry.thread_id = firstNonEmptyString( extractRequestThreadId(parsedRequestJson), extractHeaderThreadId(req.headers), ); requestEntry.model = requestJson?.model || null; requestEntry.requested_model = parsedRequestJson?.model || null; requestEntry.forwarded_model = forwardedModel || parsedRequestJson?.model || null; requestEntry.model = requestEntry.forwarded_model; requestEntry.reasoning_effort = extractRequestReasoningEffort(requestJson); requestEntry.reasoning_summary = extractRequestReasoningSummary(requestJson); requestEntry.request_stream = requestIsStream; applyThreadReasoningState(runtime, requestEntry, pathname); upsertRequestEntry(runtime, requestEntry); const upstreamRoute = selectUpstreamRoute(config, pathname); const upstreamRequestUrl = upstreamRoute.kind === "images" ? normalizeImageRequestUrl(incomingUrl) : incomingUrl; const upstreamUrl = buildUpstreamUrl(upstreamRoute.baseUrl, upstreamRequestUrl); const upstreamAuth = await resolveUpstreamAuth(upstreamRoute.authConfig, upstreamRoute.kind); requestEntry.upstream = { ...buildUpstreamSnapshot({ upstreamUrl, upstreamAuth }), route: upstreamRoute.kind, }; upsertRequestEntry(runtime, requestEntry); if (remapped && requestEntry.requested_model && requestEntry.forwarded_model) { logger?.( `[model-remap] profile=${config.profile_name || "default"} requested=${requestEntry.requested_model} forwarded=${requestEntry.forwarded_model}`, ); } const clientAbortContext = createClientAbortContext(req, res); try { const result = isResponsesReasoningRetryEligible(pathname, requestEntry) ? await runResponsesReasoningRetry({ runtime, config, logger, monitor: runtime.monitor, pathname, req, requestBody, requestIsStream, upstreamUrl, upstreamAuth, requestEntry, clientAbortSignal: clientAbortContext.signal, }) : await executeGatewayQuery({ runtime, config, logger, monitor: runtime.monitor, pathname, req, res, requestBody, requestIsStream, upstreamUrl, upstreamAuth, requestEntry, externalAbortSignals: [clientAbortContext.signal], }); const { delivery, total_upstream_attempts: observedUpstreamAttempts, ...resultFields } = result; if (delivery && !res.headersSent) { await writeCapturedResponse(res, delivery); } if (Number.isInteger(observedUpstreamAttempts)) { requestEntry.upstream_attempt_count = observedUpstreamAttempts; } if (requestEntry.reasoning_retry_enabled && !requestEntry.reasoning_retry_stop_reason) { requestEntry.reasoning_retry_stop_reason = "completed_without_retry"; } recordRequestEntry( runtime, finalizeRequestEntry(requestEntry, { response_stream: Boolean(result.response_stream), ...resultFields, }), config.request_history_limit, ); return; } finally { clientAbortContext.cleanup(); } } catch (error) { if (Number.isInteger(error?.gatewayAttemptCount)) { requestEntry.upstream_attempt_count = Math.max( requestEntry.upstream_attempt_count || 0, error.gatewayAttemptCount, ); } recordRequestEntry( runtime, finalizeRequestEntry(requestEntry, { status_code: res.headersSent ? null : 502, error: `${error?.message || error}`, }), config.request_history_limit, ); throw error; } } async function main() { const args = parseArgs(process.argv); const configPath = args.config || path.join(__dirname, "config.json"); const config = await loadConfig(configPath); const monitor = createMonitor(); const paths = buildRuntimePaths(configPath, args.log || null); if (args.log) { await mkdir(path.dirname(args.log), { recursive: true }); } const logger = createLogger(args.log, createMonitorRecorder(monitor)); const requestsDb = openRequestsDatabase(paths.requestsDbPath); const runtime = { config, configPath, logPath: args.log || null, logger, monitor, paths, threadRules: await loadThreadRules(paths.threadRulesPath), requestsDb, server: null, }; await ensureActiveImageProfile(runtime); const importedCount = await importRequestsJsonlToDb(requestsDb, runtime.paths.requestsPath); await hydrateMonitorFromDisk(monitor, runtime.paths, config.request_history_limit, requestsDb); logger(`[start] hydrated persistent request metrics from db path=${runtime.paths.requestsDbPath}`); logger(`[start] requests db ready path=${runtime.paths.requestsDbPath} imported_jsonl_rows=${importedCount}`); logger(`[start] thread rules ready path=${runtime.paths.threadRulesPath} count=${runtime.threadRules.size}`); const server = http.createServer(async (req, res) => { try { await proxyRequest(runtime, req, res); } catch (error) { logger(`[error] ${error?.stack || error}`); if (!res.headersSent) { res.writeHead(502, { "content-type": "application/json; charset=utf-8" }); res.end( JSON.stringify({ error: { message: `${error?.message || error}`, type: "codex_retry_gateway_error", code: "gateway_error", }, }), ); } else { res.socket?.destroy(); } } }); runtime.server = server; let shuttingDown = false; const shutdown = (signal) => { if (shuttingDown) { return; } shuttingDown = true; logger(`[stop] received ${signal}, closing gateway`); server.close(() => { process.exit(0); }); const hardExitTimer = setTimeout(() => { logger("[stop] forced exit after graceful shutdown timeout"); process.exit(signal === "SIGTERM" || signal === "SIGINT" ? 0 : 1); }, 5000); hardExitTimer.unref(); }; process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGINT", () => shutdown("SIGINT")); server.listen(runtime.config.listen_port, runtime.config.listen_host, () => { updateRuntimeState(runtime, { last_started_at: new Date().toISOString(), profile_name: runtime.config.profile_name || "default", image_profile_name: runtime.config.image_profile_name || "", gateway_base_url: `http://${runtime.config.listen_host}:${runtime.config.listen_port}`, }).catch((error) => logger(`[state] failed to update runtime state: ${error?.message || error}`)); logger( `[start] codex retry gateway profile=${runtime.config.profile_name || "default"} image_profile=${runtime.config.image_profile_name || "-"} auth=${normalizeAuthMode(runtime.config.upstream_auth_mode)} reasoning_match_mode=${normalizeReasoningMatchMode(runtime.config.reasoning_match_mode)} listening on http://${runtime.config.listen_host}:${runtime.config.listen_port} -> ${runtime.config.upstream_base_url}`, ); }); } main().catch((error) => { process.stderr.write(`${error?.stack || error}\n`); process.exit(1); });