Skip to content

Commit dafd16a

Browse files
mdesmetclaude
andauthored
feat: default to altimate-backend model when configured (#665)
* feat: default to altimate-backend model when configured When no model is explicitly configured and no recently-used model exists, prefer altimate-backend/altimate-default as the default model if altimate credentials are present. This applies to both the main Provider.defaultModel() and the ACP agent defaultModel() fallback chains. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: respect cfg.provider filter in altimate-backend default model check The altimate-backend early return was bypassing the cfg.provider constraint that the generic fallback respects. Now the altimate check only triggers if altimate-backend is in the user's configured provider set (or if no provider filter is configured). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: properly restore OPENCODE_TEST_HOME env var in test cleanup Use delete when originalHome is undefined instead of assigning the string "undefined" back to the env var. Also remove unused Global import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b66d9f5 commit dafd16a

File tree

3 files changed

+180
-0
lines changed

3 files changed

+180
-0
lines changed

packages/opencode/src/acp/agent.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,6 +1577,16 @@ export namespace ACP {
15771577

15781578
if (specified && !providers.length) return specified
15791579

1580+
// altimate_change start — default to altimate-backend when configured and no model chosen yet
1581+
const altimateProvider = providers.find((p) => p.id === "altimate-backend")
1582+
if (altimateProvider && altimateProvider.models["altimate-default"]) {
1583+
return {
1584+
providerID: ProviderID.make("altimate-backend"),
1585+
modelID: ModelID.make("altimate-default"),
1586+
}
1587+
}
1588+
// altimate_change end
1589+
15801590
const opencodeProvider = providers.find((p) => p.id === "opencode")
15811591
if (opencodeProvider) {
15821592
if (opencodeProvider.models["big-pickle"]) {

packages/opencode/src/provider/provider.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,21 @@ export namespace Provider {
16371637
return { providerID: entry.providerID, modelID: entry.modelID }
16381638
}
16391639

1640+
// altimate_change start — default to altimate-backend when configured and no model chosen yet
1641+
const altimateProviderID = ProviderID.make("altimate-backend")
1642+
const altimateProvider = providers[altimateProviderID]
1643+
if (
1644+
altimateProvider &&
1645+
altimateProvider.models[ModelID.make("altimate-default")] &&
1646+
(!cfg.provider || Object.keys(cfg.provider).includes(String(altimateProviderID)))
1647+
) {
1648+
return {
1649+
providerID: altimateProviderID,
1650+
modelID: ModelID.make("altimate-default"),
1651+
}
1652+
}
1653+
// altimate_change end
1654+
16401655
const provider = Object.values(providers).find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id))
16411656
if (!provider) throw new Error("no providers found")
16421657
const [model] = sort(Object.values(provider.models))

packages/opencode/test/provider/provider.test.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { test, expect } from "bun:test"
22
import path from "path"
3+
import fs from "fs/promises"
34

45
import { tmpdir } from "../fixture/fixture"
56
import { Instance } from "../../src/project/instance"
@@ -2330,4 +2331,158 @@ test("github-copilot is excluded when CODESPACES=true and only GITHUB_TOKEN is s
23302331
},
23312332
})
23322333
})
2334+
2335+
// altimate_change start — tests for altimate-backend default model preference
2336+
test("defaultModel returns altimate-backend when altimate credentials exist and no model configured", async () => {
2337+
await using tmp = await tmpdir({
2338+
init: async (dir) => {
2339+
await Bun.write(
2340+
path.join(dir, "opencode.json"),
2341+
JSON.stringify({
2342+
$schema: "https://altimate.ai/config.json",
2343+
}),
2344+
)
2345+
},
2346+
})
2347+
const originalHome = process.env.OPENCODE_TEST_HOME
2348+
process.env.OPENCODE_TEST_HOME = tmp.path
2349+
const altimateDir = path.join(tmp.path, ".altimate")
2350+
await fs.mkdir(altimateDir, { recursive: true })
2351+
await Bun.write(
2352+
path.join(altimateDir, "altimate.json"),
2353+
JSON.stringify({
2354+
altimateUrl: "https://test.altimate.ai",
2355+
altimateInstanceName: "test-instance",
2356+
altimateApiKey: "test-api-key",
2357+
}),
2358+
)
2359+
try {
2360+
await Instance.provide({
2361+
directory: tmp.path,
2362+
fn: async () => {
2363+
const model = await Provider.defaultModel()
2364+
expect(String(model.providerID)).toBe("altimate-backend")
2365+
expect(String(model.modelID)).toBe("altimate-default")
2366+
},
2367+
})
2368+
} finally {
2369+
if (originalHome === undefined) delete process.env.OPENCODE_TEST_HOME
2370+
else process.env.OPENCODE_TEST_HOME = originalHome
2371+
}
2372+
})
2373+
2374+
test("defaultModel prefers altimate-backend over other providers when altimate is configured", async () => {
2375+
await using tmp = await tmpdir({
2376+
init: async (dir) => {
2377+
await Bun.write(
2378+
path.join(dir, "opencode.json"),
2379+
JSON.stringify({
2380+
$schema: "https://altimate.ai/config.json",
2381+
}),
2382+
)
2383+
},
2384+
})
2385+
const originalHome = process.env.OPENCODE_TEST_HOME
2386+
process.env.OPENCODE_TEST_HOME = tmp.path
2387+
const altimateDir = path.join(tmp.path, ".altimate")
2388+
await fs.mkdir(altimateDir, { recursive: true })
2389+
await Bun.write(
2390+
path.join(altimateDir, "altimate.json"),
2391+
JSON.stringify({
2392+
altimateUrl: "https://test.altimate.ai",
2393+
altimateInstanceName: "test-instance",
2394+
altimateApiKey: "test-api-key",
2395+
}),
2396+
)
2397+
try {
2398+
await Instance.provide({
2399+
directory: tmp.path,
2400+
init: async () => {
2401+
Env.set("ANTHROPIC_API_KEY", "test-api-key")
2402+
},
2403+
fn: async () => {
2404+
const providers = await Provider.list()
2405+
// Both providers should be available
2406+
expect(providers["anthropic"]).toBeDefined()
2407+
expect(providers["altimate-backend"]).toBeDefined()
2408+
// But defaultModel should prefer altimate-backend
2409+
const model = await Provider.defaultModel()
2410+
expect(String(model.providerID)).toBe("altimate-backend")
2411+
expect(String(model.modelID)).toBe("altimate-default")
2412+
},
2413+
})
2414+
} finally {
2415+
if (originalHome === undefined) delete process.env.OPENCODE_TEST_HOME
2416+
else process.env.OPENCODE_TEST_HOME = originalHome
2417+
}
2418+
})
2419+
2420+
test("defaultModel respects explicit config model over altimate-backend", async () => {
2421+
await using tmp = await tmpdir({
2422+
init: async (dir) => {
2423+
await Bun.write(
2424+
path.join(dir, "opencode.json"),
2425+
JSON.stringify({
2426+
$schema: "https://altimate.ai/config.json",
2427+
model: "anthropic/claude-sonnet-4-20250514",
2428+
}),
2429+
)
2430+
},
2431+
})
2432+
const originalHome = process.env.OPENCODE_TEST_HOME
2433+
process.env.OPENCODE_TEST_HOME = tmp.path
2434+
const altimateDir = path.join(tmp.path, ".altimate")
2435+
await fs.mkdir(altimateDir, { recursive: true })
2436+
await Bun.write(
2437+
path.join(altimateDir, "altimate.json"),
2438+
JSON.stringify({
2439+
altimateUrl: "https://test.altimate.ai",
2440+
altimateInstanceName: "test-instance",
2441+
altimateApiKey: "test-api-key",
2442+
}),
2443+
)
2444+
try {
2445+
await Instance.provide({
2446+
directory: tmp.path,
2447+
init: async () => {
2448+
Env.set("ANTHROPIC_API_KEY", "test-api-key")
2449+
},
2450+
fn: async () => {
2451+
const model = await Provider.defaultModel()
2452+
expect(String(model.providerID)).toBe("anthropic")
2453+
expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
2454+
},
2455+
})
2456+
} finally {
2457+
if (originalHome === undefined) delete process.env.OPENCODE_TEST_HOME
2458+
else process.env.OPENCODE_TEST_HOME = originalHome
2459+
}
2460+
})
2461+
2462+
test("defaultModel falls through to other providers when altimate is not configured", async () => {
2463+
await using tmp = await tmpdir({
2464+
init: async (dir) => {
2465+
await Bun.write(
2466+
path.join(dir, "opencode.json"),
2467+
JSON.stringify({
2468+
$schema: "https://altimate.ai/config.json",
2469+
}),
2470+
)
2471+
},
2472+
})
2473+
await Instance.provide({
2474+
directory: tmp.path,
2475+
init: async () => {
2476+
Env.set("ANTHROPIC_API_KEY", "test-api-key")
2477+
},
2478+
fn: async () => {
2479+
const providers = await Provider.list()
2480+
// altimate-backend should NOT be available (no credentials file)
2481+
expect(providers["altimate-backend"]).toBeUndefined()
2482+
const model = await Provider.defaultModel()
2483+
// Should fall through to anthropic
2484+
expect(String(model.providerID)).toBe("anthropic")
2485+
},
2486+
})
2487+
})
23332488
// altimate_change end

0 commit comments

Comments
 (0)