Commit 519a324
authored
feat(session,client): AI session titles, path resume, and per-model anthropic-beta gating (#19)
## Summary
Three user-visible session features, plus a per-request beta-header fix
and `[1m]` opt-in for 1M context:
- **AI-generated session titles** — the first user prompt on a fresh
session spawns a detached Haiku call; the result lands as a new
`Entry::Title { source: AiGenerated }` and refreshes the TUI status bar
live. One-shot per session; failures warn-log and leave the first-prompt
fallback title in place. Haiku is pinned to a `{title: string}` JSON
schema via the `structured-outputs-2025-12-15` beta so the reply can
never drift into a conversational refusal.
- **Resume by external path** — `ox -c ` resumes session files that live
outside the XDG project subdirs. UUID-shaped prefixes keep working
unchanged.
- **Session title in the TUI status bar** — new slot between model and
status, with ellipsis truncation and graceful drop under narrow widths.
- **Per-request `anthropic-beta` gated on target model** — `Client::new`
no longer stuffs a one-size-fits-all beta set into default headers.
`compute_betas(model, auth, is_agentic, want_structured)` now emits the
right subset per call, fixing `HTTP 400` on subscriptions / gateways
that reject unsupported betas (Haiku + 1M being the common failure).
Full mapping in `docs/research/anthropic-api.md` → "Per-model beta
sets".
- **`[1m]` tag for 1M-context opt-in** — append `[1m]` to `model` (e.g.,
`claude-opus-4-7[1m]`) to enable the 1M-context beta; family-based
auto-enable was removed for the same subscription-mismatch reason. Tag
is stripped before the wire, and a shared `tag_offset` helper keeps
`has_1m_tag` and `api_model_id` in lockstep on case.
Preparatory refactors land as separate commits:
- New `crate::model` module — single `MODELS` table with per-row
`Capabilities` (interleaved thinking, context management, effort, 1M,
structured outputs) spelled out per model, replacing scattered
`is_opus_*` / `is_sonnet_*` / `is_haiku_*` substring checks in both
`client::anthropic` and `prompt::environment`. Bumping for a new model
is now a single-row edit, and a test pins every row's substring-derived
flags against the `modelSupports*`-style predicates the 3P gateway
expects.
- `Action` → `UserAction` collapse across the TUI reducer.
- Shared assistant-markdown renderer extracted from `ChatView`.
- New `session::history` module hoists `ToolUse` / `ToolResult` pairing
out of `ChatView`.
- Non-streaming `Client::complete` helper for background tasks, now also
threading an optional `OutputFormat` for JSON-schema-constrained
replies.
- `docs/research/session-persistence.md` restructured to match
`tui.md`'s research-doc shape.
Review-pass follow-ups before merge:
- `session/writer.rs` moves from 0% to ~99% coverage via direct tests of
`record_session_message` and all four branches of `log_session_err`;
negative-result test idiom standardized on `.err().unwrap()`; scattered
error paths covered in `session/store`, `session/resolver`, and
`tui/components/chat`.
- `store::read_session_info` now performs a single linear full-file scan
tracking the latest `Entry::Title` / `Entry::Summary` by `updated_at`.
The prior head-plus-4KB-tail scan left a dead zone that buried
AI-generated titles behind large `tool_result` entries, so `ox --list`
silently fell back to the first-prompt title.
## Test plan
- [x] `cargo fmt --all --check`, `cargo clippy --all-targets -- -D
warnings`, `cargo test` (733 pass)
- [x] `pnpm lint`, `pnpm spellcheck`
- [x] `cargo llvm-cov` — ~93% line / ~92% region
- [x] Manual: first prompt triggers the AI title within a few seconds
and the schema blocks conversational replies from Haiku; `ox --list`
picks up AI titles even when preceded by multi-KB tool outputs;
UUID-prefix and `.jsonl` path resume both work; `claude-opus-4-7[1m]`
opts into 1M, plain `claude-opus-4-7` does not.
## Deferred
HTTP round-trip coverage for `Client::complete`, `stream_message`, and
`title_generator::generate_and_record` needs a live mock. The principled
fix is adopting `wiremock` as a dev-dep in a follow-up PR rather than a
hand-rolled workaround here.
`/model` slash command and related runtime-switch plumbing is also a
follow-up — the `[1m]` tag convention is designed so the switch can
cleanly toggle it alongside the model string, and `crate::model::MODELS`
is now the natural data source for its picker.1 parent 171a7ef commit 519a324
29 files changed
Lines changed: 4005 additions & 719 deletions
File tree
- .cspell
- crates/oxide-code/src
- agent
- client
- prompt
- session
- tool
- tui
- components
- docs
- guide
- research
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
1 | 2 | | |
2 | 3 | | |
3 | 4 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
52 | 58 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
| |||
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
| 48 | + | |
47 | 49 | | |
48 | 50 | | |
49 | 51 | | |
50 | 52 | | |
51 | 53 | | |
| 54 | + | |
52 | 55 | | |
53 | 56 | | |
54 | 57 | | |
| |||
61 | 64 | | |
62 | 65 | | |
63 | 66 | | |
64 | | - | |
| 67 | + | |
65 | 68 | | |
66 | 69 | | |
67 | 70 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
44 | 47 | | |
45 | 48 | | |
46 | 49 | | |
| |||
131 | 134 | | |
132 | 135 | | |
133 | 136 | | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
134 | 141 | | |
135 | 142 | | |
136 | 143 | | |
137 | 144 | | |
138 | 145 | | |
139 | 146 | | |
140 | 147 | | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
0 commit comments