1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Per-tenant LLM model selection — which backend the in-tab agent uses.
//!
//! The choice is a single model ID persisted to `.lh_model` in this
//! origin's OPFS root (same pattern as [`super::key_store`] /
//! `.lh_system_prompt.txt`), read on session start by
//! [`super::chat::start_session`]. A `gemini-*` id routes to the Gemini
//! backend; a `claude-*` id routes to the Anthropic backend. Both reach
//! the model through the credit proxy in credits mode (the proxy is
//! multi-provider) and BYOK still works for Gemini.
//!
//! Unlike the encrypted `.lh_api_key`, the model id is not a secret, so
//! it's stored as plaintext UTF-8.
const MODEL_FILE: &str = ".lh_model";
/// Default model id when none is persisted — the platform's Gemini default.
/// Aliases the crate-canonical [`crate::types::DEFAULT_MODEL`] so a model-id
/// flip in ONE place propagates here (no re-typed literal to drift).
pub const DEFAULT_MODEL: &str = crateDEFAULT_MODEL;
/// The selectable models, as `(id, label)` pairs. Drives the admin
/// selector template AND is the allowlist [`save`] validates against, so a
/// stale/garbage `.lh_model` can never route to an unknown model.
///
/// The ids REFERENCE the canonical backend constants rather than re-typing
/// literals — a rename in `types`/`anthropic::wire` auto-propagates here, so
/// the selector can never advertise a dead id (the model-id-flip drift trap;
/// browser-app always pulls the `anthropic` feature, so the consts resolve).
/// `gpt-*` is intentionally absent until the OpenAI selector path is wired
/// (proxy `OPENAI_API_KEY`).
///
/// The "Local (Gemma)" entry is **feature-gated on `local`**: it's appended
/// ONLY when the heavy in-browser Burn-wgpu backend is actually compiled into
/// this bundle (the `browser-app-local` feature). The default browser bundle
/// omits it, so the selector can never advertise a model `start_session` can't
/// route (selecting it would hit the "compiled without `local`" error path).
/// `gemma-3-270m` stays a literal (the `local` backend isn't always present to
/// const-reference).
// Selectable set is deliberately just Gemini Flash + Claude Opus (on-chain
// feedback #26: drop Sonnet + Haiku from the picker, label the Gemini entry
// "Gemini Flash"). The anthropic::{DEFAULT_MODEL,SONNET_MODEL} (Haiku/Sonnet)
// consts stay DEFINED in the backend — the difficulty router still uses them
// to downgrade routine turns behind the scenes — they're just no longer
// user-selectable here.
pub const MODELS: & = &;
pub const MODELS: & = &;
/// True for a Claude/Anthropic model id (`claude-*`). Everything else is
/// treated as a Gemini id by [`super::chat::start_session`].
pub
/// True for the in-browser local model id (`gemma-*`). Routes to the local
/// (Burn-wgpu) backend rather than the credit proxy / a network API.
pub
/// The thinking-budget CEILING a session on `model` is built with — the
/// SINGLE SOURCE OF TRUTH for both `chat::session::start_session`'s
/// `with_thinking(...)` and the difficulty router's per-turn clamp. The router
/// may lower a routine turn below this, but never raises it past what the
/// user's model selection implies. `None` for backends without thinking
/// control (local Gemma), so the router leaves them alone.
///
/// - Gemini → [`crate::types::ThinkingLevel::High`] (the deep-think in-tab default).
/// - Claude Haiku → `Medium`; other Claude (Sonnet/Opus) → `High`.
/// - Local (Gemma) → `None`.
pub
/// A human-readable description of `model` for the system prompt, e.g.
/// "Claude Opus (claude-opus-4-8) via the Anthropic backend" — the friendly
/// [`MODELS`] label + the raw id + the backend, so the agent can answer "which
/// model are you?" instead of guessing (on-chain feedback).
pub
/// Read the persisted model id, validated against [`MODELS`]. A missing,
/// empty, or unrecognised file falls back to [`DEFAULT_MODEL`] — the
/// selector is never left pointing at a model the bundle can't route.
pub async
/// Persist `model` as the new selection. Rejects an id not in [`MODELS`]
/// so the file can only ever hold a routable model. Best-effort write.
pub async