Ver código fonte

fix(core): clamp compaction output budget (#36745)

Co-authored-by: Dax Raad <thdxr@users.noreply.github.com>
opencode-agent[bot] 1 mês atrás
pai
commit
fbe7f26e71

+ 2 - 1
packages/core/src/session/compaction.ts

@@ -16,6 +16,7 @@ import { Token } from "../util/token"
 
 const DEFAULT_BUFFER = 20_000
 const DEFAULT_KEEP_TOKENS = 8_000
+const OUTPUT_TOKEN_MAX = 32_000
 const TOOL_OUTPUT_MAX_CHARS = 2_000
 const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
 <template>
@@ -320,7 +321,7 @@ const make = (dependencies: Dependencies) => {
         message.type === "assistant" && message.tokens !== undefined,
     )
     if (!last) return false
-    const output = input.model.route.defaults.limits?.output ?? 0
+    const output = Math.min(input.model.route.defaults.limits?.output ?? 0, OUTPUT_TOKEN_MAX)
     const used =
       last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
     if (used <= 0) return false

+ 24 - 0
packages/core/test/session-runner.test.ts

@@ -139,6 +139,11 @@ const compactModel = Model.make({
   provider: "fake",
   route: OpenAIChat.route.with({ limits: { context: 4_000, output: 50 } }),
 })
+const fullOutputModel = Model.make({
+  id: "full-output",
+  provider: "fake",
+  route: OpenAIChat.route.with({ limits: { context: 262_144, output: 262_144 } }),
+})
 const undersizedContextModel = Model.make({
   id: "undersized-context",
   provider: "fake",
@@ -1865,6 +1870,25 @@ describe("SessionRunnerLLM", () => {
     }),
   )
 
+  it.effect("does not compact immediately when the advertised output limit fills the context", () =>
+    Effect.gen(function* () {
+      const session = yield* setup
+      currentModel = fullOutputModel
+      response = reply.textWithUsage("Earlier answer", "text-full-output-first", 9_500)
+      yield* admit(session, "Earlier question")
+      yield* session.resume(sessionID)
+
+      requests.length = 0
+      response = reply.text("Continued", "text-full-output-final")
+      yield* admit(session, "Continue")
+      yield* session.resume(sessionID)
+
+      expect(requests).toHaveLength(1)
+      expect(userTexts(requests[0])).toContain("Continue")
+      expect(yield* session.context(sessionID)).not.toContainEqual(expect.objectContaining({ type: "compaction" }))
+    }),
+  )
+
   it.effect("stops after required automatic compaction fails", () =>
     Effect.gen(function* () {
       const session = yield* setup