Skip to content

fix: backward-compatible renderMessage option for tool messages - #37

Open
ranxianglei wants to merge 2 commits into
masterfrom
2026-08-05_skip-tool-in-render
Open

fix: backward-compatible renderMessage option for tool messages#37
ranxianglei wants to merge 2 commits into
masterfrom
2026-08-05_skip-tool-in-render

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Problem

renderMessage() tags ALL messages including tool-call arguments in CoreMessage.text. In proxy adapters (e.g. acp-proxy) that store wire-format JSON in .text (OpenAI function.arguments), this corrupted the JSON → SchemaError(Missing key at ["command"]).

Approach (redesigned per review)

Unconditionally skip tool messages.rejected: breaking change for existing callers.

New approach: opt-in via RenderOptions + RenderConfig, zero behavior change for existing callers.

API changes

// New: optional parameter (backward compat — default = tag everything)
export function renderMessage(
  message, map, countTokens,
  options?: { skipToolMessages?: boolean },  // ← NEW
): CoreMessage

export function renderVisibleRefs(
  messages, state, countTokens?,
  options?: { skipToolMessages?: boolean },  // ← NEW
): CoreMessage[]

// New: config option
interface RenderConfig { skipToolMessageTags?: boolean }
interface Config { ...; render?: RenderConfig }

// New: helper for pipeline node
export function optionsFromConfig(config?): RenderOptions | undefined

Who opts in?

Caller Behavior Change needed?
Plugin (opencode-acp) Tags all messages (original) None
Pipeline default Tags all messages (original) None (render undefined)
Proxy (acp-proxy) Skips tool messages config.render = { skipToolMessageTags: true }

Tests (211/211 pass)

  • renderVisibleRefs tags tool messages by default (backward compat) — existing behavior preserved
  • renderVisibleRefs skips tool messages when options.skipToolMessages is set — opt-in works
  • renderRefsNode respects config.render.skipToolMessageTags — pipeline integration

Why this is safe

  • renderMessage() default path: identical to before
  • renderVisibleRefs() default path: identical to before
  • renderRefsNode with no config.render: identical to before
  • renderMessage() now exported: adapters can reuse without reimplementing
  • Refs still allocated for tool messages (assignRefs runs before render-refs)
  • Tool pairs still tracked (adjustBoundariesForToolPairs auto-expands ranges)

renderMessage() prepended ACP tags (<acp tokens="XX" type="YY">REF</acp>)
to ALL messages including tool-call arguments stored in CoreMessage.text.
In proxy adapters that store wire-format JSON in .text (e.g. OpenAI
function.arguments), this corrupted the JSON and caused downstream
SchemaError (Missing key at ["command"] etc).

Tool messages don't need ACP tags: they are structured metadata, not
displayable text. assignRefs still allocates refs for them (compression
ranges can still target them), and adjustBoundariesForToolPairs auto-
expands ranges to include tool_call+tool_result pairs. The model never
needs to see a tool message's ref to compress it.

Plugin (opencode-acp) is unaffected: tool args live in Part.input (a
separate JSON field), not CoreMessage.text, so tags on .text were always
cosmetic metadata, never load-bearing.
Redesign based on review feedback: instead of unconditionally skipping
tool messages (breaking change for existing callers), extract rendering
into composable building blocks with opt-in behavior.

Changes:
- renderMessage() and renderVisibleRefs() accept optional RenderOptions
  parameter ({ skipToolMessages?: boolean }). Default behavior unchanged.
- Config gains optional render?: RenderConfig ({ skipToolMessageTags?: boolean }).
- renderRefsNode reads ctx.config.render?.skipToolMessageTags via
  optionsFromConfig() helper. Pipeline callers with no config change get
  the original behavior.
- renderMessage() is now exported (was private) so adapters can reuse it.

Plugin (opencode-acp): no config change needed, identical behavior.
Proxy (acp-proxy): sets config.render.skipToolMessageTags = true.
@ranxianglei ranxianglei changed the title fix: skip tool messages in renderMessage (JSON corruption) fix: backward-compatible renderMessage option for tool messages Aug 5, 2026
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