session

package
v0.1.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package session manages the agent session lifecycle including message history, token tracking, and context compaction.

Index

Constants

View Source
const (
	// OutputTokenMax is the maximum output tokens we expect from the model.
	// Matches opencode's SessionPrompt.OUTPUT_TOKEN_MAX.
	OutputTokenMax = 32000

	// PruneMinimum is the minimum tokens to prune (to avoid tiny prunes).
	PruneMinimum = 20_000

	// PruneProtect is the number of tokens worth of tool calls to protect.
	PruneProtect = 40_000

	// CharsPerToken is the estimated number of characters per token.
	// Used for estimating token counts when actual counts aren't available.
	CharsPerToken = 4

	// ImageTokenEstimate is the per-image token budget used during pruning.
	// Mid-of-road: OpenAI charges ~85-170 per tile, Anthropic ~1000/image.
	// Providers bill real image tokens regardless of whether we ship base64
	// or a URL — so we can't count the transport string.
	ImageTokenEstimate = 1500

	// FileTokenEstimate is the per-file token budget used during pruning.
	// Heuristic; non-image files are typically PDFs where token cost tracks
	// page count more than bytes.
	FileTokenEstimate = 1000
)
View Source
const CompactionContinuationPrompt = "" /* 206-byte string literal not displayed */

CompactionContinuationPrompt tells the model how to interpret the summary and resume the conversation.

View Source
const CompactionPrompt = `` /* 309-byte string literal not displayed */

CompactionPrompt is the default prompt for context compaction. This is appended as a user message to ask for a summary.

View Source
const CompactionTriggerPrompt = "What did we do so far?"

CompactionTriggerPrompt is added by opencode when a compaction part is converted to model messages. This matches MessageV2.toModelMessages() behavior for compaction parts.

View Source
const DefaultCompactionSystemPrompt = "" /* 603-byte string literal not displayed */

DefaultCompactionSystemPrompt is the system prompt used for the compaction agent. This matches opencode's agent/prompt/compaction.txt exactly (including trailing space on line 3 and trailing newline).

View Source
const (
	// DoomLoopThreshold is the number of identical tool calls before triggering detection.
	DoomLoopThreshold = 3
)

Variables

This section is empty.

Functions

func AskDoomLoopPermission

func AskDoomLoopPermission(ctx context.Context, sessionID, toolName string) error

AskDoomLoopPermission asks the user if they want to continue despite the doom loop. Returns nil if user approves, error if rejected. Uses the context-scoped permission manager if available, otherwise the global.

func DefaultPrunedMessage

func DefaultPrunedMessage(info PrunedInfo) string

DefaultPrunedMessage returns a generic replacement string for pruned content.

func EstimateMessagesTokens

func EstimateMessagesTokens(msgs []Message) int

EstimateMessagesTokens returns a rough token estimate across a slice of messages. Used to compute post-compaction context size for UI display.

func EstimateTokens

func EstimateTokens(s string) int

EstimateTokens estimates the token count for a string using chars/4. This matches opencode's Token.estimate() function.

func MessageToGoAI

func MessageToGoAI(msg Message) []goai.Message

MessageToGoAI preserves multipart content on every role, including multiple tool results in a single message. Compacted parts are omitted from replay.

func MessagesToGoAI

func MessagesToGoAI(msgs []Message) []goai.Message

MessagesToGoAI converts session messages to goai format.

Types

type CompactOptions

type CompactOptions struct {
	// ProviderOptions are provider-specific options (store, promptCacheKey, etc.)
	ProviderOptions map[string]any

	// MaxOutputTokens limits the compaction response length.
	MaxOutputTokens int

	// TransformMessages runs on the complete request, including the compaction
	// system prompt. Callers use it to enforce outbound redaction.
	TransformMessages func([]goai.Message) []goai.Message
}

CompactOptions contains options for compaction requests. This matches opencode's behavior of passing the same provider options to compaction.

type CompactionAgent

