Files
OmniRoute/src/lib/providers/volcenginePlanBinding.ts
yangsiyuan.rengar 76ac1c8b7e feat(volcengine): live model discovery for Ark plan providers
Replace the static curated model lists for volcengine-agent-plan and
volcengine-coding-plan with live discovery from the console APIs
(GetAgentPlanModelMappingMeta / ListArkCodeLatestModel), authenticated by
the console cookie+csrf already captured at plan binding time.

- Add volcenginePlanModelDiscovery.ts: fetch + parse + capability enrichment
  (family->contextLength/vision/reasoning map, conservative default fallback).
  Console calls go through a dynamic undici import to bypass OmniRoute's
  global fetch patch (built for LLM provider traffic, reroutes console hits).
  Coding plan's ListArkCodeLatestModel needs {AccountId:<number>} extracted
  from the console cookie; agent plan's GetAgentPlanModelMappingMeta filters
  PlatformAllowStatus===true && Type==='llm'.
- Remove both plan ids from CURATED_MODEL_ONLY_PROVIDERS so synced models
  merge into /v1/models and the dashboard Sync Models button works.
- sync-models route: short-circuit to console discovery for plan providers
  (the chat API has no /models endpoint); persist via
  replaceSyncedAvailableModelsForConnection.
- volcenginePlanBinding: set autoSync:true on new plan connections so the
  24h modelSyncScheduler refreshes them automatically.
- volcPlanAutoSyncBackfill: idempotent boot-time backfill so pre-existing
  plan connections also enter the scheduler.

Verified end-to-end on local OmniRoute build against live Volcano console:
agent plan synced 7 LLMs, coding plan synced 11 models, /v1/models exposes
all of them (incl. new glm-5-3-260801 / deepseek-v4-flash-260801).
2026-08-24 12:21:59 -03:00

280 lines
7.6 KiB
TypeScript

import {
createProviderConnection,
getProviderConnections,
updateProviderConnection,
} from "@/models";
type JsonRecord = Record<string, unknown>;
export const CONSOLE_TOP_BASE = "https://console.volcengine.com/api/top/ark/cn-beijing/2024-01-01";
const CODING_PLAN_PROVIDER = "volcengine-coding-plan";
const AGENT_PLAN_PROVIDER = "volcengine-agent-plan";
const PLAN_CONFIG = {
coding: {
provider: CODING_PLAN_PROVIDER,
name: "Volcano Ark Coding Plan",
usageAction: "GetCodingPlanUsage",
listModelAction: "ListArkCodeLatestModel",
listModelPayload: {},
referer: "https://console.volcengine.com/ark/region:cn-beijing/subscription/coding-plan",
listApiKeysPayload: { ProjectName: "default" },
},
agent: {
provider: AGENT_PLAN_PROVIDER,
name: "Volcano Ark Agent Plan",
usageAction: "GetAgentPlanAFPUsage",
listModelAction: "GetAgentPlanModelMappingMeta",
listModelPayload: { Edition: "agent_plan_personal" },
referer: "https://console.volcengine.com/ark/region:cn-beijing/subscription/agent-plan",
listApiKeysPayload: {
ProjectName: "default",
Filter: { Scene: "RealAgentPlanPersonal" },
},
},
} as const;
type PlanKind = keyof typeof PLAN_CONFIG;
export interface ConsoleApiResult {
ok: boolean;
status: number;
json: JsonRecord;
error: string | null;
}
export function stringField(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}
export function record(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function buildCookieHeader(credentials: JsonRecord): string {
const rawCookie = stringField(credentials.volcConsoleCookie);
if (rawCookie) return rawCookie;
const names = ["digest", "AccountID", "csrfToken", "userInfo"];
return names
.map((name) => {
const value = stringField(credentials[name]);
return value ? `${name}=${value}` : "";
})
.filter(Boolean)
.join("; ");
}
function extractCsrf(credentials: JsonRecord, cookieHeader: string): string {
const explicit = stringField(credentials.volcCsrfToken) || stringField(credentials.csrfToken);
if (explicit) return explicit;
return cookieHeader.match(/(?:^|;\s*)csrfToken=([^;]+)/)?.[1]?.trim() || "";
}
export async function callConsoleApi(
action: string,
payload: JsonRecord,
cookieHeader: string,
csrfToken: string,
referer: string
): Promise<ConsoleApiResult> {
const response = await fetch(`${CONSOLE_TOP_BASE}/${action}?`, {
method: "POST",
headers: {
accept: "application/json, text/plain, */*",
"content-type": "application/json",
cookie: cookieHeader,
origin: "https://console.volcengine.com",
referer,
"x-csrf-token": csrfToken,
},
body: JSON.stringify(payload),
});
const text = await response.text();
let json: JsonRecord = {};
try {
json = record(JSON.parse(text));
} catch {
// Non-JSON console failures are reported through `error` below.
}
const meta = record(json.ResponseMetadata);
const err = record(meta.Error);
const message = stringField(err.Message);
return {
ok: response.ok && !message,
status: response.status,
json,
error: message || (response.ok ? null : text.slice(0, 200)),
};
}
export async function detectPlan(
kind: PlanKind,
cookieHeader: string,
csrfToken: string
): Promise<{ available: boolean; usage: JsonRecord; error: string | null }> {
const cfg = PLAN_CONFIG[kind];
const result = await callConsoleApi(cfg.usageAction, {}, cookieHeader, csrfToken, cfg.referer);
if (!result.ok) {
return { available: false, usage: {}, error: result.error };
}
return { available: true, usage: record(result.json.Result), error: null };
}
function firstApiKeyItem(result: JsonRecord): JsonRecord | null {
const items = record(result.Result).Items;
if (!Array.isArray(items)) return null;
return record(items[0]);
}
async function fetchRawApiKey(
kind: PlanKind,
cookieHeader: string,
csrfToken: string
): Promise<{ apiKey: string; id: number | null; maskedKey: string | null; error: string | null }> {
const cfg = PLAN_CONFIG[kind];
const list = await callConsoleApi(
"ListApiKeys",
cfg.listApiKeysPayload,
cookieHeader,
csrfToken,
cfg.referer
);
if (!list.ok) {
return { apiKey: "", id: null, maskedKey: null, error: list.error || "ListApiKeys failed" };
}
const item = firstApiKeyItem(list.json);
const id = Number(item?.Id);
if (!Number.isFinite(id) || id <= 0) {
return { apiKey: "", id: null, maskedKey: null, error: "No API key found for this plan" };
}
const raw = await callConsoleApi(
"GetRawApiKey",
{ Id: id },
cookieHeader,
csrfToken,
cfg.referer
);
if (!raw.ok) {
return { apiKey: "", id, maskedKey: stringField(item?.Key) || null, error: raw.error };
}
const apiKey = stringField(record(raw.json.Result).ApiKey);
if (!apiKey) {
return {
apiKey: "",
id,
maskedKey: stringField(item?.Key) || null,
error: "Raw API key missing",
};
}
return { apiKey, id, maskedKey: stringField(item?.Key) || null, error: null };
}
async function upsertConnection(
kind: PlanKind,
apiKey: string,
cookieHeader: string,
csrfToken: string,
apiKeyId: number | null,
usage: JsonRecord
) {
const cfg = PLAN_CONFIG[kind];
const providerSpecificData = {
volcConsoleCookie: cookieHeader,
volcCsrfToken: csrfToken,
volcApiKeyId: apiKeyId,
volcPlanKind: kind,
volcLastUsage: usage,
// Enable 24h model auto-sync (modelSyncScheduler picks up autoSync:true).
autoSync: true,
};
const existing = (await getProviderConnections({ provider: cfg.provider })).find(
(conn: JsonRecord) => stringField(conn.name) === cfg.name
);
if (existing?.id) {
return await updateProviderConnection(stringField(existing.id), {
apiKey,
name: cfg.name,
providerSpecificData,
isActive: true,
testStatus: "active",
});
}
return await createProviderConnection({
provider: cfg.provider,
authType: "apikey",
name: cfg.name,
apiKey,
providerSpecificData,
isActive: true,
testStatus: "active",
});
}
export async function bindVolcenginePlansFromConsoleCredentials(credentials: JsonRecord) {
const cookieHeader = buildCookieHeader(credentials);
const csrfToken = extractCsrf(credentials, cookieHeader);
if (!cookieHeader || !csrfToken) {
throw new Error("Volcano console cookie or csrfToken is missing");
}
const results: Array<{
plan: PlanKind;
available: boolean;
ok: boolean;
connectionId?: string;
apiKeyId?: number | null;
maskedKey?: string | null;
error?: string | null;
}> = [];
for (const kind of ["coding", "agent"] as PlanKind[]) {
const detected = await detectPlan(kind, cookieHeader, csrfToken);
if (!detected.available) {
results.push({ plan: kind, available: false, ok: false, error: detected.error });
continue;
}
const key = await fetchRawApiKey(kind, cookieHeader, csrfToken);
if (!key.apiKey) {
results.push({
plan: kind,
available: true,
ok: false,
apiKeyId: key.id,
maskedKey: key.maskedKey,
error: key.error,
});
continue;
}
const connection = await upsertConnection(
kind,
key.apiKey,
cookieHeader,
csrfToken,
key.id,
detected.usage
);
results.push({
plan: kind,
available: true,
ok: Boolean(connection?.id),
connectionId: stringField(connection?.id),
apiKeyId: key.id,
maskedKey: key.maskedKey,
});
}
return {
cookieCaptured: true,
results,
};
}