feat(providers): add GitLab Duo, NLP Cloud, and enterprise gateways

Expand the provider catalog with GitLab Duo PAT and OAuth support,
NLP Cloud, and new OpenAI-compatible gateways including Azure AI
Foundry, Bedrock, DataRobot, watsonx, OCI, SAP, Modal, Reka,
Clarifai, and Chutes.

Add specialized GitLab and NLP Cloud executors, provider-specific
URL normalization and validation flows, managed catalog/model
discovery updates, and dashboard metadata for the new providers.

Extend search support with You.com, including request building,
response normalization, validation coverage, and route/schema
registration.
This commit is contained in:
diegosouzapw
2026-04-24 07:00:21 -03:00
parent e2469b8f6d
commit 3a0bc955be
36 changed files with 5715 additions and 20 deletions

View File

@@ -0,0 +1,51 @@
export const AZURE_AI_DEFAULT_BASE_URL = "https://example-resource.services.ai.azure.com/openai/v1";
function normalizeBaseUrl(value: string | null | undefined): string {
return (value || "").trim().replace(/\/+$/, "");
}
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;
if (
normalized.endsWith("/chat/completions") ||
normalized.endsWith("/responses") ||
normalized.endsWith("/models")
) {
return normalized.replace(/\/(?:chat\/completions|responses|models)$/i, "");
}
if (normalized.endsWith("/openai/v1") || normalized.endsWith("/v1")) {
return normalized;
}
if (normalized.endsWith("/openai")) {
return `${normalized}/v1`;
}
const parsed = new URL(normalized);
if (
parsed.hostname.endsWith(".services.ai.azure.com") ||
parsed.hostname.endsWith(".openai.azure.com")
) {
if (!parsed.pathname || parsed.pathname === "/") {
parsed.pathname = "/openai/v1";
return parsed.toString().replace(/\/+$/, "");
}
}
return normalized;
}
export function buildAzureAiChatUrl(
value: string | null | undefined,
apiType: "chat" | "responses" = "chat"
): string {
const normalized = normalizeAzureAiBaseUrl(value);
return `${normalized}/${apiType === "responses" ? "responses" : "chat/completions"}`;
}
export function buildAzureAiModelsUrl(value: string | null | undefined): string {
return `${normalizeAzureAiBaseUrl(value)}/models`;
}

View File

@@ -0,0 +1,82 @@
export const BEDROCK_DEFAULT_BASE_URL = "https://bedrock-mantle.us-east-1.api.aws/v1";
function normalizeBaseUrl(value: string | null | undefined): string {
return (value || "").trim().replace(/\/+$/, "");
}
function isBedrockRuntimeHost(hostname: string): boolean {
return hostname.startsWith("bedrock-runtime.") && hostname.endsWith(".amazonaws.com");
}
function isBedrockMantleHost(hostname: string): boolean {
return hostname.startsWith("bedrock-mantle.") && hostname.endsWith(".api.aws");
}
export function isBedrockRuntimeBaseUrl(value: string | null | undefined): boolean {
try {
const parsed = new URL(normalizeBaseUrl(value || BEDROCK_DEFAULT_BASE_URL));
return isBedrockRuntimeHost(parsed.hostname);
} catch {
return false;
}
}
export function isBedrockMantleBaseUrl(value: string | null | undefined): boolean {
try {
const parsed = new URL(normalizeBaseUrl(value || BEDROCK_DEFAULT_BASE_URL));
return isBedrockMantleHost(parsed.hostname);
} catch {
return false;
}
}
export function normalizeBedrockBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || BEDROCK_DEFAULT_BASE_URL);
if (!normalized) return BEDROCK_DEFAULT_BASE_URL;
const stripped = normalized.replace(/\/(?:chat\/completions|responses|models)$/i, "");
try {
const parsed = new URL(stripped);
const pathname = parsed.pathname.replace(/\/+$/, "");
if (isBedrockMantleHost(parsed.hostname)) {
if (!pathname || pathname === "/" || pathname === "/openai" || pathname === "/openai/v1") {
parsed.pathname = "/v1";
} else if (!pathname.endsWith("/v1")) {
parsed.pathname = pathname;
}
} else if (isBedrockRuntimeHost(parsed.hostname)) {
if (!pathname || pathname === "/" || pathname === "/openai" || pathname === "/v1") {
parsed.pathname = "/openai/v1";
} else if (!pathname.endsWith("/openai/v1")) {
parsed.pathname = pathname;
}
} else if (pathname.endsWith("/openai")) {
parsed.pathname = `${pathname}/v1`;
} else if (!pathname) {
parsed.pathname = "/v1";
}
parsed.search = "";
parsed.hash = "";
return parsed.toString().replace(/\/+$/, "");
} catch {
if (stripped.endsWith("/openai")) {
return `${stripped}/v1`;
}
return stripped;
}
}
export function buildBedrockChatUrl(value: string | null | undefined): string {
return `${normalizeBedrockBaseUrl(value)}/chat/completions`;
}
export function buildBedrockModelsUrl(value: string | null | undefined): string {
return `${normalizeBedrockBaseUrl(value)}/models`;
}
export function getBedrockValidationModelId(value: string | null | undefined): string {
return isBedrockRuntimeBaseUrl(value) ? "openai.gpt-oss-120b-1:0" : "openai.gpt-oss-120b";
}

View File

@@ -0,0 +1,71 @@
const DATAROBOT_API_V2_SEGMENT = "/api/v2";
const DATAROBOT_LLMGW_CHAT_PATH = "/genai/llmgw/chat/completions/";
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 (value || "").trim().replace(/\/+$/, "");
}
export function normalizeDataRobotBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || DATAROBOT_DEFAULT_BASE_URL);
return normalized || DATAROBOT_DEFAULT_BASE_URL;
}
export function isDataRobotDeploymentUrl(value: string | null | undefined): boolean {
const normalized = normalizeDataRobotBaseUrl(value);
return /\/api\/v2\/deployments\/[^/]+(?:\/chat\/completions)?$/i.test(normalized);
}
export function buildDataRobotChatUrl(value: string | null | undefined): string {
const normalized = normalizeDataRobotBaseUrl(value);
if (normalized.endsWith("/chat/completions")) {
return normalized;
}
if (/\/api\/v2\/deployments\/[^/]+$/i.test(normalized)) {
return `${normalized}/chat/completions`;
}
if (/\/api\/v2\/genai\/llmgw$/i.test(normalized)) {
return `${normalized}/chat/completions/`;
}
if (/\/api\/v2\/genai\/llmgw\/chat$/i.test(normalized)) {
return `${normalized}/completions/`;
}
if (normalized.includes(DATAROBOT_API_V2_SEGMENT)) {
return `${normalized}${DATAROBOT_LLMGW_CHAT_PATH}`;
}
return `${normalized}${DATAROBOT_API_V2_SEGMENT}${DATAROBOT_LLMGW_CHAT_PATH}`;
}
export function buildDataRobotCatalogUrl(value: string | null | undefined): string | null {
const normalized = normalizeDataRobotBaseUrl(value);
if (isDataRobotDeploymentUrl(normalized)) {
return null;
}
const parsed = new URL(normalized);
let basePath = parsed.pathname.replace(/\/+$/, "");
if (/\/api\/v2\/genai\/llmgw\/chat\/completions$/i.test(basePath)) {
basePath = basePath.replace(/\/chat\/completions$/i, "");
} else if (/\/api\/v2\/genai\/llmgw$/i.test(basePath)) {
// Keep path as-is.
} else if (basePath.includes(DATAROBOT_API_V2_SEGMENT)) {
basePath = basePath.replace(/\/api\/v2.*$/i, "");
}
const catalogPath = `${basePath}${DATAROBOT_LLMGW_CATALOG_PATH}`.replace(/\/{2,}/g, "/");
parsed.pathname = catalogPath;
parsed.search = "";
parsed.hash = "";
return parsed.toString();
}

46
open-sse/config/oci.ts Normal file
View File

@@ -0,0 +1,46 @@
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 (value || "").trim().replace(/\/+$/, "");
}
export function normalizeOciBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || OCI_DEFAULT_BASE_URL);
if (!normalized) return OCI_DEFAULT_BASE_URL;
const stripped = normalized.replace(/\/(?:chat\/completions|responses|models)$/i, "");
if (stripped.endsWith("/openai/v1")) {
return stripped;
}
if (stripped.endsWith("/openai")) {
return `${stripped}/v1`;
}
try {
const parsed = new URL(stripped);
if (!parsed.pathname || parsed.pathname === "/") {
parsed.pathname = "/openai/v1";
} else if (parsed.pathname.endsWith("/openai")) {
parsed.pathname = `${parsed.pathname}/v1`;
}
parsed.search = "";
parsed.hash = "";
return parsed.toString().replace(/\/+$/, "");
} catch {
return stripped;
}
}
export function buildOciChatUrl(
value: string | null | undefined,
apiType: "chat" | "responses" = "chat"
): string {
return `${normalizeOciBaseUrl(value)}/${apiType === "responses" ? "responses" : "chat/completions"}`;
}
export function buildOciModelsUrl(value: string | null | undefined): string {
return `${normalizeOciBaseUrl(value)}/models`;
}

View File

@@ -24,6 +24,12 @@ import {
GLM_SHARED_HEADERS,
GLM_SHARED_MODELS,
} from "./glmProvider.ts";
import { DATAROBOT_DEFAULT_BASE_URL } from "./datarobot.ts";
import { AZURE_AI_DEFAULT_BASE_URL } from "./azureAi.ts";
import { BEDROCK_DEFAULT_BASE_URL } from "./bedrock.ts";
import { WATSONX_DEFAULT_BASE_URL } from "./watsonx.ts";
import { OCI_DEFAULT_BASE_URL } from "./oci.ts";
import { SAP_DEFAULT_BASE_URL } from "./sap.ts";
import {
CURSOR_REGISTRY_VERSION,
getAntigravityProviderHeaders,
@@ -138,6 +144,9 @@ const KIMI_CODING_SHARED = {
] as RegistryModel[],
} as const;
const GITLAB_DUO_BASE_URL =
process.env.GITLAB_DUO_BASE_URL || process.env.GITLAB_BASE_URL || "https://gitlab.com";
const buildModels = (ids: readonly string[]): RegistryModel[] =>
ids.map((id) => ({ id, name: id }));
@@ -190,6 +199,14 @@ const CHAT_OPENAI_COMPAT_MODELS: Record<string, RegistryModel[]> = {
cablyai: buildModels(["gpt-4o", "gpt-4o-mini", "deepseek-chat"]),
thebai: buildModels(["gpt-4o", "claude-3.5-sonnet", "llama-3.3-70b"]),
fenayai: buildModels(["gpt-4o", "claude-3.5-sonnet", "deepseek-chat"]),
gitlab: [{ id: "gitlab-duo-code-suggestions", name: "GitLab Duo Code Suggestions" }],
"gitlab-duo": [{ id: "gitlab-duo-code-suggestions", name: "GitLab Duo Code Suggestions" }],
chutes: buildModels([
"Qwen/Qwen3-32B-TEE",
"deepseek-ai/DeepSeek-V3.2-TEE",
"openai/gpt-oss-120b-TEE",
"moonshotai/Kimi-K2.6-TEE",
]),
moonshot: buildModels(["kimi-k2.5", "kimi-latest", "moonshot-v1-auto"]),
"meta-llama": buildModels([
"Llama-3.3-70B-Instruct",
@@ -209,6 +226,47 @@ const CHAT_OPENAI_COMPAT_MODELS: Record<string, RegistryModel[]> = {
"databricks-claude-sonnet-4",
"databricks-gemini-2-5-pro",
]),
datarobot: [
{ id: "azure/gpt-5-mini-2025-08-07", name: "Azure GPT-5 Mini" },
{ id: "azure/gpt-4o-mini", name: "Azure GPT-4o Mini" },
],
clarifai: [
{ id: "openai/chat-completion/models/gpt-oss-120b", name: "GPT-OSS 120B" },
{ id: "openai/chat-completion/models/gpt-4o", name: "GPT-4o" },
{ id: "openai/chat-completion/models/o4-mini", name: "o4-mini" },
{ id: "anthropic/completion/models/claude-sonnet-4", name: "Claude Sonnet 4" },
{
id: "deepseek-ai/deepseek-chat/models/DeepSeek-R1-0528-Qwen3-8B",
name: "DeepSeek R1 Qwen3 8B",
},
{ id: "gcp/generate/models/gemini-2_5-flash", name: "Gemini 2.5 Flash" },
],
watsonx: buildModels([
"ibm/granite-3-3-8b-instruct",
"meta-llama/llama-3-3-70b-instruct",
"openai/gpt-4o",
]),
oci: buildModels([
"openai.gpt-oss-20b",
"openai.gpt-oss-120b",
"google.gemini-2.5-pro",
"xai.grok-4",
]),
sap: buildModels(["gpt-4o", "gpt-5-mini", "mistralai--mistral-medium-instruct"]),
modal: buildModels([
"Qwen/Qwen3-4B-Thinking-2507-FP8",
"google/gemma-4-26B-A4B-it",
"gpt-oss-20B",
]),
reka: buildModels(["reka-core", "reka-flash", "reka-edge-2603"]),
nlpcloud: buildModels([
"gpt-oss-120b",
"llama-3-1-405b",
"finetuned-llama-3-70b",
"chatdolphin",
"dolphin-yi-34b",
"dolphin-mixtral-8x7b",
]),
snowflake: buildModels(["llama3.1-70b", "llama3.3-70b", "deepseek-r1", "claude-3-5-sonnet"]),
wandb: buildModels([
"openai/gpt-oss-120b",
@@ -647,6 +705,42 @@ export const REGISTRY: Record<string, RegistryEntry> = {
passthroughModels: true,
},
"azure-ai": {
id: "azure-ai",
alias: "azure-ai",
format: "openai",
executor: "default",
baseUrl: AZURE_AI_DEFAULT_BASE_URL,
authType: "apikey",
authHeader: "api-key",
models: [
{ id: "claude-opus-4-6", name: "Claude Opus 4.6" },
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
{ id: "deepseek-v3.2", name: "DeepSeek V3.2" },
{ id: "grok-4", name: "Grok 4" },
{ id: "kimi-k2.5", name: "Kimi K2.5" },
],
passthroughModels: true,
},
bedrock: {
id: "bedrock",
alias: "bedrock",
format: "openai",
executor: "default",
baseUrl: BEDROCK_DEFAULT_BASE_URL,
authType: "apikey",
authHeader: "bearer",
models: buildModels([
"openai.gpt-oss-20b",
"openai.gpt-oss-120b",
"openai.gpt-oss-safeguard-20b",
"openai.gpt-oss-safeguard-120b",
"mistral.mistral-large-3-675b-instruct",
]),
passthroughModels: true,
},
anthropic: {
id: "anthropic",
alias: "anthropic",
@@ -1960,6 +2054,50 @@ export const REGISTRY: Record<string, RegistryEntry> = {
passthroughModels: true,
},
gitlab: {
id: "gitlab",
alias: "gitlab",
format: "openai",
executor: "gitlab",
baseUrl: "https://gitlab.com/api/v4/code_suggestions/completions",
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS.gitlab,
},
"gitlab-duo": {
id: "gitlab-duo",
alias: "gitlab-duo",
format: "openai",
executor: "gitlab-duo",
baseUrl: `${GITLAB_DUO_BASE_URL.replace(/\/$/, "")}/api/v4/code_suggestions/completions`,
authType: "oauth",
authHeader: "bearer",
oauth: {
clientIdEnv: "GITLAB_DUO_OAUTH_CLIENT_ID",
clientIdDefault: process.env.GITLAB_OAUTH_CLIENT_ID || "",
clientSecretEnv: "GITLAB_DUO_OAUTH_CLIENT_SECRET",
clientSecretDefault: process.env.GITLAB_OAUTH_CLIENT_SECRET || "",
tokenUrl: `${GITLAB_DUO_BASE_URL.replace(/\/$/, "")}/oauth/token`,
refreshUrl: `${GITLAB_DUO_BASE_URL.replace(/\/$/, "")}/oauth/token`,
authUrl: `${GITLAB_DUO_BASE_URL.replace(/\/$/, "")}/oauth/authorize`,
},
models: CHAT_OPENAI_COMPAT_MODELS["gitlab-duo"],
},
chutes: {
id: "chutes",
alias: "chutes",
format: "openai",
executor: "default",
baseUrl: "https://llm.chutes.ai/v1/chat/completions",
modelsUrl: "https://llm.chutes.ai/v1/models",
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS.chutes,
passthroughModels: true,
},
moonshot: {
id: "moonshot",
alias: "moonshot",
@@ -2070,6 +2208,103 @@ export const REGISTRY: Record<string, RegistryEntry> = {
models: CHAT_OPENAI_COMPAT_MODELS.databricks,
},
datarobot: {
id: "datarobot",
alias: "datarobot",
format: "openai",
executor: "default",
baseUrl: DATAROBOT_DEFAULT_BASE_URL,
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS.datarobot,
passthroughModels: true,
},
clarifai: {
id: "clarifai",
alias: "clarifai",
format: "openai",
executor: "default",
baseUrl: "https://api.clarifai.com/v2/ext/openai/v1/chat/completions",
modelsUrl: "https://api.clarifai.com/v2/ext/openai/v1/models",
authType: "apikey",
authHeader: "Authorization",
authPrefix: "Key ",
models: CHAT_OPENAI_COMPAT_MODELS.clarifai,
passthroughModels: true,
},
watsonx: {
id: "watsonx",
alias: "watsonx",
format: "openai",
executor: "default",
baseUrl: WATSONX_DEFAULT_BASE_URL,
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS.watsonx,
passthroughModels: true,
},
oci: {
id: "oci",
alias: "oci",
format: "openai",
executor: "default",
baseUrl: OCI_DEFAULT_BASE_URL,
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS.oci,
passthroughModels: true,
},
sap: {
id: "sap",
alias: "sap",
format: "openai",
executor: "default",
baseUrl: SAP_DEFAULT_BASE_URL,
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS.sap,
passthroughModels: true,
},
modal: {
id: "modal",
alias: "modal",
format: "openai",
executor: "default",
baseUrl: "https://example-user--example-app.modal.run/v1",
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS.modal,
passthroughModels: true,
},
reka: {
id: "reka",
alias: "reka",
format: "openai",
executor: "default",
baseUrl: "https://api.reka.ai/v1",
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS.reka,
passthroughModels: true,
},
nlpcloud: {
id: "nlpcloud",
alias: "nlpc",
format: "openai",
executor: "nlpcloud",
baseUrl: "https://api.nlpcloud.io/v1/gpu",
authType: "apikey",
authHeader: "token",
models: CHAT_OPENAI_COMPAT_MODELS.nlpcloud,
},
snowflake: {
id: "snowflake",
alias: "snowflake",

60
open-sse/config/sap.ts Normal file
View File

@@ -0,0 +1,60 @@
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 (value || "").trim().replace(/\/+$/, "");
}
function sanitizeUrl(value: string): string {
try {
const parsed = new URL(value);
parsed.search = "";
parsed.hash = "";
return parsed.toString().replace(/\/+$/, "");
} catch {
return value;
}
}
export function normalizeSapBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || SAP_DEFAULT_BASE_URL);
if (!normalized) return SAP_DEFAULT_BASE_URL;
return sanitizeUrl(normalized.replace(/\/chat\/completions$/i, ""));
}
export function isSapDeploymentUrl(value: string | null | undefined): boolean {
const normalized = normalizeSapBaseUrl(value);
return /\/v2\/lm\/deployments\/[^/]+$/i.test(normalized);
}
export function buildSapChatUrl(value: string | null | undefined): string {
const normalized = normalizeSapBaseUrl(value);
if (normalized.endsWith("/chat/completions")) return normalized;
return `${normalized}/chat/completions`;
}
export function buildSapModelsUrl(value: string | null | undefined): string {
const normalized = normalizeSapBaseUrl(value);
const root = normalized.replace(/\/v2\/lm\/deployments\/[^/]+$/i, "");
return `${root}/v2/lm/scenarios/foundation-models/models`;
}
export function getSapResourceGroup(
providerSpecificData: Record<string, unknown> | null | undefined,
fallback = "default"
): string {
const candidates = [
providerSpecificData?.resourceGroup,
providerSpecificData?.aiResourceGroup,
providerSpecificData?.resource_group,
];
for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim();
}
}
return fallback;
}

View File

@@ -155,6 +155,22 @@ export const SEARCH_PROVIDERS: Record<string, SearchProviderConfig> = {
cacheTTLMs: 5 * 60 * 1000,
},
"youcom-search": {
id: "youcom-search",
name: "You.com Search",
baseUrl: "https://ydc-index.io/v1/search",
method: "GET",
authType: "apikey",
authHeader: "x-api-key",
costPerQuery: 0.005,
freeMonthlyQuota: 0,
searchTypes: ["web", "news"],
defaultMaxResults: 5,
maxMaxResults: 100,
timeoutMs: 10_000,
cacheTTLMs: 5 * 60 * 1000,
},
"searxng-search": {
id: "searxng-search",
name: "SearXNG Search",

View File

@@ -0,0 +1,45 @@
export const WATSONX_DEFAULT_BASE_URL = "https://ca-tor.ml.cloud.ibm.com/ml/gateway/v1";
function normalizeBaseUrl(value: string | null | undefined): string {
return (value || "").trim().replace(/\/+$/, "");
}
export function normalizeWatsonxBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || WATSONX_DEFAULT_BASE_URL);
if (!normalized) return WATSONX_DEFAULT_BASE_URL;
const stripped = normalized.replace(
/\/(?:chat\/completions|completions|embeddings|models)$/i,
""
);
if (stripped.endsWith("/ml/gateway/v1")) {
return stripped;
}
if (stripped.endsWith("/ml/gateway")) {
return `${stripped}/v1`;
}
try {
const parsed = new URL(stripped);
if (!parsed.pathname || parsed.pathname === "/") {
parsed.pathname = "/ml/gateway/v1";
} else if (parsed.pathname.endsWith("/ml/gateway")) {
parsed.pathname = `${parsed.pathname}/v1`;
}
parsed.search = "";
parsed.hash = "";
return parsed.toString().replace(/\/+$/, "");
} catch {
return stripped;
}
}
export function buildWatsonxChatUrl(value: string | null | undefined): string {
return `${normalizeWatsonxBaseUrl(value)}/chat/completions`;
}
export function buildWatsonxModelsUrl(value: string | null | undefined): string {
return `${normalizeWatsonxBaseUrl(value)}/models`;
}

View File

@@ -11,6 +11,12 @@ import { getGigachatAccessToken } from "../services/gigachatAuth.ts";
import { applyProviderRequestDefaults } from "../services/providerRequestDefaults.ts";
import { getOpenAICompatibleType, isClaudeCodeCompatible } from "../services/provider.ts";
import { sanitizeQwenThinkingToolChoice } from "../services/qwenThinking.ts";
import { buildDataRobotChatUrl } from "../config/datarobot.ts";
import { buildAzureAiChatUrl } from "../config/azureAi.ts";
import { buildBedrockChatUrl } from "../config/bedrock.ts";
import { buildWatsonxChatUrl } from "../config/watsonx.ts";
import { buildOciChatUrl } from "../config/oci.ts";
import { buildSapChatUrl, getSapResourceGroup } from "../config/sap.ts";
function normalizeBaseUrl(baseUrl) {
return (baseUrl || "").trim().replace(/\/$/, "");
@@ -34,6 +40,26 @@ function normalizeDatabricksChatUrl(baseUrl) {
return `${normalized}/chat/completions`;
}
function normalizeDataRobotChatUrl(baseUrl) {
return buildDataRobotChatUrl(baseUrl);
}
function normalizeAzureAiChatUrl(baseUrl, apiType = "chat") {
return buildAzureAiChatUrl(baseUrl, apiType);
}
function normalizeWatsonxChatUrl(baseUrl) {
return buildWatsonxChatUrl(baseUrl);
}
function normalizeOciChatUrl(baseUrl, apiType = "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`;
@@ -110,6 +136,34 @@ export class DefaultExecutor extends BaseExecutor {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
return normalizeDatabricksChatUrl(baseUrl);
}
case "datarobot": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
return normalizeDataRobotChatUrl(baseUrl);
}
case "azure-ai": {
const apiType =
credentials?.providerSpecificData?.apiType === "responses" ? "responses" : "chat";
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
return normalizeAzureAiChatUrl(baseUrl, apiType);
}
case "bedrock": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
return buildBedrockChatUrl(baseUrl);
}
case "watsonx": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
return normalizeWatsonxChatUrl(baseUrl);
}
case "oci": {
const apiType =
credentials?.providerSpecificData?.apiType === "responses" ? "responses" : "chat";
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
return normalizeOciChatUrl(baseUrl, apiType);
}
case "sap": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
return normalizeSapChatUrl(baseUrl);
}
case "xiaomi-mimo": {
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
return normalizeXiaomiMimoChatUrl(baseUrl);
@@ -123,6 +177,8 @@ export class DefaultExecutor extends BaseExecutor {
return normalizeGigachatChatUrl(baseUrl);
}
case "lm-studio":
case "modal":
case "reka":
case "vllm":
case "llamafile":
case "triton":
@@ -180,6 +236,49 @@ export class DefaultExecutor extends BaseExecutor {
case "gigachat":
headers["Authorization"] = `Bearer ${credentials.accessToken || effectiveKey}`;
break;
case "clarifai": {
const clarifaiToken = effectiveKey || credentials.accessToken;
if (clarifaiToken) {
headers["Authorization"] = `Key ${clarifaiToken}`;
}
break;
}
case "azure-ai":
if (effectiveKey || credentials.accessToken) {
headers["api-key"] = effectiveKey || credentials.accessToken;
}
delete headers["Authorization"];
break;
case "oci": {
const bearerToken = effectiveKey || credentials.accessToken;
if (bearerToken) {
headers["Authorization"] = `Bearer ${bearerToken}`;
}
const projectId =
credentials.projectId ||
credentials?.providerSpecificData?.projectId ||
credentials?.providerSpecificData?.project;
if (projectId) {
headers["OpenAI-Project"] = projectId;
}
break;
}
case "sap": {
const bearerToken = effectiveKey || credentials.accessToken;
if (bearerToken) {
headers["Authorization"] = `Bearer ${bearerToken}`;
}
headers["AI-Resource-Group"] = getSapResourceGroup(credentials?.providerSpecificData);
break;
}
case "reka": {
const bearerToken = effectiveKey || credentials.accessToken;
if (bearerToken) {
headers["Authorization"] = `Bearer ${bearerToken}`;
headers["X-Api-Key"] = bearerToken;
}
break;
}
case "claude":
case "anthropic":
effectiveKey

View File

@@ -0,0 +1,695 @@
import { randomUUID } from "node:crypto";
import {
BaseExecutor,
mergeAbortSignals,
mergeUpstreamExtraHeaders,
type ExecuteInput,
type ExecutorLog,
type ProviderCredentials,
} from "./base.ts";
import { FETCH_TIMEOUT_MS } from "../config/constants.ts";
import { getAccessToken } from "../services/tokenRefresh.ts";
import {
buildGitLabDirectGatewayUrl,
buildGitLabOAuthEndpoints,
getCachedGitLabDirectAccess,
isGitLabDirectAccessDisabled,
parseGitLabDirectAccessDetails,
resolveGitLabOAuthBaseUrl,
type GitLabDirectAccessDetails,
} from "@/lib/oauth/gitlab";
type OpenAIMessage = {
role?: string;
content?: unknown;
};
type JsonRecord = Record<string, unknown>;
type GitLabRequestTarget = {
mode: "monolith" | "direct";
url: string;
headers: Record<string, string>;
};
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function extractTextContent(content: unknown): string {
if (typeof content === "string") {
return content.trim();
}
if (!Array.isArray(content)) {
return "";
}
return content
.map((part) => {
if (!part || typeof part !== "object") return "";
const item = part as Record<string, unknown>;
if (item.type === "text" && typeof item.text === "string") {
return item.text;
}
if (item.type === "input_text" && typeof item.text === "string") {
return item.text;
}
return "";
})
.filter((text) => text.trim().length > 0)
.join("\n")
.trim();
}
function buildPrompt(messages: OpenAIMessage[] | undefined): string {
if (!Array.isArray(messages)) return "";
const systemParts: string[] = [];
const userParts: string[] = [];
for (const message of messages) {
const role = String(message?.role || "user").toLowerCase();
const text = extractTextContent(message?.content);
if (!text) continue;
if (role === "system" || role === "developer") {
systemParts.push(text);
continue;
}
if (role === "user") {
userParts.push(text);
}
}
const latestUserPrompt = userParts.at(-1) || "";
if (!systemParts.length) {
return latestUserPrompt;
}
return `System instructions:\n${systemParts.join("\n\n")}\n\n${latestUserPrompt}`.trim();
}
function toOpenAIError(status: number, message: string): Response {
return new Response(
JSON.stringify({
error: {
message,
type:
status === 401 || status === 403
? "authentication_error"
: status === 429
? "rate_limit_error"
: "api_error",
},
}),
{
status,
headers: { "Content-Type": "application/json" },
}
);
}
function buildSseChunk(data: unknown): string {
return `data: ${JSON.stringify(data)}\n\n`;
}
function buildStreamingResponse(
content: string,
model: string,
id: string,
created: number
): Response {
const encoder = new TextEncoder();
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})
)
);
if (content) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { content }, finish_reason: null }],
})
)
);
}
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
})
)
);
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
},
});
return new Response(body, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}
function buildJsonCompletion(
content: string,
model: string,
id: string,
created: number
): Response {
const estimated = Math.max(1, Math.ceil(content.length / 4));
return new Response(
JSON.stringify({
id,
object: "chat.completion",
created,
model,
choices: [
{
index: 0,
message: { role: "assistant", content },
finish_reason: "stop",
},
],
usage: {
prompt_tokens: estimated,
completion_tokens: estimated,
total_tokens: estimated * 2,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
function mergeCredentials(
current: ProviderCredentials,
patch: Partial<ProviderCredentials> | null | undefined
): ProviderCredentials {
if (!patch) return current;
return {
...current,
...patch,
providerSpecificData: {
...(current.providerSpecificData || {}),
...(patch.providerSpecificData || {}),
},
};
}
function resolveGitLabRoot(credentials: ExecuteInput["credentials"]): string {
return resolveGitLabOAuthBaseUrl(credentials?.providerSpecificData);
}
function resolveResponseModel(payload: JsonRecord, fallbackModel: string): string {
const modelField = payload.model;
if (typeof modelField === "string" && modelField.trim().length > 0) {
return modelField.trim();
}
const modelRecord = asRecord(modelField);
const modelName =
typeof modelRecord.name === "string" && modelRecord.name.trim().length > 0
? modelRecord.name.trim()
: typeof modelRecord.id === "string" && modelRecord.id.trim().length > 0
? modelRecord.id.trim()
: null;
if (modelName) {
return modelName;
}
const metadata = asRecord(payload.metadata);
const metadataModelDetails = asRecord(metadata.model_details);
const payloadModelDetails = asRecord(payload.model_details);
const nestedCandidates = [metadataModelDetails, payloadModelDetails];
for (const candidate of nestedCandidates) {
const value =
typeof candidate.model_name === "string" && candidate.model_name.trim().length > 0
? candidate.model_name.trim()
: typeof candidate.name === "string" && candidate.name.trim().length > 0
? candidate.name.trim()
: null;
if (value) {
return value;
}
}
return fallbackModel;
}
function buildMonolithHeaders(token: string | null): Record<string, string> {
return {
"Content-Type": "application/json",
Accept: "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
};
}
function buildDirectHeaders(directAccess: GitLabDirectAccessDetails): Record<string, string> {
return {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${directAccess.token}`,
...directAccess.headers,
};
}
function isGitLabDuoOAuthProvider(providerId: string): boolean {
return providerId === "gitlab-duo";
}
async function persistGitLabDirectAccessCache(
input: ExecuteInput,
credentials: ProviderCredentials,
root: string,
directAccess: GitLabDirectAccessDetails
) {
if (!input.onCredentialsRefreshed) return;
await input.onCredentialsRefreshed({
providerSpecificData: {
...(credentials.providerSpecificData || {}),
baseUrl: root,
gitlabDirectAccess: {
token: directAccess.token,
baseUrl: directAccess.baseUrl,
expiresAt: directAccess.expiresAt,
headers: directAccess.headers,
},
},
});
}
export class GitlabExecutor extends BaseExecutor {
constructor(providerId = "gitlab") {
super(providerId, {
id: providerId,
baseUrl: "https://gitlab.com/api/v4/code_suggestions/completions",
headers: { "Content-Type": "application/json" },
});
}
buildUrl(
_model: string,
_stream: boolean,
_urlIndex = 0,
credentials: ExecuteInput["credentials"] | null = null
): string {
const endpoints = buildGitLabOAuthEndpoints(resolveGitLabRoot(credentials || {}));
return endpoints.publicCompletionsUrl;
}
buildHeaders(credentials: ExecuteInput["credentials"], _stream = false): Record<string, string> {
const token = credentials?.apiKey || credentials?.accessToken || null;
return buildMonolithHeaders(token);
}
transformRequest(
_model: string,
body: Record<string, unknown>,
_stream: boolean,
credentials: ExecuteInput["credentials"]
): Record<string, unknown> {
const prompt = buildPrompt(body.messages as OpenAIMessage[] | undefined);
const providerData =
credentials?.providerSpecificData && typeof credentials.providerSpecificData === "object"
? credentials.providerSpecificData
: {};
const projectPath =
typeof providerData.projectPath === "string" && providerData.projectPath.trim().length > 0
? providerData.projectPath.trim()
: undefined;
const fileName =
typeof providerData.fileName === "string" && providerData.fileName.trim().length > 0
? providerData.fileName.trim()
: "snippet.txt";
return {
current_file: {
file_name: fileName,
content_above_cursor: prompt,
content_below_cursor: "",
},
intent: "generation",
generation_type: "small_file",
stream: false,
...(projectPath ? { project_path: projectPath } : {}),
...(prompt ? { user_instruction: prompt } : {}),
};
}
async refreshCredentials(credentials: ProviderCredentials, log: ExecutorLog | null) {
if (!isGitLabDuoOAuthProvider(this.provider) || !credentials.refreshToken) {
return null;
}
try {
return await getAccessToken(this.provider, credentials, log);
} catch (error) {
log?.error?.(
"TOKEN",
`GitLab Duo refresh error: ${error instanceof Error ? error.message : String(error)}`
);
return null;
}
}
needsRefresh(credentials?: ProviderCredentials | null) {
if (
isGitLabDuoOAuthProvider(this.provider) &&
!credentials?.accessToken &&
credentials?.refreshToken
) {
return true;
}
return super.needsRefresh(credentials);
}
private async fetchGitLabDirectAccess(
root: string,
accessToken: string,
signal: AbortSignal | null | undefined
): Promise<{
directAccess: GitLabDirectAccessDetails | null;
response: Response | null;
bodyText: string;
}> {
const endpoints = buildGitLabOAuthEndpoints(root);
const timeoutSignal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
const combinedSignal = signal ? mergeAbortSignals(signal, timeoutSignal) : timeoutSignal;
const response = await fetch(endpoints.directAccessUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
},
signal: combinedSignal,
});
const bodyText = await response.text();
if (!response.ok) {
return { directAccess: null, response, bodyText };
}
const parsed = bodyText ? JSON.parse(bodyText) : {};
return {
directAccess: parseGitLabDirectAccessDetails(parsed),
response,
bodyText,
};
}
private async resolveRequestTarget(
input: ExecuteInput,
credentials: ProviderCredentials
): Promise<{
target: GitLabRequestTarget | null;
credentials: ProviderCredentials;
errorResponse: Response | null;
}> {
const root = resolveGitLabRoot(credentials);
const endpoints = buildGitLabOAuthEndpoints(root);
if (!isGitLabDuoOAuthProvider(this.provider)) {
return {
target: {
mode: "monolith",
url: endpoints.publicCompletionsUrl,
headers: buildMonolithHeaders(credentials.apiKey || credentials.accessToken || null),
},
credentials,
errorResponse: null,
};
}
if (!credentials.accessToken) {
return {
target: null,
credentials,
errorResponse: toOpenAIError(401, "GitLab Duo OAuth connection is missing an access token"),
};
}
const cachedDirectAccess = getCachedGitLabDirectAccess(credentials.providerSpecificData);
if (cachedDirectAccess) {
return {
target: {
mode: "direct",
url: buildGitLabDirectGatewayUrl(cachedDirectAccess.baseUrl),
headers: buildDirectHeaders(cachedDirectAccess),
},
credentials,
errorResponse: null,
};
}
try {
const { directAccess, response, bodyText } = await this.fetchGitLabDirectAccess(
root,
credentials.accessToken,
input.signal
);
if (directAccess) {
await persistGitLabDirectAccessCache(input, credentials, root, directAccess);
const mergedCredentials = mergeCredentials(credentials, {
providerSpecificData: {
...(credentials.providerSpecificData || {}),
baseUrl: root,
gitlabDirectAccess: {
token: directAccess.token,
baseUrl: directAccess.baseUrl,
expiresAt: directAccess.expiresAt,
headers: directAccess.headers,
},
},
});
return {
target: {
mode: "direct",
url: buildGitLabDirectGatewayUrl(directAccess.baseUrl),
headers: buildDirectHeaders(directAccess),
},
credentials: mergedCredentials,
errorResponse: null,
};
}
if (!response) {
return {
target: {
mode: "monolith",
url: endpoints.publicCompletionsUrl,
headers: buildMonolithHeaders(credentials.accessToken),
},
credentials,
errorResponse: null,
};
}
if (response.status === 401) {
return {
target: null,
credentials,
errorResponse: toOpenAIError(401, "GitLab Duo direct access token request was rejected"),
};
}
if (response.status === 403 && !isGitLabDirectAccessDisabled(response.status, bodyText)) {
return {
target: null,
credentials,
errorResponse: toOpenAIError(403, "GitLab Duo direct access scope is unavailable"),
};
}
return {
target: {
mode: "monolith",
url: endpoints.publicCompletionsUrl,
headers: buildMonolithHeaders(credentials.accessToken),
},
credentials,
errorResponse: null,
};
} catch (error) {
return {
target: {
mode: "monolith",
url: endpoints.publicCompletionsUrl,
headers: buildMonolithHeaders(credentials.accessToken),
},
credentials,
errorResponse: null,
};
}
}
private async performRequest(
input: ExecuteInput,
target: GitLabRequestTarget,
transformedBody: Record<string, unknown>
) {
const headers = { ...target.headers };
mergeUpstreamExtraHeaders(headers, input.upstreamExtraHeaders);
const timeoutSignal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
const combinedSignal = input.signal
? mergeAbortSignals(input.signal, timeoutSignal)
: timeoutSignal;
const response = await fetch(target.url, {
method: "POST",
headers,
body: JSON.stringify(transformedBody),
signal: combinedSignal,
});
return { response, headers };
}
async execute(input: ExecuteInput) {
const prompt = buildPrompt(
(input.body as Record<string, unknown>)?.messages as OpenAIMessage[]
);
if (!prompt) {
return {
response: toOpenAIError(400, "GitLab Duo requires at least one user message"),
};
}
let activeCredentials = input.credentials;
if (this.needsRefresh(activeCredentials)) {
const refreshed = await this.refreshCredentials(activeCredentials, input.log || null);
if (refreshed) {
activeCredentials = mergeCredentials(activeCredentials, refreshed);
await input.onCredentialsRefreshed?.({
...refreshed,
providerSpecificData: {
...(input.credentials.providerSpecificData || {}),
...(refreshed.providerSpecificData || {}),
},
});
}
}
const transformedBody = this.transformRequest(
input.model,
(input.body as Record<string, unknown>) || {},
false,
activeCredentials
);
const {
target,
credentials: resolvedCredentials,
errorResponse,
} = await this.resolveRequestTarget(input, activeCredentials);
if (errorResponse || !target) {
return {
response: errorResponse || toOpenAIError(500, "GitLab Duo target resolution failed"),
};
}
activeCredentials = resolvedCredentials;
let upstream: Response;
let requestHeaders: Record<string, string>;
let activeTarget = target;
try {
const requestResult = await this.performRequest(input, target, transformedBody);
upstream = requestResult.response;
requestHeaders = requestResult.headers;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
response: toOpenAIError(502, `GitLab Duo connection failed: ${message}`),
url: target.url,
headers: target.headers,
transformedBody,
};
}
if (!upstream.ok && target.mode === "direct") {
const fallbackTarget: GitLabRequestTarget = {
mode: "monolith",
url: buildGitLabOAuthEndpoints(resolveGitLabRoot(activeCredentials)).publicCompletionsUrl,
headers: buildMonolithHeaders(activeCredentials.accessToken || null),
};
try {
const fallbackResult = await this.performRequest(input, fallbackTarget, transformedBody);
upstream = fallbackResult.response;
requestHeaders = fallbackResult.headers;
activeTarget = fallbackTarget;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
response: toOpenAIError(502, `GitLab Duo connection failed: ${message}`),
url: fallbackTarget.url,
headers: fallbackTarget.headers,
transformedBody,
};
}
}
if (!upstream.ok) {
const text = await upstream.text();
const message =
upstream.status === 401 || upstream.status === 403
? `GitLab Duo auth failed: ${upstream.status}`
: upstream.status === 429
? "GitLab Duo rate limited the request"
: text || `GitLab Duo request failed: ${upstream.status}`;
return {
response: toOpenAIError(upstream.status, message),
url: activeTarget.url,
headers: requestHeaders,
transformedBody,
};
}
const payload = (await upstream.json()) as JsonRecord;
const choices = Array.isArray(payload.choices) ? payload.choices : [];
const firstChoice =
choices[0] && typeof choices[0] === "object" ? (choices[0] as JsonRecord) : {};
const content =
typeof firstChoice.text === "string"
? firstChoice.text
: typeof payload.content === "string"
? payload.content
: "";
const resolvedModel = resolveResponseModel(payload, input.model);
const responseId = `chatcmpl-gitlab-${randomUUID()}`;
const created = Math.floor(Date.now() / 1000);
const response = input.stream
? buildStreamingResponse(content, resolvedModel, responseId, created)
: buildJsonCompletion(content, resolvedModel, responseId, created);
return { response, url: activeTarget.url, headers: requestHeaders, transformedBody };
}
}
export default GitlabExecutor;

View File

@@ -17,6 +17,8 @@ import { GrokWebExecutor } from "./grok-web.ts";
import { BlackboxWebExecutor } from "./blackbox-web.ts";
import { MuseSparkWebExecutor } from "./muse-spark-web.ts";
import { AzureOpenAIExecutor } from "./azure-openai.ts";
import { GitlabExecutor } from "./gitlab.ts";
import { NlpCloudExecutor } from "./nlpcloud.ts";
const executors = {
antigravity: new AntigravityExecutor(),
@@ -29,6 +31,9 @@ const executors = {
cursor: new CursorExecutor(),
cu: new CursorExecutor(), // Alias for cursor
"azure-openai": new AzureOpenAIExecutor(),
gitlab: new GitlabExecutor(),
"gitlab-duo": new GitlabExecutor("gitlab-duo"),
nlpcloud: new NlpCloudExecutor(),
pollinations: new PollinationsExecutor(),
pol: new PollinationsExecutor(), // Alias
"cloudflare-ai": new CloudflareAIExecutor(),
@@ -82,3 +87,5 @@ export { GrokWebExecutor } from "./grok-web.ts";
export { BlackboxWebExecutor } from "./blackbox-web.ts";
export { MuseSparkWebExecutor } from "./muse-spark-web.ts";
export { AzureOpenAIExecutor } from "./azure-openai.ts";
export { GitlabExecutor } from "./gitlab.ts";
export { NlpCloudExecutor } from "./nlpcloud.ts";

View File

@@ -0,0 +1,546 @@
import { randomUUID } from "node:crypto";
import {
BaseExecutor,
mergeUpstreamExtraHeaders,
type ExecuteInput,
type ProviderCredentials,
} from "./base.ts";
import { PROVIDERS } from "../config/constants.ts";
type JsonRecord = Record<string, unknown>;
type OpenAIMessage = {
role?: string;
content?: unknown;
};
type NlpCloudHistoryEntry = {
input: string;
response: string;
};
const DEFAULT_MODEL = "chatdolphin";
const DEFAULT_BASE_URL = "https://api.nlpcloud.io/v1/gpu";
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function extractTextContent(content: unknown): string {
if (typeof content === "string") {
return content.trim();
}
if (!Array.isArray(content)) {
return "";
}
return content
.map((part) => {
if (!part || typeof part !== "object") return "";
const item = part as Record<string, unknown>;
if (item.type === "text" && typeof item.text === "string") {
return item.text;
}
if (item.type === "input_text" && typeof item.text === "string") {
return item.text;
}
return "";
})
.filter((text) => text.trim().length > 0)
.join("\n")
.trim();
}
function normalizeBaseUrl(baseUrl: string | null | undefined): string {
const normalized = String(baseUrl || DEFAULT_BASE_URL)
.trim()
.replace(/\/+$/, "");
if (normalized.endsWith("/chatbot")) {
return normalized.replace(/\/[^/]+\/chatbot$/, "");
}
if (normalized.endsWith("/v1/gpu")) {
return normalized;
}
if (normalized.endsWith("/v1")) {
return `${normalized}/gpu`;
}
return normalized;
}
function resolvePrompt(messages: OpenAIMessage[] | undefined): {
input: string;
context: string | null;
history: NlpCloudHistoryEntry[];
} {
if (!Array.isArray(messages)) {
return { input: "", context: null, history: [] };
}
const systemParts: string[] = [];
const chatParts: Array<{ role: string; text: string }> = [];
for (const message of messages) {
const role = String(message?.role || "user").toLowerCase();
const text = extractTextContent(message?.content);
if (!text) continue;
if (role === "system" || role === "developer") {
systemParts.push(text);
continue;
}
if (role === "user" || role === "assistant") {
chatParts.push({ role, text });
}
}
const lastUserIndex = [...chatParts].map((part) => part.role).lastIndexOf("user");
if (lastUserIndex === -1) {
return {
input: "",
context: systemParts.length > 0 ? systemParts.join("\n\n") : null,
history: [],
};
}
const history: NlpCloudHistoryEntry[] = [];
let pendingUser: string | null = null;
for (let index = 0; index < lastUserIndex; index += 1) {
const part = chatParts[index];
if (part.role === "user") {
pendingUser = part.text;
continue;
}
if (part.role === "assistant" && pendingUser) {
history.push({ input: pendingUser, response: part.text });
pendingUser = null;
}
}
return {
input: chatParts[lastUserIndex]?.text || "",
context: systemParts.length > 0 ? systemParts.join("\n\n") : null,
history,
};
}
function estimateTokens(text: string): number {
return Math.max(1, Math.ceil(text.length / 4));
}
function buildSseChunk(data: unknown): string {
return `data: ${JSON.stringify(data)}\n\n`;
}
function buildOpenAiJsonCompletion(
content: string,
model: string,
id: string,
created: number
): Response {
const completionTokens = estimateTokens(content);
return new Response(
JSON.stringify({
id,
object: "chat.completion",
created,
model,
choices: [
{
index: 0,
message: { role: "assistant", content },
finish_reason: "stop",
},
],
usage: {
prompt_tokens: completionTokens,
completion_tokens: completionTokens,
total_tokens: completionTokens * 2,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
function buildSynthesizedStream(
content: string,
model: string,
id: string,
created: number
): Response {
const encoder = new TextEncoder();
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})
)
);
if (content) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { content }, finish_reason: null }],
})
)
);
}
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
})
)
);
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
},
});
return new Response(body, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}
function extractStreamDelta(data: string): string {
const trimmed = data.trim();
if (!trimmed || trimmed === "[DONE]") return "";
if (trimmed.startsWith("{")) {
try {
const payload = asRecord(JSON.parse(trimmed));
const response =
typeof payload.response === "string"
? payload.response
: typeof payload.content === "string"
? payload.content
: typeof payload.token === "string"
? payload.token
: "";
return response.trim();
} catch {
return trimmed;
}
}
return trimmed;
}
function toOpenAiError(status: number, message: string): Response {
return new Response(
JSON.stringify({
error: {
message,
type:
status === 401 || status === 403
? "authentication_error"
: status === 429
? "rate_limit_error"
: "api_error",
},
}),
{
status,
headers: { "Content-Type": "application/json" },
}
);
}
export class NlpCloudExecutor extends BaseExecutor {
constructor() {
super("nlpcloud", PROVIDERS.nlpcloud || { format: "openai" });
}
buildUrl(
model: string,
_stream: boolean,
_urlIndex = 0,
credentials: ProviderCredentials | null = null
): string {
const baseUrl = normalizeBaseUrl(
typeof credentials?.providerSpecificData?.baseUrl === "string"
? credentials.providerSpecificData.baseUrl
: this.config.baseUrl
);
return `${baseUrl}/${encodeURIComponent(model || DEFAULT_MODEL)}/chatbot`;
}
buildHeaders(credentials: ProviderCredentials | null, stream = true): Record<string, string> {
const key = credentials?.apiKey || credentials?.accessToken;
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (key) {
headers.Authorization = `Token ${key}`;
}
headers.Accept = stream ? "text/event-stream" : "application/json";
return headers;
}
private buildRequestPayload(model: string, body: unknown, stream: boolean): JsonRecord | null {
const payload = asRecord(body);
const messages = Array.isArray(payload.messages) ? (payload.messages as OpenAIMessage[]) : [];
const prompt = resolvePrompt(messages);
if (!prompt.input) {
return null;
}
return {
input: prompt.input,
...(prompt.context ? { context: prompt.context } : {}),
...(prompt.history.length > 0 ? { history: prompt.history } : {}),
...(stream ? { stream: true } : {}),
...(typeof payload.temperature === "number" ? { temperature: payload.temperature } : {}),
...(typeof payload.top_p === "number" ? { top_p: payload.top_p } : {}),
model,
};
}
private async transformEventStreamToSse(response: Response, model: string): Promise<Response> {
const upstream = response.body;
if (!upstream) {
return buildSynthesizedStream(
"",
model,
`chatcmpl-nlpcloud-${randomUUID()}`,
Math.floor(Date.now() / 1000)
);
}
const decoder = new TextDecoder();
const encoder = new TextEncoder();
const id = `chatcmpl-nlpcloud-${randomUUID()}`;
const created = Math.floor(Date.now() / 1000);
const body = new ReadableStream<Uint8Array>({
async start(controller) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})
)
);
const reader = upstream.getReader();
let buffer = "";
let finished = false;
const emitDelta = (content: string) => {
if (!content) return;
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { content }, finish_reason: null }],
})
)
);
};
const finish = () => {
if (finished) return;
finished = true;
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
})
)
);
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
};
const processEvent = (eventText: string) => {
const normalized = eventText.replace(/\r/g, "");
const lines = normalized.split("\n");
const dataParts: string[] = [];
for (const line of lines) {
if (!line) continue;
if (line.startsWith("data:")) {
dataParts.push(line.slice(5).trimStart());
continue;
}
if (line.startsWith("event:") || line.startsWith("id:") || line.startsWith(":")) {
continue;
}
dataParts.push(line.trim());
}
const raw = dataParts.join("\n").trim();
if (!raw) return;
if (raw === "[DONE]") {
finish();
return;
}
emitDelta(extractStreamDelta(raw));
};
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
buffer = buffer.replace(/\r\n/g, "\n");
let separatorIndex = buffer.indexOf("\n\n");
while (separatorIndex !== -1) {
const eventText = buffer.slice(0, separatorIndex);
buffer = buffer.slice(separatorIndex + 2);
processEvent(eventText);
separatorIndex = buffer.indexOf("\n\n");
}
}
const tail = buffer.trim();
if (tail) {
processEvent(tail);
}
finish();
} catch (error) {
controller.error(error);
} finally {
reader.releaseLock();
}
},
});
return new Response(body, {
status: response.status,
headers: { "Content-Type": "text/event-stream" },
});
}
async execute({ model, body, stream, credentials, signal, upstreamExtraHeaders }: ExecuteInput) {
const resolvedModel = model || DEFAULT_MODEL;
const payload = this.buildRequestPayload(resolvedModel, body, stream);
const url = this.buildUrl(resolvedModel, stream, 0, credentials);
const headers = this.buildHeaders(credentials, stream);
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
if (!payload) {
return {
response: toOpenAiError(400, "NLP Cloud requests require at least one user message."),
url,
headers,
transformedBody: body,
};
}
try {
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(payload),
signal,
});
if (!response.ok) {
const errorText = await response.text();
return {
response: toOpenAiError(
response.status,
`NLP Cloud API failed with status ${response.status}: ${errorText || "Unknown error"}`
),
url,
headers,
transformedBody: payload,
};
}
if (stream) {
const contentType = response.headers.get("Content-Type") || "";
if (contentType.includes("application/json")) {
const json = asRecord(await response.json());
const content = typeof json.response === "string" ? json.response : "";
return {
response: buildSynthesizedStream(
content,
resolvedModel,
`chatcmpl-nlpcloud-${randomUUID()}`,
Math.floor(Date.now() / 1000)
),
url,
headers,
transformedBody: payload,
};
}
return {
response: await this.transformEventStreamToSse(response, resolvedModel),
url,
headers,
transformedBody: payload,
};
}
const json = asRecord(await response.json());
const content = typeof json.response === "string" ? json.response : "";
return {
response: buildOpenAiJsonCompletion(
content,
resolvedModel,
`chatcmpl-nlpcloud-${randomUUID()}`,
Math.floor(Date.now() / 1000)
),
url,
headers,
transformedBody: payload,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error || "Unknown error");
return {
response: toOpenAiError(502, `NLP Cloud fetch error: ${message}`),
url,
headers,
transformedBody: payload,
};
}
}
}
export default NlpCloudExecutor;

View File

@@ -3,9 +3,9 @@ import { randomUUID } from "crypto";
* Search Handler
*
* Handles POST /v1/search requests.
* Routes to 9 search providers with automatic failover:
* Routes to 10 search providers with automatic failover:
* serper-search, brave-search, perplexity-search, exa-search, tavily-search,
* google-pse-search, linkup-search, searchapi-search, searxng-search
* google-pse-search, linkup-search, searchapi-search, youcom-search, searxng-search
*
* Request format:
* {
@@ -271,6 +271,12 @@ interface SearchRequestParams {
timeRange?: string;
offset?: number;
domainFilter?: string[];
contentOptions?: {
snippet?: boolean;
full_page?: boolean;
format?: string;
max_characters?: number;
};
providerOptions?: Record<string, unknown>;
providerSpecificData?: Record<string, unknown>;
}
@@ -498,6 +504,52 @@ function buildSearchApiRequest(
};
}
function buildYouComRequest(
config: SearchProviderConfig,
params: SearchRequestParams
): { url: string; init: RequestInit } {
const apiKey = params.token;
if (!apiKey) {
throw new Error("You.com Search requires an API key");
}
const { includes, excludes } = parseDomainFilter(params.domainFilter);
const qp = new URLSearchParams({
query: params.query,
count: String(Math.min(params.maxResults, 100)),
});
if (params.timeRange && params.timeRange !== "any") {
qp.set("freshness", params.timeRange);
}
if (typeof params.offset === "number" && params.offset > 0 && params.maxResults > 0) {
qp.set("offset", String(Math.min(Math.floor(params.offset / params.maxResults), 9)));
}
if (params.country) qp.set("country", params.country);
if (params.language) qp.set("language", params.language);
if (includes.length) qp.set("include_domains", includes.join(","));
if (excludes.length) qp.set("exclude_domains", excludes.join(","));
if (params.contentOptions?.full_page) {
qp.set("livecrawl", params.searchType === "news" ? "news" : "web");
qp.append(
"livecrawl_formats",
params.contentOptions.format === "markdown" ? "markdown" : "html"
);
}
return {
url: `${resolveSearchBaseUrl(config, params)}?${qp}`,
init: {
method: "GET",
headers: {
Accept: "application/json",
"X-API-Key": apiKey,
},
},
};
}
function buildSearxngRequest(
config: SearchProviderConfig,
params: SearchRequestParams
@@ -537,6 +589,7 @@ function buildRequest(
if (config.id === "google-pse-search") return buildGooglePseRequest(config, params);
if (config.id === "linkup-search") return buildLinkupRequest(config, params);
if (config.id === "searchapi-search") return buildSearchApiRequest(config, params);
if (config.id === "youcom-search") return buildYouComRequest(config, params);
if (config.id === "searxng-search") return buildSearxngRequest(config, params);
// Fallback for future providers: POST with bearer auth
return {
@@ -744,6 +797,56 @@ function normalizeSearchApiResponse(
};
}
function normalizeYouComResponse(
data: any,
_query: string,
searchType: string
): { results: SearchResult[]; totalResults: number | null } {
const now = new Date().toISOString();
const resultsContainer =
data?.results && typeof data.results === "object" ? data.results : undefined;
const section =
searchType === "news" ? resultsContainer?.news || [] : resultsContainer?.web || [];
const items = Array.isArray(section) ? section : [];
const results = items.map((item: any, idx: number) => {
const firstSnippet = Array.isArray(item.snippets)
? item.snippets.find((value: unknown) => typeof value === "string")
: null;
const livecrawlText =
typeof item.markdown === "string"
? item.markdown
: typeof item.html === "string"
? item.html
: undefined;
const livecrawlFormat = typeof item.markdown === "string" ? "markdown" : "html";
return makeResult(
"youcom-search",
{
title: item.title,
url: item.url,
snippet:
typeof firstSnippet === "string"
? firstSnippet
: typeof item.description === "string"
? item.description
: "",
published_at: item.page_age,
favicon_url: item.favicon_url,
image_url: item.thumbnail_url,
source_type: searchType,
full_text: livecrawlText,
text_format: livecrawlText ? livecrawlFormat : undefined,
},
idx,
now
);
});
return { results, totalResults: results.length };
}
function normalizeSearxngResponse(
data: any,
_query: string,
@@ -789,6 +892,7 @@ function normalizeResponse(
return normalizeGooglePseResponse(data, query, searchType);
if (providerId === "linkup-search") return normalizeLinkupResponse(data, query, searchType);
if (providerId === "searchapi-search") return normalizeSearchApiResponse(data, query, searchType);
if (providerId === "youcom-search") return normalizeYouComResponse(data, query, searchType);
if (providerId === "searxng-search") return normalizeSearxngResponse(data, query, searchType);
return { results: [], totalResults: null };
}
@@ -806,6 +910,7 @@ export async function handleSearch(options: SearchHandlerOptions): Promise<Searc
timeRange,
offset,
domainFilter,
contentOptions,
providerOptions,
credentials,
alternateProvider,
@@ -842,6 +947,7 @@ export async function handleSearch(options: SearchHandlerOptions): Promise<Searc
timeRange,
offset,
domainFilter,
contentOptions,
providerOptions,
};

View File

@@ -24,6 +24,26 @@ import { getAntigravityModelsDiscoveryUrls } from "@omniroute/open-sse/config/an
import { getGlmModelsUrl } from "@omniroute/open-sse/config/glmProvider.ts";
import { getImageProvider } from "@omniroute/open-sse/config/imageRegistry.ts";
import { resolveAntigravityVersion } from "@omniroute/open-sse/services/antigravityVersion.ts";
import {
AZURE_AI_DEFAULT_BASE_URL,
buildAzureAiModelsUrl,
} from "@omniroute/open-sse/config/azureAi.ts";
import { normalizeBedrockBaseUrl } from "@omniroute/open-sse/config/bedrock.ts";
import {
DATAROBOT_DEFAULT_BASE_URL,
buildDataRobotCatalogUrl,
isDataRobotDeploymentUrl,
} from "@omniroute/open-sse/config/datarobot.ts";
import { OCI_DEFAULT_BASE_URL, buildOciModelsUrl } from "@omniroute/open-sse/config/oci.ts";
import {
SAP_DEFAULT_BASE_URL,
buildSapModelsUrl,
getSapResourceGroup,
} from "@omniroute/open-sse/config/sap.ts";
import {
WATSONX_DEFAULT_BASE_URL,
buildWatsonxModelsUrl,
} from "@omniroute/open-sse/config/watsonx.ts";
import {
ANTIGRAVITY_PUBLIC_MODELS,
getClientVisibleAntigravityModelName,
@@ -59,6 +79,12 @@ function isLocalOpenAIStyleProvider(provider: string): boolean {
return isSelfHostedChatProvider(provider);
}
const NAMED_OPENAI_STYLE_PROVIDERS = new Set(["bedrock", "modal", "reka"]);
function isNamedOpenAIStyleProvider(provider: string): boolean {
return NAMED_OPENAI_STYLE_PROVIDERS.has(provider);
}
function buildOptionalBearerHeaders(token: string | null | undefined): Record<string, string> {
return {
"Content-Type": "application/json",
@@ -66,6 +92,19 @@ function buildOptionalBearerHeaders(token: string | null | undefined): Record<st
};
}
function buildNamedOpenAiStyleHeaders(
provider: string,
token: string | null | undefined
): Record<string, string> {
const headers = buildOptionalBearerHeaders(token);
if (provider === "reka" && token) {
headers["X-Api-Key"] = token;
}
return headers;
}
function normalizeAntigravityModelsResponse(data: unknown): Array<{ id: string; name: string }> {
const payload = asRecord(data).models;
@@ -118,6 +157,76 @@ function mapAntigravityModelForClient(model: { id: string; name: string }): {
};
}
function normalizeDataRobotCatalogResponse(data: unknown): Array<{ id: string; name: string }> {
const items = Array.isArray(asRecord(data).data) ? (asRecord(data).data as unknown[]) : [];
return items
.map((value) => {
const item = asRecord(value);
const model =
toNonEmptyString(item.model) || toNonEmptyString(item.id) || toNonEmptyString(item.name);
if (!model) return null;
if (item.isActive === false) return null;
const name = toNonEmptyString(item.label) || toNonEmptyString(item.displayName) || model;
return { id: model, name };
})
.filter((value): value is { id: string; name: string } => Boolean(value));
}
function normalizeOpenAiLikeModelsResponse(
data: unknown,
fallbackOwner: string
): Array<{ id: string; name: string; owned_by: string }> {
const payload = asRecord(data);
const items = Array.isArray(data)
? data
: Array.isArray(payload.data)
? (payload.data as unknown[])
: Array.isArray(payload.models)
? (payload.models as unknown[])
: [];
return items
.map((value) => {
const item = asRecord(value);
const id =
toNonEmptyString(item.id) || toNonEmptyString(item.model) || toNonEmptyString(item.name);
if (!id) return null;
const name =
toNonEmptyString(item.display_name) ||
toNonEmptyString(item.displayName) ||
toNonEmptyString(item.name) ||
id;
const ownedBy =
toNonEmptyString(item.owned_by) || toNonEmptyString(item.provider) || fallbackOwner;
return { id, name, owned_by: ownedBy };
})
.filter((value): value is { id: string; name: string; owned_by: string } => Boolean(value));
}
function normalizeSapModelsResponse(
data: unknown
): Array<{ id: string; name: string; owned_by: string }> {
const payload = asRecord(data);
const items = Array.isArray(payload.resources) ? (payload.resources as unknown[]) : [];
return items
.map((value) => {
const item = asRecord(value);
const id =
toNonEmptyString(item.model) || toNonEmptyString(item.id) || toNonEmptyString(item.name);
if (!id) return null;
const name =
toNonEmptyString(item.displayName) ||
toNonEmptyString(item.display_name) ||
toNonEmptyString(item.name) ||
id;
const ownedBy = toNonEmptyString(item.provider) || "sap";
return { id, name, owned_by: ownedBy };
})
.filter((value): value is { id: string; name: string; owned_by: string } => Boolean(value));
}
type ProviderModelsConfigEntry = {
url: string;
method: "GET" | "POST";
@@ -181,6 +290,12 @@ const STATIC_MODEL_PROVIDERS: Record<string, () => Array<{ id: string; name: str
{ id: "glm-4.7", name: "GLM 4.7" },
{ id: "kimi-k2.5", name: "Kimi K2.5" },
],
gitlab: () => [{ id: "gitlab-duo-code-suggestions", name: "GitLab Duo Code Suggestions" }],
nlpcloud: () =>
getModelsByProviderId("nlpcloud").map((model) => ({
id: model.id,
name: model.name || model.id,
})),
qoder: () => getStaticQoderModels(),
};
@@ -353,6 +468,22 @@ const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> = {
authPrefix: "Bearer ",
parseResponse: (data) => data.data || data.models || [],
},
chutes: {
url: "https://llm.chutes.ai/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.data || data.models || [],
},
clarifai: {
url: "https://api.clarifai.com/v2/ext/openai/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Key ",
parseResponse: (data) => normalizeOpenAiLikeModelsResponse(data, "clarifai"),
},
kimi: {
url: "https://api.moonshot.ai/v1/models",
method: "GET",
@@ -676,19 +807,26 @@ export async function GET(
});
};
if (isOpenAICompatibleProvider(provider) || isLocalOpenAIStyleProvider(provider)) {
if (
isOpenAICompatibleProvider(provider) ||
isLocalOpenAIStyleProvider(provider) ||
isNamedOpenAIStyleProvider(provider)
) {
const cachedResponse = maybeReturnCachedDiscovery();
if (cachedResponse) return cachedResponse;
const autoFetchDisabledResponse = maybeReturnAutoFetchDisabled();
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
const registryEntry = isLocalOpenAIStyleProvider(provider)
? getRegistryEntry(provider)
: null;
const baseUrl =
const registryEntry =
isLocalOpenAIStyleProvider(provider) || isNamedOpenAIStyleProvider(provider)
? getRegistryEntry(provider)
: null;
const rawBaseUrl =
getProviderBaseUrl(connection.providerSpecificData) ||
(typeof registryEntry?.baseUrl === "string" ? registryEntry.baseUrl : null);
const baseUrl =
provider === "bedrock" && rawBaseUrl ? normalizeBedrockBaseUrl(rawBaseUrl) : rawBaseUrl;
if (!baseUrl) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "Base URL unavailable — using cached catalog",
@@ -699,7 +837,9 @@ export async function GET(
{
error: isOpenAICompatibleProvider(provider)
? "No base URL configured for OpenAI compatible provider"
: "No base URL configured for local provider",
: isLocalOpenAIStyleProvider(provider)
? "No base URL configured for local provider"
: "No base URL configured for provider",
},
{ status: 400 }
);
@@ -734,12 +874,16 @@ export async function GET(
guard: getProviderOutboundGuard(),
proxyConfig: proxy,
method: "GET",
headers: buildOptionalBearerHeaders(token),
headers: isNamedOpenAIStyleProvider(provider)
? buildNamedOpenAiStyleHeaders(provider, token)
: buildOptionalBearerHeaders(token),
});
if (response.ok) {
const data = await response.json();
models = data.data || data.models || [];
models = isNamedOpenAIStyleProvider(provider)
? normalizeOpenAiLikeModelsResponse(data, provider)
: data.data || data.models || [];
break; // Success!
}
@@ -790,6 +934,363 @@ export async function GET(
return buildApiDiscoveryResponse(models);
}
if (provider === "datarobot") {
const cachedResponse = maybeReturnCachedDiscovery();
if (cachedResponse) return cachedResponse;
const autoFetchDisabledResponse = maybeReturnAutoFetchDisabled();
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
const token = accessToken || apiKey;
if (!token) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "No token configured — using cached catalog",
localWarning: "No token configured — using local catalog",
});
if (fallback) return fallback;
return NextResponse.json(
{
error:
"No API key configured for this provider. Please add an API key in the provider settings.",
},
{ status: 400 }
);
}
const configuredBaseUrl =
getProviderBaseUrl(connection.providerSpecificData) || DATAROBOT_DEFAULT_BASE_URL;
if (isDataRobotDeploymentUrl(configuredBaseUrl)) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "Deployment URL does not expose catalog — using cached catalog",
localWarning: "Deployment URL does not expose catalog — using local catalog",
});
if (fallback) return fallback;
return buildResponse({
provider,
connectionId,
models: toLocalCatalogModels(),
source: "local_catalog",
warning: "Deployment URL does not expose catalog — using local catalog",
});
}
const catalogUrl = buildDataRobotCatalogUrl(configuredBaseUrl);
if (!catalogUrl) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "Invalid DataRobot base URL — using cached catalog",
localWarning: "Invalid DataRobot base URL — using local catalog",
});
if (fallback) return fallback;
return NextResponse.json({ error: "Invalid DataRobot base URL" }, { status: 400 });
}
let response: Response;
try {
response = await safeOutboundFetch(catalogUrl, {
...SAFE_OUTBOUND_FETCH_PRESETS.modelsDiscovery,
guard: getProviderOutboundGuard(),
proxyConfig: proxy,
method: "GET",
headers: buildOptionalBearerHeaders(token),
});
} catch (error) {
const fallback = buildDiscoveryErrorFallbackResponse(error, {
cacheWarning: "DataRobot catalog unavailable — using cached catalog",
localWarning: "DataRobot catalog unavailable — using local catalog",
});
if (fallback) return fallback;
throw error;
}
if (!response.ok) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: `Catalog probe failed (${response.status}) — using cached catalog`,
localWarning: `Catalog probe failed (${response.status}) — using local catalog`,
});
if (fallback) return fallback;
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const models = normalizeDataRobotCatalogResponse(await response.json());
return buildApiDiscoveryResponse(
models.map((model) => ({
...model,
owned_by: "datarobot",
}))
);
}
if (provider === "azure-ai") {
const cachedResponse = maybeReturnCachedDiscovery();
if (cachedResponse) return cachedResponse;
const autoFetchDisabledResponse = maybeReturnAutoFetchDisabled();
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
const token = accessToken || apiKey;
if (!token) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "No token configured — using cached catalog",
localWarning: "No token configured — using local catalog",
});
if (fallback) return fallback;
return NextResponse.json(
{
error:
"No API key configured for this provider. Please add an API key in the provider settings.",
},
{ status: 400 }
);
}
const baseUrl =
getProviderBaseUrl(connection.providerSpecificData) || AZURE_AI_DEFAULT_BASE_URL;
const modelsUrl = buildAzureAiModelsUrl(baseUrl);
let response: Response;
try {
response = await safeOutboundFetch(modelsUrl, {
...SAFE_OUTBOUND_FETCH_PRESETS.modelsDiscovery,
guard: getProviderOutboundGuard(),
proxyConfig: proxy,
method: "GET",
headers: {
"Content-Type": "application/json",
"api-key": token,
},
});
} catch (error) {
const fallback = buildDiscoveryErrorFallbackResponse(error, {
cacheWarning: "Azure AI models API unavailable — using cached catalog",
localWarning: "Azure AI models API unavailable — using local catalog",
});
if (fallback) return fallback;
throw error;
}
if (!response.ok) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: `Models probe failed (${response.status}) — using cached catalog`,
localWarning: `Models probe failed (${response.status}) — using local catalog`,
});
if (fallback) return fallback;
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const data = await response.json();
const models = (data.data || data.models || []).map((model: Record<string, unknown>) => ({
id:
(typeof model.id === "string" && model.id) ||
(typeof model.name === "string" && model.name) ||
"",
name:
(typeof model.display_name === "string" && model.display_name) ||
(typeof model.name === "string" && model.name) ||
(typeof model.id === "string" && model.id) ||
"",
owned_by: "azure-ai",
}));
return buildApiDiscoveryResponse(models.filter((model) => model.id));
}
if (provider === "watsonx") {
const cachedResponse = maybeReturnCachedDiscovery();
if (cachedResponse) return cachedResponse;
const autoFetchDisabledResponse = maybeReturnAutoFetchDisabled();
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
const token = accessToken || apiKey;
if (!token) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "No token configured — using cached catalog",
localWarning: "No token configured — using local catalog",
});
if (fallback) return fallback;
return NextResponse.json(
{
error:
"No API key configured for this provider. Please add an API key in the provider settings.",
},
{ status: 400 }
);
}
const baseUrl =
getProviderBaseUrl(connection.providerSpecificData) || WATSONX_DEFAULT_BASE_URL;
let response: Response;
try {
response = await safeOutboundFetch(buildWatsonxModelsUrl(baseUrl), {
...SAFE_OUTBOUND_FETCH_PRESETS.modelsDiscovery,
guard: getProviderOutboundGuard(),
proxyConfig: proxy,
method: "GET",
headers: buildOptionalBearerHeaders(token),
});
} catch (error) {
const fallback = buildDiscoveryErrorFallbackResponse(error, {
cacheWarning: "watsonx models API unavailable — using cached catalog",
localWarning: "watsonx models API unavailable — using local catalog",
});
if (fallback) return fallback;
throw error;
}
if (!response.ok) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: `Models probe failed (${response.status}) — using cached catalog`,
localWarning: `Models probe failed (${response.status}) — using local catalog`,
});
if (fallback) return fallback;
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
return buildApiDiscoveryResponse(
normalizeOpenAiLikeModelsResponse(await response.json(), "watsonx")
);
}
if (provider === "oci") {
const cachedResponse = maybeReturnCachedDiscovery();
if (cachedResponse) return cachedResponse;
const autoFetchDisabledResponse = maybeReturnAutoFetchDisabled();
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
const token = accessToken || apiKey;
if (!token) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "No token configured — using cached catalog",
localWarning: "No token configured — using local catalog",
});
if (fallback) return fallback;
return NextResponse.json(
{
error:
"No API key configured for this provider. Please add an API key in the provider settings.",
},
{ status: 400 }
);
}
const psd = asRecord(connection.providerSpecificData);
const baseUrl = getProviderBaseUrl(psd) || OCI_DEFAULT_BASE_URL;
const projectId =
connection.projectId || toNonEmptyString(psd.projectId) || toNonEmptyString(psd.project);
let response: Response;
try {
response = await safeOutboundFetch(buildOciModelsUrl(baseUrl), {
...SAFE_OUTBOUND_FETCH_PRESETS.modelsDiscovery,
guard: getProviderOutboundGuard(),
proxyConfig: proxy,
method: "GET",
headers: {
...buildOptionalBearerHeaders(token),
...(projectId ? { "OpenAI-Project": projectId } : {}),
},
});
} catch (error) {
const fallback = buildDiscoveryErrorFallbackResponse(error, {
cacheWarning: "OCI models API unavailable — using cached catalog",
localWarning: "OCI models API unavailable — using local catalog",
});
if (fallback) return fallback;
throw error;
}
if (!response.ok) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: `Models probe failed (${response.status}) — using cached catalog`,
localWarning: `Models probe failed (${response.status}) — using local catalog`,
});
if (fallback) return fallback;
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
return buildApiDiscoveryResponse(
normalizeOpenAiLikeModelsResponse(await response.json(), "oci")
);
}
if (provider === "sap") {
const cachedResponse = maybeReturnCachedDiscovery();
if (cachedResponse) return cachedResponse;
const autoFetchDisabledResponse = maybeReturnAutoFetchDisabled();
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
const token = accessToken || apiKey;
if (!token) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "No token configured — using cached catalog",
localWarning: "No token configured — using local catalog",
});
if (fallback) return fallback;
return NextResponse.json(
{
error:
"No API key configured for this provider. Please add an API key in the provider settings.",
},
{ status: 400 }
);
}
const psd = asRecord(connection.providerSpecificData);
const baseUrl = getProviderBaseUrl(psd) || SAP_DEFAULT_BASE_URL;
const resourceGroup = getSapResourceGroup(psd);
let response: Response;
try {
response = await safeOutboundFetch(buildSapModelsUrl(baseUrl), {
...SAFE_OUTBOUND_FETCH_PRESETS.modelsDiscovery,
guard: getProviderOutboundGuard(),
proxyConfig: proxy,
method: "GET",
headers: {
...buildOptionalBearerHeaders(token),
"AI-Resource-Group": resourceGroup,
},
});
} catch (error) {
const fallback = buildDiscoveryErrorFallbackResponse(error, {
cacheWarning: "SAP models API unavailable — using cached catalog",
localWarning: "SAP models API unavailable — using local catalog",
});
if (fallback) return fallback;
throw error;
}
if (!response.ok) {
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: `Models probe failed (${response.status}) — using cached catalog`,
localWarning: `Models probe failed (${response.status}) — using local catalog`,
});
if (fallback) return fallback;
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
return buildApiDiscoveryResponse(normalizeSapModelsResponse(await response.json()));
}
if (provider === "claude") {
return buildResponse({
provider,

View File

@@ -16,6 +16,11 @@ import { getAccessToken } from "@omniroute/open-sse/services/tokenRefresh.ts";
import { saveCallLog } from "@/lib/usageDb";
import { logProxyEvent } from "@/lib/proxyLogger";
import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts";
import {
buildGitLabOAuthEndpoints,
isGitLabDirectAccessDisabled,
resolveGitLabOAuthBaseUrl,
} from "@/lib/oauth/gitlab";
// OAuth provider test endpoints
const OAUTH_TEST_CONFIG = {
@@ -52,6 +57,15 @@ const OAUTH_TEST_CONFIG = {
authPrefix: "Bearer ",
extraHeaders: { "User-Agent": "OmniRoute", Accept: "application/vnd.github+json" },
},
"gitlab-duo": {
getUrl: (connection: any) =>
buildGitLabOAuthEndpoints(resolveGitLabOAuthBaseUrl(connection?.providerSpecificData))
.directAccessUrl,
method: "POST",
authHeader: "Authorization",
authPrefix: "Bearer ",
refreshable: true,
},
qwen: {
// DashScope (previously portal.qwen.ai) /v1/models might return 404 or auth issues.
// Use checkExpiry instead — actual connectivity is validated via real requests.
@@ -400,7 +414,8 @@ async function testOAuthConnection(connection: any) {
...config.extraHeaders,
};
const res = await fetch(config.url, {
const url = typeof config.getUrl === "function" ? config.getUrl(connection) : config.url;
const res = await fetch(url, {
method: config.method,
headers,
});
@@ -415,6 +430,19 @@ async function testOAuthConnection(connection: any) {
};
}
if (connection.provider === "gitlab-duo") {
const gitlabText = await res.text();
if (isGitLabDirectAccessDisabled(res.status, gitlabText)) {
return {
valid: true,
error: null,
refreshed,
newTokens,
diagnosis: makeDiagnosis("ok", "upstream", null, null),
};
}
}
// If 401/403 and we haven't tried refresh yet, only attempt refresh
// if the token is actually expired. This prevents corrupting valid tokens
// when the upstream returns transient 401/403 errors (rate-limiting, etc.).
@@ -428,7 +456,7 @@ async function testOAuthConnection(connection: any) {
const tokens = await refreshOAuthToken(connection);
if (tokens) {
// Retry with new token
const retryRes = await fetch(config.url, {
const retryRes = await fetch(url, {
method: config.method,
headers: {
[config.authHeader]: `${config.authPrefix}${tokens.accessToken}`,

View File

@@ -9,6 +9,7 @@ import {
GITHUB_COPILOT_CHAT_USER_AGENT,
GITHUB_COPILOT_EDITOR_VERSION,
} from "@omniroute/open-sse/config/providerHeaderProfiles.ts";
import { buildGitLabOAuthEndpoints, GITLAB_DUO_DEFAULT_BASE_URL } from "../gitlab";
/**
* OAuth Configuration Constants
@@ -188,6 +189,21 @@ export const GITHUB_CONFIG = {
editorPluginVersion: GITHUB_COPILOT_CHAT_PLUGIN_VERSION,
};
const GITLAB_DUO_ENDPOINTS = buildGitLabOAuthEndpoints(GITLAB_DUO_DEFAULT_BASE_URL);
export const GITLAB_DUO_CONFIG = {
baseUrl: GITLAB_DUO_ENDPOINTS.root,
clientId: process.env.GITLAB_DUO_OAUTH_CLIENT_ID || process.env.GITLAB_OAUTH_CLIENT_ID || "",
clientSecret:
process.env.GITLAB_DUO_OAUTH_CLIENT_SECRET || process.env.GITLAB_OAUTH_CLIENT_SECRET || "",
authorizeUrl: GITLAB_DUO_ENDPOINTS.authorizeUrl,
tokenUrl: GITLAB_DUO_ENDPOINTS.tokenUrl,
userInfoUrl: GITLAB_DUO_ENDPOINTS.userUrl,
directAccessUrl: GITLAB_DUO_ENDPOINTS.directAccessUrl,
scope: "ai_features read_user",
codeChallengeMethod: "S256",
};
// Kiro OAuth Configuration
// Supports multiple auth methods:
// 1. AWS Builder ID (Device Code Flow)
@@ -259,6 +275,7 @@ export const PROVIDERS = {
KIMI_CODING: "kimi-coding",
OPENAI: "openai",
GITHUB: "github",
GITLAB_DUO: "gitlab-duo",
KIRO: "kiro",
AMAZON_Q: "amazon-q",
CURSOR: "cursor",

103
src/lib/oauth/gitlab.ts Normal file
View File

@@ -0,0 +1,103 @@
type JsonRecord = Record<string, unknown>;
export type GitLabDirectAccessDetails = {
token: string;
baseUrl: string;
expiresAt: string | null;
headers: Record<string, string>;
};
export const GITLAB_DUO_DEFAULT_BASE_URL =
process.env.GITLAB_DUO_BASE_URL || process.env.GITLAB_BASE_URL || "https://gitlab.com";
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
export function normalizeGitLabBaseUrl(baseUrl?: unknown): string {
const raw = typeof baseUrl === "string" ? baseUrl.trim() : "";
return (raw || GITLAB_DUO_DEFAULT_BASE_URL).replace(/\/$/, "");
}
export function resolveGitLabOAuthBaseUrl(providerSpecificData?: unknown): string {
const data = asRecord(providerSpecificData);
return normalizeGitLabBaseUrl(data.baseUrl);
}
export function buildGitLabOAuthEndpoints(baseUrl?: unknown) {
const root = normalizeGitLabBaseUrl(baseUrl);
return {
root,
authorizeUrl: `${root}/oauth/authorize`,
tokenUrl: `${root}/oauth/token`,
userUrl: `${root}/api/v4/user`,
directAccessUrl: `${root}/api/v4/code_suggestions/direct_access`,
publicCompletionsUrl: `${root}/api/v4/code_suggestions/completions`,
};
}
export function buildGitLabDirectGatewayUrl(baseUrl: string): string {
const normalized = normalizeGitLabBaseUrl(baseUrl);
if (normalized.endsWith("/ai/v2/completions")) {
return normalized;
}
if (normalized.endsWith("/ai/v2")) {
return `${normalized}/completions`;
}
return `${normalized}/ai/v2/completions`;
}
export function parseGitLabDirectAccessDetails(payload: unknown): GitLabDirectAccessDetails | null {
const data = asRecord(payload);
const token = typeof data.token === "string" ? data.token.trim() : "";
const baseUrl = typeof data.base_url === "string" ? data.base_url.trim() : "";
if (!token || !baseUrl) {
return null;
}
const rawHeaders = asRecord(data.headers);
const headers = Object.fromEntries(
Object.entries(rawHeaders).filter(
(entry): entry is [string, string] =>
typeof entry[0] === "string" && typeof entry[1] === "string"
)
);
const expiresAt =
typeof data.expires_at === "number" && Number.isFinite(data.expires_at)
? new Date(data.expires_at * 1000).toISOString()
: null;
return {
token,
baseUrl: normalizeGitLabBaseUrl(baseUrl),
expiresAt,
headers,
};
}
export function getCachedGitLabDirectAccess(
providerSpecificData?: unknown,
minValidityMs = 60_000
): GitLabDirectAccessDetails | null {
const data = asRecord(providerSpecificData);
const cache = data.gitlabDirectAccess ?? data.directAccessCache;
const parsed = parseGitLabDirectAccessDetails(cache);
if (!parsed) {
return null;
}
if (!parsed.expiresAt) {
return parsed;
}
const expiresAtMs = new Date(parsed.expiresAt).getTime();
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now() + minValidityMs) {
return null;
}
return parsed;
}
export function isGitLabDirectAccessDisabled(status: number, bodyText: string): boolean {
return status === 403 && bodyText.toLowerCase().includes("direct connections are disabled");
}

View File

@@ -0,0 +1,122 @@
import { GITLAB_DUO_CONFIG } from "../constants/oauth";
import { buildGitLabOAuthEndpoints, parseGitLabDirectAccessDetails } from "../gitlab";
function getGitLabUserEmail(userInfo: Record<string, unknown>): string | null {
const candidates = [userInfo.email, userInfo.public_email];
for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.trim().length > 0) {
return candidate.trim();
}
}
return null;
}
function getGitLabUserName(userInfo: Record<string, unknown>): string | null {
const candidates = [userInfo.name, userInfo.username, userInfo.email, userInfo.public_email];
for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.trim().length > 0) {
return candidate.trim();
}
}
return null;
}
export const gitlabDuo = {
config: GITLAB_DUO_CONFIG,
flowType: "authorization_code_pkce",
buildAuthUrl: (config, redirectUri, state, codeChallenge) => {
if (!config.clientId) {
throw new Error(
"GitLab Duo OAuth requires GITLAB_DUO_OAUTH_CLIENT_ID (or GITLAB_OAUTH_CLIENT_ID) to be configured."
);
}
const params = new URLSearchParams({
client_id: config.clientId,
redirect_uri: redirectUri,
response_type: "code",
state,
scope: config.scope,
code_challenge: codeChallenge,
code_challenge_method: config.codeChallengeMethod,
});
return `${config.authorizeUrl}?${params.toString()}`;
},
exchangeToken: async (config, code, redirectUri, codeVerifier) => {
const body = new URLSearchParams({
client_id: config.clientId,
code,
grant_type: "authorization_code",
redirect_uri: redirectUri,
code_verifier: codeVerifier,
});
if (config.clientSecret) {
body.set("client_secret", config.clientSecret);
}
const response = await fetch(config.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body,
});
if (!response.ok) {
const error = await response.text();
throw new Error(`GitLab Duo token exchange failed: ${error}`);
}
return await response.json();
},
postExchange: async (tokens) => {
const endpoints = buildGitLabOAuthEndpoints(GITLAB_DUO_CONFIG.baseUrl);
const headers = {
Authorization: `Bearer ${tokens.access_token}`,
Accept: "application/json",
};
const userRes = await fetch(endpoints.userUrl, { headers });
const userInfo = userRes.ok ? ((await userRes.json()) as Record<string, unknown>) : {};
let directAccess = null;
try {
const directRes = await fetch(endpoints.directAccessUrl, {
method: "POST",
headers,
});
if (directRes.ok) {
directAccess = parseGitLabDirectAccessDetails(await directRes.json());
}
} catch {
// Direct access is optional at login time; executor will retry later.
}
return { userInfo, directAccess };
},
mapTokens: (tokens, extra) => {
const userInfo =
extra?.userInfo && typeof extra.userInfo === "object"
? (extra.userInfo as Record<string, unknown>)
: {};
const directAccess = extra?.directAccess ?? null;
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
email: getGitLabUserEmail(userInfo),
name: getGitLabUserName(userInfo),
providerSpecificData: {
baseUrl: GITLAB_DUO_CONFIG.baseUrl,
gitlabUserId: userInfo.id,
gitlabUsername: userInfo.username,
gitlabName: userInfo.name,
...(directAccess ? { gitlabDirectAccess: directAccess } : {}),
},
};
},
};

View File

@@ -18,6 +18,7 @@ import { qoder } from "./qoder";
import { qwen } from "./qwen";
import { kimiCoding } from "./kimi-coding";
import { github } from "./github";
import { gitlabDuo } from "./gitlab-duo";
import { kiro } from "./kiro";
import { cursor } from "./cursor";
import { kilocode } from "./kilocode";
@@ -32,6 +33,7 @@ export const PROVIDERS = {
qwen,
"kimi-coding": kimiCoding,
github,
"gitlab-duo": gitlabDuo,
kiro,
"amazon-q": kiro,
cursor,

View File

@@ -27,6 +27,40 @@ import { getProviderOutboundGuard } from "@/shared/network/outboundUrlGuard";
import { normalizeSessionCookieHeader } from "@/lib/providers/webCookieAuth";
import { getGigachatAccessToken } from "@omniroute/open-sse/services/gigachatAuth.ts";
import { validateQoderCliPat } from "@omniroute/open-sse/services/qoderCli.ts";
import {
AZURE_AI_DEFAULT_BASE_URL,
buildAzureAiChatUrl,
buildAzureAiModelsUrl,
} from "@omniroute/open-sse/config/azureAi.ts";
import {
BEDROCK_DEFAULT_BASE_URL,
buildBedrockModelsUrl,
getBedrockValidationModelId,
normalizeBedrockBaseUrl,
} from "@omniroute/open-sse/config/bedrock.ts";
import {
DATAROBOT_DEFAULT_BASE_URL,
buildDataRobotCatalogUrl,
buildDataRobotChatUrl,
isDataRobotDeploymentUrl,
} from "@omniroute/open-sse/config/datarobot.ts";
import {
OCI_DEFAULT_BASE_URL,
buildOciChatUrl,
buildOciModelsUrl,
} from "@omniroute/open-sse/config/oci.ts";
import {
SAP_DEFAULT_BASE_URL,
buildSapChatUrl,
buildSapModelsUrl,
getSapResourceGroup,
isSapDeploymentUrl,
} from "@omniroute/open-sse/config/sap.ts";
import {
WATSONX_DEFAULT_BASE_URL,
buildWatsonxChatUrl,
buildWatsonxModelsUrl,
} from "@omniroute/open-sse/config/watsonx.ts";
const OPENAI_LIKE_FORMATS = new Set(["openai", "openai-responses"]);
const GEMINI_LIKE_FORMATS = new Set(["gemini", "gemini-cli"]);
@@ -162,6 +196,43 @@ function buildBearerHeaders(apiKey: string, providerSpecificData: any = {}) {
return applyCustomUserAgent(headers, providerSpecificData);
}
function buildRekaHeaders(apiKey: string, providerSpecificData: any = {}) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Bearer ${apiKey}`;
headers["X-Api-Key"] = apiKey;
}
return applyCustomUserAgent(headers, providerSpecificData);
}
function buildClarifaiHeaders(apiKey: string, providerSpecificData: any = {}) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Key ${apiKey}`;
}
return applyCustomUserAgent(headers, providerSpecificData);
}
function buildTokenHeaders(apiKey: string, providerSpecificData: any = {}) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Token ${apiKey}`;
}
return applyCustomUserAgent(headers, providerSpecificData);
}
async function validationRead(url: string, init: RequestInit) {
return safeOutboundFetch(url, {
...SAFE_OUTBOUND_FETCH_PRESETS.validationRead,
@@ -294,6 +365,59 @@ async function validateDirectChatProvider({ url, headers, body, providerSpecific
}
}
async function validateClarifaiProvider({ apiKey, providerSpecificData = {} }: any) {
const baseUrl =
normalizeBaseUrl(providerSpecificData.baseUrl) || "https://api.clarifai.com/v2/ext/openai/v1";
const modelsUrl = addModelsSuffix(baseUrl);
try {
const modelsRes = await validationRead(modelsUrl, {
method: "GET",
headers: buildClarifaiHeaders(apiKey, providerSpecificData),
});
if (modelsRes.ok) {
return { valid: true, error: null, method: "clarifai_models" };
}
if (modelsRes.status === 401 || modelsRes.status === 403) {
return { valid: false, error: "Invalid API key" };
}
const chatUrl = resolveChatUrl("clarifai", baseUrl, providerSpecificData);
const chatRes = await validationWrite(chatUrl, {
method: "POST",
headers: buildClarifaiHeaders(apiKey, providerSpecificData),
body: JSON.stringify({
model:
providerSpecificData?.validationModelId || "openai/chat-completion/models/gpt-oss-120b",
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
});
if (chatRes.ok || chatRes.status === 400 || chatRes.status === 422 || chatRes.status === 429) {
return { valid: true, error: null, method: "clarifai_chat_probe" };
}
if (chatRes.status === 401 || chatRes.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (chatRes.status === 404 || chatRes.status === 405) {
return { valid: false, error: "Provider validation endpoint not supported" };
}
if (chatRes.status >= 500) {
return { valid: false, error: `Provider unavailable (${chatRes.status})` };
}
return { valid: true, error: null, method: "clarifai_chat_probe" };
} catch (error: any) {
return toValidationErrorResult(error);
}
}
async function validateEmbeddingApiProvider({
apiKey,
providerSpecificData = {},
@@ -712,6 +836,61 @@ async function validateDatabricksProvider({ apiKey, providerSpecificData = {} }:
});
}
async function validateDataRobotProvider({ apiKey, providerSpecificData = {} }: any) {
const configuredBaseUrl =
normalizeBaseUrl(providerSpecificData.baseUrl) || DATAROBOT_DEFAULT_BASE_URL;
if (isDataRobotDeploymentUrl(configuredBaseUrl)) {
return validateDirectChatProvider({
url: buildDataRobotChatUrl(configuredBaseUrl),
headers: buildBearerHeaders(apiKey, providerSpecificData),
body: {
model: providerSpecificData.validationModelId || "datarobot-deployed-llm",
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
},
providerSpecificData,
});
}
const catalogUrl = buildDataRobotCatalogUrl(configuredBaseUrl);
if (!catalogUrl) {
return { valid: false, error: "Invalid DataRobot base URL" };
}
try {
const response = await validationRead(catalogUrl, {
method: "GET",
headers: buildBearerHeaders(apiKey, providerSpecificData),
});
if (response.ok) {
return { valid: true, error: null, method: "gateway_catalog" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status === 429) {
return {
valid: true,
error: null,
method: "gateway_catalog",
warning: "Rate limited, but credentials are valid",
};
}
if (response.status >= 400 && response.status < 500) {
return { valid: true, error: null, method: "gateway_catalog" };
}
return { valid: false, error: `Validation failed: ${response.status}` };
} catch (error: any) {
return toValidationErrorResult(error);
}
}
async function validateSnowflakeProvider({ apiKey, providerSpecificData = {} }: any) {
const baseUrl = normalizeBaseUrl(providerSpecificData.baseUrl);
if (!baseUrl) {
@@ -869,6 +1048,496 @@ async function validateAzureOpenAIProvider({ apiKey, providerSpecificData = {} }
return { valid: false, error: `Validation failed: ${response.status}` };
}
async function validateAzureAiProvider({ apiKey, providerSpecificData = {} }: any) {
const rawBaseUrl = normalizeBaseUrl(providerSpecificData.baseUrl) || AZURE_AI_DEFAULT_BASE_URL;
const modelsUrl = buildAzureAiModelsUrl(rawBaseUrl);
const headers = applyCustomUserAgent(
{
"Content-Type": "application/json",
"api-key": apiKey,
},
providerSpecificData
);
try {
const response = await validationRead(modelsUrl, {
method: "GET",
headers,
});
if (response.ok) {
return { valid: true, error: null, method: "azure_ai_models" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status === 429) {
return {
valid: true,
error: null,
method: "azure_ai_models",
warning: "Rate limited, but credentials are valid",
};
}
} catch {
// Fall through to chat probe when /models is unavailable.
}
const validationModelId =
typeof providerSpecificData.validationModelId === "string"
? providerSpecificData.validationModelId.trim()
: "";
if (!validationModelId) {
return {
valid: false,
error: "Endpoint /models unavailable. Provide a Model ID to validate via /chat/completions.",
};
}
const chatUrl = buildAzureAiChatUrl(
rawBaseUrl,
providerSpecificData.apiType === "responses" ? "responses" : "chat"
);
const chatBody =
providerSpecificData.apiType === "responses"
? {
model: validationModelId,
input: "test",
max_output_tokens: 1,
}
: {
model: validationModelId,
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
};
try {
const response = await validationWrite(chatUrl, {
method: "POST",
headers,
body: JSON.stringify(chatBody),
});
if (
response.ok ||
response.status === 400 ||
response.status === 404 ||
response.status === 422 ||
response.status === 429
) {
return { valid: true, error: null, method: "azure_ai_chat_probe" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status >= 500) {
return { valid: false, error: `Provider unavailable (${response.status})` };
}
} catch (error: any) {
return toValidationErrorResult(error);
}
return { valid: false, error: "Connection failed while testing Azure AI Foundry" };
}
async function validateWatsonxProvider({ apiKey, providerSpecificData = {} }: any) {
const rawBaseUrl = normalizeBaseUrl(providerSpecificData.baseUrl) || WATSONX_DEFAULT_BASE_URL;
const headers = applyCustomUserAgent(
{
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
providerSpecificData
);
try {
const response = await validationRead(buildWatsonxModelsUrl(rawBaseUrl), {
method: "GET",
headers,
});
if (response.ok) {
return { valid: true, error: null, method: "watsonx_models" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status === 429) {
return {
valid: true,
error: null,
method: "watsonx_models",
warning: "Rate limited, but credentials are valid",
};
}
} catch {
// Fall through to chat probe when /models is unavailable.
}
const validationModelId =
typeof providerSpecificData.validationModelId === "string" &&
providerSpecificData.validationModelId.trim()
? providerSpecificData.validationModelId.trim()
: "ibm/granite-3-3-8b-instruct";
try {
const response = await validationWrite(buildWatsonxChatUrl(rawBaseUrl), {
method: "POST",
headers,
body: JSON.stringify({
model: validationModelId,
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
});
if (
response.ok ||
response.status === 400 ||
response.status === 404 ||
response.status === 422 ||
response.status === 429
) {
return {
valid: true,
error: null,
method: "watsonx_chat_probe",
...(response.status === 404
? { warning: "watsonx credentials are valid, but the requested model is not enabled." }
: {}),
};
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status >= 500) {
return { valid: false, error: `Provider unavailable (${response.status})` };
}
} catch (error: any) {
return toValidationErrorResult(error);
}
return { valid: false, error: "Connection failed while testing watsonx.ai" };
}
async function validateOciProvider({ apiKey, providerSpecificData = {} }: any) {
const rawBaseUrl = normalizeBaseUrl(providerSpecificData.baseUrl) || OCI_DEFAULT_BASE_URL;
const projectId =
typeof providerSpecificData.projectId === "string" && providerSpecificData.projectId.trim()
? providerSpecificData.projectId.trim()
: typeof providerSpecificData.project === "string" && providerSpecificData.project.trim()
? providerSpecificData.project.trim()
: "";
const headers = applyCustomUserAgent(
{
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
...(projectId ? { "OpenAI-Project": projectId } : {}),
},
providerSpecificData
);
try {
const response = await validationRead(buildOciModelsUrl(rawBaseUrl), {
method: "GET",
headers,
});
if (response.ok) {
return { valid: true, error: null, method: "oci_models" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status === 429) {
return {
valid: true,
error: null,
method: "oci_models",
warning: "Rate limited, but credentials are valid",
};
}
} catch {
// Fall through to chat/responses probe when /models is unavailable.
}
const validationModelId =
typeof providerSpecificData.validationModelId === "string" &&
providerSpecificData.validationModelId.trim()
? providerSpecificData.validationModelId.trim()
: "openai.gpt-oss-20b";
const apiType = providerSpecificData.apiType === "responses" ? "responses" : "chat";
const body =
apiType === "responses"
? {
model: validationModelId,
input: "test",
max_output_tokens: 1,
}
: {
model: validationModelId,
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
};
try {
const response = await validationWrite(buildOciChatUrl(rawBaseUrl, apiType), {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (
response.ok ||
response.status === 400 ||
response.status === 404 ||
response.status === 422 ||
response.status === 429
) {
return {
valid: true,
error: null,
method: apiType === "responses" ? "oci_responses_probe" : "oci_chat_probe",
...(response.status === 404
? { warning: "OCI credentials are valid, but the requested model was not found." }
: {}),
};
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status >= 500) {
return { valid: false, error: `Provider unavailable (${response.status})` };
}
} catch (error: any) {
return toValidationErrorResult(error);
}
return { valid: false, error: "Connection failed while testing OCI Generative AI" };
}
async function validateSapProvider({ apiKey, providerSpecificData = {} }: any) {
const rawBaseUrl = normalizeBaseUrl(providerSpecificData.baseUrl) || SAP_DEFAULT_BASE_URL;
const resourceGroup = getSapResourceGroup(providerSpecificData);
const headers = applyCustomUserAgent(
{
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"AI-Resource-Group": resourceGroup,
},
providerSpecificData
);
try {
const response = await validationRead(buildSapModelsUrl(rawBaseUrl), {
method: "GET",
headers,
});
if (response.ok) {
return { valid: true, error: null, method: "sap_models" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status === 429) {
return {
valid: true,
error: null,
method: "sap_models",
warning: "Rate limited, but credentials are valid",
};
}
} catch {
// Fall through to deployment probe when the discovery API is unavailable.
}
const canProbeChat =
isSapDeploymentUrl(rawBaseUrl) || /\/chat\/completions$/i.test(normalizeBaseUrl(rawBaseUrl));
if (!canProbeChat) {
return {
valid: false,
error:
"SAP validation needs either a reachable AI_API_URL or a deployment URL in providerSpecificData.baseUrl",
};
}
const validationModelId =
typeof providerSpecificData.validationModelId === "string" &&
providerSpecificData.validationModelId.trim()
? providerSpecificData.validationModelId.trim()
: "gpt-4o";
try {
const response = await validationWrite(buildSapChatUrl(rawBaseUrl), {
method: "POST",
headers,
body: JSON.stringify({
model: validationModelId,
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
});
if (
response.ok ||
response.status === 400 ||
response.status === 404 ||
response.status === 422 ||
response.status === 429
) {
return {
valid: true,
error: null,
method: "sap_chat_probe",
...(response.status === 404
? { warning: "SAP credentials are valid, but the deployment URL or model was not found." }
: {}),
};
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status >= 500) {
return { valid: false, error: `Provider unavailable (${response.status})` };
}
} catch (error: any) {
return toValidationErrorResult(error);
}
return { valid: false, error: "Connection failed while testing SAP Generative AI Hub" };
}
async function validateRekaProvider({ apiKey, providerSpecificData = {} }: any) {
const baseUrl = normalizeBaseUrl(providerSpecificData.baseUrl) || "https://api.reka.ai/v1";
const headers = buildRekaHeaders(apiKey, providerSpecificData);
try {
const response = await validationRead(`${baseUrl}/models`, {
method: "GET",
headers,
});
if (response.ok) {
return { valid: true, error: null, method: "reka_models" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status === 429) {
return {
valid: true,
error: null,
method: "reka_models",
warning: "Rate limited, but credentials are valid",
};
}
} catch {
// Fall through to the chat probe when /models is unavailable.
}
try {
const response = await validationWrite(`${baseUrl}/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify({
model: providerSpecificData.validationModelId || "reka-flash",
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
});
if (
response.ok ||
response.status === 400 ||
response.status === 422 ||
response.status === 429
) {
return { valid: true, error: null, method: "reka_chat_probe" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status >= 500) {
return { valid: false, error: `Provider unavailable (${response.status})` };
}
} catch (error: any) {
return toValidationErrorResult(error);
}
return { valid: false, error: "Connection failed while testing Reka" };
}
async function validateNlpCloudProvider({ apiKey, providerSpecificData = {} }: any) {
const rawBaseUrl = normalizeBaseUrl(providerSpecificData.baseUrl) || "https://api.nlpcloud.io/v1";
const baseUrl = rawBaseUrl.endsWith("/gpu") ? rawBaseUrl : `${rawBaseUrl.replace(/\/$/, "")}/gpu`;
const modelId =
typeof providerSpecificData.validationModelId === "string" &&
providerSpecificData.validationModelId.trim()
? providerSpecificData.validationModelId.trim()
: "chatdolphin";
const headers = buildTokenHeaders(apiKey, providerSpecificData);
try {
const response = await validationWrite(`${baseUrl}/${modelId}/chatbot`, {
method: "POST",
headers,
body: JSON.stringify({
input: "test",
context: "You are a concise assistant.",
history: [],
}),
});
if (
response.ok ||
response.status === 400 ||
response.status === 422 ||
response.status === 429
) {
return {
valid: true,
error: null,
method: "nlpcloud_chatbot",
...(response.status === 429 ? { warning: "Rate limited, but credentials are valid" } : {}),
};
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status >= 500) {
return { valid: false, error: `Provider unavailable (${response.status})` };
}
} catch (error: any) {
return toValidationErrorResult(error);
}
return { valid: false, error: "Connection failed while testing NLP Cloud" };
}
async function validateOpenAICompatibleProvider({ apiKey, providerSpecificData = {} }: any) {
const baseUrl = normalizeBaseUrl(providerSpecificData.baseUrl);
if (!baseUrl) {
@@ -1259,6 +1928,13 @@ const SEARCH_VALIDATOR_CONFIGS: Record<
headers: { Accept: "application/json" },
},
}),
"youcom-search": (apiKey) => ({
url: "https://ydc-index.io/v1/search?query=test&count=1",
init: {
method: "GET",
headers: { Accept: "application/json", "X-API-Key": apiKey },
},
}),
"searxng-search": (_apiKey, providerSpecificData = {}) => {
const baseUrl =
typeof providerSpecificData?.baseUrl === "string" && providerSpecificData.baseUrl.trim()
@@ -1769,6 +2445,34 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
"bailian-coding-plan": validateBailianCodingPlanProvider,
heroku: validateHerokuProvider,
databricks: validateDatabricksProvider,
datarobot: validateDataRobotProvider,
watsonx: validateWatsonxProvider,
oci: validateOciProvider,
sap: validateSapProvider,
bedrock: ({ apiKey, providerSpecificData }: any) => {
const baseUrl = normalizeBedrockBaseUrl(
providerSpecificData?.baseUrl || BEDROCK_DEFAULT_BASE_URL
);
return validateOpenAILikeProvider({
provider: "bedrock",
apiKey,
providerSpecificData,
baseUrl,
modelId: getBedrockValidationModelId(baseUrl),
modelsUrl: buildBedrockModelsUrl(baseUrl),
});
},
modal: ({ apiKey, providerSpecificData }: any) =>
validateOpenAILikeProvider({
provider: "modal",
apiKey,
providerSpecificData,
baseUrl: normalizeBaseUrl(providerSpecificData?.baseUrl || ""),
modelId: "Qwen/Qwen3-4B-Thinking-2507-FP8",
}),
clarifai: validateClarifaiProvider,
reka: validateRekaProvider,
nlpcloud: validateNlpCloudProvider,
snowflake: validateSnowflakeProvider,
gigachat: validateGigachatProvider,
"grok-web": validateGrokWebProvider,
@@ -1776,6 +2480,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
"blackbox-web": validateBlackboxWebProvider,
"muse-spark-web": validateMuseSparkWebProvider,
"azure-openai": validateAzureOpenAIProvider,
"azure-ai": validateAzureAiProvider,
"voyage-ai": ({ apiKey, providerSpecificData }: any) => {
const embeddingProvider = getEmbeddingProvider("voyage-ai");
return validateEmbeddingApiProvider({
@@ -1794,6 +2499,26 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
modelId: rerankProvider?.models?.[0]?.id || "jina-reranker-v3",
});
},
gitlab: async ({ apiKey, providerSpecificData }: any) => {
try {
const configuredBaseUrl =
typeof providerSpecificData?.baseUrl === "string"
? providerSpecificData.baseUrl.trim()
: "";
const root = (configuredBaseUrl || "https://gitlab.com").replace(/\/$/, "");
const res = await validationWrite(`${root}/api/v4/code_suggestions/direct_access`, {
method: "POST",
headers: buildBearerHeaders(apiKey, providerSpecificData),
body: "{}",
});
if (res.status === 401) {
return { valid: false, error: "Invalid API key" };
}
return { valid: true, error: null };
} catch (error: any) {
return toValidationErrorResult(error);
}
},
vertex: async ({ apiKey }: any) => {
try {
const { parseSAFromApiKey, getAccessToken } =

View File

@@ -55,6 +55,17 @@ export const OAUTH_PROVIDERS = {
},
codex: { id: "codex", alias: "cx", name: "OpenAI Codex", icon: "code", color: "#3B82F6" },
github: { id: "github", alias: "gh", name: "GitHub Copilot", icon: "code", color: "#333333" },
"gitlab-duo": {
id: "gitlab-duo",
alias: "gitlab-duo",
name: "GitLab Duo",
icon: "hub",
color: "#FC6D26",
textIcon: "GL",
website: "https://docs.gitlab.com/user/duo_agent_platform/code_suggestions/",
authHint:
"OAuth application with ai_features + read_user scopes. Configure GITLAB_DUO_OAUTH_CLIENT_ID and optionally GITLAB_DUO_OAUTH_CLIENT_SECRET on this OmniRoute instance.",
},
cursor: { id: "cursor", alias: "cu", name: "Cursor IDE", icon: "edit_note", color: "#00D4AA" },
"kimi-coding": {
id: "kimi-coding",
@@ -252,6 +263,122 @@ export const APIKEY_PROVIDERS = {
"Use your Azure OpenAI API key. Base URL should be your resource endpoint, for example https://my-resource.openai.azure.com.",
passthroughModels: true,
},
"azure-ai": {
id: "azure-ai",
alias: "azure-ai",
name: "Azure AI Foundry",
icon: "cloud",
color: "#2563EB",
textIcon: "AF",
website: "https://learn.microsoft.com/azure/ai-foundry/",
authHint:
"Use your Azure AI Foundry key. Base URL can be https://<resource>.services.ai.azure.com/openai/v1/ or https://<resource>.openai.azure.com/openai/v1/.",
apiHint:
"Foundry uses the OpenAI v1 surface with deployment names as models. OmniRoute normalizes root resource URLs to the v1 chat and /models endpoints.",
passthroughModels: true,
},
bedrock: {
id: "bedrock",
alias: "bedrock",
name: "Amazon Bedrock",
icon: "cloud",
color: "#FF9900",
textIcon: "BR",
website: "https://aws.amazon.com/bedrock/",
authHint:
"Use your Amazon Bedrock API key in Authorization: Bearer <key>. OmniRoute defaults to the OpenAI-compatible bedrock-mantle endpoint in us-east-1; set a regional base URL if your account uses another region or the bedrock-runtime /openai/v1 path.",
apiHint:
"This integration targets Amazon Bedrock's current OpenAI-compatible surface. bedrock-mantle is the default for /models and chat; advanced users can also point baseUrl to bedrock-runtime/.../openai/v1 for runtime-specific model IDs.",
passthroughModels: true,
},
watsonx: {
id: "watsonx",
alias: "watsonx",
name: "IBM watsonx.ai Gateway",
icon: "hub",
color: "#0F62FE",
textIcon: "WX",
website: "https://www.ibm.com/products/watsonx-ai",
authHint:
"Use your watsonx bearer token. Base URL can be https://<region>.ml.cloud.ibm.com/ml/gateway/v1/ or a self-managed /ml/gateway/v1 endpoint.",
apiHint:
"The watsonx model gateway exposes OpenAI-compatible /chat/completions and /models under /ml/gateway/v1.",
passthroughModels: true,
},
oci: {
id: "oci",
alias: "oci",
name: "OCI Generative AI",
icon: "cloud",
color: "#C74634",
textIcon: "OCI",
website: "https://www.oracle.com/artificial-intelligence/generative-ai/",
authHint:
"Use your OCI Generative AI API key or IAM bearer token. Base URL can be https://inference.generativeai.<region>.oci.oraclecloud.com/openai/v1/.",
apiHint:
"OCI exposes OpenAI-compatible chat and responses endpoints. Project ID is optional in OmniRoute but may be required for Responses and agentic workflows.",
passthroughModels: true,
},
sap: {
id: "sap",
alias: "sap",
name: "SAP Generative AI Hub",
icon: "business",
color: "#0FAAFF",
textIcon: "SAP",
website:
"https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/generative-ai-hub-in-sap-ai-core",
authHint:
"Use your SAP AI Core bearer token. Base URL can be your AI_API_URL root or a deploymentUrl from Generative AI Hub.",
apiHint:
"Model discovery uses /v2/lm/scenarios/foundation-models/models on AI_API_URL. Chat requests use deploymentUrl/chat/completions and require AI-Resource-Group.",
passthroughModels: true,
},
modal: {
id: "modal",
alias: "mdl",
name: "Modal",
icon: "cloud_queue",
color: "#7C3AED",
textIcon: "MDL",
website: "https://modal.com/docs",
authHint:
"Use the bearer token that protects your Modal deployment, if enabled. Base URL should point to your OpenAI-compatible Modal app, for example https://<workspace>--<app>.modal.run/v1.",
apiHint:
"Modal commonly serves user-hosted OpenAI-compatible apps on /v1. OmniRoute will probe /v1/models and route chat traffic to /v1/chat/completions.",
hasFree: true,
freeNote: "$30/month free credits for new accounts",
passthroughModels: true,
},
reka: {
id: "reka",
alias: "reka",
name: "Reka",
icon: "auto_awesome",
color: "#111827",
textIcon: "RK",
website: "https://docs.reka.ai/chat/overview",
authHint:
"Use your Reka API key. OmniRoute supports the OpenAI-compatible base URL https://api.reka.ai/v1 and sends both Authorization and X-Api-Key headers for compatibility.",
apiHint:
"Reka Chat is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions.",
passthroughModels: true,
},
nlpcloud: {
id: "nlpcloud",
alias: "nlpc",
name: "NLP Cloud",
icon: "psychology",
color: "#2196F3",
textIcon: "NLPC",
website: "https://docs.nlpcloud.com",
authHint:
"Use your NLP Cloud API key in Authorization: Token <key>. OmniRoute targets the chatbot endpoint on https://api.nlpcloud.io/v1/gpu/<model>/chatbot by default.",
apiHint:
"NLP Cloud uses a proprietary chatbot API instead of OpenAI chat/completions. OmniRoute adapts OpenAI messages to input/context/history and exposes a local catalog of supported chatbot models.",
hasFree: true,
freeNote: "Trial credits for new accounts",
},
anthropic: {
id: "anthropic",
alias: "anthropic",
@@ -736,6 +863,34 @@ export const APIKEY_PROVIDERS = {
textIcon: "DB",
website: "https://www.databricks.com",
},
datarobot: {
id: "datarobot",
alias: "datarobot",
name: "DataRobot",
icon: "precision_manufacturing",
color: "#6D28D9",
textIcon: "DR",
website: "https://docs.datarobot.com",
authHint:
"Use your DataRobot API token. Optional Base URL can be the account root (for LLM Gateway) or a deployment URL under /api/v2/deployments/<id>.",
apiHint:
"The default gateway catalogs active models from /genai/llmgw/catalog/. Deployment URLs are also supported for direct OpenAI-compatible chat requests.",
passthroughModels: true,
},
clarifai: {
id: "clarifai",
alias: "clarifai",
name: "Clarifai",
icon: "hub",
color: "#7C3AED",
textIcon: "CF",
website: "https://docs.clarifai.com",
authHint:
"Use your Clarifai PAT or app-specific API key. OmniRoute targets the OpenAI-compatible endpoint at https://api.clarifai.com/v2/ext/openai/v1 and authenticates with Authorization: Key <token>.",
apiHint:
"Clarifai exposes OpenAI-compatible chat, responses and /models on /v2/ext/openai/v1. Public/community models typically require a PAT; app-scoped keys only work for resources inside that app.",
passthroughModels: true,
},
snowflake: {
id: "snowflake",
alias: "snowflake",
@@ -959,6 +1114,28 @@ export const APIKEY_PROVIDERS = {
authHint: "Bearer API key for the FenayAI OpenAI-compatible gateway.",
passthroughModels: true,
},
gitlab: {
id: "gitlab",
alias: "gitlab",
name: "GitLab Duo PAT",
icon: "hub",
color: "#FC6D26",
textIcon: "GL",
website: "https://docs.gitlab.com/user/duo_agent_platform/code_suggestions/",
authHint:
"GitLab personal access token for the public Code Suggestions API. Configure a self-hosted base URL when not using gitlab.com.",
},
chutes: {
id: "chutes",
alias: "chutes",
name: "Chutes.ai",
icon: "hub",
color: "#06B6D4",
textIcon: "CH",
website: "https://chutes.ai",
authHint: "Bearer API key for the Chutes OpenAI-compatible gateway.",
passthroughModels: true,
},
"voyage-ai": {
id: "voyage-ai",
alias: "voyage",
@@ -1227,6 +1404,16 @@ export const SEARCH_PROVIDERS = {
website: "https://www.searchapi.io/docs",
authHint: "API key from SearchAPI (query param or Bearer auth)",
},
"youcom-search": {
id: "youcom-search",
alias: "youcom-search",
name: "You.com Search",
icon: "travel_explore",
color: "#2563EB",
textIcon: "YOU",
website: "https://you.com/docs/search/overview",
authHint: "X-API-Key from the You.com platform dashboard",
},
"searxng-search": {
id: "searxng-search",
alias: "searxng",

View File

@@ -1747,6 +1747,7 @@ export const v1SearchSchema = z
"google-pse-search",
"linkup-search",
"searchapi-search",
"youcom-search",
"searxng-search",
])
.optional(),

View File

@@ -14,6 +14,7 @@ const CHAT_OPENAI_COMPAT_PROVIDER_IDS = [
"ovhcloud",
"baseten",
"publicai",
"chutes",
"moonshot",
"meta-llama",
"v0-vercel",
@@ -24,6 +25,15 @@ const CHAT_OPENAI_COMPAT_PROVIDER_IDS = [
"heroku",
"galadriel",
"databricks",
"datarobot",
"clarifai",
"azure-ai",
"bedrock",
"watsonx",
"oci",
"sap",
"modal",
"reka",
"snowflake",
"wandb",
"volcengine",

View File

@@ -11,6 +11,7 @@ import {
} from "../../open-sse/executors/base.ts";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
import { PROVIDERS } from "../../open-sse/config/constants.ts";
import { BEDROCK_DEFAULT_BASE_URL } from "../../open-sse/config/bedrock.ts";
import {
CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION,
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
@@ -161,6 +162,15 @@ test("DefaultExecutor.buildUrl normalizes configurable chat-openai-compat base U
const bailian = new DefaultExecutor("bailian-coding-plan");
const heroku = new DefaultExecutor("heroku");
const databricks = new DefaultExecutor("databricks");
const datarobot = new DefaultExecutor("datarobot");
const clarifai = new DefaultExecutor("clarifai");
const azureAi = new DefaultExecutor("azure-ai");
const bedrock = new DefaultExecutor("bedrock");
const watsonx = new DefaultExecutor("watsonx");
const oci = new DefaultExecutor("oci");
const sap = new DefaultExecutor("sap");
const modal = new DefaultExecutor("modal");
const reka = new DefaultExecutor("reka");
const snowflake = new DefaultExecutor("snowflake");
const gigachat = new DefaultExecutor("gigachat");
@@ -186,6 +196,84 @@ test("DefaultExecutor.buildUrl normalizes configurable chat-openai-compat base U
}),
"https://adb-1234567890123456.7.azuredatabricks.net/serving-endpoints/chat/completions"
);
assert.equal(
datarobot.buildUrl("azure/gpt-5-mini-2025-08-07", true, 0, {
providerSpecificData: { baseUrl: "https://app.datarobot.com" },
}),
"https://app.datarobot.com/api/v2/genai/llmgw/chat/completions/"
);
assert.equal(
datarobot.buildUrl("datarobot-deployed-llm", true, 0, {
providerSpecificData: {
baseUrl: "https://app.datarobot.com/api/v2/deployments/65f5b2b7c8f8c4b257e0d123",
},
}),
"https://app.datarobot.com/api/v2/deployments/65f5b2b7c8f8c4b257e0d123/chat/completions"
);
assert.equal(
clarifai.buildUrl("openai/chat-completion/models/gpt-oss-120b", true),
"https://api.clarifai.com/v2/ext/openai/v1/chat/completions"
);
assert.equal(
azureAi.buildUrl("DeepSeek-V3.1", true, 0, {
providerSpecificData: { baseUrl: "https://my-foundry.services.ai.azure.com" },
}),
"https://my-foundry.services.ai.azure.com/openai/v1/chat/completions"
);
assert.equal(
bedrock.buildUrl("openai.gpt-oss-120b", true, 0, {
providerSpecificData: { baseUrl: "https://bedrock-mantle.us-east-1.api.aws" },
}),
"https://bedrock-mantle.us-east-1.api.aws/v1/chat/completions"
);
assert.equal(
bedrock.buildUrl("openai.gpt-oss-120b-1:0", true, 0, {
providerSpecificData: { baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com" },
}),
"https://bedrock-runtime.us-east-1.amazonaws.com/openai/v1/chat/completions"
);
assert.equal(
bedrock.buildUrl("openai.gpt-oss-120b", true),
`${BEDROCK_DEFAULT_BASE_URL}/chat/completions`
);
assert.equal(
watsonx.buildUrl("ibm/granite-3-3-8b-instruct", true, 0, {
providerSpecificData: { baseUrl: "https://ca-tor.ml.cloud.ibm.com" },
}),
"https://ca-tor.ml.cloud.ibm.com/ml/gateway/v1/chat/completions"
);
assert.equal(
oci.buildUrl("openai.gpt-oss-20b", true, 0, {
providerSpecificData: {
baseUrl: "https://inference.generativeai.us-ashburn-1.oci.oraclecloud.com",
},
}),
"https://inference.generativeai.us-ashburn-1.oci.oraclecloud.com/openai/v1/chat/completions"
);
assert.equal(
sap.buildUrl("gpt-4o", true, 0, {
providerSpecificData: {
baseUrl: "https://sap.example.com/v2/lm/deployments/demo-deployment",
},
}),
"https://sap.example.com/v2/lm/deployments/demo-deployment/chat/completions"
);
assert.equal(
modal.buildUrl("Qwen/Qwen3-4B-Thinking-2507-FP8", true, 0, {
providerSpecificData: {
baseUrl: "https://alice--demo.modal.run/v1",
},
}),
"https://alice--demo.modal.run/v1/chat/completions"
);
assert.equal(
reka.buildUrl("reka-core", true, 0, {
providerSpecificData: {
baseUrl: "https://api.reka.ai/v1",
},
}),
"https://api.reka.ai/v1/chat/completions"
);
assert.equal(
snowflake.buildUrl("llama3.3-70b", true, 0, {
providerSpecificData: { baseUrl: "https://account.snowflakecomputing.com" },
@@ -209,11 +297,52 @@ test("DefaultExecutor.buildUrl falls back to OpenAI config for unknown providers
test("DefaultExecutor.buildHeaders handles Gemini and Claude auth modes", () => {
const gemini = new DefaultExecutor("gemini");
const claude = new DefaultExecutor("claude");
const clarifai = new DefaultExecutor("clarifai");
const azureAi = new DefaultExecutor("azure-ai");
const oci = new DefaultExecutor("oci");
const sap = new DefaultExecutor("sap");
const modal = new DefaultExecutor("modal");
const reka = new DefaultExecutor("reka");
const geminiApiKeyHeaders = gemini.buildHeaders({ apiKey: "gem-key" }, true);
const geminiOAuthHeaders = gemini.buildHeaders({ accessToken: "gem-token" }, false);
const claudeApiKeyHeaders = claude.buildHeaders({ apiKey: "claude-key" }, true);
const claudeOAuthHeaders = claude.buildHeaders({ accessToken: "claude-token" }, false);
const azureAiHeaders = azureAi.buildHeaders({ apiKey: "azure-ai-key" }, true);
const ociHeaders = oci.buildHeaders(
{
apiKey: "oci-key",
projectId: "ocid1.generativeaiproject.oc1.us-chicago-1.example",
},
true
);
const sapHeaders = sap.buildHeaders(
{
apiKey: "sap-key",
providerSpecificData: {
resourceGroup: "shared",
},
},
true
);
const modalHeaders = modal.buildHeaders(
{
apiKey: "modal-key",
},
true
);
const rekaHeaders = reka.buildHeaders(
{
apiKey: "reka-key",
},
true
);
const clarifaiHeaders = clarifai.buildHeaders(
{
apiKey: "clarifai-pat",
},
true
);
assert.equal(geminiApiKeyHeaders["x-goog-api-key"], "gem-key");
assert.equal(geminiApiKeyHeaders.Accept, "text/event-stream");
@@ -223,6 +352,16 @@ test("DefaultExecutor.buildHeaders handles Gemini and Claude auth modes", () =>
assert.equal(claudeApiKeyHeaders.Accept, "text/event-stream");
assert.equal(claudeOAuthHeaders.Authorization, "Bearer claude-token");
assert.equal(claudeOAuthHeaders["x-api-key"], undefined);
assert.equal(azureAiHeaders["api-key"], "azure-ai-key");
assert.equal(azureAiHeaders.Authorization, undefined);
assert.equal(ociHeaders.Authorization, "Bearer oci-key");
assert.equal(ociHeaders["OpenAI-Project"], "ocid1.generativeaiproject.oc1.us-chicago-1.example");
assert.equal(sapHeaders.Authorization, "Bearer sap-key");
assert.equal(sapHeaders["AI-Resource-Group"], "shared");
assert.equal(modalHeaders.Authorization, "Bearer modal-key");
assert.equal(rekaHeaders.Authorization, "Bearer reka-key");
assert.equal(rekaHeaders["X-Api-Key"], "reka-key");
assert.equal(clarifaiHeaders.Authorization, "Key clarifai-pat");
});
test("DefaultExecutor.buildHeaders handles GLM, default auth and anthropic-compatible headers", () => {

View File

@@ -0,0 +1,263 @@
import test from "node:test";
import assert from "node:assert/strict";
import { GitlabExecutor } from "../../open-sse/executors/gitlab.ts";
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
function jsonResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
test("GitlabExecutor is registered in the executor index", () => {
assert.equal(hasSpecializedExecutor("gitlab"), true);
assert.ok(getExecutor("gitlab") instanceof GitlabExecutor);
assert.equal(hasSpecializedExecutor("gitlab-duo"), true);
assert.ok(getExecutor("gitlab-duo") instanceof GitlabExecutor);
});
test("GitlabExecutor posts PAT-backed code suggestion requests to the configured instance", async () => {
const executor = new GitlabExecutor();
const calls: Array<{
url: string;
body: Record<string, unknown>;
headers: Record<string, string>;
}> = [];
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, init = {}) => {
calls.push({
url: String(url),
body: JSON.parse(String(init.body || "{}")),
headers: init.headers as Record<string, string>,
});
return jsonResponse({
id: "gitlab-response-1",
model: { name: "code-gecko", engine: "vertex-ai" },
choices: [{ text: "def hello():\n return 'world'", finish_reason: "stop" }],
});
};
try {
const result = await executor.execute({
model: "gitlab-duo-code-suggestions",
body: {
messages: [
{ role: "system", content: "Return Python code only." },
{ role: "user", content: "Write a hello world function" },
],
},
stream: false,
credentials: {
apiKey: "glpat-test",
providerSpecificData: {
baseUrl: "https://gitlab.example.com",
projectPath: "group/project",
fileName: "app.py",
},
},
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(calls.length, 1);
assert.equal(calls[0].url, "https://gitlab.example.com/api/v4/code_suggestions/completions");
assert.equal(calls[0].headers.Authorization, "Bearer glpat-test");
assert.equal(calls[0].body.project_path, "group/project");
assert.equal(calls[0].body.current_file.file_name, "app.py");
assert.equal(calls[0].body.intent, "generation");
assert.match(String(calls[0].body.user_instruction), /Write a hello world function/);
assert.match(String(calls[0].body.current_file.content_above_cursor), /System instructions:/);
const body = (await result.response.json()) as any;
assert.equal(body.object, "chat.completion");
assert.equal(body.choices[0].message.role, "assistant");
assert.match(body.choices[0].message.content, /hello/);
assert.equal(body.model, "code-gecko");
} finally {
globalThis.fetch = originalFetch;
}
});
test("GitlabExecutor synthesizes SSE responses from non-streaming upstream completions", async () => {
const executor = new GitlabExecutor();
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
jsonResponse({
model: { name: "code-gecko" },
choices: [{ text: "console.log('hi');" }],
});
try {
const result = await executor.execute({
model: "gitlab-duo-code-suggestions",
body: {
messages: [{ role: "user", content: "Write a JS hello world" }],
},
stream: true,
credentials: { apiKey: "glpat-test" },
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(result.response.headers.get("Content-Type"), "text/event-stream");
const text = await result.response.text();
assert.match(text, /data: \{\"id\":\"chatcmpl-gitlab-/);
assert.match(text, /console\.log\('hi'\);/);
assert.match(text, /data: \[DONE\]/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("GitlabExecutor maps upstream auth failures to OpenAI-style errors", async () => {
const executor = new GitlabExecutor();
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => jsonResponse({ message: "forbidden" }, 403);
try {
const result = await executor.execute({
model: "gitlab-duo-code-suggestions",
body: { messages: [{ role: "user", content: "hi" }] },
stream: false,
credentials: { apiKey: "glpat-test" },
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(result.response.status, 403);
const body = (await result.response.json()) as any;
assert.match(body.error.message, /auth failed/i);
} finally {
globalThis.fetch = originalFetch;
}
});
test("GitlabExecutor uses GitLab direct_access for gitlab-duo and persists the cache", async () => {
const executor = getExecutor("gitlab-duo") as GitlabExecutor;
const originalFetch = globalThis.fetch;
const calls: Array<{ url: string; headers: Record<string, string> }> = [];
const refreshedPatches: Array<Record<string, unknown>> = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({
url: String(url),
headers: (init.headers || {}) as Record<string, string>,
});
if (String(url) === "https://gitlab.example.com/api/v4/code_suggestions/direct_access") {
return jsonResponse({
token: "direct-token",
base_url: "https://cloud.gitlab.com",
expires_at: Math.floor(Date.now() / 1000) + 1800,
headers: {
"x-gitlab-feature-enabled": "true",
},
});
}
return jsonResponse({
metadata: {
model_details: {
model_name: "GitLab Duo Claude Sonnet",
},
},
choices: [{ text: "print('gitlab duo')" }],
});
};
try {
const result = await executor.execute({
model: "gitlab-duo-code-suggestions",
body: {
messages: [{ role: "user", content: "Write a hello world in Python" }],
},
stream: false,
credentials: {
accessToken: "oauth-access",
refreshToken: "oauth-refresh",
providerSpecificData: {
baseUrl: "https://gitlab.example.com",
},
},
onCredentialsRefreshed: async (patch) => {
refreshedPatches.push(patch as Record<string, unknown>);
},
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(calls[0].url, "https://gitlab.example.com/api/v4/code_suggestions/direct_access");
assert.equal(calls[0].headers.Authorization, "Bearer oauth-access");
assert.equal(calls[1].url, "https://cloud.gitlab.com/ai/v2/completions");
assert.equal(calls[1].headers.Authorization, "Bearer direct-token");
assert.equal(calls[1].headers["x-gitlab-feature-enabled"], "true");
assert.equal(refreshedPatches.length, 1);
assert.equal(
(
(refreshedPatches[0].providerSpecificData as Record<string, unknown>)
?.gitlabDirectAccess as Record<string, unknown>
)?.token,
"direct-token"
);
const body = (await result.response.json()) as any;
assert.equal(body.model, "GitLab Duo Claude Sonnet");
assert.match(body.choices[0].message.content, /gitlab duo/i);
} finally {
globalThis.fetch = originalFetch;
}
});
test("GitlabExecutor falls back to the public Code Suggestions endpoint when direct_access is disabled", async () => {
const executor = getExecutor("gitlab-duo") as GitlabExecutor;
const originalFetch = globalThis.fetch;
const calls: string[] = [];
globalThis.fetch = async (url) => {
calls.push(String(url));
if (String(url) === "https://gitlab.example.com/api/v4/code_suggestions/direct_access") {
return jsonResponse({ message: "Direct connections are disabled" }, 403);
}
return jsonResponse({
model: { name: "code-gecko" },
choices: [{ text: "fallback path works" }],
});
};
try {
const result = await executor.execute({
model: "gitlab-duo-code-suggestions",
body: {
messages: [{ role: "user", content: "Say hello" }],
},
stream: false,
credentials: {
accessToken: "oauth-access",
providerSpecificData: {
baseUrl: "https://gitlab.example.com",
},
},
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.deepEqual(calls, [
"https://gitlab.example.com/api/v4/code_suggestions/direct_access",
"https://gitlab.example.com/api/v4/code_suggestions/completions",
]);
const body = (await result.response.json()) as any;
assert.equal(body.model, "code-gecko");
assert.match(body.choices[0].message.content, /fallback path/i);
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -0,0 +1,149 @@
import test from "node:test";
import assert from "node:assert/strict";
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
import { NlpCloudExecutor } from "../../open-sse/executors/nlpcloud.ts";
const encoder = new TextEncoder();
function jsonResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
function sseResponse(events: string[]) {
return new Response(
new ReadableStream({
start(controller) {
for (const event of events) {
controller.enqueue(encoder.encode(event));
}
controller.close();
},
}),
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
}
test("NlpCloudExecutor is registered in the executor index", () => {
assert.equal(hasSpecializedExecutor("nlpcloud"), true);
assert.ok(getExecutor("nlpcloud") instanceof NlpCloudExecutor);
});
test("NlpCloudExecutor converts OpenAI messages into chatbot input/context/history and wraps JSON responses", async () => {
const executor = new NlpCloudExecutor();
const originalFetch = globalThis.fetch;
const calls: Array<{
url: string;
body: Record<string, unknown>;
headers: Record<string, string>;
}> = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({
url: String(url),
body: JSON.parse(String(init.body || "{}")),
headers: init.headers as Record<string, string>,
});
return jsonResponse({
response: "Hi back from NLP Cloud.",
history: [
{ input: "Hello", response: "Hi there!" },
{ input: "How are you?", response: "Hi back from NLP Cloud." },
],
});
};
try {
const result = await executor.execute({
model: "chatdolphin",
body: {
messages: [
{ role: "system", content: "You are concise." },
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there!" },
{ role: "user", content: "How are you?" },
],
},
stream: false,
credentials: { apiKey: "nlpc-key" },
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(calls.length, 1);
assert.equal(calls[0].url, "https://api.nlpcloud.io/v1/gpu/chatdolphin/chatbot");
assert.equal(calls[0].headers.Authorization, "Token nlpc-key");
assert.equal(calls[0].body.input, "How are you?");
assert.equal(calls[0].body.context, "You are concise.");
assert.deepEqual(calls[0].body.history, [{ input: "Hello", response: "Hi there!" }]);
const body = (await result.response.json()) as any;
assert.equal(body.object, "chat.completion");
assert.equal(body.choices[0].message.role, "assistant");
assert.equal(body.choices[0].message.content, "Hi back from NLP Cloud.");
assert.equal(body.model, "chatdolphin");
} finally {
globalThis.fetch = originalFetch;
}
});
test("NlpCloudExecutor converts raw NLP Cloud SSE text events into OpenAI chat chunks", async () => {
const executor = new NlpCloudExecutor();
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
sseResponse(["data: Hello \n\n", "data: world\n\n", "data: [DONE]\n\n"]);
try {
const result = await executor.execute({
model: "chatdolphin",
body: {
messages: [{ role: "user", content: "Say hello" }],
},
stream: true,
credentials: { apiKey: "nlpc-key" },
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(result.response.headers.get("Content-Type"), "text/event-stream");
const text = await result.response.text();
assert.match(text, /data: \{\"id\":\"chatcmpl-nlpcloud-/);
assert.match(text, /Hello/);
assert.match(text, /world/);
assert.match(text, /data: \[DONE\]/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("NlpCloudExecutor maps upstream auth failures to OpenAI-style errors", async () => {
const executor = new NlpCloudExecutor();
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => jsonResponse({ detail: "forbidden" }, 403);
try {
const result = await executor.execute({
model: "chatdolphin",
body: { messages: [{ role: "user", content: "hi" }] },
stream: false,
credentials: { apiKey: "nlpc-key" },
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(result.response.status, 403);
const body = (await result.response.json()) as any;
assert.match(body.error.message, /status 403/i);
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -78,3 +78,122 @@ test("Kiro registry exposes the current CLI model lineup with context windows",
assert.equal(byId.get("deepseek-3.2")?.contextLength, 128000);
assert.equal(byId.get("qwen3-coder-next")?.contextLength, 256000);
});
test("Chutes registry exposes a current TEE-heavy public lineup", () => {
const chutesModels = getProviderModels("chutes");
const ids = new Set(chutesModels.map((model) => model.id));
assert.ok(ids.has("Qwen/Qwen3-32B-TEE"));
assert.ok(ids.has("deepseek-ai/DeepSeek-V3.2-TEE"));
assert.ok(ids.has("openai/gpt-oss-120b-TEE"));
assert.ok(ids.has("moonshotai/Kimi-K2.6-TEE"));
});
test("DataRobot registry exposes gateway-friendly fallback examples", () => {
const datarobotModels = getProviderModels("datarobot");
const ids = new Set(datarobotModels.map((model) => model.id));
assert.ok(ids.has("azure/gpt-5-mini-2025-08-07"));
assert.ok(ids.has("azure/gpt-4o-mini"));
});
test("Clarifai registry exposes current OpenAI-compatible examples", () => {
const clarifaiModels = getProviderModels("clarifai");
const ids = new Set(clarifaiModels.map((model) => model.id));
assert.ok(ids.has("openai/chat-completion/models/gpt-oss-120b"));
assert.ok(ids.has("openai/chat-completion/models/gpt-4o"));
assert.ok(ids.has("anthropic/completion/models/claude-sonnet-4"));
assert.ok(ids.has("gcp/generate/models/gemini-2_5-flash"));
});
test("Azure AI Foundry registry exposes fallback marketplace examples", () => {
const azureAiModels = getProviderModels("azure-ai");
const ids = new Set(azureAiModels.map((model) => model.id));
assert.ok(ids.has("claude-opus-4-6"));
assert.ok(ids.has("deepseek-v3.2"));
assert.ok(ids.has("kimi-k2.5"));
});
test("Bedrock registry exposes current OpenAI-compatible mantle examples", () => {
const bedrockModels = getProviderModels("bedrock");
const ids = new Set(bedrockModels.map((model) => model.id));
assert.ok(ids.has("openai.gpt-oss-20b"));
assert.ok(ids.has("openai.gpt-oss-120b"));
assert.ok(ids.has("mistral.mistral-large-3-675b-instruct"));
});
test("watsonx registry exposes gateway-friendly fallback examples", () => {
const watsonxModels = getProviderModels("watsonx");
const ids = new Set(watsonxModels.map((model) => model.id));
assert.ok(ids.has("ibm/granite-3-3-8b-instruct"));
assert.ok(ids.has("meta-llama/llama-3-3-70b-instruct"));
assert.ok(ids.has("openai/gpt-4o"));
});
test("OCI registry exposes current OpenAI-compatible enterprise examples", () => {
const ociModels = getProviderModels("oci");
const ids = new Set(ociModels.map((model) => model.id));
assert.ok(ids.has("openai.gpt-oss-20b"));
assert.ok(ids.has("openai.gpt-oss-120b"));
assert.ok(ids.has("google.gemini-2.5-pro"));
});
test("SAP registry exposes current Generative AI Hub examples", () => {
const sapModels = getProviderModels("sap");
const ids = new Set(sapModels.map((model) => model.id));
assert.ok(ids.has("gpt-4o"));
assert.ok(ids.has("gpt-5-mini"));
assert.ok(ids.has("mistralai--mistral-medium-instruct"));
});
test("Modal registry exposes current OpenAI-compatible deployment examples", () => {
const modalModels = getProviderModels("modal");
const ids = new Set(modalModels.map((model) => model.id));
assert.ok(ids.has("Qwen/Qwen3-4B-Thinking-2507-FP8"));
assert.ok(ids.has("google/gemma-4-26B-A4B-it"));
assert.ok(ids.has("gpt-oss-20B"));
});
test("Reka registry exposes current OpenAI-compatible chat examples", () => {
const rekaModels = getProviderModels("reka");
const ids = new Set(rekaModels.map((model) => model.id));
assert.ok(ids.has("reka-core"));
assert.ok(ids.has("reka-flash"));
assert.ok(ids.has("reka-edge-2603"));
});
test("NLP Cloud registry exposes the current chatbot model lineup", () => {
const nlpCloudModels = getModelsByProviderId("nlpcloud");
const ids = new Set(nlpCloudModels.map((model) => model.id));
assert.ok(ids.has("gpt-oss-120b"));
assert.ok(ids.has("llama-3-1-405b"));
assert.ok(ids.has("finetuned-llama-3-70b"));
assert.ok(ids.has("chatdolphin"));
assert.ok(ids.has("dolphin-yi-34b"));
assert.ok(ids.has("dolphin-mixtral-8x7b"));
});
test("GitLab registry exposes the public code suggestions fallback model", () => {
const gitlabModels = getProviderModels("gitlab");
assert.deepEqual(gitlabModels, [
{ id: "gitlab-duo-code-suggestions", name: "GitLab Duo Code Suggestions" },
]);
});
test("GitLab Duo OAuth registry reuses the same fallback model catalog", () => {
const gitlabDuoModels = getModelsByProviderId("gitlab-duo");
assert.deepEqual(gitlabDuoModels, [
{ id: "gitlab-duo-code-suggestions", name: "GitLab Duo Code Suggestions" },
]);
});

View File

@@ -208,6 +208,43 @@ test("provider models route returns static catalog entries for providers with ha
assert.equal(body.models.length, 8);
});
test("provider models route returns the local catalog for GitLab Duo fallback models", async () => {
const connection = await seedConnection("gitlab", {
apiKey: "glpat-test",
});
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "gitlab");
assert.equal(body.source, "local_catalog");
assert.deepEqual(body.models, [
{ id: "gitlab-duo-code-suggestions", name: "GitLab Duo Code Suggestions", owned_by: "gitlab" },
]);
});
test("provider models route returns the local catalog for GitLab Duo OAuth fallback models", async () => {
const connection = await seedConnection("gitlab-duo", {
authType: "oauth",
accessToken: "oauth-access",
});
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "gitlab-duo");
assert.equal(body.source, "local_catalog");
assert.deepEqual(body.models, [
{
id: "gitlab-duo-code-suggestions",
name: "GitLab Duo Code Suggestions",
owned_by: "gitlab-duo",
},
]);
});
test("provider models route discovers local OpenAI-style models without requiring an API key", async () => {
process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS = "true";
@@ -298,6 +335,12 @@ test("provider models route fetches remote catalogs for new OpenAI-compatible ga
expectedUrl: "https://fenayai.com/v1/models",
model: { id: "deepseek-chat", name: "DeepSeek Chat via FenayAI" },
},
{
provider: "chutes",
apiKey: "chutes-key",
expectedUrl: "https://llm.chutes.ai/v1/models",
model: { id: "Qwen/Qwen3-32B-TEE", name: "Qwen3 32B via Chutes" },
},
];
for (const entry of cases) {
@@ -354,6 +397,22 @@ test("provider models route returns the local catalog for embedding and rerank p
assert.ok(jinaBody.models.some((model) => model.id === "jina-reranker-v2-base-multilingual"));
});
test("provider models route returns the local catalog for NLP Cloud", async () => {
const connection = await seedConnection("nlpcloud", {
apiKey: "nlpc-key",
});
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "nlpcloud");
assert.equal(body.source, "local_catalog");
assert.ok(body.models.some((model) => model.id === "chatdolphin"));
assert.ok(body.models.some((model) => model.id === "gpt-oss-120b"));
assert.ok(body.models.some((model) => model.id === "dolphin-mixtral-8x7b"));
});
test("provider models route returns the local catalog for amazon-q via the kiro-compatible registry", async () => {
const connection = await seedConnection("amazon-q", {
authType: "oauth",
@@ -875,6 +934,368 @@ test("provider models route rejects generic providers without any configured tok
assert.equal(called, false);
});
test("provider models route discovers active DataRobot gateway models from the catalog endpoint", async () => {
const connection = await seedConnection("datarobot", {
apiKey: "dr-key",
providerSpecificData: {
baseUrl: "https://app.datarobot.com",
},
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://app.datarobot.com/genai/llmgw/catalog/");
assert.equal(init.method, "GET");
assert.equal(init.headers.Authorization, "Bearer dr-key");
return Response.json({
data: [
{ model: "azure/gpt-5-mini-2025-08-07", isActive: true },
{ model: "azure/gpt-4o-mini", label: "Azure GPT-4o Mini", isActive: true },
{ model: "anthropic/claude-sonnet-4-6", isActive: false },
],
});
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "datarobot");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{
id: "azure/gpt-5-mini-2025-08-07",
name: "azure/gpt-5-mini-2025-08-07",
owned_by: "datarobot",
},
{
id: "azure/gpt-4o-mini",
name: "Azure GPT-4o Mini",
owned_by: "datarobot",
},
]);
});
test("provider models route discovers Clarifai OpenAI-compatible models with Key auth", async () => {
const connection = await seedConnection("clarifai", {
apiKey: "clarifai-pat",
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://api.clarifai.com/v2/ext/openai/v1/models");
assert.equal(init.method, "GET");
assert.equal(init.headers.Authorization, "Key clarifai-pat");
return Response.json({
data: [
{
id: "openai/chat-completion/models/gpt-oss-120b",
display_name: "GPT-OSS 120B",
},
{ id: "anthropic/completion/models/claude-sonnet-4" },
],
});
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "clarifai");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{
id: "openai/chat-completion/models/gpt-oss-120b",
name: "GPT-OSS 120B",
owned_by: "clarifai",
},
{
id: "anthropic/completion/models/claude-sonnet-4",
name: "anthropic/completion/models/claude-sonnet-4",
owned_by: "clarifai",
},
]);
});
test("provider models route discovers Azure AI Foundry deployments through the v1 models endpoint", async () => {
const connection = await seedConnection("azure-ai", {
apiKey: "azure-ai-key",
providerSpecificData: {
baseUrl: "https://my-foundry.services.ai.azure.com",
},
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://my-foundry.services.ai.azure.com/openai/v1/models");
assert.equal(init.method, "GET");
assert.equal(init.headers["api-key"], "azure-ai-key");
return Response.json({
data: [{ id: "DeepSeek-V3.1", display_name: "DeepSeek V3.1" }, { name: "Claude-Opus-4.6" }],
});
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "azure-ai");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{ id: "DeepSeek-V3.1", name: "DeepSeek V3.1", owned_by: "azure-ai" },
{ id: "Claude-Opus-4.6", name: "Claude-Opus-4.6", owned_by: "azure-ai" },
]);
});
test("provider models route discovers Bedrock mantle models from the OpenAI-compatible models endpoint", async () => {
const connection = await seedConnection("bedrock", {
apiKey: "bedrock-key",
providerSpecificData: {
baseUrl: "https://bedrock-mantle.us-east-1.api.aws",
},
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://bedrock-mantle.us-east-1.api.aws/v1/models");
assert.equal(init.method, "GET");
assert.equal(init.headers.Authorization, "Bearer bedrock-key");
return Response.json({
data: [
{ id: "openai.gpt-oss-120b", display_name: "OpenAI GPT-OSS 120B" },
{ id: "mistral.mistral-large-3-675b-instruct" },
],
});
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "bedrock");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{
id: "openai.gpt-oss-120b",
name: "OpenAI GPT-OSS 120B",
owned_by: "bedrock",
},
{
id: "mistral.mistral-large-3-675b-instruct",
name: "mistral.mistral-large-3-675b-instruct",
owned_by: "bedrock",
},
]);
});
test("provider models route discovers watsonx gateway models from the v1 models endpoint", async () => {
const connection = await seedConnection("watsonx", {
apiKey: "watsonx-key",
providerSpecificData: {
baseUrl: "https://ca-tor.ml.cloud.ibm.com",
},
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://ca-tor.ml.cloud.ibm.com/ml/gateway/v1/models");
assert.equal(init.method, "GET");
assert.equal(init.headers.Authorization, "Bearer watsonx-key");
return Response.json({
data: [
{ id: "ibm/granite-3-3-8b-instruct", display_name: "Granite 3.3 8B Instruct" },
{ model: "openai/gpt-4o", provider: "openai" },
],
});
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "watsonx");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{
id: "ibm/granite-3-3-8b-instruct",
name: "Granite 3.3 8B Instruct",
owned_by: "watsonx",
},
{
id: "openai/gpt-4o",
name: "openai/gpt-4o",
owned_by: "openai",
},
]);
});
test("provider models route discovers OCI OpenAI-compatible models and forwards the project header", async () => {
const connection = await seedConnection("oci", {
apiKey: "oci-key",
projectId: "ocid1.generativeaiproject.oc1.us-chicago-1.demo",
providerSpecificData: {
baseUrl: "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
},
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(
String(url),
"https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1/models"
);
assert.equal(init.method, "GET");
assert.equal(init.headers.Authorization, "Bearer oci-key");
assert.equal(init.headers["OpenAI-Project"], "ocid1.generativeaiproject.oc1.us-chicago-1.demo");
return Response.json({
data: [
{ id: "openai.gpt-oss-20b", display_name: "OpenAI GPT-OSS 20B" },
{ id: "google.gemini-2.5-pro" },
],
});
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "oci");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{
id: "openai.gpt-oss-20b",
name: "OpenAI GPT-OSS 20B",
owned_by: "oci",
},
{
id: "google.gemini-2.5-pro",
name: "google.gemini-2.5-pro",
owned_by: "oci",
},
]);
});
test("provider models route discovers Modal models from the configured OpenAI-compatible /v1 endpoint", async () => {
const connection = await seedConnection("modal", {
apiKey: "modal-key",
providerSpecificData: {
baseUrl: "https://alice--demo.modal.run/v1",
},
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://alice--demo.modal.run/v1/models");
assert.equal(init.method, "GET");
assert.equal(init.headers.Authorization, "Bearer modal-key");
return Response.json({
data: [
{ id: "Qwen/Qwen3-4B-Thinking-2507-FP8", display_name: "Qwen3 4B Thinking FP8" },
{ id: "google/gemma-4-26B-A4B-it" },
],
});
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "modal");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{
id: "Qwen/Qwen3-4B-Thinking-2507-FP8",
name: "Qwen3 4B Thinking FP8",
owned_by: "modal",
},
{
id: "google/gemma-4-26B-A4B-it",
name: "google/gemma-4-26B-A4B-it",
owned_by: "modal",
},
]);
});
test("provider models route discovers Reka models from the named OpenAI-compatible /v1 endpoint", async () => {
const connection = await seedConnection("reka", {
apiKey: "reka-key",
providerSpecificData: {
baseUrl: "https://api.reka.ai/v1",
},
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://api.reka.ai/v1/models");
assert.equal(init.method, "GET");
assert.equal(init.headers.Authorization, "Bearer reka-key");
assert.equal(init.headers["X-Api-Key"], "reka-key");
return Response.json([{ id: "reka-core", name: "Reka Core" }, { id: "reka-flash" }]);
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "reka");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{
id: "reka-core",
name: "Reka Core",
owned_by: "reka",
},
{
id: "reka-flash",
name: "reka-flash",
owned_by: "reka",
},
]);
});
test("provider models route discovers SAP models from AI_API_URL derived from deploymentUrl", async () => {
const connection = await seedConnection("sap", {
apiKey: "sap-key",
providerSpecificData: {
baseUrl: "https://sap.example.com/v2/lm/deployments/demo-deployment",
resourceGroup: "shared",
},
});
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://sap.example.com/v2/lm/scenarios/foundation-models/models");
assert.equal(init.method, "GET");
assert.equal(init.headers.Authorization, "Bearer sap-key");
assert.equal(init.headers["AI-Resource-Group"], "shared");
return Response.json({
resources: [
{ model: "gpt-4o", displayName: "GPT-4o", provider: "OpenAI" },
{ model: "mistralai--mistral-medium-instruct", provider: "Mistral AI" },
],
});
};
const response = await callRoute(connection.id);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.provider, "sap");
assert.equal(body.source, "api");
assert.deepEqual(body.models, [
{
id: "gpt-4o",
name: "GPT-4o",
owned_by: "OpenAI",
},
{
id: "mistralai--mistral-medium-instruct",
name: "mistralai--mistral-medium-instruct",
owned_by: "Mistral AI",
},
]);
});
test("provider models route rejects unsupported providers without a models config", async () => {
const connection = await seedConnection("unsupported-provider", {
apiKey: "sk-unsupported",

View File

@@ -29,6 +29,23 @@ test("azure-openai validation accepts a successful deployments probe", async ()
assert.equal(result.method, "azure_probe");
});
test("azure-ai validation accepts a successful v1 models probe", async () => {
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://my-foundry.services.ai.azure.com/openai/v1/models");
assert.equal((init.headers as Record<string, string>)["api-key"], "azure-ai-key");
return new Response(JSON.stringify({ data: [] }), { status: 200 });
};
const result = await validateProviderApiKey({
provider: "azure-ai",
apiKey: "azure-ai-key",
providerSpecificData: { baseUrl: "https://my-foundry.services.ai.azure.com" },
});
assert.equal(result.valid, true);
assert.equal(result.method, "azure_ai_models");
});
test("vertex-partner validation reuses the Vertex service account branch", async () => {
const invalid = await validateProviderApiKey({
provider: "vertex-partner",

View File

@@ -156,6 +156,25 @@ test("embedding and rerank specialty validators surface auth failures for Voyage
assert.equal(jina.error, "Invalid API key");
});
test("gitlab specialty validator accepts PAT auth on the direct access endpoint", async () => {
globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://gitlab.com/api/v4/code_suggestions/direct_access");
assert.equal((init.headers as Record<string, string>).Authorization, "Bearer glpat-test");
return new Response(JSON.stringify({ token: "short-lived" }), { status: 200 });
};
const result = await validateProviderApiKey({ provider: "gitlab", apiKey: "glpat-test" });
assert.equal(result.valid, true);
});
test("gitlab specialty validator treats 401 as invalid PAT", async () => {
globalThis.fetch = async () =>
new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 });
const result = await validateProviderApiKey({ provider: "gitlab", apiKey: "glpat-bad" });
assert.equal(result.error, "Invalid API key");
});
test("web-cookie provider validators accept valid Grok, Perplexity, Blackbox and Muse Spark session cookies", async () => {
const calls = [];
globalThis.fetch = async (url, init = {}) => {
@@ -346,7 +365,7 @@ test("search provider validators cover success, client errors, server errors and
assert.equal(calls[0].init.headers["User-Agent"], "SearchSuite/1.0");
});
test("extended search provider validators cover Google PSE, Linkup, SearchAPI and SearXNG", async () => {
test("extended search provider validators cover Google PSE, Linkup, SearchAPI, You.com and SearXNG", async () => {
const originalAllowPrivateProviderUrls = process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS = "true";
const calls = [];
@@ -363,6 +382,9 @@ test("extended search provider validators cover Google PSE, Linkup, SearchAPI an
if (target.startsWith("https://www.searchapi.io/api/v1/search")) {
return new Response(JSON.stringify({ organic_results: [] }), { status: 200 });
}
if (target.startsWith("https://ydc-index.io/v1/search")) {
return new Response(JSON.stringify({ results: { web: [] } }), { status: 200 });
}
if (target.startsWith("http://localhost:9999/search")) {
return new Response(JSON.stringify({ results: [] }), { status: 200 });
}
@@ -382,6 +404,10 @@ test("extended search provider validators cover Google PSE, Linkup, SearchAPI an
provider: "searchapi-search",
apiKey: "searchapi-key",
});
const youcom = await validateProviderApiKey({
provider: "youcom-search",
apiKey: "you-key",
});
const searxng = await validateProviderApiKey({
provider: "searxng-search",
providerSpecificData: { baseUrl: "http://localhost:9999/search" },
@@ -390,10 +416,12 @@ test("extended search provider validators cover Google PSE, Linkup, SearchAPI an
assert.equal(google.valid, true);
assert.equal(linkup.valid, true);
assert.equal(searchapi.valid, true);
assert.equal(youcom.valid, true);
assert.equal(searxng.valid, true);
assert.match(calls[0].url, /cx=engine-id/);
assert.equal(calls[1].init.headers.Authorization, "Bearer linkup-key");
assert.match(calls[2].url, /api_key=searchapi-key/);
assert.equal(calls[3].init.headers["X-API-Key"], "you-key");
} finally {
if (originalAllowPrivateProviderUrls === undefined) {
delete process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
@@ -869,3 +897,430 @@ test("specialty validators surface missing base URLs and invalid auth for Heroku
assert.equal(snowflakeInvalid.error, "Invalid API key");
assert.equal(gigachatInvalid.error, "Invalid API key");
});
test("specialty validator accepts DataRobot gateway and deployment credentials", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://app.datarobot.com/genai/llmgw/catalog/") {
assert.equal((init.headers as Record<string, string>).Authorization, "Bearer dr-key");
return new Response(
JSON.stringify({
data: [{ model: "azure/gpt-5-mini-2025-08-07", isActive: true }],
}),
{ status: 200 }
);
}
if (
target ===
"https://app.datarobot.com/api/v2/deployments/65f5b2b7c8f8c4b257e0d123/chat/completions"
) {
assert.equal((init.headers as Record<string, string>).Authorization, "Bearer dr-deploy-key");
const body = JSON.parse(String(init.body));
assert.equal(body.model, "datarobot-deployed-llm");
return new Response(JSON.stringify({ error: "bad request" }), { status: 400 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const gateway = await validateProviderApiKey({
provider: "datarobot",
apiKey: "dr-key",
});
const deployment = await validateProviderApiKey({
provider: "datarobot",
apiKey: "dr-deploy-key",
providerSpecificData: {
baseUrl: "https://app.datarobot.com/api/v2/deployments/65f5b2b7c8f8c4b257e0d123",
},
});
assert.equal(gateway.valid, true);
assert.equal(deployment.valid, true);
});
test("specialty validator rejects invalid DataRobot credentials", async () => {
globalThis.fetch = async (url) => {
const target = String(url);
if (target === "https://app.datarobot.com/genai/llmgw/catalog/") {
return new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 });
}
if (
target ===
"https://app.datarobot.com/api/v2/deployments/65f5b2b7c8f8c4b257e0d123/chat/completions"
) {
return new Response(JSON.stringify({ error: "forbidden" }), { status: 403 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const gateway = await validateProviderApiKey({
provider: "datarobot",
apiKey: "dr-key",
});
const deployment = await validateProviderApiKey({
provider: "datarobot",
apiKey: "dr-deploy-key",
providerSpecificData: {
baseUrl: "https://app.datarobot.com/api/v2/deployments/65f5b2b7c8f8c4b257e0d123",
},
});
assert.equal(gateway.error, "Invalid API key");
assert.equal(deployment.error, "Invalid API key");
});
test("specialty validators accept watsonx, OCI and SAP enterprise gateways", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://ca-tor.ml.cloud.ibm.com/ml/gateway/v1/models") {
assert.equal((init.headers as Record<string, string>).Authorization, "Bearer watsonx-key");
return new Response(JSON.stringify({ data: [] }), { status: 200 });
}
if (
target === "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1/models"
) {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Bearer oci-key");
assert.equal(headers["OpenAI-Project"], "ocid1.generativeaiproject.oc1.us-chicago-1.demo");
return new Response(JSON.stringify({ data: [] }), { status: 200 });
}
if (target === "https://sap.example.com/v2/lm/scenarios/foundation-models/models") {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Bearer sap-key");
assert.equal(headers["AI-Resource-Group"], "shared");
return new Response(JSON.stringify({ resources: [] }), { status: 200 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const watsonx = await validateProviderApiKey({
provider: "watsonx",
apiKey: "watsonx-key",
providerSpecificData: { baseUrl: "https://ca-tor.ml.cloud.ibm.com" },
});
const oci = await validateProviderApiKey({
provider: "oci",
apiKey: "oci-key",
providerSpecificData: {
baseUrl: "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
projectId: "ocid1.generativeaiproject.oc1.us-chicago-1.demo",
},
});
const sap = await validateProviderApiKey({
provider: "sap",
apiKey: "sap-key",
providerSpecificData: {
baseUrl: "https://sap.example.com/v2/lm/deployments/demo-deployment",
resourceGroup: "shared",
},
});
assert.equal(watsonx.valid, true);
assert.equal(watsonx.method, "watsonx_models");
assert.equal(oci.valid, true);
assert.equal(oci.method, "oci_models");
assert.equal(sap.valid, true);
assert.equal(sap.method, "sap_models");
});
test("specialty validator accepts Bedrock mantle discovery and runtime chat fallback", async () => {
let runtimeChatProbed = false;
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://bedrock-mantle.us-east-1.api.aws/v1/models") {
assert.equal((init.headers as Record<string, string>).Authorization, "Bearer bedrock-key");
return new Response(JSON.stringify({ data: [] }), { status: 200 });
}
if (target === "https://bedrock-runtime.us-east-1.amazonaws.com/openai/v1/models") {
assert.equal((init.headers as Record<string, string>).Authorization, "Bearer runtime-key");
return new Response(JSON.stringify({ error: "not found" }), { status: 404 });
}
if (target === "https://bedrock-runtime.us-east-1.amazonaws.com/openai/v1/chat/completions") {
runtimeChatProbed = true;
const body = JSON.parse(String(init.body || "{}"));
assert.equal((init.headers as Record<string, string>).Authorization, "Bearer runtime-key");
assert.equal(body.model, "openai.gpt-oss-120b-1:0");
return new Response(JSON.stringify({ error: "bad request" }), { status: 400 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const mantle = await validateProviderApiKey({
provider: "bedrock",
apiKey: "bedrock-key",
});
const runtime = await validateProviderApiKey({
provider: "bedrock",
apiKey: "runtime-key",
providerSpecificData: {
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
},
});
assert.equal(mantle.valid, true);
assert.equal(runtime.valid, true);
assert.equal(runtimeChatProbed, true);
});
test("specialty validator rejects invalid Bedrock credentials", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://bedrock-mantle.us-east-1.api.aws/v1/models") {
assert.equal((init.headers as Record<string, string>).Authorization, "Bearer bedrock-key");
return new Response(JSON.stringify({ error: "forbidden" }), { status: 403 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const bedrock = await validateProviderApiKey({
provider: "bedrock",
apiKey: "bedrock-key",
});
assert.equal(bedrock.error, "Invalid API key");
});
test("specialty validators reject invalid watsonx, OCI and SAP credentials", async () => {
globalThis.fetch = async (url) => {
const target = String(url);
if (target === "https://ca-tor.ml.cloud.ibm.com/ml/gateway/v1/models") {
return new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 });
}
if (
target === "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1/models"
) {
return new Response(JSON.stringify({ error: "forbidden" }), { status: 403 });
}
if (target === "https://sap.example.com/v2/lm/scenarios/foundation-models/models") {
return new Response(JSON.stringify({ error: "forbidden" }), { status: 403 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const watsonx = await validateProviderApiKey({
provider: "watsonx",
apiKey: "watsonx-key",
providerSpecificData: { baseUrl: "https://ca-tor.ml.cloud.ibm.com" },
});
const oci = await validateProviderApiKey({
provider: "oci",
apiKey: "oci-key",
providerSpecificData: {
baseUrl: "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
},
});
const sap = await validateProviderApiKey({
provider: "sap",
apiKey: "sap-key",
providerSpecificData: {
baseUrl: "https://sap.example.com/v2/lm/deployments/demo-deployment",
},
});
assert.equal(watsonx.error, "Invalid API key");
assert.equal(oci.error, "Invalid API key");
assert.equal(sap.error, "Invalid API key");
});
test("specialty validator accepts Modal OpenAI-compatible deployments", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://alice--demo.modal.run/v1/models") {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Bearer modal-key");
return new Response(JSON.stringify({ data: [] }), { status: 200 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const modal = await validateProviderApiKey({
provider: "modal",
apiKey: "modal-key",
providerSpecificData: {
baseUrl: "https://alice--demo.modal.run/v1",
},
});
assert.equal(modal.valid, true);
});
test("specialty validator rejects invalid Modal credentials", async () => {
globalThis.fetch = async (url) => {
const target = String(url);
if (target === "https://alice--demo.modal.run/v1/models") {
return new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const modal = await validateProviderApiKey({
provider: "modal",
apiKey: "modal-key",
providerSpecificData: {
baseUrl: "https://alice--demo.modal.run/v1",
},
});
assert.equal(modal.error, "Invalid API key");
});
test("specialty validator accepts Clarifai credentials through the OpenAI-compatible models probe", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://api.clarifai.com/v2/ext/openai/v1/models") {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Key clarifai-pat");
return new Response(
JSON.stringify({ data: [{ id: "openai/chat-completion/models/gpt-oss-120b" }] }),
{ status: 200 }
);
}
throw new Error(`unexpected fetch: ${target}`);
};
const clarifai = await validateProviderApiKey({
provider: "clarifai",
apiKey: "clarifai-pat",
});
assert.equal(clarifai.valid, true);
assert.equal(clarifai.method, "clarifai_models");
});
test("specialty validator rejects invalid Clarifai credentials", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://api.clarifai.com/v2/ext/openai/v1/models") {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Key clarifai-bad");
return new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const clarifai = await validateProviderApiKey({
provider: "clarifai",
apiKey: "clarifai-bad",
});
assert.equal(clarifai.error, "Invalid API key");
});
test("specialty validator accepts Reka credentials through the models probe with dual auth headers", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://api.reka.ai/v1/models") {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Bearer reka-key");
assert.equal(headers["X-Api-Key"], "reka-key");
return new Response(JSON.stringify([{ id: "reka-core" }]), { status: 200 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const reka = await validateProviderApiKey({
provider: "reka",
apiKey: "reka-key",
});
assert.equal(reka.valid, true);
assert.equal(reka.method, "reka_models");
});
test("specialty validator rejects invalid Reka credentials", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://api.reka.ai/v1/models") {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Bearer reka-bad");
assert.equal(headers["X-Api-Key"], "reka-bad");
return new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const reka = await validateProviderApiKey({
provider: "reka",
apiKey: "reka-bad",
});
assert.equal(reka.error, "Invalid API key");
});
test("specialty validator accepts NLP Cloud credentials on the chatbot endpoint", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://api.nlpcloud.io/v1/gpu/chatdolphin/chatbot") {
const headers = init.headers as Record<string, string>;
const body = JSON.parse(String(init.body));
assert.equal(headers.Authorization, "Token nlpc-key");
assert.equal(body.input, "test");
return new Response(JSON.stringify({ response: "ok" }), { status: 200 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const nlpCloud = await validateProviderApiKey({
provider: "nlpcloud",
apiKey: "nlpc-key",
});
assert.equal(nlpCloud.valid, true);
assert.equal(nlpCloud.method, "nlpcloud_chatbot");
});
test("specialty validator rejects invalid NLP Cloud credentials", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://api.nlpcloud.io/v1/gpu/chatdolphin/chatbot") {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Token nlpc-bad");
return new Response(JSON.stringify({ detail: "forbidden" }), { status: 403 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const nlpCloud = await validateProviderApiKey({
provider: "nlpcloud",
apiKey: "nlpc-bad",
});
assert.equal(nlpCloud.error, "Invalid API key");
});

View File

@@ -219,9 +219,22 @@ test("static catalog entries resolve local, search, audio, web-cookie and upstre
const localProvider = providerPageUtils.resolveDashboardProviderInfo("sdwebui");
const localChatProvider = providerPageUtils.resolveDashboardProviderInfo("lm-studio");
const searchProvider = providerPageUtils.resolveDashboardProviderInfo("brave-search");
const youcomSearchProvider = providerPageUtils.resolveDashboardProviderInfo("youcom-search");
const audioProvider = providerPageUtils.resolveDashboardProviderInfo("assemblyai");
const webCookieProvider = providerPageUtils.resolveDashboardProviderInfo("grok-web");
const apiKeyProvider = providerPageUtils.resolveDashboardProviderInfo("glhf");
const gitlabProvider = providerPageUtils.resolveDashboardProviderInfo("gitlab");
const gitlabDuoProvider = providerPageUtils.resolveDashboardProviderInfo("gitlab-duo");
const chutesProvider = providerPageUtils.resolveDashboardProviderInfo("chutes");
const datarobotProvider = providerPageUtils.resolveDashboardProviderInfo("datarobot");
const clarifaiProvider = providerPageUtils.resolveDashboardProviderInfo("clarifai");
const azureAiProvider = providerPageUtils.resolveDashboardProviderInfo("azure-ai");
const watsonxProvider = providerPageUtils.resolveDashboardProviderInfo("watsonx");
const ociProvider = providerPageUtils.resolveDashboardProviderInfo("oci");
const sapProvider = providerPageUtils.resolveDashboardProviderInfo("sap");
const modalProvider = providerPageUtils.resolveDashboardProviderInfo("modal");
const rekaProvider = providerPageUtils.resolveDashboardProviderInfo("reka");
const nlpCloudProvider = providerPageUtils.resolveDashboardProviderInfo("nlpcloud");
const embeddingProvider = providerPageUtils.resolveDashboardProviderInfo("voyage-ai");
const rerankProvider = providerPageUtils.resolveDashboardProviderInfo("jina-ai");
const perplexityWebProvider = providerPageUtils.resolveDashboardProviderInfo("perplexity-web");
@@ -239,12 +252,38 @@ test("static catalog entries resolve local, search, audio, web-cookie and upstre
assert.equal(searchProvider?.category, "search");
assert.equal(searchProvider?.name, providers.SEARCH_PROVIDERS["brave-search"].name);
assert.equal(youcomSearchProvider?.category, "search");
assert.equal(youcomSearchProvider?.name, providers.SEARCH_PROVIDERS["youcom-search"].name);
assert.equal(audioProvider?.category, "audio");
assert.equal(audioProvider?.name, providers.AUDIO_ONLY_PROVIDERS.assemblyai.name);
assert.equal(apiKeyProvider?.category, "apikey");
assert.equal(apiKeyProvider?.name, providers.APIKEY_PROVIDERS.glhf.name);
assert.equal(gitlabProvider?.category, "apikey");
assert.equal(gitlabProvider?.name, providers.APIKEY_PROVIDERS.gitlab.name);
assert.equal(gitlabDuoProvider?.category, "oauth");
assert.equal(gitlabDuoProvider?.name, providers.OAUTH_PROVIDERS["gitlab-duo"].name);
assert.equal(chutesProvider?.category, "apikey");
assert.equal(chutesProvider?.name, providers.APIKEY_PROVIDERS.chutes.name);
assert.equal(datarobotProvider?.category, "apikey");
assert.equal(datarobotProvider?.name, providers.APIKEY_PROVIDERS.datarobot.name);
assert.equal(clarifaiProvider?.category, "apikey");
assert.equal(clarifaiProvider?.name, providers.APIKEY_PROVIDERS.clarifai.name);
assert.equal(azureAiProvider?.category, "apikey");
assert.equal(azureAiProvider?.name, providers.APIKEY_PROVIDERS["azure-ai"].name);
assert.equal(watsonxProvider?.category, "apikey");
assert.equal(watsonxProvider?.name, providers.APIKEY_PROVIDERS.watsonx.name);
assert.equal(ociProvider?.category, "apikey");
assert.equal(ociProvider?.name, providers.APIKEY_PROVIDERS.oci.name);
assert.equal(sapProvider?.category, "apikey");
assert.equal(sapProvider?.name, providers.APIKEY_PROVIDERS.sap.name);
assert.equal(modalProvider?.category, "apikey");
assert.equal(modalProvider?.name, providers.APIKEY_PROVIDERS.modal.name);
assert.equal(rekaProvider?.category, "apikey");
assert.equal(rekaProvider?.name, providers.APIKEY_PROVIDERS.reka.name);
assert.equal(nlpCloudProvider?.category, "apikey");
assert.equal(nlpCloudProvider?.name, providers.APIKEY_PROVIDERS.nlpcloud.name);
assert.equal(embeddingProvider?.category, "apikey");
assert.equal(embeddingProvider?.name, providers.APIKEY_PROVIDERS["voyage-ai"].name);
@@ -274,9 +313,21 @@ test("static catalog entries resolve local, search, audio, web-cookie and upstre
test("managed provider connection ids include supported static categories and exclude upstream proxy", () => {
assert.equal(providerCatalog.isManagedProviderConnectionId("qoder"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("glhf"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("gitlab"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("cablyai"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("thebai"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("fenayai"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("chutes"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("datarobot"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("clarifai"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("azure-ai"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("bedrock"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("watsonx"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("oci"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("sap"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("modal"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("reka"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("nlpcloud"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("voyage-ai"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("jina-ai"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("sdwebui"), true);
@@ -288,6 +339,7 @@ test("managed provider connection ids include supported static categories and ex
assert.equal(providerCatalog.isManagedProviderConnectionId("blackbox-web"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("muse-spark-web"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("brave-search"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("youcom-search"), true);
assert.equal(providerCatalog.isManagedProviderConnectionId("cliproxyapi"), false);
assert.equal(providerCatalog.isManagedProviderConnectionId("claude"), false);
});
@@ -308,9 +360,22 @@ test("grok-web taxonomy stays web-cookie only and does not leak into api-key ent
assert.equal("muse-spark-web" in providers.APIKEY_PROVIDERS, false);
assert.equal("muse-spark-web" in providers.WEB_COOKIE_PROVIDERS, true);
assert.equal("glhf" in providers.APIKEY_PROVIDERS, true);
assert.equal("gitlab" in providers.APIKEY_PROVIDERS, true);
assert.equal("gitlab-duo" in providers.OAUTH_PROVIDERS, true);
assert.equal("cablyai" in providers.APIKEY_PROVIDERS, true);
assert.equal("thebai" in providers.APIKEY_PROVIDERS, true);
assert.equal("fenayai" in providers.APIKEY_PROVIDERS, true);
assert.equal("chutes" in providers.APIKEY_PROVIDERS, true);
assert.equal("datarobot" in providers.APIKEY_PROVIDERS, true);
assert.equal("clarifai" in providers.APIKEY_PROVIDERS, true);
assert.equal("azure-ai" in providers.APIKEY_PROVIDERS, true);
assert.equal("bedrock" in providers.APIKEY_PROVIDERS, true);
assert.equal("watsonx" in providers.APIKEY_PROVIDERS, true);
assert.equal("oci" in providers.APIKEY_PROVIDERS, true);
assert.equal("sap" in providers.APIKEY_PROVIDERS, true);
assert.equal("modal" in providers.APIKEY_PROVIDERS, true);
assert.equal("reka" in providers.APIKEY_PROVIDERS, true);
assert.equal("nlpcloud" in providers.APIKEY_PROVIDERS, true);
assert.equal("voyage-ai" in providers.APIKEY_PROVIDERS, true);
assert.equal("jina-ai" in providers.APIKEY_PROVIDERS, true);
@@ -348,6 +413,10 @@ test("grok-web taxonomy stays web-cookie only and does not leak into api-key ent
apiKeyEntries.some((entry) => entry.providerId === "glhf"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "gitlab"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "cablyai"),
true
@@ -360,6 +429,50 @@ test("grok-web taxonomy stays web-cookie only and does not leak into api-key ent
apiKeyEntries.some((entry) => entry.providerId === "fenayai"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "chutes"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "datarobot"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "clarifai"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "azure-ai"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "bedrock"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "watsonx"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "oci"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "sap"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "modal"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "reka"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "nlpcloud"),
true
);
assert.equal(
apiKeyEntries.some((entry) => entry.providerId === "voyage-ai"),
true

View File

@@ -38,6 +38,14 @@ test("providers route accepts managed local, audio, web-cookie and search provid
name: "GLHF Chat",
},
},
{
provider: "gitlab",
body: {
provider: "gitlab",
apiKey: "glpat-test",
name: "GitLab Duo PAT",
},
},
{
provider: "cablyai",
body: {
@@ -62,6 +70,100 @@ test("providers route accepts managed local, audio, web-cookie and search provid
name: "FenayAI Primary",
},
},
{
provider: "chutes",
body: {
provider: "chutes",
apiKey: "chutes-key",
name: "Chutes Primary",
},
},
{
provider: "datarobot",
body: {
provider: "datarobot",
apiKey: "datarobot-key",
name: "DataRobot Primary",
},
},
{
provider: "clarifai",
body: {
provider: "clarifai",
apiKey: "clarifai-pat",
name: "Clarifai Primary",
},
},
{
provider: "azure-ai",
body: {
provider: "azure-ai",
apiKey: "azure-ai-key",
name: "Azure AI Foundry Primary",
},
},
{
provider: "bedrock",
body: {
provider: "bedrock",
apiKey: "bedrock-key",
name: "Bedrock Mantle Primary",
},
},
{
provider: "watsonx",
body: {
provider: "watsonx",
apiKey: "watsonx-key",
name: "watsonx Gateway Primary",
},
},
{
provider: "oci",
body: {
provider: "oci",
apiKey: "oci-key",
name: "OCI GenAI Primary",
},
},
{
provider: "sap",
body: {
provider: "sap",
apiKey: "sap-key",
name: "SAP GenAI Primary",
},
},
{
provider: "modal",
body: {
provider: "modal",
apiKey: "modal-key",
name: "Modal Primary",
providerSpecificData: {
baseUrl: "https://alice--demo.modal.run/v1",
},
},
},
{
provider: "reka",
body: {
provider: "reka",
apiKey: "reka-key",
name: "Reka Primary",
providerSpecificData: {
baseUrl: "https://api.reka.ai/v1",
},
},
},
{
provider: "nlpcloud",
body: {
provider: "nlpcloud",
apiKey: "nlpc-key",
name: "NLP Cloud Primary",
},
},
{
provider: "voyage-ai",
body: {
@@ -209,6 +311,14 @@ test("providers route accepts managed local, audio, web-cookie and search provid
},
},
},
{
provider: "youcom-search",
body: {
provider: "youcom-search",
apiKey: "you-key",
name: "You.com Search",
},
},
{
provider: "searxng-search",
body: {

View File

@@ -284,6 +284,7 @@ test("OAuth test config covers all expected providers", () => {
"gemini-cli",
"antigravity",
"github",
"gitlab-duo",
"qoder",
"qwen",
"cursor",
@@ -302,6 +303,7 @@ test("OAuth test config covers all expected providers", () => {
"gemini-cli",
"antigravity",
"github",
"gitlab-duo",
"qoder",
"qwen",
"cursor",
@@ -325,6 +327,7 @@ test("Refreshable OAuth providers are correctly identified", () => {
"codex",
"gemini-cli",
"antigravity",
"gitlab-duo",
"qoder",
"qwen",
"kimi-coding",
@@ -336,6 +339,6 @@ test("Refreshable OAuth providers are correctly identified", () => {
// Verify these two sets are mutually exclusive and cover all providers
const allProviders = [...refreshable, ...nonRefreshable];
assert.equal(allProviders.length, 13);
assert.equal(new Set(allProviders).size, 13);
assert.equal(allProviders.length, 14);
assert.equal(new Set(allProviders).size, 14);
});

View File

@@ -449,6 +449,76 @@ test("handleSearch builds SearchAPI requests and normalizes organic results", as
}
});
test("handleSearch builds You.com requests with livecrawl and normalizes unified response sections", async () => {
const originalFetch = globalThis.fetch;
let capturedUrl;
let capturedHeaders;
globalThis.fetch = async (url, init = {}) => {
capturedUrl = String(url);
capturedHeaders = init.headers;
return new Response(
JSON.stringify({
results: {
web: [
{
title: "You.com result",
url: "https://you.example.com/page",
description: "Fallback description",
snippets: ["Primary snippet"],
page_age: "2026-04-23T10:00:00Z",
favicon_url: "https://you.example.com/favicon.ico",
thumbnail_url: "https://you.example.com/thumb.png",
markdown: "# Full page markdown",
},
],
news: [],
},
metadata: { search_uuid: "uuid-1", latency: 0.5 },
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleSearch({
query: "you search",
provider: "youcom-search",
maxResults: 2,
searchType: "web",
country: "US",
language: "en",
timeRange: "week",
offset: 4,
domainFilter: ["docs.you.com"],
contentOptions: { full_page: true, format: "markdown" },
credentials: { apiKey: "you-key" },
log: null,
});
const url = new URL(capturedUrl);
assert.equal(url.origin + url.pathname, "https://ydc-index.io/v1/search");
assert.equal(url.searchParams.get("query"), "you search");
assert.equal(url.searchParams.get("count"), "2");
assert.equal(url.searchParams.get("freshness"), "week");
assert.equal(url.searchParams.get("offset"), "2");
assert.equal(url.searchParams.get("country"), "US");
assert.equal(url.searchParams.get("language"), "en");
assert.equal(url.searchParams.get("include_domains"), "docs.you.com");
assert.equal(url.searchParams.get("livecrawl"), "web");
assert.equal(url.searchParams.get("livecrawl_formats"), "markdown");
assert.equal(capturedHeaders["X-API-Key"], "you-key");
assert.equal(result.success, true);
assert.equal(result.data.provider, "youcom-search");
assert.equal(result.data.results[0].snippet, "Primary snippet");
assert.equal(result.data.results[0].content?.format, "markdown");
assert.equal(result.data.results[0].content?.text, "# Full page markdown");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleSearch builds SearXNG requests with custom baseUrl and no apiKey", async () => {
const originalFetch = globalThis.fetch;
let capturedUrl;

View File

@@ -19,7 +19,7 @@ const { computeCacheKey, getOrCoalesce, getCacheStats, SEARCH_CACHE_DEFAULT_TTL_
// ─── Registry Tests ──────────────────────────────────────────
test("SEARCH_PROVIDERS has all 9 providers", () => {
test("SEARCH_PROVIDERS has all 10 providers", () => {
assert.ok(SEARCH_PROVIDERS["serper-search"], "serper should exist");
assert.ok(SEARCH_PROVIDERS["brave-search"], "brave should exist");
assert.ok(SEARCH_PROVIDERS["perplexity-search"], "perplexity-search should exist");
@@ -28,8 +28,9 @@ test("SEARCH_PROVIDERS has all 9 providers", () => {
assert.ok(SEARCH_PROVIDERS["google-pse-search"], "google-pse should exist");
assert.ok(SEARCH_PROVIDERS["linkup-search"], "linkup should exist");
assert.ok(SEARCH_PROVIDERS["searchapi-search"], "searchapi should exist");
assert.ok(SEARCH_PROVIDERS["youcom-search"], "youcom should exist");
assert.ok(SEARCH_PROVIDERS["searxng-search"], "searxng should exist");
assert.equal(Object.keys(SEARCH_PROVIDERS).length, 9);
assert.equal(Object.keys(SEARCH_PROVIDERS).length, 10);
});
test("serper-search config is correct", () => {
@@ -108,6 +109,16 @@ test("searchapi-search config is correct", () => {
assert.deepEqual(s.searchTypes, ["web", "news"]);
});
test("youcom-search config is correct", () => {
const y = SEARCH_PROVIDERS["youcom-search"];
assert.equal(y.id, "youcom-search");
assert.equal(y.method, "GET");
assert.equal(y.authHeader, "x-api-key");
assert.equal(y.baseUrl, "https://ydc-index.io/v1/search");
assert.equal(y.costPerQuery, 0.005);
assert.deepEqual(y.searchTypes, ["web", "news"]);
});
test("searxng-search config is correct", () => {
const s = SEARCH_PROVIDERS["searxng-search"];
assert.equal(s.id, "searxng-search");
@@ -119,7 +130,7 @@ test("searxng-search config is correct", () => {
test("getAllSearchProviders returns flat list", () => {
const all = getAllSearchProviders();
assert.equal(all.length, 9);
assert.equal(all.length, 10);
assert.ok(all.some((p) => p.id === "serper-search"));
assert.ok(all.some((p) => p.id === "brave-search"));
assert.ok(all.some((p) => p.id === "perplexity-search"));
@@ -128,6 +139,7 @@ test("getAllSearchProviders returns flat list", () => {
assert.ok(all.some((p) => p.id === "google-pse-search"));
assert.ok(all.some((p) => p.id === "linkup-search"));
assert.ok(all.some((p) => p.id === "searchapi-search"));
assert.ok(all.some((p) => p.id === "youcom-search"));
assert.ok(all.some((p) => p.id === "searxng-search"));
// Each entry should have id, name, searchTypes
for (const p of all) {
@@ -323,6 +335,7 @@ test("v1SearchSchema accepts new search providers", async () => {
"google-pse-search",
"linkup-search",
"searchapi-search",
"youcom-search",
"searxng-search",
] as const;

View File

@@ -45,14 +45,14 @@ test.after(() => {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("v1 search GET lists all 9 search providers", async () => {
test("v1 search GET lists all 10 search providers", async () => {
const response = await searchRoute.GET();
const body = (await response.json()) as any;
const ids = body.data.map((item: { id: string }) => item.id);
assert.equal(response.status, 200);
assert.equal(body.object, "list");
assert.equal(body.data.length, 9);
assert.equal(body.data.length, 10);
assert.deepEqual(ids, [
"serper-search",
"brave-search",
@@ -62,6 +62,7 @@ test("v1 search GET lists all 9 search providers", async () => {
"google-pse-search",
"linkup-search",
"searchapi-search",
"youcom-search",
"searxng-search",
]);
});
@@ -126,6 +127,73 @@ test("v1 search POST uses stored Linkup credentials and returns normalized resul
}
});
test("v1 search POST uses stored You.com credentials and returns unified news results", async () => {
await seedConnection("youcom-search", { apiKey: "you-key" });
const originalFetch = globalThis.fetch;
let capturedUrl = "";
let capturedInit: RequestInit | undefined;
globalThis.fetch = async (url, init = {}) => {
capturedUrl = String(url);
capturedInit = init;
return new Response(
JSON.stringify({
results: {
web: [],
news: [
{
title: "You.com news result",
description: "Breaking update",
page_age: "2026-04-23T12:00:00Z",
url: "https://news.example.com/you",
thumbnail_url: "https://news.example.com/thumb.png",
},
],
},
metadata: { search_uuid: "uuid-1", latency: 0.42 },
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const response = await searchRoute.POST(
new Request("http://localhost/api/v1/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: "latest ai regulation",
provider: "youcom-search",
max_results: 1,
search_type: "news",
time_range: "week",
content: { full_page: true, format: "markdown" },
}),
})
);
const body = (await response.json()) as any;
const url = new URL(capturedUrl);
assert.equal(response.status, 200);
assert.equal(url.origin + url.pathname, "https://ydc-index.io/v1/search");
assert.equal(url.searchParams.get("query"), "latest ai regulation");
assert.equal(url.searchParams.get("count"), "1");
assert.equal(url.searchParams.get("freshness"), "week");
assert.equal(url.searchParams.get("livecrawl"), "news");
assert.equal(url.searchParams.get("livecrawl_formats"), "markdown");
assert.equal((capturedInit?.headers as Record<string, string>)["X-API-Key"], "you-key");
assert.equal(body.provider, "youcom-search");
assert.equal(body.results.length, 1);
assert.equal(body.results[0].title, "You.com news result");
assert.equal(body.results[0].snippet, "Breaking update");
assert.equal(body.results[0].citation.provider, "youcom-search");
} finally {
globalThis.fetch = originalFetch;
}
});
test("v1 search POST accepts authless SearXNG with provider_options baseUrl", async () => {
const originalFetch = globalThis.fetch;
let capturedUrl = "";