Files
oh-my-opencode/src/tools/look-at/multimodal-agent-metadata.test.ts
T
YeonGyu-Kim fee414fd61 fix(look-at): preserve variant metadata in fallback chain and block non-vision models
- fallback-chain.ts: cache-derived entries inherit variant from matching hardcoded entries
- agent-metadata.ts: new isVisionCapableAgentModel() guard blocks non-vision registered models
- tools.ts: early vision-capability check before session creation
- Added regression tests for variant preservation and non-vision model rejection
2026-03-11 21:45:49 +09:00

171 lines
5.4 KiB
TypeScript

/// <reference types="bun-types" />
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test"
import type { PluginInput } from "@opencode-ai/plugin"
import { resolveMultimodalLookerAgentMetadata } from "./multimodal-agent-metadata"
import { setVisionCapableModelsCache, clearVisionCapableModelsCache } from "../../shared/vision-capable-models-cache"
import * as connectedProvidersCache from "../../shared/connected-providers-cache"
import * as modelAvailability from "../../shared/model-availability"
function createPluginInput(agentData: Array<Record<string, unknown>>): PluginInput {
const client = {} as PluginInput["client"]
Object.assign(client, {
app: {
agents: mock(async () => ({ data: agentData })),
},
})
return {
client,
project: {} as PluginInput["project"],
directory: "/project",
worktree: "/project",
serverUrl: new URL("http://localhost"),
$: {} as PluginInput["$"],
}
}
describe("resolveMultimodalLookerAgentMetadata", () => {
beforeEach(() => {
clearVisionCapableModelsCache()
})
afterEach(() => {
clearVisionCapableModelsCache()
;(modelAvailability.fetchAvailableModels as unknown as { mockRestore?: () => void }).mockRestore?.()
;(connectedProvidersCache.readConnectedProvidersCache as unknown as { mockRestore?: () => void }).mockRestore?.()
})
test("returns configured multimodal-looker model when it already matches a vision-capable override", async () => {
// given
setVisionCapableModelsCache(new Map([
[
"rundao/public/qwen3.5-397b",
{ providerID: "rundao", modelID: "public/qwen3.5-397b" },
],
]))
spyOn(modelAvailability, "fetchAvailableModels").mockResolvedValue(
new Set(["rundao/public/qwen3.5-397b"]),
)
spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["rundao"])
const ctx = createPluginInput([
{
name: "multimodal-looker",
model: { providerID: "rundao", modelID: "public/qwen3.5-397b" },
},
])
// when
const result = await resolveMultimodalLookerAgentMetadata(ctx)
// then
expect(result).toEqual({
agentModel: { providerID: "rundao", modelID: "public/qwen3.5-397b" },
agentVariant: undefined,
})
})
test("preserves hardcoded fallback variant when the registered model matches a cache-derived entry", async () => {
// given
setVisionCapableModelsCache(new Map([
[
"openai/gpt-5.4",
{ providerID: "openai", modelID: "gpt-5.4" },
],
]))
spyOn(modelAvailability, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.4"]),
)
spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
const ctx = createPluginInput([
{
name: "multimodal-looker",
model: { providerID: "openai", modelID: "gpt-5.4" },
},
])
// when
const result = await resolveMultimodalLookerAgentMetadata(ctx)
// then
expect(result).toEqual({
agentModel: { providerID: "openai", modelID: "gpt-5.4" },
agentVariant: "medium",
})
})
test("prefers connected vision-capable provider models before the hardcoded fallback chain", async () => {
// given
setVisionCapableModelsCache(new Map([
[
"rundao/public/qwen3.5-397b",
{ providerID: "rundao", modelID: "public/qwen3.5-397b" },
],
]))
spyOn(modelAvailability, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.4", "rundao/public/qwen3.5-397b"]),
)
spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai", "rundao"])
const ctx = createPluginInput([
{
name: "multimodal-looker",
model: { providerID: "openai", modelID: "gpt-5.4" },
variant: "medium",
},
])
// when
const result = await resolveMultimodalLookerAgentMetadata(ctx)
// then
expect(result).toEqual({
agentModel: { providerID: "rundao", modelID: "public/qwen3.5-397b" },
agentVariant: undefined,
})
})
test("falls back to the hardcoded multimodal chain when no dynamic vision model exists", async () => {
// given
setVisionCapableModelsCache(new Map([
[
"google/gemini-3-flash",
{ providerID: "google", modelID: "gemini-3-flash" },
],
]))
spyOn(modelAvailability, "fetchAvailableModels").mockResolvedValue(
new Set(["google/gemini-3-flash"]),
)
spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["google"])
const ctx = createPluginInput([])
// when
const result = await resolveMultimodalLookerAgentMetadata(ctx)
// then
expect(result).toEqual({
agentModel: { providerID: "google", modelID: "gemini-3-flash" },
agentVariant: undefined,
})
})
test("does not return a registered model when no vision-capable model is available", async () => {
// given
spyOn(modelAvailability, "fetchAvailableModels").mockResolvedValue(
new Set(["openai/gpt-5.4"]),
)
spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
const ctx = createPluginInput([
{
name: "multimodal-looker",
model: { providerID: "openai", modelID: "gpt-5.4" },
},
])
// when
const result = await resolveMultimodalLookerAgentMetadata(ctx)
// then
expect(result).toEqual({})
})
})