diff --git a/src/resources/extensions/sf/provider-catalog-config.js b/src/resources/extensions/sf/provider-catalog-config.js index 6db6ade39..8ae3a652d 100644 --- a/src/resources/extensions/sf/provider-catalog-config.js +++ b/src/resources/extensions/sf/provider-catalog-config.js @@ -141,6 +141,49 @@ export const PROVIDER_CATALOG_CONFIG = { auth: { type: "bearer" }, rateLimits: { scope: "model" }, }, + + // ─── MiniMax ───────────────────────────────────────────────────────────── + // International endpoint (api.minimax.io) — Chinese-network endpoint + // (api.minimaxi.com/anthropic) is handled separately in the SDK layer. + minimax: { + type: "openai", + baseUrl: "https://api.minimax.io", + modelsPath: "/v1/models", + auth: { type: "bearer" }, + rateLimits: { scope: "provider" }, + modelFilter: { + excludePatterns: [], + }, + }, + + // ─── OpenCode Zen ───────────────────────────────────────────────────────── + // Free-tier routing layer — all models are zero-cost. IDs may use "-free" + // suffix or unconventional names (e.g. "big-pickle"); no additional filter. + opencode: { + type: "openai", + baseUrl: "https://opencode.ai/zen", + modelsPath: "/v1/models", + auth: { type: "bearer" }, + rateLimits: { scope: "provider" }, + modelFilter: { + // All opencode entries are intended to be free-tier; the wire IDs + // typically end in "-free" but also include experimental zero-cost + // models (e.g. big-pickle). No additional filtering needed. + excludePatterns: [], + }, + }, + + // ─── OpenCode Go ───────────────────────────────────────────────────────── + // Paid/commercial tier at /zen/go — exposes upstream model IDs directly + // (minimax-m2.7, kimi-k2.6, deepseek-v4-pro, etc.) without a "-free" suffix. + "opencode-go": { + type: "openai", + baseUrl: "https://opencode.ai/zen/go", + modelsPath: "/v1/models", + auth: { type: "bearer" }, + rateLimits: { scope: "provider" }, + modelFilter: { excludePatterns: [] }, + }, }; /** diff --git a/src/resources/extensions/sf/tests/provider-catalog-discovery.test.mjs b/src/resources/extensions/sf/tests/provider-catalog-discovery.test.mjs new file mode 100644 index 000000000..51e922fff --- /dev/null +++ b/src/resources/extensions/sf/tests/provider-catalog-discovery.test.mjs @@ -0,0 +1,108 @@ +/** + * provider-catalog-discovery.test.mjs + * + * Regression tests for live-discovery entries in provider-catalog-config.js. + * Covers: opencode, opencode-go, and minimax baseline fields. + */ +import assert from "node:assert/strict"; +import { describe, test } from "vitest"; +import { + DISCOVERABLE_PROVIDER_IDS, + getProviderCatalogConfig, +} from "../provider-catalog-config.js"; + +describe("opencode discovery config", () => { + test("baseUrl points to /zen path", () => { + const cfg = getProviderCatalogConfig("opencode"); + assert.ok(cfg, "opencode entry must exist"); + assert.equal(cfg.baseUrl, "https://opencode.ai/zen"); + }); + + test("modelsPath is /v1/models", () => { + const cfg = getProviderCatalogConfig("opencode"); + assert.equal(cfg.modelsPath, "/v1/models"); + }); + + test("auth type is bearer", () => { + const cfg = getProviderCatalogConfig("opencode"); + assert.equal(cfg.auth.type, "bearer"); + }); + + test("is included in DISCOVERABLE_PROVIDER_IDS", () => { + assert.ok( + DISCOVERABLE_PROVIDER_IDS.includes("opencode"), + "opencode should be discoverable", + ); + }); +}); + +describe("opencode-go discovery config", () => { + test("baseUrl points to /zen/go path", () => { + const cfg = getProviderCatalogConfig("opencode-go"); + assert.ok(cfg, "opencode-go entry must exist"); + assert.equal(cfg.baseUrl, "https://opencode.ai/zen/go"); + }); + + test("modelsPath is /v1/models", () => { + const cfg = getProviderCatalogConfig("opencode-go"); + assert.equal(cfg.modelsPath, "/v1/models"); + }); + + test("auth type is bearer", () => { + const cfg = getProviderCatalogConfig("opencode-go"); + assert.equal(cfg.auth.type, "bearer"); + }); + + test("is included in DISCOVERABLE_PROVIDER_IDS", () => { + assert.ok( + DISCOVERABLE_PROVIDER_IDS.includes("opencode-go"), + "opencode-go should be discoverable", + ); + }); + + test("baseUrl has /go suffix (distinct from opencode)", () => { + const opencodeBase = getProviderCatalogConfig("opencode").baseUrl; + const goBase = getProviderCatalogConfig("opencode-go").baseUrl; + assert.ok( + goBase.endsWith("/go"), + "opencode-go baseUrl must end in /go", + ); + assert.notEqual(goBase, opencodeBase, "opencode-go baseUrl must differ from opencode"); + }); +}); + +describe("minimax discovery config", () => { + test("baseUrl uses international api.minimax.io endpoint", () => { + const cfg = getProviderCatalogConfig("minimax"); + assert.ok(cfg, "minimax entry must exist"); + assert.ok( + cfg.baseUrl.includes("api.minimax.io"), + `expected api.minimax.io, got ${cfg.baseUrl}`, + ); + assert.ok( + !cfg.baseUrl.includes("minimaxi.com"), + "baseUrl must not use minimaxi.com (Chinese-network endpoint)", + ); + }); + + test("modelsPath is /v1/models", () => { + const cfg = getProviderCatalogConfig("minimax"); + assert.equal(cfg.modelsPath, "/v1/models"); + }); + + test("is included in DISCOVERABLE_PROVIDER_IDS", () => { + assert.ok( + DISCOVERABLE_PROVIDER_IDS.includes("minimax"), + "minimax should be discoverable", + ); + }); +}); + +describe("DISCOVERABLE_PROVIDER_IDS completeness", () => { + test("includes all three newly added providers", () => { + const ids = DISCOVERABLE_PROVIDER_IDS; + assert.ok(ids.includes("opencode"), "missing opencode"); + assert.ok(ids.includes("opencode-go"), "missing opencode-go"); + assert.ok(ids.includes("minimax"), "missing minimax"); + }); +});