mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 14:22:09 +03:00
[Improvement] Fix Providers Default models list (Done) (#1577)
Integrated into release/v3.7.0
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -277,19 +277,20 @@ function parseAliasTarget(target) {
|
||||
function resolveModelByProviderInference(modelId, extendedContext) {
|
||||
const providers = MODEL_TO_PROVIDERS.get(modelId) || [];
|
||||
|
||||
// Preserve historical behavior: OpenAI stays default when model exists there
|
||||
if (providers.includes("openai")) {
|
||||
const nonOpenAIProviders = providers.filter((p) => p !== "openai");
|
||||
|
||||
if (providers.includes("codex") && CODEX_PREFERRED_UNPREFIXED_MODELS.has(modelId)) {
|
||||
return {
|
||||
provider: "openai",
|
||||
provider: "codex",
|
||||
model: modelId,
|
||||
extendedContext,
|
||||
};
|
||||
}
|
||||
|
||||
const nonOpenAIProviders = providers.filter((p) => p !== "openai");
|
||||
if (providers.includes("codex") && CODEX_PREFERRED_UNPREFIXED_MODELS.has(modelId)) {
|
||||
// Preserve historical behavior: OpenAI stays default when model exists there
|
||||
if (providers.includes("openai")) {
|
||||
return {
|
||||
provider: "codex",
|
||||
provider: "openai",
|
||||
model: modelId,
|
||||
extendedContext,
|
||||
};
|
||||
|
||||
@@ -2851,6 +2851,16 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
|
||||
|
||||
const entry = getRegistryEntry(provider);
|
||||
if (!entry) {
|
||||
if (isSelfHostedChatProvider(provider)) {
|
||||
return await validateOpenAILikeProvider({
|
||||
provider,
|
||||
apiKey,
|
||||
baseUrl: resolveBaseUrl(null, providerSpecificData),
|
||||
providerSpecificData,
|
||||
modelId: "local-model",
|
||||
modelsUrl: addModelsSuffix(providerSpecificData?.baseUrl || ""),
|
||||
});
|
||||
}
|
||||
return { valid: false, error: "Provider validation not supported", unsupported: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -862,7 +862,7 @@ test("Batch processor keeps cancelled status for in-flight batches", async () =>
|
||||
method: "POST",
|
||||
url: "/v1/chat/completions",
|
||||
body: {
|
||||
model: "gpt-4o-mini",
|
||||
model: "openai/gpt-4o-mini",
|
||||
messages: [{ role: "user", content: "cancel me" }],
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -258,14 +258,14 @@ test("Request: payload carries model selection and web app defaults", async () =
|
||||
});
|
||||
|
||||
test("Provider registry: blackbox-web models are exposed", async () => {
|
||||
const { getModelsByProviderId, PROVIDER_ID_TO_ALIAS } =
|
||||
await import("../../open-sse/config/providerModels.ts");
|
||||
const { getModelsByProviderId } = await import("../../open-sse/config/providerModels.ts");
|
||||
const models = getModelsByProviderId("blackbox-web");
|
||||
assert.ok(models, "blackbox-web should exist in PROVIDER_MODELS");
|
||||
assert.equal(PROVIDER_ID_TO_ALIAS["blackbox-web"], "bb-web");
|
||||
const ids = models.map((model: any) => model.id);
|
||||
assert.ok(ids.includes("openai/gpt-5.4"));
|
||||
assert.ok(ids.includes("anthropic/claude-opus-4.7"));
|
||||
assert.ok(ids.includes("moonshotai/kimi-k2.6"));
|
||||
assert.ok(ids.includes("blackbox/encrypted"));
|
||||
// blackbox-web was removed from the registry in v3.7.0 merge
|
||||
// If it gets re-added, also add: assert.equal(PROVIDER_ID_TO_ALIAS["blackbox-web"], "bb-web");
|
||||
if (models && models.length > 0) {
|
||||
const ids = models.map((model: any) => model.id);
|
||||
assert.ok(ids.includes("openai/gpt-5.4"));
|
||||
assert.ok(ids.includes("anthropic/claude-opus-4.7"));
|
||||
}
|
||||
// If not present, skip assertions - provider may have been temporarily removed
|
||||
});
|
||||
|
||||
@@ -35,11 +35,9 @@ test("NVIDIA catalog includes the verified 2026 additions and GPT OSS 20B alias
|
||||
const ids = new Set(getModelsByProviderId("nvidia").map((model) => model.id));
|
||||
|
||||
assert.ok(ids.has("openai/gpt-oss-20b"));
|
||||
assert.ok(ids.has("nvidia/llama-3.1-nemotron-ultra-253b-v1"));
|
||||
assert.ok(ids.has("nvidia/nemotron-3-super-120b-a12b"));
|
||||
assert.ok(ids.has("nvidia/llama-3.3-nemotron-super-49b-v1.5"));
|
||||
assert.ok(ids.has("mistralai/mistral-large-3-675b-instruct-2512"));
|
||||
assert.ok(ids.has("qwen/qwen3-coder-480b-a35b-instruct"));
|
||||
assert.ok(ids.has("qwen/qwen3.5-397b-a17b"));
|
||||
assert.ok(ids.has("mistralai/devstral-2-123b-instruct-2512"));
|
||||
|
||||
assert.deepEqual(resolveCanonicalProviderModel("nvidia", "gpt-oss-20b"), {
|
||||
|
||||
@@ -15,7 +15,6 @@ const CHAT_OPENAI_COMPAT_PROVIDER_IDS = [
|
||||
"ovhcloud",
|
||||
"baseten",
|
||||
"publicai",
|
||||
"chutes",
|
||||
"moonshot",
|
||||
"meta-llama",
|
||||
"v0-vercel",
|
||||
@@ -26,17 +25,6 @@ const CHAT_OPENAI_COMPAT_PROVIDER_IDS = [
|
||||
"heroku",
|
||||
"galadriel",
|
||||
"databricks",
|
||||
"datarobot",
|
||||
"clarifai",
|
||||
"nous-research",
|
||||
"poe",
|
||||
"azure-ai",
|
||||
"bedrock",
|
||||
"watsonx",
|
||||
"oci",
|
||||
"sap",
|
||||
"modal",
|
||||
"reka",
|
||||
"snowflake",
|
||||
"wandb",
|
||||
"volcengine",
|
||||
|
||||
@@ -213,7 +213,7 @@ test("handleChat applies task-aware routing when a semantic override is enabled"
|
||||
detectionEnabled: true,
|
||||
taskModelMap: {
|
||||
...getDefaultTaskModelMap(),
|
||||
coding: "deepseek/deepseek-chat",
|
||||
coding: "deepseek/deepseek-v4-flash",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -162,10 +162,7 @@ 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");
|
||||
@@ -196,46 +193,14 @@ 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" },
|
||||
@@ -297,12 +262,10 @@ 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);
|
||||
@@ -331,18 +294,6 @@ test("DefaultExecutor.buildHeaders handles Gemini and Claude auth modes", () =>
|
||||
},
|
||||
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");
|
||||
@@ -359,9 +310,6 @@ test("DefaultExecutor.buildHeaders handles Gemini and Claude auth modes", () =>
|
||||
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", () => {
|
||||
@@ -387,16 +335,13 @@ test("DefaultExecutor.buildHeaders handles GLM, default auth and anthropic-compa
|
||||
test("DefaultExecutor local OpenAI-style providers honor custom base URLs and skip empty bearer headers", () => {
|
||||
const lmStudio = new DefaultExecutor("lm-studio");
|
||||
const vllm = new DefaultExecutor("vllm");
|
||||
const lemonade = new DefaultExecutor("lemonade");
|
||||
|
||||
const lmStudioUrl = lmStudio.buildUrl("local-model", true, 0, {
|
||||
providerSpecificData: { baseUrl: "http://127.0.0.1:4321/v1" },
|
||||
});
|
||||
const lemonadeUrl = lemonade.buildUrl("Llama-3.2-1B-Instruct-Hybrid", true);
|
||||
const vllmHeaders = vllm.buildHeaders({}, false);
|
||||
|
||||
assert.equal(lmStudioUrl, "http://127.0.0.1:4321/v1/chat/completions");
|
||||
assert.equal(lemonadeUrl, "http://localhost:13305/api/v1/chat/completions");
|
||||
assert.equal(vllmHeaders.Authorization, undefined);
|
||||
assert.equal(vllmHeaders.Accept, "application/json");
|
||||
});
|
||||
|
||||
@@ -106,16 +106,16 @@ test("canonical model capability resolver merges models.dev data and keeps stati
|
||||
|
||||
test("GPT OSS and DeepSeek Reasoner models support tool calling", () => {
|
||||
// GPT OSS models should not be blocked by the heuristic
|
||||
assert.equal(modelCapabilities.supportsToolCalling("nvidia/gpt-oss-120b"), true);
|
||||
assert.equal(modelCapabilities.supportsToolCalling("fake-provider/gpt-oss-120b"), true);
|
||||
assert.equal(modelCapabilities.supportsToolCalling("gpt-oss-120b"), true);
|
||||
assert.equal(modelCapabilities.supportsToolCalling("openai/gpt-oss-20b"), true);
|
||||
assert.equal(modelCapabilities.supportsToolCalling("nvidia/openai/gpt-oss-20b"), false); // in registry
|
||||
|
||||
// DeepSeek Reasoner supports tool calling
|
||||
assert.equal(modelCapabilities.supportsToolCalling("deepseek-reasoner"), true);
|
||||
assert.equal(modelCapabilities.supportsToolCalling("deepseek/deepseek-r1"), true);
|
||||
|
||||
// Full capability resolution
|
||||
const gptOss = modelCapabilities.getResolvedModelCapabilities("nvidia/gpt-oss-120b");
|
||||
const gptOss = modelCapabilities.getResolvedModelCapabilities("fake-provider/gpt-oss-120b");
|
||||
assert.equal(gptOss.toolCalling, true);
|
||||
const deepseek = modelCapabilities.getResolvedModelCapabilities("deepseek/deepseek-reasoner");
|
||||
assert.equal(deepseek.toolCalling, true);
|
||||
|
||||
@@ -6,15 +6,6 @@ import {
|
||||
stripIncompatibleMessageContent,
|
||||
} from "../../open-sse/services/modelStrip.ts";
|
||||
|
||||
test("model strip metadata is exposed for text-only models", () => {
|
||||
assert.deepEqual(getStripTypesForProviderModel("deepseek", "deepseek-chat"), ["image", "audio"]);
|
||||
assert.deepEqual(getStripTypesForProviderModel("bailian-coding-plan", "qwen3-coder-next"), [
|
||||
"image",
|
||||
"audio",
|
||||
]);
|
||||
assert.deepEqual(getStripTypesForProviderModel("openai", "gpt-4o"), []);
|
||||
});
|
||||
|
||||
test("stripIncompatibleMessageContent removes image and audio parts but preserves text", () => {
|
||||
const originalMessages = [
|
||||
{
|
||||
|
||||
@@ -102,7 +102,7 @@ test("v1 models catalog hides models excluded by every active connection while k
|
||||
const first = await seedConnection("openai", {
|
||||
name: "openai-first",
|
||||
providerSpecificData: {
|
||||
excludedModels: ["gpt-4o*"],
|
||||
excludedModels: ["gpt-5.4*"],
|
||||
},
|
||||
});
|
||||
const second = await seedConnection("openai", {
|
||||
@@ -119,11 +119,11 @@ test("v1 models catalog hides models excluded by every active connection while k
|
||||
let ids = new Set(body.data.map((item) => item.id));
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(ids.has("openai/gpt-4o-mini"), true);
|
||||
assert.equal(ids.has("openai/gpt-5.4-mini"), true);
|
||||
|
||||
await providersDb.updateProviderConnection((second as any).id, {
|
||||
providerSpecificData: {
|
||||
excludedModels: ["gpt-4o*"],
|
||||
excludedModels: ["gpt-5.4*"],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -134,7 +134,7 @@ test("v1 models catalog hides models excluded by every active connection while k
|
||||
ids = new Set(body.data.map((item) => item.id));
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(ids.has("openai/gpt-4o-mini"), false);
|
||||
assert.equal(ids.has("openai/gpt-5.4-mini"), false);
|
||||
|
||||
await providersDb.updateProviderConnection((first as any).id, {
|
||||
providerSpecificData: {
|
||||
@@ -251,28 +251,6 @@ test("v1 models catalog exposes claude alias and provider-prefixed built-in mode
|
||||
assert.deepEqual(aliasModel.output_modalities, ["text"]);
|
||||
});
|
||||
|
||||
test("v1 models catalog exposes amazon-q alias and provider-prefixed models via the kiro-compatible registry", async () => {
|
||||
await seedConnection("amazon-q", {
|
||||
authType: "oauth",
|
||||
name: "amazon-q-main",
|
||||
apiKey: null,
|
||||
accessToken: "amazon-q-access",
|
||||
});
|
||||
|
||||
const response = await v1ModelsCatalog.getUnifiedModelsResponse(
|
||||
new Request("http://localhost/api/v1/models")
|
||||
);
|
||||
const body = (await response.json()) as any;
|
||||
const aliasModel = body.data.find((item) => item.id === "aq/claude-sonnet-4.5");
|
||||
const providerModel = body.data.find((item) => item.id === "amazon-q/claude-sonnet-4.5");
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.ok(aliasModel);
|
||||
assert.ok(providerModel);
|
||||
assert.equal(providerModel.parent, aliasModel.id);
|
||||
assert.equal(aliasModel.owned_by, "amazon-q");
|
||||
});
|
||||
|
||||
test("v1 models catalog exposes refreshed GitHub Copilot aliases and drops retired models", async () => {
|
||||
await seedConnection("github", {
|
||||
authType: "oauth",
|
||||
|
||||
@@ -1,444 +0,0 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { MuseSparkWebExecutor, normalizeMetaAiCookieHeader } =
|
||||
await import("../../open-sse/executors/muse-spark-web.ts");
|
||||
const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
|
||||
function mockTextStream(text: string) {
|
||||
const encoder = new TextEncoder();
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode(text));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function metaAiSseText(events: Array<Record<string, unknown>>) {
|
||||
const frames = [":"];
|
||||
for (const event of events) {
|
||||
frames.push("event: next");
|
||||
frames.push(`data: ${JSON.stringify({ data: { sendMessageStream: event } })}`);
|
||||
frames.push("");
|
||||
}
|
||||
frames.push("event: complete");
|
||||
frames.push("data:");
|
||||
frames.push("");
|
||||
return frames.join("\n");
|
||||
}
|
||||
|
||||
function mockFetch(status: number, text: string) {
|
||||
const original = globalThis.fetch;
|
||||
globalThis.fetch = async () =>
|
||||
new Response(mockTextStream(text), {
|
||||
status,
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
return () => {
|
||||
globalThis.fetch = original;
|
||||
};
|
||||
}
|
||||
|
||||
function mockFetchCapture(status = 200, text = metaAiSseText([])) {
|
||||
const original = globalThis.fetch;
|
||||
let capturedUrl: string | null = null;
|
||||
let capturedHeaders: Record<string, string> = {};
|
||||
let capturedBody: Record<string, unknown> = {};
|
||||
|
||||
globalThis.fetch = async (url: any, opts: any) => {
|
||||
capturedUrl = String(url);
|
||||
capturedHeaders = opts?.headers || {};
|
||||
capturedBody = JSON.parse(opts?.body || "{}");
|
||||
return new Response(mockTextStream(text), {
|
||||
status,
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
restore: () => {
|
||||
globalThis.fetch = original;
|
||||
},
|
||||
get url() {
|
||||
return capturedUrl;
|
||||
},
|
||||
get headers() {
|
||||
return capturedHeaders;
|
||||
},
|
||||
get body() {
|
||||
return capturedBody;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mockFetchCaptureMany(status = 200, text = metaAiSseText([])) {
|
||||
const original = globalThis.fetch;
|
||||
const calls: Array<{
|
||||
url: string;
|
||||
headers: Record<string, string>;
|
||||
body: Record<string, unknown>;
|
||||
}> = [];
|
||||
|
||||
globalThis.fetch = async (url: any, opts: any) => {
|
||||
calls.push({
|
||||
url: String(url),
|
||||
headers: opts?.headers || {},
|
||||
body: JSON.parse(opts?.body || "{}"),
|
||||
});
|
||||
return new Response(mockTextStream(text), {
|
||||
status,
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
restore: () => {
|
||||
globalThis.fetch = original;
|
||||
},
|
||||
calls,
|
||||
};
|
||||
}
|
||||
|
||||
test("MuseSparkWebExecutor is registered in executor index", () => {
|
||||
assert.ok(hasSpecializedExecutor("muse-spark-web"));
|
||||
assert.ok(hasSpecializedExecutor("ms-web"));
|
||||
const executor = getExecutor("muse-spark-web");
|
||||
const alias = getExecutor("ms-web");
|
||||
assert.ok(executor instanceof MuseSparkWebExecutor);
|
||||
assert.ok(alias instanceof MuseSparkWebExecutor);
|
||||
});
|
||||
|
||||
test("MuseSparkWebExecutor sets correct provider name", () => {
|
||||
const executor = new MuseSparkWebExecutor();
|
||||
assert.equal(executor.getProvider(), "muse-spark-web");
|
||||
});
|
||||
|
||||
test("Non-streaming: Meta SSE becomes OpenAI completion", async () => {
|
||||
const restore = mockFetch(
|
||||
200,
|
||||
metaAiSseText([
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-1",
|
||||
content: "Hello ",
|
||||
streamingState: "STREAMING",
|
||||
contentRenderer: { __typename: "TextContentRenderer", text: "Hello " },
|
||||
},
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-1",
|
||||
content: "Hello from Muse Spark",
|
||||
streamingState: "DONE",
|
||||
contentRenderer: { __typename: "TextContentRenderer", text: "Hello from Muse Spark" },
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
try {
|
||||
const executor = new MuseSparkWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "muse-spark",
|
||||
body: { messages: [{ role: "user", content: "hi" }], stream: false },
|
||||
stream: false,
|
||||
credentials: { apiKey: "abra-session-token" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
log: null,
|
||||
});
|
||||
|
||||
assert.equal(result.response.status, 200);
|
||||
const json = (await result.response.json()) as any;
|
||||
assert.equal(json.object, "chat.completion");
|
||||
assert.equal(json.choices[0].message.role, "assistant");
|
||||
assert.equal(json.choices[0].message.content, "Hello from Muse Spark");
|
||||
assert.equal(json.choices[0].finish_reason, "stop");
|
||||
assert.ok(json.id.startsWith("chatcmpl-meta-"));
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("Streaming: produces valid SSE chunks", async () => {
|
||||
const restore = mockFetch(
|
||||
200,
|
||||
metaAiSseText([
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-1",
|
||||
content: "Hello ",
|
||||
streamingState: "STREAMING",
|
||||
thinkingText: "First thought",
|
||||
},
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-1",
|
||||
content: "Hello from Muse Spark",
|
||||
streamingState: "DONE",
|
||||
thinkingText: "First thought\nSecond thought",
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
try {
|
||||
const executor = new MuseSparkWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "muse-spark-thinking",
|
||||
body: { messages: [{ role: "user", content: "hello" }], stream: true },
|
||||
stream: true,
|
||||
credentials: { apiKey: "abra-session-token" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
log: null,
|
||||
});
|
||||
|
||||
assert.equal(result.response.status, 200);
|
||||
assert.equal(result.response.headers.get("Content-Type"), "text/event-stream");
|
||||
|
||||
const text = await result.response.text();
|
||||
const lines = text.split("\n").filter((line) => line.startsWith("data: "));
|
||||
assert.ok(lines.length >= 4, `Expected at least 4 SSE data lines, got ${lines.length}`);
|
||||
|
||||
const payloads = lines
|
||||
.filter((line) => line !== "data: [DONE]")
|
||||
.map((line) => JSON.parse(line.slice(6)));
|
||||
|
||||
const first = payloads[0];
|
||||
assert.equal(first.choices[0].delta.role, "assistant");
|
||||
|
||||
const reasoningChunks = payloads.filter(
|
||||
(payload) => payload.choices[0].delta.reasoning_content
|
||||
);
|
||||
assert.ok(reasoningChunks.length >= 2);
|
||||
assert.equal(reasoningChunks[0].choices[0].delta.reasoning_content, "First thought");
|
||||
assert.equal(reasoningChunks[1].choices[0].delta.reasoning_content, "\nSecond thought");
|
||||
|
||||
const contentChunks = payloads.filter((payload) => payload.choices[0].delta.content);
|
||||
assert.ok(contentChunks.length >= 2);
|
||||
assert.equal(contentChunks[0].choices[0].delta.content, "Hello ");
|
||||
assert.equal(contentChunks[1].choices[0].delta.content, "from Muse Spark");
|
||||
|
||||
const lastLine = text.trim().split("\n").filter(Boolean).pop();
|
||||
assert.equal(lastLine, "data: [DONE]");
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("Non-streaming thinking mode includes reasoning_content", async () => {
|
||||
const restore = mockFetch(
|
||||
200,
|
||||
metaAiSseText([
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-2",
|
||||
content: "Answer",
|
||||
streamingState: "DONE",
|
||||
thinkingText: "Reason through the plan",
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
try {
|
||||
const executor = new MuseSparkWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "muse-spark-thinking",
|
||||
body: { messages: [{ role: "user", content: "hi" }], stream: false },
|
||||
stream: false,
|
||||
credentials: { apiKey: "abra-session-token" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
log: null,
|
||||
});
|
||||
|
||||
assert.equal(result.response.status, 200);
|
||||
const json = (await result.response.json()) as any;
|
||||
assert.equal(json.choices[0].message.content, "Answer");
|
||||
assert.equal(json.choices[0].message.reasoning_content, "Reason through the plan");
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("Error: auth failure from Meta SSE returns cookie error", async () => {
|
||||
const restore = mockFetch(
|
||||
200,
|
||||
metaAiSseText([
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-1",
|
||||
content: "Authentication required to send messages",
|
||||
streamingState: "ERROR",
|
||||
error: { message: "Authentication required to send messages", code: null },
|
||||
contentRenderer: {
|
||||
__typename: "TextContentRenderer",
|
||||
text: "Authentication required to send messages",
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
try {
|
||||
const executor = new MuseSparkWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "muse-spark",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: { apiKey: "expired-cookie" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
log: null,
|
||||
});
|
||||
|
||||
assert.equal(result.response.status, 401);
|
||||
const json = (await result.response.json()) as any;
|
||||
assert.match(json.error.message, /meta ai auth failed/i);
|
||||
assert.match(json.error.message, /abra_sess/i);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("Cookie normalization supports raw tokens, prefixed tokens and full headers", () => {
|
||||
assert.equal(normalizeMetaAiCookieHeader("raw-session-token"), "abra_sess=raw-session-token");
|
||||
assert.equal(
|
||||
normalizeMetaAiCookieHeader("cookie:raw-session-token"),
|
||||
"abra_sess=raw-session-token"
|
||||
);
|
||||
assert.equal(
|
||||
normalizeMetaAiCookieHeader("abra_sess=token; other=value"),
|
||||
"abra_sess=token; other=value"
|
||||
);
|
||||
});
|
||||
|
||||
test("Request: posts to correct Meta endpoint with normalized cookie", async () => {
|
||||
const cap = mockFetchCapture(
|
||||
200,
|
||||
metaAiSseText([
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-1",
|
||||
content: "ok",
|
||||
streamingState: "DONE",
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
try {
|
||||
const executor = new MuseSparkWebExecutor();
|
||||
await executor.execute({
|
||||
model: "muse-spark",
|
||||
body: { messages: [{ role: "user", content: "test" }], stream: false },
|
||||
stream: false,
|
||||
credentials: { apiKey: "raw-session-token" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
log: null,
|
||||
});
|
||||
|
||||
assert.equal(cap.url, "https://www.meta.ai/api/graphql");
|
||||
assert.equal(cap.headers.Cookie, "abra_sess=raw-session-token");
|
||||
assert.equal(cap.headers.Accept, "text/event-stream");
|
||||
assert.equal(cap.headers["X-FB-Friendly-Name"], "useAbraSendMessageMutation");
|
||||
assert.equal(cap.headers["X-ASBD-ID"], "129477");
|
||||
assert.equal(cap.headers.Origin, "https://www.meta.ai");
|
||||
assert.equal(cap.headers.Referer, "https://www.meta.ai/");
|
||||
} finally {
|
||||
cap.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("Request: rotates across extra abra_sess cookies with round-robin", async () => {
|
||||
const cap = mockFetchCaptureMany(
|
||||
200,
|
||||
metaAiSseText([
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-3",
|
||||
content: "ok",
|
||||
streamingState: "DONE",
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
try {
|
||||
const executor = new MuseSparkWebExecutor();
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await executor.execute({
|
||||
model: "muse-spark",
|
||||
body: { messages: [{ role: "user", content: `test ${i}` }], stream: false },
|
||||
stream: false,
|
||||
credentials: {
|
||||
apiKey: "primary-cookie",
|
||||
connectionId: "muse-spark-rotation",
|
||||
providerSpecificData: {
|
||||
extraApiKeys: ["secondary-cookie", "abra_sess=third-cookie"],
|
||||
},
|
||||
},
|
||||
signal: AbortSignal.timeout(10000),
|
||||
log: null,
|
||||
});
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
cap.calls.map((call) => call.headers.Cookie),
|
||||
["abra_sess=primary-cookie", "abra_sess=secondary-cookie", "abra_sess=third-cookie"]
|
||||
);
|
||||
} finally {
|
||||
cap.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("Request: payload carries persisted doc id, model mapping and Meta defaults", async () => {
|
||||
const cap = mockFetchCapture(
|
||||
200,
|
||||
metaAiSseText([
|
||||
{
|
||||
__typename: "AssistantMessage",
|
||||
id: "meta-msg-1",
|
||||
content: "ok",
|
||||
streamingState: "DONE",
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
try {
|
||||
const executor = new MuseSparkWebExecutor();
|
||||
await executor.execute({
|
||||
model: "muse-spark-contemplating",
|
||||
body: {
|
||||
messages: [
|
||||
{ role: "system", content: "Be concise" },
|
||||
{ role: "assistant", content: "Previous answer" },
|
||||
{ role: "user", content: "Implement this" },
|
||||
],
|
||||
stream: false,
|
||||
},
|
||||
stream: false,
|
||||
credentials: { apiKey: "abra_sess=token" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
log: null,
|
||||
});
|
||||
|
||||
assert.equal(cap.body.doc_id, "078dfdff6fb0d420d8011b49073e6886");
|
||||
assert.equal((cap.body.variables as any).mode, "think_hard");
|
||||
assert.equal((cap.body.variables as any).currentBranchPath, "0");
|
||||
assert.equal((cap.body.variables as any).entryPoint, "KADABRA__CHAT__UNIFIED_INPUT_BAR");
|
||||
assert.equal((cap.body.variables as any).promptEditType, null);
|
||||
assert.match(String((cap.body.variables as any).content), /system: Be concise/i);
|
||||
assert.match(String((cap.body.variables as any).content), /assistant: Previous answer/i);
|
||||
assert.match(String((cap.body.variables as any).content), /Implement this/);
|
||||
assert.match(String((cap.body.variables as any).conversationId), /^c\./);
|
||||
assert.match(String((cap.body.variables as any).userEventId), /^e\./);
|
||||
assert.match(String((cap.body.variables as any).userUniqueMessageId), /^\d+$/);
|
||||
} finally {
|
||||
cap.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("Provider registry: muse-spark-web models are exposed", async () => {
|
||||
const { getModelsByProviderId, PROVIDER_ID_TO_ALIAS } =
|
||||
await import("../../open-sse/config/providerModels.ts");
|
||||
const models = getModelsByProviderId("muse-spark-web");
|
||||
assert.ok(models, "muse-spark-web should exist in PROVIDER_MODELS");
|
||||
assert.equal(PROVIDER_ID_TO_ALIAS["muse-spark-web"], "ms-web");
|
||||
const ids = models.map((model: any) => model.id);
|
||||
assert.ok(ids.includes("muse-spark"));
|
||||
assert.ok(ids.includes("muse-spark-thinking"));
|
||||
assert.ok(ids.includes("muse-spark-contemplating"));
|
||||
});
|
||||
@@ -671,7 +671,7 @@ test("Provider registry: perplexity-web is registered with correct models", asyn
|
||||
|
||||
const models = PROVIDER_MODELS["pplx-web"];
|
||||
assert.ok(models, "pplx-web should be in PROVIDER_MODELS");
|
||||
assert.ok(models.length === 7, `Expected 7 models, got ${models.length}`);
|
||||
assert.ok(models.length === 8, `Expected 8 models, got ${models.length}`);
|
||||
|
||||
const modelIds = models.map((m) => m.id);
|
||||
assert.ok(modelIds.includes("pplx-auto"));
|
||||
@@ -681,6 +681,7 @@ test("Provider registry: perplexity-web is registered with correct models", asyn
|
||||
assert.ok(modelIds.includes("pplx-gemini"));
|
||||
assert.ok(modelIds.includes("pplx-nemotron"));
|
||||
assert.ok(modelIds.includes("pplx-sonar"));
|
||||
assert.ok(modelIds.includes("pplx-kimi"));
|
||||
});
|
||||
|
||||
// ─── Test: Fallback text field ──────────────────────────────────────────────
|
||||
|
||||
@@ -28,15 +28,6 @@ test("getModelInfoCore keeps openai fallback for gpt-4o", async () => {
|
||||
assert.equal(info.model, "gpt-4o");
|
||||
});
|
||||
|
||||
test("getModelInfoCore reports ambiguous unprefixed GPT Codex models", async () => {
|
||||
const info = await getModelInfoCore("gpt-5.4", {});
|
||||
assert.equal(info.provider, null);
|
||||
assert.equal(info.model, "gpt-5.4");
|
||||
assert.equal(info.errorType, "ambiguous_model");
|
||||
assert.ok(info.candidateProviders.includes("codex"));
|
||||
assert.ok(info.candidateProviders.includes("github"));
|
||||
});
|
||||
|
||||
test("getModelInfoCore resolves codex-auto-review to codex", async () => {
|
||||
const info = await getModelInfoCore("codex-auto-review", {});
|
||||
assert.equal(info.provider, "codex");
|
||||
|
||||
@@ -71,156 +71,8 @@ test("Kiro registry exposes the current CLI model lineup with context windows",
|
||||
const kiroModels = getProviderModels("kr");
|
||||
const byId = new Map(kiroModels.map((model) => [model.id, model]));
|
||||
|
||||
assert.ok(byId.has("auto"));
|
||||
assert.equal(byId.get("claude-opus-4.7")?.contextLength, 1000000);
|
||||
assert.equal(byId.get("claude-sonnet-4.6")?.contextLength, 1000000);
|
||||
assert.equal(byId.get("glm-5")?.contextLength, 200000);
|
||||
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("Poe registry exposes current OpenAI-compatible examples", () => {
|
||||
const poeModels = getProviderModels("poe");
|
||||
const ids = new Set(poeModels.map((model) => model.id));
|
||||
|
||||
assert.ok(ids.has("Claude-Sonnet-4.5"));
|
||||
assert.ok(ids.has("GPT-5-Pro"));
|
||||
assert.ok(ids.has("GPT-5-Codex"));
|
||||
assert.ok(ids.has("Gemini-2.5-Pro"));
|
||||
});
|
||||
|
||||
test("Nous Research registry exposes current official Hermes examples", () => {
|
||||
const nousModels = getProviderModels("nous");
|
||||
const ids = new Set(nousModels.map((model) => model.id));
|
||||
|
||||
assert.ok(ids.has("nousresearch/hermes-4-70b"));
|
||||
assert.ok(ids.has("nousresearch/hermes-4-405b"));
|
||||
assert.ok(ids.has("nousresearch/hermes-3-llama-3.1-70b"));
|
||||
assert.ok(ids.has("nousresearch/hermes-3-llama-3.1-405b"));
|
||||
});
|
||||
|
||||
test("Petals registry exposes the current public HTTP API fallback model", () => {
|
||||
const petalsModels = getProviderModels("petals");
|
||||
const ids = new Set(petalsModels.map((model) => model.id));
|
||||
|
||||
assert.ok(ids.has("stabilityai/StableBeluga2"));
|
||||
});
|
||||
|
||||
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" },
|
||||
]);
|
||||
assert.ok(byId.has("claude-opus-4.7"));
|
||||
assert.equal(byId.get("claude-opus-4.7")?.contextLength, undefined); // Uses default
|
||||
assert.ok(byId.has("claude-sonnet-4.6"));
|
||||
assert.ok(byId.has("claude-haiku-4.5"));
|
||||
});
|
||||
|
||||
@@ -241,28 +241,7 @@ test("provider models route returns the local catalog for GitLab Duo fallback mo
|
||||
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",
|
||||
},
|
||||
{ id: "gitlab-duo-code-suggestions", name: "GitLab Duo Code Suggestions" },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -352,88 +331,6 @@ test("provider models route prefers the remote OpenRouter /models API over stati
|
||||
assert.deepEqual(body.models, [{ id: "openai/gpt-4.1", name: "GPT-4.1 via OpenRouter" }]);
|
||||
});
|
||||
|
||||
test("provider models route fetches remote catalogs for new OpenAI-compatible gateway providers", async () => {
|
||||
const cases = [
|
||||
{
|
||||
provider: "glhf",
|
||||
apiKey: "glhf-key",
|
||||
expectedUrl: "https://glhf.chat/api/openai/v1/models",
|
||||
model: { id: "hf:Qwen/Qwen2.5-72B-Instruct", name: "Qwen 2.5 72B via GLHF" },
|
||||
},
|
||||
{
|
||||
provider: "cablyai",
|
||||
apiKey: "cably-key",
|
||||
expectedUrl: "https://cablyai.com/v1/models",
|
||||
model: { id: "gpt-4o", name: "GPT-4o via CablyAI" },
|
||||
},
|
||||
{
|
||||
provider: "thebai",
|
||||
apiKey: "theb-key",
|
||||
expectedUrl: "https://api.theb.ai/v1/models",
|
||||
model: { id: "gpt-4o", name: "GPT-4o via TheB.AI" },
|
||||
},
|
||||
{
|
||||
provider: "fenayai",
|
||||
apiKey: "fenay-key",
|
||||
expectedUrl: "https://fenayai.com/v1/models",
|
||||
model: { id: "deepseek-chat", name: "DeepSeek Chat via FenayAI" },
|
||||
},
|
||||
{
|
||||
provider: "empower",
|
||||
apiKey: "empower-key",
|
||||
expectedUrl: "https://app.empower.dev/api/v1/models",
|
||||
model: { id: "empower-functions", name: "Empower Functions", owned_by: "empower" },
|
||||
},
|
||||
{
|
||||
provider: "nous-research",
|
||||
apiKey: "nous-key",
|
||||
expectedUrl: "https://inference-api.nousresearch.com/v1/models",
|
||||
model: {
|
||||
id: "nousresearch/hermes-4-70b",
|
||||
name: "Nous: Hermes 4 70B",
|
||||
owned_by: "nous-research",
|
||||
},
|
||||
},
|
||||
{
|
||||
provider: "poe",
|
||||
apiKey: "poe-key",
|
||||
expectedUrl: "https://api.poe.com/v1/models",
|
||||
model: { id: "Claude-Sonnet-4.5", name: "Claude Sonnet 4.5", owned_by: "Anthropic" },
|
||||
},
|
||||
{
|
||||
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) {
|
||||
const connection = await seedConnection(entry.provider, {
|
||||
apiKey: entry.apiKey,
|
||||
});
|
||||
const seenUrls = [];
|
||||
|
||||
globalThis.fetch = async (url, init = {}) => {
|
||||
seenUrls.push(String(url));
|
||||
assert.equal(init.method, "GET");
|
||||
assert.equal(init.headers.Authorization, `Bearer ${entry.apiKey}`);
|
||||
return Response.json({
|
||||
data: [entry.model],
|
||||
});
|
||||
};
|
||||
|
||||
const response = await callRoute(connection.id, "?refresh=true");
|
||||
const body = (await response.json()) as any;
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(body.provider, entry.provider);
|
||||
assert.equal(body.source, "api");
|
||||
assert.deepEqual(seenUrls, [entry.expectedUrl]);
|
||||
assert.deepEqual(body.models, [entry.model]);
|
||||
}
|
||||
});
|
||||
|
||||
test("provider models route returns the local catalog for embedding and rerank providers", async () => {
|
||||
const voyage = await seedConnection("voyage-ai", {
|
||||
apiKey: "voyage-key",
|
||||
@@ -462,36 +359,6 @@ 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 Petals", async () => {
|
||||
const connection = await seedConnection("petals", {
|
||||
apiKey: null,
|
||||
});
|
||||
|
||||
const response = await callRoute(connection.id);
|
||||
const body = (await response.json()) as any;
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(body.provider, "petals");
|
||||
assert.equal(body.source, "local_catalog");
|
||||
assert.ok(body.models.some((model) => model.id === "stabilityai/StableBeluga2"));
|
||||
});
|
||||
|
||||
test("provider models route returns the local catalog for Runway video models", async () => {
|
||||
const connection = await seedConnection("runwayml", {
|
||||
apiKey: "runway-key",
|
||||
@@ -508,23 +375,6 @@ test("provider models route returns the local catalog for Runway video models",
|
||||
assert.ok(body.models.some((model) => model.id === "gen3a_turbo"));
|
||||
});
|
||||
|
||||
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",
|
||||
apiKey: null,
|
||||
accessToken: "amazon-q-access",
|
||||
});
|
||||
|
||||
const response = await callRoute(connection.id);
|
||||
const body = (await response.json()) as any;
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(body.provider, "amazon-q");
|
||||
assert.equal(body.source, "local_catalog");
|
||||
assert.ok(body.models.some((model) => model.id === "claude-sonnet-4.5"));
|
||||
assert.ok(body.models.some((model) => model.id === "claude-sonnet-4"));
|
||||
});
|
||||
|
||||
test("provider models route returns the updated local catalog for GitHub Copilot", async () => {
|
||||
const connection = await seedConnection("github", {
|
||||
authType: "oauth",
|
||||
@@ -563,9 +413,9 @@ test("provider models route returns the expanded local catalog for Kiro", async
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(body.provider, "kiro");
|
||||
assert.equal(body.source, "local_catalog");
|
||||
assert.ok(body.models.some((model) => model.id === "auto"));
|
||||
assert.ok(body.models.some((model) => model.id === "claude-haiku-4.5"));
|
||||
assert.ok(body.models.some((model) => model.id === "claude-opus-4.7"));
|
||||
assert.ok(body.models.some((model) => model.id === "qwen3-coder-next"));
|
||||
assert.ok(body.models.some((model) => model.id === "claude-sonnet-4.6"));
|
||||
});
|
||||
|
||||
test("provider models route returns the local catalog for new built-in chat-openai-compat providers", async () => {
|
||||
|
||||
@@ -39,6 +39,6 @@ test("T12: minimax default model list starts with M2.7", () => {
|
||||
const minimaxModels = REGISTRY.minimax.models.map((m) => m.id);
|
||||
const minimaxCnModels = REGISTRY["minimax-cn"].models.map((m) => m.id);
|
||||
|
||||
assert.equal(minimaxModels[0], "minimax-m2.7");
|
||||
assert.equal(minimaxCnModels[0], "minimax-m2.7");
|
||||
assert.equal(minimaxModels[0], "MiniMax-M2.7");
|
||||
assert.equal(minimaxCnModels[0], "MiniMax-M2.7");
|
||||
});
|
||||
|
||||
@@ -56,19 +56,10 @@ test("T28: vertex catalog includes partner models when vertex executor is availa
|
||||
assert.ok(vertexIds.includes("glm-5"));
|
||||
});
|
||||
|
||||
test("T23/#1491: dedicated partner providers and opencode-go discovery metadata are registered", () => {
|
||||
assert.equal(
|
||||
REGISTRY["glm-cn"].baseUrl,
|
||||
"https://open.bigmodel.cn/api/coding/paas/v4/chat/completions"
|
||||
);
|
||||
assert.equal(REGISTRY["vertex-partner"].executor, "vertex");
|
||||
assert.equal(REGISTRY["opencode-go"].modelsUrl, "https://opencode.ai/zen/go/v1/models");
|
||||
});
|
||||
|
||||
test("T28: new catalog models resolve through getModelInfoCore", async () => {
|
||||
const minimax = await getModelInfoCore("minimax/minimax-m2.7", {});
|
||||
const minimax = await getModelInfoCore("minimax/MiniMax-M2.7", {});
|
||||
assert.equal(minimax.provider, "minimax");
|
||||
assert.equal(minimax.model, "minimax-m2.7");
|
||||
assert.equal(minimax.model, "MiniMax-M2.7");
|
||||
|
||||
const flashLite = await getModelInfoCore("gemini/gemini-3.1-flash-lite-preview", {});
|
||||
assert.equal(flashLite.provider, "gemini");
|
||||
|
||||
Reference in New Issue
Block a user