Repository navigation
feat: pi-parity for chat + agent LLM loops (shared stream layer, reasoning round-trip, developer role) #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
80f94d6
feat: pi-parity for chat + agent LLM loops
ddutchie 8ccd820
fix: auto-scroll the ask-questions form into view in chat + agent
ddutchie 69d8922
fix: keep ask-questions prompt visible while answering (was textarea …
ddutchie d4ffee1
fix: use scalar dep for ask-questions scroll effect (no deps-size war…
ddutchie 5b24d4b
chore: fix lint warnings (unused arg, deps scalar extraction)
ddutchie a5df82c
feat: run chat tool calls in parallel (agent parity), preserve source…
ddutchie 7aebbf0
fix: address PR review — provider-aware agent role, a11y, robustness
ddutchie a55b8ca
feat: render agent tool chips above the reply text (match chat)
ddutchie c759357
docs: correct system-role comments (systemRole, may be developer)
ddutchie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| ## What's new | ||
|
|
||
| ### Fixes | ||
|
|
||
| - **Cairn Agent no longer fails against providers that reject the top-level `system` parameter.** The agent loop sent the system prompt as an Anthropic-style top-level `system` field in the request body (`{ model, system: …, messages: … }`), which strict OpenAI-compatible providers reject outright with *unknown parameter system* — breaking the agent (and compaction) entirely on those providers, while regular chat worked because it sends the prompt as a normal `role: "system"` message. The agent loop and the context-compaction call now both send the system prompt the same way chat does, as a `role: "system"` message, so pi-agent works identically against any endpoint that chat does. | ||
| - **A cut-off agent reply can no longer execute a half-built tool call.** The agent loop never read `finish_reason` from the stream, so a model that hit its output-token limit mid-call could still have its (possibly truncated) arguments executed — the same failure chat guarded against. The agent loop now captures `finish_reason` like chat does: any tool call from a turn that ended with `finish_reason: "length"` is refused, shown as failed, and the model is asked to re-issue with complete arguments. | ||
| - **Agent reasoning now round-trips to the same model, pi-style, instead of being thrown away.** Previously the agent stripped chain-of-thought from every re-sent message, which breaks multi-turn reasoning continuity on models that expect their thinking back (Claude/OpenAI encrypted reasoning, DeepSeek/Qwen `reasoning_content`). The agent now records which reasoning field the provider used and sends it back under that same field — but only to the *same* model that produced it. If you switch provider/model mid-session, the old model's reasoning is converted to plain text (pi behaviour) so the new model keeps the context without risking an unknown-field rejection. | ||
| - **Reasoning is never baked into the reply text anymore.** When a "thinking" model exhausts its output budget having emitted only chain-of-thought, the reasoning stays in its own panel instead of being echoed back as message content — matching pi. Reasoning-only turns are still preserved and round-tripped to the same model. | ||
| - **An interrupted stream can no longer execute a half-built tool call.** A connection that drops mid-call ends the stream without any `finish_reason`. Both chat and the agent now treat that the same as an output-token cut: buffered tool calls are refused, shown as failed, and the model is asked to re-issue with complete arguments. | ||
| - **Empty assistant turns no longer poison the next agent request.** A reasoning model that stopped mid-thought can leave behind an assistant turn with neither content nor tool calls once reasoning is stripped — replaying it makes strict providers reject the whole request with *content or tool_calls must be set*. The agent now filters those turns out of outgoing requests, matching chat. | ||
| - **Chat and the agent now share one LLM streaming layer.** The SSE parsing (reasoning-field capture, `finish_reason` tracking, tool-call buffering), the request-body construction, the truncated-tool-call guard, and the message-preparation step (system prompt, reasoning round-trip, empty-turn filtering) were duplicated between `chat-loop` and the agent loop and had silently drifted. They now live in a single shared module (`llm-stream`) consumed by both loops, so a fix or improvement lands in both at once. | ||
| - **Reasoning models get the `developer` system role on supported providers (pi parity).** The system prompt is now sent as `role: "developer"` instead of `role: "system"` when the selected model is a reasoning/thinking model (from the models.dev catalog) and the provider supports it — the OpenAI convention pi follows. It falls back to `system` on providers known to reject the `developer` role (DeepSeek, NVIDIA, xAI, Together, Moonshot, ZAI, Cloudflare, Ant Ling, on-device Llama, and non-`anthropic/`/`openai/` OpenRouter models), so this can't break a provider that doesn't understand it. | ||
| - **The ask-questions form keeps the question visible while you answer.** The question text used to live as the *placeholder* inside the answer field, so it vanished the moment you started typing — and the form could also arrive out of view if you'd scrolled up. The question is now rendered as persistent text above each answer field (in both chat and the agent), and the thread auto-scrolls to the form so you always see what's being asked next to where you type. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.