feat: qwen code support#502
Conversation
Qwen emits one type=assistant JSONL line per model output, including every tool-call iteration in a multi-step turn. Each iteration's parts typically contain only a thought + functionCall, with the final iteration carrying the user-facing text. The previous parser counted each iteration as a distinct message, inflating MessageCount well beyond what other agents report for the same turn (one real session reported 142 messages over 2.5 minutes — 138 were tool-call-only iterations of a single turn). Coalesce consecutive tool-call-only assistant entries into the next text-bearing assistant entry, aggregating their thinking text and token usage. A trailing run with no text follow-up flushes as a single coalesced assistant message so data isn't lost. Token usage sums output and tracks peak (input + cache_read) for context, keeping session-level TotalOutputTokens / PeakContextTokens arithmetically consistent. Adds fixture tests for the coalesce, trailing-tool-call, aborted (no-response), and short-clean session shapes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
Qwen functionCall parts on assistant entries and functionResponse parts on user entries were discarded, so tool-use sessions stored no ToolCalls/ToolResults and HasToolUse stayed false; tool-call-only assistant entries with no thought text were dropped entirely. Parse both into ParsedToolCall/ParsedToolResult, aggregate them on qwenAssistantBuffer (folding tool-result-only user entries into the pending turn), and keep tool-only entries pending so the coalesced turn always carries its tool-use evidence. Qwen promptTokenCount already includes cachedContentTokenCount (totalTokenCount = prompt + candidates), so adding the cached count on top of the prompt double-counted cached input and inflated ContextTokens. Normalize like Codex: store max(promptTokenCount - cachedContentTokenCount, 0) as input_tokens, keep cached separately, and use the prompt count as the context total. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
Qwen sessions live at <projectsDir>/<encoded-project>/chats/<session>.jsonl, but WatchSubdirs pointed the watcher at <projectsDir>/chats and classifyPath had no Qwen branch, so fsnotify events for new chats never reached the sync engine. Drop WatchSubdirs so the projects root is watched recursively (matching Claude and iFlow), and add a Qwen case to classifyOnePath. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
qwen stored functionResponse.response (e.g. {"output": "..."}) directly in
ContentRaw, but the shared toolResultContentLength/DecodeContent helpers only
recognise strings, arrays, and the iFlow deep-nested object shape, so paired
qwen tool calls reported a zero ContentLength and an empty decoded result.
Unwrap response.output before storing so the string branch can handle it,
matching the gemini parser. The previous test only checked ContentRaw for a
substring, which passed spuriously; tighten it to DecodeContent + ContentLength
so the decode contract is exercised.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
A qwen assistant entry that carries both text and a functionCall flushed the buffer immediately, so the next functionResponse landed on a fresh pending turn and EOF (or the next text-only entry) emitted a phantom empty assistant message holding the orphaned tool result. The tool_use and tool_result for the same id ended up on different displayed messages and MessageCount was inflated by one per occurrence. Defer the flush until a text-only "closing" entry arrives so intermediate text + tool_call iterations stay attached to their results, and append content across entries in a turn so intermediate text isn't dropped when a later iteration's text overwrites it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
Qwen sessions live at <projectsDir>/<encoded-project>/chats/<id>.jsonl, but the AgentDef pinned WatchSubdirs to "chats" — that pointed the recursive watcher at <projectsDir>/chats, which does not exist, so no fsnotify events fired for any Qwen session. Drop the subdir override so the projects root is watched recursively and new project chats directories are picked up as they appear. The path classifier had no Qwen branch either, so even synthetic SyncPaths calls fell through to no-match. Add a branch that maps paths shaped like <qwenProjectsDir>/<encoded-project>/chats/<id>.jsonl to AgentQwen, decoding the project name from the encoded directory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both sides independently fixed the WatchSubdirs/classifier bug (2b32cef vs 70f8842) and converged on the same shape. Resolved by keeping the local explanatory comment on the AgentDef and the stricter classifier (validates session ID, not just .jsonl suffix), while folding in the broader negative-path test cases from the remote. Pulled in remote-only improvements that did not conflict: - 3b1b03f: unwrap functionResponse.response.output so the shared toolResultContentLength/DecodeContent helpers can surface the result text. - 5e2ef7d: defer the buffer flush on assistant entries that carry both text and a functionCall, and append content across entries within a turn, so intermediate text + tool_call iterations stay attached to their results.
roborev: Combined Review (
|
adds results from Qwen Code