type CompactionAgent interface {
	// SystemPrompt returns the system prompt for the compaction agent.
	SystemPrompt() string

	// Model returns the model ID to use for compaction, or empty to use session's model.
	Model() string
}

CompactionAgent defines the interface for a compaction agent.

type CompactionConfig

type CompactionConfig struct {
	// Auto enables automatic compaction when context overflows. Default: true.
	Auto bool

	// Prune enables pruning of old tool outputs. Default: true.
	Prune bool

	// Agent is the agent to use for compaction. If nil, uses the model directly.
	// When set, the agent's system prompt is used for the compaction request.
	Agent CompactionAgent

	// PrunedMessage generates replacement text when content is pruned or excluded.
	// Called with metadata about the removed content. If nil, a default message is used.
	PrunedMessage func(info PrunedInfo) string
}

CompactionConfig controls compaction behavior.

func DefaultCompactionConfig

func DefaultCompactionConfig() CompactionConfig

DefaultCompactionConfig returns the default compaction configuration.

type DoomLoopDetector

type DoomLoopDetector struct {
	// contains filtered or unexported fields
}

DoomLoopDetector tracks recent tool calls to detect repetitive patterns.

func NewDoomLoopDetector

func NewDoomLoopDetector() *DoomLoopDetector

NewDoomLoopDetector creates a new doom loop detector.

func (*DoomLoopDetector) LastToolName

func (d *DoomLoopDetector) LastToolName() string

LastToolName returns the name of the last recorded tool.

func (*DoomLoopDetector) RecordCall

func (d *DoomLoopDetector) RecordCall(name string, input json.RawMessage) bool

RecordCall records a tool call and returns true if a doom loop is detected.

func (*DoomLoopDetector) Reset

func (d *DoomLoopDetector) Reset()

Reset clears the recorded calls (call after user confirms to continue).

type FilePart

type FilePart struct {
	DataType  string         `json:"dataType,omitempty"`
	Reference map[string]any `json:"reference,omitempty"`
	Data      string         `json:"data"`               // base64-encoded content, text, or URL
	MimeType  string         `json:"mimeType"`           // e.g., "application/pdf", "image/png"
	Filename  string         `json:"filename,omitempty"` // optional filename
	// Source identifies where this file came from (e.g. a file key, URL, or ID).
	// Session-level metadata only — not passed to goai or LLM providers.
	Source string `json:"source,omitempty"`
}

FilePart represents an attachment, including images. DataType is data, url, text, or reference. Reference carries provider handles; Data carries the other payloads. MimeType determines rendering.

type MemoryStore

type MemoryStore struct{}

MemoryStore is a no-op store for CLI mode and testing. Messages live only in the session's Messages slice; nothing is persisted.

func (MemoryStore) Append

func (MemoryStore) Compact

func (MemoryStore) Compact(context.Context, []Message, int) error

func (MemoryStore) Load

type Message

type Message struct {
	ProviderOptions map[string]any `json:"providerOptions,omitempty"`
	ID              string         `json:"id"`
	Role            string         `json:"role"` // "system", "user", "assistant", "tool"
	Content         string         `json:"content,omitempty"`
	Parts           []Part         `json:"parts,omitempty"`
	ParentID        string         `json:"parentId,omitempty"`  // For assistant messages, links to user message
	Summary         bool           `json:"summary,omitempty"`   // True if this is a compaction summary
	Tokens          Tokens         `json:"tokens,omitempty"`    // Token usage for this message
	Compacted       bool           `json:"compacted,omitempty"` // True if tool outputs have been pruned
}

Message represents a message in the session history.

func FromGoAIMessage

func FromGoAIMessage(m goai.Message) Message

FromGoAIMessage preserves message and part order, provider metadata, and discriminated file and tool output types for persistence and replay.

func FromGoAIMessages

func FromGoAIMessages(msgs []goai.Message) []Message

FromGoAIMessages converts goai messages to session messages.

type ModelLimits

