diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts index 5229949cb958..fb3347b2fc9b 100644 --- a/packages/core/src/session/compaction.ts +++ b/packages/core/src/session/compaction.ts @@ -1,7 +1,7 @@ export * as SessionCompaction from "./compaction" import { LLM, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@opencode-ai/llm" -import { DateTime, Effect, Stream } from "effect" +import { Cause, DateTime, Effect, Stream } from "effect" import type { Config } from "../config" import type { EventV2 } from "../event" import { SessionEvent } from "./event" @@ -213,7 +213,7 @@ export const make = (dependencies: Dependencies) => { return Effect.void }), Effect.as(true), - Effect.catchTag("LLM.Error", () => Effect.succeed(false)), + Effect.catchCause((cause) => (Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.succeed(false))), ) const summary = chunks.join("") if (!summarized || failed || !summary.trim()) return false diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index c9594e8244f1..bef89b22eedf 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -62,6 +62,7 @@ const requests: LLMRequest[] = [] let response: LLMEvent[] = [] let responses: LLMEvent[][] | undefined let responseStream: Stream.Stream | undefined +let responseStreams: Stream.Stream[] | undefined let streamGate: Deferred.Deferred | undefined let streamStarted: Deferred.Deferred | undefined let streamFailure: LLMError | undefined @@ -76,6 +77,7 @@ const client = Layer.succeed( prepare: () => Effect.die("unused"), stream: ((request: LLMRequest) => { requests.push(request) + if (responseStreams) return responseStreams.shift() ?? Stream.empty if (responseStream) { const stream = responseStream responseStream = undefined @@ -326,6 +328,7 @@ const setup = Effect.gen(function* () { responses = undefined streamFailure = undefined responseStream = undefined + responseStreams = undefined streamGate = undefined streamStarted = undefined toolExecutionGate = undefined @@ -1201,6 +1204,28 @@ describe("SessionRunnerLLM", () => { }), ) + it.effect("publishes the original overflow when recovery summarization aborts", () => + Effect.gen(function* () { + const session = yield* setupOverflowRecovery + const overflow = () => [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" }), + ] + responseStreams = [ + Stream.fromIterable(overflow()), + Stream.die(new Error("MessageAbortedError")) as Stream.Stream, + ] + yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false }) + yield* session.resume(sessionID) + + expect(requests).toHaveLength(2) + expect((yield* session.context(sessionID)).slice(-2)).toMatchObject([ + { type: "user", text: "Continue" }, + { type: "assistant", finish: "error", error: { message: "prompt too long" } }, + ]) + }), + ) + it.effect("recovers once from a raw context overflow failure", () => Effect.gen(function* () { const session = yield* setupOverflowRecovery