You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The two experimental.chat.{system,messages}.transform hooks aren't documented on the Plugins page at all — they're not in the ### Events list, and packages/web/src/content/docs/plugins.mdx never mentions output.system / output.messages. The one mutation gotcha that bites everyone is also undocumented: you have to mutate the array in place (splice/push). Reassigning the whole array (output.system = [...]) is a silent no-op — the runtime keeps the original array reference and never reads the replacement. That's the bug already reported in #25754, whose fix-option #2 explicitly asks to "document the in-place requirement."
What makes this easy to get wrong: the nearby Compaction example sets output.prompt = "..." and that does work (it's a scalar), so a reader naturally assumes output.system = [...] works too. It doesn't.
This PR adds a short subsection under ## Examples documenting both hooks plus a callout about in-place mutation, mirroring the existing ### Compaction hooks example style. Concretely:
importtype{Plugin}from"@opencode-ai/plugin"exportconstSystemContextPlugin: Plugin=async(ctx)=>{return{"experimental.chat.system.transform": async(input,output)=>{// Mutate output.system IN PLACE. Insert after the first block so the// cached system-prompt prefix stays stable for prompt caching.output.system.splice(1,0,"Extra session context")// output.system = [...] // ⚠️ silently ignored — keeps the original array},}}
And the analogous note for experimental.chat.messages.transform: use output.messages.splice(0, output.messages.length, ...next) rather than output.messages = next.
I'd add a :::caution callout: "These hooks hand you an output object whose array property is the live payload. Mutate the array in place (push/splice). Reassigning the whole array is a no-op — see #25754." I'll also cross-reference the related (but separate) guidance in #23660 (prefer merging into the primary system block over pushing extra entries for OpenAI-compatible backends) and the ordering note in #19960.
I hit this building a cross-platform MCP/hook adapter: our generated bridge has to output.system.splice(...) exactly because reassignment is dropped — so I can confirm the contract on a real integration.
How did you verify your code works?
Confirmed on current dev: packages/web/src/content/docs/plugins.mdx (389 lines) has zero mentions of experimental.chat.system.transform, messages.transform, splice, in-place, output.system, or output.messages; the hooks aren't even in the ### Events list.
Confirmed the type surface in packages/plugin/src/index.ts:282-296: both transform hooks are declared with no JSDoc, while the sibling experimental.session.compacting (:298-308) has a JSDoc block.
Issue for this PR
Closes #25754
Type of change
What does this PR do?
The two
experimental.chat.{system,messages}.transformhooks aren't documented on the Plugins page at all — they're not in the### Eventslist, andpackages/web/src/content/docs/plugins.mdxnever mentionsoutput.system/output.messages. The one mutation gotcha that bites everyone is also undocumented: you have to mutate the array in place (splice/push). Reassigning the whole array (output.system = [...]) is a silent no-op — the runtime keeps the original array reference and never reads the replacement. That's the bug already reported in #25754, whose fix-option #2 explicitly asks to "document the in-place requirement."What makes this easy to get wrong: the nearby Compaction example sets
output.prompt = "..."and that does work (it's a scalar), so a reader naturally assumesoutput.system = [...]works too. It doesn't.This PR adds a short subsection under
## Examplesdocumenting both hooks plus a callout about in-place mutation, mirroring the existing### Compaction hooksexample style. Concretely:And the analogous note for
experimental.chat.messages.transform: useoutput.messages.splice(0, output.messages.length, ...next)rather thanoutput.messages = next.I'd add a
:::cautioncallout: "These hooks hand you anoutputobject whose array property is the live payload. Mutate the array in place (push/splice). Reassigning the whole array is a no-op — see #25754." I'll also cross-reference the related (but separate) guidance in #23660 (prefer merging into the primary system block over pushing extra entries for OpenAI-compatible backends) and the ordering note in #19960.I hit this building a cross-platform MCP/hook adapter: our generated bridge has to
output.system.splice(...)exactly because reassignment is dropped — so I can confirm the contract on a real integration.How did you verify your code works?
dev:packages/web/src/content/docs/plugins.mdx(389 lines) has zero mentions ofexperimental.chat.system.transform,messages.transform,splice,in-place,output.system, oroutput.messages; the hooks aren't even in the### Eventslist.packages/plugin/src/index.ts:282-296: both transform hooks are declared with no JSDoc, while the siblingexperimental.session.compacting(:298-308) has a JSDoc block.output.messagesis a silent no-op (requires in-place splice) #25754's repro and the communitymagic-contextplugin, which usesmessages.splice(0, messages.length, ...)rather than reassignment.Checklist