mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(agent): add context usage ring indicator and /context command (#2537)
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>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user