From 5f4e9e73a423b5f4d0d866a11ee227aa91f3e4d8 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 23 Jun 2026 07:21:13 -0300 Subject: [PATCH] =?UTF-8?q?refactor(chatCore):=20extrai=20prepareUpstreamB?= =?UTF-8?q?ody=20(1=C2=AA=20sub-fatia=20do=20executeProviderRequest,=20#35?= =?UTF-8?q?01)=20(#4730)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chatCore #3501: extract prepareUpstreamBody (first sub-slice of executeProviderRequest) to leaf (upstreamBody.ts). Clean cherry-pick post-#4721. 7/7 new leaf tests, full 301/301 chatcore suite, typecheck/cycles/file-size green. Completes the 6-PR chatCore decomposition stack into release/v3.8.35. --- open-sse/handlers/chatCore.ts | 97 ++------------ open-sse/handlers/chatCore/upstreamBody.ts | 141 +++++++++++++++++++++ tests/unit/chatcore-upstream-body.test.ts | 102 +++++++++++++++ 3 files changed, 253 insertions(+), 87 deletions(-) create mode 100644 open-sse/handlers/chatCore/upstreamBody.ts create mode 100644 tests/unit/chatcore-upstream-body.test.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 1a1a16c134..da1cbef625 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -106,7 +106,6 @@ import { COOLDOWN_MS, HTTP_STATUS, FETCH_BODY_TIMEOUT_MS, - MAX_TOOLS_LIMIT, PROVIDER_MAX_TOKENS, SSE_HEARTBEAT_INTERVAL_MS, STREAM_IDLE_TIMEOUT_MS, @@ -145,6 +144,7 @@ import { } from "./chatCore/attemptLogging.ts"; import { stageTrace } from "./chatCore/stageTrace.ts"; import { attachCompressionUsageReceiptAfterAnalytics as attachCompressionUsageReceiptAfterAnalyticsFor } from "./chatCore/compressionUsageReceipt.ts"; +import { prepareUpstreamBody } from "./chatCore/upstreamBody.ts"; import { getCallLogPipelineCaptureStreamChunks, @@ -185,14 +185,7 @@ import { getProviderCredentials, extractSessionAffinityKey } from "@/sse/service import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity"; import { getCacheControlSettings } from "@/lib/cacheControlSettings"; import { guardrailRegistry, resolveDisabledGuardrails } from "@/lib/guardrails"; -import { - applyConfiguredPayloadRules, - resolvePayloadRuleProtocols, -} from "../services/payloadRules.ts"; -import { - shouldPreserveCacheControl, - providerSupportsCaching, -} from "../utils/cacheControlPolicy.ts"; +import { shouldPreserveCacheControl } from "../utils/cacheControlPolicy.ts"; import { getCachedSettings } from "@/lib/db/readCache"; import { applyCodexGlobalFastServiceTier } from "@/lib/providers/codexFastTier"; import { buildUpstreamHeadersForExecute as buildUpstreamHeadersForExecuteFor } from "./chatCore/upstreamExecuteHeaders.ts"; @@ -204,7 +197,6 @@ import { import { cacheReasoningFromAssistantMessage } from "../services/reasoningCache.ts"; import { sanitizeOpenAITool } from "../services/toolSchemaSanitizer.ts"; import { - getEffectiveToolLimit, setDetectedToolLimit, parseToolLimitFromError, shouldDetectLimit, @@ -2109,86 +2101,17 @@ export async function handleChatCore({ connectionId, credentials: executionCredentials, }); - let bodyToSend = - translatedBody.model === modelToCall - ? translatedBody - : { ...translatedBody, model: modelToCall }; - const payloadRuleModel = - typeof bodyToSend.model === "string" && bodyToSend.model.length > 0 - ? bodyToSend.model - : modelToCall; - const payloadRuleProtocols = resolvePayloadRuleProtocols({ + // Upstream body preparation extracted to chatCore/upstreamBody.ts (#3501 — first internal + // sub-slice of executeProviderRequest); produces the body sent upstream (payload rules + + // tool-limit truncation + qwen oauth user backfill + prompt_cache_key injection). + let bodyToSend = await prepareUpstreamBody({ + translatedBody, + modelToCall, provider, targetFormat, + credentials, + log, }); - const payloadRuleResult = await applyConfiguredPayloadRules( - bodyToSend, - payloadRuleModel, - payloadRuleProtocols - ); - bodyToSend = payloadRuleResult.payload; - - if (payloadRuleResult.applied.length > 0) { - const appliedSummary = payloadRuleResult.applied - .map((rule) => { - if (rule.type === "filter") return `${rule.type}:${rule.path}`; - const serializedValue = JSON.stringify(rule.value); - const safeValue = - typeof serializedValue === "string" && serializedValue.length > 80 - ? `${serializedValue.slice(0, 77)}...` - : serializedValue; - return `${rule.type}:${rule.path}=${safeValue}`; - }) - .join(", "); - log?.debug?.( - "PAYLOAD_RULES", - `Applied ${payloadRuleResult.applied.length} rule(s) for ${payloadRuleModel} (${payloadRuleProtocols.join(", ")}): ${appliedSummary}` - ); - } - - const effectiveToolLimit = getEffectiveToolLimit(provider); - if ( - effectiveToolLimit < MAX_TOOLS_LIMIT && - Array.isArray(bodyToSend.tools) && - bodyToSend.tools.length > effectiveToolLimit - ) { - const truncatedTools = bodyToSend.tools.slice(0, effectiveToolLimit); - bodyToSend = { ...bodyToSend, tools: truncatedTools }; - log?.debug?.( - "TOOL_LIMIT", - `Truncated ${bodyToSend.tools.length} tools to ${effectiveToolLimit} for ${provider}` - ); - } - - // Qwen OAuth rejects requests without a non-empty `user` field. - // Some minimal OpenAI-compatible clients omit it, so we backfill a - // stable default only for OAuth mode (API key mode is unaffected). - const hasValidQwenUser = - typeof bodyToSend.user === "string" && bodyToSend.user.trim().length > 0; - const isQwenOAuthRequest = - provider === "qwen" && - !credentials?.apiKey && - typeof credentials?.accessToken === "string" && - credentials.accessToken.trim().length > 0; - if (isQwenOAuthRequest && !hasValidQwenUser) { - bodyToSend = { ...bodyToSend, user: "omniroute-qwen-oauth" }; - log?.debug?.("QWEN", "Injected fallback user for OAuth request"); - } - - // Inject prompt_cache_key only for providers that support it - if ( - targetFormat === FORMATS.OPENAI && - providerSupportsCaching(provider) && - !bodyToSend.prompt_cache_key && - Array.isArray(bodyToSend.messages) && - !["nvidia", "codex", "xai"].includes(provider) - ) { - const { generatePromptCacheKey } = await import("@/lib/promptCache"); - const cacheKey = generatePromptCacheKey(bodyToSend.messages); - if (cacheKey) { - bodyToSend = { ...bodyToSend, prompt_cache_key: cacheKey }; - } - } updatePendingScope(pendingScope, { providerRequest: bodyToSend, diff --git a/open-sse/handlers/chatCore/upstreamBody.ts b/open-sse/handlers/chatCore/upstreamBody.ts new file mode 100644 index 0000000000..51d6c74c11 --- /dev/null +++ b/open-sse/handlers/chatCore/upstreamBody.ts @@ -0,0 +1,141 @@ +/** + * chatCore upstream body preparation (Quality Gate v2 / Fase 9 — chatCore god-file decomposition, + * #3501 — first internal sub-slice of executeProviderRequest). + * + * Extracted from handleChatCore's execute() closure: prepares the body actually sent upstream for a + * given target model. Pins the model id, applies the configured payload rules, truncates the tool + * list to the provider's effective limit, backfills a default `user` for Qwen OAuth requests, and + * injects an OpenAI `prompt_cache_key` for caching-capable providers. Pure with respect to handler + * state (returns a fresh body, only logs as a side effect); behaviour is byte-identical to the + * previous inline block. Split into small private steps so each stays under the complexity cap. + */ + +import { + applyConfiguredPayloadRules, + resolvePayloadRuleProtocols, +} from "../../services/payloadRules.ts"; +import { getEffectiveToolLimit } from "../../services/toolLimitDetector.ts"; +import { providerSupportsCaching } from "../../utils/cacheControlPolicy.ts"; +import { MAX_TOOLS_LIMIT } from "../../config/constants.ts"; +import { FORMATS } from "../../translator/formats.ts"; + +type LoggerLike = { debug?: (...args: unknown[]) => void } | null | undefined; +type Body = Record; +type CredentialsLike = { apiKey?: unknown; accessToken?: unknown } | null | undefined; + +function buildAppliedRulesSummary( + applied: Array<{ type: string; path: string; value?: unknown }> +): string { + return applied + .map((rule) => { + if (rule.type === "filter") return `${rule.type}:${rule.path}`; + const serializedValue = JSON.stringify(rule.value); + const safeValue = + typeof serializedValue === "string" && serializedValue.length > 80 + ? `${serializedValue.slice(0, 77)}...` + : serializedValue; + return `${rule.type}:${rule.path}=${safeValue}`; + }) + .join(", "); +} + +function truncateToolList(bodyToSend: Body, provider: string | null | undefined, log?: LoggerLike): Body { + const effectiveToolLimit = getEffectiveToolLimit(provider); + if ( + effectiveToolLimit < MAX_TOOLS_LIMIT && + Array.isArray(bodyToSend.tools) && + bodyToSend.tools.length > effectiveToolLimit + ) { + const truncatedTools = bodyToSend.tools.slice(0, effectiveToolLimit); + bodyToSend = { ...bodyToSend, tools: truncatedTools }; + log?.debug?.( + "TOOL_LIMIT", + `Truncated ${(bodyToSend.tools as unknown[]).length} tools to ${effectiveToolLimit} for ${provider}` + ); + } + return bodyToSend; +} + +// Qwen OAuth rejects requests without a non-empty `user` field. Some minimal OpenAI-compatible +// clients omit it, so we backfill a stable default only for OAuth mode (API key mode is unaffected). +function backfillQwenOAuthUser( + bodyToSend: Body, + provider: string | null | undefined, + credentials: CredentialsLike, + log?: LoggerLike +): Body { + const hasValidQwenUser = + typeof bodyToSend.user === "string" && bodyToSend.user.trim().length > 0; + const isQwenOAuthRequest = + provider === "qwen" && + !credentials?.apiKey && + typeof credentials?.accessToken === "string" && + credentials.accessToken.trim().length > 0; + if (isQwenOAuthRequest && !hasValidQwenUser) { + bodyToSend = { ...bodyToSend, user: "omniroute-qwen-oauth" }; + log?.debug?.("QWEN", "Injected fallback user for OAuth request"); + } + return bodyToSend; +} + +// Inject prompt_cache_key only for providers that support it. +async function injectPromptCacheKey( + bodyToSend: Body, + provider: string | null | undefined, + targetFormat: string +): Promise { + if ( + targetFormat === FORMATS.OPENAI && + providerSupportsCaching(provider) && + !bodyToSend.prompt_cache_key && + Array.isArray(bodyToSend.messages) && + !["nvidia", "codex", "xai"].includes(provider) + ) { + const { generatePromptCacheKey } = await import("@/lib/promptCache"); + const cacheKey = generatePromptCacheKey(bodyToSend.messages); + if (cacheKey) { + bodyToSend = { ...bodyToSend, prompt_cache_key: cacheKey }; + } + } + return bodyToSend; +} + +export async function prepareUpstreamBody(opts: { + translatedBody: Body; + modelToCall: string; + provider: string | null | undefined; + targetFormat: string; + credentials: CredentialsLike; + log?: LoggerLike; +}): Promise { + const { translatedBody, modelToCall, provider, targetFormat, credentials, log } = opts; + + let bodyToSend: Body = + translatedBody.model === modelToCall + ? translatedBody + : { ...translatedBody, model: modelToCall }; + const payloadRuleModel = + typeof bodyToSend.model === "string" && bodyToSend.model.length > 0 + ? bodyToSend.model + : modelToCall; + const payloadRuleProtocols = resolvePayloadRuleProtocols({ provider, targetFormat }); + const payloadRuleResult = await applyConfiguredPayloadRules( + bodyToSend, + payloadRuleModel, + payloadRuleProtocols + ); + bodyToSend = payloadRuleResult.payload; + + if (payloadRuleResult.applied.length > 0) { + log?.debug?.( + "PAYLOAD_RULES", + `Applied ${payloadRuleResult.applied.length} rule(s) for ${payloadRuleModel} (${payloadRuleProtocols.join(", ")}): ${buildAppliedRulesSummary(payloadRuleResult.applied)}` + ); + } + + bodyToSend = truncateToolList(bodyToSend, provider, log); + bodyToSend = backfillQwenOAuthUser(bodyToSend, provider, credentials, log); + bodyToSend = await injectPromptCacheKey(bodyToSend, provider, targetFormat); + + return bodyToSend; +} diff --git a/tests/unit/chatcore-upstream-body.test.ts b/tests/unit/chatcore-upstream-body.test.ts new file mode 100644 index 0000000000..463b34faed --- /dev/null +++ b/tests/unit/chatcore-upstream-body.test.ts @@ -0,0 +1,102 @@ +// tests/unit/chatcore-upstream-body.test.ts +// Characterization of prepareUpstreamBody — the first internal sub-slice of executeProviderRequest +// (chatCore god-file decomposition, #3501). Uses a fresh temp DB (no payload rules / no detected +// tool limits → defaults). Locks: target-model pinning, the Qwen OAuth user backfill (and its +// guards), and the prompt_cache_key gating (excluded providers + non-OPENAI format never inject). +import { test, before, after } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-upstream-body-test-")); +process.env.DATA_DIR = testDataDir; + +const coreDb = await import("../../src/lib/db/core.ts"); +const { prepareUpstreamBody } = await import("../../open-sse/handlers/chatCore/upstreamBody.ts"); + +before(async () => { + await coreDb.ensureDbInitialized(); +}); + +after(() => { + coreDb.resetDbInstance(); + fs.rmSync(testDataDir, { recursive: true, force: true }); +}); + +test("pins the target model when it differs from the translated body model", async () => { + const out = await prepareUpstreamBody({ + translatedBody: { model: "model-a", messages: [] }, + modelToCall: "model-b", + provider: "some-provider", + targetFormat: "claude", + credentials: null, + }); + assert.equal(out.model, "model-b"); +}); + +test("leaves the model untouched when it already matches", async () => { + const out = await prepareUpstreamBody({ + translatedBody: { model: "model-a", messages: [] }, + modelToCall: "model-a", + provider: "some-provider", + targetFormat: "claude", + credentials: null, + }); + assert.equal(out.model, "model-a"); +}); + +test("backfills the Qwen OAuth user when missing", async () => { + const out = await prepareUpstreamBody({ + translatedBody: { model: "qwen-max", messages: [] }, + modelToCall: "qwen-max", + provider: "qwen", + targetFormat: "claude", + credentials: { accessToken: "tok-123" }, + }); + assert.equal(out.user, "omniroute-qwen-oauth"); +}); + +test("does not backfill the Qwen user when an apiKey is present (API-key mode)", async () => { + const out = await prepareUpstreamBody({ + translatedBody: { model: "qwen-max", messages: [] }, + modelToCall: "qwen-max", + provider: "qwen", + targetFormat: "claude", + credentials: { apiKey: "k", accessToken: "tok-123" }, + }); + assert.equal(out.user, undefined); +}); + +test("does not backfill the Qwen user when one is already set", async () => { + const out = await prepareUpstreamBody({ + translatedBody: { model: "qwen-max", messages: [], user: "real-user" }, + modelToCall: "qwen-max", + provider: "qwen", + targetFormat: "claude", + credentials: { accessToken: "tok-123" }, + }); + assert.equal(out.user, "real-user"); +}); + +test("never injects prompt_cache_key for an excluded provider (codex)", async () => { + const out = await prepareUpstreamBody({ + translatedBody: { model: "gpt-5-codex", messages: [{ role: "user", content: "hi" }] }, + modelToCall: "gpt-5-codex", + provider: "codex", + targetFormat: "openai", + credentials: null, + }); + assert.equal(out.prompt_cache_key, undefined); +}); + +test("never injects prompt_cache_key when the target format is not OpenAI", async () => { + const out = await prepareUpstreamBody({ + translatedBody: { model: "claude-x", messages: [{ role: "user", content: "hi" }] }, + modelToCall: "claude-x", + provider: "claude", + targetFormat: "claude", + credentials: null, + }); + assert.equal(out.prompt_cache_key, undefined); +});