Context Window Warning (#152)
* context window warning & compact command * auto compact * fix permissions * update readme * fix 3.5 context window * small update * remove unused interface * remove unused msg
This commit is contained in:
parent
9345830c8a
commit
90084ce43d
12 changed files with 537 additions and 98 deletions
|
|
@ -10,14 +10,17 @@ import (
|
|||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/opencode-ai/opencode/internal/app"
|
||||
"github.com/opencode-ai/opencode/internal/config"
|
||||
"github.com/opencode-ai/opencode/internal/llm/agent"
|
||||
"github.com/opencode-ai/opencode/internal/logging"
|
||||
"github.com/opencode-ai/opencode/internal/permission"
|
||||
"github.com/opencode-ai/opencode/internal/pubsub"
|
||||
"github.com/opencode-ai/opencode/internal/session"
|
||||
"github.com/opencode-ai/opencode/internal/tui/components/chat"
|
||||
"github.com/opencode-ai/opencode/internal/tui/components/core"
|
||||
"github.com/opencode-ai/opencode/internal/tui/components/dialog"
|
||||
"github.com/opencode-ai/opencode/internal/tui/layout"
|
||||
"github.com/opencode-ai/opencode/internal/tui/page"
|
||||
"github.com/opencode-ai/opencode/internal/tui/theme"
|
||||
"github.com/opencode-ai/opencode/internal/tui/util"
|
||||
)
|
||||
|
||||
|
|
@ -32,6 +35,8 @@ type keyMap struct {
|
|||
SwitchTheme key.Binding
|
||||
}
|
||||
|
||||
type startCompactSessionMsg struct{}
|
||||
|
||||
const (
|
||||
quitKey = "q"
|
||||
)
|
||||
|
|
@ -91,13 +96,14 @@ var logsKeyReturnKey = key.NewBinding(
|
|||
)
|
||||
|
||||
type appModel struct {
|
||||
width, height int
|
||||
currentPage page.PageID
|
||||
previousPage page.PageID
|
||||
pages map[page.PageID]tea.Model
|
||||
loadedPages map[page.PageID]bool
|
||||
status core.StatusCmp
|
||||
app *app.App
|
||||
width, height int
|
||||
currentPage page.PageID
|
||||
previousPage page.PageID
|
||||
pages map[page.PageID]tea.Model
|
||||
loadedPages map[page.PageID]bool
|
||||
status core.StatusCmp
|
||||
app *app.App
|
||||
selectedSession session.Session
|
||||
|
||||
showPermissions bool
|
||||
permissions dialog.PermissionDialogCmp
|
||||
|
|
@ -126,9 +132,12 @@ type appModel struct {
|
|||
|
||||
showThemeDialog bool
|
||||
themeDialog dialog.ThemeDialog
|
||||
|
||||
|
||||
showArgumentsDialog bool
|
||||
argumentsDialog dialog.ArgumentsDialogCmp
|
||||
|
||||
isCompacting bool
|
||||
compactingMessage string
|
||||
}
|
||||
|
||||
func (a appModel) Init() tea.Cmd {
|
||||
|
|
@ -151,6 +160,7 @@ func (a appModel) Init() tea.Cmd {
|
|||
cmd = a.initDialog.Init()
|
||||
cmds = append(cmds, cmd)
|
||||
cmd = a.filepicker.Init()
|
||||
cmds = append(cmds, cmd)
|
||||
cmd = a.themeDialog.Init()
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
|
|
@ -203,7 +213,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
cmds = append(cmds, filepickerCmd)
|
||||
|
||||
a.initDialog.SetSize(msg.Width, msg.Height)
|
||||
|
||||
|
||||
if a.showArgumentsDialog {
|
||||
a.argumentsDialog.SetSize(msg.Width, msg.Height)
|
||||
args, argsCmd := a.argumentsDialog.Update(msg)
|
||||
|
|
@ -293,6 +303,70 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
a.showCommandDialog = false
|
||||
return a, nil
|
||||
|
||||
case startCompactSessionMsg:
|
||||
// Start compacting the current session
|
||||
a.isCompacting = true
|
||||
a.compactingMessage = "Starting summarization..."
|
||||
|
||||
if a.selectedSession.ID == "" {
|
||||
a.isCompacting = false
|
||||
return a, util.ReportWarn("No active session to summarize")
|
||||
}
|
||||
|
||||
// Start the summarization process
|
||||
return a, func() tea.Msg {
|
||||
ctx := context.Background()
|
||||
a.app.CoderAgent.Summarize(ctx, a.selectedSession.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
case pubsub.Event[agent.AgentEvent]:
|
||||
payload := msg.Payload
|
||||
if payload.Error != nil {
|
||||
a.isCompacting = false
|
||||
return a, util.ReportError(payload.Error)
|
||||
}
|
||||
|
||||
a.compactingMessage = payload.Progress
|
||||
|
||||
if payload.Done && payload.Type == agent.AgentEventTypeSummarize {
|
||||
a.isCompacting = false
|
||||
|
||||
if payload.SessionID != "" {
|
||||
// Switch to the new session
|
||||
return a, func() tea.Msg {
|
||||
sessions, err := a.app.Sessions.List(context.Background())
|
||||
if err != nil {
|
||||
return util.InfoMsg{
|
||||
Type: util.InfoTypeError,
|
||||
Msg: "Failed to list sessions: " + err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range sessions {
|
||||
if s.ID == payload.SessionID {
|
||||
return dialog.SessionSelectedMsg{Session: s}
|
||||
}
|
||||
}
|
||||
|
||||
return util.InfoMsg{
|
||||
Type: util.InfoTypeError,
|
||||
Msg: "Failed to find new session",
|
||||
}
|
||||
}
|
||||
}
|
||||
return a, util.ReportInfo("Session summarization complete")
|
||||
} else if payload.Done && payload.Type == agent.AgentEventTypeResponse && a.selectedSession.ID != "" {
|
||||
model := a.app.CoderAgent.Model()
|
||||
contextWindow := model.ContextWindow
|
||||
tokens := a.selectedSession.CompletionTokens + a.selectedSession.PromptTokens
|
||||
if (tokens >= int64(float64(contextWindow)*0.95)) && config.Get().AutoCompact {
|
||||
return a, util.CmdHandler(startCompactSessionMsg{})
|
||||
}
|
||||
}
|
||||
// Continue listening for events
|
||||
return a, nil
|
||||
|
||||
case dialog.CloseThemeDialogMsg:
|
||||
a.showThemeDialog = false
|
||||
return a, nil
|
||||
|
|
@ -342,7 +416,13 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
return a, nil
|
||||
|
||||
case chat.SessionSelectedMsg:
|
||||
a.selectedSession = msg
|
||||
a.sessionDialog.SetSelectedSession(msg.ID)
|
||||
|
||||
case pubsub.Event[session.Session]:
|
||||
if msg.Type == pubsub.UpdatedEvent && msg.Payload.ID == a.selectedSession.ID {
|
||||
a.selectedSession = msg.Payload
|
||||
}
|
||||
case dialog.SessionSelectedMsg:
|
||||
a.showSessionDialog = false
|
||||
if a.currentPage == page.ChatPage {
|
||||
|
|
@ -357,22 +437,22 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
return a, msg.Command.Handler(msg.Command)
|
||||
}
|
||||
return a, util.ReportInfo("Command selected: " + msg.Command.Title)
|
||||
|
||||
|
||||
case dialog.ShowArgumentsDialogMsg:
|
||||
// Show arguments dialog
|
||||
a.argumentsDialog = dialog.NewArgumentsDialogCmp(msg.CommandID, msg.Content)
|
||||
a.showArgumentsDialog = true
|
||||
return a, a.argumentsDialog.Init()
|
||||
|
||||
|
||||
case dialog.CloseArgumentsDialogMsg:
|
||||
// Close arguments dialog
|
||||
a.showArgumentsDialog = false
|
||||
|
||||
|
||||
// If submitted, replace $ARGUMENTS and run the command
|
||||
if msg.Submit {
|
||||
// Replace $ARGUMENTS with the provided arguments
|
||||
content := strings.ReplaceAll(msg.Content, "$ARGUMENTS", msg.Arguments)
|
||||
|
||||
|
||||
// Execute the command with arguments
|
||||
return a, util.CmdHandler(dialog.CommandRunCustomMsg{
|
||||
Content: content,
|
||||
|
|
@ -387,7 +467,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
a.argumentsDialog = args.(dialog.ArgumentsDialogCmp)
|
||||
return a, cmd
|
||||
}
|
||||
|
||||
|
||||
switch {
|
||||
|
||||
case key.Matches(msg, keys.Quit):
|
||||
|
|
@ -606,6 +686,15 @@ func (a *appModel) RegisterCommand(cmd dialog.Command) {
|
|||
a.commands = append(a.commands, cmd)
|
||||
}
|
||||
|
||||
func (a *appModel) findCommand(id string) (dialog.Command, bool) {
|
||||
for _, cmd := range a.commands {
|
||||
if cmd.ID == id {
|
||||
return cmd, true
|
||||
}
|
||||
}
|
||||
return dialog.Command{}, false
|
||||
}
|
||||
|
||||
func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
|
||||
if a.app.CoderAgent.IsBusy() {
|
||||
// For now we don't move to any page if the agent is busy
|
||||
|
|
@ -668,10 +757,29 @@ func (a appModel) View() string {
|
|||
|
||||
}
|
||||
|
||||
if !a.app.CoderAgent.IsBusy() {
|
||||
a.status.SetHelpWidgetMsg("ctrl+? help")
|
||||
} else {
|
||||
a.status.SetHelpWidgetMsg("? help")
|
||||
// Show compacting status overlay
|
||||
if a.isCompacting {
|
||||
t := theme.CurrentTheme()
|
||||
style := lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(t.BorderFocused()).
|
||||
BorderBackground(t.Background()).
|
||||
Padding(1, 2).
|
||||
Background(t.Background()).
|
||||
Foreground(t.Text())
|
||||
|
||||
overlay := style.Render("Summarizing\n" + a.compactingMessage)
|
||||
row := lipgloss.Height(appView) / 2
|
||||
row -= lipgloss.Height(overlay) / 2
|
||||
col := lipgloss.Width(appView) / 2
|
||||
col -= lipgloss.Width(overlay) / 2
|
||||
appView = layout.PlaceOverlay(
|
||||
col,
|
||||
row,
|
||||
overlay,
|
||||
appView,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
if a.showHelp {
|
||||
|
|
@ -789,7 +897,7 @@ func (a appModel) View() string {
|
|||
true,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if a.showArgumentsDialog {
|
||||
overlay := a.argumentsDialog.View()
|
||||
row := lipgloss.Height(appView) / 2
|
||||
|
|
@ -850,7 +958,17 @@ If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (
|
|||
)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
model.RegisterCommand(dialog.Command{
|
||||
ID: "compact",
|
||||
Title: "Compact Session",
|
||||
Description: "Summarize the current session and create a new one with the summary",
|
||||
Handler: func(cmd dialog.Command) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return startCompactSessionMsg{}
|
||||
}
|
||||
},
|
||||
})
|
||||
// Load custom commands
|
||||
customCommands, err := dialog.LoadCustomCommands()
|
||||
if err != nil {
|
||||
|
|
@ -860,6 +978,6 @@ If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (
|
|||
model.RegisterCommand(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return model
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue