fix: offer Altimate Base to every user on an implicit free Zen default
Issue for this PR
Closes #1301
Type of change
- Bug fix
- New feature
- Refactor / code improvement
- Documentation
What does this PR do?
Problem. The 0.11.0 Big Pickle → Altimate Base migration only fired when opencode/big-pickle was in persisted recent, and only a picker writes recent. App Insights shows 1,027 of 1,031 Big Pickle machines were implicit-default users with no recent, so on 0.11.x they were silently rerouted to the next public free Zen model (nemotron-3.5-lightning-free) and never offered Base. Verified against the 0.11.1 binary with a PTY harness: no model.json or empty recents → no dialog at all.
TUI (packages/tui).
- Eligibility is now "the launch default (
fallbackModel()) resolves to a free public OpenCode Zen model, Big Pickle included; no explicit selection judged against that same default; no project provider allowlist". Norecentrequirement. - One startup decision in
app.tsxreplaces two racing effects: prior decline → skip; Base registered and the default was never a deliberate pick → migrate silently; returning user (prompt history at startup, project sessions, or a recent) → the migration disclosure; brand-new user → the first-run welcome picker as today. Adialog.replace()that loses a race falls through instead of latching. - Migration changes the default only: recents are rewritten (Base first, the previous free default dropped); the active agent is switched only if it was on that default, so a restored conversation keeps its model.
- Escape, Ctrl-C, the
esclabel and backdrop click on the migration disclosure now behave like explicit No: the decline is persisted and the provider picker opens. Programmatic dialog replacement (command palette, session list) is no longer mistaken for a refusal (DialogProviderguards now receive a"dismiss" | "programmatic"reason). - A free Zen model the user chose explicitly, or kept after declining, is treated as usable:
useReady()accepts it and the first-run picker no longer reopens on every launch and every prompt submit. - The decline is persisted where the server can see it (
declinedManagedBaseDefaultinmodel.json), cleared by any later explicit Base selection (picker,/connect, favorite cycling). - Migration copy names the actual model being moved; migration telemetry (
altimate_base_confirm_shown/altimate_base_choice/altimate_base_register_result) now fires with anoriginfield (it was gated on the first-run funnel and therefore invisible).
Server (packages/opencode). Provider.defaultModel() and the ACP default: a registered Altimate Base outranks the keyless public Zen tier (options.apiKey === "public") and nothing else; any keyed or BYOK provider still wins; a persisted decline restores the old order; persisted recents are honoured in ACP as they already were headlessly. This is what moves the VS Code extension population (1,010 of the 1,031 machines drive altimate serve) once the extension registers Base (companion: AltimateAI/vscode-altimate-mcp-server#464).
Why it works: eligibility and explicitness are judged on the same value (the launch default), the decision runs once after every readiness signal, the refusal is persisted in both the TUI kv store and model.json, and the server only reorders the keyless public tier below a registered Base, so no connected provider ever loses precedence.
How did you verify your code works?
bun run typecheckinpackages/tuiandpackages/opencode.packages/tui: full suite 301 tests, 300 pass, 1 pre-existing skip, 0 fail; new tests intest/context/local.test.ts(free-Zen detection, eligibility, usable-free-default, recents rewrite) andtest/cli/tui/dialog-altimate-base.test.tsx(migration telemetry without first-run, Escape and mouse dismissal → decline + picker, programmatic replace does not persist, generalized copy).packages/opencode:test/provider/provider.test.tsandtest/acp/default-model.test.ts(public tier vs keyed Zen vs registered Base, decline flag, recents precedence, Big Pickle never migrated headlessly) — 648 pass, 0 fail across provider/ACP/telemetry; full suite 14,403 tests with only the 6 known local-environment MCP failures from #1300.- Marker guard:
bun run script/upstream/analyze.ts --markers --base origin/main --strictclean. - End-to-end on the built binary (PTY harness, isolated HOME, stub HTTPS gateway): implicit free-default user sees the disclosure (not the welcome picker); Escape, the esc label and No all persist the decline and open the picker; Yes registers once via POST /register and rewrites recents to Base; a registered user migrates silently; a fresh user still gets the first-run picker; an explicit re-pick and a prior decline are left alone with no picker on relaunch or on typing; a free model already in recents gets the disclosure instead of silent migration; a project provider allowlist suppresses everything.
- Two Codex review rounds on the plan and the diff; all findings addressed.
Not verified: the App Insights effect itself (needs a release and adoption); the extension side is a separate PR.
Screenshots / recordings
Migration disclosure for an implicit free-default user (PTY capture):
│ │
╰──────────────────��───────────────────────────────────────────────────────────────────────────────────────────────
Use Altimate Base? esc
Your default model, Nemotron 3.5 Lightning Free, is a
public free model. Altimate Base is the free model
Altimate hosts for data work.
Altimate Base is free and requires no signup. Requests
and responses may be logged and used to improve
─────── Altimate's products, including the model. Secrets are ────────
Checklist
- I have tested my changes locally
- I have not included unrelated changes in this PR
🤖 Generated with Claude Code
https://claude.ai/code/session_012uzYijGHdhxvpUzhgWxayo
[!NOTE]
Medium Risk
Changes default-model precedence and first-launch onboarding for a large implicit-user cohort; mistakes could mis-route sessions or repeat/skip migration prompts, though behavior is heavily regression-tested.
Overview
Fixes #1301 by treating any implicit free public Zen launch default (not only Big Pickle in recent) as eligible for the Altimate Base migration, while honoring explicit picks, prior declines, and paid/keyed providers.
TUI merges migration and first-run into one startup decision (waits on sync, model state, prompt history, and KV). Returning users get a migration disclosure with updated copy and origin: "migration" telemetry; declines persist to model.json (declinedManagedBaseDefault) as well as KV. Usable free defaults (explicit or declined) stop the welcome picker and prompt gate from reopening; submit defers while KV is pending and cancels auto-retry if the prompt changed. Dialog close reasons separate dismiss from programmatic/interrupt so quit and Ctrl+C do not count as declines.
Server / ACP share Provider.readDefaultModelState(): registered Altimate Base outranks keyless public Zen unless the user declined; recents apply in ACP defaults via re-reading model.json per session (not a frozen snapshot). Tests use OPENCODE_TEST_STATE_HOME and a Flock state getter so model.json writes do not touch real developer state.
Docs and telemetry describe the decline flag and migration origin.
Reviewed by Cursor Bugbot for commit 55c2705f2ac4e9f3bfd2ededb874d52be19844bf. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Fixes #1301 by offering Altimate Base to users whose implicit default resolves to a free public Zen model, not only users with Big Pickle in recent. It preserves explicit and keyed-provider choices while sharing migration state across the TUI, headless, and ACP paths.
TUI
- Uses one startup decision: prior declines skip, registered Base migrates silently, returning users see the disclosure, and new users keep the welcome picker.
- Treats explicitly chosen or retained free-Zen models as usable, preventing repeated onboarding prompts.
- Escape and No record a decline and open the provider picker; Ctrl-C, quit, and programmatic dialog replacement do not record one.
- Defers prompt submission during KV hydration, cancels it when the prompt edits, and preserves typed input if the picker cannot open.
- Serializes prompt-history startup writes and keeps model cycling stable while persisting each pick to
recent.
Server
- Registered Base outranks only keyless public Zen; keyed Zen, BYOK providers, recents, and project allowlists retain precedence.
- ACP rereads
model.jsonrecents and the decline flag for each default selection. - Migration telemetry includes an
originfield, and provider documentation describes the persisted decline behavior. - Tests isolate the state directory via
OPENCODE_TEST_STATE_HOME, withFlockreading the path through a getter so tests never touch a developer's real lock files.
Summary by CodeRabbit
-
New Features
- Improved Altimate Base migration for users on an implicit free default.
- Migration prompts identify the model being changed and support clear accept or decline actions.
- Default model selection considers recent models and saved migration preferences across sessions.
- Explicitly choosing Altimate Base clears a previous migration decline.
- Migration dismissals are handled consistently across keyboard, mouse, and programmatic actions.
- Registered Altimate Base is prioritized over public Zen when appropriate, while keyed Zen remains prioritized.
-
Bug Fixes
- Preserved conversation models when migration should not apply.
- Improved startup handling for returning users and usable free defaults.
-
Documentation
- Documented migration settings and telemetry events.