mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 14:22:09 +03:00
refactor(chatCore): extrai prepareUpstreamBody (1ª sub-fatia do executeProviderRequest, #3501) (#4730)
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.
This commit is contained in:
committed by
GitHub
parent
48dea489c1
commit
5f4e9e73a4
@@ -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,
|
||||
|
||||
141
open-sse/handlers/chatCore/upstreamBody.ts
Normal file
141
open-sse/handlers/chatCore/upstreamBody.ts
Normal file
@@ -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<string, unknown>;
|
||||
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<Body> {
|
||||
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<Body> {
|
||||
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;
|
||||
}
|
||||
102
tests/unit/chatcore-upstream-body.test.ts
Normal file
102
tests/unit/chatcore-upstream-body.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user