diff --git a/src/lib/providers/volcenginePlanModelDiscovery.ts b/src/lib/providers/volcenginePlanModelDiscovery.ts index 556c78472a..ee7102c729 100644 --- a/src/lib/providers/volcenginePlanModelDiscovery.ts +++ b/src/lib/providers/volcenginePlanModelDiscovery.ts @@ -7,13 +7,16 @@ * top-level Ark actions, authenticated by the same console cookie + csrf * token already captured during plan binding (see volcenginePlanBinding.ts). * - * - Agent Plan: `GetAgentPlanModelMappingMeta` → Result.Data[] - * filter: platform-enabled LLMs plus API-verified canonical registry IDs - * id : RespModelID (already version-suffixed, matches chat endpoint) + * - Agent Plan: `ListAgentPlanLatestModel` → Result.Data[] + * id : ModelId (version-suffixed, matches chat endpoint) * - Coding Plan: `ListArkCodeLatestModel` → Result.Data[] * id : ModelId (version-suffixed) * - * The console API returns only id/name/version/description — NOT capabilities + * Both APIs return the same response shape (ModelId / OutputName / Enabled / + * Description / EnabledThinking). We keep ALL entries — the chat endpoint + * accepts every listed ModelId, and `Enabled` only reflects console visibility. + * + * The console API returns only id/name/description — NOT capabilities * (contextLength, toolCalling, vision, reasoning). We enrich each discovered * model from a static family→capability map keyed by the OutputName/ModelName * prefix, falling back to conservative defaults so new families stay usable @@ -23,8 +26,6 @@ * persist it via replaceSyncedAvailableModelsForConnection. */ -import { VOLCENGINE_AGENT_PLAN_MODELS } from "@omniroute/open-sse/config/providers/registry/volcengine/agent-plan/index.ts"; - import type { SyncedAvailableModelInput } from "@/lib/db/models/synced"; type JsonRecord = Record; @@ -128,8 +129,8 @@ const PLAN_DISCOVERY_CONFIG: Record< } > = { agent: { - action: "GetAgentPlanModelMappingMeta", - payload: { Edition: "agent_plan_personal" }, + action: "ListAgentPlanLatestModel", + payload: {}, referer: AGENT_PLAN_REFERER, requiresAccountId: false, }, @@ -301,48 +302,20 @@ export function enrichModel(model: DiscoveredVolcModel): SyncedAvailableModelInp }; } -const AGENT_PLAN_API_VERIFIED_IDS = new Set(VOLCENGINE_AGENT_PLAN_MODELS.map((model) => model.id)); - /** - * Parse Agent Plan `GetAgentPlanModelMappingMeta` Result.Data[]. + * Parse `ListAgentPlanLatestModel` / `ListArkCodeLatestModel` Result.Data[]. * - * `PlatformAllowStatus` controls console platform visibility/mapping; it is - * NOT an API availability flag. Some canonical Agent Plan chat models (for - * example kimi-k3) are returned with `PlatformAllowStatus=false`, `Type` and - * display metadata unset, while `/api/plan/v3/chat/completions` accepts them. - * Keep those only when they are in the API-verified canonical registry. This - * avoids admitting the same response's media models, auto-router, and legacy - * aliases while still allowing newly platform-enabled LLMs to be discovered. + * Both console APIs return the same response shape: each entry has + * `ModelId` (the version-suffixed ID accepted by the chat endpoint), + * `OutputName` / `ModelName` (the canonical family name used for capability + * enrichment), `Enabled` (console visibility — not API availability), and + * optional `Description` / `EnabledThinking`. + * + * We keep ALL entries with a non-empty `ModelId`. The chat endpoint accepts + * every listed model; `Enabled` only controls whether the model appears in + * the console's model picker, so filtering on it would hide callable models. */ -export function parseAgentPlanModels(json: JsonRecord): DiscoveredVolcModel[] { - const data = record(json.Result).Data; - const arr = Array.isArray(data) ? data : []; - const out: DiscoveredVolcModel[] = []; - for (const raw of arr) { - const item = record(raw); - const platformEnabledLlm = - item.PlatformAllowStatus === true && stringField(item.Type) === "llm"; - const id = stringField(item.RespModelID); - if (!id || (!platformEnabledLlm && !AGENT_PLAN_API_VERIFIED_IDS.has(id))) continue; - const name = stringField(item.RespModelName) || id; - out.push({ - id, - name, - ...(stringField(item.RespModelVersion) - ? { description: `v${stringField(item.RespModelVersion)}` } - : {}), - }); - } - return out; -} - -/** - * Parse Coding Plan `ListArkCodeLatestModel` Result.Data[]. - * The API exposes every catalog entry regardless of subscription state; we - * keep ALL of them (the chat endpoint accepts them, Enabled only reflects - * console visibility) but surface Enabled through the description. - */ -function parseCodingPlanModels(json: JsonRecord): DiscoveredVolcModel[] { +export function parseLatestModelList(json: JsonRecord): DiscoveredVolcModel[] { const data = record(json.Result).Data; const arr = Array.isArray(data) ? data : []; const out: DiscoveredVolcModel[] = []; @@ -412,8 +385,7 @@ export async function fetchVolcPlanModels( ); } - const discovered = - kind === "agent" ? parseAgentPlanModels(result.json) : parseCodingPlanModels(result.json); + const discovered = parseLatestModelList(result.json); if (discovered.length === 0) { throw new Error(`Volcano ${kind} plan returned no usable models`); } diff --git a/tests/unit/lib/volcengine-plan-model-discovery.test.ts b/tests/unit/lib/volcengine-plan-model-discovery.test.ts index 730e1dffd4..7a40fe2904 100644 --- a/tests/unit/lib/volcengine-plan-model-discovery.test.ts +++ b/tests/unit/lib/volcengine-plan-model-discovery.test.ts @@ -1,27 +1,39 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { enrichModel, parseAgentPlanModels } from "@/lib/providers/volcenginePlanModelDiscovery"; +import { enrichModel, parseLatestModelList } from "@/lib/providers/volcenginePlanModelDiscovery"; -test("Agent Plan discovery keeps canonical API-callable models hidden by the console", () => { - const models = parseAgentPlanModels({ +test("Agent Plan discovery keeps all ListAgentPlanLatestModel entries", () => { + // `ListAgentPlanLatestModel` returns the same shape as Coding Plan's + // `ListArkCodeLatestModel`: ModelId / OutputName / Enabled / Description. + // We keep ALL entries — `Enabled` only reflects console visibility, not + // API availability. Previously disabled-but-callable models like + // kimi-k3 must be retained. + const models = parseLatestModelList({ Result: { Data: [ { - RespModelID: "doubao-seed-evolving", - RespModelName: "doubao-seed-evolving", - PlatformAllowStatus: true, - Type: "llm", + ModelId: "doubao-seed-evolving-latest-version", + OutputName: "doubao-seed-evolving", + Enabled: false, + EnabledThinking: true, }, { - RespModelID: "kimi-k3", - PlatformAllowStatus: false, - Type: null, + ModelId: "kimi-k3-260701", + OutputName: "kimi-k3", + Enabled: false, + EnabledThinking: true, }, { - RespModelID: "minimax-m3", - PlatformAllowStatus: false, - Type: null, + ModelId: "auto", + OutputName: "auto", + Enabled: true, + }, + { + ModelId: "minimax-m3-modelhub", + OutputName: "minimax-m3", + Enabled: false, + EnabledThinking: true, }, ], }, @@ -29,49 +41,29 @@ test("Agent Plan discovery keeps canonical API-callable models hidden by the con assert.deepEqual( models.map((model) => model.id), - ["doubao-seed-evolving", "kimi-k3", "minimax-m3"] + ["doubao-seed-evolving-latest-version", "kimi-k3-260701", "auto", "minimax-m3-modelhub"] ); + // OutputName is used as the canonical family name for enrichment. + assert.equal(models[1].name, "kimi-k3"); + assert.equal(models[1].enabledThinking, true); }); -test("Agent Plan discovery excludes disabled aliases, auto-routing, and non-chat models", () => { - const models = parseAgentPlanModels({ - Result: { - Data: [ - { - RespModelID: "future-platform-model", - PlatformAllowStatus: true, - Type: "llm", - }, - { - RespModelID: "glm-5.2", - PlatformAllowStatus: false, - Type: null, - }, - { - RespModelID: "auto", - PlatformAllowStatus: false, - Type: null, - }, - { - RespModelID: "doubao-seed-tts-2.0", - PlatformAllowStatus: true, - Type: "audio", - }, - ], - }, - }); - - assert.deepEqual( - models.map((model) => model.id), - ["future-platform-model"] - ); -}); - -test("Agent Plan canonical Kimi K3 receives registry-equivalent capabilities", () => { - const kimi = enrichModel({ id: "kimi-k3", name: "kimi-k3" }); +test("enrichment maps context/vision/tools from the OutputName family", () => { + const kimi = enrichModel({ id: "kimi-k3-260701", name: "kimi-k3" }); assert.equal(kimi.inputTokenLimit, 1048576); assert.equal(kimi.supportsVision, true); assert.equal(kimi.supportsTools, true); assert.equal(kimi.supportsThinking, true); + + const glm = enrichModel({ id: "glm-5-3-260801", name: "glm-5.3" }); + assert.equal(glm.inputTokenLimit, 1048576); + assert.equal(glm.supportsVision, false); + + const doubao = enrichModel({ + id: "doubao-seed-evolving-latest-version", + name: "doubao-seed-evolving", + }); + assert.equal(doubao.inputTokenLimit, 1048576); + assert.equal(doubao.supportsVision, true); });