refactor(executors): deduplicate shared utilities and add comprehensive tests (#5720)

Integrated into release/v3.8.43
This commit is contained in:
PizzaV
2026-07-02 03:02:22 +02:00
committed by GitHub
parent 9bd0211da7
commit 36167d7bb7
27 changed files with 1252 additions and 293 deletions

View File

@@ -1,11 +1,7 @@
import { stripTrailingSlashes } from "../utils/urlSanitize.ts";
import { stripTrailingSlashes, normalizeBaseUrl } from "../utils/urlSanitize.ts";
export const AZURE_AI_DEFAULT_BASE_URL = "https://example-resource.services.ai.azure.com/openai/v1";
function normalizeBaseUrl(value: string | null | undefined): string {
return stripTrailingSlashes((value || "").trim());
}
export function normalizeAzureAiBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || AZURE_AI_DEFAULT_BASE_URL);
if (!normalized) return AZURE_AI_DEFAULT_BASE_URL;

View File

@@ -1,4 +1,4 @@
import { stripTrailingSlashes } from "../utils/urlSanitize.ts";
import { stripTrailingSlashes, normalizeBaseUrl } from "../utils/urlSanitize.ts";
const DATAROBOT_API_V2_SEGMENT = "/api/v2";
const DATAROBOT_LLMGW_CHAT_PATH = "/genai/llmgw/chat/completions/";
@@ -6,10 +6,6 @@ const DATAROBOT_LLMGW_CATALOG_PATH = "/genai/llmgw/catalog/";
export const DATAROBOT_DEFAULT_BASE_URL = "https://app.datarobot.com";
function normalizeBaseUrl(value: string | null | undefined): string {
return stripTrailingSlashes((value || "").trim());
}
export function normalizeDataRobotBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || DATAROBOT_DEFAULT_BASE_URL);
return normalized || DATAROBOT_DEFAULT_BASE_URL;

View File

@@ -1,12 +1,8 @@
import { stripTrailingSlashes } from "../utils/urlSanitize.ts";
import { stripTrailingSlashes, normalizeBaseUrl } from "../utils/urlSanitize.ts";
export const OCI_DEFAULT_BASE_URL =
"https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1";
function normalizeBaseUrl(value: string | null | undefined): string {
return stripTrailingSlashes((value || "").trim());
}
export function normalizeOciBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || OCI_DEFAULT_BASE_URL);
if (!normalized) return OCI_DEFAULT_BASE_URL;

View File

@@ -1,12 +1,9 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
export const nebiusProvider: RegistryEntry = {
export const nebiusProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "nebius",
alias: "nebius",
format: "openai",
executor: "default",
baseUrl: "https://api.tokenfactory.nebius.com/v1/chat/completions",
authType: "apikey",
authHeader: "bearer",
models: [{ id: "meta-llama/Llama-3.3-70B-Instruct", name: "Llama 3.3 70B Instruct" }],
};
});

View File

@@ -1,13 +1,10 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
export const siliconflowProvider: RegistryEntry = {
export const siliconflowProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "siliconflow",
alias: "siliconflow",
format: "openai",
executor: "default",
baseUrl: "https://api.siliconflow.com/v1/chat/completions",
authType: "apikey",
authHeader: "bearer",
models: [
// DeepSeek
{ id: "deepseek-ai/DeepSeek-V3.2", name: "DeepSeek V3.2" },
@@ -84,4 +81,4 @@ export const siliconflowProvider: RegistryEntry = {
{ id: "google/gemma-4-26B-A4B-it", name: "Gemma 4 26B" },
{ id: "ByteDance-Seed/Seed-OSS-36B-Instruct", name: "Seed OSS 36B" },
],
};
});

View File

@@ -1,13 +1,10 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
export const togetherProvider: RegistryEntry = {
export const togetherProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "together",
alias: "together",
format: "openai",
executor: "default",
baseUrl: "https://api.together.xyz/v1/chat/completions",
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free", name: "Llama 3.3 70B Turbo (🆓 Free)" },
{ id: "meta-llama/Llama-Vision-Free", name: "Llama Vision (🆓 Free)" },
@@ -20,4 +17,4 @@ export const togetherProvider: RegistryEntry = {
{ id: "Qwen/Qwen3-235B-A22B", name: "Qwen3 235B" },
{ id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", name: "Llama 4 Maverick" },
],
};
});

View File

@@ -143,6 +143,24 @@ export interface RegistryEntry {
anonymousApiKey?: string;
}
/**
* Build a standard OpenAI-compatible provider registry entry.
* Eliminates the 4-field boilerplate (format, executor, authType, authHeader)
* repeated across 40+ provider files.
*/
export function buildOpenAiCompatibleRegistryEntry(
overrides: Pick<RegistryEntry, "id"> &
Partial<Omit<RegistryEntry, "id" | "format" | "executor" | "authType" | "authHeader">>
): RegistryEntry {
return {
format: "openai",
executor: "default",
authType: "apikey",
authHeader: "bearer",
...overrides,
} as RegistryEntry;
}
export interface LegacyProvider {
format: string;
baseUrl?: string;

View File

@@ -1,12 +1,8 @@
import { stripTrailingSlashes } from "../utils/urlSanitize.ts";
import { stripTrailingSlashes, normalizeBaseUrl } from "../utils/urlSanitize.ts";
export const SAP_DEFAULT_BASE_URL =
"https://example-aicore.cfapps.eu10.hana.ondemand.com/v2/lm/deployments/example-deployment";
function normalizeBaseUrl(value: string | null | undefined): string {
return stripTrailingSlashes((value || "").trim());
}
function sanitizeUrl(value: string): string {
try {
const parsed = new URL(value);

View File

@@ -1,11 +1,7 @@
import { stripTrailingSlashes } from "../utils/urlSanitize.ts";
import { stripTrailingSlashes, normalizeBaseUrl } from "../utils/urlSanitize.ts";
export const WATSONX_DEFAULT_BASE_URL = "https://ca-tor.ml.cloud.ibm.com/ml/gateway/v1";
function normalizeBaseUrl(value: string | null | undefined): string {
return stripTrailingSlashes((value || "").trim());
}
export function normalizeWatsonxBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || WATSONX_DEFAULT_BASE_URL);
if (!normalized) return WATSONX_DEFAULT_BASE_URL;

View File

@@ -234,7 +234,10 @@ export function stripStainlessHeadersForOpenAICompat(
// Normalize User-Agent: SDK-based clients send verbose product strings that some
// upstreams block. Replace with a clean browser-like UA only when it looks SDK-derived.
const ua = (headers["User-Agent"] || headers["user-agent"] || "").toLowerCase();
if (ua.includes("openai") && (ua.includes("node") || ua.includes("axios") || ua.includes("undici"))) {
if (
ua.includes("openai") &&
(ua.includes("node") || ua.includes("axios") || ua.includes("undici"))
) {
setUserAgentHeader(headers, "Mozilla/5.0 (compatible; OpenAI Compatible)");
}
@@ -529,15 +532,49 @@ export class BaseExecutor {
return baseUrls[urlIndex] || baseUrls[0] || this.config.baseUrl || "";
}
buildHeaders(
/**
* Resolve the effective base URL for a request, preferring per-connection
* providerSpecificData.baseUrl over the static provider config baseUrl.
*/
protected resolveBaseUrl(credentials: ProviderCredentials | null, fallback?: string): string {
return credentials?.providerSpecificData?.baseUrl || fallback || this.config.baseUrl || "";
}
/**
* Resolve the effective API key via extra-keys round-robin rotation.
* Mutates `credentials.providerSpecificData.selectedKeyId` on rotation.
*/
protected resolveEffectiveKey(credentials: ProviderCredentials): string {
const extraKeys =
(credentials.providerSpecificData?.extraApiKeys as string[] | undefined) ?? [];
const selectedKeyId = (credentials.providerSpecificData as Record<string, unknown> | undefined)
?.selectedKeyId as string | undefined;
let effectiveKey = credentials.apiKey;
if (extraKeys.length > 0 && credentials.connectionId && credentials.apiKey) {
const resolved = resolveKeyForRequest(
credentials.connectionId,
credentials.apiKey,
extraKeys,
selectedKeyId ?? null
);
effectiveKey = resolved?.key ?? credentials.apiKey;
if (resolved && credentials.providerSpecificData) {
(credentials.providerSpecificData as Record<string, unknown>).selectedKeyId =
resolved.keyId;
}
}
return effectiveKey;
}
/**
* Build the common header preamble shared by BaseExecutor and DefaultExecutor:
* Content-Type, config.headers, per-provider User-Agent env override, and
* resolved effective key (via extra-keys round-robin).
*/
protected buildHeadersPreamble(
credentials: ProviderCredentials,
stream = true,
clientHeaders?: Record<string, string> | null,
model?: string,
health?: Record<string, KeyHealth>
): Record<string, string> {
void clientHeaders;
void model;
stream: boolean
): { headers: Record<string, string>; effectiveKey: string } {
const headers: Record<string, string> = {
"Content-Type": "application/json",
...this.config.headers,
@@ -554,28 +591,25 @@ export class BaseExecutor {
}
}
const effectiveKey = this.resolveEffectiveKey(credentials);
void stream;
return { headers, effectiveKey };
}
buildHeaders(
credentials: ProviderCredentials,
stream = true,
clientHeaders?: Record<string, string> | null,
model?: string,
health?: Record<string, KeyHealth>
): Record<string, string> {
void clientHeaders;
void model;
const { headers, effectiveKey } = this.buildHeadersPreamble(credentials, stream);
if (credentials.accessToken) {
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
} else if (credentials.apiKey) {
const extraKeys =
(credentials.providerSpecificData?.extraApiKeys as string[] | undefined) ?? [];
const selectedKeyId = (
credentials.providerSpecificData as Record<string, unknown> | undefined
)?.selectedKeyId as string | undefined;
let effectiveKey = credentials.apiKey;
if (extraKeys.length > 0 && credentials.connectionId) {
const resolved = resolveKeyForRequest(
credentials.connectionId,
credentials.apiKey,
extraKeys,
selectedKeyId ?? null
);
effectiveKey = resolved?.key ?? credentials.apiKey;
if (resolved && credentials.providerSpecificData) {
(credentials.providerSpecificData as Record<string, unknown>).selectedKeyId =
resolved.keyId;
}
}
headers["Authorization"] = `Bearer ${effectiveKey}`;
}
@@ -1112,14 +1146,11 @@ export class BaseExecutor {
const seed = activeCredentials?.accessToken || activeCredentials?.apiKey || "anon";
const psd = activeCredentials?.providerSpecificData as
| Record<string, unknown>
| undefined;
Record<string, unknown> | undefined;
let identitySource:
| "upstream-metadata"
| "upstream-header"
| "synthesized"
| "synthesized-cloaked" = "synthesized";
"upstream-metadata" | "upstream-header" | "synthesized" | "synthesized-cloaked" =
"synthesized";
let sessionId: string;
let deviceId: string;
let accountUUID: string;

View File

@@ -1,12 +1,7 @@
import { BaseExecutor, setUserAgentHeader, type ExecuteInput } from "./base.ts";
import { BaseExecutor, type ExecuteInput } from "./base.ts";
import { PROVIDERS, OAUTH_ENDPOINTS } from "../config/constants.ts";
import { getAccessToken } from "../services/tokenRefresh.ts";
import {
getRotatingApiKey,
getValidApiKey,
resolveKeyForRequest,
} from "../services/apiKeyRotator.ts";
import type { KeyHealth } from "../services/apiKeyRotator.ts";
import {
buildClaudeCodeCompatibleHeaders,
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
@@ -38,6 +33,14 @@ 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,
normalizeSnowflakeChatUrl,
normalizeGigachatChatUrl,
} from "@/lib/providers/validation/urlHelpers";
import { forwardOpencodeClientHeaders } from "../utils/opencodeHeaders.ts";
import type { PoolConfig } from "../services/sessionPool/types.ts";
@@ -83,28 +86,12 @@ function applyCustomHeaders(headers: Record<string, string>, rawCustomHeaders: u
}
}
function normalizeBaseUrl(baseUrl) {
return (baseUrl || "").trim().replace(/\/$/, "");
}
function normalizeBailianMessagesUrl(baseUrl) {
const normalized = normalizeBaseUrl(baseUrl).replace(/\?beta=true$/, "");
const messagesUrl = normalized.endsWith("/messages") ? normalized : `${normalized}/messages`;
return messagesUrl;
}
function normalizeHerokuChatUrl(baseUrl) {
const normalized = normalizeBaseUrl(baseUrl);
if (normalized.endsWith("/v1/chat/completions")) return normalized;
return `${normalized}/v1/chat/completions`;
}
function normalizeDatabricksChatUrl(baseUrl) {
const normalized = normalizeBaseUrl(baseUrl);
if (normalized.endsWith("/chat/completions")) return normalized;
return `${normalized}/chat/completions`;
}
function normalizeDataRobotChatUrl(baseUrl) {
return buildDataRobotChatUrl(baseUrl);
}
@@ -130,18 +117,6 @@ function normalizeXiaomiMimoChatUrl(baseUrl) {
return `${normalized}/chat/completions`;
}
function normalizeSnowflakeChatUrl(baseUrl) {
const normalized = normalizeBaseUrl(baseUrl)
.replace(/\/cortex\/inference:complete$/, "")
.replace(/\/api\/v2$/, "");
return `${normalized}/api/v2/cortex/inference:complete`;
}
function normalizeGigachatChatUrl(baseUrl) {
const normalized = normalizeBaseUrl(baseUrl).replace(/\/chat\/completions$/, "");
return `${normalized}/chat/completions`;
}
function normalizeOpenAIChatUrl(baseUrl) {
const normalized = normalizeBaseUrl(baseUrl);
if (
@@ -208,19 +183,19 @@ export class DefaultExecutor extends BaseExecutor {
}
switch (this.provider) {
case "bailian-coding-plan": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeBailianMessagesUrl(baseUrl);
}
case "heroku": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeHerokuChatUrl(baseUrl);
}
case "databricks": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeDatabricksChatUrl(baseUrl);
}
case "datarobot": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeDataRobotChatUrl(baseUrl);
}
case "azure-ai": {
@@ -230,11 +205,11 @@ export class DefaultExecutor extends BaseExecutor {
forceResponses || credentials?.providerSpecificData?.apiType === "responses"
? "responses"
: "chat";
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeAzureAiChatUrl(baseUrl, apiType);
}
case "watsonx": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeWatsonxChatUrl(baseUrl);
}
case "oci": {
@@ -244,31 +219,31 @@ export class DefaultExecutor extends BaseExecutor {
forceResponses || credentials?.providerSpecificData?.apiType === "responses"
? "responses"
: "chat";
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeOciChatUrl(baseUrl, apiType);
}
case "sap": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeSapChatUrl(baseUrl);
}
case "xiaomi-mimo": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeXiaomiMimoChatUrl(baseUrl);
}
case "snowflake": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeSnowflakeChatUrl(baseUrl);
}
case "gigachat": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeGigachatChatUrl(baseUrl);
}
case "maritalk": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return buildMaritalkChatUrl(baseUrl);
}
case "siliconflow": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeOpenAIChatUrl(baseUrl);
}
case "ollama-local":
@@ -295,7 +270,7 @@ export class DefaultExecutor extends BaseExecutor {
}
case "zai":
case "glm-coding-apikey": {
const zaiBaseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
const zaiBaseUrl = this.resolveBaseUrl(credentials);
return `${zaiBaseUrl}?beta=true`;
}
case "claude":
@@ -335,40 +310,7 @@ export class DefaultExecutor extends BaseExecutor {
}
buildHeaders(credentials, stream = true, clientHeaders?: Record<string, string> | null) {
const headers = { "Content-Type": "application/json", ...this.config.headers };
// Allow per-provider User-Agent override via environment variable.
const providerId = this.config?.id || this.provider;
if (providerId) {
const envKey = `${providerId.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_USER_AGENT`;
const envUA = process.env[envKey]?.trim();
if (envUA) {
headers["User-Agent"] = envUA;
if ("user-agent" in headers) {
headers["user-agent"] = envUA;
}
}
}
// T07: resolve extra keys round-robin locally since DefaultExecutor overrides BaseExecutor buildHeaders
const extraKeys =
(credentials.providerSpecificData?.extraApiKeys as string[] | undefined) ?? [];
const selectedKeyId = (credentials.providerSpecificData as Record<string, unknown> | undefined)
?.selectedKeyId as string | undefined;
let effectiveKey = credentials.apiKey;
if (extraKeys.length > 0 && credentials.connectionId && credentials.apiKey) {
const resolved = resolveKeyForRequest(
credentials.connectionId,
credentials.apiKey,
extraKeys,
selectedKeyId ?? null
);
effectiveKey = resolved?.key ?? credentials.apiKey;
if (resolved && credentials.providerSpecificData) {
(credentials.providerSpecificData as Record<string, unknown>).selectedKeyId =
resolved.keyId;
}
}
const { headers, effectiveKey } = this.buildHeadersPreamble(credentials, stream);
switch (this.provider) {
case "gemini":
@@ -546,25 +488,7 @@ export class DefaultExecutor extends BaseExecutor {
// Forward client request metadata headers (from OpenCode or similar clients)
// Allowlist-based: only specific x-opencode-* headers and User-Agent are forwarded
if (clientHeaders) {
const clientUA = clientHeaders["User-Agent"] || clientHeaders["user-agent"];
if (clientUA) {
setUserAgentHeader(headers, clientUA);
}
const opencodeHeaderKeys = [
"x-opencode-session",
"x-opencode-request",
"x-opencode-project",
"x-opencode-client",
];
for (const headerName of opencodeHeaderKeys) {
const value = Object.entries(clientHeaders).find(
([key]) => key.toLowerCase() === headerName.toLowerCase()
)?.[1];
if (value) {
headers[headerName] = value;
}
}
forwardOpencodeClientHeaders(headers, clientHeaders);
// #3974: merge the client's negotiated anthropic-beta (allowlisted) into the
// outbound set. The registry's static ANTHROPIC_BETA_CLAUDE_OAUTH lacks

View File

@@ -1,10 +1,4 @@
import { randomUUID } from "crypto";
import {
BaseExecutor,
setUserAgentHeader,
type ExecuteInput,
type ProviderCredentials,
} from "./base.ts";
import { BaseExecutor, type ExecuteInput, type ProviderCredentials } from "./base.ts";
import { PROVIDERS } from "../config/constants.ts";
import { getModelTargetFormat } from "../config/providerModels.ts";
import {
@@ -12,6 +6,7 @@ import {
isThinkingMessageModel,
} from "../utils/reasoningContentInjector.ts";
import { runWithProxyContext } from "../utils/proxyFetch.ts";
import { forwardOpencodeClientHeaders } from "../utils/opencodeHeaders.ts";
/**
* Per-account proxy configuration, persisted by NoAuthAccountCard under
@@ -42,6 +37,22 @@ interface OpencodeAccountState {
const OPENCODE_COOLDOWN_BASE_MS = 5_000;
const OPENCODE_COOLDOWN_MAX_MS = 60_000;
const EFFORT_LEVELS = ["low", "medium", "high", "max"] as const;
/**
* Parse a DeepSeek V4 Pro model string with an effort-level suffix.
* e.g. "deepseek-v4-pro-low" → { baseModel: "deepseek-v4-pro", effort: "low" }
* Returns null if the model doesn't match the pattern.
*/
function parseDeepSeekEffortLevel(model: string): { baseModel: string; effort: string } | null {
const m = String(model || "");
const matchedLevel = EFFORT_LEVELS.find((level) => m.endsWith(`-${level}`));
if (!matchedLevel) return null;
const baseModel = m.slice(0, -matchedLevel.length - 1);
if (baseModel.toLowerCase() !== "deepseek-v4-pro") return null;
return { baseModel: "deepseek-v4-pro", effort: matchedLevel };
}
export class OpencodeExecutor extends BaseExecutor {
_requestFormat: string | null = null;
@@ -159,7 +170,9 @@ export class OpencodeExecutor extends BaseExecutor {
log?.info?.(
"OPENCODE",
`dispatch via account ${masked} (idx ${attempt + 1}/${this.accounts.length})` +
(account.proxy ? ` through proxy ${account.proxy.host}:${account.proxy.port}` : " direct")
(account.proxy
? ` through proxy ${account.proxy.host}:${account.proxy.port}`
: " direct")
);
// Pin egress to this account's proxy for the whole BaseExecutor dispatch
@@ -173,10 +186,7 @@ export class OpencodeExecutor extends BaseExecutor {
const status = result.response.status;
if (status === 429) {
this.markCooldown(account);
log?.warn?.(
"OPENCODE",
`Rate limited (429) on account ${masked}, rotating to next…`
);
log?.warn?.("OPENCODE", `Rate limited (429) on account ${masked}, rotating to next…`);
continue;
}
@@ -239,59 +249,16 @@ export class OpencodeExecutor extends BaseExecutor {
}
if (clientHeaders) {
const clientUA = clientHeaders["User-Agent"] || clientHeaders["user-agent"];
if (clientUA) {
setUserAgentHeader(headers, clientUA);
}
forwardOpencodeClientHeaders(headers, clientHeaders, {
synthesizeRequestId: true,
});
}
// Forward OpenCode request metadata headers from client
const findClientHeader = (name: string) =>
Object.entries(clientHeaders).find(
([key]) => key.toLowerCase() === name.toLowerCase()
)?.[1];
const opencodeHeaderKeys = [
"x-opencode-session",
"x-opencode-request",
"x-opencode-project",
"x-opencode-client",
];
for (const headerName of opencodeHeaderKeys) {
const value = findClientHeader(headerName);
if (value) {
headers[headerName] = value;
}
}
// #4022: OpenCode CLI only emits x-opencode-* headers when the provider id
// starts with "opencode". For a custom-named provider (e.g. "omniroute") it
// instead sends x-session-affinity / X-Session-Id, which both carry the same
// OpenCode sessionID. Map that session id onto x-opencode-session so session
// continuity to the opencode.ai upstream works regardless of how the user
// named the provider. Scoped to this executor (opencode.ai/zen upstreams
// only) — the generic DefaultExecutor intentionally does NOT do this, to
// avoid leaking the client session id to arbitrary third-party upstreams.
if (!headers["x-opencode-session"]) {
const sessionAffinity =
findClientHeader("x-session-affinity") || findClientHeader("x-session-id");
if (sessionAffinity) {
headers["x-opencode-session"] = sessionAffinity;
// #4465: a custom-named provider only reaches this fallback because the
// OpenCode CLI did NOT emit the x-opencode-* set (it only does so when the
// provider id starts with "opencode"). It therefore also dropped
// x-opencode-request, a per-request correlation id. Synthesize one so these
// users are not disadvantaged versus opencode-prefixed providers on the
// opencode.ai upstream. x-opencode-client / x-opencode-project are NOT
// fabricated: their valid values are opencode-internal and inventing them
// could be rejected upstream — they remain forward-only above. Scoped to this
// executor (opencode.ai/zen) and only to the fallback path, so the direct
// OpenCode CLI flow (which controls its own request id) is untouched.
if (!headers["x-opencode-request"]) {
headers["x-opencode-request"] = randomUUID();
}
}
}
if (!headers["User-Agent"] && !headers["user-agent"]) {
headers["User-Agent"] = "opencode/local";
}
if (!headers["x-opencode-client"]) {
headers["x-opencode-client"] = "cli";
}
void model;
@@ -316,16 +283,11 @@ export class OpencodeExecutor extends BaseExecutor {
}
if (modifiedBody && typeof modifiedBody === "object" && !Array.isArray(modifiedBody)) {
const mb = modifiedBody as Record<string, unknown>;
const m = String(model || "");
const effortLevels = ["low", "medium", "high", "max"] as const;
const matchedLevel = effortLevels.find((level) => m.endsWith(`-${level}`));
if (matchedLevel) {
const base = m.slice(0, -matchedLevel.length - 1);
if (base.toLowerCase() === "deepseek-v4-pro") {
mb.model = "deepseek-v4-pro";
if (mb.reasoning_effort === undefined) {
mb.reasoning_effort = matchedLevel;
}
const parsed = parseDeepSeekEffortLevel(model);
if (parsed) {
mb.model = parsed.baseModel;
if (mb.reasoning_effort === undefined) {
mb.reasoning_effort = parsed.effort;
}
}
}

View File

@@ -14,7 +14,7 @@
*/
import { BaseExecutor, type ExecuteInput } from "./base.ts";
import { sanitizeErrorMessage } from "../utils/error.ts";
import { errorResponse } from "../utils/error.ts";
import { prepareToolMessages, buildToolAwareResult } from "../translator/webTools.ts";
// ─── Constants ───────────────────────────────────────────────────────────────
@@ -110,16 +110,7 @@ export function validateT3Credentials(creds: T3ChatCredentials | null | undefine
}
function buildErrorResponse(status: number, message: string): Response {
return new Response(
JSON.stringify({
error: {
message: sanitizeErrorMessage(message),
type: "upstream_error",
code: `HTTP_${status}`,
},
}),
{ status, headers: { "Content-Type": "application/json" } }
);
return errorResponse(status, message);
}
/**

View File

@@ -0,0 +1,67 @@
import { randomUUID } from "crypto";
import { setUserAgentHeader } from "../executors/base.ts";
/**
* Header keys that are forwarded from the client to the upstream provider.
* Used by both OpencodeExecutor and DefaultExecutor.
*/
const OPENCODE_HEADER_KEYS = [
"x-opencode-session",
"x-opencode-request",
"x-opencode-project",
"x-opencode-client",
] as const;
/**
* Case-insensitive lookup for a header in a headers record.
*/
function findHeader(headers: Record<string, string>, name: string): string | undefined {
return Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
}
/**
* Forward OpenCode client request metadata headers to the upstream provider.
*
* Shared logic used by OpencodeExecutor and DefaultExecutor:
* 1. Forwards User-Agent from clientHeaders via `setUserAgentHeader()`
* 2. Forwards x-opencode-session, x-opencode-request, x-opencode-project,
* x-opencode-client headers (case-insensitive match)
*
* @param headers - The outbound headers record to mutate
* @param clientHeaders - The client-provided headers to forward from
* @param options.synthesizeRequestId - When true (OpencodeExecutor only), maps
* x-session-affinity / x-session-id to x-opencode-session when the latter is
* missing, and synthesizes a UUID for x-opencode-request if also missing.
*/
export function forwardOpencodeClientHeaders(
headers: Record<string, string>,
clientHeaders: Record<string, string>,
options?: { synthesizeRequestId?: boolean }
): void {
// 1. Forward User-Agent
const clientUA = clientHeaders["User-Agent"] || clientHeaders["user-agent"];
if (clientUA) {
setUserAgentHeader(headers, clientUA);
}
// 2. Forward x-opencode-* metadata headers
for (const headerName of OPENCODE_HEADER_KEYS) {
const value = findHeader(clientHeaders, headerName);
if (value) {
headers[headerName] = value;
}
}
// 3. OpencodeExecutor-only: synthesize session/request id from fallback headers
if (options?.synthesizeRequestId && !headers["x-opencode-session"]) {
const sessionAffinity =
findHeader(clientHeaders, "x-session-affinity") || findHeader(clientHeaders, "x-session-id");
if (sessionAffinity) {
headers["x-opencode-session"] = sessionAffinity;
if (!headers["x-opencode-request"]) {
headers["x-opencode-request"] = randomUUID();
}
}
}
}

View File

@@ -12,3 +12,13 @@ export function stripTrailingSlashes(value: string): string {
}
return end === value.length ? value : value.slice(0, end);
}
/**
* Normalize a base URL by trimming whitespace and stripping trailing slashes.
* Handles non-string inputs gracefully (returns empty string).
* Single source of truth — replaces per-file inline copies in config/*.ts.
*/
export function normalizeBaseUrl(value: string | null | undefined): string {
const str = typeof value === "string" ? value : "";
return stripTrailingSlashes(str.trim());
}

View File

@@ -1,18 +1,6 @@
/**
* Service kind — declarative tag for what a provider can do beyond basic LLM chat.
* Affects UI filtering and playground routing; does not influence request routing.
*/
export type ServiceKind =
| "llm"
| "embedding"
| "image"
| "imageToText"
| "tts"
| "stt"
| "webSearch"
| "webFetch"
| "video"
| "music";
// Re-export service kinds from leaf module (avoids circular dep with providerSchema)
export type { ServiceKind } from "./serviceKinds";
export { SERVICE_KIND_VALUES } from "./serviceKinds";
export type RiskNoticeVariant = "oauth" | "webCookie" | "deprecated" | "embedded-service";

View File

@@ -0,0 +1,32 @@
/**
* Service kind — declarative tag for what a provider can do beyond basic LLM chat.
* Affects UI filtering and playground routing; does not influence request routing.
*
* This is a dependency-free leaf module to avoid circular imports between
* providers.ts and providerSchema.ts.
*/
export type ServiceKind =
| "llm"
| "embedding"
| "image"
| "imageToText"
| "tts"
| "stt"
| "webSearch"
| "webFetch"
| "video"
| "music";
export const SERVICE_KIND_VALUES: readonly ServiceKind[] = [
"llm",
"embedding",
"image",
"imageToText",
"tts",
"stt",
"webSearch",
"webFetch",
"video",
"music",
];

View File

@@ -9,19 +9,7 @@
*/
import { z } from "zod";
const SERVICE_KIND_VALUES = [
"llm",
"embedding",
"image",
"imageToText",
"tts",
"stt",
"webSearch",
"webFetch",
"video",
"music",
] as const;
import { SERVICE_KIND_VALUES } from "@/shared/constants/serviceKinds";
export const ProviderSchema = z.object({
id: z.string().min(1),

View File

@@ -0,0 +1,129 @@
import test from "node:test";
import assert from "node:assert/strict";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
test("DefaultExecutor.buildHeaders: gemini uses x-goog-api-key header", () => {
const executor = new DefaultExecutor("gemini");
const headers = executor.buildHeaders({ apiKey: "gem-key-1" }, true);
assert.equal(headers["x-goog-api-key"], "gem-key-1");
assert.equal(headers["Authorization"], undefined);
});
test("DefaultExecutor.buildHeaders: gemini falls back to accessToken when no apiKey", () => {
const executor = new DefaultExecutor("gemini");
const headers = executor.buildHeaders({ accessToken: "tok-gem" }, true);
assert.equal(headers["Authorization"], "Bearer tok-gem");
assert.equal(headers["x-goog-api-key"], undefined);
});
test("DefaultExecutor.buildHeaders: claude uses x-api-key header", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ apiKey: "sk-ant-123" }, true);
assert.equal(headers["x-api-key"], "sk-ant-123");
assert.equal(headers["Authorization"], undefined);
});
test("DefaultExecutor.buildHeaders: claude falls back to accessToken", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ accessToken: "tok-claude" }, true);
assert.equal(headers["Authorization"], "Bearer tok-claude");
assert.equal(headers["x-api-key"], undefined);
});
test("DefaultExecutor.buildHeaders: anthropic uses x-api-key header", () => {
const executor = new DefaultExecutor("anthropic");
const headers = executor.buildHeaders({ apiKey: "sk-ant-456" }, true);
assert.equal(headers["x-api-key"], "sk-ant-456");
assert.equal(headers["Authorization"], undefined);
});
test("DefaultExecutor.buildHeaders: azure-ai uses api-key header", () => {
const executor = new DefaultExecutor("azure-ai");
const headers = executor.buildHeaders({ apiKey: "az-key-1" }, true);
assert.equal(headers["api-key"], "az-key-1");
assert.equal(headers["Authorization"], undefined);
});
test("DefaultExecutor.buildHeaders: azure-ai uses accessToken when no apiKey", () => {
const executor = new DefaultExecutor("azure-ai");
const headers = executor.buildHeaders({ accessToken: "tok-az" }, true);
assert.equal(headers["api-key"], "tok-az");
assert.equal(headers["Authorization"], undefined);
});
test("DefaultExecutor.buildHeaders: snowflake strips pat/ prefix", () => {
const executor = new DefaultExecutor("snowflake");
const headers = executor.buildHeaders({ apiKey: "pat/my-token" }, true);
assert.equal(headers["Authorization"], "Bearer my-token");
assert.equal(headers["X-Snowflake-Authorization-Token-Type"], "PROGRAMMATIC_ACCESS_TOKEN");
});
test("DefaultExecutor.buildHeaders: snowflake uses KEYPAIR_JWT when no pat/ prefix", () => {
const executor = new DefaultExecutor("snowflake");
const headers = executor.buildHeaders({ apiKey: "jwt-token-abc" }, true);
assert.equal(headers["Authorization"], "Bearer jwt-token-abc");
assert.equal(headers["X-Snowflake-Authorization-Token-Type"], "KEYPAIR_JWT");
});
test("DefaultExecutor.buildHeaders: clarifai uses Key prefix", () => {
const executor = new DefaultExecutor("clarifai");
const headers = executor.buildHeaders({ apiKey: "clar-123" }, true);
assert.equal(headers["Authorization"], "Key clar-123");
});
test("DefaultExecutor.buildHeaders: maritalk uses Key prefix", () => {
const executor = new DefaultExecutor("maritalk");
const headers = executor.buildHeaders({ apiKey: "mt-key-1" }, true);
assert.equal(headers["Authorization"], "Key mt-key-1");
});
test("DefaultExecutor.buildHeaders: reka sets both Authorization and X-Api-Key", () => {
const executor = new DefaultExecutor("reka");
const headers = executor.buildHeaders({ apiKey: "reka-1" }, true);
assert.equal(headers["Authorization"], "Bearer reka-1");
assert.equal(headers["X-Api-Key"], "reka-1");
});
test("DefaultExecutor.buildHeaders: gigachat uses accessToken preferentially", () => {
const executor = new DefaultExecutor("gigachat");
const headers = executor.buildHeaders({ apiKey: "giga-key", accessToken: "giga-tok" }, true);
assert.equal(headers["Authorization"], "Bearer giga-tok");
});
test("DefaultExecutor.buildHeaders: gigachat falls back to apiKey", () => {
const executor = new DefaultExecutor("gigachat");
const headers = executor.buildHeaders({ apiKey: "giga-key" }, true);
assert.equal(headers["Authorization"], "Bearer giga-key");
});
test("DefaultExecutor.buildHeaders: generic provider uses Bearer Authorization", () => {
const executor = new DefaultExecutor("openai");
const headers = executor.buildHeaders({ apiKey: "sk-openai-1" }, true);
assert.equal(headers["Authorization"], "Bearer sk-openai-1");
});
test("DefaultExecutor.buildHeaders: stream=true sets Accept text/event-stream", () => {
const executor = new DefaultExecutor("openai");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["Accept"], "text/event-stream");
});
test("DefaultExecutor.buildHeaders: stream=false sets Accept application/json", () => {
const executor = new DefaultExecutor("openai");
const headers = executor.buildHeaders({ apiKey: "key-1" }, false);
assert.equal(headers["Accept"], "application/json");
});
test("DefaultExecutor.buildHeaders: OCI adds OpenAI-Project header when projectId present", () => {
const executor = new DefaultExecutor("oci");
const headers = executor.buildHeaders({ apiKey: "oci-key", projectId: "proj-123" }, true);
assert.equal(headers["Authorization"], "Bearer oci-key");
assert.equal(headers["OpenAI-Project"], "proj-123");
});
test("DefaultExecutor.buildHeaders: OCI omits OpenAI-Project when projectId absent", () => {
const executor = new DefaultExecutor("oci");
const headers = executor.buildHeaders({ apiKey: "oci-key" }, true);
assert.equal(headers["Authorization"], "Bearer oci-key");
assert.equal(headers["OpenAI-Project"], undefined);
});

View File

@@ -0,0 +1,104 @@
import test from "node:test";
import assert from "node:assert/strict";
import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts";
// ---------------------------------------------------------------------------
// OpencodeExecutor.buildHeaders — request format auth switch
// ---------------------------------------------------------------------------
test("OpencodeExecutor.buildHeaders: default format uses Bearer Authorization", () => {
const executor = new OpencodeExecutor("opencode");
// _requestFormat defaults to null → default Bearer path
const headers = executor.buildHeaders({ apiKey: "sk-oc-1" }, true);
assert.equal(headers["Authorization"], "Bearer sk-oc-1");
assert.equal(headers["x-api-key"], undefined);
});
test("OpencodeExecutor.buildHeaders: claude format uses x-api-key header", () => {
const executor = new OpencodeExecutor("opencode");
executor._requestFormat = "claude";
const headers = executor.buildHeaders({ apiKey: "sk-claude-1" }, true);
assert.equal(headers["x-api-key"], "sk-claude-1");
assert.equal(headers["Authorization"], undefined);
});
test("OpencodeExecutor.buildHeaders: claude format sets anthropic-version header", () => {
const executor = new OpencodeExecutor("opencode");
executor._requestFormat = "claude";
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["anthropic-version"], "2023-06-01");
});
test("OpencodeExecutor.buildHeaders: non-claude format omits anthropic-version", () => {
const executor = new OpencodeExecutor("opencode");
executor._requestFormat = "openai";
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["anthropic-version"], undefined);
});
test("OpencodeExecutor.buildHeaders: stream=true sets Accept text/event-stream", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["Accept"], "text/event-stream");
});
test("OpencodeExecutor.buildHeaders: stream=false omits Accept header", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, false);
assert.equal(headers["Accept"], undefined);
});
test("OpencodeExecutor.buildHeaders: uses accessToken when apiKey is absent", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ accessToken: "tok-oc" }, true);
assert.equal(headers["Authorization"], "Bearer tok-oc");
});
test("OpencodeExecutor.buildHeaders: apiKey takes precedence over accessToken", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "sk-pri", accessToken: "tok-sec" }, true);
assert.equal(headers["Authorization"], "Bearer sk-pri");
});
test("OpencodeExecutor.buildHeaders: claude format with accessToken still uses x-api-key", () => {
const executor = new OpencodeExecutor("opencode");
executor._requestFormat = "claude";
const headers = executor.buildHeaders({ apiKey: "sk-a", accessToken: "tok-b" }, true);
assert.equal(headers["x-api-key"], "sk-a");
assert.equal(headers["Authorization"], undefined);
});
test("OpencodeExecutor.buildHeaders: Content-Type always application/json", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["Content-Type"], "application/json");
});
test("OpencodeExecutor.buildHeaders: defaults User-Agent to opencode/local when no client UA", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["User-Agent"], "opencode/local");
});
test("OpencodeExecutor.buildHeaders: preserves client User-Agent when provided", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true, {
"User-Agent": "opencode/1.17.12",
});
assert.equal(headers["User-Agent"], "opencode/1.17.12");
});
test("OpencodeExecutor.buildHeaders: defaults x-opencode-client to cli when absent", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true);
assert.equal(headers["x-opencode-client"], "cli");
});
test("OpencodeExecutor.buildHeaders: preserves x-opencode-client from client headers", () => {
const executor = new OpencodeExecutor("opencode");
const headers = executor.buildHeaders({ apiKey: "key-1" }, true, {
"x-opencode-client": "desktop",
});
assert.equal(headers["x-opencode-client"], "desktop");
});

