9daaeedc50
Prevent src/shared batch runs from leaking module mocks into later files, which was breaking Linux CI cache metadata and legacy plugin warning assertions.
325 lines
9.0 KiB
TypeScript
325 lines
9.0 KiB
TypeScript
import { afterAll, describe, expect, test, mock } from "bun:test"
|
|
|
|
// Mock connected-providers-cache to prevent local disk cache from polluting test results.
|
|
// Without this, findProviderModelMetadata reads real cached model metadata (e.g., from opencode serve)
|
|
// which causes the "prefers runtime models.dev cache" test to get different values than expected.
|
|
mock.module("./connected-providers-cache", () => ({
|
|
findProviderModelMetadata: () => undefined,
|
|
readConnectedProvidersCache: () => null,
|
|
hasConnectedProvidersCache: () => false,
|
|
hasProviderModelsCache: () => false,
|
|
}))
|
|
|
|
afterAll(() => {
|
|
mock.restore()
|
|
})
|
|
|
|
import {
|
|
getModelCapabilities,
|
|
getBundledModelCapabilitiesSnapshot,
|
|
type ModelCapabilitiesSnapshot,
|
|
} from "./model-capabilities"
|
|
import { AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS } from "./model-requirements"
|
|
|
|
describe("getModelCapabilities", () => {
|
|
const bundledSnapshot: ModelCapabilitiesSnapshot = {
|
|
generatedAt: "2026-03-25T00:00:00.000Z",
|
|
sourceUrl: "https://models.dev/api.json",
|
|
models: {
|
|
"claude-opus-4-6": {
|
|
id: "claude-opus-4-6",
|
|
family: "claude-opus",
|
|
reasoning: true,
|
|
temperature: true,
|
|
modalities: {
|
|
input: ["text", "image", "pdf"],
|
|
output: ["text"],
|
|
},
|
|
limit: {
|
|
context: 1_000_000,
|
|
output: 128_000,
|
|
},
|
|
toolCall: true,
|
|
},
|
|
"gemini-3.1-pro": {
|
|
id: "gemini-3.1-pro",
|
|
family: "gemini",
|
|
reasoning: true,
|
|
temperature: true,
|
|
modalities: {
|
|
input: ["text", "image"],
|
|
output: ["text"],
|
|
},
|
|
limit: {
|
|
context: 1_000_000,
|
|
output: 65_000,
|
|
},
|
|
},
|
|
"gpt-5.4": {
|
|
id: "gpt-5.4",
|
|
family: "gpt",
|
|
reasoning: true,
|
|
temperature: false,
|
|
modalities: {
|
|
input: ["text", "image", "pdf"],
|
|
output: ["text"],
|
|
},
|
|
limit: {
|
|
context: 1_050_000,
|
|
output: 128_000,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
test("uses runtime metadata before snapshot data", () => {
|
|
const result = getModelCapabilities({
|
|
providerID: "anthropic",
|
|
modelID: "claude-opus-4-6",
|
|
runtimeModel: {
|
|
variants: {
|
|
low: {},
|
|
medium: {},
|
|
high: {},
|
|
},
|
|
},
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result).toMatchObject({
|
|
canonicalModelID: "claude-opus-4-6",
|
|
family: "claude-opus",
|
|
variants: ["low", "medium", "high"],
|
|
supportsThinking: true,
|
|
supportsTemperature: true,
|
|
maxOutputTokens: 128_000,
|
|
toolCall: true,
|
|
})
|
|
expect(result.diagnostics).toMatchObject({
|
|
resolutionMode: "snapshot-backed",
|
|
canonicalization: { source: "canonical" },
|
|
snapshot: { source: "bundled-snapshot" },
|
|
variants: { source: "runtime" },
|
|
})
|
|
})
|
|
|
|
test("reads structured runtime capabilities from the SDK v2 shape", () => {
|
|
const result = getModelCapabilities({
|
|
providerID: "openai",
|
|
modelID: "gpt-5.4",
|
|
runtimeModel: {
|
|
capabilities: {
|
|
reasoning: true,
|
|
temperature: false,
|
|
toolcall: true,
|
|
input: {
|
|
text: true,
|
|
image: true,
|
|
},
|
|
output: {
|
|
text: true,
|
|
},
|
|
},
|
|
},
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result).toMatchObject({
|
|
canonicalModelID: "gpt-5.4",
|
|
reasoning: true,
|
|
supportsThinking: true,
|
|
supportsTemperature: false,
|
|
toolCall: true,
|
|
modalities: {
|
|
input: ["text", "image"],
|
|
output: ["text"],
|
|
},
|
|
})
|
|
expect(result.diagnostics).toMatchObject({
|
|
resolutionMode: "snapshot-backed",
|
|
reasoning: { source: "runtime" },
|
|
supportsThinking: { source: "runtime" },
|
|
toolCall: { source: "runtime" },
|
|
})
|
|
})
|
|
|
|
test("respects root-level thinking flags when providers do not nest them under capabilities", () => {
|
|
const result = getModelCapabilities({
|
|
providerID: "custom-proxy",
|
|
modelID: "gpt-5.4",
|
|
runtimeModel: {
|
|
supportsThinking: true,
|
|
},
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result).toMatchObject({
|
|
canonicalModelID: "gpt-5.4",
|
|
supportsThinking: true,
|
|
})
|
|
expect(result.diagnostics).toMatchObject({
|
|
supportsThinking: { source: "runtime" },
|
|
})
|
|
})
|
|
|
|
test("accepts runtime variant arrays without corrupting them into numeric keys", () => {
|
|
const result = getModelCapabilities({
|
|
providerID: "openai",
|
|
modelID: "gpt-5.4",
|
|
runtimeModel: {
|
|
variants: ["low", "medium", "high", "xhigh"],
|
|
},
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result.variants).toEqual(["low", "medium", "high", "xhigh"])
|
|
})
|
|
|
|
test("normalizes the legacy Claude Opus thinking alias before snapshot lookup", () => {
|
|
const result = getModelCapabilities({
|
|
providerID: "anthropic",
|
|
modelID: "claude-opus-4-6-thinking",
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result).toMatchObject({
|
|
canonicalModelID: "claude-opus-4-6",
|
|
family: "claude-opus",
|
|
supportsThinking: true,
|
|
supportsTemperature: true,
|
|
maxOutputTokens: 128_000,
|
|
})
|
|
expect(result.diagnostics).toMatchObject({
|
|
resolutionMode: "alias-backed",
|
|
canonicalization: {
|
|
source: "exact-alias",
|
|
ruleID: "claude-opus-4-6-thinking-legacy-alias",
|
|
},
|
|
snapshot: { source: "bundled-snapshot" },
|
|
})
|
|
})
|
|
|
|
test("maps local gemini aliases to canonical models.dev entries", () => {
|
|
const result = getModelCapabilities({
|
|
providerID: "google",
|
|
modelID: "gemini-3.1-pro-high",
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result).toMatchObject({
|
|
canonicalModelID: "gemini-3.1-pro",
|
|
family: "gemini",
|
|
supportsThinking: true,
|
|
supportsTemperature: true,
|
|
maxOutputTokens: 65_000,
|
|
})
|
|
expect(result.diagnostics).toMatchObject({
|
|
resolutionMode: "alias-backed",
|
|
canonicalization: {
|
|
source: "exact-alias",
|
|
ruleID: "gemini-3.1-pro-tier-alias",
|
|
},
|
|
snapshot: { source: "bundled-snapshot" },
|
|
})
|
|
})
|
|
|
|
test("prefers runtime models.dev cache over bundled snapshot", () => {
|
|
const runtimeSnapshot: ModelCapabilitiesSnapshot = {
|
|
...bundledSnapshot,
|
|
models: {
|
|
...bundledSnapshot.models,
|
|
"gpt-5.4": {
|
|
...bundledSnapshot.models["gpt-5.4"],
|
|
limit: {
|
|
context: 1_050_000,
|
|
output: 64_000,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
const result = getModelCapabilities({
|
|
providerID: "openai",
|
|
modelID: "gpt-5.4",
|
|
bundledSnapshot,
|
|
runtimeSnapshot,
|
|
})
|
|
|
|
expect(result).toMatchObject({
|
|
canonicalModelID: "gpt-5.4",
|
|
maxOutputTokens: 64_000,
|
|
supportsTemperature: false,
|
|
})
|
|
expect(result.diagnostics).toMatchObject({
|
|
snapshot: { source: "runtime-snapshot" },
|
|
maxOutputTokens: { source: "runtime-snapshot" },
|
|
supportsTemperature: { source: "runtime-snapshot" },
|
|
})
|
|
})
|
|
|
|
test("falls back to heuristic family rules when no snapshot entry exists", () => {
|
|
const result = getModelCapabilities({
|
|
providerID: "openai",
|
|
modelID: "o3-mini",
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result).toMatchObject({
|
|
canonicalModelID: "o3-mini",
|
|
family: "openai-reasoning",
|
|
variants: ["low", "medium", "high"],
|
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
|
|
})
|
|
expect(result.diagnostics).toMatchObject({
|
|
resolutionMode: "heuristic-backed",
|
|
snapshot: { source: "none" },
|
|
family: { source: "heuristic" },
|
|
reasoningEfforts: { source: "heuristic" },
|
|
})
|
|
})
|
|
|
|
test("detects prefixed o-series model IDs through the heuristic fallback", () => {
|
|
const result = getModelCapabilities({
|
|
providerID: "azure-openai",
|
|
modelID: "openai/o3-mini",
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result).toMatchObject({
|
|
canonicalModelID: "openai/o3-mini",
|
|
family: "openai-reasoning",
|
|
variants: ["low", "medium", "high"],
|
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],
|
|
})
|
|
expect(result.diagnostics).toMatchObject({
|
|
resolutionMode: "heuristic-backed",
|
|
snapshot: { source: "none" },
|
|
family: { source: "heuristic" },
|
|
})
|
|
})
|
|
|
|
test("keeps every built-in OmO requirement model snapshot-backed", () => {
|
|
const bundledSnapshot = getBundledModelCapabilitiesSnapshot()
|
|
const requirementModels = new Set<string>()
|
|
|
|
for (const requirement of Object.values(AGENT_MODEL_REQUIREMENTS)) {
|
|
for (const entry of requirement.fallbackChain) requirementModels.add(entry.model)
|
|
}
|
|
|
|
for (const requirement of Object.values(CATEGORY_MODEL_REQUIREMENTS)) {
|
|
for (const entry of requirement.fallbackChain) requirementModels.add(entry.model)
|
|
}
|
|
|
|
for (const modelID of requirementModels) {
|
|
const result = getModelCapabilities({
|
|
providerID: "test-provider",
|
|
modelID,
|
|
bundledSnapshot,
|
|
})
|
|
|
|
expect(result.diagnostics.resolutionMode).toBe("snapshot-backed")
|
|
expect(result.diagnostics.snapshot.source).toBe("bundled-snapshot")
|
|
}
|
|
})
|
|
})
|