Add a new "Runtimes" sidebar tab to manage local agent runtimes with three main capabilities: runtime status overview, token usage tracking (reading Claude Code and Codex CLI local JSONL logs via daemon), and an interactive connection test that sends a ping through the daemon to verify end-to-end agent CLI connectivity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
68 lines
1.7 KiB
Go
68 lines
1.7 KiB
Go
package usage
|
|
|
|
import (
|
|
"log/slog"
|
|
)
|
|
|
|
// Record represents aggregated token usage for one (date, provider, model) tuple.
|
|
type Record struct {
|
|
Date string `json:"date"` // "2006-01-02"
|
|
Provider string `json:"provider"` // "claude" or "codex"
|
|
Model string `json:"model"`
|
|
InputTokens int64 `json:"input_tokens"`
|
|
OutputTokens int64 `json:"output_tokens"`
|
|
CacheReadTokens int64 `json:"cache_read_tokens"`
|
|
CacheWriteTokens int64 `json:"cache_write_tokens"`
|
|
}
|
|
|
|
// Scanner scans local CLI log files for token usage data.
|
|
type Scanner struct {
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// NewScanner creates a new usage scanner.
|
|
func NewScanner(logger *slog.Logger) *Scanner {
|
|
return &Scanner{logger: logger}
|
|
}
|
|
|
|
// Scan reads local JSONL log files for both Claude Code and Codex CLI,
|
|
// and returns aggregated usage records keyed by (date, provider, model).
|
|
func (s *Scanner) Scan() []Record {
|
|
var records []Record
|
|
|
|
claudeRecords := s.scanClaude()
|
|
records = append(records, claudeRecords...)
|
|
|
|
codexRecords := s.scanCodex()
|
|
records = append(records, codexRecords...)
|
|
|
|
return records
|
|
}
|
|
|
|
// aggregation key for merging records.
|
|
type aggKey struct {
|
|
Date string
|
|
Provider string
|
|
Model string
|
|
}
|
|
|
|
func mergeRecords(records []Record) []Record {
|
|
m := make(map[aggKey]*Record)
|
|
for _, r := range records {
|
|
k := aggKey{Date: r.Date, Provider: r.Provider, Model: r.Model}
|
|
if existing, ok := m[k]; ok {
|
|
existing.InputTokens += r.InputTokens
|
|
existing.OutputTokens += r.OutputTokens
|
|
existing.CacheReadTokens += r.CacheReadTokens
|
|
existing.CacheWriteTokens += r.CacheWriteTokens
|
|
} else {
|
|
copy := r
|
|
m[k] = ©
|
|
}
|
|
}
|
|
result := make([]Record, 0, len(m))
|
|
for _, r := range m {
|
|
result = append(result, *r)
|
|
}
|
|
return result
|
|
}
|