Switch to local onde dependency and update model handling
- Use local onde crate via path dependency - Update crossterm and related dependencies in Cargo.lock - Switch mistralrs crates to git source - Add model selection and listing to chat TUI - Improve model config and sampling handling for Qwen 3 - Update comments and clarify code in chat and main modules
Seto Elkahfi committed
Apr 14, 2026 at 19:20 UTC
9548958673868927782b43c0e7137c4c6b09402c
4 files changed
+208
-102
Cargo.lock
+40
-56
index 2c24312..a994f4f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1056,15 +1056,16 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossterm"
-version = "0.25.0"
+version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.11.0",
"crossterm_winapi",
- "libc",
- "mio 0.8.11",
+ "futures-core",
+ "mio",
"parking_lot",
+ "rustix 0.38.44",
"signal-hook",
"signal-hook-mio",
"winapi",
@@ -1072,16 +1073,17 @@ dependencies = [
[[package]]
name = "crossterm"
-version = "0.28.1"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [
"bitflags 2.11.0",
"crossterm_winapi",
- "futures-core",
- "mio 1.2.0",
+ "derive_more",
+ "document-features",
+ "mio",
"parking_lot",
- "rustix 0.38.44",
+ "rustix 1.1.4",
"signal-hook",
"signal-hook-mio",
"winapi",
@@ -1435,6 +1437,15 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359"
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
[[package]]
name = "dtoa"
version = "1.0.11"
@@ -3016,6 +3027,12 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
[[package]]
name = "llguidance"
version = "1.7.2"
@@ -3270,18 +3287,6 @@ dependencies = [
"simd-adler32",
]
-[[package]]
-name = "mio"
-version = "0.8.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
-dependencies = [
- "libc",
- "log",
- "wasi",
- "windows-sys 0.48.0",
-]
-
[[package]]
name = "mio"
version = "1.2.0"
@@ -3297,8 +3302,7 @@ dependencies = [
[[package]]
name = "mistralrs"
version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bb0a83340b4492ebba9760ba6845364de369c7e10c1f91af8733d574c7405fa"
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
dependencies = [
"anyhow",
"candle-core",
@@ -3325,8 +3329,7 @@ dependencies = [
[[package]]
name = "mistralrs-audio"
version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ac5d36f634c7c20c45845bc995be3b6387ab01048410386a5b180cfeaf89c72"
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
dependencies = [
"anyhow",
"apodize",
@@ -3337,8 +3340,7 @@ dependencies = [
[[package]]
name = "mistralrs-core"
version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2b8b9e5c94491d9ceeded3a30291cb6af6ae74810a5776dd55e3bbb8b3429d4"
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
dependencies = [
"ahash",
"akin",
@@ -3435,8 +3437,7 @@ dependencies = [
[[package]]
name = "mistralrs-macros"
version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aa9b4794322d3f89fe61d21f33f67be494c16d5bd869604b111218f32544d32"
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
dependencies = [
"darling 0.23.0",
"proc-macro2",
@@ -3447,8 +3448,7 @@ dependencies = [
[[package]]
name = "mistralrs-mcp"
version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fa97d4e3189ed80ebbc730b7da5b2356df0ae004ab489066249340c33c089ab"
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
dependencies = [
"anyhow",
"async-trait",
@@ -3468,8 +3468,7 @@ dependencies = [
[[package]]
name = "mistralrs-paged-attn"
version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e53ddf1537426997b46abdadbe6ca8a7ce7668004ed2b7cf00115016558bae8"
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
dependencies = [
"anyhow",
"candle-core",
@@ -3485,8 +3484,7 @@ dependencies = [
[[package]]
name = "mistralrs-quant"
version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "980715493d252e9aaf0779c4c101576d67ef4dd9812bfd77a54987f6f17526f4"
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
dependencies = [
"byteorder",
"candle-core",
@@ -3515,8 +3513,7 @@ dependencies = [
[[package]]
name = "mistralrs-vision"
version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10046a3da2de5b702d3e829b5ddef7aa829ad454819dcc4876dea481a6cb5456"
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
dependencies = [
"candle-core",
"image",
@@ -3830,9 +3827,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "onde"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5436633863ffc31311f529439353ba06a436dd661d2a5d93c8793c3500123a13"
+version = "0.1.3"
dependencies = [
"anyhow",
"cc",
@@ -5330,8 +5325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
dependencies = [
"libc",
- "mio 0.8.11",
- "mio 1.2.0",
+ "mio",
"signal-hook",
]
@@ -6112,7 +6106,7 @@ checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
dependencies = [
"bytes",
"libc",
- "mio 1.2.0",
+ "mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
@@ -6292,11 +6286,10 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tqdm"
version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b316d5c2ac649ca856dacd487d0ebb94f3b746bada51355d93dd2c007ab62a2e"
+source = "git+https://github.com/setoelkahfi/tqdm?branch=deps%2Fbump-crossterm#41e4182829136e6dadbdd1c36af824d19219147a"
dependencies = [
"anyhow",
- "crossterm 0.25.0",
+ "crossterm 0.29.0",
"once_cell",
]
@@ -7273,15 +7266,6 @@ dependencies = [
"windows-targets 0.42.2",
]
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
[[package]]
name = "windows-sys"
version = "0.52.0"
Cargo.toml
+1
-1
index 550dc65..a5d8bca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,7 +15,7 @@ path = "src/main.rs"
agent-client-protocol = "0.10.4"
# Onde Inference engine (local LLM)
-onde = "0.1.1"
+onde = { path = "../onde" }
# Async runtime
async-trait = "0.1"
src/chat.rs
+144
-26
index c85a611..6015630 100644
--- a/src/chat.rs
+++ b/src/chat.rs
@@ -1,14 +1,13 @@
-//! Full-screen terminal chat UI for siGit Code.
-//!
-//! Takes over the alternate screen and multiplexes terminal events with
-//! streaming LLM tokens via `tokio::select!`.
+//! Full-screen chat TUI. Runs on the alternate screen so nothing leaks into
+//! the main terminal buffer. Streaming tokens and key events share the loop
+//! via `tokio::select!`.
use std::future::pending;
use anyhow::Result;
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use futures::StreamExt;
-use onde::inference::{ChatEngine, StreamChunk};
+use onde::inference::{ChatEngine, GgufModelConfig, SamplingConfig, StreamChunk};
use ratatui::{
Frame,
layout::{Constraint, Layout, Position},
@@ -25,7 +24,7 @@ enum Role {
User,
Assistant,
System,
- /// Banner art — rendered with per-character digit colorization.
+ /// Banner art — each character gets its own color.
Banner,
}
@@ -74,7 +73,7 @@ struct App {
stream_rx: Option<mpsc::Receiver<StreamChunk>>,
stream_buf: String,
quit: bool,
- /// Toggled every other tick while streaming — drives the blinking cursor.
+ /// Flips every few ticks while streaming to make the cursor blink.
blink_on: bool,
blink_counter: u8,
}
@@ -143,17 +142,17 @@ impl App {
self.blink_on = self.blink_counter % 4 < 2;
}
- /// Total lines the messages area would need (rough estimate for scrolling).
+ /// Rough line count for the messages area — used to drive auto-scroll.
fn total_message_lines(&self, width: u16) -> u16 {
if width == 0 {
return 0;
}
- let w = width.saturating_sub(2) as usize; // account for block borders
+ let w = width.saturating_sub(2) as usize; // subtract border columns
let mut lines: u16 = 0;
for msg in &self.messages {
lines += wrapped_line_count(&msg.text, msg.role, w);
}
- // streaming buffer
+ // count any in-progress streaming text too
if !self.stream_buf.is_empty() {
lines += wrapped_line_count(&self.stream_buf, Role::Assistant, w);
}
@@ -174,7 +173,7 @@ fn banner_char_color(_ch: char) -> Color {
Color::White
}
-/// Estimate how many terminal rows a message takes once wrapped.
+/// How many terminal rows a message takes up after line-wrapping.
fn wrapped_line_count(text: &str, role: Role, width: usize) -> u16 {
let prefix_len = match role {
Role::User => 6, // "you > "
@@ -198,12 +197,52 @@ fn wrapped_line_count(text: &str, role: Role, width: usize) -> u16 {
count.max(1)
}
+// ── Model table ──────────────────────────────────────────────────────────────
+
+struct ModelOption {
+ /// Name shown in `/models`. Must match `GgufModelConfig::display_name`.
+ name: &'static str,
+ /// Short blurb shown next to the name, e.g. "~2.7 GB".
+ description: &'static str,
+ /// True if this model actually handles tool calls.
+ tool_calling: bool,
+ /// Token budget for generation. Qwen 3 needs 4096+ or it outputs nothing.
+ max_tokens: u64,
+ config_fn: fn() -> GgufModelConfig,
+}
+
+const SIGIT_MODELS: &[ModelOption] = &[
+ ModelOption {
+ name: "Qwen 3 4B (Q4_K_M)",
+ description: "~2.7 GB",
+ tool_calling: true,
+ max_tokens: 4096,
+ config_fn: GgufModelConfig::qwen3_4b,
+ },
+ ModelOption {
+ name: "Qwen 2.5 Coder 3B (Q4_K_M)",
+ description: "~1.93 GB",
+ tool_calling: false,
+ max_tokens: 512,
+ config_fn: GgufModelConfig::qwen25_coder_3b,
+ },
+ ModelOption {
+ name: "Qwen 2.5 Coder 1.5B (Q4_K_M)",
+ description: "~941 MB",
+ tool_calling: false,
+ max_tokens: 512,
+ config_fn: GgufModelConfig::qwen25_coder_1_5b,
+ },
+];
+
// ── Slash commands ───────────────────────────────────────────────────────────
enum SlashCommand {
Help,
Clear,
Status,
+ /// `/models` lists models. `/models N` switches to model N (1-based).
+ Models(Option<usize>),
Exit,
Unknown(String),
}
@@ -213,11 +252,14 @@ fn parse_slash(input: &str) -> Option<SlashCommand> {
if !trimmed.starts_with('/') {
return None;
}
- let cmd = trimmed.split_whitespace().next().unwrap_or("");
+ let mut parts = trimmed.splitn(2, char::is_whitespace);
+ let cmd = parts.next().unwrap_or("");
+ let arg = parts.next().map(|s| s.trim());
Some(match cmd {
"/help" => SlashCommand::Help,
"/clear" => SlashCommand::Clear,
"/status" => SlashCommand::Status,
+ "/models" => SlashCommand::Models(arg.and_then(|s| s.parse::<usize>().ok())),
"/exit" | "/quit" | "/q" => SlashCommand::Exit,
other => SlashCommand::Unknown(other.to_string()),
})
@@ -283,7 +325,7 @@ fn render_messages(frame: &mut Frame, app: &mut App, area: ratatui::layout::Rect
let buf_lines: Vec<&str> = app.stream_buf.split('\n').collect();
for (i, segment) in buf_lines.iter().enumerate() {
if i > 0 {
- lines.push(Line::from(spans.drain(..).collect::<Vec<_>>()));
+ lines.push(Line::from(std::mem::take(&mut spans)));
// continuation lines get no prefix
}
spans.push(Span::raw(segment.to_string()));
@@ -500,14 +542,21 @@ fn handle_key(app: &mut App, key: KeyEvent) -> Option<String> {
// ── Slash command execution ──────────────────────────────────────────────────
-async fn exec_slash(app: &mut App, cmd: SlashCommand, engine: &ChatEngine) {
+async fn exec_slash(
+ app: &mut App,
+ cmd: SlashCommand,
+ engine: &ChatEngine,
+ terminal: &mut ratatui::DefaultTerminal,
+) {
match cmd {
SlashCommand::Help => {
app.messages.push(ChatMessage::system(
- "/help — show this message\n\
- /clear — wipe conversation history\n\
- /status — show engine status\n\
- /exit — quit chat",
+ "/help — show this message\n\
+ /models — list available models\n\
+ /models N — switch to model N\n\
+ /clear — wipe conversation history\n\
+ /status — show engine status\n\
+ /exit — quit chat",
));
}
SlashCommand::Clear => {
@@ -527,6 +576,77 @@ async fn exec_slash(app: &mut App, cmd: SlashCommand, engine: &ChatEngine) {
info.status, model, mem, info.history_length,
)));
}
+ SlashCommand::Models(selection) => match selection {
+ None => {
+ // Show the model list.
+ let info = engine.info().await;
+ let current = info.model_name.clone().unwrap_or_default();
+
+ let mut text = String::from("Available models — type /models <n> to switch:\n");
+ for (i, model) in SIGIT_MODELS.iter().enumerate() {
+ let current_marker = if current == model.name {
+ " ← current"
+ } else {
+ ""
+ };
+ let tool_badge = if model.tool_calling {
+ " ✓ tool calling"
+ } else {
+ ""
+ };
+ text.push_str(&format!(
+ "\n {} {} {}{}{}",
+ i + 1,
+ model.name,
+ model.description,
+ tool_badge,
+ current_marker,
+ ));
+ }
+ app.messages.push(ChatMessage::system(text));
+ }
+ Some(n) => {
+ let idx = n.saturating_sub(1);
+ match SIGIT_MODELS.get(idx) {
+ None => {
+ app.messages.push(ChatMessage::system(format!(
+ "error: no model #{n} — type /models to see the list."
+ )));
+ }
+ Some(model) => {
+ // Redraw first — "Loading…" has to be on screen before
+ // we block for however long the load takes.
+ app.messages
+ .push(ChatMessage::system(format!("Loading {}…", model.name)));
+ terminal.draw(|frame| render(frame, app)).ok();
+
+ engine.unload_model().await;
+
+ let config = (model.config_fn)();
+ let sampling = SamplingConfig {
+ max_tokens: Some(model.max_tokens),
+ ..SamplingConfig::default()
+ };
+
+ match engine.load_gguf_model(config, None, Some(sampling)).await {
+ Ok(_) => {
+ engine.clear_history().await;
+ app.messages.push(ChatMessage::system(format!(
+ "✓ Switched to {}",
+ model.name
+ )));
+ }
+ Err(err) => {
+ app.messages.push(ChatMessage::system(format!(
+ "error loading {}: {err}",
+ model.name
+ )));
+ }
+ }
+ }
+ }
+ }
+ },
SlashCommand::Exit => {
app.quit = true;
}
@@ -539,9 +659,7 @@ async fn exec_slash(app: &mut App, cmd: SlashCommand, engine: &ChatEngine) {
// ── Main loop ────────────────────────────────────────────────────────────────
-/// Run the interactive chat UI. Blocks until the user quits.
-///
-/// The caller must have already loaded a model into `engine`.
+/// Starts the TUI and blocks until the user quits. Model must be loaded first.
pub async fn run(engine: &ChatEngine) -> Result<()> {
let mut terminal = ratatui::init();
let result = event_loop(&mut terminal, engine).await;
@@ -554,7 +672,7 @@ async fn event_loop(terminal: &mut ratatui::DefaultTerminal, engine: &ChatEngine
let mut event_stream = EventStream::new();
loop {
- // draw
+ // redraw every iteration
terminal.draw(|frame| render(frame, &mut app))?;
if app.quit {
@@ -565,7 +683,7 @@ async fn event_loop(terminal: &mut ratatui::DefaultTerminal, engine: &ChatEngine
tokio::select! {
biased;
- // streaming chunks — only active when we have a receiver
+ // streaming tokens — only polls when there's an active receiver
chunk = async {
match app.stream_rx.as_mut() {
Some(rx) => rx.recv().await,
@@ -596,12 +714,12 @@ async fn event_loop(terminal: &mut ratatui::DefaultTerminal, engine: &ChatEngine
};
if let Event::Key(key) = event {
- // while streaming, only ctrl+c/d work
+ // ignore everything except Ctrl+C while a response is coming in
if app.is_streaming() {
if key.kind == KeyEventKind::Press {
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
if ctrl && (key.code == KeyCode::Char('c') || key.code == KeyCode::Char('d')) {
- // drop the receiver to stop reading
+ // dropping the receiver makes the stream drain itself
app.finalize_stream();
app.messages.push(ChatMessage::system("(cancelled)"));
}
@@ -612,7 +730,7 @@ async fn event_loop(terminal: &mut ratatui::DefaultTerminal, engine: &ChatEngine
if let Some(text) = handle_key(&mut app, key) {
// check for slash command first
if let Some(cmd) = parse_slash(&text) {
- exec_slash(&mut app, cmd, engine).await;
+ exec_slash(&mut app, cmd, engine, terminal).await;
continue;
}
src/main.rs
+23
-19
index 346a17f..66c8d79 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,9 @@
-//! siGit Code — AI coding agent powered by a local LLM via Onde Inference.
+//! siGit Code — local LLM coding agent built on Onde.
//!
-//! Two modes of operation:
+//! Two modes:
//!
-//! - **Interactive** (stdin is a TTY): full-screen chat UI built on ratatui.
-//! - **ACP server** (stdin is piped): JSON-RPC over stdio for editors like Zed.
+//! - Interactive (stdin is a TTY): full-screen TUI via ratatui.
+//! - ACP server (stdin is piped): JSON-RPC over stdio for Zed and friends.
//!
//! On macOS the model cache is shared with the siGit desktop app through an
//! App Group container. See [`setup`].
@@ -58,8 +58,7 @@ root cause, not the symptom. Correct beats clever.";
// ── Per-session state ────────────────────────────────────────────────────────
-/// One active session at a time. We store the `SessionId` directly (not as a
-/// `String`) so `==` just works.
+/// One active session. `SessionId` stored directly (not `String`) so `==` works.
struct Session {
id: SessionId,
}
@@ -70,7 +69,7 @@ struct Session {
struct SiGitAgent {
engine: Arc<ChatEngine>,
active_session: Arc<Mutex<Option<Session>>>,
- /// Sends streaming chunks to the forwarder task, which writes them out.
+ /// Streaming chunks go here; a separate task drains this and writes to the editor.
notification_tx: mpsc::Sender<SessionNotification>,
}
@@ -116,7 +115,7 @@ impl Agent for SiGitAgent {
log::info!("new_session: id={session_id}");
if self.engine.is_loaded().await {
- // Model is already warm — just wipe the conversation.
+ // Already loaded — just clear history for the new session.
log::info!("model already loaded — clearing history for new session");
self.engine.clear_history().await;
} else {
@@ -215,14 +214,20 @@ impl Agent for SiGitAgent {
// ── Interactive mode ─────────────────────────────────────────────────────────
-/// Load the model, then hand off to the ratatui chat TUI.
+/// Loads the model and starts the TUI.
async fn run_interactive() -> anyhow::Result<()> {
println!(" Loading siGit...");
let engine = ChatEngine::new();
- let config = GgufModelConfig::platform_default();
+ let config = GgufModelConfig::qwen3_4b();
+ // Qwen 3's <think>…</think> block burns 300-400 tokens before the actual reply.
+ // The default 512 isn't enough — the model outputs nothing. 4096 fixes it.
+ let sampling = onde::inference::SamplingConfig {
+ max_tokens: Some(4096),
+ ..onde::inference::SamplingConfig::default()
+ };
engine
- .load_gguf_model(config, Some(SYSTEM_PROMPT.to_string()), None)
+ .load_gguf_model(config, Some(SYSTEM_PROMPT.to_string()), Some(sampling))
.await
.map_err(|e| anyhow::anyhow!("model load failed: {e}"))?;
@@ -238,9 +243,9 @@ async fn run_interactive() -> anyhow::Result<()> {
// ── ACP server mode ──────────────────────────────────────────────────────────
-/// The editor spawns us and talks ACP over stdio.
+/// The editor spawns us as a subprocess and speaks ACP over stdio.
async fn run_acp_server() -> anyhow::Result<()> {
- // Agent::prompt sends chunks here; the forwarder task writes them out.
+ // Chunks from Agent::prompt land here; a forwarder task ships them to the editor.
let (notification_tx, mut notification_rx) = mpsc::channel::<SessionNotification>(256);
let agent = SiGitAgent::new(notification_tx);
@@ -254,7 +259,7 @@ async fn run_acp_server() -> anyhow::Result<()> {
local
.run_until(async move {
- // Wire up the ACP connection.
+ // Connect stdin/stdout to the ACP layer.
let (conn, io_task) = AgentSideConnection::new(
agent,
stdout,
@@ -264,7 +269,7 @@ async fn run_acp_server() -> anyhow::Result<()> {
},
);
- // Forwarder: drains the mpsc channel and pushes chunks to the client.
+ // Pull chunks off the channel and push them to the editor as they arrive.
tokio::task::spawn_local(async move {
while let Some(notification) = notification_rx.recv().await {
if let Err(err) = conn.session_notification(notification).await {
@@ -273,7 +278,7 @@ async fn run_acp_server() -> anyhow::Result<()> {
}
});
- // Blocks until the editor disconnects.
+ // Runs until the editor closes the connection.
if let Err(err) = io_task.await {
log::error!("ACP IO error: {err}");
}
@@ -287,13 +292,12 @@ async fn run_acp_server() -> anyhow::Result<()> {
#[tokio::main]
async fn main() -> anyhow::Result<()> {
- // Logs always go to stderr (stdout is either the TUI or the ACP wire).
+ // stderr for logs — stdout belongs to either the TUI or the ACP wire.
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.target(env_logger::Target::Stderr)
.init();
- // Shared model cache (macOS App Group) — must run before anything
- // touches hf-hub or ChatEngine.
+ // Set up the shared model cache before anything tries to hit hf-hub.
setup::setup_shared_model_cache();
if std::io::stdin().is_terminal() {