M3: models.dev catalog and cost display wiring
Adds a bundled models.dev snapshot plus harness-providers::modelsdev to resolve model metadata, and wires live cost display into the TUI's input bar and message state, updating the render snapshots accordingly.
This commit is contained in:
@@ -187,6 +187,8 @@ pub async fn apply_action(action: InputAction, state: &mut AppState, engine: &En
|
||||
state.session_id = Some(id);
|
||||
state.messages.clear();
|
||||
state.scroll_offset = 0;
|
||||
state.session_cost = 0.0;
|
||||
state.session_tokens = 0;
|
||||
state.dirty = true;
|
||||
}
|
||||
Err(e) => tracing::error!(error = %e, "failed to create session"),
|
||||
|
||||
@@ -171,11 +171,25 @@ fn render_status(frame: &mut Frame, state: &AppState, area: Rect) {
|
||||
} else {
|
||||
"Ctrl+S: sessions | Esc: abort | Ctrl+C: quit"
|
||||
};
|
||||
let text = format!("{spinner}{status} · {hints}");
|
||||
let usage = format!(
|
||||
"{} · ${:.4}",
|
||||
format_tokens(state.session_tokens),
|
||||
state.session_cost
|
||||
);
|
||||
let text = format!("{spinner}{status} · {usage} · {hints}");
|
||||
let paragraph = Paragraph::new(text).style(Style::new().fg(Color::Gray));
|
||||
frame.render_widget(paragraph, area);
|
||||
}
|
||||
|
||||
/// Compact token count: `1234` → `1.2k`, `2_000_000` → `2.0M`.
|
||||
fn format_tokens(n: u64) -> String {
|
||||
match n {
|
||||
0..=999 => format!("{n} tok"),
|
||||
1_000..=999_999 => format!("{:.1}k tok", n as f64 / 1_000.0),
|
||||
_ => format!("{:.1}M tok", n as f64 / 1_000_000.0),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_permission_modal(
|
||||
frame: &mut Frame,
|
||||
request: &harness_core::event::PermissionRequest,
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/harness-tui/src/render.rs
|
||||
assertion_line: 277
|
||||
assertion_line: 300
|
||||
expression: buffer_to_string(terminal.backend())
|
||||
---
|
||||
new session · orchestrator · anthropic/claude-sonnet-4-5
|
||||
@@ -26,4 +26,4 @@ expression: buffer_to_string(terminal.backend())
|
||||
┌ input ───────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
idle · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
idle · 0 tok · $0.0000 · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/harness-tui/src/render.rs
|
||||
assertion_line: 397
|
||||
assertion_line: 430
|
||||
expression: buffer_to_string(terminal.backend())
|
||||
---
|
||||
new session · orchestrator · anthropic/claude-sonnet-4-5
|
||||
@@ -26,4 +26,4 @@ expression: buffer_to_string(terminal.backend())
|
||||
┌ input ───────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
idle · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
idle · 0 tok · $0.0000 · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/harness-tui/src/render.rs
|
||||
assertion_line: 449
|
||||
assertion_line: 487
|
||||
expression: buffer_to_string(terminal.backend())
|
||||
---
|
||||
new session · orchestrator · anthropic/claude-sonnet-4-5
|
||||
@@ -26,4 +26,4 @@ expression: buffer_to_string(terminal.backend())
|
||||
┌ input ───────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
idle · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
idle · 0 tok · $0.0000 · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/harness-tui/src/render.rs
|
||||
assertion_line: 330
|
||||
assertion_line: 357
|
||||
expression: buffer_to_string(terminal.backend())
|
||||
---
|
||||
new session · orchestrator · anthropic/claude-sonnet-4-5
|
||||
@@ -26,4 +26,4 @@ expression: buffer_to_string(terminal.backend())
|
||||
┌ input ───────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
idle · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
idle · 0 tok · $0.0000 · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/harness-tui/src/render.rs
|
||||
assertion_line: 376
|
||||
assertion_line: 407
|
||||
expression: buffer_to_string(terminal.backend())
|
||||
---
|
||||
new session · orchestrator · anthropic/claude-sonnet-4-5
|
||||
@@ -26,4 +26,4 @@ expression: buffer_to_string(terminal.backend())
|
||||
┌ input ───────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
idle · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
idle · 0 tok · $0.0000 · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/harness-tui/src/render.rs
|
||||
assertion_line: 353
|
||||
assertion_line: 382
|
||||
expression: buffer_to_string(terminal.backend())
|
||||
---
|
||||
new session · orchestrator · anthropic/claude-sonnet-4-5
|
||||
@@ -26,4 +26,4 @@ expression: buffer_to_string(terminal.backend())
|
||||
┌ input ───────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
idle · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
idle · 0 tok · $0.0000 · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: crates/harness-tui/src/render.rs
|
||||
assertion_line: 306
|
||||
assertion_line: 331
|
||||
expression: buffer_to_string(terminal.backend())
|
||||
---
|
||||
new session · orchestrator · anthropic/claude-sonnet-4-5
|
||||
@@ -26,4 +26,4 @@ expression: buffer_to_string(terminal.backend())
|
||||
┌ input ───────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
idle · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
idle · 0 tok · $0.0000 · Ctrl+S: sessions | Esc: abort | Ctrl+C: quit
|
||||
|
||||
@@ -105,6 +105,10 @@ pub struct AppState {
|
||||
pub ctrl_c_pressed: bool,
|
||||
/// Width the currently cached part lines were wrapped to; a change invalidates them.
|
||||
pub render_width: u16,
|
||||
/// Accumulated session cost in USD, from `SessionCreated`/`SessionUpdated` events.
|
||||
pub session_cost: f64,
|
||||
/// Accumulated session tokens (input + output), for the status bar.
|
||||
pub session_tokens: u64,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -125,6 +129,8 @@ impl AppState {
|
||||
dirty: true,
|
||||
ctrl_c_pressed: false,
|
||||
render_width: 0,
|
||||
session_cost: 0.0,
|
||||
session_tokens: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +153,8 @@ impl AppState {
|
||||
self.session_title = session.title;
|
||||
self.agent = session.agent;
|
||||
self.model_ref = format!("{}/{}", session.model.provider_id, session.model.model_id);
|
||||
self.session_cost = session.cost;
|
||||
self.session_tokens = session.usage.input + session.usage.output;
|
||||
self.messages.clear();
|
||||
|
||||
let mut messages = messages;
|
||||
@@ -171,6 +179,8 @@ impl AppState {
|
||||
self.agent = session.agent;
|
||||
self.model_ref =
|
||||
format!("{}/{}", session.model.provider_id, session.model.model_id);
|
||||
self.session_cost = session.cost;
|
||||
self.session_tokens = session.usage.input + session.usage.output;
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user