Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1073,7 +1073,21 @@ export namespace Provider {
throw new ModelNotFoundError({ providerID, modelID, suggestions })
}

const info = provider.models[modelID]
let info = provider.models[modelID]

// Fallback for google-vertex-anthropic: if model not found and ID is unversioned,
// search for versioned models (e.g., claude-sonnet-4-5@20250929)
if (!info && providerID === "google-vertex-anthropic" && !modelID.includes("@")) {
const versionedMatches = Object.keys(provider.models)
.filter((key) => key.startsWith(modelID + "@"))
.sort()
.reverse()

if (versionedMatches.length > 0) {
info = provider.models[versionedMatches[0]]
}
}

if (!info) {
const availableModels = Object.keys(provider.models)
const matches = fuzzysort.go(modelID, availableModels, { limit: 3, threshold: -10000 })
Expand Down
93 changes: 93 additions & 0 deletions packages/opencode/test/provider/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,99 @@ test("ModelNotFoundError for provider includes suggestions", async () => {
})
})

test("getModel resolves unversioned google-vertex-anthropic model ID to latest versioned model", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
}),
)
},
})
await Instance.provide({
directory: tmp.path,
init: async () => {
Env.set("GOOGLE_CLOUD_PROJECT", "test-project")
Env.set("GOOGLE_CLOUD_LOCATION", "us-central1")
},
fn: async () => {
// Test unversioned model ID resolves to latest versioned
const model = await Provider.getModel("google-vertex-anthropic", "claude-sonnet-4-5")
expect(model).toBeDefined()
expect(model.providerID).toBe("google-vertex-anthropic")
// Should resolve to versioned model
expect(model.id).toContain("claude-sonnet-4-5@")
// Verify pricing data is loaded
expect(model.cost).toBeDefined()
expect(model.cost.input).toBeGreaterThan(0)
expect(model.cost.output).toBeGreaterThan(0)
},
})
})

test("getModel backward compatibility: versioned google-vertex-anthropic model ID works directly", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
}),
)
},
})
await Instance.provide({
directory: tmp.path,
init: async () => {
Env.set("GOOGLE_CLOUD_PROJECT", "test-project")
Env.set("GOOGLE_CLOUD_LOCATION", "us-central1")
},
fn: async () => {
// Test that fully versioned model ID still works
const model = await Provider.getModel("google-vertex-anthropic", "claude-sonnet-4-5@20250929")
expect(model).toBeDefined()
expect(model.providerID).toBe("google-vertex-anthropic")
expect(model.id).toBe("claude-sonnet-4-5@20250929")
// Verify pricing data is loaded
expect(model.cost).toBeDefined()
expect(model.cost.input).toBeGreaterThan(0)
expect(model.cost.output).toBeGreaterThan(0)
},
})
})

test("getModel unversioned ID only works for google-vertex-anthropic, not google-vertex", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
}),
)
},
})
await Instance.provide({
directory: tmp.path,
init: async () => {
Env.set("GOOGLE_CLOUD_PROJECT", "test-project")
},
fn: async () => {
// google-vertex (Gemini) should NOT have fallback logic, unversioned ID should fail
try {
await Provider.getModel("google-vertex", "gemini-2-flash")
// If it finds it, that's okay (backward compat if unversioned model exists)
// If it fails, that's also okay - the fallback only applies to google-vertex-anthropic
} catch (e: any) {
// Expected: unversioned model not found in google-vertex
expect(e.constructor.name).toBe("ProviderModelNotFoundError")
}
},
})
})

test("getProvider returns undefined for nonexistent provider", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
Expand Down