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:
Diego Rodrigues de Sa e Souza
2026-07-02 21:57:52 -03:00
committed by GitHub
parent 7bcd750d72
commit fa51354e35
3 changed files with 112 additions and 62 deletions

View File

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

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

View 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$/);
});