mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
6ca7311273
Add a context window usage indicator to the web chat UI and a /context slash command that works across all channels. Backend: - Add computeContextUsage() estimating history + system + tool tokens - Attach ContextUsage to outbound messages via the pico WebSocket protocol - Add /context command showing context stats as formatted text - Add EstimateSystemTokens() on ContextBuilder for system prompt estimation Frontend: - Add ContextUsageRing component (SVG ring + hover/tap popover) - Show usage percentage, token counts, and compression threshold - Hover on desktop (150ms leave delay), tap on mobile - "View Details" sends /context with 1s cooldown - i18n support (en/zh) for popover labels Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"github.com/sipeed/picoclaw/pkg/bus"
|
|
)
|
|
|
|
// computeContextUsage estimates current context window consumption for the
|
|
// given agent and session. Includes history, system prompt (with dynamic context,
|
|
// summary, and skills — mirroring BuildMessages composition), and tool definitions.
|
|
// The output reserve (MaxTokens) is not counted as "used" but reduces the
|
|
// effective budget, matching isOverContextBudget's compression trigger:
|
|
//
|
|
// compress when: history + system + tools + maxTokens > contextWindow
|
|
// equivalent to: history + system + tools > contextWindow - maxTokens
|
|
//
|
|
// Returns nil when the agent or session is unavailable.
|
|
func computeContextUsage(agent *AgentInstance, sessionKey string) *bus.ContextUsage {
|
|
if agent == nil || agent.Sessions == nil {
|
|
return nil
|
|
}
|
|
contextWindow := agent.ContextWindow
|
|
if contextWindow <= 0 {
|
|
return nil
|
|
}
|
|
|
|
// History tokens
|
|
history := agent.Sessions.GetHistory(sessionKey)
|
|
historyTokens := 0
|
|
for _, m := range history {
|
|
historyTokens += EstimateMessageTokens(m)
|
|
}
|
|
|
|
// System message tokens: uses EstimateSystemTokens which mirrors
|
|
// the full system message composition in BuildMessages (static prompt,
|
|
// dynamic context, active skills, summary with wrapping prefix).
|
|
systemTokens := 0
|
|
if agent.ContextBuilder != nil {
|
|
summary := agent.Sessions.GetSummary(sessionKey)
|
|
// Pass nil for active skills: skills are only injected when the user
|
|
// explicitly activates them via /use, which is rare. Using nil matches
|
|
// the common case and avoids over-counting all installed skills.
|
|
systemTokens = agent.ContextBuilder.EstimateSystemTokens(summary, nil)
|
|
}
|
|
|
|
// Tool definition tokens
|
|
toolTokens := 0
|
|
if agent.Tools != nil {
|
|
toolTokens = EstimateToolDefsTokens(agent.Tools.ToProviderDefs())
|
|
}
|
|
|
|
// Used = history + system (includes summary) + tools
|
|
usedTokens := historyTokens + systemTokens + toolTokens
|
|
|
|
// Effective budget = contextWindow minus output reserve (maxTokens)
|
|
effectiveWindow := contextWindow - agent.MaxTokens
|
|
if effectiveWindow < 0 {
|
|
effectiveWindow = contextWindow
|
|
}
|
|
|
|
// compressAt = effectiveWindow: aligns with isOverContextBudget's
|
|
// proactive trigger (msgTokens + toolTokens + maxTokens > contextWindow).
|
|
compressAt := effectiveWindow
|
|
|
|
usedPercent := 0
|
|
if compressAt > 0 {
|
|
usedPercent = usedTokens * 100 / compressAt
|
|
}
|
|
if usedPercent > 100 {
|
|
usedPercent = 100
|
|
}
|
|
|
|
return &bus.ContextUsage{
|
|
UsedTokens: usedTokens,
|
|
TotalTokens: contextWindow,
|
|
CompressAtTokens: compressAt,
|
|
UsedPercent: usedPercent,
|
|
}
|
|
}
|