Documentation
¶
Overview ¶
Package ctxmgr provides automatic context window management for Celeste CLI. The package name is ctxmgr (not context) to avoid collision with the stdlib context package. Import path: github.com/whykusanagi/celeste-cli/v2/cmd/celeste/context
Index ¶
- Constants
- Variables
- func CapToolResult(result string, maxBytes int, sessionID, toolCallID, baseDir string) (capped string, wasCapped bool, err error)
- func EstimateTokens(text string) int
- func FormatTokenCount(tokens int) string
- func GetModelLimit(model string) int
- func LoadSpilled(baseDir, id string) (string, error)
- func LookupModelLimit(model string) (int, bool)
- func PruneToolResults(baseDir, keep string, now time.Time) error
- func SnipToolResult(result string, maxBytes int, note string) string
- func ToolResultsBaseDir() (string, error)
- type TokenBudget
- func (tb *TokenBudget) AddTurn(promptTokens, completionTokens int)
- func (tb *TokenBudget) Available() int
- func (tb *TokenBudget) GetUsagePercent() float64
- func (tb *TokenBudget) GetWarningLevel() string
- func (tb *TokenBudget) RecordCompaction(historyTokens int)
- func (tb *TokenBudget) SetHistoryTokens(tokens int)
- func (tb *TokenBudget) ShouldCompactProactive(interval int) bool
- func (tb *TokenBudget) ShouldCompactReactive() bool
- func (tb *TokenBudget) Summary() string
- func (tb *TokenBudget) TotalUsed() int
Constants ¶
const ( // DefaultMaxToolResultBytes is the maximum size in bytes for a single tool // result before it gets capped and spilled to disk: 128 KiB. DefaultMaxToolResultBytes = 128 * 1024 // SpillKeepAge is how long a session's spilled tool results are kept // after its last spill. SpillKeepAge = 30 * 24 * time.Hour )
const LocalDefaultLimit = 8192
LocalDefaultLimit is the fallback window for local endpoints (Ollama, llama.cpp, MLX) with no context_limit set: their window is whatever the server was started with, and small defaults are common (#201).
Variables ¶
var ModelLimits = map[string]int{
"gpt-4.1": 1050000,
"gpt-4.1-mini": 400000,
"gpt-4.1-nano": 400000,
"gpt-5.3-codex": 1050000,
"gpt-5.4": 1050000,
"gpt-5.4-mini": 400000,
"gpt-5.4-nano": 400000,
"gpt-5.4-pro": 1050000,
"o3": 1050000,
"o4-mini": 400000,
"claude-fable-5-1": 1000000,
"claude-fable-5": 1000000,
"claude-opus-5-5": 1000000,
"claude-opus-5": 1000000,
"claude-opus-4-8": 1000000,
"claude-opus-4-7": 1000000,
"claude-opus-4-6": 1000000,
"claude-sonnet-5-5": 1000000,
"claude-sonnet-5": 1000000,
"claude-sonnet-4-6": 1000000,
"claude-haiku-4-5": 200000,
"claude-haiku-4-5-20251001": 200000,
"claude-sonnet-4-5": 200000,
"claude-sonnet-4-5-20250929": 200000,
"claude-opus-4-5": 200000,
"claude-opus-4-5-20251101": 200000,
"claude-opus-4-1": 200000,
"claude-opus-4-1-20250805": 200000,
"grok-build-0.1": 256000,
"grok-4-1-fast": 2000000,
"grok-4-1-fast-reasoning": 2000000,
"grok-4-1-fast-non-reasoning": 2000000,
"grok-4.3": 1000000,
"grok-4.20-0309-reasoning": 1000000,
"grok-4.20-0309-non-reasoning": 1000000,
"grok-4.20-multi-agent-0309": 1000000,
"grok-code-fast-1": 2000000,
"fugu": 1000000,
"fugu-ultra": 1000000,
"fugu-ultra-20260615": 1000000,
"gemini-flash-latest": 1048576,
"gemini-2.0-flash": 1048576,
"openai/gpt-4.1-nano": 1047576,
"venice-uncensored": 32000,
"venice-uncensored-role-play": 128000,
"deepseek-v3.2": 160000,
"qwen3-coder-480b-a35b-instruct": 256000,
"qwen3-coder-480b-a35b-instruct-turbo": 256000,
"qwen3-235b-a22b-thinking-2507": 128000,
"kimi-k2-5": 256000,
"zai-org-glm-4.7": 198000,
"mistral-small-3-2-24b-instruct": 256000,
"llama-3.3-70b": 128000,
"minimax-m25": 198000,
"default": 128000,
}
ModelLimits maps model names to their context window sizes in tokens. Migrated from config/tokens.go.
Functions ¶
func CapToolResult ¶
func CapToolResult(result string, maxBytes int, sessionID, toolCallID, baseDir string) (capped string, wasCapped bool, err error)
CapToolResult checks whether a tool result exceeds maxBytes. If it does, the full result is written to disk at:
{baseDir}/{sessionID}/{toolCallID}.txt
and SnipToolResult's head and tail of it are returned, around a marker that names the file.
If baseDir is empty, ToolResultsBaseDir() is used.
Returns:
- capped: the (possibly truncated) result string to send to the model
- wasCapped: true if the result was truncated
- err: any I/O error from writing the spill file
func EstimateTokens ¶
EstimateTokens approximates token count from text length. Uses the rough heuristic of 4 characters per token.
func FormatTokenCount ¶
FormatTokenCount formats a token count with K/M suffix for display.
func GetModelLimit ¶
GetModelLimit returns the token limit for a model name. Falls back to the "default" entry if the model is not found.
func LoadSpilled ¶
LoadSpilled returns the full tool result CapToolResult spilled under baseDir ("" is ToolResultsBaseDir) for a recall id <sessionID>/<toolCallID>, the id its notice names.
func LookupModelLimit ¶
LookupModelLimit returns the token limit and whether the model was actually found. Callers that validate a user's configured window need the second value: for an unrecognised model the returned limit is a conservative fallback, not knowledge, and treating it as an upper bound would reject a correct setting. Local models are never in the table — their window is whatever the server was started with.
func PruneToolResults ¶
PruneToolResults deletes spilled tool results under baseDir ("" is ToolResultsBaseDir): only spill session directories (spillSessionDir), never the compaction store beside them, whose bodies expire one by one after SpillKeepAge instead (expireCompactStore). Every session directory last changed more than SpillKeepAge ago, then the oldest others idle for spillActiveAge while all of them together are over maxTotalSpillBytes. keep (the session running now) always survives, and so does any session that spilled within spillActiveAge, as another celeste process may still be using it. A missing baseDir is nothing to prune (Aikido 806869375).
func SnipToolResult ¶
SnipToolResult cuts result to at most maxBytes in memory, keeping its head and tail around a "[...snipped N bytes...]" marker, without the spill file CapToolResult writes (2.0 F3). note, if not empty, goes into the marker: callers say there why the middle cannot be recalled and what to do instead. The loop uses it when the spill file cannot be written; sessions and checkpoints use it on tool results loaded from disk. Results at or under maxBytes come back unchanged. Cuts fall on UTF-8 character boundaries. The result is never over maxBytes: a cap too small for the marker gets a plain cut of the head.
func ToolResultsBaseDir ¶
ToolResultsBaseDir returns the base directory for spilled tool results. Default: ~/.celeste/tool-results
Types ¶
type TokenBudget ¶
type TokenBudget struct {
// Capacity
ModelLimit int // Total context window for the model
// Fixed allocations (set once at session start, updated rarely)
SystemPromptTokens int // Tokens consumed by system prompt
ToolDefinitionTokens int // Tokens consumed by tool/function schemas
// Dynamic tracking
HistoryTokens int // Tokens consumed by conversation history (all messages)
LastPromptTokens int // Prompt tokens from last API response
LastCompTokens int // Completion tokens from last API response
// Counters
TurnCount int // Number of user-assistant turn pairs completed
CompactCount int // Number of times compaction has been triggered
// contains filtered or unexported fields
}
TokenBudget tracks token usage across all components of a conversation. It provides fine-grained tracking beyond a simple current/max counter, separating system prompt, tool definitions, conversation history, and per-turn usage to enable intelligent compaction decisions.
func NewTokenBudget ¶
func NewTokenBudget(modelLimit, systemPromptTokens, toolDefTokens int) *TokenBudget
NewTokenBudget creates a TokenBudget for the given model. systemPromptTokens and toolDefTokens represent the fixed token overhead that is sent with every request.
func (*TokenBudget) AddTurn ¶
func (tb *TokenBudget) AddTurn(promptTokens, completionTokens int)
AddTurn records token usage from an API response and increments the turn counter. promptTokens and completionTokens come from the API's usage field. If the API does not return usage, callers should pass estimates.
func (*TokenBudget) Available ¶
func (tb *TokenBudget) Available() int
Available returns tokens remaining before hitting the model limit. This is the space available for the next prompt + completion.
func (*TokenBudget) GetUsagePercent ¶
func (tb *TokenBudget) GetUsagePercent() float64
GetUsagePercent returns usage as a fraction from 0.0 to 1.0.
func (*TokenBudget) GetWarningLevel ¶
func (tb *TokenBudget) GetWarningLevel() string
GetWarningLevel returns a severity string based on current usage. Returns "ok", "warn" (75%), "caution" (85%), or "critical" (95%).
func (*TokenBudget) RecordCompaction ¶
func (tb *TokenBudget) RecordCompaction(historyTokens int)
RecordCompaction records that compaction replaced the history, which now costs about historyTokens. The last API-reported prompt count described the old history, so it is cleared until the next response.
func (*TokenBudget) SetHistoryTokens ¶
func (tb *TokenBudget) SetHistoryTokens(tokens int)
SetHistoryTokens directly sets the history token count. Use this when recalculating after compaction or when API usage data is unavailable.
func (*TokenBudget) ShouldCompactProactive ¶
func (tb *TokenBudget) ShouldCompactProactive(interval int) bool
ShouldCompactProactive returns true when the turn count has reached a multiple of the given interval AND usage is above 50%. This catches slow-growing sessions before they hit the reactive threshold.
func (*TokenBudget) ShouldCompactReactive ¶
func (tb *TokenBudget) ShouldCompactReactive() bool
ShouldCompactReactive returns true when usage has crossed the reactive compaction threshold (80% of model limit). This is checked after every API response.
func (*TokenBudget) Summary ¶
func (tb *TokenBudget) Summary() string
Summary returns a human-readable one-line summary of the budget state.
func (*TokenBudget) TotalUsed ¶
func (tb *TokenBudget) TotalUsed() int
TotalUsed returns the total tokens currently consumed across all components.