Repository navigation
Description
Environment
- OpenCode 1.17.15 (Desktop app and Homebrew CLI, macOS / Darwin 25.5)
- Custom provider:
npm: "@ai-sdk/openai-compatible"→ Crusoe.ai
(api.inference.crusoecloud.com, vLLM 0.24) - Model:
moonshotai/Kimi-K2.6
Problem
Some OpenAI-compatible servers (vLLM 0.24 with the kimi_k2 tool-call parser) occasionally return tool calls whose arguments are invalid JSON containing the model's raw control-token text, e.g.:
{"pattern": "8765", "path": "/repo/app <|tool_calls_section_begin|> <|tool_call_begin|> functions.read:3 <|tool_call_argument_begin|> {"filePath": "/repo/app/server.py"}
That first failure is the provider's bug, and OpenCode handles it gracefully in principle: the call is converted to the invalid tool and the model gets an error result so it can retry (this recovery design is great).
However, the error text quotes the raw unparseable input verbatim:
Invalid input for tool grep: JSON parsing failed:
Text: {"pattern": "8765", "path": "/repo/app <|tool_calls_section_begin|> <|tool_call_begin|> ...
On the next request this string is rendered into the prompt as tool-result content, so the model's own control tokens (<|tool_calls_section_begin|>, <|tool_call_begin|>, <|tool_call_argument_begin|>, </think>) reappear inside message text. Depending on server tokenization these either become real control tokens or strongly prime the model to emit more malformed tool sections. Either way the effect is visible in live sessions: invalid tool calls cluster immediately after the first one (in one session 5 of 16 calls were invalid, two of them consecutive; the raw echoed text was present in each follow-up request).
Steps to reproduce
- Configure a custom openai-compatible provider backed by vLLM 0.24 serving Kimi K2.x with the
kimi_k2tool parser (Crusoe.ai's hostedmoonshotai/Kimi-K2.6exhibits this). - Run any multi-tool agentic task, e.g.
opencode run --auto "explore this repo, edit two files, verify". - Wait for a
JSON parsing failedinvalid-tool event (~1 in 10–20 calls on this provider; edit/write calls with multiline arguments fail most), then observe elevated/consecutive failures on the following turns. Inspectpartrows ("tool":"invalid") to see the echoed<|tool_call...|>text.
Suggested fix
Sanitize provider special-token sequences from the raw text before embedding it in the invalid-tool error message (and/or truncate the echoed text):
const SPECIAL_TOKEN_RE = /<\|\/?[a-z0-9_]+\|>/gi; // ChatML/Kimi/GLM-style control tokens
errorText = rawText.replace(SPECIAL_TOKEN_RE, "[token]");The model does not need to see its own control tokens to correct a malformed call — the JSON error position plus the cleaned text is sufficient — and no legitimate tool argument should contain <|...|> sequences.
Workaround we're using today
A user plugin on experimental.chat.messages.transform that scrubs <|...tool_call...|> sequences from text parts and tool state in history before every request. Works, but this seems to belong in core since it affects any provider whose tool parser can leak (Kimi K2 family on vLLM being the current prominent case).
Related issues
- KImi K 2.7 back to back repeated tool calling failures #34071 (Kimi K2.7 back-to-back repeated tool calling failures — same clustering symptom, different provider)
- Kimi k2.5 has issues with tool calling #20650 (Kimi K2.5 tool calling JSON parse failures)
Happy to provide raw captures, failure-rate measurements (126 instrumented agentic trials against the raw provider API), and the plugin source.
Activity
github-actions commentedon Sep 16, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.