diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 0544a90db3..4013722220 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -44,7 +44,8 @@ import { import { CORS_HEADERS } from "../utils/cors.ts"; import { checkHeapPressureGuard } from "../utils/heapPressure.ts"; import { normalizeHeaders } from "../utils/headers.ts"; -import { detectFormatFromEndpoint, getTargetFormat } from "../services/provider.ts"; +import { getTargetFormat } from "../services/provider.ts"; +import { resolveChatCoreRequestFormat } from "./chatCore/requestFormat.ts"; import { injectSystemPrompt } from "../services/systemPrompt.ts"; import { translateRequest, needsTranslation } from "../translator/index.ts"; import { FORMATS } from "../translator/formats.ts"; @@ -553,28 +554,6 @@ function buildExecutorClientHeaders( return Object.keys(normalized).length > 0 ? normalized : null; } -function isCopilotClient( - headers: Headers | Record | null | undefined, - userAgent?: string | null -) { - const isMatch = (value: unknown) => - typeof value === "string" && value.toLowerCase().includes("copilot"); - - if (isMatch(userAgent)) return true; - - if (headers instanceof Headers) { - for (const [key, value] of headers as unknown as Iterable<[string, string]>) { - if (isMatch(key) || isMatch(value)) return true; - } - } else if (headers && typeof headers === "object") { - for (const [key, value] of Object.entries(headers)) { - if (isMatch(key) || isMatch(value)) return true; - } - } - - return false; -} - export function extractSystemRoleMessages(payload: Record): void { if (!Array.isArray(payload.messages)) return; const messages = payload.messages as Array<{ role?: unknown; content?: unknown }>; @@ -878,22 +857,17 @@ export async function handleChatCore({ credentials.connectionId = connectionId; } - const endpointPath = String(clientRawRequest?.endpoint || ""); - const sourceFormat = detectFormatFromEndpoint(body, endpointPath); - const isResponsesEndpoint = - /\/responses(?=\/|$)/i.test(endpointPath) || /^responses(?=\/|$)/i.test(endpointPath); - const nativeCodexPassthrough = shouldUseNativeCodexPassthrough({ - provider, - sourceFormat, + // Endpoint/format resolution extracted to chatCore/requestFormat.ts (#3501); pure derivation + // from the inbound request, destructured so every downstream use stays byte-identical. + const { endpointPath, - }); - const isDroidCLI = - userAgent?.toLowerCase().includes("droid") || userAgent?.toLowerCase().includes("codex-cli"); - const copilotCompatibleReasoning = isCopilotClient(clientRawRequest?.headers, userAgent); - const clientResponseFormat = - sourceFormat === FORMATS.OPENAI_RESPONSES && !isResponsesEndpoint && !isDroidCLI - ? FORMATS.OPENAI - : sourceFormat; + sourceFormat, + isResponsesEndpoint, + nativeCodexPassthrough, + isDroidCLI, + copilotCompatibleReasoning, + clientResponseFormat, + } = resolveChatCoreRequestFormat({ clientRawRequest, body, provider, userAgent }); // Check for bypass patterns (warmup, skip) - return fake response const bypassResponse = handleBypassRequest(body, model, userAgent); diff --git a/open-sse/handlers/chatCore/requestFormat.ts b/open-sse/handlers/chatCore/requestFormat.ts new file mode 100644 index 0000000000..63ad12589b --- /dev/null +++ b/open-sse/handlers/chatCore/requestFormat.ts @@ -0,0 +1,82 @@ +/** + * chatCore request endpoint/format resolvers (Quality Gate v2 / Fase 9 — chatCore god-file + * decomposition, #3501). + * + * Pure slice of handleChatCore's request-setup phase: derives the wire-format facts of an inbound + * request from its endpoint, body, provider, and user-agent — the source format, whether it targets + * the Responses endpoint, native-Codex passthrough eligibility, Droid CLI / Copilot detection, and + * the effective client response format (an OpenAI Responses shape off a non-/responses, non-Droid + * endpoint collapses back to plain OpenAI). Side-effect-free; behaviour is byte-identical to the + * previous inline block. Sits alongside resolveChatCoreRequestSetup as the request-setup phase grows. + */ + +import { detectFormatFromEndpoint } from "../../services/provider.ts"; +import { shouldUseNativeCodexPassthrough } from "./passthroughHelpers.ts"; +import { FORMATS } from "../../translator/formats.ts"; + +/** True when the request originates from a Copilot client (matched by user-agent or any header). */ +function isCopilotClient( + headers: Headers | Record | null | undefined, + userAgent?: string | null +) { + const isMatch = (value: unknown) => + typeof value === "string" && value.toLowerCase().includes("copilot"); + + if (isMatch(userAgent)) return true; + + if (headers instanceof Headers) { + for (const [key, value] of headers as unknown as Iterable<[string, string]>) { + if (isMatch(key) || isMatch(value)) return true; + } + } else if (headers && typeof headers === "object") { + for (const [key, value] of Object.entries(headers)) { + if (isMatch(key) || isMatch(value)) return true; + } + } + + return false; +} + +/** + * Resolve the per-request endpoint/format facts at the top of handleChatCore. Pure: a function of + * the inbound endpoint, the (possibly already-mutated) body, the resolved provider, and the + * user-agent. + */ +export function resolveChatCoreRequestFormat(opts: { + clientRawRequest: + | { endpoint?: unknown; headers?: Headers | Record | null } + | null + | undefined; + body: unknown; + provider: string | null | undefined; + userAgent: string | null | undefined; +}) { + const { clientRawRequest, body, provider, userAgent } = opts; + const endpointPath = String(clientRawRequest?.endpoint || ""); + const sourceFormat = detectFormatFromEndpoint(body, endpointPath); + const isResponsesEndpoint = + /\/responses(?=\/|$)/i.test(endpointPath) || /^responses(?=\/|$)/i.test(endpointPath); + const nativeCodexPassthrough = shouldUseNativeCodexPassthrough({ + provider, + sourceFormat, + endpointPath, + }); + const isDroidCLI = + userAgent?.toLowerCase().includes("droid") || userAgent?.toLowerCase().includes("codex-cli"); + const copilotCompatibleReasoning = isCopilotClient(clientRawRequest?.headers, userAgent); + const clientResponseFormat = + sourceFormat === FORMATS.OPENAI_RESPONSES && !isResponsesEndpoint && !isDroidCLI + ? FORMATS.OPENAI + : sourceFormat; + return { + endpointPath, + sourceFormat, + isResponsesEndpoint, + nativeCodexPassthrough, + isDroidCLI, + copilotCompatibleReasoning, + clientResponseFormat, + }; +} + +export type ChatCoreRequestFormat = ReturnType; diff --git a/tests/unit/chatcore-request-format.test.ts b/tests/unit/chatcore-request-format.test.ts new file mode 100644 index 0000000000..064a2384f2 --- /dev/null +++ b/tests/unit/chatcore-request-format.test.ts @@ -0,0 +1,103 @@ +// tests/unit/chatcore-request-format.test.ts +// Characterization of resolveChatCoreRequestFormat — the endpoint/format resolution slice extracted +// from the top of handleChatCore (chatCore god-file decomposition, #3501). Locks the endpointPath +// construction, the /responses detection, the nativeCodexPassthrough + isDroidCLI + copilot wiring, +// and the clientResponseFormat downgrade (OpenAI Responses shape off a non-/responses, non-Droid +// endpoint collapses to plain OpenAI). +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { resolveChatCoreRequestFormat } from "../../open-sse/handlers/chatCore/requestFormat.ts"; +import { shouldUseNativeCodexPassthrough } from "../../open-sse/handlers/chatCore/passthroughHelpers.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; + +const base = { body: { messages: [{ role: "user", content: "hi" }] }, provider: "openai", userAgent: "unit-test" }; + +test("chat/completions endpoint → openai source, not a responses endpoint, no downgrade", () => { + const r = resolveChatCoreRequestFormat({ + ...base, + clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Headers() }, + }); + assert.equal(r.endpointPath, "/v1/chat/completions"); + assert.equal(r.sourceFormat, FORMATS.OPENAI); + assert.equal(r.isResponsesEndpoint, false); + assert.equal(r.clientResponseFormat, FORMATS.OPENAI); + assert.equal(r.nativeCodexPassthrough, false); // provider !== codex + assert.equal(r.isDroidCLI, false); + assert.equal(r.copilotCompatibleReasoning, false); +}); + +test("/responses endpoint → openai-responses source + isResponsesEndpoint, kept (no downgrade)", () => { + const r = resolveChatCoreRequestFormat({ + body: { input: "x" }, + provider: "openai", + userAgent: "unit-test", + clientRawRequest: { endpoint: "/v1/responses", headers: new Headers() }, + }); + assert.equal(r.sourceFormat, FORMATS.OPENAI_RESPONSES); + assert.equal(r.isResponsesEndpoint, true); + assert.equal(r.clientResponseFormat, FORMATS.OPENAI_RESPONSES); +}); + +test("Responses-shaped body on a /chat/completions endpoint downgrades clientResponseFormat to openai", () => { + const r = resolveChatCoreRequestFormat({ + body: { input: "describe" }, // input + no messages → openai-responses via body + provider: "openai", + userAgent: "unit-test", + clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Headers() }, + }); + assert.equal(r.sourceFormat, FORMATS.OPENAI_RESPONSES); + assert.equal(r.isResponsesEndpoint, false); + assert.equal(r.clientResponseFormat, FORMATS.OPENAI); // downgraded +}); + +test("Droid CLI suppresses the downgrade (clientResponseFormat stays openai-responses)", () => { + const r = resolveChatCoreRequestFormat({ + body: { input: "describe" }, + provider: "openai", + userAgent: "Droid/1.2 codex-cli", + clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Headers() }, + }); + assert.equal(r.isDroidCLI, true); + assert.equal(r.sourceFormat, FORMATS.OPENAI_RESPONSES); + assert.equal(r.clientResponseFormat, FORMATS.OPENAI_RESPONSES); // !isDroidCLI is false → no downgrade +}); + +test("copilotCompatibleReasoning detects copilot via header or user-agent", () => { + const viaUa = resolveChatCoreRequestFormat({ + ...base, + userAgent: "GitHubCopilotChat/0.1", + clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Headers() }, + }); + assert.equal(viaUa.copilotCompatibleReasoning, true); + + const viaHeader = resolveChatCoreRequestFormat({ + ...base, + clientRawRequest: { + endpoint: "/v1/chat/completions", + headers: new Headers({ "x-client": "copilot-vscode" }), + }, + }); + assert.equal(viaHeader.copilotCompatibleReasoning, true); +}); + +test("nativeCodexPassthrough delegates to shouldUseNativeCodexPassthrough (codex + responses)", () => { + const r = resolveChatCoreRequestFormat({ + body: { input: "x" }, + provider: "codex", + userAgent: "unit-test", + clientRawRequest: { endpoint: "/v1/responses", headers: new Headers() }, + }); + assert.equal( + r.nativeCodexPassthrough, + shouldUseNativeCodexPassthrough({ + provider: "codex", + sourceFormat: r.sourceFormat, + endpointPath: r.endpointPath, + }) + ); +}); + +test("missing clientRawRequest → empty endpointPath", () => { + const r = resolveChatCoreRequestFormat({ ...base, clientRawRequest: null }); + assert.equal(r.endpointPath, ""); +});