Skip to content

refactor(core): move native compaction mechanisms into a plugin - #49575

Merged
nexxeln merged 3 commits into
v2from
compaction-plugin
Sep 18, 2026
Merged

nexxeln merged 3 commits into
v2from
compaction-plugin

Conversation

@nexxeln

@nexxeln nexxeln commented Sep 17, 2026

Copy link
Copy Markdown
Member

Why the change

SessionCompaction hard-coded how a provider's native compaction is performed (trigger vs endpoint, checkpoint window assembly), so this moves that mechanism into an internal plugin and leaves Core owning only the parts that must stay invariant: policy, provenance, retries, overflow fallback, and persistence.

Special things to note

  • Behavior is unchanged for every route with compaction operations (OpenAI, Azure, xAI, Cloudflare AI Gateway, custom packages). One generic plugin covers them all because they share the same typed @opencode/ai operations; per-provider copies would have been identical.
  • A mode: "provider" policy with no registered strategy now fails with provider.unsupported-operation before compaction.started, instead of a defect after it.
  • The public plugin API (ctx.session.hook("compaction")) is untouched. The seam lives on the Core SessionCompaction service and is reachable by internal plugins only.

Change outline

The new contract. Core hands a strategy the fully prepared request and gets back a replacement window; undefined means "not my mechanism".

// packages/core/src/session/compaction.ts
type NativeInput = {
  request: LLMRequest                       // after model.request hooks + provenance check
  options: StreamOptions                    // http middleware + session websocket
  retained: Effect<ReadonlyArray<Message>>  // real user messages within keep.tokens
}
type NativeResult = { replacement: ReadonlyArray<Message>; usage?: Usage }
type NativeStrategy = (input: NativeInput) => Effect<NativeResult, AIError> | undefined

Editor.native(strategy)                     // later registrations win

Where the mechanism went.

 packages/core/src/
 ├── session/compaction.ts      # policy, provenance, retry, fallback, persistence
-│                              # + trigger/endpoint selection, llm.compact calls
+│                              # + native strategy registry
+├── plugin/compaction.ts       # NativeCompactionPlugin: trigger if available, else endpoint
 └── plugin/internal.ts
+    LLMClient.Service added to internal plugin requirements
+    NativeCompactionPlugin registered before ...ProviderPlugins

What executeProvider does now.

 executeProvider(input)
   prepare request (compaction hook, model.request hook, http/ws options)
   reject unless catalog route has stable provenance
   reject if request hooks rewrote the route
-  publish compaction.started
-  if route.compact.trigger
-    retained = retainUsers(original history)
-    result = llm.compact(request, trigger) with retry
-    replacement = [...retained, assistant(checkpoint)]
-  else if route.compact.endpoint
-    replacement = llm.compact(request, endpoint) with retry
-  else die
+  native = last registered strategy that returns an effect for this request
+  reject "No plugin provides native compaction" if none
+  publish compaction.started
+  result = native with retry
   record usage
   publish compaction.ended with providerContext = encode(provenance, replacement)
   on context overflow (auto only): recover locally from original history

The plugin is the removed block, reattached through the editor.

// packages/core/src/plugin/compaction.ts
editor.native((input) => {
  if (canCompact(request, trigger))
    return retained ++ [assistant(llm.compact(request, trigger).checkpoint)]
  if (canCompact(request))
    return llm.compact(request, endpoint)
  return undefined
})

Tests: the existing native-compaction and runner scenarios now activate the plugin and run unchanged. One new test checks the seam itself: no strategy → typed failure with nothing sent; a registered strategy's window is persisted with the catalog route's provenance and its usage, with zero HTTP calls.

@nexxeln
nexxeln merged commit db80806 into v2 Sep 18, 2026
7 of 8 checks passed
@nexxeln
nexxeln deleted the compaction-plugin branch September 18, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant