From 73092146e85630cbd31cf18c7f5dcfd9b9df6e2a Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sat, 20 Jun 2026 16:56:23 -0300 Subject: [PATCH] refactor(chatCore): extract resolveChatCoreRequestSetup (first setup-phase slice) (#4392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Segundo incremento da decomposição do chatCore.ts (5127 LOC). Extrai a primeira fatia PURA da fase de request-setup do handleChatCore: a metadata de roteamento de modelo (apiFormat + targetFormat via structural-narrowing sem 'as any', + o requestedModel com fallback) — para um leaf testável em chatCore/requestSetup.ts. - resolveChatCoreRequestSetup(modelInfo, body, model): ChatCoreRequestSetup — puro, byte-idêntico ao bloco inline. Tipo ChatCoreRequestSetup é a semente do futuro ChatCoreContext (as fases maiores — setup/dispatch/streaming — virão depois). - chatCore.ts: bloco inline (~21 ln) vira 5 linhas. - 5 testes de caracterização (narrowing + fallback do requestedModel). 185/185 chatcore tests (caracterização do handleChatCore preservada), typecheck 0, complexity OK 1896 (mudança neutra), file-size encolhe. Timing: feito como leaf PURO seguro porque a v3.8.30 está finalizando — o ChatCoreContext mutável (carrega body + 20 inputs no hot-path) cabe melhor em v3.8.31. Follow-up: atribuir requestSetup.ts a um batch de mutação no nightly (como os demais chatCore leaves); 5 testes cobrem as branches por ora. --- open-sse/handlers/chatCore.ts | 28 +++--------- open-sse/handlers/chatCore/requestSetup.ts | 51 ++++++++++++++++++++++ tests/unit/chatcore-request-setup.test.ts | 40 +++++++++++++++++ 3 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 open-sse/handlers/chatCore/requestSetup.ts create mode 100644 tests/unit/chatcore-request-setup.test.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 76eb361ca1..a1c15d447d 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -1,4 +1,5 @@ import { injectMemoryAndSkills } from "./chatCore/memorySkillsInjection.ts"; +import { resolveChatCoreRequestSetup } from "./chatCore/requestSetup.ts"; import { checkIdempotencyCache } from "./chatCore/idempotency.ts"; import { checkSemanticCache } from "./chatCore/semanticCache.ts"; import { sanitizeChatRequestBody } from "./chatCore/sanitization.ts"; @@ -649,27 +650,12 @@ export async function handleChatCore({ /* memoryUsage() never throws */ } - // apiFormat is an optional custom-model marker injected by getModelInfo for - // providers whose models can route to /chat/completions or /responses - // (Azure AI Foundry, OCI generic OpenAI). It's not on the base ModelInfo - // shape, so we read it via a structural narrowing without `as any`. - const apiFormat: string | undefined = - modelInfo && typeof modelInfo === "object" && "apiFormat" in modelInfo - ? typeof (modelInfo as { apiFormat?: unknown }).apiFormat === "string" - ? ((modelInfo as { apiFormat?: string }).apiFormat as string) - : undefined - : undefined; - // #2905: per-model wire-format override for custom models, injected by - // getModelInfo. Custom models are not in the static registry, so - // getModelTargetFormat() can't see this — use it before the provider default. - const customModelTargetFormat: string | undefined = - modelInfo && typeof modelInfo === "object" && "targetFormat" in modelInfo - ? typeof (modelInfo as { targetFormat?: unknown }).targetFormat === "string" - ? ((modelInfo as { targetFormat?: string }).targetFormat as string) - : undefined - : undefined; - const requestedModel = - typeof body?.model === "string" && body.model.trim().length > 0 ? body.model : model; + // Per-request model-routing metadata (first extracted slice of the request-setup phase). + const { apiFormat, customModelTargetFormat, requestedModel } = resolveChatCoreRequestSetup( + modelInfo, + body, + model + ); const isModelScope = () => isModelScopeProvider(provider, credentials?.providerSpecificData); const startTime = Date.now(); // Per-request trace id + checkpoint helper. Lets us see exactly which await diff --git a/open-sse/handlers/chatCore/requestSetup.ts b/open-sse/handlers/chatCore/requestSetup.ts new file mode 100644 index 0000000000..6c193cc4a9 --- /dev/null +++ b/open-sse/handlers/chatCore/requestSetup.ts @@ -0,0 +1,51 @@ +/** + * chatCore request-setup resolvers (Quality Gate v2 / Fase 9 — chatCore god-file decomposition). + * + * The first pure slice of handleChatCore's request-setup phase: the per-request model-routing + * metadata resolved at the very top of the handler. Side-effect-free; future increments grow this + * into the full ChatCoreContext carrier (setup / dispatch / streaming phases). + */ + +export type ChatCoreRequestSetup = { + /** + * Optional custom-model wire-format marker injected by getModelInfo for providers whose models + * can route to /chat/completions or /responses (Azure AI Foundry, OCI generic OpenAI). Not on + * the base ModelInfo shape, so read via structural narrowing (never `as any`). + */ + apiFormat: string | undefined; + /** + * #2905: per-model wire-format override for custom models, injected by getModelInfo. Custom + * models aren't in the static registry, so getModelTargetFormat() can't see this — used before + * the provider default. + */ + customModelTargetFormat: string | undefined; + /** The client-requested model string, falling back to the resolved model id when absent/blank. */ + requestedModel: string; +}; + +/** + * Resolve the per-request model-routing metadata at the top of handleChatCore. Pure: a function of + * the injected modelInfo, the request body, and the resolved model id. Behaviour is byte-identical + * to the previous inline code. + */ +export function resolveChatCoreRequestSetup( + modelInfo: unknown, + body: { model?: unknown } | null | undefined, + model: string +): ChatCoreRequestSetup { + const apiFormat: string | undefined = + modelInfo && typeof modelInfo === "object" && "apiFormat" in modelInfo + ? typeof (modelInfo as { apiFormat?: unknown }).apiFormat === "string" + ? ((modelInfo as { apiFormat?: string }).apiFormat as string) + : undefined + : undefined; + const customModelTargetFormat: string | undefined = + modelInfo && typeof modelInfo === "object" && "targetFormat" in modelInfo + ? typeof (modelInfo as { targetFormat?: unknown }).targetFormat === "string" + ? ((modelInfo as { targetFormat?: string }).targetFormat as string) + : undefined + : undefined; + const requestedModel = + typeof body?.model === "string" && body.model.trim().length > 0 ? body.model : model; + return { apiFormat, customModelTargetFormat, requestedModel }; +} diff --git a/tests/unit/chatcore-request-setup.test.ts b/tests/unit/chatcore-request-setup.test.ts new file mode 100644 index 0000000000..ab89087793 --- /dev/null +++ b/tests/unit/chatcore-request-setup.test.ts @@ -0,0 +1,40 @@ +// tests/unit/chatcore-request-setup.test.ts +// Characterization of resolveChatCoreRequestSetup — the first extracted pure slice of +// chatCore's request-setup phase (god-file decomposition). Locks the structural-narrowing +// of apiFormat/targetFormat and the requestedModel fallback. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { resolveChatCoreRequestSetup } from "../../open-sse/handlers/chatCore/requestSetup.ts"; + +test("reads apiFormat / targetFormat only when present as strings", () => { + const r = resolveChatCoreRequestSetup( + { apiFormat: "responses", targetFormat: "openai" }, + { model: "gpt-4o" }, + "resolved-model" + ); + assert.equal(r.apiFormat, "responses"); + assert.equal(r.customModelTargetFormat, "openai"); +}); + +test("apiFormat / targetFormat absent or non-string → undefined", () => { + assert.equal(resolveChatCoreRequestSetup({}, {}, "m").apiFormat, undefined); + assert.equal(resolveChatCoreRequestSetup({ apiFormat: 123 }, {}, "m").apiFormat, undefined); + assert.equal(resolveChatCoreRequestSetup({ targetFormat: null }, {}, "m").customModelTargetFormat, undefined); +}); + +test("modelInfo that is not an object → both markers undefined", () => { + const r = resolveChatCoreRequestSetup(null, { model: "x" }, "m"); + assert.equal(r.apiFormat, undefined); + assert.equal(r.customModelTargetFormat, undefined); +}); + +test("requestedModel uses the client body.model when it is a non-blank string", () => { + assert.equal(resolveChatCoreRequestSetup({}, { model: "claude-3" }, "fallback").requestedModel, "claude-3"); +}); + +test("requestedModel falls back to the resolved model id when body.model is blank/absent/non-string", () => { + assert.equal(resolveChatCoreRequestSetup({}, { model: " " }, "fallback").requestedModel, "fallback"); + assert.equal(resolveChatCoreRequestSetup({}, {}, "fallback").requestedModel, "fallback"); + assert.equal(resolveChatCoreRequestSetup({}, { model: 42 }, "fallback").requestedModel, "fallback"); + assert.equal(resolveChatCoreRequestSetup({}, null, "fallback").requestedModel, "fallback"); +});