View File

@@ -0,0 +1,120 @@
import test from "node:test";
import assert from "node:assert/strict";
import { BaseExecutor } from "../../open-sse/executors/base.ts";
import type { ProviderCredentials } from "../../open-sse/executors/base.ts";
/**
* TestExecutor exposes protected buildHeadersPreamble and resolveEffectiveKey.
*/
class TestExecutor extends BaseExecutor {
constructor(config = {}) {
super("test-provider", {
baseUrls: ["https://default.example/v1/chat/completions"],
headers: { "X-Provider-Header": "from-config" },
...config,
});
}
publicBuildHeadersPreamble(
credentials: ProviderCredentials,
stream: boolean
): { headers: Record<string, string>; effectiveKey: string } {
return this.buildHeadersPreamble(credentials, stream);
}
publicResolveEffectiveKey(credentials: ProviderCredentials): string {
return this.resolveEffectiveKey(credentials);
}
async transformRequest(model: string, body: unknown, stream: boolean) {
return body;
}
}
// ---------------------------------------------------------------------------
// resolveEffectiveKey tests
// ---------------------------------------------------------------------------
test("resolveEffectiveKey: returns apiKey when no extraApiKeys present", () => {
const executor = new TestExecutor();
const result = executor.publicResolveEffectiveKey({
apiKey: "sk-primary-123",
});
assert.equal(result, "sk-primary-123");
});
test("resolveEffectiveKey: returns apiKey when extraApiKeys is empty", () => {
const executor = new TestExecutor();
const result = executor.publicResolveEffectiveKey({
apiKey: "sk-primary-123",
providerSpecificData: { extraApiKeys: [] },
});
assert.equal(result, "sk-primary-123");
});
test("resolveEffectiveKey: returns apiKey when no connectionId", () => {
const executor = new TestExecutor();
const result = executor.publicResolveEffectiveKey({
apiKey: "sk-primary-123",
providerSpecificData: { extraApiKeys: ["sk-extra-1", "sk-extra-2"] },
});
assert.equal(result, "sk-primary-123");
});
test("resolveEffectiveKey: returns accessToken when apiKey is undefined", () => {
const executor = new TestExecutor();
const result = executor.publicResolveEffectiveKey({
accessToken: "tok-abc",
});
assert.equal(result, undefined);
});
// ---------------------------------------------------------------------------
// buildHeadersPreamble tests
// ---------------------------------------------------------------------------
test("buildHeadersPreamble: includes Content-Type application/json", () => {
const executor = new TestExecutor();
const { headers } = executor.publicBuildHeadersPreamble({ apiKey: "key-1" }, true);
assert.equal(headers["Content-Type"], "application/json");
});
test("buildHeadersPreamble: merges config.headers into result", () => {
const executor = new TestExecutor({
headers: { "X-Custom-Header": "custom-value" },
});
const { headers } = executor.publicBuildHeadersPreamble({ apiKey: "key-1" }, true);
assert.equal(headers["X-Custom-Header"], "custom-value");
});
test("buildHeadersPreamble: returns effectiveKey from resolveEffectiveKey", () => {
const executor = new TestExecutor();
const { effectiveKey } = executor.publicBuildHeadersPreamble({ apiKey: "sk-abc-123" }, true);
assert.equal(effectiveKey, "sk-abc-123");
});
test("buildHeadersPreamble: sets User-Agent from provider env var override", () => {
const envKey = "TEST_PROVIDER_USER_AGENT";
process.env[envKey] = "custom-agent/1.0";
try {
const executor = new TestExecutor({ id: "test-provider" });
const { headers } = executor.publicBuildHeadersPreamble({ apiKey: "key-1" }, true);
assert.equal(headers["User-Agent"], "custom-agent/1.0");
} finally {
delete process.env[envKey];
}
});
test("buildHeadersPreamble: does not set User-Agent when env var is empty", () => {
const envKey = "TEST_PROVIDER_USER_AGENT";
const saved = process.env[envKey];
delete process.env[envKey];
try {
const executor = new TestExecutor({ id: "test-provider" });
const { headers } = executor.publicBuildHeadersPreamble({ apiKey: "key-1" }, true);
assert.equal(headers["User-Agent"], undefined);
} finally {
if (saved !== undefined) process.env[envKey] = saved;
}
});

