From 0d005f92e2b8c26bad79da10780554fc19b048f6 Mon Sep 17 00:00:00 2001 From: filipeforattini Date: Fri, 21 Aug 2026 09:22:09 -0300 Subject: [PATCH] feat(opencode): V2 compaction.pre-compact alongside V1 trigger Adds `SessionEvent.Compaction.PreCompact` (live waterfall event, type `session.next.compaction.pre_compact`) carrying `{ sessionID, context, prompt }` and wires `EventV2Bridge.waterfall` into the compaction dispatch next to the existing V1 `plugin.trigger('experimental.session.compacting', ...)`. The waterfall currently has a single listener that delegates to the V1 trigger so existing plugins keep working. Listeners return `{ context, prompt }` to inject context or replace the compaction prompt. The V1 result is mutated with the V2-decided values so downstream code keeps working with a single source of truth. --- packages/opencode/src/session/compaction.ts | 18 ++++++++++++++++++ packages/schema/src/session-event.ts | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 75d6374bfa54..124c9fa5664c 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -8,6 +8,7 @@ import { MessageV2 } from "./message-v2" import { Token } from "@/util/token" import { SessionProcessor } from "./processor" import { Agent } from "@/agent/agent" +import { SessionEvent } from "@opencode-ai/core/session/event" import { Plugin } from "@/plugin" import { Config } from "@/config/config" import { NotFoundError } from "@/storage/storage" @@ -375,6 +376,23 @@ const layer = Layer.effect( { sessionID: input.sessionID }, { context: [], prompt: undefined }, ) + const compactingV2 = yield* events.waterfall(SessionEvent.Compaction.PreCompact, [ + (_e, next) => + Effect.gen(function* () { + const downstream = yield* next() + const downstreamCtx = (downstream as { context?: unknown[] } | undefined)?.context + const downstreamPrompt = (downstream as { prompt?: string } | undefined)?.prompt + return { + context: downstreamCtx ?? compacting.context, + prompt: downstreamPrompt ?? compacting.prompt, + } + }), + ]) + const compactingFinal = compactingV2 as { context: unknown[]; prompt: string | undefined } + // Mutate the V1 trigger result with the V2-decided context/prompt so the + // downstream code keeps working with a single source of truth. + ;(compacting as { context: unknown[] }).context = compactingFinal.context + ;(compacting as { prompt: string | undefined }).prompt = compactingFinal.prompt const msgs = structuredClone(selected.head) yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) const conversation = msgs.map(serialize).filter(Boolean).join("\n\n") diff --git a/packages/schema/src/session-event.ts b/packages/schema/src/session-event.ts index 4155781ed5fa..41215225e783 100644 --- a/packages/schema/src/session-event.ts +++ b/packages/schema/src/session-event.ts @@ -500,6 +500,22 @@ export namespace Compaction { }) export type Started = typeof Started.Type + /** + * Live waterfall fired right before a compaction runs. Listeners return + * `{ context, prompt }` to inject context or replace the compaction prompt. + * This is the V2 replacement for the legacy V1 `experimental.session.compacting` + * hook and gives plugins proper `next()`-based mutation semantics. + */ + export const PreCompact = Event.define({ + type: "session.next.compaction.pre_compact", + schema: { + ...Base, + context: Schema.Array(Schema.Unknown), + prompt: Schema.optional(Schema.String), + }, + }) + export type PreCompact = typeof PreCompact.Type + export const Delta = Event.define({ type: "session.next.compaction.delta", schema: {