feat(providers): live discovery for opencode, opencode-go, minimax
Some checks are pending
CI / detect-changes (push) Waiting to run
CI / docs-check (push) Blocked by required conditions
CI / lint (push) Blocked by required conditions
CI / build (push) Blocked by required conditions
CI / integration-tests (push) Blocked by required conditions
CI / windows-portability (push) Blocked by required conditions
CI / rtk-portability (linux, blacksmith-4vcpu-ubuntu-2404) (push) Blocked by required conditions
CI / rtk-portability (macos, macos-15) (push) Blocked by required conditions
CI / rtk-portability (windows, blacksmith-4vcpu-windows-2025) (push) Blocked by required conditions
Some checks are pending
CI / detect-changes (push) Waiting to run
CI / docs-check (push) Blocked by required conditions
CI / lint (push) Blocked by required conditions
CI / build (push) Blocked by required conditions
CI / integration-tests (push) Blocked by required conditions
CI / windows-portability (push) Blocked by required conditions
CI / rtk-portability (linux, blacksmith-4vcpu-ubuntu-2404) (push) Blocked by required conditions
CI / rtk-portability (macos, macos-15) (push) Blocked by required conditions
CI / rtk-portability (windows, blacksmith-4vcpu-windows-2025) (push) Blocked by required conditions
Three providers were missing from PROVIDER_CATALOG_CONFIG so their
model lists couldn't be auto-discovered. Their wire ids only existed
in packages/ai/src/models.generated.ts as hand-coded entries, meaning
new model variants from these providers required manual catalog edits.
Verified live endpoints respond to /v1/models with bearer auth:
- opencode → https://opencode.ai/zen/v1/models (6 free models)
- opencode-go → https://opencode.ai/zen/go/v1/models (15 models)
- minimax → https://api.minimax.io/v1/models (works)
Added entries:
opencode: baseUrl https://opencode.ai/zen, modelsPath /v1/models
opencode-go: baseUrl https://opencode.ai/zen/go, modelsPath /v1/models
minimax: baseUrl https://api.minimax.io, modelsPath /v1/models
(international endpoint; Chinese-network api.minimaxi.com
still handled separately in the SDK)
Auth keys already wired: OPENCODE_API_KEY, OPENCODE_GO_API_KEY (with
OPENCODE_API_KEY fallback), MINIMAX_API_KEY. No env-api-keys.ts changes.
Combined with 385e0b448 (dynamic canonicalIdFor resolver), new model
variants from these three providers will be auto-grouped in
.sf/model-performance.json without hand-editing CANONICAL_BY_ROUTE.
Live counts after fresh discovery will reveal experimental models
absent from static catalog (e.g. opencode's "big-pickle", opencode-go's
deepseek-v4-pro, mimo-v2.5-pro, hy3-preview). The model-router
tolerates unconventional wire IDs — no naming constraints.
To populate cache: rm -rf ~/.sf/runtime/model-catalog/ + relaunch sf.
Tests: 13 new in provider-catalog-discovery.test.mjs (catalog shape,
modelsPath presence, DISCOVERABLE_PROVIDER_IDS inclusion). Full suite
183 files / 1940 tests pass, zero regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
db3525b933
commit
1b5348e28e
2 changed files with 151 additions and 0 deletions
|
|
@ -141,6 +141,49 @@ export const PROVIDER_CATALOG_CONFIG = {
|
||||||
auth: { type: "bearer" },
|
auth: { type: "bearer" },
|
||||||
rateLimits: { scope: "model" },
|
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: [] },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue