5.4 KiB
Models Startup + TUI Gating Plan
Goal: let the TUI render immediately and accept input, while ensuring we do not
lie about the model actually used. Model selection (/model) and turn submission
are gated until the session is configured. If /models refresh fails, core
keeps using the fallback models seeded at startup (from models.json) via the
existing RwLock.
This plan is intended to be executed top-to-bottom. Each step is checked off when completed.
Step 1 - Core: Bound /models refresh + prevent duplicate refreshes
- Add a 5s timeout to the remote
/modelsrequest incore/src/models_manager/manager.rs::refresh_available_models.- On timeout/error, do not overwrite
remote_models(fallback seeded at startup remains available automatically). - Continue to log failures for visibility.
- On timeout/error, do not overwrite
- Add a
refresh_lock: tokio::sync::Mutex<()>field toModelsManager.- Initialize it in
ModelsManager::newandModelsManager::with_provider. - Acquire it around the refresh logic so concurrent calls do not issue
duplicate
/modelsrequests.
- Initialize it in
Acceptance:
refresh_available_modelsreturns promptly (<= ~5s) even when/modelshangs.- Concurrent
list_models/get_modelcallers do not duplicate the refresh.
Step 2 - Protocol: SessionConfigured includes the selected model family metadata
- Change
SessionConfiguredEventto carrymodel_familymetadata (not a plain model string).- Implemented as
codex_protocol::openai_models::ModelFamily(a type alias ofModelInfo, matching the/modelspayload shape). - Consumers derive the model slug from
event.model_family.slug.
- Implemented as
Acceptance:
- UIs can render the selected model name (and gate features) without making additional
/modelscalls after startup completes.- No placeholder model family exists or is required.
Step 3 - TUI: Boot without blocking on models
Files:
-
tui/src/app.rs -
tui2/src/app.rs -
Remove startup awaits that currently block the first render:
- Do not call
ModelsManager::get_model(...).awaitduring startup. - Do not call
ModelsManager::list_models(...).awaitduring startup. - Do not run the existing model migration prompt at startup.
- Do not call
-
Construct
ChatWidgetimmediately withmodel_family: None(no placeholder).
Acceptance:
- The TUI event loop begins immediately (frame scheduled before any
/modelsIO).
Step 4 - TUI: Truthful readiness gating (Loading/Ready only)
Design:
- Ready is defined as "we have received
EventMsg::SessionConfigured", which includes the model actually used. - While Loading, allow typing, but queue submissions and disable
/model.
Files:
-
tui/src/chatwidget.rs -
tui2/src/chatwidget.rs -
Keep any model-family-dependent behavior gated until
SessionConfigured.- No placeholder:
ChatWidgetstoresmodel_family: Option<ModelFamily>. - Once
SessionConfiguredarrives, hydratemodel_familyfromevent.model_family.
- No placeholder:
-
Gate turn submission:
- While not configured, pressing Enter enqueues into the existing
queued_user_messagesqueue and updates the queued display. - Prevent
maybe_send_next_queued_inputfrom sending anything until the session is configured. - After session configured:
- If there is no CLI-provided
initial_user_message, start draining the queued inputs by submitting exactly one (the existing turn-completion loop will send the rest sequentially). - If there is an
initial_user_message, do not start draining immediately (avoid sending queued inputs before the initial message begins a turn).
- If there is no CLI-provided
- While not configured, pressing Enter enqueues into the existing
-
Gate
/model:- While not configured, show an info message explaining
/modelis disabled until startup completes.
- While not configured, show an info message explaining
-
Gate other model-family-dependent flows (e.g. popups/status helpers) so they early-return instead of assuming a model family exists.
Acceptance:
- Users can type immediately.
- Users can press Enter multiple times during startup; submits are queued and later executed in order.
/modelis unavailable until the actual model is known (SessionConfigured).
Step 5 - Migration: Schedule for next run (like update)
Goal: never interrupt the user during startup. Migration UX becomes a "pending notice" rendered on the next run.
- After
SessionConfigured, compute whether a migration notice should be scheduled using the currentModelsManagermodel list (from theRwLock). - Persist a "pending migration notice" to a separate file under
codex_home(similar toversion.json) so we don’t overcrowdconfig.toml. - On next run, display the notice as a history cell (non-modal), then clear the pending notice (and record it as seen so it won't reappear).
Acceptance:
- No migration prompt blocks startup.
- If migration is relevant, the user sees it next run similarly to update notices.
Step 6 - Formatting, lint, and tests
- Run
just fmt(required after Rust changes). - Ask before running
just fix -p codex-core/just fix -p codex-tui/just fix -p codex-tui2. - Run targeted tests:
cargo test -p codex-corecargo test -p codex-tuicargo test -p codex-tui2(if changed)
- Ask before running
cargo test --all-features(since core changed).
Notes:
cargo test -p codex-coremust be run outside the sandbox in this environment (wiremock binds an OS port; some seatbelt tests invoke sandbox-exec).