diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index aa41aee014..2816ad6439 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -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 . 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", @@ -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": { @@ -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": { @@ -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, }, @@ -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", }, @@ -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", }, @@ -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", }, @@ -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", }, @@ -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 . Fully OpenAI-compatible. API base URL: https://api.tokenrouter.com/v1.", apiHint: diff --git a/tests/unit/bazaarlink-provider-integrity.test.ts b/tests/unit/bazaarlink-provider-integrity.test.ts new file mode 100644 index 0000000000..13302d1b1e --- /dev/null +++ b/tests/unit/bazaarlink-provider-integrity.test.ts @@ -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>)["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"); + }); +});