fix(models): add image support for deepseek-v4-flash-vision-exp
deepseek/deepseek-v4-flash-vision-exp is served by the Provider API but was
missing from the hardcoded MODEL_INPUT_MODALITIES allowlist, so pi rejected
any conversation containing an image block. Notably this also rejected images
returned by the read tool via toolResult:
Error: Selected Command Code model does not support image content in tool results
Adding the allowlist entry lets modelSupportsImageInput() return true and the
converters forward images using the current Command Code wire format.
Coverage:
- regression test for modelSupportsImageInput("deepseek/deepseek-v4-flash-vision-exp")
- end-to-end stream test for a tool-result image forwarded as a following user
image (the concrete read reproduction), not only a user-attached image
- end-to-end stream test asserting a text-only model still rejects tool-result
images before any network access
Refs: https://github.com/patlux/pi-commandcode-provider/issues/54
This commit is contained in:
@@ -173,6 +173,85 @@ 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" },
|
||||
),
|
||||
)
|
||||
|
||||
assert.equal(events.at(-1)?.type, "error")
|
||||
assert.match(events.at(-1)?.error.errorMessage ?? "", /does not support image content/i)
|
||||
assert.equal(server.requestCount(), 0)
|
||||
})
|
||||
|
||||
it("rejects images before network access for text-only models", async () => {
|
||||
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
||||
|
||||
|
||||
Reference in New Issue
Block a user