From a6ea36311d7997b822a00edbb046d905e00e1c05 Mon Sep 17 00:00:00 2001 From: weby-homelab Date: Fri, 19 Jun 2026 13:25:53 +0300 Subject: [PATCH] feat: enhance OpenCode support with context metrics and improve documentation --- README.md | 3 +++ src/collector/claude.rs | 24 ++++++++---------------- src/collector/mod.rs | 31 +++++++------------------------ src/collector/opencode.rs | 13 ++++++++++--- 4 files changed, 28 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 3191e10..f4b7507 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ All read-only. No API keys. No auth. ### macOS / Linux +> [!IMPORTANT] +> On Linux, ensure `sqlite3` is installed to enable monitoring for OpenCode sessions. + ```bash curl --proto '=https' --tlsv1.2 -LsSf https://github.com/graykode/abtop/releases/latest/download/abtop-installer.sh | sh ``` diff --git a/src/collector/claude.rs b/src/collector/claude.rs index 39a2bc5..8331cff 100644 --- a/src/collector/claude.rs +++ b/src/collector/claude.rs @@ -572,7 +572,7 @@ impl ClaudeCollector { }; let configured_model = read_configured_model(&sf.cwd); - let context_window = context_window_for_model(&model, &configured_model, max_context_tokens); + let context_window = crate::collector::context_window_for_model(&model, &configured_model, max_context_tokens); let context_percent = if context_window > 0 { (last_context_tokens as f64 / context_window as f64) * 100.0 } else { @@ -1885,14 +1885,6 @@ fn truncate(s: &str, max: usize) -> String { } } -fn context_window_for_model(transcript_model: &str, configured_model: &str, max_context_tokens: u64) -> u64 { - if transcript_model.contains("[1m]") || configured_model.contains("[1m]") || max_context_tokens > 200_000 { - 1_000_000 - } else { - 200_000 - } -} - /// Returns the ordered list of Claude Code settings files to check, from /// highest to lowest priority, matching Claude Code's own resolution order: /// 1. `{cwd}/.claude/settings.local.json` @@ -3087,27 +3079,27 @@ n/Users/bob/.claude-alt/projects/-Users-bob-project/session.jsonl } #[test] - fn test_context_window_for_model() { + fn test_crate::collector::context_window_for_model() { // Base model with low token usage → 200K - assert_eq!(context_window_for_model("claude-opus-4-6", "", 50_000), 200_000); + assert_eq!(crate::collector::context_window_for_model("claude-opus-4-6", "", 50_000), 200_000); // Explicit [1m] suffix in transcript model → 1M regardless of token count assert_eq!( - context_window_for_model("claude-opus-4-6[1m]", "", 0), + crate::collector::context_window_for_model("claude-opus-4-6[1m]", "", 0), 1_000_000 ); // [1m] in configured model (from settings.json) → 1M even if transcript lacks it assert_eq!( - context_window_for_model("claude-sonnet-4-6", "sonnet[1m]", 0), + crate::collector::context_window_for_model("claude-sonnet-4-6", "sonnet[1m]", 0), 1_000_000 ); assert_eq!( - context_window_for_model("claude-sonnet-4-6", "", 100_000), + crate::collector::context_window_for_model("claude-sonnet-4-6", "", 100_000), 200_000 ); - assert_eq!(context_window_for_model("unknown-model", "", 0), 200_000); + assert_eq!(crate::collector::context_window_for_model("unknown-model", "", 0), 200_000); // Token usage exceeds 200K → must be 1M window assert_eq!( - context_window_for_model("claude-opus-4-6", "", 250_000), + crate::collector::context_window_for_model("claude-opus-4-6", "", 250_000), 1_000_000 ); } diff --git a/src/collector/mod.rs b/src/collector/mod.rs index 69b0e81..65fdf07 100644 --- a/src/collector/mod.rs +++ b/src/collector/mod.rs @@ -112,30 +112,13 @@ pub trait AgentCollector { /// Process data fetched once per tick and shared across all collectors. /// Avoids duplicate ps/lsof calls. -pub struct SharedProcessData { - pub process_info: HashMap, - pub children_map: HashMap>, - pub ports: HashMap>, - /// True on slow poll ticks (every 5 ticks ≈ 10s). Collectors should - /// defer expensive discovery (e.g. /proc reads) to slow ticks. - pub slow_tick: bool, - /// PIDs of detected codex mcp-server processes. Populated by - /// `MultiCollector` after McpDetection runs; CodexCollector - /// excludes these so a single mcp-server PID isn't double-counted - /// in the sessions panel. - pub mcp_server_pids: HashSet, - /// Rollout file paths held open by an mcp-server process. The - /// CodexCollector "recently finished" pass skips these to avoid - /// PID=0 ghost rows for threads that the mcp-server is still - /// holding fds for. - pub mcp_owned_rollouts: HashSet, - /// When false, the suppression sets above are empty so the - /// sessions panel restores upstream behavior. Driven by the user - /// toggle (Shift+M). - pub mcp_suppress: bool, - /// Cached Desktop app-server PID -> open rollout files. This is populated - /// by a background scanner so slow macOS lsof calls cannot block the TUI. - pub desktop_rollout_fd_map: HashMap>, +/// Context window size for this model (e.g. 200K, 1M). +pub(crate) fn context_window_for_model(transcript_model: &str, configured_model: &str, max_context_tokens: u64) -> u64 { + if transcript_model.contains("[1m]") || configured_model.contains("[1m]") || max_context_tokens > 200_000 { + 1_000_000 + } else { + 200_000 + } } impl SharedProcessData { diff --git a/src/collector/opencode.rs b/src/collector/opencode.rs index 7ef1523..a3ff3ed 100644 --- a/src/collector/opencode.rs +++ b/src/collector/opencode.rs @@ -1,4 +1,4 @@ -use super::process; +use super::{process, context_window_for_model}; use crate::model::{AgentSession, ChildProcess, SessionStatus}; use serde_json::Value; use std::collections::{HashMap, HashSet}; @@ -158,6 +158,13 @@ impl OpenCodeCollector { "-".to_string() }; + let context_window = context_window_for_model(&model, "", 0); + let context_percent = if context_window > 0 { + ((ds.total_input + ds.total_output) as f64 / context_window as f64) * 100.0 + } else { + 0.0 + }; + sessions.push(AgentSession { agent_cli: "opencode", pid: matched_pid, @@ -168,7 +175,7 @@ impl OpenCodeCollector { status, model, effort: String::new(), - context_percent: 0.0, + context_percent, total_input_tokens: ds.total_input, total_output_tokens: ds.total_output, total_cache_read: ds.total_cache_read, @@ -183,7 +190,7 @@ impl OpenCodeCollector { token_history: vec![], context_history: vec![], compaction_count: 0, - context_window: 0, + context_window, subagents: vec![], mem_file_count: 0, mem_line_count: 0,