diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts index e90b6455dce..8b975e32981 100644 --- a/packages/core/src/session/compaction.ts +++ b/packages/core/src/session/compaction.ts @@ -25,20 +25,27 @@ const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside Rules: - Keep every section, even when empty. - Use terse bullets, not prose paragraphs. - Preserve exact file paths, symbols, commands, error strings, URLs, and identifiers when known. -- Put relevant files and symbols inside the section where they matter; do not add extra sections. - Do not mention the summary process or that context was compacted.` type Settings = { diff --git a/packages/core/test/session-compaction.test.ts b/packages/core/test/session-compaction.test.ts index 1cfd353f545..d4d96abbc43 100644 --- a/packages/core/test/session-compaction.test.ts +++ b/packages/core/test/session-compaction.test.ts @@ -52,6 +52,15 @@ const it = testEffect( ), ) +test("compaction prompt preserves detailed work state and relevant files", () => { + const prompt = SessionCompaction.buildPrompt({ context: ["conversation history"] }) + + expect(prompt).toContain("## Work State\n### Completed") + expect(prompt).toContain("### Active") + expect(prompt).toContain("### Blocked") + expect(prompt).toContain("## Relevant Files") +}) + test("compaction describes tool media without embedding base64", () => { const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB" const serialized = SessionCompaction.serializeToolContent([ @@ -74,13 +83,14 @@ test("compaction prompt requires the checkpoint headings in order", () => { "## Objective", "## Important Details", "## Work State", + "### Completed", + "### Active", + "### Blocked", "## Next Move", + "## Relevant Files", ]) expect(prompt).toContain("one or two brief sentences") expect(prompt).toContain("constraints/preferences, decisions and why") - expect(prompt).toContain("Completed:") - expect(prompt).toContain("Active:") - expect(prompt).toContain("Blocked:") expect(prompt).toContain("immediate concrete action") expect(prompt).toContain("next action if known") expect(prompt).toContain("Keep every section, even when empty.") diff --git a/packages/llm/src/provider-error.ts b/packages/llm/src/provider-error.ts index 8d3f2a8f63a..321bd7927ef 100644 --- a/packages/llm/src/provider-error.ts +++ b/packages/llm/src/provider-error.ts @@ -6,6 +6,7 @@ const patterns = [ /input is too long for requested model/i, /exceeds the context window/i, /input token count.*exceeds the maximum/i, + /tokens in request more than max tokens allowed/i, /maximum prompt length is \d+/i, /reduce the length of the messages/i, /maximum context length is \d+ tokens/i, diff --git a/packages/llm/test/provider-error.test.ts b/packages/llm/test/provider-error.test.ts new file mode 100644 index 00000000000..3622c894541 --- /dev/null +++ b/packages/llm/test/provider-error.test.ts @@ -0,0 +1,8 @@ +import { describe, expect, test } from "bun:test" +import { isContextOverflow } from "../src" + +describe("provider error classification", () => { + test("classifies Z.AI GLM token limit messages as context overflow", () => { + expect(isContextOverflow("tokens in request more than max tokens allowed")).toBe(true) + }) +}) diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 1de84c9dd95..9bb688aeddb 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -1448,6 +1448,7 @@ describe("session.message-v2.fromError", () => { "prompt is too long: 213462 tokens > 200000 maximum", "Your input exceeds the context window of this model", "The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)", + "tokens in request more than max tokens allowed", "Please reduce the length of the messages or completion", "400 status code (no body)", "413 status code (no body)",