mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-04 04:53:39 +00:00
Merge pull request #4339 from sabrimete/fix/alibaba-catalog-puter-ids
This commit is contained in:
2 files changed
+146
-5
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,115 @@ 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.each([
|
||||
[
|
||||
'ASR',
|
||||
{
|
||||
capabilities: ['ASR'],
|
||||
inference_metadata: {
|
||||
request_modality: ['Audio'],
|
||||
response_modality: ['Text'],
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'rerank',
|
||||
{
|
||||
capabilities: ['TR'],
|
||||
inference_metadata: {
|
||||
request_modality: ['Text'],
|
||||
response_modality: ['Text'],
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'image generation',
|
||||
{
|
||||
capabilities: ['IG'],
|
||||
inference_metadata: {
|
||||
request_modality: ['Text'],
|
||||
response_modality: ['Text', 'Image'],
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'realtime omni',
|
||||
{
|
||||
capabilities: ['Realtime-Omni'],
|
||||
inference_metadata: {
|
||||
request_modality: ['Text', 'Image', 'Audio', 'Video'],
|
||||
response_modality: ['Text', 'Audio'],
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'TTS',
|
||||
{
|
||||
capabilities: ['TTS'],
|
||||
inference_metadata: {
|
||||
request_modality: ['Audio'],
|
||||
response_modality: ['Text'],
|
||||
},
|
||||
},
|
||||
],
|
||||
])('rejects %s models that cannot serve chat completions', (_kind, raw) => {
|
||||
expect(
|
||||
mapAlibabaCatalogModel({
|
||||
model: 'non-chat-fixture',
|
||||
...(raw as AlibabaApiModel),
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('admits a vision model', () => {
|
||||
const model = mapAlibabaCatalogModel({
|
||||
model: 'vision-fixture',
|
||||
capabilities: ['VU'],
|
||||
inference_metadata: {
|
||||
request_modality: ['Text', 'Image'],
|
||||
response_modality: ['Text'],
|
||||
},
|
||||
})!;
|
||||
expect(model.modalities?.input).toEqual(['text', 'image']);
|
||||
});
|
||||
|
||||
it('does not admit an uncurated entry without capability or modality data', () => {
|
||||
expect(mapAlibabaCatalogModel({ model: 'mystery-model' })).toBeNull();
|
||||
expect(mapAlibabaCatalogModel({ model: 'qwen-plus' })).not.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,
|
||||
@@ -90,16 +107,25 @@ export const mapAlibabaCatalogModel = (
|
||||
const id = raw.model.trim();
|
||||
const existing =
|
||||
previous ?? ALIBABA_MODELS.find((model) => model.id === id);
|
||||
const input = raw.inference_metadata?.request_modality;
|
||||
const output = raw.inference_metadata?.response_modality;
|
||||
if (output && !output.includes('Text')) return null;
|
||||
if (
|
||||
!output &&
|
||||
!existing &&
|
||||
!raw.capabilities?.some((capability) =>
|
||||
['TG', 'Reasoning', 'VU'].includes(capability),
|
||||
raw.capabilities &&
|
||||
!raw.capabilities.some((capability) =>
|
||||
chatCapabilities.includes(capability),
|
||||
)
|
||||
)
|
||||
return null;
|
||||
if (input && !input.includes('Text')) return null;
|
||||
if (output && !output.includes('Text')) return null;
|
||||
if (!raw.capabilities && !input && !output && !existing) 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 +175,7 @@ export const mapAlibabaCatalogModel = (
|
||||
return {
|
||||
...existing,
|
||||
id,
|
||||
puterId,
|
||||
name: raw.name ?? existing?.name ?? id,
|
||||
description: raw.description ?? existing?.description,
|
||||
aliases: existing?.aliases,
|
||||
|
||||
Reference in new issue
Block a user