Skip to content

Commit c4574ff

Browse files
zoomote[bot]taltas
andauthored
feat(providers): add DeepSeek V4 Flash Vision Exp (#1488)
Co-authored-by: @taltas <6816042+taltas@users.noreply.github.com>
1 parent 49c3f58 commit c4574ff

7 files changed

Lines changed: 101 additions & 44 deletions

File tree

‎packages/types/src/providers/deepseek.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@ export const deepSeekModels = {
4949
supportsReasoningEffort: ["disable", "low", "high", "max"], // Updated 2026-08-13
5050
preserveReasoning: true,
5151
reasoningEffort: "high",
52+
supportsTemperature: true,
53+
defaultTemperature: 1.0,
5254
inputPrice: 0, // the inputs are priced as cache read/write, so `inputPrice` should be 0
5355
// Static estimates use peak rates; off-peak rates are 50% lower.
5456
outputPrice: 1.32,
5557
cacheWritesPrice: 0.44,
5658
cacheReadsPrice: 0.014,
57-
description: `DeepSeek-V4-Flash-Vision-Exp is DeepSeek's experimental multimodal V4 Flash model with image understanding. It supports thinking and non-thinking modes, JSON output, tool calls, chat prefix completion (beta), and image inputs.`,
59+
description: `DeepSeek-V4-Flash-Vision-Exp is DeepSeek's experimental multimodal V4 Flash model with image understanding. It supports thinking and non-thinking modes, JSON output, tool calls, chat prefix completion (beta), and image input through Chat Completions, Responses, and Anthropic-compatible APIs.`,
5860
},
5961
} as const satisfies Record<string, ModelInfo>
6062

‎packages/types/src/providers/fireworks.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type FireworksModelId =
1717
| "accounts/fireworks/models/deepseek-v3p2"
1818
| "accounts/fireworks/models/deepseek-v4-pro"
1919
| "accounts/fireworks/models/deepseek-v4-pro-0813"
20+
| "accounts/fireworks/models/deepseek-v4-flash-vision-exp"
2021
| "accounts/fireworks/models/glm-4p5"
2122
| "accounts/fireworks/models/glm-4p5-air"
2223
| "accounts/fireworks/models/glm-4p6"
@@ -280,6 +281,19 @@ export const fireworksModels = {
280281
description:
281282
"DeepSeek V4 Pro 0813 is DeepSeek's production checkpoint for advanced reasoning, coding, and long-context agentic workloads.",
282283
},
284+
"accounts/fireworks/models/deepseek-v4-flash-vision-exp": {
285+
displayName: "DeepSeek V4 Flash Vision Exp",
286+
maxTokens: 384_000,
287+
contextWindow: 1_048_576,
288+
supportsImages: true,
289+
supportsPromptCache: true,
290+
supportsMaxTokens: true,
291+
inputPrice: 0.22,
292+
outputPrice: 0.66,
293+
cacheReadsPrice: 0.007,
294+
description:
295+
"DeepSeek V4 Flash Vision Exp is an experimental multimodal model with text and image input, function calling, and long-context support.",
296+
},
283297
"accounts/fireworks/models/kimi-k2p7-code": {
284298
maxTokens: 16384,
285299
contextWindow: 262144,

‎src/api/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
236236
case providerIdentifiers.poe:
237237
return new PoeHandler(options)
238238
case providerIdentifiers.geminiCli:
239-
// Intentionally falls through to the Anthropic handler pending a dedicated Gemini CLI handler implementation.
239+
// Intentionally falls through to the Anthropic handler pending a dedicated Gemini CLI handler implementation.
240240
default:
241241
return new AnthropicHandler(options)
242242
}

‎src/api/providers/__tests__/deepseek.spec.ts‎

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -249,16 +249,23 @@ describe("DeepSeekHandler", () => {
249249
expect((model.info as ModelInfo).reasoningEffort).toBe("high")
250250
})
251251

