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
17 changes: 17 additions & 0 deletions src/daemon/context-windows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ describe("contextWindowForModel", () => {
expect(contextWindowForModel("claude-sonnet-4-5-1m")).toBe(ONE_MILLION_CONTEXT);
});

test("bracket [1m] form -> 1M (the form Claude Code actually emits)", () => {
// Regression: these resolved to 200k, so asking EXPLICITLY for the 1M
// variant sized the window WORSE than the bare `opus` alias — observed on
// a live session running `opus[1m]`, which reported ~500% occupancy and a
// 5x-too-small fork seed budget.
expect(contextWindowForModel("opus[1m]")).toBe(ONE_MILLION_CONTEXT);
expect(contextWindowForModel("sonnet[1m]")).toBe(ONE_MILLION_CONTEXT);
expect(contextWindowForModel("claude-opus-5[1m]")).toBe(ONE_MILLION_CONTEXT);
// Case-insensitive, like every other branch.
expect(contextWindowForModel("OPUS[1M]")).toBe(ONE_MILLION_CONTEXT);
});

test("haiku stays 200k in bracket form too", () => {
// haiku has no 1M variant; a bracket suffix must not manufacture one.
expect(contextWindowForModel("haiku")).toBe(DEFAULT_CONTEXT_WINDOW);
});

test("unknown claude model -> conservative 200k miss", () => {
expect(contextWindowForModel("claude-sonnet-4-0")).toBe(DEFAULT_CONTEXT_WINDOW);
expect(contextWindowForModel("custom-model")).toBe(DEFAULT_CONTEXT_WINDOW);
Expand Down
9 changes: 8 additions & 1 deletion src/daemon/context-windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ export function contextWindowForModel(modelId: string | undefined | null): numbe
for (const family of ONE_MILLION_FAMILIES) {
if (m.includes(family)) return ONE_MILLION_CONTEXT;
}
if (m.includes("-1m")) return ONE_MILLION_CONTEXT;
// The 1M variant appears in two forms: the suffix on a full model id
// (`claude-opus-4-5-1m`) and the BRACKET form Claude Code uses on aliases
// and ids alike (`opus[1m]`, `claude-opus-5[1m]`). Matching only the former
// meant an EXPLICIT 1M request resolved to 200k while the bare `opus` alias
// correctly resolved to 1M — inverting the caller's intent, and under-sizing
// the window that drives the percent-of-window display, the fork seed budget
// (seedBudgetChars), and auto-rotate occupancy.
if (m.includes("-1m") || m.includes("[1m]")) return ONE_MILLION_CONTEXT;

// Aliases (matching the daemon's model resolver: opus → Opus 4.8,
// sonnet → Sonnet 5 — both 1M; haiku → Haiku 4.5 at 200k).
Expand Down
Loading