From fdb3163e2e3745938353b748f0d4aa96771eb246 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sun, 21 Jun 2026 08:04:24 -0300 Subject: [PATCH 1/7] refactor(chatCore): extrai recordKeyHealthStatus para leaf dedicado (#3501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move o closure `recordKeyHealthStatus` (~56 ln) do topo de handleChatCore para o novo leaf open-sse/handlers/chatCore/keyHealth.ts, byte-idêntico: 401 → recordKeyFailure + persist sempre; 2xx → recordKeySuccess + persist só na recuperação de warning/invalid; demais status apenas atualizam o set de extra-keys. O handler mantém um closure fino de binding que repassa `log`, então os 2 call sites ficam inalterados. Imports que ficaram órfãos (recordKeyFailure / recordKeySuccess / trackConnectionExtraKeys / KeyHealth) migram para o leaf; connectionHasExtraKeys permanece (ainda usado no site de rotação) e updateProviderConnection é importado em ambos (usado em vários pontos do handler). chatCore.ts 5110->5055 (shrink -55); baseline file-size ratchetado. complexity 1905=1905 (neutro). Coberto por tests/unit/chatcore-key-health.test.ts (6 casos — transições in-memory do apiKeyRotator: warning/invalid no threshold, recuperação 2xx, escopo por selectedKeyId, no-op sem connectionId e em status fora de 401/2xx). --- open-sse/handlers/chatCore.ts | 65 ++----------------- open-sse/handlers/chatCore/keyHealth.ts | 84 +++++++++++++++++++++++++ tests/unit/chatcore-key-health.test.ts | 70 +++++++++++++++++++++ 3 files changed, 159 insertions(+), 60 deletions(-) create mode 100644 open-sse/handlers/chatCore/keyHealth.ts create mode 100644 tests/unit/chatcore-key-health.test.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index dd733d926cfa..cc11cc99f399 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -120,13 +120,8 @@ import { } from "../services/errorClassifier.ts"; import { updateProviderConnection, getProviderConnectionById } from "@/lib/db/providers"; import { wasRefreshTokenRotated } from "@omniroute/open-sse/services/refreshSerializer.ts"; -import { - recordKeyFailure, - recordKeySuccess, - trackConnectionExtraKeys, - connectionHasExtraKeys, - type KeyHealth, -} from "../services/apiKeyRotator.ts"; +import { connectionHasExtraKeys } from "../services/apiKeyRotator.ts"; +import { recordKeyHealthStatus as recordKeyHealthStatusFor } from "./chatCore/keyHealth.ts"; import { getCallLogPipelineCaptureStreamChunks, @@ -757,62 +752,12 @@ export async function handleChatCore({ }).catch(() => {}); }; + // Key-health updater extracted to chatCore/keyHealth.ts (#3501); bind the per-request log once + // and delegate so the existing call sites stay byte-identical. const recordKeyHealthStatus = ( status: number, creds: Record | null | undefined - ): void => { - const connId = creds?.connectionId as string | undefined; - if (!connId) return; - - const psd = creds.providerSpecificData as Record | undefined; - const extraKeys = (psd?.extraApiKeys as string[] | undefined) ?? []; - const health = psd?.apiKeyHealth as Record | undefined; - const currentKeyId = (psd?.selectedKeyId as string | undefined) ?? "primary"; - - trackConnectionExtraKeys(connId, extraKeys); - - if (status === 401) { - const updatedHealth = recordKeyFailure(connId, currentKeyId); - log?.warn?.( - "AUTH", - `401 on connection ${connId.slice(0, 8)} - key marked as failed (failure #${updatedHealth.failures})` - ); - - // Persist health status to DB on every failure (not just invalid transitions) - // This ensures in-memory state survives process restarts - const prevStatus = health?.[currentKeyId]?.status; - const prevFailures = health?.[currentKeyId]?.failures ?? 0; - if (updatedHealth.status !== prevStatus || updatedHealth.failures !== prevFailures) { - updateProviderConnection(connId, { - providerSpecificData: { - ...psd, - apiKeyHealth: { ...health, [currentKeyId]: updatedHealth }, - }, - }).catch((err: unknown) => { - log?.error?.( - "DB", - `Failed to persist apiKeyHealth: ${err instanceof Error ? err.message : String(err)}` - ); - }); - } - } else if (status >= 200 && status < 300) { - const updatedHealth = recordKeySuccess(connId, currentKeyId); - const prevStatus = health?.[currentKeyId]?.status; - if (prevStatus === "warning" || prevStatus === "invalid") { - updateProviderConnection(connId, { - providerSpecificData: { - ...psd, - apiKeyHealth: { ...health, [currentKeyId]: updatedHealth }, - }, - }).catch((err: unknown) => { - log?.error?.( - "DB", - `Failed to persist apiKeyHealth: ${err instanceof Error ? err.message : String(err)}` - ); - }); - } - } - }; + ): void => recordKeyHealthStatusFor(status, creds, log); const persistCodexQuotaState = async (headers: Record | null, status = 0) => { if (provider !== "codex" || !connectionId || !headers) return; diff --git a/open-sse/handlers/chatCore/keyHealth.ts b/open-sse/handlers/chatCore/keyHealth.ts new file mode 100644 index 000000000000..91e7c63618a8 --- /dev/null +++ b/open-sse/handlers/chatCore/keyHealth.ts @@ -0,0 +1,84 @@ +/** + * chatCore per-request API-key health updater (Quality Gate v2 / Fase 9 — chatCore god-file + * decomposition, #3501). + * + * Byte-identical extraction of the `recordKeyHealthStatus` closure that lived at the top of + * handleChatCore. Translates an upstream HTTP status into the in-memory key-health state + * (apiKeyRotator) for the connection's currently-selected key, and persists the change to the + * provider connection so it survives process restarts: + * - 401 → record a failure (warning, then invalid at the threshold), always persisted. + * - 2xx → record a success, persisted only when recovering from a warning/invalid state. + * Any other status only refreshes the tracked extra-key set. The handler binds its `log` once and + * delegates here, keeping the existing call sites unchanged. + */ + +import { + recordKeyFailure, + recordKeySuccess, + trackConnectionExtraKeys, + type KeyHealth, +} from "../../services/apiKeyRotator.ts"; +import { updateProviderConnection } from "@/lib/db/providers"; + +type KeyHealthLog = { + warn?: (tag: string, message: string) => void; + error?: (tag: string, message: string) => void; +} | null; + +export function recordKeyHealthStatus( + status: number, + creds: Record | null | undefined, + log?: KeyHealthLog +): void { + const connId = creds?.connectionId as string | undefined; + if (!connId) return; + + const psd = creds.providerSpecificData as Record | undefined; + const extraKeys = (psd?.extraApiKeys as string[] | undefined) ?? []; + const health = psd?.apiKeyHealth as Record | undefined; + const currentKeyId = (psd?.selectedKeyId as string | undefined) ?? "primary"; + + trackConnectionExtraKeys(connId, extraKeys); + + if (status === 401) { + const updatedHealth = recordKeyFailure(connId, currentKeyId); + log?.warn?.( + "AUTH", + `401 on connection ${connId.slice(0, 8)} - key marked as failed (failure #${updatedHealth.failures})` + ); + + // Persist health status to DB on every failure (not just invalid transitions) + // This ensures in-memory state survives process restarts + const prevStatus = health?.[currentKeyId]?.status; + const prevFailures = health?.[currentKeyId]?.failures ?? 0; + if (updatedHealth.status !== prevStatus || updatedHealth.failures !== prevFailures) { + updateProviderConnection(connId, { + providerSpecificData: { + ...psd, + apiKeyHealth: { ...health, [currentKeyId]: updatedHealth }, + }, + }).catch((err: unknown) => { + log?.error?.( + "DB", + `Failed to persist apiKeyHealth: ${err instanceof Error ? err.message : String(err)}` + ); + }); + } + } else if (status >= 200 && status < 300) { + const updatedHealth = recordKeySuccess(connId, currentKeyId); + const prevStatus = health?.[currentKeyId]?.status; + if (prevStatus === "warning" || prevStatus === "invalid") { + updateProviderConnection(connId, { + providerSpecificData: { + ...psd, + apiKeyHealth: { ...health, [currentKeyId]: updatedHealth }, + }, + }).catch((err: unknown) => { + log?.error?.( + "DB", + `Failed to persist apiKeyHealth: ${err instanceof Error ? err.message : String(err)}` + ); + }); + } + } +} diff --git a/tests/unit/chatcore-key-health.test.ts b/tests/unit/chatcore-key-health.test.ts new file mode 100644 index 000000000000..28e3e2c8c8b2 --- /dev/null +++ b/tests/unit/chatcore-key-health.test.ts @@ -0,0 +1,70 @@ +// tests/unit/chatcore-key-health.test.ts +// Characterization of recordKeyHealthStatus — the per-request API-key health updater extracted +// from handleChatCore (chatCore god-file decomposition, #3501). Locks the observable in-memory +// transitions driven through apiKeyRotator: 401 → failure (warning, then invalid at the threshold), +// 2xx → success/recovery, selectedKeyId scoping, and the no-op paths (missing connectionId, and +// non-401/non-2xx statuses). The DB persistence side effect (updateProviderConnection) is moved +// byte-identically and is fire-and-forget; these tests assert the synchronous health mutations. +import { test, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { recordKeyHealthStatus } from "../../open-sse/handlers/chatCore/keyHealth.ts"; +import { getAllKeyHealth, removeConnectionHealth } from "../../open-sse/services/apiKeyRotator.ts"; + +const noopLog = { warn: () => {}, error: () => {} }; +const touched: string[] = []; + +function creds(connectionId: string, psd: Record = {}) { + touched.push(connectionId); + return { connectionId, providerSpecificData: psd }; +} + +afterEach(() => { + for (const c of touched.splice(0)) removeConnectionHealth(c); +}); + +test("missing connectionId is a no-op (no health entry created)", () => { + const before = Object.keys(getAllKeyHealth()).length; + const r = recordKeyHealthStatus(200, { providerSpecificData: {} }, noopLog); + assert.equal(r, undefined); + assert.equal(Object.keys(getAllKeyHealth()).length, before); +}); + +test("401 marks the selected key as failed → warning after the first failure", () => { + const conn = "kh-401-warning"; + recordKeyHealthStatus(401, creds(conn), noopLog); + const h = getAllKeyHealth()[`${conn}:primary`]; + assert.equal(h?.failures, 1); + assert.equal(h?.status, "warning"); +}); + +test("401 reaches invalid at the failure threshold (2 consecutive)", () => { + const conn = "kh-401-invalid"; + recordKeyHealthStatus(401, creds(conn), noopLog); + recordKeyHealthStatus(401, creds(conn), noopLog); + const h = getAllKeyHealth()[`${conn}:primary`]; + assert.equal(h?.failures, 2); + assert.equal(h?.status, "invalid"); +}); + +test("2xx after a failure resets the key to active with 0 failures", () => { + const conn = "kh-2xx-recover"; + recordKeyHealthStatus(401, creds(conn), noopLog); + recordKeyHealthStatus(204, creds(conn), noopLog); + const h = getAllKeyHealth()[`${conn}:primary`]; + assert.equal(h?.failures, 0); + assert.equal(h?.status, "active"); +}); + +test("honors selectedKeyId — scopes the update to the active extra key, not primary", () => { + const conn = "kh-selected-key"; + recordKeyHealthStatus(401, creds(conn, { selectedKeyId: "extra_1" }), noopLog); + const all = getAllKeyHealth(); + assert.equal(all[`${conn}:extra_1`]?.status, "warning"); + assert.equal(all[`${conn}:primary`], undefined); +}); + +test("non-401 / non-2xx status does not touch key health", () => { + const conn = "kh-5xx-noop"; + recordKeyHealthStatus(500, creds(conn), noopLog); + assert.equal(getAllKeyHealth()[`${conn}:primary`], undefined); +}); From f982350451ff27e79bcadad8c8ba35337906b1b3 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 21 Jun 2026 09:27:00 -0300 Subject: [PATCH 2/7] =?UTF-8?q?refactor(chatCore):=20extrai=20resolu=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20endpoint/formato=20para=20leaf=20puro=20(#3501)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move o bloco de resolução de formato do topo de handleChatCore (endpointPath / sourceFormat / isResponsesEndpoint / nativeCodexPassthrough / isDroidCLI / copilotCompatibleReasoning / clientResponseFormat) para o novo leaf puro open-sse/handlers/chatCore/requestFormat.ts (resolveChatCoreRequestFormat), ao lado de resolveChatCoreRequestSetup (fase de request-setup). O handler agora desestrutura o resultado, então todo uso downstream fica byte-idêntico. A função local isCopilotClient (usada só por esse bloco) vira helper privado do leaf; o import órfão detectFormatFromEndpoint migra junto (getTargetFormat permanece no import de provider). shouldUseNativeCodexPassthrough e FORMATS seguem importados no handler (re-exportado / usado em vários pontos). chatCore.ts 5019->4993 (shrink -26, cruza abaixo de 5000); baseline file-size ratchetado. complexity 1905=1905 (neutro). Coberto por tests/unit/chatcore-request-format.test.ts (7 casos, incluindo o downgrade OpenAI-Responses->OpenAI e sua supressão pelo Droid CLI). --- open-sse/handlers/chatCore.ts | 50 +++------- open-sse/handlers/chatCore/requestFormat.ts | 82 ++++++++++++++++ tests/unit/chatcore-request-format.test.ts | 103 ++++++++++++++++++++ 3 files changed, 197 insertions(+), 38 deletions(-) create mode 100644 open-sse/handlers/chatCore/requestFormat.ts create mode 100644 tests/unit/chatcore-request-format.test.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index cc11cc99f399..c315b206fdc5 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"; @@ -544,28 +545,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 }>; @@ -819,22 +798,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 000000000000..63ad12589bab --- /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 000000000000..064a2384f21a --- /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, ""); +}); From 4e27536b8d7d17e0a37123b13e54a505e959cbaa Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 21 Jun 2026 10:45:17 -0300 Subject: [PATCH 3/7] =?UTF-8?q?refactor(chatCore):=20extrai=20normaliza?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20effort-variant=20do=20Claude=20(#3501)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move o bloco de effort-variant do Claude/Claude-Code do topo de handleChatCore (strip de claude-...-{low,medium,high,xhigh,max} de volta ao id base + expõe o nível como reasoning_effort; effort explícito do cliente vence; passthrough nativo com sourceFormat==='claude' fica intocado) para o novo leaf open-sse/handlers/chatCore/claudeEffortVariant.ts (applyClaudeEffortVariant). A função muta o body in-place e retorna { effectiveModel, log }, que o handler aplica (reatribuição de effectiveModel + log?.info), byte-idêntico. Import órfão splitClaudeEffortSuffix migra para o leaf (getModelTargetFormat / PROVIDER_ID_TO_ALIAS permanecem no import de providerModels); isClaudeCodeCompatibleProvider e FORMATS seguem no handler (usados em outros pontos). A detecção de effort explícito foi isolada no helper privado hasExplicitClaudeEffort para manter applyClaudeEffortVariant abaixo do teto de complexidade (16->≤15; mesmo padrão de resolveContextCachePin/ markConnectionLevelExhaustion). chatCore.ts 4993->4978 (shrink -15); baseline file-size ratchetado. complexity 1905=1905 (neutro). Coberto por tests/unit/chatcore-claude-effort-variant.test.ts (7 casos). --- open-sse/handlers/chatCore.ts | 43 +++---- .../handlers/chatCore/claudeEffortVariant.ts | 62 ++++++++++ .../chatcore-claude-effort-variant.test.ts | 107 ++++++++++++++++++ 3 files changed, 183 insertions(+), 29 deletions(-) create mode 100644 open-sse/handlers/chatCore/claudeEffortVariant.ts create mode 100644 tests/unit/chatcore-claude-effort-variant.test.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index c315b206fdc5..cb3299b4bb7a 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -71,11 +71,8 @@ import { import { createRequestLogger } from "../utils/requestLogger.ts"; import { createPreparedRequestLogger, runWithCapture } from "../utils/providerRequestLogging.ts"; import { applyResponsesPreviousResponseIdPolicy } from "../utils/responsesStatePolicy.ts"; -import { - getModelTargetFormat, - PROVIDER_ID_TO_ALIAS, - splitClaudeEffortSuffix, -} from "../config/providerModels.ts"; +import { getModelTargetFormat, PROVIDER_ID_TO_ALIAS } from "../config/providerModels.ts"; +import { applyClaudeEffortVariant } from "./chatCore/claudeEffortVariant.ts"; import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../config/defaultThinkingSignature.ts"; import { getStripTypesForProviderModel, @@ -869,30 +866,18 @@ export async function handleChatCore({ // it into Claude thinking/effort config. An explicit client-supplied effort always // wins; native Claude passthrough is left untouched (it carries its own `thinking`), // and non-thinking base models are cleaned up later by normalizeThinkingForModel(). - if ( - (provider === "claude" || isClaudeCodeCompatibleProvider(provider)) && - typeof effectiveModel === "string" - ) { - const { baseModel, effort } = splitClaudeEffortSuffix(effectiveModel); - if (effort) { - effectiveModel = baseModel; - if (body && typeof body === "object" && !Array.isArray(body)) { - const claudeBody = body as Record; - claudeBody.model = baseModel; - if (sourceFormat !== FORMATS.CLAUDE) { - const explicitEffort = - claudeBody.reasoning_effort ?? - (claudeBody.reasoning as Record | undefined)?.effort ?? - (claudeBody.output_config as Record | undefined)?.effort; - if (explicitEffort === undefined || explicitEffort === null || explicitEffort === "") { - claudeBody.reasoning_effort = effort; - } - } - } - log?.info?.( - "PARAMS", - `Claude effort variant: stripped "-${effort}" → ${baseModel} (reasoning_effort=${effort})` - ); + // Extracted to chatCore/claudeEffortVariant.ts (#3501); mutates body in place and returns the + // stripped model + an optional log line, keeping behaviour byte-identical. + { + const effortVariant = applyClaudeEffortVariant({ + provider, + effectiveModel, + body, + sourceFormat, + }); + effectiveModel = effortVariant.effectiveModel; + if (effortVariant.log) { + log?.info?.("PARAMS", effortVariant.log); } } diff --git a/open-sse/handlers/chatCore/claudeEffortVariant.ts b/open-sse/handlers/chatCore/claudeEffortVariant.ts new file mode 100644 index 000000000000..e2a2a8e188a6 --- /dev/null +++ b/open-sse/handlers/chatCore/claudeEffortVariant.ts @@ -0,0 +1,62 @@ +/** + * chatCore Claude effort-variant normalizer (Quality Gate v2 / Fase 9 — chatCore god-file + * decomposition, #3501). + * + * The Claude / Claude-Code model picker (e.g. VS Code's "Effort" slider) advertises + * claude-...-{low,medium,high,xhigh,max}. Anthropic has no such model, so the suffixed id 404s + * upstream. This strips it back to the real base id and surfaces the level as reasoning_effort so + * the OpenAI→Claude translator / Claude-Code bridge can turn it into Claude thinking/effort config. + * An explicit client-supplied effort always wins; native Claude passthrough (sourceFormat === claude) + * is left untouched (it carries its own `thinking`). The body is mutated in place (model + + * reasoning_effort), byte-identical to the previous inline block; the new effectiveModel and an + * optional log line are returned for the handler to apply. + */ + +import { splitClaudeEffortSuffix } from "../../config/providerModels.ts"; +import { isClaudeCodeCompatibleProvider } from "../../services/claudeCodeCompatible.ts"; +import { FORMATS } from "../../translator/formats.ts"; + +/** + * True when the client already supplied an explicit reasoning effort (top-level reasoning_effort, + * reasoning.effort, or output_config.effort) — in which case the stripped suffix must not overwrite + * it. A blank string counts as "not supplied". + */ +function hasExplicitClaudeEffort(claudeBody: Record): boolean { + const explicitEffort = + claudeBody.reasoning_effort ?? + (claudeBody.reasoning as Record | undefined)?.effort ?? + (claudeBody.output_config as Record | undefined)?.effort; + return !(explicitEffort === undefined || explicitEffort === null || explicitEffort === ""); +} + +export function applyClaudeEffortVariant(opts: { + provider: string | null | undefined; + effectiveModel: string; + /** Mutated in place (model + reasoning_effort) when an effort suffix is stripped. */ + body: unknown; + sourceFormat: string; +}): { effectiveModel: string; log: string | null } { + const { provider, body, sourceFormat } = opts; + let effectiveModel = opts.effectiveModel; + let log: string | null = null; + + if ( + (provider === "claude" || isClaudeCodeCompatibleProvider(provider)) && + typeof effectiveModel === "string" + ) { + const { baseModel, effort } = splitClaudeEffortSuffix(effectiveModel); + if (effort) { + effectiveModel = baseModel; + if (body && typeof body === "object" && !Array.isArray(body)) { + const claudeBody = body as Record; + claudeBody.model = baseModel; + if (sourceFormat !== FORMATS.CLAUDE && !hasExplicitClaudeEffort(claudeBody)) { + claudeBody.reasoning_effort = effort; + } + } + log = `Claude effort variant: stripped "-${effort}" → ${baseModel} (reasoning_effort=${effort})`; + } + } + + return { effectiveModel, log }; +} diff --git a/tests/unit/chatcore-claude-effort-variant.test.ts b/tests/unit/chatcore-claude-effort-variant.test.ts new file mode 100644 index 000000000000..03bf40a4c1f0 --- /dev/null +++ b/tests/unit/chatcore-claude-effort-variant.test.ts @@ -0,0 +1,107 @@ +// tests/unit/chatcore-claude-effort-variant.test.ts +// Characterization of applyClaudeEffortVariant — the Claude effort-suffix normalization extracted +// from handleChatCore (chatCore god-file decomposition, #3501). The VS Code "Effort" slider +// advertises claude-...-{low,medium,high,xhigh,max}; Anthropic has no such model, so the suffix is +// stripped to the base id and surfaced as reasoning_effort. Locks: the provider gate (claude / +// claude-code-compatible only), the in-place body mutation (model + reasoning_effort), the +// sourceFormat==="claude" skip, the explicit-effort-wins rule, and the returned effectiveModel/log. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { applyClaudeEffortVariant } from "../../open-sse/handlers/chatCore/claudeEffortVariant.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; + +test("claude provider + effort suffix → strips to base, mutates body model + reasoning_effort, returns log", () => { + const body: Record = { model: "claude-sonnet-4-high", messages: [] }; + const r = applyClaudeEffortVariant({ + provider: "claude", + effectiveModel: "claude-sonnet-4-high", + body, + sourceFormat: FORMATS.OPENAI, + }); + assert.equal(r.effectiveModel, "claude-sonnet-4"); + assert.equal(body.model, "claude-sonnet-4"); + assert.equal(body.reasoning_effort, "high"); + assert.match(String(r.log), /stripped "-high" → claude-sonnet-4 \(reasoning_effort=high\)/); +}); + +test("claude-code-compatible provider triggers the same stripping", () => { + const body: Record = { model: "claude-opus-4-xhigh", messages: [] }; + const r = applyClaudeEffortVariant({ + provider: "anthropic-compatible-cc-default", + effectiveModel: "claude-opus-4-xhigh", + body, + sourceFormat: FORMATS.OPENAI, + }); + assert.equal(r.effectiveModel, "claude-opus-4"); + assert.equal(body.model, "claude-opus-4"); + assert.equal(body.reasoning_effort, "xhigh"); +}); + +test("sourceFormat 'claude' strips the model but does NOT inject reasoning_effort", () => { + const body: Record = { model: "claude-sonnet-4-medium", messages: [] }; + const r = applyClaudeEffortVariant({ + provider: "claude", + effectiveModel: "claude-sonnet-4-medium", + body, + sourceFormat: FORMATS.CLAUDE, + }); + assert.equal(r.effectiveModel, "claude-sonnet-4"); + assert.equal(body.model, "claude-sonnet-4"); + assert.equal(body.reasoning_effort, undefined); +}); + +test("an explicit client reasoning_effort wins (not overwritten)", () => { + const body: Record = { model: "claude-sonnet-4-low", reasoning_effort: "high", messages: [] }; + const r = applyClaudeEffortVariant({ + provider: "claude", + effectiveModel: "claude-sonnet-4-low", + body, + sourceFormat: FORMATS.OPENAI, + }); + assert.equal(r.effectiveModel, "claude-sonnet-4"); + assert.equal(body.reasoning_effort, "high"); // unchanged +}); + +test("explicit effort nested under reasoning.effort also wins", () => { + const body: Record = { + model: "claude-sonnet-4-low", + reasoning: { effort: "medium" }, + messages: [], + }; + const r = applyClaudeEffortVariant({ + provider: "claude", + effectiveModel: "claude-sonnet-4-low", + body, + sourceFormat: FORMATS.OPENAI, + }); + assert.equal(body.reasoning_effort, undefined); // explicit reasoning.effort present → no injection + assert.equal(r.effectiveModel, "claude-sonnet-4"); +}); + +test("no effort suffix → no change, no log", () => { + const body: Record = { model: "claude-sonnet-4", messages: [] }; + const r = applyClaudeEffortVariant({ + provider: "claude", + effectiveModel: "claude-sonnet-4", + body, + sourceFormat: FORMATS.OPENAI, + }); + assert.equal(r.effectiveModel, "claude-sonnet-4"); + assert.equal(body.model, "claude-sonnet-4"); + assert.equal(body.reasoning_effort, undefined); + assert.equal(r.log, null); +}); + +test("non-claude provider is a no-op even with an effort suffix", () => { + const body: Record = { model: "gpt-5-high", messages: [] }; + const r = applyClaudeEffortVariant({ + provider: "openai", + effectiveModel: "gpt-5-high", + body, + sourceFormat: FORMATS.OPENAI, + }); + assert.equal(r.effectiveModel, "gpt-5-high"); + assert.equal(body.model, "gpt-5-high"); + assert.equal(body.reasoning_effort, undefined); + assert.equal(r.log, null); +}); From 686371849450dd3797aa65faada8c0d3853f744c Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 21 Jun 2026 13:55:50 -0300 Subject: [PATCH 4/7] =?UTF-8?q?refactor(chatCore):=20extrai=20resolu=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20target-format=20para=20leaf=20puro=20(#3501)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move a resolução de wire target-format do topo de handleChatCore para o novo leaf puro open-sse/handlers/chatCore/targetFormat.ts (resolveChatCoreTargetFormat), ao lado de resolveChatCoreRequestSetup/resolveChatCoreRequestFormat. Retorna { alias, targetFormat }: apiFormat==='responses' força OpenAI Responses; senão o target-format do modelo no registry, depois o override custom (#2905), depois o default do provider. O handler desestrutura — `alias` segue reusado downstream ao remover o prefixo alias/ do model id upstream. Imports órfãos (getModelTargetFormat / PROVIDER_ID_TO_ALIAS / getTargetFormat) migram para o leaf; FORMATS permanece no handler (usado em vários pontos). Tamanho de chatCore.ts neutro (call+comentário compensa os 2 imports removidos), mas a lógica agora é unit-testada e co-localizada. complexity 1905=1905; typecheck 0. Coberto por tests/unit/chatcore-target-format.test.ts (5 casos, asseridos contra a composição inline). --- open-sse/handlers/chatCore.ts | 20 ++--- open-sse/handlers/chatCore/targetFormat.ts | 34 +++++++++ tests/unit/chatcore-target-format.test.ts | 89 ++++++++++++++++++++++ 3 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 open-sse/handlers/chatCore/targetFormat.ts create mode 100644 tests/unit/chatcore-target-format.test.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index cb3299b4bb7a..4ab2ef592b0e 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -44,8 +44,8 @@ import { import { CORS_HEADERS } from "../utils/cors.ts"; import { checkHeapPressureGuard } from "../utils/heapPressure.ts"; import { normalizeHeaders } from "../utils/headers.ts"; -import { getTargetFormat } from "../services/provider.ts"; import { resolveChatCoreRequestFormat } from "./chatCore/requestFormat.ts"; +import { resolveChatCoreTargetFormat } from "./chatCore/targetFormat.ts"; import { injectSystemPrompt } from "../services/systemPrompt.ts"; import { translateRequest, needsTranslation } from "../translator/index.ts"; import { FORMATS } from "../translator/formats.ts"; @@ -71,7 +71,6 @@ import { import { createRequestLogger } from "../utils/requestLogger.ts"; import { createPreparedRequestLogger, runWithCapture } from "../utils/providerRequestLogging.ts"; import { applyResponsesPreviousResponseIdPolicy } from "../utils/responsesStatePolicy.ts"; -import { getModelTargetFormat, PROVIDER_ID_TO_ALIAS } from "../config/providerModels.ts"; import { applyClaudeEffortVariant } from "./chatCore/claudeEffortVariant.ts"; import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../config/defaultThinkingSignature.ts"; import { @@ -881,14 +880,15 @@ export async function handleChatCore({ } } - const alias = PROVIDER_ID_TO_ALIAS[provider] || provider; - const modelTargetFormat = getModelTargetFormat(alias, resolvedModel); - const targetFormat = - apiFormat === "responses" - ? FORMATS.OPENAI_RESPONSES - : modelTargetFormat || - customModelTargetFormat || - getTargetFormat(provider, credentials?.providerSpecificData); + // Wire target-format resolution extracted to chatCore/targetFormat.ts (#3501); `alias` is reused + // downstream when stripping the alias/ prefix off the upstream model id. + const { alias, targetFormat } = resolveChatCoreTargetFormat({ + provider, + resolvedModel, + apiFormat, + customModelTargetFormat, + providerSpecificData: credentials?.providerSpecificData, + }); const initialProviderRequest = body && typeof body === "object" && !Array.isArray(body) diff --git a/open-sse/handlers/chatCore/targetFormat.ts b/open-sse/handlers/chatCore/targetFormat.ts new file mode 100644 index 000000000000..8b1ed014c626 --- /dev/null +++ b/open-sse/handlers/chatCore/targetFormat.ts @@ -0,0 +1,34 @@ +/** + * chatCore wire target-format resolver (Quality Gate v2 / Fase 9 — chatCore god-file + * decomposition, #3501). + * + * Pure resolution of the provider alias + the upstream target format used to translate the request: + * apiFormat==="responses" forces OpenAI Responses; otherwise the model's registry target format, then + * the per-model custom override (#2905), then the provider default. Returns both `alias` (reused by + * the handler when stripping the `alias/` prefix off the upstream model id) and `targetFormat`. + * Side-effect-free; byte-identical to the previous inline block. Sits alongside the other + * request-setup resolvers (resolveChatCoreRequestSetup / resolveChatCoreRequestFormat). + */ + +import { PROVIDER_ID_TO_ALIAS, getModelTargetFormat } from "../../config/providerModels.ts"; +import { getTargetFormat } from "../../services/provider.ts"; +import { FORMATS } from "../../translator/formats.ts"; + +export function resolveChatCoreTargetFormat(opts: { + provider: string; + resolvedModel: string; + apiFormat: string | undefined; + customModelTargetFormat: string | undefined; + providerSpecificData: unknown; +}) { + const { provider, resolvedModel, apiFormat, customModelTargetFormat, providerSpecificData } = opts; + const alias = PROVIDER_ID_TO_ALIAS[provider] || provider; + const modelTargetFormat = getModelTargetFormat(alias, resolvedModel); + const targetFormat = + apiFormat === "responses" + ? FORMATS.OPENAI_RESPONSES + : modelTargetFormat || customModelTargetFormat || getTargetFormat(provider, providerSpecificData); + return { alias, targetFormat }; +} + +export type ChatCoreTargetFormat = ReturnType; diff --git a/tests/unit/chatcore-target-format.test.ts b/tests/unit/chatcore-target-format.test.ts new file mode 100644 index 000000000000..affa94a113d4 --- /dev/null +++ b/tests/unit/chatcore-target-format.test.ts @@ -0,0 +1,89 @@ +// tests/unit/chatcore-target-format.test.ts +// Characterization of resolveChatCoreTargetFormat — the wire target-format resolution extracted +// from handleChatCore (chatCore god-file decomposition, #3501). Resolves the provider alias and the +// upstream target format: apiFormat==="responses" forces OpenAI Responses; otherwise the model's +// registry target format, then the custom-model override, then the provider default. Returns both +// `alias` (reused downstream when stripping the alias/ prefix off the upstream model) and +// `targetFormat`. Asserted against the inline composition so the delegation stays byte-identical. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { resolveChatCoreTargetFormat } from "../../open-sse/handlers/chatCore/targetFormat.ts"; +import { PROVIDER_ID_TO_ALIAS, getModelTargetFormat } from "../../open-sse/config/providerModels.ts"; +import { getTargetFormat } from "../../open-sse/services/provider.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; + +function expected( + provider: string, + resolvedModel: string, + apiFormat: string | undefined, + customModelTargetFormat: string | undefined, + providerSpecificData: unknown +) { + const alias = PROVIDER_ID_TO_ALIAS[provider] || provider; + const modelTargetFormat = getModelTargetFormat(alias, resolvedModel); + const targetFormat = + apiFormat === "responses" + ? FORMATS.OPENAI_RESPONSES + : modelTargetFormat || customModelTargetFormat || getTargetFormat(provider, providerSpecificData); + return { alias, targetFormat }; +} + +test("apiFormat='responses' short-circuits to OPENAI_RESPONSES (alias still resolved)", () => { + const r = resolveChatCoreTargetFormat({ + provider: "openai", + resolvedModel: "gpt-4o", + apiFormat: "responses", + customModelTargetFormat: undefined, + providerSpecificData: undefined, + }); + assert.equal(r.targetFormat, FORMATS.OPENAI_RESPONSES); + assert.equal(r.alias, PROVIDER_ID_TO_ALIAS["openai"] || "openai"); +}); + +test("delegates byte-identically for a normal model (no apiFormat / no custom override)", () => { + const r = resolveChatCoreTargetFormat({ + provider: "openai", + resolvedModel: "gpt-4o", + apiFormat: undefined, + customModelTargetFormat: undefined, + providerSpecificData: undefined, + }); + assert.deepEqual(r, expected("openai", "gpt-4o", undefined, undefined, undefined)); +}); + +test("customModelTargetFormat is used when the model has no registry target format", () => { + const customModel = "totally-unknown-custom-model-xyz"; + // precondition: the registry has no target format for this unknown model + assert.ok(!getModelTargetFormat(PROVIDER_ID_TO_ALIAS["openai"] || "openai", customModel)); + const r = resolveChatCoreTargetFormat({ + provider: "openai", + resolvedModel: customModel, + apiFormat: undefined, + customModelTargetFormat: "claude", + providerSpecificData: undefined, + }); + assert.equal(r.targetFormat, "claude"); +}); + +test("falls back to getTargetFormat(provider) when neither model nor custom format apply", () => { + const customModel = "totally-unknown-custom-model-xyz"; + const r = resolveChatCoreTargetFormat({ + provider: "openai", + resolvedModel: customModel, + apiFormat: undefined, + customModelTargetFormat: undefined, + providerSpecificData: undefined, + }); + assert.equal(r.targetFormat, getTargetFormat("openai", undefined)); +}); + +test("unmapped provider → alias falls back to the provider id", () => { + const r = resolveChatCoreTargetFormat({ + provider: "some-unmapped-provider", + resolvedModel: "x", + apiFormat: "responses", + customModelTargetFormat: undefined, + providerSpecificData: undefined, + }); + assert.equal(r.alias, "some-unmapped-provider"); +}); From b9c04cb1efcfe07a7116176f4049851df49c929c Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 21 Jun 2026 15:57:43 -0300 Subject: [PATCH 5/7] refactor(chatCore): extrai builder do usage-record de falha (#3501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move o núcleo puro do closure persistFailureUsage para o novo leaf open-sse/handlers/chatCore/failureUsage.ts (buildFailureUsageRecord): monta o registro de usage-history de uma request falha (tokens/timing zerados, success:false, fallbacks unknown/undefined, gate de comboStrategy, timestamp ISO). O handler mantém o impuro byte-idêntico: calcula latencyMs (Date.now()-startTime) e dispara o fire-and-forget saveRequestUsage(...).catch(). O closure binding preserva a assinatura (statusCode, errorCode?), então os 13 call sites + o pass-through ficam inalterados. complexity neutro (leaf puro trivial); typecheck 0. Coberto por tests/unit/chatcore-failure-usage.test.ts (3 casos: record completo, fallbacks, gate combo-strategy). --- open-sse/handlers/chatCore.ts | 33 ++++----- open-sse/handlers/chatCore/failureUsage.ts | 39 +++++++++++ tests/unit/chatcore-failure-usage.test.ts | 79 ++++++++++++++++++++++ 3 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 open-sse/handlers/chatCore/failureUsage.ts create mode 100644 tests/unit/chatcore-failure-usage.test.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 4ab2ef592b0e..ae7365ad2b57 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -1,5 +1,6 @@ import { injectMemoryAndSkills } from "./chatCore/memorySkillsInjection.ts"; import { resolveChatCoreRequestSetup } from "./chatCore/requestSetup.ts"; +import { buildFailureUsageRecord } from "./chatCore/failureUsage.ts"; import { checkIdempotencyCache } from "./chatCore/idempotency.ts"; import { checkSemanticCache } from "./chatCore/semanticCache.ts"; import { sanitizeChatRequestBody } from "./chatCore/sanitization.ts"; @@ -708,23 +709,23 @@ export async function handleChatCore({ payload?: unknown, maxDepth = 3 ): EffectiveServiceTier | null => resolveReportedServiceTierFor(provider, payload, maxDepth); + // Failure usage record building extracted to chatCore/failureUsage.ts (#3501); the handler keeps + // the fire-and-forget save + computes latencyMs, so the call sites stay byte-identical. const persistFailureUsage = (statusCode: number, errorCode?: string | null) => { - saveRequestUsage({ - provider: provider || "unknown", - model: model || "unknown", - tokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 }, - status: String(statusCode), - success: false, - latencyMs: Date.now() - startTime, - timeToFirstTokenMs: 0, - errorCode: errorCode || String(statusCode), - timestamp: new Date().toISOString(), - connectionId: connectionId || undefined, - apiKeyId: apiKeyInfo?.id || undefined, - apiKeyName: apiKeyInfo?.name || undefined, - serviceTier: effectiveServiceTier, - comboStrategy: isCombo ? comboStrategy || undefined : undefined, - }).catch(() => {}); + saveRequestUsage( + buildFailureUsageRecord({ + provider, + model, + connectionId, + apiKeyInfo, + effectiveServiceTier, + isCombo, + comboStrategy, + statusCode, + errorCode, + latencyMs: Date.now() - startTime, + }) + ).catch(() => {}); }; // Key-health updater extracted to chatCore/keyHealth.ts (#3501); bind the per-request log once diff --git a/open-sse/handlers/chatCore/failureUsage.ts b/open-sse/handlers/chatCore/failureUsage.ts new file mode 100644 index 000000000000..fd1860eba9f9 --- /dev/null +++ b/open-sse/handlers/chatCore/failureUsage.ts @@ -0,0 +1,39 @@ +/** + * chatCore failed-request usage record builder (Quality Gate v2 / Fase 9 — chatCore god-file + * decomposition, #3501). + * + * Pure core of handleChatCore's persistFailureUsage closure: builds the usage-history entry for a + * failed request (zeroed tokens/timing, success:false, the unknown/undefined fallbacks, and the + * combo-strategy gate). The handler keeps the impure parts byte-identically: it computes + * `latencyMs` (Date.now() - startTime) and fires the fire-and-forget saveRequestUsage(...).catch(). + */ + +export function buildFailureUsageRecord(opts: { + provider: string | null | undefined; + model: string | null | undefined; + connectionId: string | null | undefined; + apiKeyInfo: { id?: string; name?: string } | null | undefined; + effectiveServiceTier: string; + isCombo: boolean; + comboStrategy: string | null | undefined; + statusCode: number; + errorCode: string | null | undefined; + latencyMs: number; +}) { + return { + provider: opts.provider || "unknown", + model: opts.model || "unknown", + tokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 }, + status: String(opts.statusCode), + success: false, + latencyMs: opts.latencyMs, + timeToFirstTokenMs: 0, + errorCode: opts.errorCode || String(opts.statusCode), + timestamp: new Date().toISOString(), + connectionId: opts.connectionId || undefined, + apiKeyId: opts.apiKeyInfo?.id || undefined, + apiKeyName: opts.apiKeyInfo?.name || undefined, + serviceTier: opts.effectiveServiceTier, + comboStrategy: opts.isCombo ? opts.comboStrategy || undefined : undefined, + }; +} diff --git a/tests/unit/chatcore-failure-usage.test.ts b/tests/unit/chatcore-failure-usage.test.ts new file mode 100644 index 000000000000..cc20cf78240e --- /dev/null +++ b/tests/unit/chatcore-failure-usage.test.ts @@ -0,0 +1,79 @@ +// tests/unit/chatcore-failure-usage.test.ts +// Characterization of buildFailureUsageRecord — the failed-request usage payload builder extracted +// from handleChatCore's persistFailureUsage closure (chatCore god-file decomposition, #3501). Pure: +// the handler keeps the fire-and-forget saveRequestUsage(...).catch() call and computes latencyMs; +// this builds the record. Locks the unknown/undefined fallbacks, the zeroed token/timing fields, +// the combo-strategy gate, and the ISO timestamp. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { buildFailureUsageRecord } from "../../open-sse/handlers/chatCore/failureUsage.ts"; + +const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; + +test("maps a fully-populated failure into the usage record", () => { + const r = buildFailureUsageRecord({ + provider: "openai", + model: "gpt-4o", + connectionId: "conn-1", + apiKeyInfo: { id: "key-1", name: "My Key" }, + effectiveServiceTier: "priority", + isCombo: true, + comboStrategy: "weighted", + statusCode: 429, + errorCode: "rate_limited", + latencyMs: 1234, + }); + assert.equal(r.provider, "openai"); + assert.equal(r.model, "gpt-4o"); + assert.deepEqual(r.tokens, { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 }); + assert.equal(r.status, "429"); + assert.equal(r.success, false); + assert.equal(r.latencyMs, 1234); + assert.equal(r.timeToFirstTokenMs, 0); + assert.equal(r.errorCode, "rate_limited"); + assert.match(String(r.timestamp), ISO); + assert.equal(r.connectionId, "conn-1"); + assert.equal(r.apiKeyId, "key-1"); + assert.equal(r.apiKeyName, "My Key"); + assert.equal(r.serviceTier, "priority"); + assert.equal(r.comboStrategy, "weighted"); +}); + +test("applies the unknown/undefined fallbacks", () => { + const r = buildFailureUsageRecord({ + provider: null, + model: undefined, + connectionId: null, + apiKeyInfo: null, + effectiveServiceTier: "standard", + isCombo: false, + comboStrategy: "priority", + statusCode: 502, + errorCode: null, + latencyMs: 7, + }); + assert.equal(r.provider, "unknown"); + assert.equal(r.model, "unknown"); + assert.equal(r.errorCode, "502"); // falls back to String(statusCode) + assert.equal(r.connectionId, undefined); + assert.equal(r.apiKeyId, undefined); + assert.equal(r.apiKeyName, undefined); + // isCombo false → comboStrategy is dropped even when supplied + assert.equal(r.comboStrategy, undefined); +}); + +test("combo strategy is included only for combo requests", () => { + const combo = buildFailureUsageRecord({ + provider: "x", model: "y", connectionId: null, apiKeyInfo: null, + effectiveServiceTier: "standard", isCombo: true, comboStrategy: "round-robin", + statusCode: 500, errorCode: "boom", latencyMs: 1, + }); + assert.equal(combo.comboStrategy, "round-robin"); + + const comboNoStrategy = buildFailureUsageRecord({ + provider: "x", model: "y", connectionId: null, apiKeyInfo: null, + effectiveServiceTier: "standard", isCombo: true, comboStrategy: null, + statusCode: 500, errorCode: "boom", latencyMs: 1, + }); + assert.equal(comboNoStrategy.comboStrategy, undefined); +}); From fe3073cfae81143b1fd4775906089f3f8b205b55 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 21 Jun 2026 16:41:10 -0300 Subject: [PATCH 6/7] refactor(chatCore): extrai extractSystemRoleMessages para leaf puro (#3501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move a função top-level pura extractSystemRoleMessages (lift de mensagens role=system/developer para o campo top-level `system`, exigido pela Messages API da Anthropic) para o novo leaf open-sse/handlers/chatCore/claudeSystemRole.ts, byte-idêntico. O chatCore.ts importa e **re-exporta** o símbolo, então os importadores existentes (tests/unit/system-role-extraction.test.ts) seguem resolvendo de chatCore.ts; os 3 call sites internos (incl. dentro de normalizeClaudeUpstreamMessages) usam a versão importada — leaf autocontido, sem import circular. chatCore.ts encolhe ~33 linhas; typecheck 0; file-size OK. Coberto pelos 10 casos de tests/unit/system-role-extraction.test.ts (via re-export). complexity 1908=base v3.8.33 (drift pré-existente, mover função top-level é neutro). --- open-sse/handlers/chatCore.ts | 41 ++-------------- .../handlers/chatCore/claudeSystemRole.ts | 48 +++++++++++++++++++ 2 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 open-sse/handlers/chatCore/claudeSystemRole.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index ae7365ad2b57..997eff7a2708 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -1,6 +1,8 @@ import { injectMemoryAndSkills } from "./chatCore/memorySkillsInjection.ts"; import { resolveChatCoreRequestSetup } from "./chatCore/requestSetup.ts"; import { buildFailureUsageRecord } from "./chatCore/failureUsage.ts"; +import { extractSystemRoleMessages } from "./chatCore/claudeSystemRole.ts"; +export { extractSystemRoleMessages } from "./chatCore/claudeSystemRole.ts"; import { checkIdempotencyCache } from "./chatCore/idempotency.ts"; import { checkSemanticCache } from "./chatCore/semanticCache.ts"; import { sanitizeChatRequestBody } from "./chatCore/sanitization.ts"; @@ -542,43 +544,8 @@ function buildExecutorClientHeaders( return Object.keys(normalized).length > 0 ? normalized : null; } -export function extractSystemRoleMessages(payload: Record): void { - if (!Array.isArray(payload.messages)) return; - const messages = payload.messages as Array<{ role?: unknown; content?: unknown }>; - // Treat both `system` and `developer` as system-equivalent (OpenAI's Responses - // API renamed system → developer). Anthropic rejects either as a chat role, so - // both must be lifted into the top-level `system` field — parity with the - // normal-path extractSystemMessagesToBody closure. - const isSystemRole = (role: unknown): boolean => - typeof role === "string" && - (role.toLowerCase() === "system" || role.toLowerCase() === "developer"); - const systemMessages = messages.filter((m) => isSystemRole(m.role)); - if (systemMessages.length === 0) return; - - const extraBlocks: Array> = []; - for (const sm of systemMessages) { - if (typeof sm.content === "string" && sm.content.length > 0) { - extraBlocks.push({ type: "text", text: sm.content }); - } else if (Array.isArray(sm.content)) { - for (const block of sm.content as Array>) { - if (block?.type === "text" && typeof block.text === "string" && block.text.length > 0) { - extraBlocks.push({ ...block }); - } - } - } - } - if (extraBlocks.length > 0) { - const existingSystem = payload.system; - if (typeof existingSystem === "string" && existingSystem.length > 0) { - payload.system = [{ type: "text", text: existingSystem }, ...extraBlocks]; - } else if (Array.isArray(existingSystem)) { - payload.system = [...(existingSystem as Array>), ...extraBlocks]; - } else { - payload.system = extraBlocks; - } - } - payload.messages = messages.filter((m) => !isSystemRole(m.role)); -} +// extractSystemRoleMessages extracted to chatCore/claudeSystemRole.ts (#3501); re-exported below so +// existing importers (e.g. tests/unit/system-role-extraction.test.ts) keep resolving it from here. export async function handleChatCore({ body, diff --git a/open-sse/handlers/chatCore/claudeSystemRole.ts b/open-sse/handlers/chatCore/claudeSystemRole.ts new file mode 100644 index 000000000000..0c2218016621 --- /dev/null +++ b/open-sse/handlers/chatCore/claudeSystemRole.ts @@ -0,0 +1,48 @@ +/** + * chatCore Claude system-role lifter (Quality Gate v2 / Fase 9 — chatCore god-file + * decomposition, #3501). + * + * Pure helper extracted from chatCore.ts: lifts any `system`/`developer` role messages out of the + * messages[] array into the top-level `system` field. Anthropic's Messages API rejects either as a + * chat role, so they must be hoisted. `developer` is OpenAI's Responses-API rename of `system` and + * is treated identically. Mutates the payload in place; behaviour is byte-identical to the previous + * top-level definition (still re-exported from chatCore.ts for existing importers/tests). + */ + +export function extractSystemRoleMessages(payload: Record): void { + if (!Array.isArray(payload.messages)) return; + const messages = payload.messages as Array<{ role?: unknown; content?: unknown }>; + // Treat both `system` and `developer` as system-equivalent (OpenAI's Responses + // API renamed system → developer). Anthropic rejects either as a chat role, so + // both must be lifted into the top-level `system` field — parity with the + // normal-path extractSystemMessagesToBody closure. + const isSystemRole = (role: unknown): boolean => + typeof role === "string" && + (role.toLowerCase() === "system" || role.toLowerCase() === "developer"); + const systemMessages = messages.filter((m) => isSystemRole(m.role)); + if (systemMessages.length === 0) return; + + const extraBlocks: Array> = []; + for (const sm of systemMessages) { + if (typeof sm.content === "string" && sm.content.length > 0) { + extraBlocks.push({ type: "text", text: sm.content }); + } else if (Array.isArray(sm.content)) { + for (const block of sm.content as Array>) { + if (block?.type === "text" && typeof block.text === "string" && block.text.length > 0) { + extraBlocks.push({ ...block }); + } + } + } + } + if (extraBlocks.length > 0) { + const existingSystem = payload.system; + if (typeof existingSystem === "string" && existingSystem.length > 0) { + payload.system = [{ type: "text", text: existingSystem }, ...extraBlocks]; + } else if (Array.isArray(existingSystem)) { + payload.system = [...(existingSystem as Array>), ...extraBlocks]; + } else { + payload.system = extraBlocks; + } + } + payload.messages = messages.filter((m) => !isSystemRole(m.role)); +} From 8c914a276db3040c8473f0f4de4b9d6baffb7bf4 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 21 Jun 2026 17:03:54 -0300 Subject: [PATCH 7/7] refactor(chatCore): extrai buildUpstreamHeadersForExecute para leaf puro (#3501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move o closure buildUpstreamHeadersForExecute do handleChatCore para o novo leaf puro open-sse/handlers/chatCore/upstreamExecuteHeaders.ts: monta os headers extra de upstream por modelo (merge dos headers configurados do modelo + alias), aplica o User-Agent custom da conexão e ativa o Claude Fast Mode (header CPA) quando habilitado nas settings para o provider canônico `claude` + modelo suportado. O handler mantém um binding fino que repassa os 7 inputs por request, então os 3 call sites (passando só modelToCall/retryModelId) ficam byte-idênticos. Imports órfãos migram: getModelUpstreamExtraHeaders agora do módulo específico @/lib/db/models (não barrel localDb — check-db-rules OK); CPA_FORCE_FAST_MODE_HEADER + shouldRequestClaudeFastMode do claudeFastMode. resolveModelAlias permanece. chatCore.ts -31 linhas; typecheck 0; check-db-rules OK; file-size OK; 172/172 suíte. Coberto por tests/unit/chatcore-upstream-execute-headers.test.ts (6 casos: User-Agent override, fast-mode só p/ claude+enabled+supported, branches). --- open-sse/handlers/chatCore.ts | 59 ++++----------- .../chatCore/upstreamExecuteHeaders.ts | 71 +++++++++++++++++++ .../chatcore-upstream-execute-headers.test.ts | 70 ++++++++++++++++++ 3 files changed, 155 insertions(+), 45 deletions(-) create mode 100644 open-sse/handlers/chatCore/upstreamExecuteHeaders.ts create mode 100644 tests/unit/chatcore-upstream-execute-headers.test.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 997eff7a2708..a949057b2a83 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -166,7 +166,6 @@ import { import { getModelNormalizeToolCallId, getModelPreserveOpenAIDeveloperRole, - getModelUpstreamExtraHeaders, } from "@/lib/localDb"; import { getProviderCredentials, extractSessionAffinityKey } from "@/sse/services/auth"; import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity"; @@ -183,10 +182,7 @@ import { } from "../utils/cacheControlPolicy.ts"; import { getCachedSettings } from "@/lib/db/readCache"; import { applyCodexGlobalFastServiceTier } from "@/lib/providers/codexFastTier"; -import { - CPA_FORCE_FAST_MODE_HEADER, - shouldRequestClaudeFastMode, -} from "@/lib/providers/claudeFastMode"; +import { buildUpstreamHeadersForExecute as buildUpstreamHeadersForExecuteFor } from "./chatCore/upstreamExecuteHeaders.ts"; import { resolveEffectiveServiceTier as resolveEffectiveServiceTierFor, resolveReportedServiceTier as resolveReportedServiceTierFor, @@ -1067,46 +1063,19 @@ export async function handleChatCore({ ? credentials.providerSpecificData.customUserAgent.trim() : ""; - const buildUpstreamHeadersForExecute = (modelToCall: string): Record => { - const upstreamHeaders = - modelToCall === effectiveModel - ? { - ...getModelUpstreamExtraHeaders(provider || "", model || "", sourceFormat), - ...getModelUpstreamExtraHeaders(provider || "", resolvedModel || "", sourceFormat), - } - : (() => { - const r = resolveModelAlias(modelToCall); - return { - ...getModelUpstreamExtraHeaders(provider || "", modelToCall || "", sourceFormat), - ...getModelUpstreamExtraHeaders(provider || "", r || "", sourceFormat), - }; - })(); - - if (connectionCustomUserAgent) { - upstreamHeaders["User-Agent"] = connectionCustomUserAgent; - if ("user-agent" in upstreamHeaders) { - upstreamHeaders["user-agent"] = connectionCustomUserAgent; - } - } - - // Claude Fast Mode opt-in. When the user has enabled this in - // Settings > AI AND the target provider is the canonical Anthropic - // `claude` provider (Claude Code-compatible CPA bridges are excluded - // since they already select their own entrypoint) AND the model id - // matches the configured list, signal to a paired CLIProxyAPI build to - // rewrite the cc_entrypoint so the request can reach Anthropic Fast - // Mode (speed:"fast"). CPA builds that do not understand the header - // forward it harmlessly. - if ( - provider === "claude" && - typeof settings !== "undefined" && - shouldRequestClaudeFastMode(settings, modelToCall) - ) { - upstreamHeaders[CPA_FORCE_FAST_MODE_HEADER] = "1"; - } - - return upstreamHeaders; - }; + // Upstream extra-header building extracted to chatCore/upstreamExecuteHeaders.ts (#3501); bind the + // per-request inputs once and delegate so the existing call sites stay byte-identical. + const buildUpstreamHeadersForExecute = (modelToCall: string): Record => + buildUpstreamHeadersForExecuteFor({ + modelToCall, + effectiveModel, + provider, + model, + resolvedModel, + sourceFormat, + connectionCustomUserAgent, + settings, + }); // Default to false unless client explicitly sets stream: true (OpenAI spec compliant) const acceptHeader = diff --git a/open-sse/handlers/chatCore/upstreamExecuteHeaders.ts b/open-sse/handlers/chatCore/upstreamExecuteHeaders.ts new file mode 100644 index 000000000000..cb8adae950db --- /dev/null +++ b/open-sse/handlers/chatCore/upstreamExecuteHeaders.ts @@ -0,0 +1,71 @@ +/** + * chatCore per-model upstream extra-header builder (Quality Gate v2 / Fase 9 — chatCore god-file + * decomposition, #3501). + * + * Pure builder extracted from handleChatCore: assembles the per-model upstream extra headers for an + * execute() call. Merges the configured per-model headers (for the resolved model and its alias), + * applies a connection's custom User-Agent override, and opts into Claude Fast Mode (CPA bridge + * header) when enabled in settings for the canonical `claude` provider and a supported model. + * Side-effect-free; behaviour is byte-identical to the previous inline closure. + */ + +import { getModelUpstreamExtraHeaders } from "@/lib/db/models"; +import { resolveModelAlias } from "../../services/modelDeprecation.ts"; +import { CPA_FORCE_FAST_MODE_HEADER, shouldRequestClaudeFastMode } from "@/lib/providers/claudeFastMode"; + +export function buildUpstreamHeadersForExecute(opts: { + modelToCall: string; + effectiveModel: string; + provider: string | null | undefined; + model: string | null | undefined; + resolvedModel: string; + sourceFormat: string; + connectionCustomUserAgent: string; + settings: unknown; +}): Record { + const { + modelToCall, + effectiveModel, + provider, + model, + resolvedModel, + sourceFormat, + connectionCustomUserAgent, + settings, + } = opts; + + const upstreamHeaders: Record = + modelToCall === effectiveModel + ? { + ...getModelUpstreamExtraHeaders(provider || "", model || "", sourceFormat), + ...getModelUpstreamExtraHeaders(provider || "", resolvedModel || "", sourceFormat), + } + : (() => { + const r = resolveModelAlias(modelToCall); + return { + ...getModelUpstreamExtraHeaders(provider || "", modelToCall || "", sourceFormat), + ...getModelUpstreamExtraHeaders(provider || "", r || "", sourceFormat), + }; + })(); + + if (connectionCustomUserAgent) { + upstreamHeaders["User-Agent"] = connectionCustomUserAgent; + if ("user-agent" in upstreamHeaders) { + upstreamHeaders["user-agent"] = connectionCustomUserAgent; + } + } + + // Claude Fast Mode opt-in. When enabled in Settings > AI AND the target provider is the canonical + // Anthropic `claude` provider (Claude Code-compatible CPA bridges are excluded since they select + // their own entrypoint) AND the model id matches the configured list, signal to a paired + // CLIProxyAPI build to rewrite the cc_entrypoint so the request reaches Anthropic Fast Mode. + if ( + provider === "claude" && + typeof settings !== "undefined" && + shouldRequestClaudeFastMode(settings, modelToCall) + ) { + upstreamHeaders[CPA_FORCE_FAST_MODE_HEADER] = "1"; + } + + return upstreamHeaders; +} diff --git a/tests/unit/chatcore-upstream-execute-headers.test.ts b/tests/unit/chatcore-upstream-execute-headers.test.ts new file mode 100644 index 000000000000..f770a1276a37 --- /dev/null +++ b/tests/unit/chatcore-upstream-execute-headers.test.ts @@ -0,0 +1,70 @@ +// tests/unit/chatcore-upstream-execute-headers.test.ts +// Characterization of buildUpstreamHeadersForExecute — the per-model upstream extra-header builder +// extracted from handleChatCore (chatCore god-file decomposition, #3501). Locks: the +// connection custom User-Agent override, the Claude Fast Mode opt-in (claude provider + enabled +// settings + supported model only), and the modelToCall===effectiveModel vs alias branches. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { buildUpstreamHeadersForExecute } from "../../open-sse/handlers/chatCore/upstreamExecuteHeaders.ts"; +import { CPA_FORCE_FAST_MODE_HEADER } from "../../src/lib/providers/claudeFastMode.ts"; + +const base = { + modelToCall: "some-model", + effectiveModel: "some-model", + provider: "openai", + model: "some-model", + resolvedModel: "some-model", + sourceFormat: "openai", + connectionCustomUserAgent: "", + settings: {}, +}; + +test("no custom UA / non-claude / no fast settings → no UA and no fast-mode header", () => { + const h = buildUpstreamHeadersForExecute({ ...base }); + assert.equal(h["User-Agent"], undefined); + assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined); +}); + +test("connection custom User-Agent overrides the upstream User-Agent", () => { + const h = buildUpstreamHeadersForExecute({ ...base, connectionCustomUserAgent: "MyAgent/1.0" }); + assert.equal(h["User-Agent"], "MyAgent/1.0"); +}); + +test("claude provider + enabled fast-mode settings + supported model → CPA header set", () => { + const h = buildUpstreamHeadersForExecute({ + ...base, + provider: "claude", + modelToCall: "claude-fast-x", + effectiveModel: "claude-fast-x", + settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } }, + }); + assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], "1"); +}); + +test("fast-mode header is NOT set when the model is not in the supported list", () => { + const h = buildUpstreamHeadersForExecute({ + ...base, + provider: "claude", + modelToCall: "claude-other", + effectiveModel: "claude-other", + settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } }, + }); + assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined); +}); + +test("fast-mode header is NOT set for non-claude providers even with fast settings", () => { + const h = buildUpstreamHeadersForExecute({ + ...base, + provider: "openai", + modelToCall: "claude-fast-x", + effectiveModel: "claude-fast-x", + settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } }, + }); + assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined); +}); + +test("returns a plain object (no per-model extra headers configured for unknown models)", () => { + const h = buildUpstreamHeadersForExecute({ ...base, modelToCall: "totally-unknown", effectiveModel: "x" }); + assert.equal(typeof h, "object"); + assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined); +});