mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
refactor(executors): extract pure URL normalizers from default (#6015)
Extract the pure per-provider chat-URL normalizers (normalizeBailianMessagesUrl, normalizeDataRobotChatUrl, normalizeAzureAiChatUrl, normalizeWatsonxChatUrl, normalizeOciChatUrl, normalizeSapChatUrl, normalizeXiaomiMimoChatUrl, normalizeOpenAIChatUrl, getOpenRouterConnectionPreset) verbatim into the leaf default/urlNormalizers.ts. Host imports them back into buildUrl/transformRequest; the now-dead build*ChatUrl/normalizeBaseUrl imports move to the leaf. All module-private (no re-export). Host 864 -> 815 LOC (shrunk below its frozen baseline). Byte-identical bodies (verbatim 45/45), leaf does not import the host (no cycle). buildHeaders/execute/auth untouched. Adds a split-guard; consumer tests stay green (executor-default-base 49, anthropic-compatible-bearer 3, strip-client-metadata 3).
This commit is contained in:
committed by
GitHub
parent
7bcd750d72
commit
fa51354e35
@@ -24,17 +24,23 @@ import {
|
||||
isClaudeCodeCompatible,
|
||||
} from "../services/provider.ts";
|
||||
import { sanitizeQwenThinkingToolChoice } from "../services/qwenThinking.ts";
|
||||
import { buildDataRobotChatUrl } from "../config/datarobot.ts";
|
||||
import { buildAzureAiChatUrl } from "../config/azureAi.ts";
|
||||
import { buildWatsonxChatUrl } from "../config/watsonx.ts";
|
||||
import { buildOciChatUrl } from "../config/oci.ts";
|
||||
import { buildSapChatUrl, getSapResourceGroup } from "../config/sap.ts";
|
||||
import { getSapResourceGroup } from "../config/sap.ts";
|
||||
import {
|
||||
normalizeBailianMessagesUrl,
|
||||
normalizeDataRobotChatUrl,
|
||||
normalizeAzureAiChatUrl,
|
||||
normalizeWatsonxChatUrl,
|
||||
normalizeOciChatUrl,
|
||||
normalizeSapChatUrl,
|
||||
normalizeXiaomiMimoChatUrl,
|
||||
normalizeOpenAIChatUrl,
|
||||
getOpenRouterConnectionPreset,
|
||||
} from "./default/urlNormalizers.ts";
|
||||
import { buildMaritalkChatUrl } from "../config/maritalk.ts";
|
||||
import { LOCAL_PROVIDERS } from "@/shared/constants/providers";
|
||||
import { isForbiddenCustomHeaderName } from "@/shared/constants/upstreamHeaders";
|
||||
import { getClaudeCodeCompatibleRequestDefaults } from "@/lib/providers/requestDefaults";
|
||||
import { buildClineHeaders } from "@/shared/utils/clineAuth";
|
||||
import { normalizeBaseUrl } from "../utils/urlSanitize.ts";
|
||||
import {
|
||||
normalizeHerokuChatUrl,
|
||||
normalizeDatabricksChatUrl,
|
||||
@@ -87,62 +93,6 @@ function applyCustomHeaders(headers: Record<string, string>, rawCustomHeaders: u
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeBailianMessagesUrl(baseUrl) {
|
||||
const normalized = normalizeBaseUrl(baseUrl).replace(/\?beta=true$/, "");
|
||||
const messagesUrl = normalized.endsWith("/messages") ? normalized : `${normalized}/messages`;
|
||||
return messagesUrl;
|
||||
}
|
||||
|
||||
function normalizeDataRobotChatUrl(baseUrl) {
|
||||
return buildDataRobotChatUrl(baseUrl);
|
||||
}
|
||||
|
||||
function normalizeAzureAiChatUrl(baseUrl: string, apiType: "chat" | "responses" = "chat") {
|
||||
return buildAzureAiChatUrl(baseUrl, apiType);
|
||||
}
|
||||
|
||||
function normalizeWatsonxChatUrl(baseUrl: string) {
|
||||
return buildWatsonxChatUrl(baseUrl);
|
||||
}
|
||||
|
||||
function normalizeOciChatUrl(baseUrl: string, apiType: "chat" | "responses" = "chat") {
|
||||
return buildOciChatUrl(baseUrl, apiType);
|
||||
}
|
||||
|
||||
function normalizeSapChatUrl(baseUrl) {
|
||||
return buildSapChatUrl(baseUrl);
|
||||
}
|
||||
|
||||
function normalizeXiaomiMimoChatUrl(baseUrl) {
|
||||
const normalized = normalizeBaseUrl(baseUrl).replace(/\/chat\/completions$/, "");
|
||||
return `${normalized}/chat/completions`;
|
||||
}
|
||||
|
||||
function normalizeOpenAIChatUrl(baseUrl) {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
if (
|
||||
normalized.endsWith("/chat/completions") ||
|
||||
normalized.endsWith("/responses") ||
|
||||
normalized.endsWith("/chat")
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
if (normalized.endsWith("/v1")) {
|
||||
return `${normalized}/chat/completions`;
|
||||
}
|
||||
// Assume OpenAI-compatible /v1/chat/completions path structure
|
||||
// when the base URL is a bare hostname or custom path (e.g. llama.cpp, vLLM, LM Studio).
|
||||
return `${normalized}/v1/chat/completions`;
|
||||
}
|
||||
|
||||
function getOpenRouterConnectionPreset(
|
||||
providerSpecificData?: Record<string, unknown> | null
|
||||
): string | null {
|
||||
const preset =
|
||||
typeof providerSpecificData?.preset === "string" ? providerSpecificData.preset.trim() : "";
|
||||
return preset || null;
|
||||
}
|
||||
|
||||
export class DefaultExecutor extends BaseExecutor {
|
||||
constructor(provider) {
|
||||
super(provider, PROVIDERS[provider] || PROVIDERS.openai);
|
||||
|
||||
64
open-sse/executors/default/urlNormalizers.ts
Normal file
64
open-sse/executors/default/urlNormalizers.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
// Pure per-provider chat-URL normalizers + connection-preset reader.
|
||||
// Extracted verbatim from default.ts (string transforms only, no host state/this).
|
||||
import { buildDataRobotChatUrl } from "../../config/datarobot.ts";
|
||||
import { buildAzureAiChatUrl } from "../../config/azureAi.ts";
|
||||
import { buildWatsonxChatUrl } from "../../config/watsonx.ts";
|
||||
import { buildOciChatUrl } from "../../config/oci.ts";
|
||||
import { buildSapChatUrl } from "../../config/sap.ts";
|
||||
import { normalizeBaseUrl } from "../../utils/urlSanitize.ts";
|
||||
|
||||
export function normalizeBailianMessagesUrl(baseUrl) {
|
||||
const normalized = normalizeBaseUrl(baseUrl).replace(/\?beta=true$/, "");
|
||||
const messagesUrl = normalized.endsWith("/messages") ? normalized : `${normalized}/messages`;
|
||||
return messagesUrl;
|
||||
}
|
||||
|
||||
export function normalizeDataRobotChatUrl(baseUrl) {
|
||||
return buildDataRobotChatUrl(baseUrl);
|
||||
}
|
||||
|
||||
export function normalizeAzureAiChatUrl(baseUrl: string, apiType: "chat" | "responses" = "chat") {
|
||||
return buildAzureAiChatUrl(baseUrl, apiType);
|
||||
}
|
||||
|
||||
export function normalizeWatsonxChatUrl(baseUrl: string) {
|
||||
return buildWatsonxChatUrl(baseUrl);
|
||||
}
|
||||
|
||||
export function normalizeOciChatUrl(baseUrl: string, apiType: "chat" | "responses" = "chat") {
|
||||
return buildOciChatUrl(baseUrl, apiType);
|
||||
}
|
||||
|
||||
export function normalizeSapChatUrl(baseUrl) {
|
||||
return buildSapChatUrl(baseUrl);
|
||||
}
|
||||
|
||||
export function normalizeXiaomiMimoChatUrl(baseUrl) {
|
||||
const normalized = normalizeBaseUrl(baseUrl).replace(/\/chat\/completions$/, "");
|
||||
return `${normalized}/chat/completions`;
|
||||
}
|
||||
|
||||
export function normalizeOpenAIChatUrl(baseUrl) {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
if (
|
||||
normalized.endsWith("/chat/completions") ||
|
||||
normalized.endsWith("/responses") ||
|
||||
normalized.endsWith("/chat")
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
if (normalized.endsWith("/v1")) {
|
||||
return `${normalized}/chat/completions`;
|
||||
}
|
||||
// Assume OpenAI-compatible /v1/chat/completions path structure
|
||||
// when the base URL is a bare hostname or custom path (e.g. llama.cpp, vLLM, LM Studio).
|
||||
return `${normalized}/v1/chat/completions`;
|
||||
}
|
||||
|
||||
export function getOpenRouterConnectionPreset(
|
||||
providerSpecificData?: Record<string, unknown> | null
|
||||
): string | null {
|
||||
const preset =
|
||||
typeof providerSpecificData?.preset === "string" ? providerSpecificData.preset.trim() : "";
|
||||
return preset || null;
|
||||
}
|
||||
36
tests/unit/default-url-normalizers-split.test.ts
Normal file
36
tests/unit/default-url-normalizers-split.test.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
// Split-guard for the default executor URL-normalizer extraction.
|
||||
// The pure per-provider chat-URL normalizers live in default/urlNormalizers.ts
|
||||
// (string transforms only). Host imports them back into buildUrl/transformRequest.
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const EXE = join(HERE, "../../open-sse/executors");
|
||||
const HOST = join(EXE, "default.ts");
|
||||
const LEAF = join(EXE, "default/urlNormalizers.ts");
|
||||
|
||||
test("leaf hosts the normalizers and does not import the host", () => {
|
||||
const src = readFileSync(LEAF, "utf8");
|
||||
for (const sym of [
|
||||
"normalizeOpenAIChatUrl",
|
||||
"normalizeSapChatUrl",
|
||||
"getOpenRouterConnectionPreset",
|
||||
]) {
|
||||
assert.match(src, new RegExp(`export function ${sym}\\b`));
|
||||
}
|
||||
assert.doesNotMatch(src, /from "\.\.\/default\.ts"/);
|
||||
});
|
||||
|
||||
test("host imports the normalizers back from the leaf", () => {
|
||||
const host = readFileSync(HOST, "utf8");
|
||||
assert.match(host, /from "\.\/default\/urlNormalizers\.ts"/);
|
||||
});
|
||||
|
||||
test("normalizeOpenAIChatUrl appends chat/completions for a bare base URL", async () => {
|
||||
const { normalizeOpenAIChatUrl } =
|
||||
await import("../../open-sse/executors/default/urlNormalizers.ts");
|
||||
assert.match(normalizeOpenAIChatUrl("https://api.example.com"), /\/v1\/chat\/completions$/);
|
||||
});
|
||||
Reference in New Issue
Block a user