refactor(combo): ComboContext + extract phaseComboSetup (god-file split fase 1) (#4326)

Primeiro incremento da decomposição do god-file combo.ts (2604 LOC). Extrai o
bloco de setup do handleComboChat (strategy/relay/resilience/universal-handoff/
context-cache pinning/agent middleware/config cascade/timeout) para
phaseComboSetup(ctx), com ComboContext carregando o body mutável compartilhado.

- combo/context.ts: ComboContext (carrier do body mutável) + createComboContext.
- combo/comboSetup.ts: phaseComboSetup(ctx): ComboSetup (byte-equivalente ao bloco
  inline; reescreve ctx.body no pin + middleware, retorna os locals derivados).
- comboConfig.ts: resolveComboSetupConfig (DRY — encapsula o ternário do config,
  tipo único p/ ComboContext.config sem cast).
- combo.ts: -47 linhas; rebinda os locals do ctx, resto de handleComboChat intacto;
  remove 6 imports que ficaram órfãos.

Comportamento preservado: 357 testes de caracterização combo seguem verdes (+4 novos),
integração sse-correctness 5/5, typecheck 0, file-size encolhe (sem _rebaseline).
Fases 2-N = sub-planos follow-up. Ver _tasks/quality/2026-06-19-DESIGN-godfiles-decomposition.md.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-19 22:39:49 -03:00
committed by GitHub
parent 5cca4ff90c
commit fbb423f32b
5 changed files with 259 additions and 68 deletions

View File

@@ -18,7 +18,7 @@ import {
isProviderExhaustedReason,
hasPerModelQuota,
} from "./accountFallback.ts";
import { FETCH_TIMEOUT_MS, RateLimitReason } from "../config/constants.ts";
import { RateLimitReason } from "../config/constants.ts";
import { errorResponse, unavailableResponse } from "../utils/error.ts";
import {
recordComboIntent,
@@ -29,14 +29,11 @@ import {
import {
resolveComboConfig,
getDefaultComboConfig,
resolveComboTargetTimeoutMs,
} from "./comboConfig.ts";
import {
maybeGenerateHandoff,
resolveContextRelayConfig,
maybeGenerateUniversalHandoff,
injectUniversalHandoffBody,
resolveUniversalHandoffConfig,
SKIP_UNIVERSAL_HANDOFF_FLAG,
type MessageLike,
} from "./contextHandoff.ts";
@@ -53,7 +50,8 @@ import * as semaphore from "./rateLimitSemaphore.ts";
import { getCircuitBreaker } from "../../src/shared/utils/circuitBreaker";
import { fisherYatesShuffle, getNextFromDeck } from "../../src/shared/utils/shuffleDeck";
import { parseModel } from "./model.ts";
import { applyComboAgentMiddleware } from "./comboAgentMiddleware.ts";
import { createComboContext } from "./combo/context.ts";
import { phaseComboSetup } from "./combo/comboSetup.ts";
import { checkCredentialGate, logCredentialSkip } from "./credentialGate.ts";
import { emit } from "../../src/lib/events/eventBus";
import { notifyWebhookEvent } from "../../src/lib/webhookDispatcher";
@@ -76,7 +74,6 @@ import type { RoutingHint } from "./manifestAdapter";
import { buildComplexityRoutingHint } from "./autoCombo/complexityRouter";
import type { CompressionMode } from "./compression/types.ts";
import { getProviderConnections } from "../../src/lib/db/providers";
import { normalizeRoutingStrategy } from "../../src/shared/constants/routingStrategies.ts";
import {
isProviderInCooldown,
recordProviderCooldown,
@@ -148,7 +145,6 @@ import {
applyRequestTagRouting,
scoreAutoTargets,
expandAutoComboCandidatePool,
deriveComboSessionKey,
} from "./combo/autoStrategy.ts";
import {
resolveResetWindowConfig,
@@ -426,67 +422,24 @@ export async function handleComboChat({
signal,
apiKeyAllowedConnections = null,
}: HandleComboChatOptions): Promise<Response> {
const strategy = normalizeRoutingStrategy(combo.strategy || "priority");
const relayConfig =
strategy === "context-relay" ? resolveContextRelayConfig(relayOptions?.config || null) : null;
const resilienceSettings: ResilienceSettings = settings
? resolveResilienceSettings(settings)
: resolveResilienceSettings(null);
const universalHandoffConfig = resolveUniversalHandoffConfig(
(combo.universal_handoff || combo.universalHandoff) as
| Record<string, unknown>
| null
| undefined,
relayOptions?.universalHandoffConfig as Record<string, unknown> | null | undefined
);
// ── Server-side context cache pinning (replaces <omniModel> tag roundtrip) ─
// Uses session_model_history — no client-side tag injection, no visible output pollution.
//
// #3825: when the client sends no session id (most OpenAI-compatible clients), fall
// back to a stable conversation fingerprint derived from the body so the combo still
// re-pins to the same model across turns. ONLY engaged when context_cache_protection
// is truthy — when the toggle is off, behavior is unchanged (combos rotate as before,
// no pin read/write, no <omniModel> tag).
const effectiveSessionId: string | null = combo.context_cache_protection
? (relayOptions?.sessionId ?? deriveComboSessionKey(body))
: null;
let pinnedModel: string | null = null;
if (
combo.context_cache_protection &&
effectiveSessionId &&
!(body as Record<string, unknown>)?.[SKIP_UNIVERSAL_HANDOFF_FLAG]
) {
const pinned = getLastSessionModel(effectiveSessionId, combo.name);
if (pinned) {
body = { ...body, model: pinned };
pinnedModel = pinned;
log.info("COMBO", `[#401] Context cache: pinned model=${pinned} (server-side)`);
}
}
// ── Combo Agent Middleware (#399 + #401) ────────────────────────────────
// Apply system_message override, tool_filter_regex.
// Context cache pinning is handled above via session_model_history.
const { body: agentBody } = applyComboAgentMiddleware(
body,
combo,
"" // provider/model not yet known — resolved per-model in loop
);
body = agentBody;
const clientRequestedStream = body?.stream === true;
// Context cache pinning is handled above via server-side session_model_history.
// No tag injection on response — use handleSingleModel directly.
// ─────────────────────────────────────────────────────────────────────────
// Use config cascade before dispatch so all strategies, pinned context routes,
// and round-robin targets share the same timeout policy.
const config = settings
? resolveComboConfig(combo, settings)
: { ...getDefaultComboConfig(), ...(combo.config || {}) };
const comboTargetTimeoutMs = resolveComboTargetTimeoutMs(config, FETCH_TIMEOUT_MS);
const reasoningTokenBufferEnabled = config.reasoningTokenBufferEnabled !== false;
// Combo setup phase (god-file decomposition fase 1): strategy / relay / resilience /
// universal-handoff / context-cache pinning / agent middleware / config cascade / timeout.
// phaseComboSetup rewrites ctx.body (pinning + middleware); rebind `body` from it so the
// rest of handleComboChat is unchanged. See combo/comboSetup.ts + combo/context.ts.
const comboCtx = createComboContext({ body, combo, settings, relayOptions, log });
const {
strategy,
relayConfig,
resilienceSettings,
universalHandoffConfig,
effectiveSessionId,
pinnedModel,
clientRequestedStream,
config,
comboTargetTimeoutMs,
reasoningTokenBufferEnabled,
} = phaseComboSetup(comboCtx);
body = comboCtx.body;
// ── Per-model timeout wrapper ────────────────────────────────────────────
// Combo target timeouts inherit FETCH_TIMEOUT_MS by default. Operators can

View File

@@ -0,0 +1,115 @@
/**
* phaseComboSetup — the first extracted phase of handleComboChat (combo.ts).
*
* Resolves the per-request combo setup BEFORE dispatch: routing strategy, context-relay
* config, resilience settings, universal-handoff config, server-side context-cache pinning
* (which rewrites ctx.body), combo agent middleware (also rewrites ctx.body), the config
* cascade, the per-target timeout, and the reasoning-token-buffer flag. Behaviour is
* byte-identical to the inline block it replaces — it just reads/writes via ComboContext.
*
* See _tasks/quality/2026-06-19-DESIGN-godfiles-decomposition.md §4.
*/
import { normalizeRoutingStrategy } from "../../../src/shared/constants/routingStrategies.ts";
import {
resolveContextRelayConfig,
resolveUniversalHandoffConfig,
SKIP_UNIVERSAL_HANDOFF_FLAG,
} from "../contextHandoff.ts";
import { getLastSessionModel } from "../../../src/lib/db/contextHandoffs.ts";
import { applyComboAgentMiddleware } from "../comboAgentMiddleware.ts";
import {
resolveComboSetupConfig,
resolveComboTargetTimeoutMs,
} from "../comboConfig.ts";
import { resolveResilienceSettings } from "../../../src/lib/resilience/settings";
import { FETCH_TIMEOUT_MS } from "../../config/constants.ts";
import { deriveComboSessionKey } from "./autoStrategy.ts";
import type { ComboContext } from "./context.ts";
export interface ComboSetup {
strategy: ReturnType<typeof normalizeRoutingStrategy>;
relayConfig: ReturnType<typeof resolveContextRelayConfig> | null;
resilienceSettings: ReturnType<typeof resolveResilienceSettings>;
universalHandoffConfig: ReturnType<typeof resolveUniversalHandoffConfig>;
effectiveSessionId: string | null;
pinnedModel: string | null;
clientRequestedStream: boolean;
config: ReturnType<typeof resolveComboSetupConfig>;
comboTargetTimeoutMs: number;
reasoningTokenBufferEnabled: boolean;
}
export function phaseComboSetup(ctx: ComboContext): ComboSetup {
const { combo, settings, relayOptions, log } = ctx;
const strategy = normalizeRoutingStrategy(combo.strategy || "priority");
const relayConfig =
strategy === "context-relay" ? resolveContextRelayConfig(relayOptions?.config || null) : null;
const resilienceSettings = settings
? resolveResilienceSettings(settings)
: resolveResilienceSettings(null);
const universalHandoffConfig = resolveUniversalHandoffConfig(
(combo.universal_handoff || combo.universalHandoff) as
| Record<string, unknown>
| null
| undefined,
relayOptions?.universalHandoffConfig as Record<string, unknown> | null | undefined
);
// ── Server-side context cache pinning (replaces <omniModel> tag roundtrip) ─
// Uses session_model_history — no client-side tag injection, no visible output pollution.
//
// #3825: when the client sends no session id (most OpenAI-compatible clients), fall
// back to a stable conversation fingerprint derived from the body so the combo still
// re-pins to the same model across turns. ONLY engaged when context_cache_protection
// is truthy — when the toggle is off, behavior is unchanged (combos rotate as before,
// no pin read/write, no <omniModel> tag).
const effectiveSessionId: string | null = combo.context_cache_protection
? (relayOptions?.sessionId ?? deriveComboSessionKey(ctx.body))
: null;
let pinnedModel: string | null = null;
if (
combo.context_cache_protection &&
effectiveSessionId &&
!(ctx.body as Record<string, unknown>)?.[SKIP_UNIVERSAL_HANDOFF_FLAG]
) {
const pinned = getLastSessionModel(effectiveSessionId, combo.name);
if (pinned) {
ctx.body = { ...ctx.body, model: pinned };
pinnedModel = pinned;
log.info("COMBO", `[#401] Context cache: pinned model=${pinned} (server-side)`);
}
}
// ── Combo Agent Middleware (#399 + #401) ────────────────────────────────
// Apply system_message override, tool_filter_regex.
// Context cache pinning is handled above via session_model_history.
const { body: agentBody } = applyComboAgentMiddleware(
ctx.body,
combo,
"" // provider/model not yet known — resolved per-model in loop
);
ctx.body = agentBody;
const clientRequestedStream = ctx.body?.stream === true;
// Use config cascade before dispatch so all strategies, pinned context routes,
// and round-robin targets share the same timeout policy.
const config = resolveComboSetupConfig(combo, settings);
const comboTargetTimeoutMs = resolveComboTargetTimeoutMs(config, FETCH_TIMEOUT_MS);
const reasoningTokenBufferEnabled = config.reasoningTokenBufferEnabled !== false;
return {
strategy,
relayConfig,
resilienceSettings,
universalHandoffConfig,
effectiveSessionId,
pinnedModel,
clientRequestedStream,
config,
comboTargetTimeoutMs,
reasoningTokenBufferEnabled,
};
}

View File

@@ -0,0 +1,46 @@
/**
* ComboContext — the per-request mutable carrier for the combo dispatch pipeline
* (Quality Gate v2 / Fase 9 — god-file decomposition, fase 1).
*
* handleComboChat in combo.ts is a ~1600-LOC orchestrator whose phases share a set of
* "dispatch frontier" locals — chief among them the request `body`, which the setup phase
* rewrites (context-cache pinning + combo agent middleware) and later dispatch phases keep
* sharing. Carrying that mutable state on a single ctx object lets each phase be extracted
* into a small, independently testable/mutatable function instead of living inside the
* monolith. This is the first extracted slice; subsequent phases will add their own shared
* mutable fields (executor, runUpstreamStream, semaphore, retry accumulators) to ComboContext.
*
* See _tasks/quality/2026-06-19-DESIGN-godfiles-decomposition.md §4.
*/
import type { ComboLike, ComboLogger, ComboRelayOptions } from "./types.ts";
export interface ComboContext {
/**
* Request body — MUTABLE. phaseComboSetup rewrites it for server-side context-cache
* pinning and combo agent middleware; later dispatch phases keep reading/mutating it.
*/
body: Record<string, unknown>;
readonly combo: ComboLike;
readonly settings: Record<string, unknown> | null;
readonly relayOptions: ComboRelayOptions | null;
readonly log: ComboLogger;
}
export function createComboContext(opts: {
body: Record<string, unknown>;
combo: ComboLike;
settings?: Record<string, unknown> | null;
relayOptions?: ComboRelayOptions | null;
log: ComboLogger;
}): ComboContext {
// body is carried by reference (not copied) so phaseComboSetup's reassignments are
// byte-identical to the original inline code: it only ever replaces ctx.body with a NEW
// object on a pin ({ ...body, model }) or via the middleware result, never mutates in place.
return {
body: opts.body,
combo: opts.combo,
settings: opts.settings ?? null,
relayOptions: opts.relayOptions ?? null,
log: opts.log,
};
}

View File

@@ -180,3 +180,15 @@ export function resolveComboConfig(
export function getDefaultComboConfig() {
return { ...DEFAULT_COMBO_CONFIG };
}
/**
* Resolve the effective combo config the same way handleComboChat does: cascade via
* resolveComboConfig when settings exist, else the defaults merged with the combo's own
* config. Encapsulated here so the ternary lives in one place (DRY) and its inferred union
* return type is the single source of truth for ComboContext.config (combo/context.ts).
*/
export function resolveComboSetupConfig(combo: ComboConfigLike, settings: ComboSettingsLike) {
return settings
? resolveComboConfig(combo, settings)
: { ...getDefaultComboConfig(), ...((combo?.config as Record<string, unknown>) || {}) };
}

View File

@@ -0,0 +1,65 @@
// tests/unit/combo/combo-context.test.ts
// Unit tests for the first extracted combo phase (god-file decomposition fase 1):
// createComboContext (combo/context.ts) + phaseComboSetup (combo/comboSetup.ts).
// The pinning-ON path (getLastSessionModel) is covered end-to-end by the existing combo
// characterization suite (combo-sessionless-pin-3825, combo-config, etc.); here we exercise
// the pure path (context_cache_protection OFF, no settings -> no DB) and the body carrier.
import { test } from "node:test";
import assert from "node:assert/strict";
import { createComboContext } from "../../../open-sse/services/combo/context.ts";
import { phaseComboSetup } from "../../../open-sse/services/combo/comboSetup.ts";
const log = { info() {}, warn() {}, error() {}, debug() {} };
test("createComboContext carries inputs and the body BY REFERENCE", () => {
const body = { model: "auto", messages: [], stream: true };
const combo = { name: "c1", models: ["a", "b"] };
const ctx = createComboContext({ body, combo, log });
assert.equal(ctx.body, body, "body must be the same reference (not copied) for byte-identical pinning");
assert.equal(ctx.combo, combo);
assert.equal(ctx.settings, null);
assert.equal(ctx.relayOptions, null);
assert.equal(ctx.log, log);
});
test("phaseComboSetup resolves strategy/config/stream with pinning OFF (pure, no DB)", () => {
const body = { model: "auto", messages: [], stream: true };
const combo = { name: "c1", models: ["a"], strategy: "priority" };
const ctx = createComboContext({ body, combo, log });
const setup = phaseComboSetup(ctx);
assert.equal(setup.strategy, "priority");
assert.equal(setup.pinnedModel, null, "no pin when context_cache_protection is off");
assert.equal(setup.effectiveSessionId, null);
assert.equal(setup.clientRequestedStream, true, "body.stream === true");
assert.equal(typeof setup.comboTargetTimeoutMs, "number");
assert.equal(typeof setup.reasoningTokenBufferEnabled, "boolean");
assert.ok(setup.config && typeof setup.config === "object", "config cascade resolved");
assert.ok(
setup.resilienceSettings && typeof setup.resilienceSettings === "object",
"resilience settings resolved"
);
});
test("phaseComboSetup: clientRequestedStream is false when body.stream is not true", () => {
const ctx = createComboContext({
body: { model: "auto", messages: [] },
combo: { name: "c", models: ["a"] },
log,
});
const setup = phaseComboSetup(ctx);
assert.equal(setup.clientRequestedStream, false);
});
test("phaseComboSetup normalizes an unknown strategy to a valid routing strategy", () => {
const ctx = createComboContext({
body: { model: "auto", messages: [] },
combo: { name: "c", models: ["a"], strategy: "not-a-real-strategy" },
log,
});
const setup = phaseComboSetup(ctx);
// normalizeRoutingStrategy falls back to the default for unknown values.
assert.equal(typeof setup.strategy, "string");
assert.ok(setup.strategy.length > 0);
});