ollama-adapter: stop leaking raw chain-of-thought into chat replies - #323
Merged
Conversation
Ollama's OpenAI-compatible endpoint never populates the reasoning_content field @intx/inference's OpenAI provider checks for reasoning; gpt-oss and qwen instead wrap their chain-of-thought inline in <think>...</think> inside the ordinary content stream, so it rides through as plain reply text. This module splits those spans out into inference.thinking.delta events instead, at the one place that knows the tokens came from Ollama.
Wraps parseResponse/parseJSONResponse so a <think>...</think> span in a local model's output is reclassified as thinking rather than text before anything downstream sees it. Once correctly typed, the harness already keeps thinking content out of the finalized turn's visible text (see agent-events's existing "drops block kinds with no chat-part equivalent" behavior), and the existing live "Thinking..." strip in turn-activity.tsx now activates for the whole reasoning window instead of never firing — this closes CL-6654's leak of raw model reasoning and internal tool identifiers into the visible transcript. Reclassified thinking events use a fixed sentinel index distinct from the adapter's own text/tool-call indices, since the harness's per-index block map throws a ProtocolMismatchError if two block kinds share an index.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
CL-6654: an agent's visible reply leaked the model's raw reasoning/thinking
text and internal tool identifiers into the timeline (screenshots on the
ticket). Root cause: Ollama's OpenAI-compatible endpoint never populates
the
reasoning_contentfield@intx/inference's OpenAI provider checksfor reasoning — gpt-oss/qwen instead wrap their chain-of-thought inline in
<think>...</think>inside the ordinarycontentstream, so it rodethrough every hop as genuine reply text: harness →
inferenceDoneBlocks(correctly typed
"text", since that's what it actually was) →chat-orchestrator.ts'sTextPart→timeline.tsx's plain bubble.The fix is contained entirely in
packages/ollama-adapter, which alreadywraps
@intx/inference's built-in OpenAI adapter for this provider: it nowalso wraps
parseResponse/parseJSONResponseto reclassify a<think>span's tokens as
inference.thinking.deltabefore anything downstreamever sees them. Once correctly typed:
vendor/intx/hub-sessions/event-collector.ts)already turns a
type: "thinking"content block into a"reasoning"part, and
packages/agent-events'sinferenceDoneBlocksalready dropsit from the finalized reply (see its existing "drops block kinds with no
chat-part equivalent" test) — no visible chain-of-thought disclosure by
default, per the product call on this ticket.
TurnActivityStrip"Thinking…" indicator(
packages/chat-ui/src/turn-activity.tsx) starts correctly firing forOllama models for the first time — it already listens for
inference.thinking.deltaand previously never received one from thisprovider, so gpt-oss/qwen turns had no working-indicator at all.
Giphy tool...") are dropped along with the rest of the reasoning span,
so no raw
@corbits/...paths reach the transcript either.This closes the leak without inventing a new UI surface — react-ui's
ReasoningPartViewdisclosure andtool-activity.ts's natural-languagetool rows already exist and are simply routed to correctly now.
Everything upstream (
@intx/inference) is unmodified; the fix is awrapper in our own package, consistent with how it already wraps
buildRequestfor request-body overrides.Scope cut under timebox
A named, per-agent typing indicator ("Myra is typing…") and
connector-logo tool-activity rows were both raised as follow-on design
asks on this ticket. The generic "Thinking…" strip and text-only tool
rows already ship safely (no leak, no raw identifiers) as of this PR;
the identity/logo polish is filed as a follow-up rather than blocking this
fix.
Live browser verification (
agent-browseragainst a running Ollamamodel) was not completed in this pass — the fix is validated at the unit
level against the actual vendored harness's block-indexing behavior
(
vendor/intx/hub-sessions) and@intx/inference's real event shapes.Recommend a manual pass with a real gpt-oss/qwen model before merge.
Test plan
packages/ollama-adapter/src/think-tags.test.ts— whole/split<think>spans reclassified, ordinary text and tool-call eventspass through untouched, thinking events never collide with the
text index the harness's block map keys on
WORKBENCH_CHECK_SINCE=origin/main bun run testWORKBENCH_CHECK_SINCE=origin/main bun run typecheckbun run lintonly the polished reply, no
<think>text, no raw tool paths