type ModelLimits struct {
	Context int // Total context window size
	Input   int // Independent max input tokens
	Output  int // Max output tokens
}

ModelLimits records model capability maxima, not per-call token allocations. Zero means the corresponding limit is unknown.

func (ModelLimits) Validate added in v0.1.11

func (l ModelLimits) Validate(auto bool) error

Validate rejects invalid budgets and requires a usable limit for auto compaction.

type Part

type Part struct {
	ID               string                            `json:"id"`
	Type             string                            `json:"type"` // "text", "tool", "reasoning", "compaction", "file"
	Text             string                            `json:"text,omitempty"`
	ProviderOptions  map[string]any                    `json:"providerOptions,omitempty"`
	Tool             *ToolPart                         `json:"tool,omitempty"`
	File             *FilePart                         `json:"file,omitempty"`
	Compacted        bool                              `json:"compacted,omitempty"` // True if output has been pruned
	ApprovalRequest  *message.ToolApprovalRequestPart  `json:"approvalRequest,omitempty"`
	ApprovalResponse *message.ToolApprovalResponsePart `json:"approvalResponse,omitempty"`
}

Part represents a part of a message (text, tool call, file, etc.)

type PrunedInfo

type PrunedInfo struct {
	Type     string // "tool_output", "image", "file"
	MimeType string // for images/files
	Filename string // FilePart.Filename
	Source   string // FilePart.Source (e.g. S3 key)
}

PrunedInfo describes content that was pruned or excluded from context.

type Session

type Session struct {
	ID        string
	AgentName string
	ModelID   string

	// Message history
	Messages []Message

	// Token tracking
	Tokens Tokens

	// Model limits for overflow detection
	ModelLimits ModelLimits

	// Compaction configuration
	CompactionConfig CompactionConfig
	// contains filtered or unexported fields
}

Session represents an active agent session.

func New

func New(id, agentName, modelID string, limits ModelLimits) *Session

New creates a new session.

func NewWithOptions

func NewWithOptions(opts SessionOptions) *Session

NewWithOptions creates a new session with options.

func (*Session) AddMessage

func (s *Session) AddMessage(msg Message)

AddMessage adds a message to the session history.

func (*Session) Cancel

func (s *Session) Cancel()

Cancel cancels the session.

func (*Session) Compact

func (s *Session) Compact(ctx context.Context, model stream.Model, conversationMessages []goai.Message, opts *CompactOptions) (string, error)

Compact performs context compaction by asking the model to summarize. conversationMessages should be the current conversation history (from the runner). Returns the summary text that should be used to continue the conversation.

func (*Session) CompactAndContinue

func (s *Session) CompactAndContinue(ctx context.Context, model stream.Model, conversationMessages []goai.Message, opts *CompactOptions) error

CompactAndContinue performs compaction and adds a continuation instruction. This is used for automatic compaction during the thinking loop. conversationMessages should be the current conversation history (from the runner).

func (*Session) Context

func (s *Session) Context() context.Context

Context returns the session context.

func (*Session) FilterCompacted

func (s *Session) FilterCompacted() []Message

FilterCompacted returns messages up to and including the last compaction point. This prevents showing compacted context twice when rebuilding messages.

func (*Session) GetMessages

func (s *Session) GetMessages() []Message

GetMessages returns all messages in the session.

func (*Session) IsOverflow

func (s *Session) IsOverflow() bool

IsOverflow checks if the session has exceeded its context limit.

func (*Session) Prune

func (s *Session) Prune() int

Prune removes old tool outputs to reduce context size. Goes backwards through messages, keeping recent tool outputs and clearing older ones past the PruneProtect threshold.

func (*Session) PublishUpdate

func (s *Session) PublishUpdate()

PublishUpdate publishes a session update event on the session's bus.

func (*Session) ToGoAIMessages

func (s *Session) ToGoAIMessages() []goai.Message

ToGoAIMessages converts session messages to goai.Message format.

func (*Session) UpdateTokens

