Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 2394dcd

Browse files
committed
fix(vscode-lm): use static vscodeLlmModels definitions for image support
- Import vscodeLlmModels from @roo-code/types to check static definitions first - Use pattern matching only for unknown models not in static definitions - Add IMAGE_INCAPABLE_MODEL_PATTERNS for explicit non-vision models - Update IMAGE_CAPABLE_MODEL_PATTERNS to use RegExp for precise matching - Fix inconsistency where older models incorrectly reported supportsImages: true - Update tests to verify static definitions take precedence over pattern matching
1 parent 7ec9664 commit 2394dcd

2 files changed

Lines changed: 145 additions & 73 deletions

File tree

src/api/providers/__tests__/vscode-lm.spec.ts

Lines changed: 100 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Mock } from "vitest"
2-
import { checkModelSupportsImages, IMAGE_CAPABLE_MODEL_PREFIXES } from "../vscode-lm"
2+
import { checkModelSupportsImages, IMAGE_CAPABLE_MODEL_PATTERNS, IMAGE_INCAPABLE_MODEL_PATTERNS } from "../vscode-lm"
33

44
// Mocks must come first, before imports
55
vi.mock("vscode", () => {
@@ -540,90 +540,132 @@ describe("VsCodeLmHandler", () => {
540540
})
541541

542542
describe("checkModelSupportsImages", () => {
543-
describe("OpenAI GPT models", () => {
544-
it("should return true for all gpt-* models (GitHub Copilot)", () => {
545-
// All GPT models in GitHub Copilot support images
546-
expect(checkModelSupportsImages("gpt", "gpt-4o")).toBe(true)
547-
expect(checkModelSupportsImages("gpt", "gpt-4.1")).toBe(true)
548-
expect(checkModelSupportsImages("gpt", "gpt-5")).toBe(true)
549-
expect(checkModelSupportsImages("gpt", "gpt-5.1")).toBe(true)
550-
expect(checkModelSupportsImages("gpt", "gpt-5.2")).toBe(true)
551-
expect(checkModelSupportsImages("gpt-mini", "gpt-5-mini")).toBe(true)
552-
expect(checkModelSupportsImages("gpt-codex", "gpt-5.1-codex")).toBe(true)
553-
expect(checkModelSupportsImages("gpt-codex", "gpt-5.2-codex")).toBe(true)
554-
expect(checkModelSupportsImages("gpt-codex", "gpt-5.1-codex-max")).toBe(true)
555-
expect(checkModelSupportsImages("gpt-codex", "gpt-5.1-codex-mini")).toBe(true)
556-
})
557-
558-
it("should return true for o1 and o3 reasoning models", () => {
559-
expect(checkModelSupportsImages("o1", "o1-preview")).toBe(true)
560-
expect(checkModelSupportsImages("o1", "o1-mini")).toBe(true)
561-
expect(checkModelSupportsImages("o3", "o3")).toBe(true)
543+
describe("static vscodeLlmModels lookup", () => {
544+
it("should return supportsImages from static definitions when model family matches", () => {
545+
// Models in vscodeLlmModels should return their static supportsImages value
546+
expect(checkModelSupportsImages("gpt-3.5-turbo", "gpt-3.5-turbo")).toBe(false)
547+
expect(checkModelSupportsImages("gpt-4", "gpt-4")).toBe(false)
548+
expect(checkModelSupportsImages("gpt-4o-mini", "gpt-4o-mini")).toBe(false)
549+
expect(checkModelSupportsImages("gpt-4o", "gpt-4o")).toBe(true)
550+
expect(checkModelSupportsImages("gpt-4.1", "gpt-4.1")).toBe(true)
551+
expect(checkModelSupportsImages("gpt-5", "gpt-5")).toBe(true)
552+
expect(checkModelSupportsImages("gpt-5-mini", "gpt-5-mini")).toBe(true)
553+
expect(checkModelSupportsImages("o1", "o1")).toBe(false)
554+
expect(checkModelSupportsImages("o3-mini", "o3-mini")).toBe(false)
555+
expect(checkModelSupportsImages("o4-mini", "o4-mini")).toBe(false)
556+
})
557+
558+
it("should return supportsImages from static definitions for claude models", () => {
559+
expect(checkModelSupportsImages("claude-3.5-sonnet", "claude-3.5-sonnet")).toBe(true)
560+
expect(checkModelSupportsImages("claude-4-sonnet", "claude-4-sonnet")).toBe(true)
562561
})
563-
})
564562

565-
describe("Anthropic Claude models", () => {
566-
it("should return true for all claude-* models (GitHub Copilot)", () => {
567-
// All Claude models in GitHub Copilot support images
568-
expect(checkModelSupportsImages("claude-haiku", "claude-haiku-4.5")).toBe(true)
569-
expect(checkModelSupportsImages("claude-opus", "claude-opus-4.5")).toBe(true)
570-
expect(checkModelSupportsImages("claude-sonnet", "claude-sonnet-4")).toBe(true)
571-
expect(checkModelSupportsImages("claude-sonnet", "claude-sonnet-4.5")).toBe(true)
563+
it("should return supportsImages from static definitions for gemini models", () => {
564+
expect(checkModelSupportsImages("gemini-2.0-flash-001", "gemini-2.0-flash-001")).toBe(true)
565+
expect(checkModelSupportsImages("gemini-2.5-pro", "gemini-2.5-pro")).toBe(true)
572566
})
573567
})
574568

575-
describe("Google Gemini models", () => {
576-
it("should return true for all gemini-* models (GitHub Copilot)", () => {
577-
// All Gemini models in GitHub Copilot support images
578-
expect(checkModelSupportsImages("gemini-pro", "gemini-2.5-pro")).toBe(true)
579-
expect(checkModelSupportsImages("gemini-flash", "gemini-3-flash-preview")).toBe(true)
580-
expect(checkModelSupportsImages("gemini-pro", "gemini-3-pro-preview")).toBe(true)
569+
describe("pattern matching for unknown models", () => {
570+
it("should return true for gpt-4o (but not gpt-4o-mini)", () => {
571+
expect(checkModelSupportsImages("custom", "gpt-4o")).toBe(true)
572+
expect(checkModelSupportsImages("custom", "gpt-4o-mini")).toBe(false)
573+
})
574+
575+
it("should return true for gpt-4.x and higher versions", () => {
576+
expect(checkModelSupportsImages("custom", "gpt-4.1-preview")).toBe(true)
577+
expect(checkModelSupportsImages("custom", "gpt-4.2")).toBe(true)
578+
})
579+
580+
it("should return true for gpt-5 and higher (unknown variants)", () => {
581+
expect(checkModelSupportsImages("custom", "gpt-5-turbo")).toBe(true)
582+
expect(checkModelSupportsImages("custom", "gpt-6")).toBe(true)
583+
})
584+
585+
it("should return true for all claude-* models", () => {
586+
expect(checkModelSupportsImages("custom", "claude-haiku-4.5")).toBe(true)
587+
expect(checkModelSupportsImages("custom", "claude-opus-4.5")).toBe(true)
588+
expect(checkModelSupportsImages("custom", "claude-sonnet-4")).toBe(true)
589+
})
590+
591+
it("should return true for all gemini-* models", () => {
592+
expect(checkModelSupportsImages("custom", "gemini-2.5-pro")).toBe(true)
593+
expect(checkModelSupportsImages("custom", "gemini-3-flash-preview")).toBe(true)
581594
})
582595
})
583596

584597
describe("non-vision models", () => {
585-
it("should return false for grok models (text-only in GitHub Copilot)", () => {
586-
// Grok is the only model family in GitHub Copilot that doesn't support images
587-
expect(checkModelSupportsImages("grok", "grok-code-fast-1")).toBe(false)
598+
it("should return false for gpt-3.5 models", () => {
599+
expect(checkModelSupportsImages("custom", "gpt-3.5-turbo")).toBe(false)
600+
expect(checkModelSupportsImages("custom", "gpt-3.5-turbo-16k")).toBe(false)
601+
})
602+
603+
it("should return false for base gpt-4 and gpt-4-* variants", () => {
604+
expect(checkModelSupportsImages("custom", "gpt-4")).toBe(false)
605+
expect(checkModelSupportsImages("custom", "gpt-4-0125-preview")).toBe(false)
606+
expect(checkModelSupportsImages("custom", "gpt-4-turbo")).toBe(false)
607+
})
608+
609+
it("should return false for reasoning models (o1, o3-mini, o4-mini)", () => {
610+
expect(checkModelSupportsImages("custom", "o1")).toBe(false)
611+
expect(checkModelSupportsImages("custom", "o1-preview")).toBe(false)
612+
expect(checkModelSupportsImages("custom", "o1-mini")).toBe(false)
613+
expect(checkModelSupportsImages("custom", "o3-mini")).toBe(false)
614+
expect(checkModelSupportsImages("custom", "o4-mini")).toBe(false)
588615
})
589616

590-
it("should return false for models with non-matching prefixes", () => {
591-
// Models that don't start with gpt, claude, gemini, o1, or o3
617+
it("should return false for grok models", () => {
618+
expect(checkModelSupportsImages("custom", "grok-code-fast-1")).toBe(false)
619+
expect(checkModelSupportsImages("custom", "grok-2")).toBe(false)
620+
})
621+
622+
it("should return false for unknown model families", () => {
592623
expect(checkModelSupportsImages("mistral", "mistral-large")).toBe(false)
593624
expect(checkModelSupportsImages("llama", "llama-3-70b")).toBe(false)
594625
expect(checkModelSupportsImages("unknown", "some-random-model")).toBe(false)
595626
})
596627
})
597628

598629
describe("case insensitivity", () => {
599-
it("should match regardless of case", () => {
600-
expect(checkModelSupportsImages("GPT", "GPT-4O")).toBe(true)
601-
expect(checkModelSupportsImages("CLAUDE", "CLAUDE-SONNET-4")).toBe(true)
602-
expect(checkModelSupportsImages("GEMINI", "GEMINI-2.5-PRO")).toBe(true)
630+
it("should match regardless of case for pattern matching", () => {
631+
expect(checkModelSupportsImages("custom", "GPT-4O")).toBe(true)
632+
expect(checkModelSupportsImages("custom", "CLAUDE-SONNET-4")).toBe(true)
633+
expect(checkModelSupportsImages("custom", "GEMINI-2.5-PRO")).toBe(true)
603634
})
604635
})
605636

606-
describe("prefix matching", () => {
607-
it("should only match IDs that start with known prefixes", () => {
608-
// ID must START with the prefix, not just contain it
609-
expect(checkModelSupportsImages("custom", "gpt-4o")).toBe(true) // ID starts with gpt
610-
expect(checkModelSupportsImages("custom", "my-gpt-model")).toBe(false) // gpt not at start
637+
describe("pattern matching edge cases", () => {
638+
it("should only match IDs that start with known patterns", () => {
639+
expect(checkModelSupportsImages("custom", "my-gpt-4o-model")).toBe(false) // gpt not at start
611640
expect(checkModelSupportsImages("custom", "not-claude-model")).toBe(false) // claude not at start
612641
})
613642
})
614643
})
615644

616-
describe("IMAGE_CAPABLE_MODEL_PREFIXES", () => {
617-
it("should export the model prefixes array", () => {
618-
expect(Array.isArray(IMAGE_CAPABLE_MODEL_PREFIXES)).toBe(true)
619-
expect(IMAGE_CAPABLE_MODEL_PREFIXES.length).toBeGreaterThan(0)
645+
describe("IMAGE_CAPABLE_MODEL_PATTERNS", () => {
646+
it("should export the model patterns array", () => {
647+
expect(Array.isArray(IMAGE_CAPABLE_MODEL_PATTERNS)).toBe(true)
648+
expect(IMAGE_CAPABLE_MODEL_PATTERNS.length).toBeGreaterThan(0)
649+
})
650+
651+
it("should contain RegExp patterns for vision-capable models", () => {
652+
// All patterns should be RegExp instances
653+
IMAGE_CAPABLE_MODEL_PATTERNS.forEach((pattern) => {
654+
expect(pattern).toBeInstanceOf(RegExp)
655+
})
656+
})
657+
})
658+
659+
describe("IMAGE_INCAPABLE_MODEL_PATTERNS", () => {
660+
it("should export the incapable model patterns array", () => {
661+
expect(Array.isArray(IMAGE_INCAPABLE_MODEL_PATTERNS)).toBe(true)
662+
expect(IMAGE_INCAPABLE_MODEL_PATTERNS.length).toBeGreaterThan(0)
620663
})
621664

622-
it("should include key model prefixes", () => {
623-
expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("gpt")
624-
expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("claude")
625-
expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("gemini")
626-
expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("o1")
627-
expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("o3")
665+
it("should contain RegExp patterns for non-vision models", () => {
666+
// All patterns should be RegExp instances
667+
IMAGE_INCAPABLE_MODEL_PATTERNS.forEach((pattern) => {
668+
expect(pattern).toBeInstanceOf(RegExp)
669+
})
628670
})
629671
})

src/api/providers/vscode-lm.ts

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
22
import * as vscode from "vscode"
33
import OpenAI from "openai"
44

5-
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@roo-code/types"
5+
import { type ModelInfo, openAiModelInfoSaneDefaults, vscodeLlmModels } from "@roo-code/types"
66

77
import type { ApiHandlerOptions } from "../../shared/api"
88
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "../../shared/vsCodeSelectorUtils"
@@ -591,33 +591,63 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
591591
}
592592

593593
/**
594-
* Model ID prefixes that support image inputs via VS Code Language Model API.
594+
* Model ID patterns that support image inputs via VS Code Language Model API.
595595
* These models support the LanguageModelDataPart.image() API introduced in VS Code 1.106+.
596596
*
597-
* All GitHub Copilot models with these prefixes support images.
598-
* Only grok-* models don't support images (text only).
597+
* For models not in the static vscodeLlmModels definitions, we use pattern matching
598+
* to determine image support. Only newer model versions support images.
599599
*
600600
* Source: https://models.dev/api.json (github-copilot provider models)
601601
*/
602-
export const IMAGE_CAPABLE_MODEL_PREFIXES = [
603-
"gpt", // All GPT models (gpt-4o, gpt-4.1, gpt-5, gpt-5.1, gpt-5.2, gpt-5-mini, gpt-5.1-codex, etc.)
604-
"claude", // All Claude models (claude-haiku-4.5, claude-opus-4.5, claude-sonnet-4, claude-sonnet-4.5)
605-
"gemini", // All Gemini models (gemini-2.5-pro, gemini-3-flash-preview, gemini-3-pro-preview)
606-
"o1", // OpenAI o1 reasoning models
607-
"o3", // OpenAI o3 reasoning models
602+
export const IMAGE_CAPABLE_MODEL_PATTERNS = [
603+
/^gpt-4o$/i, // GPT-4o (omni) supports images, but NOT gpt-4o-mini
604+
/^gpt-4\.[1-9]/i, // GPT-4.1 and higher versions
605+
/^gpt-[5-9]/i, // GPT-5 and higher (gpt-5, gpt-5-mini, gpt-5.1-codex, etc.)
606+
/^claude-/i, // All Claude models support images
607+
/^gemini-/i, // All Gemini models support images
608+
]
609+
610+
/**
611+
* Model ID patterns that explicitly do NOT support images.
612+
* These patterns are checked before IMAGE_CAPABLE_MODEL_PATTERNS.
613+
*/
614+
export const IMAGE_INCAPABLE_MODEL_PATTERNS = [
615+
/^gpt-3\.5/i, // GPT-3.5 models don't support images
616+
/^gpt-4$/i, // Base GPT-4 doesn't support images
617+
/^gpt-4-/i, // GPT-4 variants like gpt-4-0125-preview don't support images
618+
/^gpt-4o-mini/i, // GPT-4o-mini doesn't support images
619+
/^o[1-4]-?/i, // Reasoning models (o1, o3-mini, o4-mini) don't support images
620+
/^grok-/i, // Grok models don't support images
608621
]
609622

610623
/**
611624
* Checks if a model supports image inputs based on its model ID.
612-
* Uses prefix matching against known image-capable model families.
625+
* First checks static vscodeLlmModels definitions for known models,
626+
* then falls back to pattern matching for unknown models.
613627
*
614-
* @param _family The model family (unused, kept for API compatibility)
628+
* @param family The model family (used for lookup in static definitions)
615629
* @param id The model ID
616630
* @returns true if the model supports image inputs
617631
*/
618-
export function checkModelSupportsImages(_family: string, id: string): boolean {
619-
const idLower = id.toLowerCase()
620-
return IMAGE_CAPABLE_MODEL_PREFIXES.some((prefix) => idLower.startsWith(prefix))
632+
export function checkModelSupportsImages(family: string, id: string): boolean {
633+
// First, check if the model exists in static definitions by family or id
634+
const familyInfo = vscodeLlmModels[family as keyof typeof vscodeLlmModels]
635+
if (familyInfo) {
636+
return familyInfo.supportsImages ?? false
637+
}
638+
639+
const idInfo = vscodeLlmModels[id as keyof typeof vscodeLlmModels]
640+
if (idInfo) {
641+
return idInfo.supportsImages ?? false
642+
}
643+
644+
// For unknown models, first check if it matches any incapable patterns
645+
if (IMAGE_INCAPABLE_MODEL_PATTERNS.some((pattern) => pattern.test(id))) {
646+
return false
647+
}
648+
649+
// Then check if it matches any capable patterns
650+
return IMAGE_CAPABLE_MODEL_PATTERNS.some((pattern) => pattern.test(id))
621651
}
622652

623653
// Static blacklist of VS Code Language Model IDs that should be excluded from the model list

0 commit comments

Comments
 (0)