feat: add native Alibaba Token Plan provider

This commit is contained in:
Brent
2026-07-23 22:19:21 +00:00
parent 126765c020
commit e184fe8f8d
24 changed files with 1142 additions and 20 deletions
+19 -9
View File
@@ -29,6 +29,7 @@ import {
} from "../src/provider-models/descriptor-types";
import { PROVIDER_DESCRIPTORS } from "../src/provider-models/descriptors";
import {
ALIBABA_TOKEN_PLAN_STATIC_MODELS,
ANTHROPIC_CURATED_FALLBACK_MODELS,
buildFireworksFastSeed,
buildXaiOAuthStaticSeed,
@@ -104,13 +105,16 @@ async function resolveProviderApiKey(providerId: string, catalog: CatalogDiscove
return undefined;
}
type CatalogProviderFetchResult = { models: ModelSpec[]; succeeded: boolean };
async function fetchProviderModelsFromCatalog(descriptor: CatalogProviderDescriptor): Promise<ModelSpec[]> {
async function fetchProviderModelsFromCatalog(
descriptor: CatalogProviderDescriptor,
): Promise<CatalogProviderFetchResult> {
const apiKey = await resolveProviderApiKey(descriptor.providerId, descriptor.catalogDiscovery);
if (!apiKey && !allowsUnauthenticatedCatalogDiscovery(descriptor)) {
console.log(`No ${descriptor.catalogDiscovery.label} credentials found (env or agent.db), using fallback models`);
return [];
return { models: [], succeeded: false };
}
try {
@@ -129,19 +133,19 @@ async function fetchProviderModelsFromCatalog(descriptor: CatalogProviderDescrip
console.warn(
`${descriptor.catalogDiscovery.label} dynamic fetch failed (stale cache merge), using fallback models`,
);
return [];
return { models: [], succeeded: false };
}
const models = result.models.filter(model => model.provider === descriptor.providerId);
if (models.length === 0) {
console.warn(`${descriptor.catalogDiscovery.label} discovery returned no models, using fallback models`);
return [];
console.warn(`${descriptor.catalogDiscovery.label} discovery returned no models`);
return { models: [], succeeded: true };
}
console.log(`Fetched ${models.length} models from ${descriptor.catalogDiscovery.label} model manager`);
// The manager returns built models; models.json stores specs (sparse compat).
return models.map(model => toModelSpec(model));
return { models: models.map(model => toModelSpec(model)), succeeded: true };
} catch (error) {
console.error(`Failed to fetch ${descriptor.catalogDiscovery.label} models:`, error);
return [];
return { models: [], succeeded: false };
}
}
@@ -485,12 +489,12 @@ async function generateModels() {
const catalogProviderModelBatches = await Promise.all(
catalogProviderDescriptors.map(async descriptor => ({
descriptor,
models: await fetchProviderModelsFromCatalog(descriptor),
...(await fetchProviderModelsFromCatalog(descriptor)),
})),
);
const authoritativeCatalogProviders = new Set(
catalogProviderModelBatches
.filter(batch => batch.descriptor.dynamicModelsAuthoritative === true && batch.models.length > 0)
.filter(batch => batch.descriptor.dynamicModelsAuthoritative === true && batch.succeeded)
.map(batch => batch.descriptor.providerId),
);
const catalogProviderModels = catalogProviderModelBatches.flatMap(batch => batch.models);
@@ -517,6 +521,12 @@ async function generateModels() {
// persisted `modelRoles.default = "xai-oauth/<id>"` is honored before the
// async refresh fires (interactive boot does not await refresh).
allModels.push(...buildXaiOAuthStaticSeed());
// Seed QwenCloud's documented Token Plan models when credentialed
// discovery is unavailable. A successful `/models` response is authoritative
// for the subscribed edition and must not be widened by the fallback.
if (!authoritativeCatalogProviders.has("alibaba-token-plan")) {
allModels.push(...ALIBABA_TOKEN_PLAN_STATIC_MODELS);
}
// Seed Anthropic models that are live on the first-party API or in limited
// release but that models.dev has not catalogued yet (e.g. Claude Fable 5 /
// Mythos 5). Deduped behind upstream entries; metadata is pinned in