Skip to content
Merged
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ file is the human-readable summary of what changed and why.

## Unreleased

### TUI

- **The model you pick stays picked** — setup no longer pins its "Choose default
model" answer as a top-level `model` in the global config. That pin outranked
everything else on startup (config `model` beats the saved recent models), so
every `/models` switch silently reverted on the next launch and the only fix
was hand-editing `codev.jsonc`. The chosen model is now seeded as the most
recent selection instead, so it still starts as the default and a later switch
simply replaces it. Existing configs keep their pin — delete the `"model"`
line from `~/.config/codev/codev.jsonc` to pick up the new behavior.

## 1.18.4-3

### TUI
Expand Down
10 changes: 9 additions & 1 deletion packages/codev-gateway/src/config-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@ const MODEL_MODALITIES: { attachment: boolean; modalities: { input: Modality[];
// mandatory: without it opencode sizes the context window to 0 and disables
// auto-compaction; the modality block is mandatory for the same reason (hub PR
// #219) — see MODEL_MODALITIES.
//
// Deliberately no top-level `model`, and the hub fragment dropped it in the
// same round (hub PR #221): a pinned default outranks the TUI's persisted
// model selection every startup (Provider defaultModel checks config first,
// then the state dir's model.json recents), so the wizard's one-time choice
// would silently undo every later /models switch. The configure flow seeds
// that choice into the recents instead, which the next switch displaces; the
// hub leads its models map with the chosen id for the same effect. A config
// written by an older hub still carries a pin — nothing here removes one.
export function buildGatewayConfig(input: { apiKey: string; baseUrl: string; model: string; models?: string[] }) {
const ids = [...new Set([input.model, ...(input.models ?? [])])]
return {
model: `${GATEWAY_PROVIDER.id}/${input.model}`,
compaction: {
auto: true,
reserved: GATEWAY_COMPACT_RESERVED,
Expand Down
8 changes: 7 additions & 1 deletion packages/codev-gateway/test/config-block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ describe("buildGatewayConfig", () => {
models: ["MiniMax/MiniMax-M2.7", "other/model"],
})
expect(config).toEqual({
model: "netgate/MiniMax/MiniMax-M2.7",
compaction: { auto: true, reserved: 29491 },
provider: {
netgate: {
Expand Down Expand Up @@ -65,6 +64,13 @@ describe("buildGatewayConfig", () => {
const withDupe = buildGatewayConfig({ apiKey: "k", baseUrl: "https://gw.test", model: "m", models: ["m", "n"] })
expect(Object.keys(withDupe.provider.netgate.models)).toEqual(["m", "n"])
})

// A pinned default outranks the TUI's persisted selection on every startup,
// so the chosen model is seeded into the recents by the configure flow
// instead of written here.
test("pins no top-level default model", () => {
expect(buildGatewayConfig({ apiKey: "k", baseUrl: "https://gw.test", model: "m" })).not.toHaveProperty("model")
})
})

describe("buildCustomProviderConfig", () => {
Expand Down
14 changes: 14 additions & 0 deletions packages/tui/src/component/dialog-configure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
fetchApiKey,
fetchCodevConfig,
fetchModels,
GATEWAY_PROVIDER,
login,
saveApiKey,
saveCodevConfig,
Expand All @@ -13,6 +14,7 @@ import open from "open"
import { createSignal, onMount, Show, type Accessor } from "solid-js"
import { useClipboard } from "../context/clipboard"
import { useEpilogue } from "../context/epilogue"
import { useLocal } from "../context/local"
import { runCustomProviderFlow, RETRIES, StatusBox, withRetries } from "./dialog-custom-provider"
import { DialogProvider } from "./dialog-provider"
import { useExit } from "../context/exit"
Expand Down Expand Up @@ -46,6 +48,7 @@ interface FlowContext {
toast: ReturnType<typeof useToast>
exit: ReturnType<typeof useExit>
epilogue: ReturnType<typeof useEpilogue>
local: ReturnType<typeof useLocal>
}

export function DialogConfigure() {
Expand All @@ -56,6 +59,7 @@ export function DialogConfigure() {
toast: useToast(),
exit: useExit(),
epilogue: useEpilogue(),
local: useLocal(),
}

// The entry chooser is a non-dismissable setup gate: no escape, no
Expand Down Expand Up @@ -321,7 +325,17 @@ async function runModelFlow(ctx: FlowContext, creds: GatewayCreds) {
})
if (result.error) throw new Error(JSON.stringify(result.error))
})
// The config block deliberately pins no top-level `model` (it would outrank
// every later /models switch on restart), so persist the choice as the most
// recent selection instead — the highest-priority source a switch can still
// replace. Dispose first like the custom-provider flow does: config.update
// only forks the instance reload, so a bootstrap can otherwise race it and
// come back without the new provider. Skip the seed rather than let
// model.set warn about an unknown provider mid-onboarding if it does.
await ctx.sdk.client.instance.dispose()
await ctx.sync.bootstrap()
if (ctx.sync.data.provider.some((item) => item.id === GATEWAY_PROVIDER.id && !!item.models[model]))
ctx.local.model.set({ providerID: GATEWAY_PROVIDER.id, modelID: model }, { recent: true })
ctx.toast.show({ variant: "info", message: `CoDev Code configured — using ${model}` })
ctx.dialog.clear()
} catch (err) {
Expand Down
Loading