detached de925da

Recover from context window overflows

commit
de925da5a704912222a7823a8d3205d96237b940
author
mi-skam · · 2026-07-29 09:42 UTC
parent
d3084cc
signature
SSH UnverifiedSHA256:Yu1uEv3+xppM2hvm7BsWney9tUWafRiTe/kq8zX8X0w The key is unknown
4 files · +118 -21
M README.md +2 -2
@@ -378,7 +378,7 @@ Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` o
378378
379379 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.
380380 - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents.
381-- **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from payload-too-large responses.
381+- **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from context-window and payload-too-large responses.
382382 - **jail new sessions by default**: start every new agent with tools confined to its working directory. Off by default. The setting applies to interactive, print, JSON, RPC, and background-agent runs, persists as `jail_by_default`, and immediately updates the current interactive session. `/jail` and `/unjail` remain session-scoped overrides and do not change this default.
383383 - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`.
384384 - **show loaded resources at startup**: list the active Zotfile agent (for `zot run`), loaded `AGENTS.md` paths, extensions, and user-installed skills in compact sections above the transcript. Built-in skills are omitted. Off by default. Changes apply immediately and persist to `config.json` as `show_instructions_at_startup`.
@@ -395,7 +395,7 @@ Opens a picker listing every discovered SKILL.md file, built-ins hidden. Each ro
395395
396396 Sends the current transcript through the model with a structured summarization prompt. The returned summary replaces the transcript as one synthetic user message, with the last few exchanges kept verbatim for continuity. The status bar's context meter resets. Use it when the context meter creeps past ~80%.
397397
398-zot also auto-compacts in the background: after any turn that reaches the configured context threshold, the agent kicks off a condense pass on its own. Choose `off`, `70%`, `80%`, `85%` (default), or `90%` under `/settings` → **auto-compact threshold**. You'll see `condensing history, esc to cancel` above the status bar and an `(auto)` tag next to the context percentage; `esc` aborts it without touching the transcript. Turning the percentage trigger off does not disable automatic compaction and retry after a payload-too-large response.
398+zot also auto-compacts in the background: after any turn that reaches the configured context threshold, the agent kicks off a condense pass on its own. Choose `off`, `70%`, `80%`, `85%` (default), or `90%` under `/settings` → **auto-compact threshold**. You'll see `condensing history, esc to cancel` above the status bar and an `(auto)` tag next to the context percentage; `esc` aborts it without touching the transcript. Turning the percentage trigger off does not disable automatic compaction and retry after a context-window or payload-too-large response.
399399
400400 ### `/jail`
401401
@@ -378,7 +378,7 @@ Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` o
378 378
379 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.379 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.
380 - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents.380 - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents.
381-- **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from payload-too-large responses.381+- **auto-compact threshold** — choose `off`, `70%`, `80%`, `85%` (default), or `90%` of the model's advertised context window. The selected percentage controls automatic compaction before and after interactive turns and persists as `auto_compact_threshold`. `off` disables percentage-based triggers but keeps manual `/compact` and automatic recovery from context-window and payload-too-large responses.
382 - **jail new sessions by default**: start every new agent with tools confined to its working directory. Off by default. The setting applies to interactive, print, JSON, RPC, and background-agent runs, persists as `jail_by_default`, and immediately updates the current interactive session. `/jail` and `/unjail` remain session-scoped overrides and do not change this default.382 - **jail new sessions by default**: start every new agent with tools confined to its working directory. Off by default. The setting applies to interactive, print, JSON, RPC, and background-agent runs, persists as `jail_by_default`, and immediately updates the current interactive session. `/jail` and `/unjail` remain session-scoped overrides and do not change this default.
383 - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`.383 - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`.
384 - **show loaded resources at startup**: list the active Zotfile agent (for `zot run`), loaded `AGENTS.md` paths, extensions, and user-installed skills in compact sections above the transcript. Built-in skills are omitted. Off by default. Changes apply immediately and persist to `config.json` as `show_instructions_at_startup`.384 - **show loaded resources at startup**: list the active Zotfile agent (for `zot run`), loaded `AGENTS.md` paths, extensions, and user-installed skills in compact sections above the transcript. Built-in skills are omitted. Off by default. Changes apply immediately and persist to `config.json` as `show_instructions_at_startup`.
@@ -395,7 +395,7 @@ Opens a picker listing every discovered SKILL.md file, built-ins hidden. Each ro
395 395
396 Sends the current transcript through the model with a structured summarization prompt. The returned summary replaces the transcript as one synthetic user message, with the last few exchanges kept verbatim for continuity. The status bar's context meter resets. Use it when the context meter creeps past ~80%.396 Sends the current transcript through the model with a structured summarization prompt. The returned summary replaces the transcript as one synthetic user message, with the last few exchanges kept verbatim for continuity. The status bar's context meter resets. Use it when the context meter creeps past ~80%.
397 397
398-zot also auto-compacts in the background: after any turn that reaches the configured context threshold, the agent kicks off a condense pass on its own. Choose `off`, `70%`, `80%`, `85%` (default), or `90%` under `/settings` → **auto-compact threshold**. You'll see `condensing history, esc to cancel` above the status bar and an `(auto)` tag next to the context percentage; `esc` aborts it without touching the transcript. Turning the percentage trigger off does not disable automatic compaction and retry after a payload-too-large response.398+zot also auto-compacts in the background: after any turn that reaches the configured context threshold, the agent kicks off a condense pass on its own. Choose `off`, `70%`, `80%`, `85%` (default), or `90%` under `/settings` → **auto-compact threshold**. You'll see `condensing history, esc to cancel` above the status bar and an `(auto)` tag next to the context percentage; `esc` aborts it without touching the transcript. Turning the percentage trigger off does not disable automatic compaction and retry after a context-window or payload-too-large response.
399 399
400 ### `/jail`400 ### `/jail`
401 401
M packages/agent/modes/compact_queue_test.go +89 -0
@@ -2,6 +2,7 @@ package modes
22
33 import (
44 "context"
5+ "errors"
56 "sync"
67 "testing"
78 "time"
@@ -93,6 +94,94 @@ func TestPromptSubmittedDuringCompactionStartsFollowUpTurn(t *testing.T) {
9394 }
9495 }
9596
97+type contextRecoveryClient struct {
98+ mu sync.Mutex
99+ calls int
100+ retried chan provider.Request
101+}
102+
103+func (c *contextRecoveryClient) Name() string { return "context-recovery-test" }
104+
105+func (c *contextRecoveryClient) Stream(_ context.Context, req provider.Request) (<-chan provider.Event, error) {
106+ c.mu.Lock()
107+ c.calls++
108+ call := c.calls
109+ c.mu.Unlock()
110+
111+ out := make(chan provider.Event, 2)
112+ go func() {
113+ defer close(out)
114+ switch call {
115+ case 1:
116+ out <- provider.EventDone{
117+ Stop: provider.StopError,
118+ Err: errors.New("provider error: Your input exceeds the context window of this model. Please adjust your input and try again."),
119+ }
120+ case 2:
121+ out <- provider.EventTextDelta{Delta: "summary"}
122+ out <- provider.EventDone{Stop: provider.StopEnd}
123+ default:
124+ c.retried <- req
125+ out <- provider.EventDone{
126+ Stop: provider.StopEnd,
127+ Message: provider.Message{
128+ Role: provider.RoleAssistant,
129+ Content: []provider.Content{provider.TextBlock{Text: "done"}},
130+ },
131+ }
132+ }
133+ }()
134+ return out, nil
135+}
136+
137+func TestContextWindowErrorCompactsAndRetriesPrompt(t *testing.T) {
138+ client := &contextRecoveryClient{retried: make(chan provider.Request, 1)}
139+ agent := core.NewAgent(client, "test-model", "", nil)
140+ agent.SetMessages([]provider.Message{
141+ {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "one"}}},
142+ {Role: provider.RoleAssistant, Content: []provider.Content{provider.TextBlock{Text: "two"}}},
143+ {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "three"}}},
144+ {Role: provider.RoleAssistant, Content: []provider.Content{provider.TextBlock{Text: "four"}}},
145+ {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "five"}}},
146+ })
147+ interactive := NewInteractive(InteractiveConfig{Agent: agent})
148+ interactive.runCtx = context.Background()
149+
150+ interactive.startTurn(context.Background(), "retry me")
151+
152+ select {
153+ case req := <-client.retried:
154+ if !requestContainsUserText(req, "retry me") {
155+ t.Fatalf("retried request does not contain original prompt: %#v", req.Messages)
156+ }
157+ case <-time.After(2 * time.Second):
158+ t.Fatal("context-window error did not compact and retry the prompt")
159+ }
160+}
161+
162+func TestContextOverflowErrorRecognizesProviderMessages(t *testing.T) {
163+ tests := []struct {
164+ name string
165+ err error
166+ want bool
167+ }{
168+ {name: "http 413", err: errors.New("provider http 413: request rejected"), want: true},
169+ {name: "context error code", err: errors.New("context_length_exceeded"), want: true},
170+ {name: "maximum context length", err: errors.New("This model's maximum context length is 128000 tokens"), want: true},
171+ {name: "context window exceeded", err: errors.New("context window exceeded"), want: true},
172+ {name: "maximum token count", err: errors.New("input token count exceeds the maximum number of tokens allowed"), want: true},
173+ {name: "unrelated error", err: errors.New("rate limit exceeded"), want: false},
174+ }
175+
176+ for _, tt := range tests {
177+ t.Run(tt.name, func(t *testing.T) {
178+ if got := isContextOverflowError(tt.err); got != tt.want {
179+ t.Fatalf("isContextOverflowError(%q) = %t, want %t", tt.err, got, tt.want)
180+ }
181+ })
182+ }
183+}
184+
96185 func requestContainsUserText(req provider.Request, want string) bool {
97186 for _, message := range req.Messages {
98187 if message.Role != provider.RoleUser {
@@ -2,6 +2,7 @@ package modes
2 2
3 import (3 import (
4 "context"4 "context"
5+ "errors"
5 "sync"6 "sync"
6 "testing"7 "testing"
7 "time"8 "time"
@@ -93,6 +94,94 @@ func TestPromptSubmittedDuringCompactionStartsFollowUpTurn(t *testing.T) {
93 }94 }
94 }95 }
95 96
97+type contextRecoveryClient struct {
98+ mu sync.Mutex
99+ calls int
100+ retried chan provider.Request
101+}
102+
103+func (c *contextRecoveryClient) Name() string { return "context-recovery-test" }
104+
105+func (c *contextRecoveryClient) Stream(_ context.Context, req provider.Request) (<-chan provider.Event, error) {
106+ c.mu.Lock()
107+ c.calls++
108+ call := c.calls
109+ c.mu.Unlock()
110+
111+ out := make(chan provider.Event, 2)
112+ go func() {
113+ defer close(out)
114+ switch call {
115+ case 1:
116+ out <- provider.EventDone{
117+ Stop: provider.StopError,
118+ Err: errors.New("provider error: Your input exceeds the context window of this model. Please adjust your input and try again."),
119+ }
120+ case 2:
121+ out <- provider.EventTextDelta{Delta: "summary"}
122+ out <- provider.EventDone{Stop: provider.StopEnd}
123+ default:
124+ c.retried <- req
125+ out <- provider.EventDone{
126+ Stop: provider.StopEnd,
127+ Message: provider.Message{
128+ Role: provider.RoleAssistant,
129+ Content: []provider.Content{provider.TextBlock{Text: "done"}},
130+ },
131+ }
132+ }
133+ }()
134+ return out, nil
135+}
136+
137+func TestContextWindowErrorCompactsAndRetriesPrompt(t *testing.T) {
138+ client := &contextRecoveryClient{retried: make(chan provider.Request, 1)}
139+ agent := core.NewAgent(client, "test-model", "", nil)
140+ agent.SetMessages([]provider.Message{
141+ {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "one"}}},
142+ {Role: provider.RoleAssistant, Content: []provider.Content{provider.TextBlock{Text: "two"}}},
143+ {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "three"}}},
144+ {Role: provider.RoleAssistant, Content: []provider.Content{provider.TextBlock{Text: "four"}}},
145+ {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "five"}}},
146+ })
147+ interactive := NewInteractive(InteractiveConfig{Agent: agent})
148+ interactive.runCtx = context.Background()
149+
150+ interactive.startTurn(context.Background(), "retry me")
151+
152+ select {
153+ case req := <-client.retried:
154+ if !requestContainsUserText(req, "retry me") {
155+ t.Fatalf("retried request does not contain original prompt: %#v", req.Messages)
156+ }
157+ case <-time.After(2 * time.Second):
158+ t.Fatal("context-window error did not compact and retry the prompt")
159+ }
160+}
161+
162+func TestContextOverflowErrorRecognizesProviderMessages(t *testing.T) {
163+ tests := []struct {
164+ name string
165+ err error
166+ want bool
167+ }{
168+ {name: "http 413", err: errors.New("provider http 413: request rejected"), want: true},
169+ {name: "context error code", err: errors.New("context_length_exceeded"), want: true},
170+ {name: "maximum context length", err: errors.New("This model's maximum context length is 128000 tokens"), want: true},
171+ {name: "context window exceeded", err: errors.New("context window exceeded"), want: true},
172+ {name: "maximum token count", err: errors.New("input token count exceeds the maximum number of tokens allowed"), want: true},
173+ {name: "unrelated error", err: errors.New("rate limit exceeded"), want: false},
174+ }
175+
176+ for _, tt := range tests {
177+ t.Run(tt.name, func(t *testing.T) {
178+ if got := isContextOverflowError(tt.err); got != tt.want {
179+ t.Fatalf("isContextOverflowError(%q) = %t, want %t", tt.err, got, tt.want)
180+ }
181+ })
182+ }
183+}
184+
96 func requestContainsUserText(req provider.Request, want string) bool {185 func requestContainsUserText(req provider.Request, want string) bool {
97 for _, message := range req.Messages {186 for _, message := range req.Messages {
98 if message.Role != provider.RoleUser {187 if message.Role != provider.RoleUser {
M packages/agent/modes/interactive.go +24 -16
@@ -5476,13 +5476,13 @@ func (i *Interactive) startTurnWithImages(parent context.Context, prompt string,
54765476 i.statusErr = ""
54775477 }
54785478 }
5479- // Detect HTTP 413 "payload too large" responses. The provider
5480- // rejected the request because the request body exceeded its
5481- // per-request limit. Token-based auto-compact can miss this
5482- // because the limit is on raw bytes, not tokens. Re-queue the
5483- // prompt so it survives the condense pass and trigger one.
5484- payloadTooLarge := err != nil && ctx.Err() == nil && isPayloadTooLargeError(err)
5485- if payloadTooLarge {
5479+ // Detect responses that reject the current context, either as an
5480+ // HTTP 413 payload limit or a model context-window error. Token-
5481+ // based auto-compact can miss both when metadata is stale or the
5482+ // limit is measured in raw bytes. Re-queue the prompt so it
5483+ // survives the condense pass and trigger one.
5484+ contextOverflow := err != nil && ctx.Err() == nil && isContextOverflowError(err)
5485+ if contextOverflow {
54865486 i.statusErr = ""
54875487 i.queued = append([]string{prompt}, i.queued...)
54885488 i.extNotes = append(i.extNotes, autoCompactNoteLine(i.cfg.Theme, "request was too large. condensing history before retrying ..."))
@@ -5509,7 +5509,7 @@ func (i *Interactive) startTurnWithImages(parent context.Context, prompt string,
55095509 }
55105510 // If the turn was cancelled or errored, drop the queue so the
55115511 // user isn't bombarded with stale messages after an interrupt.
5512- if ctx.Err() != nil || err != nil {
5512+ if ctx.Err() != nil || (err != nil && !contextOverflow) {
55135513 i.queued = nil
55145514 if i.agent != nil {
55155515 i.agent.DrainQueuedMessages()
@@ -5535,7 +5535,7 @@ func (i *Interactive) startTurnWithImages(parent context.Context, prompt string,
55355535 i.startTurn(parent, next)
55365536 case offer:
55375537 i.openRescueDialog(rescueProv, rescueFprov, rescueModel, rescueWhy, prompt, rescueImgs)
5538- case payloadTooLarge:
5538+ case contextOverflow:
55395539 i.runCompact(parent, true)
55405540 case shouldAutoCompact:
55415541 i.runCompact(parent, true)
@@ -5614,17 +5614,25 @@ func autoCompactNoteLine(th tui.Theme, msg string) string {
56145614 return " " + th.FG256(th.Warning, "⚠ "+msg)
56155615 }
56165616
5617-// isPayloadTooLargeError matches HTTP 413 responses surfaced by the
5618-// provider clients. The error formatting differs slightly between
5619-// providers (anthropic and openai both prepend the status code), so
5620-// we look for the canonical 413 marker as well as the conventional
5621-// 'payload too large' phrase.
5622-func isPayloadTooLargeError(err error) bool {
5617+// isContextOverflowError matches provider responses that reject the
5618+// current request because its raw payload or tokenized input is too
5619+// large. Providers surface this as either HTTP 413 or semantic context
5620+// errors with differing codes and messages.
5621+func isContextOverflowError(err error) bool {
56235622 if err == nil {
56245623 return false
56255624 }
56265625 msg := strings.ToLower(err.Error())
5627- return strings.Contains(msg, "http 413") || strings.Contains(msg, " 413") || strings.HasPrefix(msg, "413 ") || strings.Contains(msg, "payload too large") || strings.Contains(msg, "request entity too large")
5626+ return strings.Contains(msg, "http 413") ||
5627+ strings.Contains(msg, " 413") ||
5628+ strings.HasPrefix(msg, "413 ") ||
5629+ strings.Contains(msg, "payload too large") ||
5630+ strings.Contains(msg, "request entity too large") ||
5631+ strings.Contains(msg, "input exceeds the context window") ||
5632+ strings.Contains(msg, "context window exceeded") ||
5633+ strings.Contains(msg, "maximum context length") ||
5634+ strings.Contains(msg, "context_length_exceeded") ||
5635+ strings.Contains(msg, "exceeds the maximum number of tokens")
56285636 }
56295637
56305638 const defaultAutoCompactThreshold = 85
@@ -5476,13 +5476,13 @@ func (i *Interactive) startTurnWithImages(parent context.Context, prompt string,
5476 i.statusErr = ""5476 i.statusErr = ""
5477 }5477 }
5478 }5478 }
5479- // Detect HTTP 413 "payload too large" responses. The provider5479+ // Detect responses that reject the current context, either as an
5480- // rejected the request because the request body exceeded its5480+ // HTTP 413 payload limit or a model context-window error. Token-
5481- // per-request limit. Token-based auto-compact can miss this5481+ // based auto-compact can miss both when metadata is stale or the
5482- // because the limit is on raw bytes, not tokens. Re-queue the5482+ // limit is measured in raw bytes. Re-queue the prompt so it
5483- // prompt so it survives the condense pass and trigger one.5483+ // survives the condense pass and trigger one.
5484- payloadTooLarge := err != nil && ctx.Err() == nil && isPayloadTooLargeError(err)5484+ contextOverflow := err != nil && ctx.Err() == nil && isContextOverflowError(err)
5485- if payloadTooLarge {5485+ if contextOverflow {
5486 i.statusErr = ""5486 i.statusErr = ""
5487 i.queued = append([]string{prompt}, i.queued...)5487 i.queued = append([]string{prompt}, i.queued...)
5488 i.extNotes = append(i.extNotes, autoCompactNoteLine(i.cfg.Theme, "request was too large. condensing history before retrying ..."))5488 i.extNotes = append(i.extNotes, autoCompactNoteLine(i.cfg.Theme, "request was too large. condensing history before retrying ..."))
@@ -5509,7 +5509,7 @@ func (i *Interactive) startTurnWithImages(parent context.Context, prompt string,
5509 }5509 }
5510 // If the turn was cancelled or errored, drop the queue so the5510 // If the turn was cancelled or errored, drop the queue so the
5511 // user isn't bombarded with stale messages after an interrupt.5511 // user isn't bombarded with stale messages after an interrupt.
5512- if ctx.Err() != nil || err != nil {5512+ if ctx.Err() != nil || (err != nil && !contextOverflow) {
5513 i.queued = nil5513 i.queued = nil
5514 if i.agent != nil {5514 if i.agent != nil {
5515 i.agent.DrainQueuedMessages()5515 i.agent.DrainQueuedMessages()
@@ -5535,7 +5535,7 @@ func (i *Interactive) startTurnWithImages(parent context.Context, prompt string,
5535 i.startTurn(parent, next)5535 i.startTurn(parent, next)
5536 case offer:5536 case offer:
5537 i.openRescueDialog(rescueProv, rescueFprov, rescueModel, rescueWhy, prompt, rescueImgs)5537 i.openRescueDialog(rescueProv, rescueFprov, rescueModel, rescueWhy, prompt, rescueImgs)
5538- case payloadTooLarge:5538+ case contextOverflow:
5539 i.runCompact(parent, true)5539 i.runCompact(parent, true)
5540 case shouldAutoCompact:5540 case shouldAutoCompact:
5541 i.runCompact(parent, true)5541 i.runCompact(parent, true)
@@ -5614,17 +5614,25 @@ func autoCompactNoteLine(th tui.Theme, msg string) string {
5614 return " " + th.FG256(th.Warning, "⚠ "+msg)5614 return " " + th.FG256(th.Warning, "⚠ "+msg)
5615 }5615 }
5616 5616
5617-// isPayloadTooLargeError matches HTTP 413 responses surfaced by the5617+// isContextOverflowError matches provider responses that reject the
5618-// provider clients. The error formatting differs slightly between5618+// current request because its raw payload or tokenized input is too
5619-// providers (anthropic and openai both prepend the status code), so5619+// large. Providers surface this as either HTTP 413 or semantic context
5620-// we look for the canonical 413 marker as well as the conventional5620+// errors with differing codes and messages.
5621-// 'payload too large' phrase.5621+func isContextOverflowError(err error) bool {
5622-func isPayloadTooLargeError(err error) bool {
5623 if err == nil {5622 if err == nil {
5624 return false5623 return false
5625 }5624 }
5626 msg := strings.ToLower(err.Error())5625 msg := strings.ToLower(err.Error())
5627- return strings.Contains(msg, "http 413") || strings.Contains(msg, " 413") || strings.HasPrefix(msg, "413 ") || strings.Contains(msg, "payload too large") || strings.Contains(msg, "request entity too large")5626+ return strings.Contains(msg, "http 413") ||
5627+ strings.Contains(msg, " 413") ||
5628+ strings.HasPrefix(msg, "413 ") ||
5629+ strings.Contains(msg, "payload too large") ||
5630+ strings.Contains(msg, "request entity too large") ||
5631+ strings.Contains(msg, "input exceeds the context window") ||
5632+ strings.Contains(msg, "context window exceeded") ||
5633+ strings.Contains(msg, "maximum context length") ||
5634+ strings.Contains(msg, "context_length_exceeded") ||
5635+ strings.Contains(msg, "exceeds the maximum number of tokens")
5628 }5636 }
5629 5637
5630 const defaultAutoCompactThreshold = 855638 const defaultAutoCompactThreshold = 85
M packages/agent/modes/rescue_dialog.go +3 -3
@@ -231,9 +231,9 @@ func classifyRescueError(err error) (bool, string) {
231231 msg := err.Error()
232232 low := strings.ToLower(msg)
233233
234- // Don't trigger on payload-too-large; that path already has its
235- // own auto-compact handling.
236- if isPayloadTooLargeError(err) {
234+ // Don't trigger on context overflow; that path already has its own
235+ // auto-compact handling.
236+ if isContextOverflowError(err) {
237237 return false, ""
238238 }
239239
@@ -231,9 +231,9 @@ func classifyRescueError(err error) (bool, string) {
231 msg := err.Error()231 msg := err.Error()
232 low := strings.ToLower(msg)232 low := strings.ToLower(msg)
233 233
234- // Don't trigger on payload-too-large; that path already has its234+ // Don't trigger on context overflow; that path already has its own
235- // own auto-compact handling.235+ // auto-compact handling.
236- if isPayloadTooLargeError(err) {236+ if isContextOverflowError(err) {
237 return false, ""237 return false, ""
238 }238 }
239 239

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close