feat: add /usage command to show model info and token usage

Adds token usage tracking to AgentInstance using atomic counters
(TotalPromptTokens, TotalCompletionTokens, TotalRequests) that
accumulate after each LLM call. The /usage command displays current
model name, max tokens, temperature, and session token statistics.
Available in all interactive modes like /help.

https://claude.ai/code/session_01PoHBfe7eKmhHvVF12W3xZ2
This commit is contained in:
seagochen 2026-02-24 06:03:19 +00:00
parent a961328127
commit 1ddec1673c
3 changed files with 79 additions and 2 deletions

View file

@ -109,6 +109,7 @@ func agentCmd() {
} else {
fmt.Printf("%s Interactive mode (Ctrl+C to exit)\n", logo)
fmt.Println(" /help - show detailed help")
fmt.Println(" /usage - show model info and token usage")
fmt.Println(" /cmd - switch to command mode")
fmt.Println(" /pico - switch to chat mode")
fmt.Println(" /hipico - AI assistance in command mode")
@ -163,11 +164,15 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
return
}
// /help works in all modes
// /help and /usage work in all modes
if input == "/help" {
printHelp()
continue
}
if input == "/usage" {
printUsage(agentLoop)
continue
}
switch mode {
case modePico:
@ -284,11 +289,15 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
return
}
// /help works in all modes
// /help and /usage work in all modes
if input == "/help" {
printHelp()
continue
}
if input == "/usage" {
printUsage(agentLoop)
continue
}
switch mode {
case modePico:
@ -383,6 +392,7 @@ PicoClaw has three interactive modes:
Commands (available in all modes):
/help Show this help message
/usage Show model info and token usage
exit Exit PicoClaw
quit Exit PicoClaw
Ctrl+C Exit PicoClaw
@ -410,6 +420,35 @@ Examples:
`, logo, logo, logo, logo)
}
// printUsage displays current model information and accumulated token usage.
func printUsage(agentLoop *agent.AgentLoop) {
info := agentLoop.GetUsageInfo()
if info == nil {
fmt.Println("No usage information available.")
return
}
fmt.Printf(`%s Usage
Model: %s
Max tokens: %d
Temperature: %.1f
Token usage (this session):
Prompt tokens: %d
Completion tokens:%d
Total tokens: %d
Requests: %d
`, logo,
info["model"],
info["max_tokens"],
info["temperature"],
info["prompt_tokens"],
info["completion_tokens"],
info["total_tokens"],
info["requests"],
)
}
// executeShellCommand runs a shell command in the current working directory
// and prints the output. It also handles the cd command to change directories.
func executeShellCommand(input string) {

View file

@ -4,6 +4,7 @@ import (
"os"
"path/filepath"
"strings"
"sync/atomic"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/providers"
@ -31,6 +32,21 @@ type AgentInstance struct {
Subagents *config.SubagentsConfig
SkillsFilter []string
Candidates []providers.FallbackCandidate
// Accumulated token usage counters (atomic for concurrent safety)
TotalPromptTokens atomic.Int64
TotalCompletionTokens atomic.Int64
TotalRequests atomic.Int64
}
// AddUsage accumulates token usage from a single LLM response.
func (a *AgentInstance) AddUsage(usage *providers.UsageInfo) {
if usage == nil {
return
}
a.TotalPromptTokens.Add(int64(usage.PromptTokens))
a.TotalCompletionTokens.Add(int64(usage.CompletionTokens))
a.TotalRequests.Add(1)
}
// NewAgentInstance creates an agent instance from config.

View file

@ -594,6 +594,9 @@ func (al *AgentLoop) runLLMIteration(
return "", iteration, fmt.Errorf("LLM call failed after retries: %w", err)
}
// Accumulate token usage
agent.AddUsage(response.Usage)
// Check if no tool calls - we're done
if len(response.ToolCalls) == 0 {
finalContent = response.Content
@ -852,6 +855,25 @@ func (al *AgentLoop) GetStartupInfo() map[string]any {
return info
}
// GetUsageInfo returns accumulated token usage for the default agent.
func (al *AgentLoop) GetUsageInfo() map[string]any {
agent := al.registry.GetDefaultAgent()
if agent == nil {
return nil
}
promptTokens := agent.TotalPromptTokens.Load()
completionTokens := agent.TotalCompletionTokens.Load()
return map[string]any{
"model": agent.Model,
"max_tokens": agent.MaxTokens,
"temperature": agent.Temperature,
"prompt_tokens": promptTokens,
"completion_tokens": completionTokens,
"total_tokens": promptTokens + completionTokens,
"requests": agent.TotalRequests.Load(),
}
}
// formatMessagesForLog formats messages for logging
func formatMessagesForLog(messages []providers.Message) string {
if len(messages) == 0 {