Skip to content

Commit e07d776

Browse files
fix(zoo-gateway): enable image attach when model supports vision (#897)
* fix(zoo-gateway): enable image attach from live vision model tags Prefer models-API vision tags over the stale hardcoded allowlist so Zoo Gateway (and Vercel AI Gateway) correctly enable image attachment for models like Claude Sonnet 4.5. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(zoo-gateway): drop unused eslint-disable that failed CI lint CI does not flag no-var on the ambient global declaration, so the disable became an unused-directive warning. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(zoo-gateway): remove redundant vision-capability comments Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 50801da commit e07d776

4 files changed

Lines changed: 64 additions & 2 deletions

File tree

packages/types/src/providers/vercel-ai-gateway.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,14 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([
5353
"anthropic/claude-3-sonnet",
5454
"anthropic/claude-3.5-sonnet",
5555
"anthropic/claude-3.7-sonnet",
56+
"anthropic/claude-haiku-4.5",
5657
"anthropic/claude-opus-4",
5758
"anthropic/claude-opus-4.1",
5859
"anthropic/claude-opus-4.5",
5960
"anthropic/claude-opus-4.6",
6061
"anthropic/claude-fable-5",
6162
"anthropic/claude-sonnet-4",
63+
"anthropic/claude-sonnet-4.5",
6264
"anthropic/claude-sonnet-4.6",
6365
"anthropic/claude-sonnet-5",
6466
"google/gemini-1.5-flash",
@@ -85,6 +87,9 @@ export const VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS = new Set([
8587
"openai/gpt-4.5-preview",
8688
"openai/gpt-4o",
8789
"openai/gpt-4o-mini",
90+
"openai/gpt-5",
91+
"openai/gpt-5-mini",
92+
"openai/gpt-5-nano",
8893
"openai/gpt-oss-120b",
8994
"openai/gpt-oss-20b",
9095
"openai/o3",

src/api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,39 @@ describe("Vercel AI Gateway Fetchers", () => {
269269
)
270270
})
271271

272+
it("prefers live vision tags over hardcoded allowlists", () => {
273+
const taggedVision = parseVercelAiGatewayModel({
274+
id: "anthropic/claude-sonnet-4.5",
275+
model: {
276+
...baseModel,
277+
id: "anthropic/claude-sonnet-4.5",
278+
tags: ["tool-use", "vision"],
279+
},
280+
})
281+
expect(taggedVision.supportsImages).toBe(true)
282+
283+
const taggedTextOnly = parseVercelAiGatewayModel({
284+
id: "anthropic/claude-sonnet-4",
285+
model: {
286+
...baseModel,
287+
id: "anthropic/claude-sonnet-4",
288+
tags: ["tool-use"],
289+
},
290+
})
291+
expect(taggedTextOnly.supportsImages).toBe(false)
292+
})
293+
294+
it("falls back to allowlists when tags are absent", () => {
295+
const result = parseVercelAiGatewayModel({
296+
id: "anthropic/claude-sonnet-4.5",
297+
model: {
298+
...baseModel,
299+
id: "anthropic/claude-sonnet-4.5",
300+
},
301+
})
302+
expect(result.supportsImages).toBe(true)
303+
})
304+
272305
it("handles missing cache pricing", () => {
273306
const modelNoCachePricing = {
274307
...baseModel,

src/api/providers/fetchers/__tests__/zoo-gateway.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,28 @@ describe("Zoo Gateway Fetchers", () => {
159159
})
160160

161161
describe("parseZooGatewayModel", () => {
162+
it("enables image attachment from Zoo Gateway vision tags", () => {
163+
const result = parseZooGatewayModel({
164+
id: "anthropic/claude-sonnet-4.5",
165+
model: {
166+
id: "anthropic/claude-sonnet-4.5",
167+
object: "model",
168+
owned_by: "anthropic",
169+
name: "Claude Sonnet 4.5",
170+
context_window: 200000,
171+
max_tokens: 64000,
172+
type: "language",
173+
tags: ["tool-use", "vision"],
174+
pricing: {
175+
input: "3.00",
176+
output: "15.00",
177+
},
178+
},
179+
})
180+
181+
expect(result.supportsImages).toBe(true)
182+
})
183+
162184
it("delegates to the vercel-ai-gateway parser", () => {
163185
const result = parseZooGatewayModel({
164186
id: "anthropic/claude-sonnet-4",

src/api/providers/fetchers/vercel-ai-gateway.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const vercelAiGatewayModelSchema = z.object({
3434
context_window: z.number(),
3535
max_tokens: z.number(),
3636
type: z.string(),
37+
tags: z.array(z.string()).optional(),
3738
pricing: vercelAiGatewayPricingSchema,
3839
})
3940

@@ -99,8 +100,9 @@ export const parseVercelAiGatewayModel = ({ id, model }: { id: string; model: Ve
99100
const cacheReadsPrice = model.pricing?.input_cache_read ? parseApiPrice(model.pricing?.input_cache_read) : undefined
100101

101102
const supportsPromptCache = typeof cacheWritesPrice !== "undefined" && typeof cacheReadsPrice !== "undefined"
102-
const supportsImages =
103-
VERCEL_AI_GATEWAY_VISION_ONLY_MODELS.has(id) || VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS.has(id)
103+
const supportsImages = Array.isArray(model.tags)
104+
? model.tags.includes("vision")
105+
: VERCEL_AI_GATEWAY_VISION_ONLY_MODELS.has(id) || VERCEL_AI_GATEWAY_VISION_AND_TOOLS_MODELS.has(id)
104106

105107
const modelInfo: ModelInfo = {
106108
maxTokens: model.max_tokens,

0 commit comments

Comments
 (0)