mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-17 12:42:21 +03:00
* fix(providers): correct Kiro model catalog to real upstream ids
Kiro's API (generateAssistantResponse) returns 400 "Invalid model. Please
select a different model" for any id it does not recognize. The registry
exposed fabricated ids (copied from OmniRoute's own Anthropic catalog) that
Kiro never serves, so every call to them 400'd. Live-verified on the VPS:
Removed (400 Invalid model):
- auto-kiro (no "auto" model id — was sent verbatim upstream)
- claude-fable-5 (Kiro offers no Fable)
- claude-opus-4.8/4.7/4.6 (Kiro offers no Opus)
Corrected:
- claude-sonnet-4.6 -> claude-sonnet-4.5 (Kiro's Sonnet is 4.5; 4.5 -> 200)
Kept:
- claude-sonnet-5 (real Kiro model, plan-gated per account)
- claude-haiku-4.5, deepseek-3.2, glm-5, minimax-m2.5/m2.1,
qwen3-coder-next (all proven 200 on the VPS)
Aligns the free-model catalog and drops the orphaned auto-kiro price key.
Regression guard: tests/unit/kiro-catalog-real-models.test.ts (3/3).
Kiro cluster #6112/#6113/#6099.
* test(providers): align stale Kiro-catalog tests to the corrected upstream ids
The fabricated Kiro ids removed in the parent commit (claude-fable-5,
claude-opus-4.8/4.7/4.6, claude-sonnet-4.6) were still asserted as present by
three pre-existing tests, which encoded the bug:
- catalog-updates-v3x: now asserts Kiro does NOT expose Fable 5 / Opus (kept the
legit cc exposure) and guards the real claude-sonnet-4.5 pricing.
- model-family-fallback-notation: the dot-notation example moves from kiro/ to
anthropic/ (which genuinely serves Opus/Fable in dot notation) — coverage kept.
- provider-models-route: the Kiro local-catalog assertion now expects the real
Sonnet 5 / Sonnet 4.5 set and negatively guards the fabricated ids.
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
1752 lines
59 KiB
TypeScript
1752 lines
59 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-provider-model-routes-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const providersDb = await import("../../src/lib/db/providers.ts");
|
|
const modelsDb = await import("../../src/lib/db/models.ts");
|
|
const providerModelsRoute = await import("../../src/app/api/providers/[id]/models/route.ts");
|
|
const antigravityVersion = await import("../../open-sse/services/antigravityVersion.ts");
|
|
const providerRegistry = await import("../../open-sse/config/providerRegistry.ts");
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
const originalAllowPrivateProviderUrls = process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
|
|
|
|
async function resetStorage() {
|
|
globalThis.fetch = originalFetch;
|
|
if (originalAllowPrivateProviderUrls === undefined) {
|
|
delete process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
|
|
} else {
|
|
process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS = originalAllowPrivateProviderUrls;
|
|
}
|
|
antigravityVersion.clearAntigravityVersionCache();
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
async function seedConnection(provider, overrides = {}) {
|
|
return providersDb.createProviderConnection({
|
|
provider,
|
|
authType: overrides.authType || "apikey",
|
|
name: overrides.name || `${provider}-${Math.random().toString(16).slice(2, 8)}`,
|
|
apiKey: overrides.apiKey,
|
|
accessToken: overrides.accessToken,
|
|
projectId: overrides.projectId,
|
|
isActive: overrides.isActive ?? true,
|
|
testStatus: overrides.testStatus || "active",
|
|
providerSpecificData: overrides.providerSpecificData || {},
|
|
});
|
|
}
|
|
|
|
async function callRoute(connectionId, search = "") {
|
|
return providerModelsRoute.GET(
|
|
new Request(`http://localhost/api/providers/${connectionId}/models${search}`),
|
|
{ params: { id: connectionId } }
|
|
);
|
|
}
|
|
|
|
test.beforeEach(async () => {
|
|
await resetStorage();
|
|
});
|
|
|
|
test.after(async () => {
|
|
globalThis.fetch = originalFetch;
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
});
|
|
|
|
test("provider models route returns a static local catalog for non-LLM search/agent providers (#5569/#5571/#5573/#5575)", async () => {
|
|
const cases = [
|
|
{ provider: "jules", expectId: "jules" },
|
|
{ provider: "linkup-search", expectId: "standard" },
|
|
{ provider: "ollama-search", expectId: "web_search" },
|
|
{ provider: "searchapi-search", expectId: "google" },
|
|
];
|
|
for (const { provider, expectId } of cases) {
|
|
const connection = await seedConnection(provider, { apiKey: `${provider}-key` });
|
|
const response = await callRoute(connection.id);
|
|
// RED before the fix: these had no static catalog → 400 "does not support models listing".
|
|
assert.equal(response.status, 200, `${provider} should not 400 on model import`);
|
|
const body = await response.json();
|
|
assert.equal(body.source, "local_catalog", `${provider} should serve a local catalog`);
|
|
const ids = (body.models || []).map((m) => m.id);
|
|
assert.ok(
|
|
ids.includes(expectId),
|
|
`${provider} should list "${expectId}"; got: ${ids.join(", ")}`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("provider models route fetches the live AI/ML API catalog from the auth-free /models endpoint (#5570)", async () => {
|
|
const connection = await seedConnection("aimlapi", { apiKey: "aiml-key" });
|
|
let calledUrl = "";
|
|
globalThis.fetch = async (url) => {
|
|
calledUrl = String(url);
|
|
return Response.json([
|
|
{ id: "openai/gpt-5.5", type: "chat-completion", info: { name: "GPT-5.5" } },
|
|
{ id: "zhipu/glm-5.2", type: "chat-completion", info: { name: "GLM 5.2" } },
|
|
{ id: "flux/flux-pro", type: "image", info: { name: "FLUX Pro" } },
|
|
]);
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
// RED before the fix: aimlapi had no PROVIDER_MODELS_CONFIG entry → stale
|
|
// 6-model local seed (source "local_catalog"), live endpoint never called.
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "api");
|
|
assert.equal(calledUrl, "https://api.aimlapi.com/models");
|
|
const ids = body.models.map((m: any) => m.id);
|
|
assert.ok(ids.includes("openai/gpt-5.5") && ids.includes("zhipu/glm-5.2"));
|
|
assert.ok(!ids.includes("flux/flux-pro"), "non-chat model types are filtered out");
|
|
});
|
|
|
|
test("provider models route falls back to the local AI/ML API catalog when the live fetch fails (#5570)", async () => {
|
|
const connection = await seedConnection("aimlapi", { apiKey: "aiml-key" });
|
|
globalThis.fetch = async () => new Response("upstream down", { status: 500 });
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.ok(body.models.length > 0);
|
|
});
|
|
|
|
test("cablyai is flagged deprecated (domain NXDOMAIN) and no longer 500s on model import (#5568)", async () => {
|
|
const { APIKEY_PROVIDERS_GATEWAYS } =
|
|
await import("../../src/shared/constants/providers/apikey/gateways.ts");
|
|
const cablyai = (APIKEY_PROVIDERS_GATEWAYS as Record<string, any>).cablyai;
|
|
assert.equal(cablyai?.deprecated, true, "cablyai must be marked deprecated (domain is NXDOMAIN)");
|
|
assert.ok(
|
|
typeof cablyai?.deprecationReason === "string" && cablyai.deprecationReason.length > 0,
|
|
"cablyai must carry a deprecationReason"
|
|
);
|
|
|
|
// Removed from PROVIDER_MODELS_CONFIG → no live fetch to the dead domain → a
|
|
// controlled 400 instead of an unhandled 500 crash.
|
|
const connection = await seedConnection("cablyai", { apiKey: "dead-key" });
|
|
const response = await callRoute(connection.id);
|
|
assert.notEqual(response.status, 500, "cablyai must not 500-crash on a dead domain");
|
|
});
|
|
|
|
test("provider models route returns 404 for unknown connections", async () => {
|
|
const response = await callRoute("missing-connection");
|
|
|
|
assert.equal(response.status, 404);
|
|
assert.deepEqual(await response.json(), { error: "Connection not found" });
|
|
});
|
|
|
|
test("provider models route rejects connections with an empty provider id", async () => {
|
|
const connection = await seedConnection("openai", {
|
|
apiKey: "sk-openai",
|
|
});
|
|
const db = core.getDbInstance();
|
|
|
|
db.prepare("UPDATE provider_connections SET provider = '' WHERE id = ?").run(connection.id);
|
|
|
|
const response = await callRoute(connection.id);
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), { error: "Invalid connection provider" });
|
|
});
|
|
|
|
test("provider models route rejects OpenAI-compatible providers without a base URL", async () => {
|
|
const connection = await seedConnection("openai-compatible-demo", {
|
|
apiKey: "sk-openai-compatible",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), {
|
|
error: "No base URL configured for OpenAI compatible provider",
|
|
});
|
|
});
|
|
|
|
test("provider models route blocks private OpenAI-compatible base URLs", async () => {
|
|
delete process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS;
|
|
|
|
const connection = await seedConnection("openai-compatible-private", {
|
|
apiKey: "sk-openai-compatible",
|
|
providerSpecificData: {
|
|
baseUrl: "http://127.0.0.1:11434/v1",
|
|
},
|
|
});
|
|
|
|
let called = false;
|
|
globalThis.fetch = async () => {
|
|
called = true;
|
|
return Response.json({ data: [] });
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), {
|
|
error: "Blocked private or local provider URL",
|
|
});
|
|
assert.equal(called, false);
|
|
});
|
|
|
|
test("provider models route returns auth failures from OpenAI-compatible upstreams", async () => {
|
|
const connection = await seedConnection("openai-compatible-auth", {
|
|
apiKey: "sk-openai-compatible",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com/v1/chat/completions",
|
|
},
|
|
});
|
|
const seenUrls = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
seenUrls.push(String(url));
|
|
return new Response("unauthorized", { status: 401 });
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
|
|
assert.equal(response.status, 401);
|
|
assert.deepEqual(await response.json(), { error: "Auth failed: 401" });
|
|
assert.equal(seenUrls.length, 1);
|
|
});
|
|
|
|
test("provider models route falls back after OpenAI-compatible endpoint probes all fail", async () => {
|
|
const connection = await seedConnection("openai-compatible-fallback", {
|
|
apiKey: "sk-openai-compatible",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com/v1",
|
|
},
|
|
});
|
|
const seenUrls = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
seenUrls.push(String(url));
|
|
return new Response("bad gateway", { status: 502 });
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "openai-compatible-fallback");
|
|
assert.ok(Array.isArray(body.models));
|
|
assert.ok(seenUrls.length >= 2);
|
|
});
|
|
|
|
test("provider models route retries transient OpenAI-compatible probe failures before succeeding", async () => {
|
|
const connection = await seedConnection("openai-compatible-retry", {
|
|
apiKey: "sk-openai-compatible",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com/v1",
|
|
},
|
|
});
|
|
const seenUrls = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
seenUrls.push(String(url));
|
|
if (seenUrls.length === 1) {
|
|
throw new Error("temporary upstream failure");
|
|
}
|
|
|
|
return Response.json({
|
|
data: [{ id: "demo-model", name: "Demo Model" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "api");
|
|
assert.deepEqual(seenUrls, [
|
|
"https://proxy.example.com/v1/models",
|
|
"https://proxy.example.com/v1/models",
|
|
]);
|
|
assert.deepEqual(body.models, [{ id: "demo-model", name: "Demo Model" }]);
|
|
});
|
|
|
|
test("provider models route discovers SiliconFlow models from configured China base URL", async () => {
|
|
const connection = await seedConnection("siliconflow", {
|
|
apiKey: "sf-cn-key",
|
|
providerSpecificData: {
|
|
baseUrl: "https://api.siliconflow.cn/v1",
|
|
},
|
|
});
|
|
const seenRequests: Array<{
|
|
url: string;
|
|
method: string | undefined;
|
|
authorization: string | null;
|
|
}> = [];
|
|
|
|
globalThis.fetch = async (url, init) => {
|
|
const headers = new Headers(init?.headers as HeadersInit | undefined);
|
|
seenRequests.push({
|
|
url: String(url),
|
|
method: init?.method,
|
|
authorization: headers.get("authorization"),
|
|
});
|
|
|
|
return Response.json({
|
|
data: [{ id: "Qwen/Qwen3-Coder-480B-A35B-Instruct", name: "Qwen3 Coder" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id, "?refresh=true");
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "siliconflow");
|
|
assert.equal(body.source, "api");
|
|
assert.deepEqual(seenRequests, [
|
|
{
|
|
url: "https://api.siliconflow.cn/v1/models",
|
|
method: "GET",
|
|
authorization: "Bearer sf-cn-key",
|
|
},
|
|
]);
|
|
assert.deepEqual(body.models, [
|
|
{
|
|
id: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
name: "Qwen3 Coder",
|
|
owned_by: "siliconflow",
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("provider models route handles local hostnames named 'v1' correctly", async () => {
|
|
const connection = await seedConnection("openai-compatible-local-v1", {
|
|
apiKey: "sk-local",
|
|
providerSpecificData: {
|
|
baseUrl: "http://v1/chat/completions",
|
|
},
|
|
});
|
|
const seenUrls: string[] = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
seenUrls.push(String(url));
|
|
return Response.json({
|
|
data: [{ id: "local-v1-model", name: "Local v1 Model" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "api");
|
|
assert.deepEqual(seenUrls, ["http://v1/v1/models"]);
|
|
});
|
|
|
|
test("provider models route correctly strips standard /v1 paths", async () => {
|
|
const connection = await seedConnection("openai-compatible-standard-v1", {
|
|
apiKey: "sk-standard",
|
|
providerSpecificData: {
|
|
baseUrl: "https://api.openai.com/v1",
|
|
},
|
|
});
|
|
const seenUrls: string[] = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
seenUrls.push(String(url));
|
|
return Response.json({
|
|
data: [{ id: "standard-model", name: "Standard Model" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "api");
|
|
assert.deepEqual(seenUrls, ["https://api.openai.com/v1/models"]);
|
|
});
|
|
|
|
test("provider models route strips /v1 when it precedes /chat/completions (#5899 no double /v1)", async () => {
|
|
// Regression for #5899 (Api Airforce): a baseUrl of the form
|
|
// "https://api.airforce/v1/chat/completions" must probe ".../v1/models" — NOT
|
|
// ".../v1/v1/models". The old `else if` strip chain only removed
|
|
// "/chat/completions", leaving a trailing "/v1" that the endpoint builder then
|
|
// doubled, producing a 308 redirect that aborted discovery.
|
|
const connection = await seedConnection("openai-compatible-airforce-v1", {
|
|
apiKey: "sk-airforce",
|
|
providerSpecificData: {
|
|
baseUrl: "https://api.airforce/v1/chat/completions",
|
|
},
|
|
});
|
|
const seenUrls: string[] = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
seenUrls.push(String(url));
|
|
return Response.json({
|
|
data: [{ id: "airforce-model", name: "Airforce Model" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "api");
|
|
// First probed endpoint must have a single /v1 — no ".../v1/v1/models".
|
|
assert.equal(seenUrls[0], "https://api.airforce/v1/models");
|
|
assert.ok(
|
|
!seenUrls.some((u) => u.includes("/v1/v1/")),
|
|
`no endpoint should contain a doubled /v1: ${JSON.stringify(seenUrls)}`
|
|
);
|
|
});
|
|
|
|
test("provider models route continues probing past a REDIRECT_BLOCKED endpoint (#5899)", async () => {
|
|
// Regression for #5899: a REDIRECT_BLOCKED error on one candidate endpoint must
|
|
// not abort the whole probe loop — discovery should fall through to the next
|
|
// endpoint instead of surfacing an empty catalog.
|
|
const connection = await seedConnection("openai-compatible-redirect-v1", {
|
|
apiKey: "sk-redirect",
|
|
providerSpecificData: {
|
|
baseUrl: "https://redirect.example",
|
|
},
|
|
});
|
|
const seenUrls: string[] = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
const u = String(url);
|
|
seenUrls.push(u);
|
|
// First candidate ".../v1/models" answers with a real 308 redirect →
|
|
// safeOutboundFetch throws a SafeOutboundFetchError(REDIRECT_BLOCKED). The old
|
|
// code re-threw on it (status 503) and aborted the loop; the fix `continue`s.
|
|
if (u === "https://redirect.example/v1/models") {
|
|
return new Response(null, {
|
|
status: 308,
|
|
headers: { location: "https://redirect.example/models" },
|
|
});
|
|
}
|
|
return Response.json({
|
|
data: [{ id: "redirect-model", name: "Redirect Model" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
// Without the REDIRECT_BLOCKED `continue`, discovery aborted and fell back to a
|
|
// non-api catalog. The fix lets it reach the next endpoint and return live models.
|
|
assert.equal(body.source, "api");
|
|
assert.ok(
|
|
seenUrls.length >= 2,
|
|
`expected the loop to continue past REDIRECT_BLOCKED: ${JSON.stringify(seenUrls)}`
|
|
);
|
|
});
|
|
|
|
test("provider models route returns static catalog entries for providers with hardcoded models", async () => {
|
|
const connection = await seedConnection("bailian-coding-plan", {
|
|
apiKey: "bailian-key",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "bailian-coding-plan");
|
|
assert.equal(body.models.length, providerRegistry.REGISTRY["bailian-coding-plan"].models?.length);
|
|
assert.deepEqual(
|
|
body.models.map((model) => model.id),
|
|
providerRegistry.REGISTRY["bailian-coding-plan"].models?.map((model) => model.id)
|
|
);
|
|
});
|
|
|
|
test("provider models route returns AWS Polly speech engines from the audio registry", async () => {
|
|
const connection = await seedConnection("aws-polly", {
|
|
apiKey: "aws-secret-key",
|
|
providerSpecificData: {
|
|
accessKeyId: "AKIA_TEST",
|
|
region: "us-east-1",
|
|
},
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "aws-polly");
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.deepEqual(
|
|
body.models.map((model) => model.id),
|
|
["standard", "neural", "long-form", "generative"]
|
|
);
|
|
});
|
|
|
|
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" },
|
|
]);
|
|
});
|
|
|
|
test("provider models route discovers local OpenAI-style models without requiring an API key", async () => {
|
|
process.env.OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS = "true";
|
|
|
|
const lmStudioConnection = await seedConnection("lm-studio", {
|
|
providerSpecificData: {
|
|
baseUrl: "http://localhost:1234/v1",
|
|
},
|
|
});
|
|
const lemonadeConnection = await seedConnection("lemonade", {
|
|
providerSpecificData: {
|
|
baseUrl: "http://localhost:13305/api/v1",
|
|
},
|
|
});
|
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
const target = String(url);
|
|
assert.equal(init.headers.Authorization, undefined);
|
|
if (target === "http://localhost:1234/v1/models") {
|
|
return Response.json({
|
|
data: [{ id: "local-model", name: "Local Model" }],
|
|
});
|
|
}
|
|
if (target === "http://localhost:13305/api/v1/models") {
|
|
return Response.json({
|
|
data: [{ id: "Llama-3.2-1B-Instruct-Hybrid", name: "Lemonade Llama" }],
|
|
});
|
|
}
|
|
throw new Error(`unexpected fetch: ${target}`);
|
|
};
|
|
|
|
const lmStudioResponse = await callRoute(lmStudioConnection.id);
|
|
const lmStudioBody = (await lmStudioResponse.json()) as any;
|
|
const lemonadeResponse = await callRoute(lemonadeConnection.id);
|
|
const lemonadeBody = (await lemonadeResponse.json()) as any;
|
|
|
|
assert.equal(lmStudioResponse.status, 200);
|
|
assert.equal(lmStudioBody.provider, "lm-studio");
|
|
assert.equal(lmStudioBody.source, "api");
|
|
assert.deepEqual(lmStudioBody.models, [{ id: "local-model", name: "Local Model" }]);
|
|
|
|
assert.equal(lemonadeResponse.status, 200);
|
|
assert.equal(lemonadeBody.provider, "lemonade");
|
|
assert.equal(lemonadeBody.source, "api");
|
|
assert.deepEqual(lemonadeBody.models, [
|
|
{ id: "Llama-3.2-1B-Instruct-Hybrid", name: "Lemonade Llama" },
|
|
]);
|
|
});
|
|
|
|
test("provider models route returns the local catalog for built-in image providers", async () => {
|
|
const connection = await seedConnection("topaz", {
|
|
apiKey: "topaz-key",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "topaz");
|
|
assert.ok(Array.isArray(body.models));
|
|
assert.deepEqual(body.models, [{ id: "topaz-enhance", name: "topaz-enhance" }]);
|
|
});
|
|
|
|
test("provider models route prefers the remote OpenRouter /models API over static image models", async () => {
|
|
const connection = await seedConnection("openrouter", {
|
|
apiKey: "openrouter-key",
|
|
});
|
|
const seenUrls = [];
|
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
seenUrls.push(String(url));
|
|
assert.equal(init.method, "GET");
|
|
assert.equal(init.headers.Authorization, "Bearer openrouter-key");
|
|
return Response.json({
|
|
data: [{ id: "openai/gpt-4.1", name: "GPT-4.1 via OpenRouter" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id, "?refresh=true");
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "api");
|
|
assert.deepEqual(seenUrls, ["https://openrouter.ai/api/v1/models"]);
|
|
assert.deepEqual(body.models, [{ id: "openai/gpt-4.1", name: "GPT-4.1 via OpenRouter" }]);
|
|
});
|
|
|
|
test("provider models route returns the local catalog for embedding and rerank providers", async () => {
|
|
const voyage = await seedConnection("voyage-ai", {
|
|
apiKey: "voyage-key",
|
|
});
|
|
const jina = await seedConnection("jina-ai", {
|
|
apiKey: "jina-key",
|
|
});
|
|
|
|
const [voyageResponse, jinaResponse] = await Promise.all([
|
|
callRoute(voyage.id),
|
|
callRoute(jina.id),
|
|
]);
|
|
const voyageBody = (await voyageResponse.json()) as any;
|
|
const jinaBody = (await jinaResponse.json()) as any;
|
|
|
|
assert.equal(voyageResponse.status, 200);
|
|
assert.equal(voyageBody.provider, "voyage-ai");
|
|
assert.equal(voyageBody.source, "local_catalog");
|
|
assert.ok(voyageBody.models.some((model) => model.id === "voyage-4-large"));
|
|
assert.ok(voyageBody.models.some((model) => model.id === "voyage-code-3"));
|
|
assert.ok(voyageBody.models.some((model) => model.id === "voyage-4-lite"));
|
|
|
|
assert.equal(jinaResponse.status, 200);
|
|
assert.equal(jinaBody.provider, "jina-ai");
|
|
assert.equal(jinaBody.source, "local_catalog");
|
|
assert.ok(
|
|
jinaBody.models.some(
|
|
(model) =>
|
|
model.id === "jina-embeddings-v5-text-small" &&
|
|
model.apiFormat === "embeddings" &&
|
|
model.supportedEndpoints?.includes("embeddings")
|
|
)
|
|
);
|
|
assert.ok(
|
|
jinaBody.models.some(
|
|
(model) =>
|
|
model.id === "jina-reranker-v3" &&
|
|
model.apiFormat === "rerank" &&
|
|
model.supportedEndpoints?.includes("rerank")
|
|
)
|
|
);
|
|
assert.ok(jinaBody.models.some((model) => model.id === "jina-reranker-m0"));
|
|
});
|
|
|
|
test("provider models route flags intentional local-catalog-only providers so model-sync imports them (#5460/#5465)", async () => {
|
|
// reka + voyage-ai never do a remote /models fetch — their local catalog is
|
|
// the intended source, so the response must carry `intentional: true` for the
|
|
// sync route to import instead of 502-ing ("local catalog fallback not synced").
|
|
const reka = await seedConnection("reka", { apiKey: "reka-key" });
|
|
const voyage = await seedConnection("voyage-ai", { apiKey: "voyage-key" });
|
|
|
|
const [rekaBody, voyageBody] = await Promise.all([
|
|
callRoute(reka.id).then((r) => r.json() as any),
|
|
callRoute(voyage.id).then((r) => r.json() as any),
|
|
]);
|
|
|
|
assert.equal(rekaBody.source, "local_catalog");
|
|
assert.equal(rekaBody.intentional, true, "reka local catalog must be flagged intentional");
|
|
assert.equal(voyageBody.source, "local_catalog");
|
|
assert.equal(voyageBody.intentional, true, "voyage-ai local catalog must be flagged intentional");
|
|
});
|
|
|
|
test("provider models route does NOT flag a degraded remote-fetch fallback as intentional (#5460/#5465)", async () => {
|
|
// aimlapi normally discovers remotely; when the live fetch fails it falls back
|
|
// to the local catalog — that IS degraded and must NOT be flagged intentional,
|
|
// so model-sync still surfaces the failure (502) for it.
|
|
const connection = await seedConnection("aimlapi", { apiKey: "aiml-key" });
|
|
globalThis.fetch = async () => new Response("upstream down", { status: 500 });
|
|
|
|
const body = (await (await callRoute(connection.id)).json()) as any;
|
|
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.notEqual(body.intentional, true, "degraded fallback must not be flagged intentional");
|
|
});
|
|
|
|
test("provider models route returns the local catalog for Runway video models", async () => {
|
|
const connection = await seedConnection("runwayml", {
|
|
apiKey: "runway-key",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "runwayml");
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.ok(body.models.some((model) => model.id === "gen4.5"));
|
|
assert.ok(body.models.some((model) => model.id === "veo3.1"));
|
|
assert.ok(body.models.some((model) => model.id === "gen3a_turbo"));
|
|
});
|
|
|
|
test("provider models route returns the updated local catalog for GitHub Copilot", async () => {
|
|
const connection = await seedConnection("github", {
|
|
authType: "oauth",
|
|
apiKey: null,
|
|
accessToken: "github-access",
|
|
providerSpecificData: {
|
|
copilotToken: "copilot-token",
|
|
},
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "github");
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.ok(body.models.some((model) => model.id === "gpt-5.4"));
|
|
assert.ok(body.models.some((model) => model.id === "gpt-5.3-codex"));
|
|
assert.ok(body.models.some((model) => model.id === "claude-opus-4.7"));
|
|
assert.equal(
|
|
body.models.some((model) => model.id === "gpt-5.1"),
|
|
false
|
|
);
|
|
});
|
|
|
|
test("provider models route returns codex gpt-5.4 effort variants in the local catalog", async () => {
|
|
const connection = await seedConnection("codex", {
|
|
authType: "oauth",
|
|
apiKey: null,
|
|
accessToken: "codex-access",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
const modelIds = new Set((body.models || []).map((model: any) => model.id));
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "codex");
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.ok(modelIds.has("gpt-5.4"));
|
|
assert.ok(modelIds.has("gpt-5.4-low"));
|
|
assert.ok(modelIds.has("gpt-5.4-medium"));
|
|
assert.ok(modelIds.has("gpt-5.4-high"));
|
|
assert.ok(modelIds.has("gpt-5.4-xhigh"));
|
|
});
|
|
|
|
test("provider models route returns the expanded local catalog for Kiro", async () => {
|
|
const connection = await seedConnection("kiro", {
|
|
authType: "oauth",
|
|
apiKey: null,
|
|
accessToken: "kiro-access",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "kiro");
|
|
assert.equal(body.source, "local_catalog");
|
|
const kiroIds = new Set(body.models.map((model) => model.id)); // #6170: real upstream lineup
|
|
assert.ok(kiroIds.has("claude-sonnet-5") && kiroIds.has("claude-sonnet-4.5") && kiroIds.has("claude-haiku-4.5"));
|
|
assert.equal(kiroIds.has("claude-opus-4.7") || kiroIds.has("claude-sonnet-4.6"), false); // fabricated ids removed
|
|
});
|
|
|
|
test("provider models route returns the local catalog for new built-in chat-openai-compat providers", async () => {
|
|
const connection = await seedConnection("deepinfra", {
|
|
apiKey: "deepinfra-key",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "deepinfra");
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.match(body.warning, /local catalog/i);
|
|
assert.ok(Array.isArray(body.models));
|
|
assert.ok(body.models.length > 0);
|
|
assert.ok(body.models.some((model) => model.id === "openai/gpt-oss-120b"));
|
|
});
|
|
|
|
test("provider models route merges Upstage chat and embedding catalogs", async () => {
|
|
const connection = await seedConnection("upstage", {
|
|
apiKey: "upstage-key",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
const modelIds = body.models.map((model) => model.id);
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "upstage");
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.ok(modelIds.includes("solar-pro3"));
|
|
assert.ok(modelIds.includes("solar-mini"));
|
|
assert.ok(modelIds.includes("embedding-query"));
|
|
assert.ok(modelIds.includes("embedding-passage"));
|
|
assert.equal(modelIds.includes("document-parse"), false);
|
|
});
|
|
|
|
test("provider models route caches discovered opencode-go models per connection", async () => {
|
|
const connection = await seedConnection("opencode-go", {
|
|
apiKey: "opencode-go-key",
|
|
});
|
|
let fetchCalls = 0;
|
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
fetchCalls += 1;
|
|
assert.equal(String(url), "https://opencode.ai/zen/go/v1/models");
|
|
assert.equal(init.method, "GET");
|
|
assert.equal(init.headers.Authorization, "Bearer opencode-go-key");
|
|
return Response.json({
|
|
data: [{ id: "glm-5.1", name: "GLM 5.1" }],
|
|
});
|
|
};
|
|
|
|
const firstResponse = await callRoute(connection.id);
|
|
const firstBody = (await firstResponse.json()) as any;
|
|
const cachedModels = await modelsDb.getSyncedAvailableModelsForConnection(
|
|
"opencode-go",
|
|
connection.id
|
|
);
|
|
|
|
assert.equal(firstResponse.status, 200);
|
|
assert.equal(firstBody.source, "api");
|
|
assert.deepEqual(firstBody.models, [{ id: "glm-5.1", name: "GLM 5.1", owned_by: "opencode-go" }]);
|
|
assert.deepEqual(cachedModels, [{ id: "glm-5.1", name: "GLM 5.1", source: "imported" }]);
|
|
|
|
globalThis.fetch = async () => {
|
|
throw new Error("cached route should not hit upstream");
|
|
};
|
|
|
|
const cachedResponse = await callRoute(connection.id);
|
|
const cachedBody = (await cachedResponse.json()) as any;
|
|
|
|
assert.equal(cachedResponse.status, 200);
|
|
assert.equal(cachedBody.source, "cache");
|
|
assert.deepEqual(cachedBody.models, [{ id: "glm-5.1", name: "GLM 5.1", source: "imported" }]);
|
|
assert.equal(fetchCalls, 1);
|
|
});
|
|
|
|
test("provider models route falls back to cached models when a refresh fails", async () => {
|
|
const connection = await seedConnection("opencode-go", {
|
|
apiKey: "opencode-go-key",
|
|
});
|
|
await modelsDb.replaceSyncedAvailableModelsForConnection("opencode-go", connection.id, [
|
|
{ id: "cached-go", name: "Cached Go", source: "imported" },
|
|
]);
|
|
let fetchCalls = 0;
|
|
|
|
globalThis.fetch = async () => {
|
|
fetchCalls += 1;
|
|
return new Response("upstream unavailable", { status: 503 });
|
|
};
|
|
|
|
const response = await callRoute(connection.id, "?refresh=true");
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "cache");
|
|
assert.match(body.warning, /cached catalog/i);
|
|
assert.deepEqual(body.models, [{ id: "cached-go", name: "Cached Go", source: "imported" }]);
|
|
// T39 multi-endpoint discovery probes `${base}/v1/models` then `${base}/models`
|
|
// before giving up; both 503 here, so it makes 2 attempts and then falls back to cache.
|
|
assert.equal(fetchCalls, 2);
|
|
});
|
|
|
|
test("provider models route clears cached discovery when a refresh returns no remote models", async () => {
|
|
const connection = await seedConnection("opencode-go", {
|
|
apiKey: "opencode-go-key",
|
|
});
|
|
await modelsDb.replaceSyncedAvailableModelsForConnection("opencode-go", connection.id, [
|
|
{ id: "cached-go", name: "Cached Go", source: "imported" },
|
|
]);
|
|
|
|
globalThis.fetch = async () => {
|
|
return Response.json({ data: [] });
|
|
};
|
|
|
|
const response = await callRoute(connection.id, "?refresh=true");
|
|
const body = (await response.json()) as any;
|
|
const cachedModels = await modelsDb.getSyncedAvailableModelsForConnection(
|
|
"opencode-go",
|
|
connection.id
|
|
);
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.match(body.warning, /no remote models discovered/i);
|
|
assert.ok(body.models.every((model) => model.id !== "cached-go"));
|
|
assert.deepEqual(cachedModels, []);
|
|
});
|
|
|
|
test("provider models route honors autoFetchModels=false and skips remote discovery", async () => {
|
|
const connection = await seedConnection("opencode-go", {
|
|
apiKey: "opencode-go-key",
|
|
providerSpecificData: {
|
|
autoFetchModels: false,
|
|
},
|
|
});
|
|
let called = false;
|
|
|
|
globalThis.fetch = async () => {
|
|
called = true;
|
|
return Response.json({
|
|
data: [{ id: "glm-5.1", name: "GLM 5.1" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.match(body.warning, /auto-fetch disabled/i);
|
|
assert.equal(called, false);
|
|
assert.ok(body.models.some((model) => model.id === "glm-5"));
|
|
});
|
|
|
|
test("provider models route uses synced models as the authoritative local catalog (#3148)", async () => {
|
|
// A connection that resolves to the local catalog (auto-fetch off, no remote
|
|
// discovery). Once a sync has populated the synced-models table for this
|
|
// provider, the route must surface the synced list — even on a connection
|
|
// that never ran the sync itself — instead of the static catalog.
|
|
const connection = await seedConnection("opencode-go", {
|
|
apiKey: "opencode-go-key",
|
|
providerSpecificData: {
|
|
autoFetchModels: false,
|
|
},
|
|
});
|
|
|
|
await modelsDb.replaceSyncedAvailableModelsForConnection("opencode-go", "synced-conn", [
|
|
{ id: "synced-only-model", name: "Synced Only Model" },
|
|
]);
|
|
|
|
let called = false;
|
|
globalThis.fetch = async () => {
|
|
called = true;
|
|
return Response.json({ data: [] });
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.equal(called, false);
|
|
// Synced models become the catalog…
|
|
assert.ok(
|
|
body.models.some((model) => model.id === "synced-only-model"),
|
|
"synced model should be present in the local catalog"
|
|
);
|
|
// …and the static catalog entries are no longer surfaced for this provider.
|
|
assert.equal(
|
|
body.models.some((model) => model.id === "glm-5"),
|
|
false,
|
|
"static catalog should be superseded by the synced list"
|
|
);
|
|
});
|
|
|
|
test("provider models route retries Antigravity discovery endpoints before returning remote models", async () => {
|
|
const connection = await seedConnection("antigravity", {
|
|
authType: "oauth",
|
|
accessToken: "ag-access",
|
|
apiKey: null,
|
|
});
|
|
const seenUrls: string[] = [];
|
|
antigravityVersion.seedAntigravityVersionCache("1.22.2");
|
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
const urlString = String(url);
|
|
// After PR #2219, the discovery flow calls loadCodeAssist first as a project
|
|
// bootstrap; treat all bootstrap calls as non-fatal failures so the test
|
|
// exercises the discovery retry path.
|
|
if (urlString.includes("/v1internal:loadCodeAssist")) {
|
|
return new Response("nope", { status: 503 });
|
|
}
|
|
seenUrls.push(urlString);
|
|
if (seenUrls.length === 1) {
|
|
return new Response("unavailable", { status: 503 });
|
|
}
|
|
|
|
assert.equal(init.method, "POST");
|
|
assert.equal(init.headers.Authorization, "Bearer ag-access");
|
|
assert.match(init.headers["User-Agent"], /^Antigravity\/1\.22\.2 /);
|
|
assert.equal(init.headers["x-goog-api-client"], undefined);
|
|
// Use a model id that is in the current user-callable Antigravity allowlist, otherwise
|
|
// filterUserCallableAntigravityModels() drops it and discovery silently yields 0 models
|
|
// → the route falls back to local_catalog instead of returning the remote (api) list.
|
|
return Response.json({
|
|
models: [{ id: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
// After PR #2219, the route tries `:fetchAvailableModels` URLs before
|
|
// `:models` URLs. The test mock returns 503 on the first call and success
|
|
// on the second, so only the first two `:fetchAvailableModels` URLs are
|
|
// hit — `:models` URLs are never reached. Assert on the actual discovery
|
|
// sequence the route follows.
|
|
const discoveryUrls = seenUrls.filter(
|
|
(url) => url.includes("/v1internal:fetchAvailableModels") || url.includes("/v1internal:models")
|
|
);
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "api");
|
|
assert.deepEqual(discoveryUrls, [
|
|
"https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels",
|
|
"https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels",
|
|
]);
|
|
assert.deepEqual(body.models, [{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" }]);
|
|
});
|
|
|
|
test("provider models route falls back through all Antigravity discovery endpoints when needed", async () => {
|
|
const connection = await seedConnection("antigravity", {
|
|
authType: "oauth",
|
|
accessToken: "ag-access",
|
|
apiKey: null,
|
|
});
|
|
const seenUrls = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
seenUrls.push(String(url));
|
|
return new Response("down", { status: 502 });
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
const discoveryUrls = seenUrls.filter((url) => url.includes("/v1internal:models"));
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.match(body.warning, /local catalog/i);
|
|
assert.deepEqual(discoveryUrls, [
|
|
"https://daily-cloudcode-pa.googleapis.com/v1internal:models",
|
|
"https://cloudcode-pa.googleapis.com/v1internal:models",
|
|
"https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:models",
|
|
]);
|
|
assert.ok(body.models.some((model) => model.id === "gemini-3-pro-preview"));
|
|
});
|
|
|
|
test("provider models route returns the local catalog for OAuth-backed Qwen connections", async () => {
|
|
const connection = await seedConnection("qwen", {
|
|
authType: "oauth",
|
|
accessToken: "qwen-access",
|
|
apiKey: null,
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.ok(Array.isArray(body.models));
|
|
});
|
|
|
|
test("provider models route filters hidden models from the static Claude catalog when requested", async () => {
|
|
const connection = await seedConnection("claude", {
|
|
authType: "oauth",
|
|
accessToken: "claude-access",
|
|
apiKey: null,
|
|
});
|
|
modelsDb.mergeModelCompatOverride("claude", "claude-sonnet-4-6", { isHidden: true });
|
|
|
|
const response = await callRoute(connection.id, "?excludeHidden=true");
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "claude");
|
|
assert.ok(body.models.some((model) => model.id === "claude-opus-4-7"));
|
|
assert.equal(
|
|
body.models.some((model) => model.id === "claude-sonnet-4-6"),
|
|
false
|
|
);
|
|
assert.ok(body.models.some((model) => model.id === "claude-opus-4-6"));
|
|
});
|
|
|
|
test("provider models route rejects Anthropic-compatible providers without a base URL", async () => {
|
|
const connection = await seedConnection("anthropic-compatible-demo", {
|
|
apiKey: "sk-anthropic-compatible",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), {
|
|
error: "No base URL configured for Anthropic compatible provider",
|
|
});
|
|
});
|
|
|
|
test("provider models route trims Anthropic-compatible message URLs and filters hidden upstream models", async () => {
|
|
const connection = await seedConnection("anthropic-compatible-demo", {
|
|
apiKey: "sk-anthropic-compatible",
|
|
accessToken: "anthropic-access",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com/v1/messages",
|
|
},
|
|
});
|
|
modelsDb.mergeModelCompatOverride("anthropic-compatible-demo", "hidden-model", {
|
|
isHidden: true,
|
|
});
|
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
assert.equal(String(url), "https://proxy.example.com/v1/models");
|
|
assert.equal(init.method, "GET");
|
|
assert.equal(init.headers["Content-Type"], "application/json");
|
|
assert.equal(init.headers["x-api-key"], "sk-anthropic-compatible");
|
|
assert.equal(init.headers.Authorization, "Bearer anthropic-access");
|
|
assert.equal(init.headers["anthropic-version"], "2023-06-01");
|
|
|
|
return Response.json({
|
|
data: [
|
|
{ id: "visible-model", name: "Visible Model" },
|
|
{ id: "hidden-model", name: "Hidden Model" },
|
|
],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id, "?excludeHidden=true");
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.deepEqual(body.models, [{ id: "visible-model", name: "Visible Model" }]);
|
|
});
|
|
|
|
test("provider models route forwards Anthropic-compatible upstream failures", async () => {
|
|
const connection = await seedConnection("anthropic-compatible-demo", {
|
|
apiKey: "sk-anthropic-compatible",
|
|
providerSpecificData: {
|
|
baseUrl: "https://proxy.example.com/v1/messages",
|
|
},
|
|
});
|
|
|
|
globalThis.fetch = async () => new Response("upstream unavailable", { status: 502 });
|
|
|
|
const response = await callRoute(connection.id);
|
|
|
|
assert.equal(response.status, 502);
|
|
assert.deepEqual(await response.json(), {
|
|
error: "Failed to fetch models: 502",
|
|
});
|
|
});
|
|
|
|
test("provider models route paginates generic providers and filters hidden models when requested", async () => {
|
|
const connection = await seedConnection("gemini", {
|
|
apiKey: "gm-key",
|
|
});
|
|
modelsDb.mergeModelCompatOverride("gemini", "gemini-hidden", { isHidden: true });
|
|
const seenUrls = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
const currentUrl = String(url);
|
|
seenUrls.push(currentUrl);
|
|
if (!currentUrl.includes("pageToken=")) {
|
|
assert.match(currentUrl, /key=gm-key/);
|
|
return Response.json({
|
|
models: [
|
|
{
|
|
name: "models/gemini-visible",
|
|
displayName: "Gemini Visible",
|
|
supportedGenerationMethods: ["generateContent"],
|
|
},
|
|
{
|
|
name: "models/gemini-hidden",
|
|
displayName: "Gemini Hidden",
|
|
supportedGenerationMethods: ["generateContent"],
|
|
},
|
|
],
|
|
nextPageToken: "page-2",
|
|
});
|
|
}
|
|
|
|
assert.match(currentUrl, /pageToken=page-2/);
|
|
assert.match(currentUrl, /key=gm-key/);
|
|
return Response.json({
|
|
models: [
|
|
{
|
|
name: "models/text-embedding-004",
|
|
displayName: "Text Embedding 004",
|
|
supportedGenerationMethods: ["embedContent"],
|
|
},
|
|
],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id, "?excludeHidden=true");
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.deepEqual(body.models.map((model) => model.id).sort(), [
|
|
"gemini-visible",
|
|
"text-embedding-004",
|
|
]);
|
|
assert.equal(seenUrls.length, 2);
|
|
});
|
|
|
|
test("provider models route stops pagination when the upstream repeats the next page token", async () => {
|
|
const connection = await seedConnection("gemini", {
|
|
apiKey: "gm-key",
|
|
});
|
|
let calls = 0;
|
|
|
|
globalThis.fetch = async () => {
|
|
calls += 1;
|
|
return Response.json({
|
|
models: [
|
|
{
|
|
name: `models/gemini-page-${calls}`,
|
|
displayName: `Gemini Page ${calls}`,
|
|
supportedGenerationMethods: ["generateContent"],
|
|
},
|
|
],
|
|
nextPageToken: "duplicate-token",
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.deepEqual(
|
|
body.models.map((model) => model.id),
|
|
["gemini-page-1", "gemini-page-2"]
|
|
);
|
|
assert.equal(calls, 2);
|
|
});
|
|
|
|
test("provider models route forwards upstream status codes for generic provider model fetch failures", async () => {
|
|
const connection = await seedConnection("groq", {
|
|
apiKey: "groq-models-token",
|
|
});
|
|
|
|
globalThis.fetch = async () => new Response("upstream unavailable", { status: 503 });
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.match(body.warning, /local catalog/i);
|
|
assert.ok(Array.isArray(body.models));
|
|
assert.ok(body.models.length > 0);
|
|
});
|
|
|
|
test("provider models route returns 500 when fetching models throws unexpectedly", async () => {
|
|
const connection = await seedConnection("groq", {
|
|
apiKey: "groq-models-token",
|
|
});
|
|
|
|
globalThis.fetch = async () => {
|
|
throw new Error("socket closed");
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.match(body.warning, /local catalog/i);
|
|
});
|
|
|
|
test("provider models route rejects generic providers without any configured token", async () => {
|
|
const connection = await seedConnection("groq", {
|
|
apiKey: null,
|
|
accessToken: null,
|
|
});
|
|
let called = false;
|
|
|
|
globalThis.fetch = async () => {
|
|
called = true;
|
|
return Response.json({ data: [] });
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.source, "local_catalog");
|
|
assert.match(body.warning, /local catalog/i);
|
|
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 Azure OpenAI deployments from the resource endpoint", async () => {
|
|
const connection = await seedConnection("azure-openai", {
|
|
apiKey: "azure-openai-key",
|
|
providerSpecificData: {
|
|
baseUrl: "https://my-resource.openai.azure.com/openai",
|
|
apiVersion: "2024-12-01-preview",
|
|
},
|
|
});
|
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
assert.equal(
|
|
String(url),
|
|
"https://my-resource.openai.azure.com/openai/deployments?api-version=2024-12-01-preview"
|
|
);
|
|
assert.equal(init.method, "GET");
|
|
assert.equal(init.headers["api-key"], "azure-openai-key");
|
|
|
|
return Response.json({
|
|
data: [
|
|
{ id: "gpt4o-prod", model: "gpt-4o", display_name: "GPT-4o Production" },
|
|
{ id: "o3-mini-staging", model: "o3-mini" },
|
|
],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "azure-openai");
|
|
assert.equal(body.source, "api");
|
|
assert.deepEqual(body.models, [
|
|
{ id: "gpt4o-prod", name: "GPT-4o Production", owned_by: "azure-openai" },
|
|
{ id: "o3-mini-staging", name: "o3-mini-staging", owned_by: "azure-openai" },
|
|
]);
|
|
});
|
|
|
|
test("provider models route discovers native Bedrock foundation models and inference profiles", async () => {
|
|
const connection = await seedConnection("bedrock", {
|
|
apiKey: "bedrock-key",
|
|
providerSpecificData: {
|
|
region: "eu-west-2",
|
|
},
|
|
});
|
|
const seenUrls: string[] = [];
|
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
const target = String(url);
|
|
seenUrls.push(target);
|
|
assert.equal(init.method, "GET");
|
|
assert.equal(init.headers.Authorization, "Bearer bedrock-key");
|
|
|
|
if (
|
|
target === "https://bedrock.eu-west-2.amazonaws.com/foundation-models?byOutputModality=TEXT"
|
|
) {
|
|
return Response.json({
|
|
modelSummaries: [
|
|
{
|
|
modelId: "anthropic.claude-sonnet-4-6",
|
|
modelName: "Claude Sonnet 4.6",
|
|
providerName: "Anthropic",
|
|
inputModalities: ["TEXT", "IMAGE"],
|
|
outputModalities: ["TEXT"],
|
|
responseStreamingSupported: true,
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
if (
|
|
target ===
|
|
"https://bedrock.eu-west-2.amazonaws.com/inference-profiles?maxResults=100&typeEquals=SYSTEM_DEFINED"
|
|
) {
|
|
return Response.json({
|
|
inferenceProfileSummaries: [
|
|
{
|
|
inferenceProfileId: "eu.anthropic.claude-sonnet-4-6",
|
|
inferenceProfileName: "EU Claude Sonnet 4.6",
|
|
models: [
|
|
{
|
|
modelArn: "arn:aws:bedrock:eu-west-2::foundation-model/anthropic.claude-sonnet-4-6",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
throw new Error("unexpected fetch: " + target);
|
|
};
|
|
|
|
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(seenUrls, [
|
|
"https://bedrock.eu-west-2.amazonaws.com/foundation-models?byOutputModality=TEXT",
|
|
"https://bedrock.eu-west-2.amazonaws.com/inference-profiles?maxResults=100&typeEquals=SYSTEM_DEFINED",
|
|
]);
|
|
assert.deepEqual(body.models, [
|
|
{
|
|
id: "anthropic.claude-sonnet-4-6",
|
|
name: "Claude Sonnet 4.6",
|
|
owned_by: "Anthropic",
|
|
source: "foundation",
|
|
supportsStreaming: true,
|
|
supportsVision: true,
|
|
inputTokenLimit: 1000000,
|
|
outputTokenLimit: 64000,
|
|
},
|
|
{
|
|
id: "eu.anthropic.claude-sonnet-4-6",
|
|
name: "EU Claude Sonnet 4.6",
|
|
owned_by: "bedrock",
|
|
source: "inference_profile",
|
|
supportsStreaming: true,
|
|
inputTokenLimit: 1000000,
|
|
outputTokenLimit: 64000,
|
|
},
|
|
]);
|
|
});
|
|
|
|
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 always returns the Reka preset catalog", async () => {
|
|
const connection = await seedConnection("reka", {
|
|
apiKey: "reka-key",
|
|
providerSpecificData: {
|
|
baseUrl: "https://api.reka.ai/v1",
|
|
},
|
|
});
|
|
|
|
globalThis.fetch = async () => {
|
|
throw new Error("Reka models endpoint should not be probed");
|
|
};
|
|
|
|
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, "local_catalog");
|
|
assert.deepEqual(
|
|
body.models.map((model) => model.id),
|
|
["reka-flash-3", "reka-flash", "reka-edge-2603"]
|
|
);
|
|
});
|
|
|
|
test("provider models route returns Reka local catalog without an API key", async () => {
|
|
const connection = await seedConnection("reka", {
|
|
providerSpecificData: {
|
|
baseUrl: "https://api.reka.ai/v1",
|
|
},
|
|
});
|
|
|
|
globalThis.fetch = async () => {
|
|
throw new Error("Reka models endpoint should not be probed without a token");
|
|
};
|
|
|
|
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, "local_catalog");
|
|
assert.deepEqual(
|
|
body.models.map((model) => model.id),
|
|
["reka-flash-3", "reka-flash", "reka-edge-2603"]
|
|
);
|
|
});
|
|
|
|
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",
|
|
});
|
|
|
|
const response = await callRoute(connection.id);
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), {
|
|
error: "Provider unsupported-provider does not support models listing",
|
|
});
|
|
});
|
|
|
|
test("provider models route uses provider-specific auth headers for Kimi Coding", async () => {
|
|
const connection = await seedConnection("kimi-coding", {
|
|
apiKey: "kimi-coding-key",
|
|
});
|
|
|
|
globalThis.fetch = async (url, init = {}) => {
|
|
assert.equal(String(url), "https://api.kimi.com/coding/v1/models");
|
|
assert.equal(init.method, "GET");
|
|
assert.equal(init.headers["x-api-key"], "kimi-coding-key");
|
|
assert.equal(init.headers.Authorization, undefined);
|
|
|
|
return Response.json({
|
|
data: [{ id: "kimi-k2.5", name: "Kimi K2.5" }],
|
|
});
|
|
};
|
|
|
|
const response = await callRoute(connection.id);
|
|
const body = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.provider, "kimi-coding");
|
|
assert.deepEqual(body.models, [{ id: "kimi-k2.5", name: "Kimi K2.5" }]);
|
|
});
|