session: optional stripReasoning for compaction and cross-model safety#25184
session: optional stripReasoning for compaction and cross-model safety#25184jackmazac wants to merge 1 commit intoanomalyco:devfrom
Conversation
Opt-in stripReasoning on toModelMessagesEffect; compaction enables it alongside stripMedia and toolOutputMaxChars. Skip reasoning when model changed; keep providerMetadata when same model.
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: Based on the search results, I found a potentially related PR: Related PR Found:
Why it's related: This PR also addresses reasoning handling across model switches and provider metadata preservation. It appears to cover similar territory around preserving/managing reasoning data when models change, which is directly mentioned in the current PR's "Cross-model turns" section (point 3 of the technical breakdown). The current PR (#25184) builds upon these concerns by adding explicit |
|
Closing to reopen with a linked issue and the repo PR template per CONTRIBUTING. |
Summary
When the app summarizes a long chat (compaction) or you switch models mid-session, hidden “reasoning” data from the old model can leak into the next request. Providers then reject the call or behave inconsistently. This change gives the session layer an explicit switch to drop that reasoning for compaction, while keeping normal chat behavior unchanged.
Problem (plain language)
Long conversations are periodically summarized so they fit the model’s context window. That summary step should read like a normal transcript: goals, decisions, and tool outcomes—not low-level reasoning blobs tied to a specific model. If those blobs are forwarded anyway, the next model may error (for example missing signatures or wrong format) or users see confusing internal content in places it was never meant to go.
Separately, when you change which model is answering, reasoning attached to an earlier model is often not valid for the new one. The UI already treats “different model” as a special case; the conversion path to provider messages should align with that.
Technical breakdown
MessageV2.toModelMessagesEffectconverts stored session parts into the wire format the AI SDK sends to providers. It already supports stripping media and truncating large tool output (toolOutputMaxChars). It did not support omitting reasoning parts for specific call sites (notably compaction).Compaction builds a shorter context for the compaction model. That path should not include assistant reasoning parts from the pre-compaction history, because they are not part of the user-visible story and can break or bloat the compaction request.
Cross-model turns: when the assistant message was produced with a different
providerID/modelIDthan the target model, we already omit some provider metadata on text tools. Reasoning parts should follow the same rule: skip them when the model changed, instead of sending partial or incompatible reasoning metadata.When we keep reasoning (same model, normal chat), reasoning parts should carry
providerMetadataconsistently so providers that need it still receive it.Solution
toModelMessagesEffect/toModelMessagesoptions with optionalstripReasoning?: boolean(alongside existingstripMediaandtoolOutputMaxChars).stripReasoningordifferentModel, omit the reasoning part; otherwise emit reasoning withproviderMetadatafrom the stored part.toModelMessagesEffectwithstripReasoning: truein addition to existingstripMediaandtoolOutputMaxChars.Files
packages/opencode/src/session/message-v2.tspackages/opencode/src/session/compaction.tsHow to test
From
packages/opencode:bun typecheck bun test test/session/message-v2.test.tsMerge order
None. This PR is safe to merge first. PR 2–4 in this series do not depend on this branch for compilation (they touch other files), but landing this first keeps the story and review surface clear.
Notes for reviewers
toModelMessagesEffectcalls:stripReasoningis opt-in.stripReasoning: truein this PR.