refactor(chatCore): extract resolveChatCoreRequestSetup (first setup-phase slice) (#4392)

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.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-20 16:56:23 -03:00
committed by GitHub
parent eb48e4d953
commit 73092146e8
3 changed files with 98 additions and 21 deletions

View File

@@ -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

View File

@@ -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 };
}

View File

@@ -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");
});