Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
18.8 kB
import { NextResponse } from "next/server";
import { createHash, timingSafeEqual } from "node:crypto";
import { z } from "zod";
import { CodexExecutor } from "@omniroute/open-sse/executors/codex.ts";
import { getApiKeyMetadata } from "@/lib/db/apiKeys";
import { authorizeWebSocketHandshake, extractWsTokenFromRequest } from "@/lib/ws/handshake";
import { getModelInfo } from "@/sse/services/model";
import { getProviderCredentialsWithQuotaPreflight } from "@/sse/services/auth";
import { checkAndRefreshToken } from "@/sse/services/tokenRefresh";
import { resolveCodexWsModelInfo } from "./modelResolution";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
import { formatMemoryContext } from "@/lib/memory/injection";
import { retrieveMemories } from "@/lib/memory/retrieval";
import {
DEFAULT_MEMORY_SETTINGS,
getMemorySettings,
toMemoryRetrievalConfig,
} from "@/lib/memory/settings";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
import { logger } from "@omniroute/open-sse/utils/logger.ts";
const CODEX_RESPONSES_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
const executor = new CodexExecutor();
const log = logger("RESPONSES_WS");
type JsonRecord = Record<string, unknown>;
type ApiKeyMetadata = Awaited<ReturnType<typeof getApiKeyMetadata>>;
const bridgePayloadSchema = z
.object({
action: z.string().optional(),
requestUrl: z.string().optional(),
headers: z.record(z.string(), z.unknown()).optional(),
response: z.record(z.string(), z.unknown()).optional(),
})
.passthrough();
function isRecord(value: unknown): value is JsonRecord {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function toStringOrNull(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
const RESPONSES_WS_MEMORY_CONTEXT_PREFIX = "Memory context:";
const RESPONSES_WS_MEMORY_TEXT_PART_TYPES = new Set(["text", "input_text", "output_text"]);
const RESPONSES_WS_MEMORY_SKIP_ITEM_TYPES = new Set([
"function_call",
"function_call_output",
"tool_call",
"tool_call_output",
"reasoning",
"computer_call",
"computer_call_output",
"web_search_call",
"file_search_call",
]);
function compactText(parts: Array<string | null>): string | null {
const text = parts
.map((part) => (typeof part === "string" ? part.trim() : ""))
.filter(Boolean)
.join("\n");
return text.length > 0 ? text : null;
}
function extractResponsesWsContentText(value: unknown): string | null {
if (typeof value === "string") return toStringOrNull(value);
if (Array.isArray(value)) {
return compactText(
value.map((part) => {
if (typeof part === "string") return toStringOrNull(part);
if (!isRecord(part)) return null;
const type = typeof part.type === "string" ? part.type : "";
if (type && !RESPONSES_WS_MEMORY_TEXT_PART_TYPES.has(type)) return null;
return toStringOrNull(part.text) || toStringOrNull(part.input_text);
})
);
}
if (isRecord(value)) {
const type = typeof value.type === "string" ? value.type : "";
if (type && !RESPONSES_WS_MEMORY_TEXT_PART_TYPES.has(type)) return null;
return toStringOrNull(value.text) || toStringOrNull(value.input_text);
}
return null;
}
function extractResponsesWsItemText(value: unknown): string | null {
if (typeof value === "string") return toStringOrNull(value);
if (!isRecord(value)) return null;
return (
extractResponsesWsContentText(value.content) ||
extractResponsesWsContentText(value.text) ||
extractResponsesWsContentText(value.input_text) ||
extractResponsesWsContentText(value.output_text) ||
extractResponsesWsContentText(value.output)
);
}
function isResponsesWsMemoryCandidate(value: unknown): boolean {
if (!isRecord(value)) return typeof value === "string";
const type = typeof value.type === "string" ? value.type : "";
return !RESPONSES_WS_MEMORY_SKIP_ITEM_TYPES.has(type);
}
function extractLatestResponsesWsInputText(input: unknown): string | null {
if (typeof input === "string") return toStringOrNull(input);
if (!Array.isArray(input)) return null;
for (let index = input.length - 1; index >= 0; index -= 1) {
const item = input[index];
if (!isResponsesWsMemoryCandidate(item) || !isRecord(item) || item.role !== "user") continue;
const text = extractResponsesWsItemText(item);
if (text) return text;
}
for (let index = input.length - 1; index >= 0; index -= 1) {
const item = input[index];
if (!isResponsesWsMemoryCandidate(item)) continue;
const text = extractResponsesWsItemText(item);
if (text) return text;
}
return null;
}
export function extractResponsesWsMemoryQuery(body: JsonRecord): string {
return (
extractLatestResponsesWsInputText(body.input) ||
extractLatestResponsesWsInputText(body.messages) ||
toStringOrNull(body.prompt) ||
toStringOrNull(body.instructions) ||
""
);
}
export function injectResponsesWsMemoryInstructions(
body: JsonRecord,
memoryText: string
): JsonRecord {
const memoryContext = toStringOrNull(memoryText);
if (!memoryContext) return body;
const existingInstructions = toStringOrNull(body.instructions);
if (existingInstructions?.includes(RESPONSES_WS_MEMORY_CONTEXT_PREFIX)) return body;
return {
...body,
instructions: [memoryContext, existingInstructions].filter(Boolean).join("\n\n"),
};
}
async function getMemorySettingsForResponsesWs() {
try {
return await getMemorySettings();
} catch (error) {
log.warn("memory.settings.defaulted", {
error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
});
return DEFAULT_MEMORY_SETTINGS;
}
}
async function maybeInjectResponsesWsMemory(
responseBody: JsonRecord,
metadata: ApiKeyMetadata | null
): Promise<JsonRecord> {
if (!metadata?.id) return responseBody;
const query = extractResponsesWsMemoryQuery(responseBody);
if (!query) return responseBody;
try {
const memorySettings = await getMemorySettingsForResponsesWs();
const memories = await retrieveMemories(
metadata.id,
toMemoryRetrievalConfig(memorySettings, { query })
);
const memoryText = formatMemoryContext(memories);
return injectResponsesWsMemoryInstructions(responseBody, memoryText);
} catch (error) {
log.warn("memory.injection.skipped", {
error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
});
return responseBody;
}
}
function toFiniteNumber(value: unknown, fallback = 0): number {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
function toHttpStatus(value: unknown, fallback: number): number {
const status = Number(value);
return Number.isInteger(status) && status >= 100 && status <= 599 ? status : fallback;
}
function getResponseCreateBody(body: JsonRecord): JsonRecord {
if (isRecord(body.clientRequest)) return body.clientRequest;
if (isRecord(body.response)) return body.response;
return {};
}
function getTerminalMessage(body: JsonRecord): JsonRecord | null {
return isRecord(body.terminalMessage) ? body.terminalMessage : null;
}
function getTerminalResponseBody(body: JsonRecord): JsonRecord | null {
if (isRecord(body.responseBody)) return body.responseBody;
const terminalMessage = getTerminalMessage(body);
if (isRecord(terminalMessage?.response)) return terminalMessage.response;
return terminalMessage;
}
function getErrorRecord(body: JsonRecord, responseBody: JsonRecord | null): JsonRecord | null {
if (isRecord(body.error)) return body.error;
if (isRecord(responseBody?.error)) return responseBody.error;
const terminalMessage = getTerminalMessage(body);
if (isRecord(terminalMessage?.error)) return terminalMessage.error;
return null;
}
function getTimestamp(value: unknown): string {
const raw = toStringOrNull(value);
if (!raw) return new Date().toISOString();
const parsed = new Date(raw);
return Number.isNaN(parsed.getTime()) ? new Date().toISOString() : parsed.toISOString();
}
function getRequestPath(body: JsonRecord): string {
const explicitPath = toStringOrNull(body.path);
if (explicitPath) return explicitPath;
try {
const requestUrl = toStringOrNull(body.requestUrl) || "/v1/responses";
return new URL(requestUrl, "http://omniroute.local").pathname;
} catch {
return "/v1/responses";
}
}
function getServiceTier(requestBody: JsonRecord): string | null {
return toStringOrNull(requestBody.service_tier) || toStringOrNull(requestBody.serviceTier);
}
async function getApiKeyMetadataFromBody(body: JsonRecord) {
const authRequest = getAuthRequest(body);
const apiKey = extractWsTokenFromRequest(authRequest);
return apiKey ? getApiKeyMetadata(apiKey).catch(() => null) : null;
}
function getBridgeSecret(): string {
return process.env.OMNIROUTE_WS_BRIDGE_SECRET || "";
}
function hashBridgeSecret(value: string): Buffer {
return createHash("sha256").update(value).digest();
}
export function bridgeSecretMatches(expectedSecret: string, receivedSecret: string): boolean {
if (!expectedSecret || !receivedSecret) return false;
const expectedHash = hashBridgeSecret(expectedSecret);
const receivedHash = hashBridgeSecret(receivedSecret);
return timingSafeEqual(expectedHash, receivedHash);
}
function getAuthRequest(body: JsonRecord): Request {
const requestUrl = typeof body.requestUrl === "string" ? body.requestUrl : "/api/v1/responses";
const headers = isRecord(body.headers) ? body.headers : {};
const url = new URL(requestUrl, "http://omniroute.local");
const requestHeaders = new Headers();
for (const [key, value] of Object.entries(headers)) {
if (typeof value === "string") {
requestHeaders.set(key, value);
}
}
return new Request(url, { headers: requestHeaders });
}
function jsonError(status: number, code: string, message: string) {
return NextResponse.json(
{
error: {
code,
message,
},
},
{ status }
);
}
function normalizeUpstreamHeaders(headers: Record<string, string>): Record<string, string> {
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(headers)) {
const lower = key.toLowerCase();
if (
lower === "host" ||
lower === "connection" ||
lower === "upgrade" ||
lower === "sec-websocket-key" ||
lower === "sec-websocket-version" ||
lower === "sec-websocket-extensions"
) {
continue;
}
result[key] = value;
}
result.Origin = "https://chatgpt.com";
return result;
}
async function authenticate(body: JsonRecord) {
const authRequest = getAuthRequest(body);
const auth = await authorizeWebSocketHandshake(authRequest);
if (!auth.authorized) {
return jsonError(
auth.hasCredential ? 403 : 401,
auth.hasCredential ? "ws_auth_invalid" : "ws_auth_required",
auth.hasCredential ? "Invalid WebSocket credential" : "WebSocket auth required"
);
}
return NextResponse.json({
ok: true,
authenticated: auth.authenticated,
authType: auth.authType,
wsAuth: auth.wsAuth,
});
}
async function prepare(body: JsonRecord) {
// Global kill-switch (feature flag OMNIROUTE_CODEX_WS_ENABLED, default ON).
// When disabled, the public Responses-over-WebSocket endpoint is unavailable.
if (!isFeatureFlagEnabled("OMNIROUTE_CODEX_WS_ENABLED")) {
return jsonError(503, "codex_ws_disabled", "Codex Responses WebSocket transport is disabled");
}
const authResponse = await authenticate(body);
if (!authResponse.ok) return authResponse;
const authRequest = getAuthRequest(body);
const apiKey = extractWsTokenFromRequest(authRequest);
const metadata = apiKey ? await getApiKeyMetadata(apiKey).catch(() => null) : null;
const allowedConnections =
metadata && Array.isArray(metadata.allowedConnections) && metadata.allowedConnections.length > 0
? metadata.allowedConnections
: null;
const responseBody = isRecord(body.response) ? body.response : {};
const requestedModel =
typeof responseBody.model === "string" && responseBody.model.trim()
? responseBody.model.trim()
: "gpt-5.5";
// codex-only bridge: re-resolve bare ChatGPT model ids (the Codex CLI rejects
// provider-prefixed ids client-side over WebSocket) as codex models.
const modelInfo = await resolveCodexWsModelInfo(requestedModel, getModelInfo);
const provider = modelInfo.provider;
const model = modelInfo.model || requestedModel;
if (provider !== "codex") {
return jsonError(
400,
"codex_ws_provider_required",
`Responses WebSocket bridge only supports Codex models, got ${provider || "unknown"}`
);
}
const credentials = await getProviderCredentialsWithQuotaPreflight(
provider,
null,
allowedConnections,
model
);
if (!credentials || "allRateLimited" in credentials) {
return jsonError(
503,
"codex_credentials_unavailable",
"No available Codex OAuth connection for Responses WebSocket"
);
}
const refreshedCredentials = await checkAndRefreshToken(provider, credentials);
if (!refreshedCredentials?.accessToken) {
return jsonError(401, "codex_oauth_token_missing", "Codex OAuth access token is missing");
}
const responseBodyWithMemory = await maybeInjectResponsesWsMemory(responseBody, metadata);
const transformed = (await executor.transformRequest(
model,
responseBodyWithMemory,
true,
refreshedCredentials
)) as JsonRecord;
transformed.model = model;
delete transformed.stream;
delete transformed.stream_options;
const headers = normalizeUpstreamHeaders(executor.buildHeaders(refreshedCredentials, true));
return NextResponse.json({
ok: true,
upstreamUrl: CODEX_RESPONSES_WS_URL,
browser: "chrome_142",
os: "windows",
connectionId: refreshedCredentials.connectionId,
provider,
account: refreshedCredentials.email || null,
model,
headers,
response: transformed,
});
}
async function persistResponsesWsCallHistory(body: JsonRecord) {
const [{ saveCallLog }, { saveRequestUsage }, { logProxyEvent }] = await Promise.all([
import("@/lib/usage/callLogs"),
import("@/lib/usage/usageHistory"),
import("@/lib/proxyLogger"),
]);
const metadata = await getApiKeyMetadataFromBody(body);
const requestBody = getResponseCreateBody(body);
const terminalMessage = getTerminalMessage(body);
const responseBody = getTerminalResponseBody(body);
const usage = isRecord(responseBody?.usage) ? responseBody.usage : {};
const errorRecord = getErrorRecord(body, responseBody);
const status = toHttpStatus(
body.status ?? errorRecord?.status_code ?? errorRecord?.status,
body.success === false ? 500 : 200
);
const success = typeof body.success === "boolean" ? body.success : status < 400;
const errorCode =
toStringOrNull(body.errorCode) ||
toStringOrNull(errorRecord?.code) ||
(success ? null : "responses_websocket_failed");
const errorMessage = success
? null
: sanitizeErrorMessage(
toStringOrNull(body.errorMessage) ||
toStringOrNull(errorRecord?.message) ||
"Responses WebSocket request failed"
);
const timestamp = getTimestamp(body.startedAt);
const durationMs = Math.max(0, Math.round(toFiniteNumber(body.durationMs, 0)));
const provider = toStringOrNull(body.provider) || "codex";
const model =
toStringOrNull(body.model) ||
toStringOrNull(responseBody?.model) ||
toStringOrNull(requestBody.model) ||
"-";
const requestedModel = toStringOrNull(body.requestedModel) || toStringOrNull(requestBody.model);
const connectionId = toStringOrNull(body.connectionId);
const apiKeyId = metadata?.id || null;
const apiKeyName = metadata?.name || null;
const noLog = metadata?.noLog === true;
const path = getRequestPath(body);
const sourceFormat = toStringOrNull(body.sourceFormat) || "openai-responses";
const targetFormat = toStringOrNull(body.targetFormat) || "openai-responses";
const targetUrl = toStringOrNull(body.upstreamUrl) || CODEX_RESPONSES_WS_URL;
const account = toStringOrNull(body.account);
await saveCallLog({
id: toStringOrNull(body.sessionId) || undefined,
timestamp,
method: "WEBSOCKET",
path,
status,
model,
requestedModel,
provider,
connectionId,
duration: durationMs,
tokens: usage,
requestType: "responses_websocket",
sourceFormat,
targetFormat,
apiKeyId,
apiKeyName,
noLog,
requestBody,
responseBody: responseBody ?? terminalMessage,
error: errorMessage ? { code: errorCode, message: errorMessage } : null,
pipelinePayloads: {
clientRequest: requestBody,
providerRequest: requestBody,
providerResponse: responseBody,
clientResponse: terminalMessage,
},
});
await saveRequestUsage({
timestamp,
provider,
model,
connectionId,
apiKeyId,
apiKeyName,
tokens: usage,
serviceTier: getServiceTier(requestBody),
status: String(status),
success,
latencyMs: durationMs,
timeToFirstTokenMs: durationMs,
errorCode,
endpoint: "/v1/responses",
});
logProxyEvent({
status: success ? "success" : "error",
level: "direct",
provider,
targetUrl,
latencyMs: durationMs,
error: errorMessage,
connectionId,
account,
});
return NextResponse.json({ ok: true, logged: true });
}
export async function POST(request: Request) {
const expectedSecret = getBridgeSecret();
const receivedSecret = request.headers.get("x-omniroute-ws-bridge-secret") || "";
if (!bridgeSecretMatches(expectedSecret, receivedSecret)) {
return jsonError(403, "internal_bridge_forbidden", "Forbidden");
}
let body: JsonRecord;
try {
const parsed = bridgePayloadSchema.safeParse(await request.json());
if (!parsed.success) {
return jsonError(400, "invalid_json", "Request body must be a JSON object");
}
body = parsed.data as JsonRecord;
} catch {
return jsonError(400, "invalid_json", "Request body must be JSON");
}
const action = typeof body.action === "string" ? body.action : "";
if (action === "authenticate") {
return authenticate(body);
}
if (action === "prepare") {
return prepare(body);
}
if (action === "log") {
try {
return await persistResponsesWsCallHistory(body);
} catch (error) {
return jsonError(
500,
"responses_ws_history_log_failed",
sanitizeErrorMessage(error instanceof Error ? error.message : String(error))
);
}
}
return jsonError(400, "invalid_action", "Unsupported bridge action");
}