func (s *Session) UpdateTokens(usage stream.Usage)

UpdateTokens updates the session token counts from a step result. Note: We replace (not accumulate) because each API response's input tokens already includes ALL messages in the conversation, not just the new ones.

type SessionOptions

type SessionOptions struct {
	ID               string
	AgentName        string
	ModelID          string
	Limits           ModelLimits
	CompactionConfig *CompactionConfig // nil = use defaults
	Bus              *bus.Bus
}

SessionOptions contains options for creating a new session.

type SessionStore

type SessionStore interface {
	// Load returns the messages that should form the LLM context for the next
	// turn. Implementations may hide pre-checkpoint history here — Sol only
	// sees what the store decides to return. Returns empty slice (not error)
	// if the conversation has no post-checkpoint messages yet.
	Load(ctx context.Context) ([]Message, error)

	// Append persists new messages from the current turn.
	// Called after each step with the messages produced during that step.
	Append(ctx context.Context, msgs []Message) error

	// Compact atomically records a compaction: the summary messages become
	// the head of the new context window, and the store advances its
	// checkpoint so that subsequent Load calls return only `summary` + any
	// later Appends. Pre-checkpoint messages are not deleted — the store
	// decides how to keep them available for UI / audit.
	// tokensFreed is the delta between pre- and post-compaction input tokens,
	// recorded so the UI can show how much context was freed.
	Compact(ctx context.Context, summary []Message, tokensFreed int) error
}

SessionStore provides pluggable persistence for conversation messages. Each store instance is pre-scoped to a single conversation by the caller. Sol doesn't know about conversation IDs, tenants, or routing.

type SimpleCompactionAgent

type SimpleCompactionAgent struct {
	// contains filtered or unexported fields
}

SimpleCompactionAgent is a simple implementation of CompactionAgent.

func NewCompactionAgent

func NewCompactionAgent(systemPrompt, model string) *SimpleCompactionAgent

NewCompactionAgent creates a new compaction agent with the given system prompt and model.

func (*SimpleCompactionAgent) Model

func (a *SimpleCompactionAgent) Model() string

Model returns the model ID to use for compaction.

func (*SimpleCompactionAgent) SystemPrompt

func (a *SimpleCompactionAgent) SystemPrompt() string

SystemPrompt returns the system prompt for the compaction agent.

type Tokens

type Tokens struct {
	Input     int `json:"input"`
	Output    int `json:"output"`
	Reasoning int `json:"reasoning"`
	Cache     struct {
		Read  int `json:"read"`
		Write int `json:"write"`
	} `json:"cache"`
}

Tokens tracks token usage across the session.

type ToolCallRecord

type ToolCallRecord struct {
	Name  string `json:"name"`
	Input string `json:"input"` // JSON string for comparison
}

ToolCallRecord represents a tool call for doom loop detection.

type ToolPart

type ToolPart struct {
	// OutputType preserves the discriminated result type. Empty uses Outcome
	// and Output for persisted records without a discriminant.
	OutputType            string         `json:"outputType,omitempty"`
	OutputProviderOptions map[string]any `json:"outputProviderOptions,omitempty"`
	ProviderExecuted      bool           `json:"providerExecuted,omitempty"`
	// Result distinguishes assistant-side provider results from calls.
	Result bool   `json:"result,omitempty"`
	CallID string `json:"callId"`
	Name   string `json:"name"`
	Input  string `json:"input,omitempty"`
	Output string `json:"output,omitempty"`
	Status string `json:"status"` // lifecycle: "pending", "running", "completed"
	// Outcome is the structured tool-result outcome, decoupled from the
	// lifecycle Status so compaction (which prunes Status=="completed")
	// keeps working: "" | "success" | "error" | "denied". It survives the
	// goai↔session round-trip so the persisted/UI tool status is correct.
	Outcome   string `json:"outcome,omitempty"`
	Compacted bool   `json:"compacted,omitempty"` // True if output has been pruned
}

ToolPart represents a tool call and its result.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL