Merge pull request #56 from newCman1/fix/deepseek-vision-image-support
fix(models): add image support for deepseek/deepseek-v4-flash-vision-exp
This commit is contained in:
@@ -43,6 +43,7 @@ export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCod
|
|||||||
"meta/muse-spark-1.1": ["text", "image"],
|
"meta/muse-spark-1.1": ["text", "image"],
|
||||||
"meta/muse-spark-1.2": ["text", "image"],
|
"meta/muse-spark-1.2": ["text", "image"],
|
||||||
"meta/muse-spark-1.2-contributor": ["text", "image"],
|
"meta/muse-spark-1.2-contributor": ["text", "image"],
|
||||||
|
"deepseek/deepseek-v4-flash-vision-exp": ["text", "image"],
|
||||||
"moonshotai/Kimi-K2.5": ["text", "image"],
|
"moonshotai/Kimi-K2.5": ["text", "image"],
|
||||||
"moonshotai/Kimi-K2.6": ["text", "image"],
|
"moonshotai/Kimi-K2.6": ["text", "image"],
|
||||||
"moonshotai/Kimi-K2.7-Code": ["text", "image"],
|
"moonshotai/Kimi-K2.7-Code": ["text", "image"],
|
||||||
|
|||||||
@@ -103,11 +103,16 @@ describe("commandCodeModelsFromApiResponse()", () => {
|
|||||||
it("matches command-code@1.32.1 image input capabilities", () => {
|
it("matches command-code@1.32.1 image input capabilities", () => {
|
||||||
assert.deepEqual(inputModalitiesForModel("gpt-5.6-luna"), ["text", "image"])
|
assert.deepEqual(inputModalitiesForModel("gpt-5.6-luna"), ["text", "image"])
|
||||||
assert.deepEqual(inputModalitiesForModel("meta/muse-spark-1.2"), ["text", "image"])
|
assert.deepEqual(inputModalitiesForModel("meta/muse-spark-1.2"), ["text", "image"])
|
||||||
|
assert.deepEqual(inputModalitiesForModel("deepseek/deepseek-v4-flash-vision-exp"), [
|
||||||
|
"text",
|
||||||
|
"image",
|
||||||
|
])
|
||||||
assert.deepEqual(inputModalitiesForModel("deepseek/deepseek-v4-pro"), ["text"])
|
assert.deepEqual(inputModalitiesForModel("deepseek/deepseek-v4-pro"), ["text"])
|
||||||
assert.deepEqual(inputModalitiesForModel("unknown-new-model"), ["text"])
|
assert.deepEqual(inputModalitiesForModel("unknown-new-model"), ["text"])
|
||||||
assert.equal(modelSupportsImageInput("gpt-5.6-luna"), true)
|
assert.equal(modelSupportsImageInput("gpt-5.6-luna"), true)
|
||||||
|
assert.equal(modelSupportsImageInput("deepseek/deepseek-v4-flash-vision-exp"), true)
|
||||||
assert.equal(modelSupportsImageInput("deepseek/deepseek-v4-pro"), false)
|
assert.equal(modelSupportsImageInput("deepseek/deepseek-v4-pro"), false)
|
||||||
assert.equal(Object.keys(MODEL_INPUT_MODALITIES).length, 37)
|
assert.equal(Object.keys(MODEL_INPUT_MODALITIES).length, 38)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("marks only known reasoning models as reasoning-capable", () => {
|
it("marks only known reasoning models as reasoning-capable", () => {
|
||||||
|
|||||||
@@ -173,6 +173,87 @@ describe("streamCommandCode — successful streams", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("forwards a tool-result image as a following user image for vision-capable models", async () => {
|
||||||
|
server.mockResponse({
|
||||||
|
type: "success",
|
||||||
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
||||||
|
})
|
||||||
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
||||||
|
|
||||||
|
const events = await collectEvents(
|
||||||
|
streamCommandCode(
|
||||||
|
makeModel({ id: "deepseek/deepseek-v4-flash-vision-exp" }),
|
||||||
|
makeContext({
|
||||||
|
messages: [
|
||||||
|
{ role: "user", content: "read the image" },
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "toolCall", id: "c1", name: "read", arguments: {} }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "toolResult",
|
||||||
|
toolCallId: "c1",
|
||||||
|
toolName: "read",
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "image attached" },
|
||||||
|
{ type: "image", data: "aGVsbG8=", mimeType: "image/png" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
{ apiKey: "mock-key" },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// No error: the tool-result image must not be rejected for this model.
|
||||||
|
assert.equal(events.at(-1)?.type, "done")
|
||||||
|
|
||||||
|
const body = server.lastRequestBody()
|
||||||
|
// The tool-result text is forwarded on the tool message at index 2.
|
||||||
|
assert.equal(
|
||||||
|
objectAt(body, ["params", "messages", "2", "content", "0", "output", "value"]),
|
||||||
|
"image attached",
|
||||||
|
)
|
||||||
|
// The tool-result image is forwarded as a following user image message at index 3.
|
||||||
|
assert.equal(objectAt(body, ["params", "messages", "3", "role"]), "user")
|
||||||
|
assert.equal(
|
||||||
|
objectAt(body, ["params", "messages", "3", "content", "0", "image"]),
|
||||||
|
"data:image/png;base64,aGVsbG8=",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("rejects a tool-result image before network access for text-only models", async () => {
|
||||||
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
||||||
|
|
||||||
|
const events = await collectEvents(
|
||||||
|
streamCommandCode(
|
||||||
|
makeModel({ id: "deepseek/deepseek-v4-pro" }),
|
||||||
|
makeContext({
|
||||||
|
messages: [
|
||||||
|
{ role: "user", content: "read the image" },
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "toolCall", id: "c1", name: "read", arguments: {} }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "toolResult",
|
||||||
|
toolCallId: "c1",
|
||||||
|
toolName: "read",
|
||||||
|
content: [{ type: "image", data: "aGVsbG8=", mimeType: "image/png" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
{ apiKey: "mock-key" },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const lastEvent = events.at(-1)
|
||||||
|
assert.equal(lastEvent?.type, "error")
|
||||||
|
if (lastEvent?.type !== "error") throw new Error("expected error event")
|
||||||
|
assert.match(lastEvent.error.errorMessage ?? "", /does not support image content/i)
|
||||||
|
assert.equal(server.requestCount(), 0)
|
||||||
|
})
|
||||||
|
|
||||||
it("rejects images before network access for text-only models", async () => {
|
it("rejects images before network access for text-only models", async () => {
|
||||||
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user