View File

@@ -0,0 +1,127 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
/**
* Characterization tests for the DeepSeek V4 Pro effort-level parsing logic.
*
* The parseDeepSeekEffortLevel function is module-level (non-exported) in
* open-sse/executors/opencode.ts. We re-implement the same logic here to lock
* down the expected behavior as a regression safety net.
*
* Source reference: open-sse/executors/opencode.ts lines 44-62
*/
const EFFORT_LEVELS = ["low", "medium", "high", "max"] as const;
function parseDeepSeekEffortLevel(model: string): { baseModel: string; effort: string } | null {
const m = String(model || "");
const matchedLevel = EFFORT_LEVELS.find((level) => m.endsWith(`-${level}`));
if (!matchedLevel) return null;
const baseModel = m.slice(0, -matchedLevel.length - 1);
if (baseModel.toLowerCase() !== "deepseek-v4-pro") return null;
return { baseModel: "deepseek-v4-pro", effort: matchedLevel };
}
// -- Valid effort levels -------------------------------------------------------
describe("parseDeepSeekEffortLevel - valid suffixes", () => {
it("deepseek-v4-pro-low returns low effort", () => {
const result = parseDeepSeekEffortLevel("deepseek-v4-pro-low");
assert.deepEqual(result, { baseModel: "deepseek-v4-pro", effort: "low" });
});
it("deepseek-v4-pro-medium returns medium effort", () => {
const result = parseDeepSeekEffortLevel("deepseek-v4-pro-medium");
assert.deepEqual(result, { baseModel: "deepseek-v4-pro", effort: "medium" });
});
it("deepseek-v4-pro-high returns high effort", () => {
const result = parseDeepSeekEffortLevel("deepseek-v4-pro-high");
assert.deepEqual(result, { baseModel: "deepseek-v4-pro", effort: "high" });
});
it("deepseek-v4-pro-max returns max effort", () => {
const result = parseDeepSeekEffortLevel("deepseek-v4-pro-max");
assert.deepEqual(result, { baseModel: "deepseek-v4-pro", effort: "max" });
});
});
// -- Case-insensitive base model matching --------------------------------------
describe("parseDeepSeekEffortLevel - case-insensitive base model", () => {
it("matches DeepSeek-V4-Pro-high (uppercase base, lowercase suffix)", () => {
const result = parseDeepSeekEffortLevel("DeepSeek-V4-Pro-high");
assert.deepEqual(result, { baseModel: "deepseek-v4-pro", effort: "high" });
});
it("matches deepseek-V4-pro-low (mixed case base, lowercase suffix)", () => {
const result = parseDeepSeekEffortLevel("deepseek-V4-pro-low");
assert.deepEqual(result, { baseModel: "deepseek-v4-pro", effort: "low" });
});
it("returns null for DeepSeek-V4-Pro-High (uppercase suffix does not match)", () => {
// Suffix matching is case-sensitive — only the base model comparison uses toLowerCase
assert.equal(parseDeepSeekEffortLevel("DeepSeek-V4-Pro-High"), null);
});
});
// -- Returns null for non-matching inputs --------------------------------------
describe("parseDeepSeekEffortLevel - no match cases", () => {
it("returns null for bare deepseek-v4-pro (no suffix)", () => {
assert.equal(parseDeepSeekEffortLevel("deepseek-v4-pro"), null);
});
it("returns null for gpt-5-high (not deepseek-v4-pro)", () => {
assert.equal(parseDeepSeekEffortLevel("gpt-5-high"), null);
});
it("returns null for empty string", () => {
assert.equal(parseDeepSeekEffortLevel(""), null);
});
it("returns null for trailing dash with no level (deepseek-v4-pro-)", () => {
assert.equal(parseDeepSeekEffortLevel("deepseek-v4-pro-"), null);
});
it("returns null for deepseek-v4-pro-lowextra (suffix is not an exact level)", () => {
assert.equal(parseDeepSeekEffortLevel("deepseek-v4-pro-lowextra"), null);
});
it("returns null for deepseek-v4-pro (case mismatch on level)", () => {
// The suffix "Low" does not match "low" exactly
assert.equal(parseDeepSeekEffortLevel("deepseek-v4-pro-Low"), null);
});
it("returns null for a random string", () => {
assert.equal(parseDeepSeekEffortLevel("random-string"), null);
});
it("returns null for just the suffix -high", () => {
assert.equal(parseDeepSeekEffortLevel("-high"), null);
});
});
// -- Edge cases ----------------------------------------------------------------
describe("parseDeepSeekEffortLevel - edge cases", () => {
it("handles numeric input via String coercion", () => {
// String(123) === "123" — no dash suffix, returns null
assert.equal(parseDeepSeekEffortLevel(123 as unknown as string), null);
});
it("handles null input via String coercion", () => {
// String(null) === "null" — no match
assert.equal(parseDeepSeekEffortLevel(null as unknown as string), null);
});
it("handles undefined input via String coercion", () => {
// String(undefined) === "undefined" — no match
assert.equal(parseDeepSeekEffortLevel(undefined as unknown as string), null);
});
it("returns the canonical baseModel as lowercase when suffix matches", () => {
const result = parseDeepSeekEffortLevel("DeepSeek-V4-Pro-max");
assert.deepEqual(result, { baseModel: "deepseek-v4-pro", effort: "max" });
});
});

View File

@@ -0,0 +1,227 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { forwardOpencodeClientHeaders } from "../../open-sse/utils/opencodeHeaders.ts";
// Helper: create a fresh empty headers record
function h(): Record<string, string> {
return {};
}
// ── User-Agent forwarding ───────────────────────────────────────────────────
describe("forwardOpencodeClientHeaders User-Agent", () => {
it("forwards User-Agent from client headers", () => {
const headers = h();
const clientHeaders = { "User-Agent": "MyTool/1.0" };
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["User-Agent"], "MyTool/1.0");
});
it("does NOT set lowercase user-agent when it is absent from headers", () => {
const headers = h();
const clientHeaders = { "User-Agent": "MyTool/1.0" };
forwardOpencodeClientHeaders(headers, clientHeaders);
// setUserAgentHeader only sets lowercase "user-agent" if it already exists
assert.equal(headers["user-agent"], undefined);
});
it("sets lowercase user-agent when it already exists in headers", () => {
const headers = { "user-agent": "old" } as Record<string, string>;
const clientHeaders = { "User-Agent": "MyTool/1.0" };
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["user-agent"], "MyTool/1.0");
assert.equal(headers["User-Agent"], "MyTool/1.0");
});
it("falls back to lowercase user-agent from client headers", () => {
const headers = h();
const clientHeaders = { "user-agent": "FallbackTool/2.0" };
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["User-Agent"], "FallbackTool/2.0");
});
});
// ── x-opencode-* header forwarding ─────────────────────────────────────────
describe("forwardOpencodeClientHeaders x-opencode-* headers", () => {
it("forwards x-opencode-session", () => {
const headers = h();
const clientHeaders = { "x-opencode-session": "sess-123" };
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-opencode-session"], "sess-123");
});
it("forwards x-opencode-request", () => {
const headers = h();
const clientHeaders = { "x-opencode-request": "req-abc" };
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-opencode-request"], "req-abc");
});
it("forwards x-opencode-project", () => {
const headers = h();
const clientHeaders = { "x-opencode-project": "/home/user/project" };
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-opencode-project"], "/home/user/project");
});
it("forwards x-opencode-client", () => {
const headers = h();
const clientHeaders = { "x-opencode-client": "opencode-cli" };
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-opencode-client"], "opencode-cli");
});
it("forwards all four x-opencode-* headers at once", () => {
const headers = h();
const clientHeaders = {
"x-opencode-session": "sess-1",
"x-opencode-request": "req-1",
"x-opencode-project": "/proj",
"x-opencode-client": "cli",
};
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-opencode-session"], "sess-1");
assert.equal(headers["x-opencode-request"], "req-1");
assert.equal(headers["x-opencode-project"], "/proj");
assert.equal(headers["x-opencode-client"], "cli");
});
it("matches x-opencode-* headers case-insensitively", () => {
const headers = h();
const clientHeaders = {
"X-OpenCode-Session": "Sess-Upper",
"X-OpenCode-Request": "Req-Upper",
"X-OpenCode-Project": "/Upper",
"X-OpenCode-Client": "UpperClient",
};
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-opencode-session"], "Sess-Upper");
assert.equal(headers["x-opencode-request"], "Req-Upper");
assert.equal(headers["x-opencode-project"], "/Upper");
assert.equal(headers["x-opencode-client"], "UpperClient");
});
it("does NOT forward unknown headers", () => {
const headers = h();
const clientHeaders = {
"x-opencode-session": "s1",
"x-random-header": "should-not-forward",
Authorization: "Bearer tok",
};
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-random-header"], undefined);
assert.equal(headers["Authorization"], undefined);
});
});
// ── synthesizeRequestId ─────────────────────────────────────────────────────
describe("forwardOpencodeClientHeaders synthesizeRequestId", () => {
it("maps x-session-affinity → x-opencode-session when latter is missing", () => {
const headers = h();
const clientHeaders = { "x-session-affinity": "affinity-abc" };
forwardOpencodeClientHeaders(headers, clientHeaders, {
synthesizeRequestId: true,
});
assert.equal(headers["x-opencode-session"], "affinity-abc");
});
it("maps x-session-id → x-opencode-session when latter is missing", () => {
const headers = h();
const clientHeaders = { "x-session-id": "sid-xyz" };
forwardOpencodeClientHeaders(headers, clientHeaders, {
synthesizeRequestId: true,
});
assert.equal(headers["x-opencode-session"], "sid-xyz");
});
it("prefers x-session-affinity over x-session-id", () => {
const headers = h();
const clientHeaders = {
"x-session-affinity": "aff-1",
"x-session-id": "sid-1",
};
forwardOpencodeClientHeaders(headers, clientHeaders, {
synthesizeRequestId: true,
});
assert.equal(headers["x-opencode-session"], "aff-1");
});
it("synthesizes x-opencode-request (UUID) when session and request are missing", () => {
const headers = h();
const clientHeaders = { "x-session-affinity": "aff-2" };
forwardOpencodeClientHeaders(headers, clientHeaders, {
synthesizeRequestId: true,
});
// x-opencode-request should be a valid UUID string
assert.ok(headers["x-opencode-request"], "should have synthesized x-opencode-request");
assert.match(
headers["x-opencode-request"],
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
"synthesized request id should be a UUID"
);
});
it("does NOT overwrite existing x-opencode-session", () => {
const headers = { "x-opencode-session": "existing-sess" } as Record<string, string>;
const clientHeaders = { "x-session-affinity": "aff-3" };
forwardOpencodeClientHeaders(headers, clientHeaders, {
synthesizeRequestId: true,
});
assert.equal(headers["x-opencode-session"], "existing-sess");
});
it("does NOT synthesize when synthesizeRequestId is false", () => {
const headers = h();
const clientHeaders = { "x-session-affinity": "aff-4" };
forwardOpencodeClientHeaders(headers, clientHeaders, {
synthesizeRequestId: false,
});
assert.equal(headers["x-opencode-session"], undefined);
assert.equal(headers["x-opencode-request"], undefined);
});
it("does NOT synthesize when synthesizeRequestId is absent", () => {
const headers = h();
const clientHeaders = { "x-session-affinity": "aff-5" };
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-opencode-session"], undefined);
assert.equal(headers["x-opencode-request"], undefined);
});
it("does NOT synthesize when there is no session affinity header", () => {
const headers = h();
const clientHeaders = { "x-random": "value" };
forwardOpencodeClientHeaders(headers, clientHeaders, {
synthesizeRequestId: true,
});
assert.equal(headers["x-opencode-session"], undefined);
assert.equal(headers["x-opencode-request"], undefined);
});
});
// ── Edge cases ──────────────────────────────────────────────────────────────
describe("forwardOpencodeClientHeaders edge cases", () => {
it("empty clientHeaders → no mutation on headers", () => {
const headers = h();
forwardOpencodeClientHeaders(headers, {});
assert.deepEqual(headers, {});
});
it("does not overwrite existing x-opencode-* headers from client", () => {
const headers = h();
const clientHeaders = {
"x-opencode-session": "client-sess",
"x-opencode-request": "client-req",
};
// Pre-set the headers — forwardOpencodeClientHeaders sets them from clientHeaders,
// which is the same value here. The function does overwrite with the same value.
// This test documents that client headers are the authoritative source.
forwardOpencodeClientHeaders(headers, clientHeaders);
assert.equal(headers["x-opencode-session"], "client-sess");
assert.equal(headers["x-opencode-request"], "client-req");
});
});

