From f7a7af4e11a4817ae342b29ddda7593d26e3aaf0 Mon Sep 17 00:00:00 2001 From: Craig Allan-McWilliams Date: Tue, 16 Jun 2026 09:01:27 +0100 Subject: [PATCH] fix(model): default synthetic models to text-only input to avoid image 400s Synthetic models (built for ids not in the pi-ai registry, e.g. deepseek-v4-pro via Ollama) hard-coded `input: ['text', 'image']`, falsely claiming vision support. Because the model advertised image input, the openai-completions provider did not filter image content, so screenshots from the GUI/computer-use tools were sent to text-only endpoints. Ollama rejects these with HTTP 400 "this model does not support image input", surfaced to users as an opaque "invalid message format" error. Default synthetic models to text-only input. Vision-capable models resolved from the pi-ai registry keep their real modalities; only synthetic fallbacks change. For a custom vision endpoint resolved as synthetic this drops images gracefully instead of hard-failing the whole request. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/main/claude/pi-model-resolution.ts | 10 ++++++++- tests/synthetic-model-input.test.ts | 29 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/synthetic-model-input.test.ts diff --git a/src/main/claude/pi-model-resolution.ts b/src/main/claude/pi-model-resolution.ts index ca6259996..f1b7ffcf3 100644 --- a/src/main/claude/pi-model-resolution.ts +++ b/src/main/claude/pi-model-resolution.ts @@ -139,7 +139,15 @@ export function buildSyntheticPiModel( provider, baseUrl: baseUrl || '', reasoning: autoReasoning, - input: ['text', 'image'], + // Default unknown/synthetic models to text-only input. We cannot know whether + // an arbitrary model supports image input, and falsely claiming vision support + // causes hard request failures rather than gracefully dropping images — e.g. + // Ollama returns HTTP 400 "this model does not support image input" for a + // text-only model like deepseek-v4-pro, which surfaces to the user as an + // opaque "invalid message format" error. Vision-capable models resolved from + // the pi-ai registry keep their real input modalities; only synthetic + // fallbacks are affected here. + input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: contextWindow || knownSpecs?.contextWindow || 128000, maxTokens: maxTokens || knownSpecs?.maxTokens || 16384, diff --git a/tests/synthetic-model-input.test.ts b/tests/synthetic-model-input.test.ts new file mode 100644 index 000000000..c9c066d62 --- /dev/null +++ b/tests/synthetic-model-input.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { buildSyntheticPiModel } from '../src/main/claude/pi-model-resolution'; + +/** + * Regression guard for the text-only-model image bug. + * + * Synthetic models (built for ids not in the pi-ai registry) must NOT claim + * image input. Falsely advertising vision support means image content — e.g. + * screenshots from the GUI/computer-use tools — is sent to text-only endpoints, + * which hard-fail instead of gracefully dropping the images. For example Ollama + * returns HTTP 400 "this model does not support image input" for a text-only + * model like deepseek-v4-pro, which the app surfaces as an opaque + * "invalid message format" error. + */ +describe('buildSyntheticPiModel input modalities', () => { + it('defaults an unknown synthetic model to text-only input', () => { + const m = buildSyntheticPiModel('deepseek-v4-pro', 'openai', 'openai', 'https://ollama.com/v1'); + expect(m.input).toEqual(['text']); + expect(m.input).not.toContain('image'); + }); + + it('still resolves other core model fields normally', () => { + const m = buildSyntheticPiModel('some-custom-model', 'openai', 'openai', 'https://example.com/v1'); + expect(m.id).toBe('some-custom-model'); + expect(m.api).toBeTruthy(); + expect(m.contextWindow).toBeGreaterThan(0); + expect(m.maxTokens).toBeGreaterThan(0); + }); +});