diff --git a/packages/types/src/providers/deepseek.ts b/packages/types/src/providers/deepseek.ts index 286855a423..3e42bbfeec 100644 --- a/packages/types/src/providers/deepseek.ts +++ b/packages/types/src/providers/deepseek.ts @@ -49,12 +49,14 @@ export const deepSeekModels = { supportsReasoningEffort: ["disable", "low", "high", "max"], // Updated 2026-08-13 preserveReasoning: true, reasoningEffort: "high", + supportsTemperature: true, + defaultTemperature: 1.0, inputPrice: 0, // the inputs are priced as cache read/write, so `inputPrice` should be 0 // Static estimates use peak rates; off-peak rates are 50% lower. outputPrice: 1.32, cacheWritesPrice: 0.44, cacheReadsPrice: 0.014, - 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.`, + 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.`, }, } as const satisfies Record diff --git a/packages/types/src/providers/fireworks.ts b/packages/types/src/providers/fireworks.ts index 993bf1ad8e..5417618ac6 100644 --- a/packages/types/src/providers/fireworks.ts +++ b/packages/types/src/providers/fireworks.ts @@ -17,6 +17,7 @@ export type FireworksModelId = | "accounts/fireworks/models/deepseek-v3p2" | "accounts/fireworks/models/deepseek-v4-pro" | "accounts/fireworks/models/deepseek-v4-pro-0813" + | "accounts/fireworks/models/deepseek-v4-flash-vision-exp" | "accounts/fireworks/models/glm-4p5" | "accounts/fireworks/models/glm-4p5-air" | "accounts/fireworks/models/glm-4p6" @@ -280,6 +281,19 @@ export const fireworksModels = { description: "DeepSeek V4 Pro 0813 is DeepSeek's production checkpoint for advanced reasoning, coding, and long-context agentic workloads.", }, + "accounts/fireworks/models/deepseek-v4-flash-vision-exp": { + displayName: "DeepSeek V4 Flash Vision Exp", + maxTokens: 384_000, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + inputPrice: 0.22, + outputPrice: 0.66, + cacheReadsPrice: 0.007, + description: + "DeepSeek V4 Flash Vision Exp is an experimental multimodal model with text and image input, function calling, and long-context support.", + }, "accounts/fireworks/models/kimi-k2p7-code": { maxTokens: 16384, contextWindow: 262144, diff --git a/src/api/index.ts b/src/api/index.ts index f45412e32c..98c3c5dc7b 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -236,7 +236,7 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { case providerIdentifiers.poe: return new PoeHandler(options) case providerIdentifiers.geminiCli: - // Intentionally falls through to the Anthropic handler pending a dedicated Gemini CLI handler implementation. + // Intentionally falls through to the Anthropic handler pending a dedicated Gemini CLI handler implementation. default: return new AnthropicHandler(options) } diff --git a/src/api/providers/__tests__/deepseek.spec.ts b/src/api/providers/__tests__/deepseek.spec.ts index b99cbe4ff5..b4c5d1f5f6 100644 --- a/src/api/providers/__tests__/deepseek.spec.ts +++ b/src/api/providers/__tests__/deepseek.spec.ts @@ -249,16 +249,23 @@ describe("DeepSeekHandler", () => { expect((model.info as ModelInfo).reasoningEffort).toBe("high") }) - it("should return vision model info for deepseek-v4-flash-vision-exp", () => { - const handlerWithVision = new DeepSeekHandler({ + it("should return vision metadata for deepseek-v4-flash-vision-exp", () => { + const visionHandler = new DeepSeekHandler({ ...mockOptions, apiModelId: "deepseek-v4-flash-vision-exp", }) - const model = handlerWithVision.getModel() + const model = visionHandler.getModel() + expect(model.id).toBe("deepseek-v4-flash-vision-exp") - expect(model.info.supportsImages).toBe(true) - expect(model.info.supportsPromptCache).toBe(true) - expect((model.info as ModelInfo).preserveReasoning).toBe(true) + expect(model.info).toMatchObject({ + maxTokens: 384_000, + contextWindow: 1_000_000, + supportsImages: true, + supportsPromptCache: true, + preserveReasoning: true, + reasoningEffort: "high", + defaultTemperature: 1.0, + }) }) it("should return provided model ID with default model info if model does not exist", () => { @@ -336,6 +343,43 @@ describe("DeepSeekHandler", () => { expect(textChunks[0].text).toBe("Test response") }) + it("should send images and V4 thinking controls to deepseek-v4-flash-vision-exp", async () => { + const visionHandler = new DeepSeekHandler({ + ...mockOptions, + apiModelId: "deepseek-v4-flash-vision-exp", + }) + const visionMessages: Anthropic.Messages.MessageParam[] = [ + { + role: "user", + content: [ + { type: "text", text: "Describe this image." }, + { + type: "image", + source: { type: "base64", media_type: "image/png", data: "image-data" }, + }, + ], + }, + ] + + await collectStream(visionHandler.createMessage(systemPrompt, visionMessages)) + + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs).toMatchObject({ + model: "deepseek-v4-flash-vision-exp", + thinking: { type: "enabled" }, + reasoning_effort: "high", + max_completion_tokens: 200_000, + }) + expect(callArgs.temperature).toBeUndefined() + expect(callArgs.messages).toContainEqual({ + role: "user", + content: expect.arrayContaining([ + { type: "text", text: expect.stringContaining("Describe this image.") }, + { type: "image_url", image_url: { url: "data:image/png;base64,image-data" } }, + ]), + }) + }) + it("should include usage information", async () => { const chunks: any[] = await collectStream(handler.createMessage(systemPrompt, messages)) @@ -424,42 +468,6 @@ describe("DeepSeekHandler", () => { const reasoningChunks = chunks.filter((chunk) => chunk.type === "reasoning") expect(reasoningChunks).toEqual([{ type: "reasoning", text: "primary thought" }]) }) - - it("enables thinking and forwards image_url for the vision model", async () => { - const visionHandler = new DeepSeekHandler({ - ...mockOptions, - apiModelId: "deepseek-v4-flash-vision-exp", - }) - const withImage: Anthropic.Messages.MessageParam[] = [ - { - role: "user", - content: [ - { type: "text", text: "Describe:" }, - { type: "image", source: { type: "base64", media_type: "image/png", data: "abc" } }, - ], - }, - ] - - await collectStream(visionHandler.createMessage(systemPrompt, withImage)) - - const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs.thinking).toEqual({ type: "enabled" }) - expect(callArgs.messages).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - role: "user", - content: expect.arrayContaining([ - expect.objectContaining({ - type: "image_url", - image_url: expect.objectContaining({ - url: "data:image/png;base64,abc", - }), - }), - ]), - }), - ]), - ) - }) }) describe("processUsageMetrics", () => { diff --git a/src/api/providers/__tests__/fireworks.spec.ts b/src/api/providers/__tests__/fireworks.spec.ts index bde144591d..de56c5a803 100644 --- a/src/api/providers/__tests__/fireworks.spec.ts +++ b/src/api/providers/__tests__/fireworks.spec.ts @@ -127,6 +127,13 @@ describe("FireworksHandler", () => { outputPrice: 3.96, cacheReadsPrice: 0.044, }, + { + modelId: "accounts/fireworks/models/deepseek-v4-flash-vision-exp" as const, + contextWindow: 1_048_576, + inputPrice: 0.22, + outputPrice: 0.66, + cacheReadsPrice: 0.007, + }, ])( "should expose newly added model $modelId", ({ modelId, contextWindow, inputPrice, outputPrice, cacheReadsPrice }) => { @@ -147,6 +154,16 @@ describe("FireworksHandler", () => { }, ) + it("should expose vision support for DeepSeek V4 Flash Vision Exp", () => { + const model = fireworksModels["accounts/fireworks/models/deepseek-v4-flash-vision-exp"] + + expect(model).toMatchObject({ + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + }) + }) + it("should return Kimi K2 Instruct model with correct configuration", () => { const testModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2-instruct" const handlerWithModel = new FireworksHandler({ diff --git a/src/api/providers/fetchers/__tests__/deepseek.spec.ts b/src/api/providers/fetchers/__tests__/deepseek.spec.ts index 55df933141..e44190a141 100644 --- a/src/api/providers/fetchers/__tests__/deepseek.spec.ts +++ b/src/api/providers/fetchers/__tests__/deepseek.spec.ts @@ -31,6 +31,7 @@ describe("getDeepSeekModels", () => { expect(globalThis.fetch).toHaveBeenCalledWith("http://127.0.0.1:43123/models", expect.any(Object)) expect(models["deepseek-v4-flash"]).toEqual(deepSeekModels["deepseek-v4-flash"]) expect(models["deepseek-v4-pro"]).toEqual(deepSeekModels["deepseek-v4-pro"]) + expect(models["deepseek-v4-flash-vision-exp"]).toEqual(deepSeekModels["deepseek-v4-flash-vision-exp"]) }) it("throws for 404 responses when fallback flag is not enabled", async () => { diff --git a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts index 33a9df16da..0931c8be62 100644 --- a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts +++ b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts @@ -220,6 +220,21 @@ describe("useSelectedModel", () => { }, ) + it("selects static vision metadata when the DeepSeek catalog is unavailable", () => { + const modelId = "deepseek-v4-flash-vision-exp" + mockUseRouterModels.mockReturnValue(createRouterModelsResult({ [providerIdentifiers.deepseek]: null })) + mockUseOpenRouterModelProviders.mockReturnValue(createOpenRouterModelProvidersResult({})) + + const { result } = renderHook( + () => useSelectedModel({ apiProvider: providerIdentifiers.deepseek, apiModelId: modelId }), + { wrapper: createWrapper() }, + ) + + expect(result.current.id).toBe(modelId) + expect(result.current.info).toEqual(deepSeekModels[modelId]) + expect(result.current.info?.supportsImages).toBe(true) + }) + it.each([providerIdentifiers.deepseek, providerIdentifiers.moonshot])( "falls back to static data when the %s router catalog is null", (provider) => {