fix(models): update Gemini 3 to 3.1 Pro and add Kimi to writing category fallback
Closes #2065 Closes #1968
This commit is contained in:
@@ -774,7 +774,7 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
test("migrates model to category when mapping exists", () => {
|
||||
// given: Config with a model that has a category mapping
|
||||
const config = {
|
||||
model: "google/gemini-3-pro",
|
||||
model: "google/gemini-3.1-pro",
|
||||
temperature: 0.5,
|
||||
top_p: 0.9,
|
||||
}
|
||||
@@ -823,7 +823,7 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
test("handles all mapped models correctly", () => {
|
||||
// given: Configs for each mapped model
|
||||
const configs = [
|
||||
{ model: "google/gemini-3-pro" },
|
||||
{ model: "google/gemini-3.1-pro" },
|
||||
{ model: "google/gemini-3-flash" },
|
||||
{ model: "openai/gpt-5.2" },
|
||||
{ model: "anthropic/claude-haiku-4-5" },
|
||||
@@ -893,7 +893,7 @@ describe("shouldDeleteAgentConfig", () => {
|
||||
// given: Config with fields matching category defaults
|
||||
const config = {
|
||||
category: "visual-engineering",
|
||||
model: "google/gemini-3-pro",
|
||||
model: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when: Check if config should be deleted
|
||||
@@ -1021,7 +1021,7 @@ describe("migrateConfigFile with backup", () => {
|
||||
agents: {
|
||||
"multimodal-looker": { model: "anthropic/claude-haiku-4-5" },
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
"my-custom-agent": { model: "google/gemini-3-pro" },
|
||||
"my-custom-agent": { model: "google/gemini-3.1-pro" },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1037,7 +1037,7 @@ describe("migrateConfigFile with backup", () => {
|
||||
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
|
||||
expect(agents["multimodal-looker"].model).toBe("anthropic/claude-haiku-4-5")
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.2")
|
||||
expect(agents["my-custom-agent"].model).toBe("google/gemini-3-pro")
|
||||
expect(agents["my-custom-agent"].model).toBe("google/gemini-3.1-pro")
|
||||
})
|
||||
|
||||
test("preserves category setting when explicitly set", () => {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* This map will be removed in a future major version once migration period ends.
|
||||
*/
|
||||
export const MODEL_TO_CATEGORY_MAP: Record<string, string> = {
|
||||
"google/gemini-3-pro": "visual-engineering",
|
||||
"google/gemini-3.1-pro": "visual-engineering",
|
||||
"google/gemini-3-flash": "writing",
|
||||
"openai/gpt-5.2": "ultrabrain",
|
||||
"anthropic/claude-haiku-4-5": "quick",
|
||||
|
||||
@@ -63,7 +63,7 @@ describe("fetchAvailableModels", () => {
|
||||
writeModelsCache({
|
||||
openai: { id: "openai", models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
anthropic: { id: "anthropic", models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
google: { id: "google", models: { "gemini-3-pro": { id: "gemini-3-pro" } } },
|
||||
google: { id: "google", models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels(undefined, {
|
||||
@@ -74,7 +74,7 @@ describe("fetchAvailableModels", () => {
|
||||
expect(result.size).toBe(3)
|
||||
expect(result.has("openai/gpt-5.2")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("google/gemini-3-pro")).toBe(true)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(true)
|
||||
})
|
||||
|
||||
it("#given connectedProviders unknown #when fetchAvailableModels called without options #then returns empty Set", async () => {
|
||||
@@ -97,7 +97,7 @@ describe("fetchAvailableModels", () => {
|
||||
list: async () => ({
|
||||
data: [
|
||||
{ id: "gpt-5.3-codex", provider: "openai" },
|
||||
{ id: "gemini-3-pro", provider: "google" },
|
||||
{ id: "gemini-3.1-pro", provider: "google" },
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -107,7 +107,7 @@ describe("fetchAvailableModels", () => {
|
||||
|
||||
expect(result).toBeInstanceOf(Set)
|
||||
expect(result.has("openai/gpt-5.3-codex")).toBe(true)
|
||||
expect(result.has("google/gemini-3-pro")).toBe(false)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(false)
|
||||
})
|
||||
|
||||
it("#given cache file not found #when fetchAvailableModels called with connectedProviders #then returns empty Set", async () => {
|
||||
@@ -126,7 +126,7 @@ describe("fetchAvailableModels", () => {
|
||||
list: async () => ({
|
||||
data: [
|
||||
{ id: "gpt-5.3-codex", provider: "openai" },
|
||||
{ id: "gemini-3-pro", provider: "google" },
|
||||
{ id: "gemini-3.1-pro", provider: "google" },
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -136,7 +136,7 @@ describe("fetchAvailableModels", () => {
|
||||
|
||||
expect(result).toBeInstanceOf(Set)
|
||||
expect(result.has("openai/gpt-5.3-codex")).toBe(true)
|
||||
expect(result.has("google/gemini-3-pro")).toBe(true)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(true)
|
||||
})
|
||||
|
||||
it("#given cache read twice #when second call made with same providers #then reads fresh each time", async () => {
|
||||
@@ -515,7 +515,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
google: { models: { "gemini-3-pro": { id: "gemini-3-pro" } } },
|
||||
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels(undefined, {
|
||||
@@ -525,7 +525,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
expect(result.size).toBe(1)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("openai/gpt-5.2")).toBe(false)
|
||||
expect(result.has("google/gemini-3-pro")).toBe(false)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(false)
|
||||
})
|
||||
|
||||
// given cache with multiple providers
|
||||
@@ -535,7 +535,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
google: { models: { "gemini-3-pro": { id: "gemini-3-pro" } } },
|
||||
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels(undefined, {
|
||||
@@ -544,7 +544,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
|
||||
expect(result.size).toBe(2)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("google/gemini-3-pro")).toBe(true)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(true)
|
||||
expect(result.has("openai/gpt-5.2")).toBe(false)
|
||||
})
|
||||
|
||||
@@ -759,7 +759,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
models: {
|
||||
opencode: ["big-pickle"],
|
||||
anthropic: ["claude-opus-4-6"],
|
||||
google: ["gemini-3-pro"]
|
||||
google: ["gemini-3.1-pro"]
|
||||
},
|
||||
connected: ["opencode", "anthropic", "google"]
|
||||
})
|
||||
@@ -771,7 +771,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
expect(result.size).toBe(1)
|
||||
expect(result.has("opencode/big-pickle")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(false)
|
||||
expect(result.has("google/gemini-3-pro")).toBe(false)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle object[] format with metadata (Ollama-style)", async () => {
|
||||
@@ -953,7 +953,7 @@ describe("fallback model availability", () => {
|
||||
{ providers: ["openai"], model: "gpt-5.2" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
]
|
||||
const availableModels = new Set(["google/gemini-3-pro"])
|
||||
const availableModels = new Set(["google/gemini-3.1-pro"])
|
||||
|
||||
// when
|
||||
const result = resolveFirstAvailableFallback(fallbackChain, availableModels)
|
||||
|
||||
@@ -248,19 +248,19 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
|
||||
expect(primary.providers[0]).toBe("openai")
|
||||
})
|
||||
|
||||
test("visual-engineering has valid fallbackChain with gemini-3-pro high as primary", () => {
|
||||
test("visual-engineering has valid fallbackChain with gemini-3.1-pro high as primary", () => {
|
||||
// given - visual-engineering category requirement
|
||||
const visualEngineering = CATEGORY_MODEL_REQUIREMENTS["visual-engineering"]
|
||||
|
||||
// when - accessing visual-engineering requirement
|
||||
// then - fallbackChain: gemini-3-pro(high) → glm-5 → opus-4-6(max)
|
||||
// then - fallbackChain: gemini-3.1-pro(high) → glm-5 → opus-4-6(max)
|
||||
expect(visualEngineering).toBeDefined()
|
||||
expect(visualEngineering.fallbackChain).toBeArray()
|
||||
expect(visualEngineering.fallbackChain).toHaveLength(3)
|
||||
|
||||
const primary = visualEngineering.fallbackChain[0]
|
||||
expect(primary.providers[0]).toBe("google")
|
||||
expect(primary.model).toBe("gemini-3-pro")
|
||||
expect(primary.model).toBe("gemini-3.1-pro")
|
||||
expect(primary.variant).toBe("high")
|
||||
|
||||
const second = visualEngineering.fallbackChain[1]
|
||||
@@ -319,39 +319,43 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
|
||||
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"])
|
||||
})
|
||||
|
||||
test("artistry has valid fallbackChain with gemini-3-pro as primary", () => {
|
||||
test("artistry has valid fallbackChain with gemini-3.1-pro as primary", () => {
|
||||
// given - artistry category requirement
|
||||
const artistry = CATEGORY_MODEL_REQUIREMENTS["artistry"]
|
||||
|
||||
// when - accessing artistry requirement
|
||||
// then - fallbackChain exists with gemini-3-pro as first entry
|
||||
// then - fallbackChain exists with gemini-3.1-pro as first entry
|
||||
expect(artistry).toBeDefined()
|
||||
expect(artistry.fallbackChain).toBeArray()
|
||||
expect(artistry.fallbackChain.length).toBeGreaterThan(0)
|
||||
|
||||
const primary = artistry.fallbackChain[0]
|
||||
expect(primary.model).toBe("gemini-3-pro")
|
||||
expect(primary.model).toBe("gemini-3.1-pro")
|
||||
expect(primary.variant).toBe("high")
|
||||
expect(primary.providers[0]).toBe("google")
|
||||
})
|
||||
|
||||
test("writing has valid fallbackChain with gemini-3-flash as primary", () => {
|
||||
test("writing has valid fallbackChain with kimi-k2.5-free as primary", () => {
|
||||
// given - writing category requirement
|
||||
const writing = CATEGORY_MODEL_REQUIREMENTS["writing"]
|
||||
|
||||
// when - accessing writing requirement
|
||||
// then - fallbackChain: gemini-3-flash → claude-sonnet-4-6
|
||||
// then - fallbackChain: kimi-k2.5-free -> gemini-3-flash -> claude-sonnet-4-6
|
||||
expect(writing).toBeDefined()
|
||||
expect(writing.fallbackChain).toBeArray()
|
||||
expect(writing.fallbackChain).toHaveLength(2)
|
||||
expect(writing.fallbackChain).toHaveLength(3)
|
||||
|
||||
const primary = writing.fallbackChain[0]
|
||||
expect(primary.model).toBe("gemini-3-flash")
|
||||
expect(primary.providers[0]).toBe("google")
|
||||
expect(primary.model).toBe("kimi-k2.5-free")
|
||||
expect(primary.providers[0]).toBe("opencode")
|
||||
|
||||
const second = writing.fallbackChain[1]
|
||||
expect(second.model).toBe("claude-sonnet-4-6")
|
||||
expect(second.providers[0]).toBe("anthropic")
|
||||
expect(second.model).toBe("gemini-3-flash")
|
||||
expect(second.providers[0]).toBe("google")
|
||||
|
||||
const third = writing.fallbackChain[2]
|
||||
expect(third.model).toBe("claude-sonnet-4-6")
|
||||
expect(third.providers[0]).toBe("anthropic")
|
||||
})
|
||||
|
||||
test("all 8 categories have valid fallbackChain arrays", () => {
|
||||
@@ -489,12 +493,12 @@ describe("requiresModel field in categories", () => {
|
||||
expect(deep.requiresModel).toBe("gpt-5.3-codex")
|
||||
})
|
||||
|
||||
test("artistry category has requiresModel set to gemini-3-pro", () => {
|
||||
test("artistry category has requiresModel set to gemini-3.1-pro", () => {
|
||||
// given
|
||||
const artistry = CATEGORY_MODEL_REQUIREMENTS["artistry"]
|
||||
|
||||
// when / #then
|
||||
expect(artistry.requiresModel).toBe("gemini-3-pro")
|
||||
expect(artistry.requiresModel).toBe("gemini-3.1-pro")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
oracle: {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
],
|
||||
},
|
||||
@@ -65,7 +65,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
},
|
||||
metis: {
|
||||
@@ -73,14 +73,14 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
|
||||
],
|
||||
},
|
||||
momus: {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "medium" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
|
||||
],
|
||||
},
|
||||
atlas: {
|
||||
@@ -95,7 +95,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
"visual-engineering": {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
|
||||
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
],
|
||||
@@ -103,7 +103,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
ultrabrain: {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "xhigh" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
],
|
||||
},
|
||||
@@ -111,17 +111,17 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
|
||||
],
|
||||
requiresModel: "gpt-5.3-codex",
|
||||
},
|
||||
artistry: {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
|
||||
],
|
||||
requiresModel: "gemini-3-pro",
|
||||
requiresModel: "gemini-3.1-pro",
|
||||
},
|
||||
quick: {
|
||||
fallbackChain: [
|
||||
@@ -141,11 +141,12 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
},
|
||||
writing: {
|
||||
fallbackChain: [
|
||||
{ providers: ["opencode"], model: "kimi-k2.5-free" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
|
||||
],
|
||||
|
||||
@@ -10,7 +10,7 @@ describe("resolveModel", () => {
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
inheritedModel: "openai/gpt-5.2",
|
||||
systemDefault: "google/gemini-3-pro",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -25,7 +25,7 @@ describe("resolveModel", () => {
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: undefined,
|
||||
inheritedModel: "openai/gpt-5.2",
|
||||
systemDefault: "google/gemini-3-pro",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -40,14 +40,14 @@ describe("resolveModel", () => {
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: undefined,
|
||||
inheritedModel: undefined,
|
||||
systemDefault: "google/gemini-3-pro",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = resolveModel(input)
|
||||
|
||||
// then
|
||||
expect(result).toBe("google/gemini-3-pro")
|
||||
expect(result).toBe("google/gemini-3.1-pro")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -57,7 +57,7 @@ describe("resolveModel", () => {
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: "",
|
||||
inheritedModel: "openai/gpt-5.2",
|
||||
systemDefault: "google/gemini-3-pro",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -72,14 +72,14 @@ describe("resolveModel", () => {
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: " ",
|
||||
inheritedModel: "",
|
||||
systemDefault: "google/gemini-3-pro",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = resolveModel(input)
|
||||
|
||||
// then
|
||||
expect(result).toBe("google/gemini-3-pro")
|
||||
expect(result).toBe("google/gemini-3.1-pro")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -89,7 +89,7 @@ describe("resolveModel", () => {
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
inheritedModel: "openai/gpt-5.2",
|
||||
systemDefault: "google/gemini-3-pro",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -123,7 +123,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -141,7 +141,7 @@ describe("resolveModelWithFallback", () => {
|
||||
uiSelectedModel: "opencode/big-pickle",
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -158,7 +158,7 @@ describe("resolveModelWithFallback", () => {
|
||||
uiSelectedModel: " ",
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -175,7 +175,7 @@ describe("resolveModelWithFallback", () => {
|
||||
uiSelectedModel: "",
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -195,7 +195,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -215,7 +215,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -234,7 +234,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -252,7 +252,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -271,7 +271,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels: new Set(["github-copilot/claude-opus-4-6-preview", "opencode/claude-opus-4-7"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -294,8 +294,8 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "anthropic", "google"], model: "gpt-5.2" },
|
||||
],
|
||||
availableModels: new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6", "google/gemini-3-pro"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
availableModels: new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6", "google/gemini-3.1-pro"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -313,7 +313,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic", "opencode"], model: "gpt-5-nano" },
|
||||
],
|
||||
availableModels: new Set(["opencode/gpt-5-nano"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -331,7 +331,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -346,7 +346,7 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -361,7 +361,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -378,7 +378,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic"], model: "CLAUDE-OPUS" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -397,7 +397,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
||||
],
|
||||
availableModels: new Set(["opencode/glm-5", "anthropic/claude-sonnet-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -420,7 +420,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["zai-coding-plan"], model: "glm-5" },
|
||||
],
|
||||
availableModels: new Set(["zai-coding-plan/glm-5", "opencode/glm-5"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -438,7 +438,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["zai-coding-plan"], model: "glm-5", variant: "high" },
|
||||
],
|
||||
availableModels: new Set(["opencode/glm-5"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -457,7 +457,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic"], model: "claude-sonnet-4-6" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-sonnet-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -477,14 +477,14 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic"], model: "nonexistent-model" },
|
||||
],
|
||||
availableModels: new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("google/gemini-3-pro")
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro")
|
||||
expect(result!.source).toBe("system-default")
|
||||
expect(logSpy).toHaveBeenCalledWith("No available model found in fallback chain, falling through to system default")
|
||||
})
|
||||
@@ -516,7 +516,7 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic", "openai"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -533,7 +533,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["github-copilot"])
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
||||
@@ -544,7 +544,7 @@ describe("resolveModelWithFallback", () => {
|
||||
|
||||
// then - should use github-copilot (second provider) since google not connected
|
||||
// model name is transformed to preview variant for github-copilot provider
|
||||
expect(result!.model).toBe("github-copilot/gemini-3-pro-preview")
|
||||
expect(result!.model).toBe("github-copilot/gemini-3.1-pro-preview")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
cacheSpy.mockRestore()
|
||||
})
|
||||
@@ -577,14 +577,14 @@ describe("resolveModelWithFallback", () => {
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - should fall through to system default
|
||||
expect(result!.model).toBe("google/gemini-3-pro")
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro")
|
||||
expect(result!.source).toBe("system-default")
|
||||
cacheSpy.mockRestore()
|
||||
})
|
||||
@@ -593,14 +593,14 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
availableModels: new Set(["openai/gpt-5.2"]),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("google/gemini-3-pro")
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro")
|
||||
expect(result!.source).toBe("system-default")
|
||||
})
|
||||
})
|
||||
@@ -627,20 +627,20 @@ describe("resolveModelWithFallback", () => {
|
||||
|
||||
test("tries all providers in first entry before moving to second entry", () => {
|
||||
// given
|
||||
const availableModels = new Set(["google/gemini-3-pro"])
|
||||
const availableModels = new Set(["google/gemini-3.1-pro"])
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback({
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "anthropic"], model: "gpt-5.2" },
|
||||
{ providers: ["google"], model: "gemini-3-pro" },
|
||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels,
|
||||
systemDefaultModel: "system/default",
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("google/gemini-3-pro")
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
@@ -675,7 +675,7 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai"], model: "gpt-5.2" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["google"], model: "gemini-3-pro" },
|
||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels,
|
||||
systemDefaultModel: "system/default",
|
||||
@@ -693,7 +693,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "google/gemini-3-pro",
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -708,32 +708,32 @@ describe("resolveModelWithFallback", () => {
|
||||
|
||||
describe("categoryDefaultModel (fuzzy matching for category defaults)", () => {
|
||||
test("applies fuzzy matching to categoryDefaultModel when userModel not provided", () => {
|
||||
// given - gemini-3-pro is the category default, but only gemini-3-pro-preview is available
|
||||
// given - gemini-3.1-pro is the category default, but only gemini-3.1-pro-preview is available
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
categoryDefaultModel: "google/gemini-3-pro",
|
||||
categoryDefaultModel: "google/gemini-3.1-pro",
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
||||
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels: new Set(["google/gemini-3-pro-preview", "anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
||||
}
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - should fuzzy match gemini-3-pro → gemini-3-pro-preview
|
||||
expect(result!.model).toBe("google/gemini-3-pro-preview")
|
||||
// then - should fuzzy match gemini-3.1-pro → gemini-3.1-pro-preview
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro-preview")
|
||||
expect(result!.source).toBe("category-default")
|
||||
})
|
||||
|
||||
test("categoryDefaultModel uses exact match when available", () => {
|
||||
// given - exact match exists
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
categoryDefaultModel: "google/gemini-3-pro",
|
||||
categoryDefaultModel: "google/gemini-3.1-pro",
|
||||
fallbackChain: [
|
||||
{ providers: ["google"], model: "gemini-3-pro" },
|
||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels: new Set(["google/gemini-3-pro", "google/gemini-3-pro-preview"]),
|
||||
availableModels: new Set(["google/gemini-3.1-pro", "google/gemini-3.1-pro-preview"]),
|
||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
||||
}
|
||||
|
||||
@@ -741,14 +741,14 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - should use exact match
|
||||
expect(result!.model).toBe("google/gemini-3-pro")
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro")
|
||||
expect(result!.source).toBe("category-default")
|
||||
})
|
||||
|
||||
test("categoryDefaultModel falls through to fallbackChain when no match in availableModels", () => {
|
||||
// given - categoryDefaultModel has no match, but fallbackChain does
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
categoryDefaultModel: "google/gemini-3-pro",
|
||||
categoryDefaultModel: "google/gemini-3.1-pro",
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
],
|
||||
@@ -768,11 +768,11 @@ describe("resolveModelWithFallback", () => {
|
||||
// given - both userModel and categoryDefaultModel provided
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
categoryDefaultModel: "google/gemini-3-pro",
|
||||
categoryDefaultModel: "google/gemini-3.1-pro",
|
||||
fallbackChain: [
|
||||
{ providers: ["google"], model: "gemini-3-pro" },
|
||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels: new Set(["google/gemini-3-pro-preview", "anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "system/default",
|
||||
}
|
||||
|
||||
@@ -788,7 +788,7 @@ describe("resolveModelWithFallback", () => {
|
||||
// given - no availableModels but connected provider cache exists
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["google"])
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
categoryDefaultModel: "google/gemini-3-pro",
|
||||
categoryDefaultModel: "google/gemini-3.1-pro",
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "anthropic/claude-sonnet-4-6",
|
||||
}
|
||||
@@ -797,7 +797,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - should use transformed categoryDefaultModel since google is connected
|
||||
expect(result!.model).toBe("google/gemini-3-pro-preview")
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro-preview")
|
||||
expect(result!.source).toBe("category-default")
|
||||
cacheSpy.mockRestore()
|
||||
})
|
||||
@@ -824,7 +824,7 @@ describe("resolveModelWithFallback", () => {
|
||||
// given - category default already has -preview suffix
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["google"])
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
categoryDefaultModel: "google/gemini-3-pro-preview",
|
||||
categoryDefaultModel: "google/gemini-3.1-pro-preview",
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "anthropic/claude-sonnet-4-5",
|
||||
}
|
||||
@@ -832,18 +832,18 @@ describe("resolveModelWithFallback", () => {
|
||||
// when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - should NOT become gemini-3-pro-preview-preview
|
||||
expect(result!.model).toBe("google/gemini-3-pro-preview")
|
||||
// then - should NOT become gemini-3.1-pro-preview-preview
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro-preview")
|
||||
expect(result!.source).toBe("category-default")
|
||||
cacheSpy.mockRestore()
|
||||
})
|
||||
|
||||
test("transforms gemini-3-pro in fallback chain for google connected provider", () => {
|
||||
// given - google connected, fallback chain has gemini-3-pro
|
||||
test("transforms gemini-3.1-pro in fallback chain for google connected provider", () => {
|
||||
// given - google connected, fallback chain has gemini-3.1-pro
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["google"])
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["google", "github-copilot"], model: "gemini-3-pro" },
|
||||
{ providers: ["google", "github-copilot"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: "anthropic/claude-sonnet-4-5",
|
||||
@@ -853,7 +853,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then - should transform to preview variant for google provider
|
||||
expect(result!.model).toBe("google/gemini-3-pro-preview")
|
||||
expect(result!.model).toBe("google/gemini-3.1-pro-preview")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
cacheSpy.mockRestore()
|
||||
})
|
||||
|
||||
@@ -6,12 +6,12 @@ export function transformModelForProvider(provider: string, model: string): stri
|
||||
.replace("claude-sonnet-4-5", "claude-sonnet-4.5")
|
||||
.replace("claude-haiku-4-5", "claude-haiku-4.5")
|
||||
.replace("claude-sonnet-4", "claude-sonnet-4")
|
||||
.replace(/gemini-3-pro(?!-)/g, "gemini-3-pro-preview")
|
||||
.replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview")
|
||||
.replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview")
|
||||
}
|
||||
if (provider === "google") {
|
||||
return model
|
||||
.replace(/gemini-3-pro(?!-)/g, "gemini-3-pro-preview")
|
||||
.replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview")
|
||||
.replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview")
|
||||
}
|
||||
return model
|
||||
|
||||
Reference in New Issue
Block a user