mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 06:12:10 +03:00
refactor(chatCore): extrai extractSystemRoleMessages + buildUpstreamHeadersForExecute para leaves puros (#3501) (#4548)
Integrated into release/v3.8.33
This commit is contained in:
committed by
GitHub
parent
69c35bd342
commit
4b34a75fe3
@@ -1,6 +1,8 @@
|
||||
import { injectMemoryAndSkills } from "./chatCore/memorySkillsInjection.ts";
|
||||
import { resolveChatCoreRequestSetup } from "./chatCore/requestSetup.ts";
|
||||
import { buildFailureUsageRecord } from "./chatCore/failureUsage.ts";
|
||||
import { extractSystemRoleMessages } from "./chatCore/claudeSystemRole.ts";
|
||||
export { extractSystemRoleMessages } from "./chatCore/claudeSystemRole.ts";
|
||||
import { checkIdempotencyCache } from "./chatCore/idempotency.ts";
|
||||
import { checkSemanticCache } from "./chatCore/semanticCache.ts";
|
||||
import { sanitizeChatRequestBody } from "./chatCore/sanitization.ts";
|
||||
@@ -164,7 +166,6 @@ import {
|
||||
import {
|
||||
getModelNormalizeToolCallId,
|
||||
getModelPreserveOpenAIDeveloperRole,
|
||||
getModelUpstreamExtraHeaders,
|
||||
} from "@/lib/localDb";
|
||||
import { getProviderCredentials, extractSessionAffinityKey } from "@/sse/services/auth";
|
||||
import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity";
|
||||
@@ -181,10 +182,7 @@ import {
|
||||
} from "../utils/cacheControlPolicy.ts";
|
||||
import { getCachedSettings } from "@/lib/db/readCache";
|
||||
import { applyCodexGlobalFastServiceTier } from "@/lib/providers/codexFastTier";
|
||||
import {
|
||||
CPA_FORCE_FAST_MODE_HEADER,
|
||||
shouldRequestClaudeFastMode,
|
||||
} from "@/lib/providers/claudeFastMode";
|
||||
import { buildUpstreamHeadersForExecute as buildUpstreamHeadersForExecuteFor } from "./chatCore/upstreamExecuteHeaders.ts";
|
||||
import {
|
||||
resolveEffectiveServiceTier as resolveEffectiveServiceTierFor,
|
||||
resolveReportedServiceTier as resolveReportedServiceTierFor,
|
||||
@@ -542,43 +540,8 @@ function buildExecutorClientHeaders(
|
||||
return Object.keys(normalized).length > 0 ? normalized : null;
|
||||
}
|
||||
|
||||
export function extractSystemRoleMessages(payload: Record<string, unknown>): void {
|
||||
if (!Array.isArray(payload.messages)) return;
|
||||
const messages = payload.messages as Array<{ role?: unknown; content?: unknown }>;
|
||||
// Treat both `system` and `developer` as system-equivalent (OpenAI's Responses
|
||||
// API renamed system → developer). Anthropic rejects either as a chat role, so
|
||||
// both must be lifted into the top-level `system` field — parity with the
|
||||
// normal-path extractSystemMessagesToBody closure.
|
||||
const isSystemRole = (role: unknown): boolean =>
|
||||
typeof role === "string" &&
|
||||
(role.toLowerCase() === "system" || role.toLowerCase() === "developer");
|
||||
const systemMessages = messages.filter((m) => isSystemRole(m.role));
|
||||
if (systemMessages.length === 0) return;
|
||||
|
||||
const extraBlocks: Array<Record<string, unknown>> = [];
|
||||
for (const sm of systemMessages) {
|
||||
if (typeof sm.content === "string" && sm.content.length > 0) {
|
||||
extraBlocks.push({ type: "text", text: sm.content });
|
||||
} else if (Array.isArray(sm.content)) {
|
||||
for (const block of sm.content as Array<Record<string, unknown>>) {
|
||||
if (block?.type === "text" && typeof block.text === "string" && block.text.length > 0) {
|
||||
extraBlocks.push({ ...block });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extraBlocks.length > 0) {
|
||||
const existingSystem = payload.system;
|
||||
if (typeof existingSystem === "string" && existingSystem.length > 0) {
|
||||
payload.system = [{ type: "text", text: existingSystem }, ...extraBlocks];
|
||||
} else if (Array.isArray(existingSystem)) {
|
||||
payload.system = [...(existingSystem as Array<Record<string, unknown>>), ...extraBlocks];
|
||||
} else {
|
||||
payload.system = extraBlocks;
|
||||
}
|
||||
}
|
||||
payload.messages = messages.filter((m) => !isSystemRole(m.role));
|
||||
}
|
||||
// extractSystemRoleMessages extracted to chatCore/claudeSystemRole.ts (#3501); re-exported above so
|
||||
// existing importers (e.g. tests/unit/system-role-extraction.test.ts) keep resolving it from here.
|
||||
|
||||
export async function handleChatCore({
|
||||
body,
|
||||
@@ -1100,46 +1063,19 @@ export async function handleChatCore({
|
||||
? credentials.providerSpecificData.customUserAgent.trim()
|
||||
: "";
|
||||
|
||||
const buildUpstreamHeadersForExecute = (modelToCall: string): Record<string, string> => {
|
||||
const upstreamHeaders =
|
||||
modelToCall === effectiveModel
|
||||
? {
|
||||
...getModelUpstreamExtraHeaders(provider || "", model || "", sourceFormat),
|
||||
...getModelUpstreamExtraHeaders(provider || "", resolvedModel || "", sourceFormat),
|
||||
}
|
||||
: (() => {
|
||||
const r = resolveModelAlias(modelToCall);
|
||||
return {
|
||||
...getModelUpstreamExtraHeaders(provider || "", modelToCall || "", sourceFormat),
|
||||
...getModelUpstreamExtraHeaders(provider || "", r || "", sourceFormat),
|
||||
};
|
||||
})();
|
||||
|
||||
if (connectionCustomUserAgent) {
|
||||
upstreamHeaders["User-Agent"] = connectionCustomUserAgent;
|
||||
if ("user-agent" in upstreamHeaders) {
|
||||
upstreamHeaders["user-agent"] = connectionCustomUserAgent;
|
||||
}
|
||||
}
|
||||
|
||||
// Claude Fast Mode opt-in. When the user has enabled this in
|
||||
// Settings > AI AND the target provider is the canonical Anthropic
|
||||
// `claude` provider (Claude Code-compatible CPA bridges are excluded
|
||||
// since they already select their own entrypoint) AND the model id
|
||||
// matches the configured list, signal to a paired CLIProxyAPI build to
|
||||
// rewrite the cc_entrypoint so the request can reach Anthropic Fast
|
||||
// Mode (speed:"fast"). CPA builds that do not understand the header
|
||||
// forward it harmlessly.
|
||||
if (
|
||||
provider === "claude" &&
|
||||
typeof settings !== "undefined" &&
|
||||
shouldRequestClaudeFastMode(settings, modelToCall)
|
||||
) {
|
||||
upstreamHeaders[CPA_FORCE_FAST_MODE_HEADER] = "1";
|
||||
}
|
||||
|
||||
return upstreamHeaders;
|
||||
};
|
||||
// Upstream extra-header building extracted to chatCore/upstreamExecuteHeaders.ts (#3501); bind the
|
||||
// per-request inputs once and delegate so the existing call sites stay byte-identical.
|
||||
const buildUpstreamHeadersForExecute = (modelToCall: string): Record<string, string> =>
|
||||
buildUpstreamHeadersForExecuteFor({
|
||||
modelToCall,
|
||||
effectiveModel,
|
||||
provider,
|
||||
model,
|
||||
resolvedModel,
|
||||
sourceFormat,
|
||||
connectionCustomUserAgent,
|
||||
settings,
|
||||
});
|
||||
|
||||
// Default to false unless client explicitly sets stream: true (OpenAI spec compliant)
|
||||
const acceptHeader =
|
||||
|
||||
48
open-sse/handlers/chatCore/claudeSystemRole.ts
Normal file
48
open-sse/handlers/chatCore/claudeSystemRole.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* chatCore Claude system-role lifter (Quality Gate v2 / Fase 9 — chatCore god-file
|
||||
* decomposition, #3501).
|
||||
*
|
||||
* Pure helper extracted from chatCore.ts: lifts any `system`/`developer` role messages out of the
|
||||
* messages[] array into the top-level `system` field. Anthropic's Messages API rejects either as a
|
||||
* chat role, so they must be hoisted. `developer` is OpenAI's Responses-API rename of `system` and
|
||||
* is treated identically. Mutates the payload in place; behaviour is byte-identical to the previous
|
||||
* top-level definition (still re-exported from chatCore.ts for existing importers/tests).
|
||||
*/
|
||||
|
||||
export function extractSystemRoleMessages(payload: Record<string, unknown>): void {
|
||||
if (!Array.isArray(payload.messages)) return;
|
||||
const messages = payload.messages as Array<{ role?: unknown; content?: unknown }>;
|
||||
// Treat both `system` and `developer` as system-equivalent (OpenAI's Responses
|
||||
// API renamed system → developer). Anthropic rejects either as a chat role, so
|
||||
// both must be lifted into the top-level `system` field — parity with the
|
||||
// normal-path extractSystemMessagesToBody closure.
|
||||
const isSystemRole = (role: unknown): boolean =>
|
||||
typeof role === "string" &&
|
||||
(role.toLowerCase() === "system" || role.toLowerCase() === "developer");
|
||||
const systemMessages = messages.filter((m) => isSystemRole(m.role));
|
||||
if (systemMessages.length === 0) return;
|
||||
|
||||
const extraBlocks: Array<Record<string, unknown>> = [];
|
||||
for (const sm of systemMessages) {
|
||||
if (typeof sm.content === "string" && sm.content.length > 0) {
|
||||
extraBlocks.push({ type: "text", text: sm.content });
|
||||
} else if (Array.isArray(sm.content)) {
|
||||
for (const block of sm.content as Array<Record<string, unknown>>) {
|
||||
if (block?.type === "text" && typeof block.text === "string" && block.text.length > 0) {
|
||||
extraBlocks.push({ ...block });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extraBlocks.length > 0) {
|
||||
const existingSystem = payload.system;
|
||||
if (typeof existingSystem === "string" && existingSystem.length > 0) {
|
||||
payload.system = [{ type: "text", text: existingSystem }, ...extraBlocks];
|
||||
} else if (Array.isArray(existingSystem)) {
|
||||
payload.system = [...(existingSystem as Array<Record<string, unknown>>), ...extraBlocks];
|
||||
} else {
|
||||
payload.system = extraBlocks;
|
||||
}
|
||||
}
|
||||
payload.messages = messages.filter((m) => !isSystemRole(m.role));
|
||||
}
|
||||
71
open-sse/handlers/chatCore/upstreamExecuteHeaders.ts
Normal file
71
open-sse/handlers/chatCore/upstreamExecuteHeaders.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* chatCore per-model upstream extra-header builder (Quality Gate v2 / Fase 9 — chatCore god-file
|
||||
* decomposition, #3501).
|
||||
*
|
||||
* Pure builder extracted from handleChatCore: assembles the per-model upstream extra headers for an
|
||||
* execute() call. Merges the configured per-model headers (for the resolved model and its alias),
|
||||
* applies a connection's custom User-Agent override, and opts into Claude Fast Mode (CPA bridge
|
||||
* header) when enabled in settings for the canonical `claude` provider and a supported model.
|
||||
* Side-effect-free; behaviour is byte-identical to the previous inline closure.
|
||||
*/
|
||||
|
||||
import { getModelUpstreamExtraHeaders } from "@/lib/db/models";
|
||||
import { resolveModelAlias } from "../../services/modelDeprecation.ts";
|
||||
import { CPA_FORCE_FAST_MODE_HEADER, shouldRequestClaudeFastMode } from "@/lib/providers/claudeFastMode";
|
||||
|
||||
export function buildUpstreamHeadersForExecute(opts: {
|
||||
modelToCall: string;
|
||||
effectiveModel: string;
|
||||
provider: string | null | undefined;
|
||||
model: string | null | undefined;
|
||||
resolvedModel: string;
|
||||
sourceFormat: string;
|
||||
connectionCustomUserAgent: string;
|
||||
settings: unknown;
|
||||
}): Record<string, string> {
|
||||
const {
|
||||
modelToCall,
|
||||
effectiveModel,
|
||||
provider,
|
||||
model,
|
||||
resolvedModel,
|
||||
sourceFormat,
|
||||
connectionCustomUserAgent,
|
||||
settings,
|
||||
} = opts;
|
||||
|
||||
const upstreamHeaders: Record<string, string> =
|
||||
modelToCall === effectiveModel
|
||||
? {
|
||||
...getModelUpstreamExtraHeaders(provider || "", model || "", sourceFormat),
|
||||
...getModelUpstreamExtraHeaders(provider || "", resolvedModel || "", sourceFormat),
|
||||
}
|
||||
: (() => {
|
||||
const r = resolveModelAlias(modelToCall);
|
||||
return {
|
||||
...getModelUpstreamExtraHeaders(provider || "", modelToCall || "", sourceFormat),
|
||||
...getModelUpstreamExtraHeaders(provider || "", r || "", sourceFormat),
|
||||
};
|
||||
})();
|
||||
|
||||
if (connectionCustomUserAgent) {
|
||||
upstreamHeaders["User-Agent"] = connectionCustomUserAgent;
|
||||
if ("user-agent" in upstreamHeaders) {
|
||||
upstreamHeaders["user-agent"] = connectionCustomUserAgent;
|
||||
}
|
||||
}
|
||||
|
||||
// Claude Fast Mode opt-in. When enabled in Settings > AI AND the target provider is the canonical
|
||||
// Anthropic `claude` provider (Claude Code-compatible CPA bridges are excluded since they select
|
||||
// their own entrypoint) AND the model id matches the configured list, signal to a paired
|
||||
// CLIProxyAPI build to rewrite the cc_entrypoint so the request reaches Anthropic Fast Mode.
|
||||
if (
|
||||
provider === "claude" &&
|
||||
typeof settings !== "undefined" &&
|
||||
shouldRequestClaudeFastMode(settings, modelToCall)
|
||||
) {
|
||||
upstreamHeaders[CPA_FORCE_FAST_MODE_HEADER] = "1";
|
||||
}
|
||||
|
||||
return upstreamHeaders;
|
||||
}
|
||||
70
tests/unit/chatcore-upstream-execute-headers.test.ts
Normal file
70
tests/unit/chatcore-upstream-execute-headers.test.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// tests/unit/chatcore-upstream-execute-headers.test.ts
|
||||
// Characterization of buildUpstreamHeadersForExecute — the per-model upstream extra-header builder
|
||||
// extracted from handleChatCore (chatCore god-file decomposition, #3501). Locks: the
|
||||
// connection custom User-Agent override, the Claude Fast Mode opt-in (claude provider + enabled
|
||||
// settings + supported model only), and the modelToCall===effectiveModel vs alias branches.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { buildUpstreamHeadersForExecute } from "../../open-sse/handlers/chatCore/upstreamExecuteHeaders.ts";
|
||||
import { CPA_FORCE_FAST_MODE_HEADER } from "../../src/lib/providers/claudeFastMode.ts";
|
||||
|
||||
const base = {
|
||||
modelToCall: "some-model",
|
||||
effectiveModel: "some-model",
|
||||
provider: "openai",
|
||||
model: "some-model",
|
||||
resolvedModel: "some-model",
|
||||
sourceFormat: "openai",
|
||||
connectionCustomUserAgent: "",
|
||||
settings: {},
|
||||
};
|
||||
|
||||
test("no custom UA / non-claude / no fast settings → no UA and no fast-mode header", () => {
|
||||
const h = buildUpstreamHeadersForExecute({ ...base });
|
||||
assert.equal(h["User-Agent"], undefined);
|
||||
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined);
|
||||
});
|
||||
|
||||
test("connection custom User-Agent overrides the upstream User-Agent", () => {
|
||||
const h = buildUpstreamHeadersForExecute({ ...base, connectionCustomUserAgent: "MyAgent/1.0" });
|
||||
assert.equal(h["User-Agent"], "MyAgent/1.0");
|
||||
});
|
||||
|
||||
test("claude provider + enabled fast-mode settings + supported model → CPA header set", () => {
|
||||
const h = buildUpstreamHeadersForExecute({
|
||||
...base,
|
||||
provider: "claude",
|
||||
modelToCall: "claude-fast-x",
|
||||
effectiveModel: "claude-fast-x",
|
||||
settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } },
|
||||
});
|
||||
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], "1");
|
||||
});
|
||||
|
||||
test("fast-mode header is NOT set when the model is not in the supported list", () => {
|
||||
const h = buildUpstreamHeadersForExecute({
|
||||
...base,
|
||||
provider: "claude",
|
||||
modelToCall: "claude-other",
|
||||
effectiveModel: "claude-other",
|
||||
settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } },
|
||||
});
|
||||
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined);
|
||||
});
|
||||
|
||||
test("fast-mode header is NOT set for non-claude providers even with fast settings", () => {
|
||||
const h = buildUpstreamHeadersForExecute({
|
||||
...base,
|
||||
provider: "openai",
|
||||
modelToCall: "claude-fast-x",
|
||||
effectiveModel: "claude-fast-x",
|
||||
settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } },
|
||||
});
|
||||
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined);
|
||||
});
|
||||
|
||||
test("returns a plain object (no per-model extra headers configured for unknown models)", () => {
|
||||
const h = buildUpstreamHeadersForExecute({ ...base, modelToCall: "totally-unknown", effectiveModel: "x" });
|
||||
assert.equal(typeof h, "object");
|
||||
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined);
|
||||
});
|
||||
Reference in New Issue
Block a user