Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions adk-code/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ func (a *Application) initializeREPL() error {
ModelRegistry: a.model.Registry,
SelectedModel: a.model.Selected,
MCPComponents: a.mcp,
AppConfig: a.config,
SessionManager: a.session,
})
if err != nil {
return fmt.Errorf("failed to create REPL: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions adk-code/internal/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ func HandleCLICommands(ctx context.Context, args []string, dbPath string) bool {
// HandleBuiltinCommand handles built-in REPL commands like /help, /tools, etc.
// Returns true if a command was handled, false if input should be sent to agent
// Note: /exit and /quit are handled separately in repl.go to break the loop
func HandleBuiltinCommand(ctx context.Context, input string, renderer *display.Renderer, sessionTokens *tracking.SessionTokens, modelRegistry *models.Registry, currentModel models.Config, mcpManager interface{}) bool {
func HandleBuiltinCommand(ctx context.Context, input string, renderer *display.Renderer, sessionTokens *tracking.SessionTokens, modelRegistry *models.Registry, currentModel models.Config, mcpManager interface{}, appConfig interface{}) bool {
var mgr *mcp.Manager
if mcpManager != nil {
mgr, _ = mcpManager.(*mcp.Manager)
}
return clicommands.HandleBuiltinCommand(ctx, input, renderer, sessionTokens, modelRegistry, currentModel, mgr)
return clicommands.HandleBuiltinCommand(ctx, input, renderer, sessionTokens, modelRegistry, currentModel, mgr, appConfig)
}
32 changes: 32 additions & 0 deletions adk-code/internal/cli/commands/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,43 @@ func (c *SetModelCommand) Execute(ctx context.Context, args []string) error {
return nil
}

// CompactionCommand implements REPLCommand for /compaction
type CompactionCommand struct {
renderer *display.Renderer
config interface{} // Will accept *config.Config
}

// NewCompactionCommand creates a new compaction command
func NewCompactionCommand(renderer *display.Renderer, appConfig interface{}) *CompactionCommand {
return &CompactionCommand{
renderer: renderer,
config: appConfig,
}
}

// Name returns the command name
func (c *CompactionCommand) Name() string {
return "compaction"
}

// Description returns command help text
func (c *CompactionCommand) Description() string {
return "Display session history compaction configuration"
}

// Execute runs the compaction command
func (c *CompactionCommand) Execute(ctx context.Context, args []string) error {
handleCompactionCommand(c.renderer, c.config)
return nil
}

// NewDefaultCommandRegistry creates a command registry with all standard REPL commands
func NewDefaultCommandRegistry(
renderer *display.Renderer,
modelRegistry *models.Registry,
currentModel models.Config,
sessionTokens *tracking.SessionTokens,
appConfig interface{},
) *CommandRegistry {
registry := NewCommandRegistry()

Expand All @@ -334,6 +365,7 @@ func NewDefaultCommandRegistry(
registry.Register(NewProvidersCommand(renderer, modelRegistry))
registry.Register(NewTokensCommand(sessionTokens))
registry.Register(NewSetModelCommand(renderer, modelRegistry))
registry.Register(NewCompactionCommand(renderer, appConfig))

return registry
}
61 changes: 60 additions & 1 deletion adk-code/internal/cli/commands/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

"adk-code/internal/config"
"adk-code/internal/display"
"adk-code/internal/mcp"
agentprompts "adk-code/internal/prompts"
Expand All @@ -20,7 +21,7 @@ import (
// HandleBuiltinCommand handles built-in REPL commands like /help, /tools, etc.
// Returns true if a command was handled, false if input should be sent to agent
// Note: /exit and /quit are handled separately in repl.go to break the loop
func HandleBuiltinCommand(ctx context.Context, input string, renderer *display.Renderer, sessionTokens *tracking.SessionTokens, modelRegistry *models.Registry, currentModel models.Config, mcpManager *mcp.Manager) bool {
func HandleBuiltinCommand(ctx context.Context, input string, renderer *display.Renderer, sessionTokens *tracking.SessionTokens, modelRegistry *models.Registry, currentModel models.Config, mcpManager *mcp.Manager, appConfig interface{}) bool {
switch input {
case "/prompt":
handlePromptCommand(renderer)
Expand Down Expand Up @@ -50,6 +51,10 @@ func HandleBuiltinCommand(ctx context.Context, input string, renderer *display.R
handleTokensCommand(sessionTokens)
return true

case "/compaction":
handleCompactionCommand(renderer, appConfig)
return true

case "/agents":
handleAgentsCommand(renderer)
return true
Expand Down Expand Up @@ -139,6 +144,60 @@ func handleTokensCommand(sessionTokens *tracking.SessionTokens) {
fmt.Print(tracking.FormatSessionSummary(summary))
}

// handleCompactionCommand displays the session history compaction configuration
func handleCompactionCommand(renderer *display.Renderer, appConfig interface{}) {
// Type assert to get the actual config
cfg, ok := appConfig.(*config.Config)
if !ok {
fmt.Println(renderer.Red("Error: Unable to access configuration"))
return
}

fmt.Println()
fmt.Println(renderer.Bold("Session History Compaction Configuration:"))
fmt.Println()

// Display status
if cfg.CompactionEnabled {
fmt.Println(renderer.Green("✓ Status: ") + renderer.Cyan("ENABLED"))
} else {
fmt.Println(renderer.Yellow("⚠ Status: ") + renderer.Dim("DISABLED"))
}
fmt.Println()

// Display current settings
fmt.Println(renderer.Bold("Current Settings:"))
fmt.Printf(" %s Invocation Threshold: %d invocations\n", renderer.Dim("•"), cfg.CompactionThreshold)
fmt.Printf(" %s Overlap Window: %d invocations\n", renderer.Dim("•"), cfg.CompactionOverlap)
fmt.Printf(" %s Token Threshold: %d tokens\n", renderer.Dim("•"), cfg.CompactionTokens)
fmt.Printf(" %s Safety Ratio: %.1f%%\n", renderer.Dim("•"), cfg.CompactionSafety*100)
fmt.Println()

// Display what this means
fmt.Println(renderer.Bold("What This Means:"))
fmt.Println()
fmt.Println(renderer.Dim(" • Invocation Threshold: Compaction triggers after this many agent interactions"))
fmt.Println(renderer.Dim(" • Overlap Window: How many recent invocations to retain in context"))
fmt.Println(renderer.Dim(" • Token Threshold: Summarization occurs when session exceeds this token limit"))
fmt.Println(renderer.Dim(" • Safety Ratio: Buffer below the token limit to prevent exceeding it"))
fmt.Println()

// Display usage information
fmt.Println(renderer.Bold("Enable Compaction:"))
fmt.Println(renderer.Dim(" To enable compaction, start adk-code with the --compaction flag:"))
fmt.Println()
fmt.Println(" " + renderer.Cyan("adk-code --compaction"))
fmt.Println()
fmt.Println(renderer.Dim(" Or customize settings:"))
fmt.Println()
fmt.Println(" " + renderer.Cyan("adk-code --compaction \\"))
fmt.Println(" " + renderer.Cyan("--compaction-threshold 5 \\"))
fmt.Println(" " + renderer.Cyan("--compaction-overlap 2 \\"))
fmt.Println(" " + renderer.Cyan("--compaction-tokens 700000 \\"))
fmt.Println(" " + renderer.Cyan("--compaction-safety 0.7"))
fmt.Println()
}

// handleMCPCommand handles /mcp commands and subcommands
func handleMCPCommand(input string, renderer *display.Renderer, mcpManager *mcp.Manager) {
// Handle case where MCP is disabled or not available
Expand Down
8 changes: 8 additions & 0 deletions adk-code/internal/cli/commands/repl_builders.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func buildHelpMessageLines(renderer *display.Renderer) []string {
lines = append(lines, " • "+renderer.Bold("/run-agent <name>")+" - Show agent details or execute agent (preview)")
lines = append(lines, " • "+renderer.Bold("/prompt")+" - Display the system prompt")
lines = append(lines, " • "+renderer.Bold("/tokens")+" - Show token usage statistics")
lines = append(lines, " • "+renderer.Bold("/compaction")+" - Show session history compaction configuration")
lines = append(lines, " • "+renderer.Bold("/mcp")+" - Manage MCP servers (list, status, tools)")
lines = append(lines, " • "+renderer.Bold("/exit")+" - Exit the agent")
lines = append(lines, "")
Expand All @@ -57,6 +58,13 @@ func buildHelpMessageLines(renderer *display.Renderer) []string {
lines = append(lines, " Thinking helps with debugging and transparency at a small token cost")
lines = append(lines, "")

lines = append(lines, renderer.Bold("📦 Session History Compaction:"))
lines = append(lines, " Automatically summarize old conversation history to save tokens:")
lines = append(lines, " • "+renderer.Dim("./code-agent --compaction")+" (enable with defaults)")
lines = append(lines, " • "+renderer.Dim("./code-agent --compaction --compaction-threshold 5")+" (customize)")
lines = append(lines, " Use "+renderer.Cyan("'/compaction'")+" command in REPL to see current settings")
lines = append(lines, "")

lines = append(lines, renderer.Bold("📚 Session Management (CLI commands):"))
lines = append(lines, " • "+renderer.Bold("./code-agent new-session <name>")+" - Create a new session")
lines = append(lines, " • "+renderer.Bold("./code-agent list-sessions")+" - List all sessions")
Expand Down
47 changes: 33 additions & 14 deletions adk-code/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ type Config struct {
// MCP configuration
MCPConfigPath string
MCPConfig *MCPConfig

// Compaction configuration
CompactionEnabled bool
CompactionThreshold int // Invocation threshold for triggering compaction
CompactionOverlap int // Number of invocations to retain in overlap
CompactionTokens int // Token threshold for triggering compaction
CompactionSafety float64 // Safety ratio for token limits (0.0-1.0)
}

// LoadFromEnv loads configuration from environment and CLI flags
Expand Down Expand Up @@ -77,6 +84,13 @@ func LoadFromEnv() (Config, []string) {
// MCP configuration flags
mcpConfigPath := flag.String("mcp-config", "", "Path to MCP config file (optional)")

// Compaction configuration flags
compactionEnabled := flag.Bool("compaction", false, "Enable session history compaction (optional, default: false)")
compactionThreshold := flag.Int("compaction-threshold", 5, "Number of invocations before triggering compaction (default: 5)")
compactionOverlap := flag.Int("compaction-overlap", 2, "Number of invocations to retain in overlap window (default: 2)")
compactionTokens := flag.Int("compaction-tokens", 700000, "Token threshold for triggering compaction (default: 700000)")
compactionSafety := flag.Float64("compaction-safety", 0.7, "Safety ratio for token limits 0.0-1.0 (default: 0.7)")

flag.Parse()

// Use provided flags or fall back to environment
Expand Down Expand Up @@ -124,20 +138,25 @@ func LoadFromEnv() (Config, []string) {
}

return Config{
OutputFormat: *outputFormat,
TypewriterEnabled: *typewriterEnabled,
SessionName: *sessionName,
DBPath: *dbPath,
WorkingDirectory: *workingDirectory,
Backend: selectedBackend,
APIKey: apiKeyValue,
VertexAIProject: projectValue,
VertexAILocation: locationValue,
Model: *model,
EnableThinking: *enableThinking,
ThinkingBudget: int32(*thinkingBudget),
MCPConfigPath: *mcpConfigPath,
MCPConfig: mcpConfig,
OutputFormat: *outputFormat,
TypewriterEnabled: *typewriterEnabled,
SessionName: *sessionName,
DBPath: *dbPath,
WorkingDirectory: *workingDirectory,
Backend: selectedBackend,
APIKey: apiKeyValue,
VertexAIProject: projectValue,
VertexAILocation: locationValue,
Model: *model,
EnableThinking: *enableThinking,
ThinkingBudget: int32(*thinkingBudget),
MCPConfigPath: *mcpConfigPath,
MCPConfig: mcpConfig,
CompactionEnabled: *compactionEnabled,
CompactionThreshold: *compactionThreshold,
CompactionOverlap: *compactionOverlap,
CompactionTokens: *compactionTokens,
CompactionSafety: *compactionSafety,
}, flag.Args()
}

Expand Down
29 changes: 29 additions & 0 deletions adk-code/internal/display/events/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"adk-code/internal/display/streaming"
"adk-code/internal/display/tools"
"adk-code/internal/grounding"
"adk-code/internal/session/compaction"
"adk-code/internal/tracking"

"google.golang.org/adk/session"
Expand Down Expand Up @@ -55,6 +56,34 @@ func PrintEventEnhanced(renderer *Renderer, streamDisplay *StreamingDisplay,
return
}

// Check if this is a compaction event and display feedback
if compaction.IsCompactionEvent(event) {
spinner.Stop()
metadata, err := compaction.GetCompactionMetadata(event)
if err == nil {
// Display compaction notification
fmt.Println()
fmt.Println(renderer.Cyan("📦 Session History Compaction:"))
fmt.Printf(" %s Compacted %d events into 1 summary\n", renderer.Dim("•"), metadata.EventCount)
if metadata.CompactedTokens == 0 {
fmt.Printf(" %s Token reduction: %d → %d tokens (N/A compression)\n",
renderer.Dim("•"),
metadata.OriginalTokens,
metadata.CompactedTokens)
} else {
fmt.Printf(" %s Token reduction: %d → %d tokens (%.1f%% compression)\n",
renderer.Dim("•"),
metadata.OriginalTokens,
metadata.CompactedTokens,
metadata.CompressionRatio)
}
fmt.Printf(" %s Session context optimized for better performance\n", renderer.Dim("•"))
fmt.Println()
}
// Don't process the compaction event further
return
}

// Record token metrics if available and update spinner with metrics
if event.UsageMetadata != nil {
sessionTokens.RecordMetrics(event.UsageMetadata, requestID)
Expand Down
8 changes: 6 additions & 2 deletions adk-code/internal/orchestration/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (o *Orchestrator) WithSession() *Orchestrator {
return o
}

// Session requires agent and display components
// Session requires agent, display, and model components
if o.agentComponent == nil {
o.err = fmt.Errorf("session requires agent component; call WithAgent() first")
return o
Expand All @@ -79,8 +79,12 @@ func (o *Orchestrator) WithSession() *Orchestrator {
o.err = fmt.Errorf("session requires display component; call WithDisplay() first")
return o
}
if o.modelComponents == nil {
o.err = fmt.Errorf("session requires model component; call WithModel() first")
return o
}

o.sessionComponents, o.err = InitializeSessionComponents(o.ctx, o.cfg, o.agentComponent, o.displayComponents.BannerRenderer)
o.sessionComponents, o.err = InitializeSessionComponents(o.ctx, o.cfg, o.agentComponent, o.displayComponents.BannerRenderer, o.modelComponents.LLM)
return o
}

Expand Down
9 changes: 6 additions & 3 deletions adk-code/internal/orchestration/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"adk-code/internal/display"
"adk-code/internal/mcp"
"adk-code/internal/session"
"adk-code/internal/session/compaction"
"adk-code/internal/tracking"
"adk-code/pkg/models"
)
Expand All @@ -28,9 +29,11 @@ type ModelComponents struct {

// SessionComponents groups all session-related fields
type SessionComponents struct {
Manager *session.SessionManager
Runner *runner.Runner
Tokens *tracking.SessionTokens
Manager *session.SessionManager
Runner *runner.Runner
Tokens *tracking.SessionTokens
Coordinator *compaction.Coordinator
CompactionCfg *compaction.Config
}

// MCPComponents groups MCP-related fields
Expand Down
Loading