diff --git a/packages/core/src/__tests__/model-catalog.test.ts b/packages/core/src/__tests__/model-catalog.test.ts index 3a6c57a9ac..de02f028fb 100644 --- a/packages/core/src/__tests__/model-catalog.test.ts +++ b/packages/core/src/__tests__/model-catalog.test.ts @@ -174,3 +174,34 @@ test('Alibaba Token Plan catalogs the formal Qwen3.8 model instead of its retire assert.equal(model?.canUseAsChatDefault, true, providerType); } }); + +test('DeepSeek catalogs the V4 vision model display metadata from a bare provider id', () => { + const modelId = 'deepseek-v4-flash-vision-exp'; + const [model] = buildConnectionModelCatalogEntries({ + connection: { + slug: 'deepseek', + providerType: 'deepseek', + defaultModel: modelId, + models: [{ id: modelId }], + modelSource: 'fetched', + }, + }); + + assert.equal(model?.displayName, 'DeepSeek-V4-Flash-Vision-Exp'); + assert.equal( + model?.description, + 'Experimental DeepSeek V4 Flash model for image understanding and multimodal agent tasks', + ); + assert.equal(model?.docsUrl, 'https://api-docs.deepseek.com/guides/vision/'); + assert.equal(model?.contextWindow, 1_000_000); + assert.equal(model?.maxOutputTokens, 384_000); + assert.equal(model?.structuredOutput, true); + assert.equal(model?.lastUpdated, '2026-08-21'); + assert.deepEqual(model?.capabilities, { + reasoning: true, + functionCalling: true, + vision: true, + }); + assert.deepEqual(model?.modalities, { input: ['text', 'image'], output: ['text'] }); + assert.equal(model?.canUseAsChatDefault, true); +}); diff --git a/packages/core/src/__tests__/model-metadata.test.ts b/packages/core/src/__tests__/model-metadata.test.ts index 9bf9ae6322..bdaee19100 100644 --- a/packages/core/src/__tests__/model-metadata.test.ts +++ b/packages/core/src/__tests__/model-metadata.test.ts @@ -3,11 +3,43 @@ import { describe, it } from 'node:test'; import { lookupModelMetadata, openAiAdapterApiProtocol, + resolveModelInputModalities, resolveModelVisionSupport, } from '../model-metadata.js'; import type { ModelInfo, ProviderType } from '../llm-connections.js'; describe('model-metadata vision capability', () => { + it('recognizes the DeepSeek V4 vision model from a bare discovered id', () => { + const modelId = 'deepseek-v4-flash-vision-exp'; + const discovered: ModelInfo[] = [{ id: modelId }]; + const metadata = lookupModelMetadata('deepseek', modelId); + + assert.equal(metadata.displayName, 'DeepSeek-V4-Flash-Vision-Exp'); + assert.equal( + metadata.description, + 'Experimental DeepSeek V4 Flash model for image understanding and multimodal agent tasks', + ); + assert.equal(metadata.docsUrl, 'https://api-docs.deepseek.com/guides/vision/'); + assert.equal(metadata.contextWindow, 1_000_000); + assert.equal(metadata.maxOutputTokens, 384_000); + assert.equal(metadata.structuredOutput, true); + assert.equal(metadata.lastUpdated, '2026-08-21'); + assert.deepEqual(metadata.thinkingOptions, { + efforts: ['low', 'high', 'max'], + toggle: true, + }); + assert.equal(metadata.capabilities?.vision, true); + assert.deepEqual(resolveModelInputModalities('deepseek', discovered, modelId), [ + 'text', + 'image', + ]); + assert.equal(resolveModelVisionSupport('deepseek', discovered, modelId), true); + assert.equal( + resolveModelVisionSupport('deepseek', [{ id: 'deepseek-v4-flash' }], 'deepseek-v4-flash'), + false, + ); + }); + it('treats a Claude newer than the generated snapshot as able to read images', () => { assert.deepEqual(lookupModelMetadata('anthropic', 'claude-opus-6'), {}); assert.equal(resolveModelVisionSupport('anthropic', undefined, 'claude-opus-6'), true); diff --git a/packages/core/src/model-metadata.ts b/packages/core/src/model-metadata.ts index 7615075038..cf5f65c829 100644 --- a/packages/core/src/model-metadata.ts +++ b/packages/core/src/model-metadata.ts @@ -395,6 +395,21 @@ const STATIC_MODEL_METADATA: Partial