252-
it("should return vision model info for deepseek-v4-flash-vision-exp", () => {
253-
const handlerWithVision = new DeepSeekHandler({
252+
it("should return vision metadata for deepseek-v4-flash-vision-exp", () => {
253+
const visionHandler = new DeepSeekHandler({
254254
...mockOptions,
255255
apiModelId: "deepseek-v4-flash-vision-exp",
256256
})
257-
const model = handlerWithVision.getModel()
257+
const model = visionHandler.getModel()
258+
258259
expect(model.id).toBe("deepseek-v4-flash-vision-exp")
259-
expect(model.info.supportsImages).toBe(true)
260-
expect(model.info.supportsPromptCache).toBe(true)
261-
expect((model.info as ModelInfo).preserveReasoning).toBe(true)
260+
expect(model.info).toMatchObject({
261+
maxTokens: 384_000,
262+
contextWindow: 1_000_000,
263+
supportsImages: true,
264+
supportsPromptCache: true,
265+
preserveReasoning: true,
266+
reasoningEffort: "high",
267+
defaultTemperature: 1.0,
268+
})
262269
})
263270

264271
it("should return provided model ID with default model info if model does not exist", () => {
@@ -336,6 +343,43 @@ describe("DeepSeekHandler", () => {
336343
expect(textChunks[0].text).toBe("Test response")
337344
})
338345

346+
it("should send images and V4 thinking controls to deepseek-v4-flash-vision-exp", async () => {
347+
const visionHandler = new DeepSeekHandler({
348+
...mockOptions,
349+
apiModelId: "deepseek-v4-flash-vision-exp",
350+
})
351+
const visionMessages: Anthropic.Messages.MessageParam[] = [
352+
{
353+
role: "user",
354+
content: [
355+
{ type: "text", text: "Describe this image." },
356+
{
357+
type: "image",
358+
source: { type: "base64", media_type: "image/png", data: "image-data" },
359+
},
360+
],
361+
},
362+
]
363+
364+
await collectStream(visionHandler.createMessage(systemPrompt, visionMessages))
365+
366+
const callArgs = mockCreate.mock.calls[0][0]
367+
expect(callArgs).toMatchObject({
368+
model: "deepseek-v4-flash-vision-exp",
369+
thinking: { type: "enabled" },
370+
reasoning_effort: "high",
371+
max_completion_tokens: 200_000,
372+
})
373+
expect(callArgs.temperature).toBeUndefined()
374+
expect(callArgs.messages).toContainEqual({
375+
role: "user",
376+
content: expect.arrayContaining([
377+
{ type: "text", text: expect.stringContaining("Describe this image.") },
378+
{ type: "image_url", image_url: { url: "data:image/png;base64,image-data" } },
379+
]),
380+
})
381+
})
382+
339383
it("should include usage information", async () => {
340384
const chunks: any[] = await collectStream(handler.createMessage(systemPrompt, messages))
341385

@@ -424,42 +468,6 @@ describe("DeepSeekHandler", () => {
424468
const reasoningChunks = chunks.filter((chunk) => chunk.type === "reasoning")
425469
expect(reasoningChunks).toEqual([{ type: "reasoning", text: "primary thought" }])
426470
})
427-
428-
it("enables thinking and forwards image_url for the vision model", async () => {
429-
const visionHandler = new DeepSeekHandler({
430-
...mockOptions,
431-
apiModelId: "deepseek-v4-flash-vision-exp",
432-
})
433-
const withImage: Anthropic.Messages.MessageParam[] = [
434-
{
435-
role: "user",
436-
content: [
437-
{ type: "text", text: "Describe:" },
438-
{ type: "image", source: { type: "base64", media_type: "image/png", data: "abc" } },
439-
],
440-
},
441-
]
442-
443-
await collectStream(visionHandler.createMessage(systemPrompt, withImage))
444-
445-
const callArgs = mockCreate.mock.calls[0][0]
446-
expect(callArgs.thinking).toEqual({ type: "enabled" })
447-
expect(callArgs.messages).toEqual(
448-
expect.arrayContaining([
449-
expect.objectContaining({
450-
role: "user",
451-
content: expect.arrayContaining([
452-
expect.objectContaining({
453-
type: "image_url",
454-
image_url: expect.objectContaining({
455-
url: "data:image/png;base64,abc",
456-
}),
457-
}),
458-
]),
459-
}),
460-
]),
461-
)
462-
})
463471
})
464472

465473
describe("processUsageMetrics", () => {

‎src/api/providers/__tests__/fireworks.spec.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ describe("FireworksHandler", () => {
127127
outputPrice: 3.96,
128128
cacheReadsPrice: 0.044,
129129
},
130+
{
131+
modelId: "accounts/fireworks/models/deepseek-v4-flash-vision-exp" as const,
132+
contextWindow: 1_048_576,
133+
inputPrice: 0.22,
134+
outputPrice: 0.66,
135+
cacheReadsPrice: 0.007,
136+
},
130137
])(
131138
"should expose newly added model $modelId",
132139
({ modelId, contextWindow, inputPrice, outputPrice, cacheReadsPrice }) => {
@@ -147,6 +154,16 @@ describe("FireworksHandler", () => {
147154
},
148155
)
149156

157+
it("should expose vision support for DeepSeek V4 Flash Vision Exp", () => {
158+
const model = fireworksModels["accounts/fireworks/models/deepseek-v4-flash-vision-exp"]
159+
160+
expect(model).toMatchObject({
161+
supportsImages: true,
162+
supportsPromptCache: true,
163+
supportsMaxTokens: true,
164+
})
165+
})
166+
150167
it("should return Kimi K2 Instruct model with correct configuration", () => {
151168
const testModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2-instruct"
152169
const handlerWithModel = new FireworksHandler({

‎src/api/providers/fetchers/__tests__/deepseek.spec.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe("getDeepSeekModels", () => {
3131
expect(globalThis.fetch).toHaveBeenCalledWith("http://127.0.0.1:43123/models", expect.any(Object))
3232
expect(models["deepseek-v4-flash"]).toEqual(deepSeekModels["deepseek-v4-flash"])
3333
expect(models["deepseek-v4-pro"]).toEqual(deepSeekModels["deepseek-v4-pro"])
34+
expect(models["deepseek-v4-flash-vision-exp"]).toEqual(deepSeekModels["deepseek-v4-flash-vision-exp"])
3435
})
3536

3637
it("throws for 404 responses when fallback flag is not enabled", async () => {

‎webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,21 @@ describe("useSelectedModel", () => {
220220
},
221221
)
222222

223+
it("selects static vision metadata when the DeepSeek catalog is unavailable", () => {
224+
const modelId = "deepseek-v4-flash-vision-exp"
225+
mockUseRouterModels.mockReturnValue(createRouterModelsResult({ [providerIdentifiers.deepseek]: null }))
226+
mockUseOpenRouterModelProviders.mockReturnValue(createOpenRouterModelProvidersResult({}))
227+
228+
const { result } = renderHook(
229+
() => useSelectedModel({ apiProvider: providerIdentifiers.deepseek, apiModelId: modelId }),
230+
{ wrapper: createWrapper() },
231+
)
232+
233+
expect(result.current.id).toBe(modelId)
234+
expect(result.current.info).toEqual(deepSeekModels[modelId])
235+
expect(result.current.info?.supportsImages).toBe(true)
236+
})
237+
223238
it.each([providerIdentifiers.deepseek, providerIdentifiers.moonshot])(
224239
"falls back to static data when the %s router catalog is null",
225240
(provider) => {

0 commit comments

Comments
 (0)