fix(ai-chat): give uncurated Alibaba catalog models a puterId

Uncurated entries admitted from the DashScope catalog feed previously
surfaced with their raw feed id. They now get an
alibaba:<vendor>/<model> puterId, vendor-prefixed ids keep their org,
and others infer the org from the curated family sibling. Entries whose
synthesized puterId collides with a curated entry are skipped so the
model is not listed twice.
This commit is contained in:
sabrimete committed 2026-10-25 21:04:28 +00:00
1 parent 24aac2f6d2
commit f0f97a08cf
2 files changed
+63 -1

No files matched your search

@@ -809,6 +809,11 @@ describe('Alibaba official catalog', () => {
expect(models.some((model) => model.id === 'qwen-image-fixture')).toBe(
false,
);
const fixture = models.find(
(model) => model.id === 'qwen-catalog-fixture',
)!;
expect(fixture.puterId).toBe('alibaba:qwen/qwen-catalog-fixture');
expect(modelLookupNames(models)).toContain('qwen-catalog-fixture');
const plus = models.find((model) => model.id === 'qwen-plus')!;
const original = ALIBABA_MODELS.find(
(model) => model.id === 'qwen-plus',
@@ -909,6 +914,38 @@ describe('Alibaba official catalog', () => {
expect(model.costs).toEqual({ tokens: 1_000_000 });
});
it.each([
['qwen3.9-plus-2027-01-01', 'alibaba:qwen/qwen3.9-plus-2027-01-01'],
['kimi/kimi-k3', 'alibaba:kimi/kimi-k3'],
[
'deepseek-v4.1-flash-1017',
'alibaba:deepseek/deepseek-v4.1-flash-1017',
],
['glm-5.3-prime', 'alibaba:zhipu/glm-5.3-prime'],
['decision-model-preview', 'alibaba:qwen/decision-model-preview'],
])(
'gives uncurated model %s a puterId in the alibaba:<vendor>/<id> form',
(id, puterId) => {
const model = mapAlibabaCatalogModel({
model: id,
capabilities: ['TG'],
})!;
expect(model.puterId).toBe(puterId);
},
);
it('skips a vendor-prefixed spelling of a curated model', () => {
const model = mapAlibabaCatalogModel({
model: 'zhipu/glm-5.2',
capabilities: ['TG', 'Reasoning'],
inference_metadata: {
request_modality: ['Text'],
response_modality: ['Text'],
},
});
expect(model).toBeNull();
});
it('refuses inference with unverified catalog prices', async () => {
seedPages();
const { provider } = makeProvider();
@@ -82,6 +82,23 @@ const positiveLimit = (value: unknown): number | undefined =>
? value
: undefined;
const chatCapabilities = ['TG', 'Reasoning', 'VU'];
const curatedPuterIds = new Set(ALIBABA_MODELS.map((model) => model.puterId));
const inferPuterId = (id: string): string => {
if (id.includes('/')) return `alibaba:${id}`;
const family = id.split('-')[0];
const sibling = ALIBABA_MODELS.find(
(model) => model.puterId && model.id.split('-')[0] === family,
);
// Unprefixed feed ids are Model Studio's own family unless a curated
// sibling points at a third-party org.
const org =
sibling?.puterId?.slice('alibaba:'.length).split('/')[0] ?? 'qwen';
return `alibaba:${org}/${id}`;
};
export const mapAlibabaCatalogModel = (
raw: AlibabaApiModel,
previous?: IChatModel,
@@ -96,10 +113,17 @@ export const mapAlibabaCatalogModel = (
!output &&
!existing &&
!raw.capabilities?.some((capability) =>
['TG', 'Reasoning', 'VU'].includes(capability),
chatCapabilities.includes(capability),
)
)
return null;
let puterId = existing?.puterId;
if (!puterId) {
puterId = inferPuterId(id);
// A feed entry spelling a curated model differently would duplicate
// the curated entry's puterId and list the model twice.
if (curatedPuterIds.has(puterId)) return null;
}
const catalogPricing: AlibabaCatalogTier[] = (raw.prices ?? []).map(
(tier) => {
// Abbreviated ranges do not specify whether k means 1000 or 1024 tokens.
@@ -149,6 +173,7 @@ export const mapAlibabaCatalogModel = (
return {
...existing,
id,
puterId,
name: raw.name ?? existing?.name ?? id,
description: raw.description ?? existing?.description,
aliases: existing?.aliases,