View File

@@ -0,0 +1,105 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { buildOpenAiCompatibleRegistryEntry } from "../../open-sse/config/providers/shared.ts";
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
// ── buildOpenAiCompatibleRegistryEntry ──────────────────────────────────────
describe("buildOpenAiCompatibleRegistryEntry", () => {
it("returns all default fields when only id is provided", () => {
const entry = buildOpenAiCompatibleRegistryEntry({ id: "my-provider" });
assert.equal(entry.id, "my-provider");
assert.equal(entry.format, "openai");
assert.equal(entry.executor, "default");
assert.equal(entry.authType, "apikey");
assert.equal(entry.authHeader, "bearer");
});
it("includes baseUrl when provided", () => {
const entry = buildOpenAiCompatibleRegistryEntry({
id: "my-provider",
baseUrl: "https://api.example.com/v1",
});
assert.equal(entry.baseUrl, "https://api.example.com/v1");
assert.equal(entry.format, "openai");
assert.equal(entry.executor, "default");
});
it("includes models array when provided", () => {
const models = [
{ id: "gpt-4", name: "GPT-4" },
{ id: "gpt-3.5-turbo", name: "GPT-3.5 Turbo" },
];
const entry = buildOpenAiCompatibleRegistryEntry({
id: "my-provider",
models,
});
assert.deepEqual(entry.models, models);
assert.equal(entry.models.length, 2);
});
it("overrides default authHeader when custom value is provided", () => {
const entry = buildOpenAiCompatibleRegistryEntry({
id: "my-provider",
authHeader: "x-api-key",
} as Parameters<typeof buildOpenAiCompatibleRegistryEntry>[0]);
assert.equal(entry.authHeader, "x-api-key");
// Other defaults still present
assert.equal(entry.format, "openai");
assert.equal(entry.executor, "default");
assert.equal(entry.authType, "apikey");
});
it("overrides default executor when custom value is provided", () => {
const entry = buildOpenAiCompatibleRegistryEntry({
id: "my-provider",
executor: "opencode",
});
assert.equal(entry.executor, "opencode");
// Other defaults still present
assert.equal(entry.format, "openai");
assert.equal(entry.authType, "apikey");
assert.equal(entry.authHeader, "bearer");
});
it("includes passthroughModels when set to true", () => {
const entry = buildOpenAiCompatibleRegistryEntry({
id: "my-provider",
passthroughModels: true,
});
assert.equal(entry.passthroughModels, true);
assert.equal(entry.format, "openai");
});
it("allows combining multiple overrides", () => {
const entry = buildOpenAiCompatibleRegistryEntry({
id: "multi",
baseUrl: "https://multi.api.com",
executor: "cursor",
authHeader: "x-token",
passthroughModels: true,
timeoutMs: 30_000,
});
assert.equal(entry.id, "multi");
assert.equal(entry.baseUrl, "https://multi.api.com");
assert.equal(entry.executor, "cursor");
assert.equal(entry.authHeader, "x-token");
assert.equal(entry.passthroughModels, true);
assert.equal(entry.timeoutMs, 30_000);
assert.equal(entry.format, "openai");
assert.equal(entry.authType, "apikey");
});
it("returns a RegistryEntry-compatible object", () => {
const entry: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "type-check",
});
// Verify the type is assignable (compile-time) and runtime shape is correct
assert.equal(typeof entry.id, "string");
assert.equal(typeof entry.format, "string");
assert.equal(typeof entry.executor, "string");
assert.equal(typeof entry.authType, "string");
assert.equal(typeof entry.authHeader, "string");
});
});

View File

@@ -0,0 +1,93 @@
import test from "node:test";
import assert from "node:assert/strict";
import { BaseExecutor } from "../../open-sse/executors/base.ts";
import type { ProviderCredentials } from "../../open-sse/executors/base.ts";
/**
* TestExecutor exposes protected resolveBaseUrl for unit testing.
*/
class TestExecutor extends BaseExecutor {
constructor(config = {}) {
super("test-provider", {
baseUrls: ["https://default.example/v1/chat/completions"],
...config,
});
}
/** Public wrapper for the protected method. */
publicResolveBaseUrl(credentials: ProviderCredentials | null, fallback?: string): string {
return this.resolveBaseUrl(credentials, fallback);
}
async transformRequest(model: string, body: unknown, stream: boolean) {
return body;
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
test("resolveBaseUrl: returns credentials.providerSpecificData.baseUrl when set", () => {
const executor = new TestExecutor();
const result = executor.publicResolveBaseUrl({
apiKey: "key-1",
providerSpecificData: { baseUrl: "https://custom.example/v1/chat/completions" },
});
assert.equal(result, "https://custom.example/v1/chat/completions");
});
test("resolveBaseUrl: returns fallback when credentials have no baseUrl", () => {
const executor = new TestExecutor();
const result = executor.publicResolveBaseUrl(
{ apiKey: "key-1" },
"https://fallback.example/v1/chat/completions"
);
assert.equal(result, "https://fallback.example/v1/chat/completions");
});
test("resolveBaseUrl: returns config.baseUrl when no credentials and no fallback", () => {
const executor = new TestExecutor({
baseUrl: "https://config.example/v1/chat/completions",
});
const result = executor.publicResolveBaseUrl(null);
assert.equal(result, "https://config.example/v1/chat/completions");
});
test("resolveBaseUrl: returns empty string when nothing is configured", () => {
const executor = new TestExecutor();
const result = executor.publicResolveBaseUrl(null);
assert.equal(result, "");
});
test("resolveBaseUrl: credentials.baseUrl takes precedence over fallback", () => {
const executor = new TestExecutor();
const result = executor.publicResolveBaseUrl(
{
apiKey: "key-1",
providerSpecificData: { baseUrl: "https://cred.example/v1" },
},
"https://fallback.example/v1"
);
assert.equal(result, "https://cred.example/v1");
});
test("resolveBaseUrl: credentials.baseUrl takes precedence over config.baseUrl", () => {
const executor = new TestExecutor({
baseUrl: "https://config.example/v1",
});
const result = executor.publicResolveBaseUrl({
apiKey: "key-1",
providerSpecificData: { baseUrl: "https://cred.example/v1" },
});
assert.equal(result, "https://cred.example/v1");
});
test("resolveBaseUrl: fallback takes precedence over config.baseUrl", () => {
const executor = new TestExecutor({
baseUrl: "https://config.example/v1",
});
const result = executor.publicResolveBaseUrl({ apiKey: "key-1" }, "https://fallback.example/v1");
assert.equal(result, "https://fallback.example/v1");
});

View File

@@ -0,0 +1,72 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { stripTrailingSlashes, normalizeBaseUrl } from "../../open-sse/utils/urlSanitize.ts";
// ── stripTrailingSlashes ────────────────────────────────────────────────────
describe("stripTrailingSlashes", () => {
it("returns unchanged string when there is no trailing slash", () => {
assert.equal(stripTrailingSlashes("hello"), "hello");
});
it("strips a single trailing slash", () => {
assert.equal(stripTrailingSlashes("hello/"), "hello");
});
it("strips multiple trailing slashes", () => {
assert.equal(stripTrailingSlashes("hello///"), "hello");
});
it("returns empty string for empty input", () => {
assert.equal(stripTrailingSlashes(""), "");
});
it("returns empty string when input is only a slash", () => {
assert.equal(stripTrailingSlashes("/"), "");
});
it("strips one trailing slash from a full URL with path", () => {
assert.equal(stripTrailingSlashes("https://api.example.com/v1/"), "https://api.example.com/v1");
});
it("leaves a URL without trailing slash unchanged", () => {
assert.equal(stripTrailingSlashes("https://api.example.com"), "https://api.example.com");
});
it("strips multiple trailing slashes from a URL", () => {
assert.equal(stripTrailingSlashes("https://api.example.com///"), "https://api.example.com");
});
});
// ── normalizeBaseUrl ────────────────────────────────────────────────────────
describe("normalizeBaseUrl", () => {
it("returns empty string for null", () => {
assert.equal(normalizeBaseUrl(null), "");
});
it("returns empty string for undefined", () => {
assert.equal(normalizeBaseUrl(undefined), "");
});
it("returns empty string for a number", () => {
assert.equal(normalizeBaseUrl(123 as unknown as string), "");
});
it("trims whitespace and strips trailing slash", () => {
assert.equal(normalizeBaseUrl(" https://api.example.com/v1/ "), "https://api.example.com/v1");
});
it("leaves a clean URL unchanged", () => {
assert.equal(normalizeBaseUrl("https://api.example.com/v1"), "https://api.example.com/v1");
});
it("strips trailing slash from a URL", () => {
assert.equal(normalizeBaseUrl("https://api.example.com/"), "https://api.example.com");
});
it("returns empty string for empty input", () => {
assert.equal(normalizeBaseUrl(""), "");
});
});

View File

@@ -80,5 +80,5 @@ test("execute(): empty apiKey still returns a 400 error response", async () => {
} as never);
assert.equal(result.response.status, 400);
const body = await result.response.json();
assert.equal(body.error.code, "HTTP_400");
assert.equal(body.error.code, "bad_request");
});