Skip to content

fix(opencode): concatenate routing block to last system entry instead of splice(1,0) - #989

Open
aalvarez-dev wants to merge 3 commits into
mksglu:nextfrom
aalvarez-dev:fix/strict-openai-provider
Open

fix(opencode): concatenate routing block to last system entry instead of splice(1,0)#989
aalvarez-dev wants to merge 3 commits into
mksglu:nextfrom
aalvarez-dev:fix/strict-openai-provider

Conversation

@aalvarez-dev

Copy link
Copy Markdown

What / Why / How

fix(opencode): concatenate routing block to last system entry instead of splice(1,0)

Strict OpenAI-compatible providers (NaN builders, and potentially others) reject requests with HTTP 400 when multiple role:system messages are present. The OpenCode adapter's experimental.chat.system.transform hook used output.system.splice(1, 0, routingBlock) to inject the routing block and resume snapshot at index 1 — preserving system[0] for OpenCode's cache-fold.

However, OpenCode serializes system[] entries as separate role:system messages for OpenAI-compatible providers, creating a second system message at index 1 that strict providers reject.

Changed both splice(1,0) calls to concatenate to the last system entry — the same pattern used by engram.ts for memory instructions. This sacrifices the cache-fold optimization for cross-provider compatibility. Anthropic and Moonshot are unaffected (they merge system blocks internally).

Affected platforms

  • Claude Code
  • Cursor
  • VS Code Copilot (GitHub Copilot)
  • JetBrains Copilot
  • Gemini CLI
  • Qwen Code
  • OpenCode
  • KiloCode
  • Codex CLI
  • OpenClaw (Pi Agent)
  • Pi
  • Kiro
  • Antigravity
  • Zed
  • All platforms

Test plan

Updated 32 test assertions in tests/opencode-plugin.test.ts to reflect concatenation behavior:

  • system.length no longer increases for each injection (routing/snapshot appended to existing entries)
  • Index references updated (system[1]→system[0], system[2]→system[last])
  • Strict equality (.toBe) changed to partial match (.toContain) for entries with appended content
  • Cache-fold test uses join() instead of slice(1,-1) since content is merged into existing entries

All 57 plugin tests pass (npm test). TypeScript typecheck passes (npm run typecheck).

Checklist

  • Tests added/updated (TDD: red → green)
  • npm test passes
  • npm run typecheck passes
  • Docs updated if needed (README, platform-support.md)
  • No Windows path regressions (forward slashes only)
  • Targets next branch (unless hotfix)
Cross-platform notes

Our CI runs on Ubuntu, macOS, and Windows.

  • If touching file paths, verify forward-slash normalization on Windows
  • If touching hook paths, verify no backslash separators
  • Use path.join() / path.resolve(), never hardcode / separators
  • Use event-based stdin reading — readFileSync(0) breaks on Windows
  • Use os.tmpdir(), never hardcode /tmp

… of splice(1,0)

Strict OpenAI-compatible providers (NaN, and others) reject requests
when multiple role:system messages are present because they require
the system message at messages[0] only (HTTP 400).

The splice(1,0) pattern was intentionally inserting the routing block
and resume snapshot at index 1 to preserve OpenCode's cache-fold
(llm.ts:117-128). However, OpenCode serializes system[] entries as
separate role:system messages for OpenAI-compatible providers,
creating a second system message at index 1 that strict providers
reject.

Changed to concatenate to the last system entry — same pattern used
by engram.ts. This sacrifices the cache-fold optimization but ensures
compatibility with strict providers. Anthropic/Moonshot are unaffected
(they merge system blocks internally).

Also updated debug log references from system[1] to the injected
content directly.
Update all 57 system.transform test assertions to reflect the new
concatenation approach: routing block and resume snapshot are appended
to the last system entry instead of splice(1,0). System length no longer
increases for each injection.

Affected assertions:
- length expectations (decreased by 1 per concatenated entry)
- index references (system[1]→system[0], system[2]→system[1])
- strict equality (.toBe) → partial match (.toContain) for entries
  that now have appended content
- cache-fold test uses join() instead of slice(1,-1)
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