Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions src/shared/constants/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -999,9 +999,12 @@ export const APIKEY_PROVIDERS = {
textIcon: "BZ",
website: "https://bazaarlink.ai",
hasFree: true,
freeNote: "Free tier with auto:free routing — zero-cost inference, no credit card required",
freeNote:
"Free tier: 4M tokens/day per account with auto:free routing — zero-cost inference, no credit card required.",
authHint:
"Use your BazaarLink API key (starts with sk-bl-) in Authorization: Bearer <key>. OpenAI SDK works with base URL https://bazaarlink.ai/api/v1. Models use provider/model-name format.",
apiHint:
"Get free API key at https://bazaarlink.ai — use model 'auto:free' for zero-cost inference. OpenAI-compatible.",
"Create a free API key at https://bazaarlink.ai — model 'auto:free' routes to zero-cost inference. All models use the provider/model-name format, e.g. xiaomi/mimo-v2.5-pro.",
},
xai: {
id: "xai",
Expand Down Expand Up @@ -1745,7 +1748,8 @@ export const APIKEY_PROVIDERS = {
textIcon: "GLB",
website: "https://opengateway.gitlawb.com",
hasFree: false,
freeNote: "Free MiMo (xiaomi/mimo-v2.5) revoked 2026-05 — Opengateway is now a pay-as-you-go credit gateway; no recurring free model.",
freeNote:
"Free MiMo (xiaomi/mimo-v2.5) revoked 2026-05 — Opengateway is now a pay-as-you-go credit gateway; no recurring free model.",
apiHint: "Get your API key from Gitlawb Opengateway dashboard.",
},
"gitlawb-gmi": {
Expand All @@ -1757,7 +1761,8 @@ export const APIKEY_PROVIDERS = {
textIcon: "GMI",
website: "https://opengateway.gitlawb.com",
hasFree: false,
freeNote: "Free Nemotron promo ended 2026-06 — the GMI Cloud route is now pay-as-you-go credit only.",
freeNote:
"Free Nemotron promo ended 2026-06 — the GMI Cloud route is now pay-as-you-go credit only.",
apiHint: "Get your API key from Gitlawb Opengateway dashboard.",
},
"inference-net": {
Expand Down Expand Up @@ -1974,7 +1979,8 @@ export const APIKEY_PROVIDERS = {
textIcon: "CH",
website: "https://chutes.ai",
hasFree: false,
freeNote: "No free tier as of 2026 — Chutes moved to pay-as-you-go (free Early Access ended 2026-03).",
freeNote:
"No free tier as of 2026 — Chutes moved to pay-as-you-go (free Early Access ended 2026-03).",
authHint: "Bearer API key for the Chutes OpenAI-compatible gateway.",
passthroughModels: true,
},
Expand Down Expand Up @@ -2082,7 +2088,8 @@ export const APIKEY_PROVIDERS = {
textIcon: "IF",
website: "https://xinghuo.xfyun.cn",
hasFree: true,
freeNote: "Spark Lite is free (2 QPS rate-limited), but iFlytek ToS §2.4(3) prohibits programmatic extraction and requires Chinese real-name auth — use with caution.",
freeNote:
"Spark Lite is free (2 QPS rate-limited), but iFlytek ToS §2.4(3) prohibits programmatic extraction and requires Chinese real-name auth — use with caution.",
passthroughModels: true,
authHint: "Get API key at console.xfyun.cn",
},
Expand All @@ -2108,7 +2115,8 @@ export const APIKEY_PROVIDERS = {
textIcon: "YI",
website: "https://01.ai",
hasFree: false,
freeNote: "No free API tier (2026) — Yi-Light retired; platform.01.ai is pay-as-you-go (Yi-Lightning paid). Open weights are download-only.",
freeNote:
"No free API tier (2026) — Yi-Light retired; platform.01.ai is pay-as-you-go (Yi-Lightning paid). Open weights are download-only.",
passthroughModels: true,
authHint: "Get API key at platform.lingyiwanwu.com",
},
Expand Down Expand Up @@ -2186,7 +2194,8 @@ export const APIKEY_PROVIDERS = {
textIcon: "SD",
website: "https://xinghuo.xfyun.cn",
hasFree: true,
freeNote: "Spark Lite free (alias for iflytek), but ToS restricts to personal/non-commercial use and prohibits relaying access to third parties — use with caution.",
freeNote:
"Spark Lite free (alias for iflytek), but ToS restricts to personal/non-commercial use and prohibits relaying access to third parties — use with caution.",
passthroughModels: true,
authHint: "Get API key at console.xfyun.cn",
},
Expand Down Expand Up @@ -2296,7 +2305,8 @@ export const APIKEY_PROVIDERS = {
textIcon: "MA",
website: "https://monsterapi.ai",
hasFree: true,
freeNote: "One-time signup trial credits for decentralized GPU inference (no recurring free plan). No credit card required.",
freeNote:
"One-time signup trial credits for decentralized GPU inference (no recurring free plan). No credit card required.",
passthroughModels: true,
authHint: "Get API key at monsterapi.ai",
},
Expand Down Expand Up @@ -2439,7 +2449,8 @@ export const APIKEY_PROVIDERS = {
textIcon: "TK",
website: "https://tokenrouter.com",
hasFree: true,
freeNote: "Free tier includes the MiniMax 3 model. Get your API key at https://tokenrouter.com.",
freeNote:
"Free tier includes the MiniMax 3 model. Get your API key at https://tokenrouter.com.",
authHint:
"Use your TokenRouter API key in Authorization: Bearer <key>. Fully OpenAI-compatible. API base URL: https://api.tokenrouter.com/v1.",
apiHint:
Expand Down
76 changes: 76 additions & 0 deletions tests/unit/bazaarlink-provider-integrity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, it } from "node:test";
import assert from "node:assert";

describe("bazaarlink provider entry in APIKEY_PROVIDERS", () => {
it("exists with required fields", async () => {
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
const p = (APIKEY_PROVIDERS as Record<string, Record<string, unknown>>)["bazaarlink"];
assert.ok(p, "bazaarlink must exist in APIKEY_PROVIDERS");
assert.equal(p.id, "bazaarlink");
assert.equal(p.alias, "bzl");
assert.equal(p.name, "BazaarLink");
assert.equal(p.icon, "storefront");
assert.equal(p.color, "#6366F1");
assert.equal(p.textIcon, "BZ");
assert.equal(p.website, "https://bazaarlink.ai");
});

it("advertises the free tier and includes authHint", async () => {
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
const p = (
APIKEY_PROVIDERS as Record<
string,
{ hasFree?: boolean; authHint?: string; freeNote?: string; apiHint?: string }
>
)["bazaarlink"];
assert.equal(p.hasFree, true, "bazaarlink should advertise a free tier");
assert.ok(p.authHint, "bazaarlink must have an authHint field");
assert.ok(p.authHint.includes("sk-bl-"), "authHint should mention the sk-bl- key prefix");
assert.ok(p.freeNote, "bazaarlink must have a freeNote describing the free tier");
assert.ok(p.apiHint, "bazaarlink must have an apiHint");
assert.ok(p.apiHint.includes("bazaarlink.ai"), "apiHint should reference bazaarlink.ai");
});

it("is registered in the execution registry", async () => {
const { getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts");
const entry = getRegistryEntry("bazaarlink");
assert.ok(entry, "bazaarlink must have a registry entry");
assert.equal(entry.format, "openai");
assert.equal(entry.authType, "apikey");
assert.ok(entry.baseUrl, "registry entry must have a baseUrl");
assert.ok(entry.modelsUrl, "registry entry must have a modelsUrl");
assert.ok(
entry.models && entry.models.length > 0,
"registry entry must list at least one model"
);
assert.ok(
entry.models.some((m: { id: string }) => m.id === "auto:free"),
"registry must include auto:free model"
);
assert.ok(
entry.models.some((m: { id: string }) => m.id === "mimo-v2.5-pro"),
"registry must include mimo-v2.5-pro"
);
});

it("is resolvable through the static catalog for managed connections", async () => {
const { resolveStaticProviderCatalogEntry } =
await import("../../src/lib/providers/catalog.ts");
const entry = resolveStaticProviderCatalogEntry("bazaarlink");
assert.ok(entry, "bazaarlink must resolve through the static provider catalog");
assert.ok(entry.id, "bazaarlink", "resolved entry id must match");
assert.equal(entry.category, "apikey", "bazaarlink must be in the apikey category");
});

it("passes isManagedProviderConnectionId check so connections can be created", async () => {
const { isManagedProviderConnectionId } = await import("../../src/lib/providers/catalog.ts");
const result = isManagedProviderConnectionId("bazaarlink");
assert.equal(result, true, "bazaarlink must pass isManagedProviderConnectionId");
});

it("supports bulk API key add (not excluded)", async () => {
const { supportsBulkApiKey } = await import("../../src/shared/constants/providers.ts");
const result = supportsBulkApiKey("bazaarlink");
assert.equal(result, true, "bazaarlink must support bulk API key add");
});
});
Loading