feat(compat): plain-text tool-result rendering via toModelOutput (#127)#171
Conversation
The eight fs/edit/search/bash tools returned typed objects and defined no
toModelOutput, so the model saw every result as a JSON-escaped envelope
(e.g. a Read as {"content":"…"} with literal \n/\t). That taught the
model to reproduce escape sequences in editFile's oldString (breaking the
exact-match contract) and taxed context by ~5-15% on the results that
dominate volume. Claude Code presents these as raw text; this is
trained-format parity at the model-visible layer only.
Define toModelOutput on each tool, returning { type: 'text', value }:
- readFile → content verbatim (the #104 cat -n window rides inside it);
writeFile → a confirmation naming the path.
- editFile/multiEdit → a confirmation with the replacement count + the #104
numbered snippet (shared renderer).
- grep/glob → entries newline-joined + a truncation notice only when capped
(shared renderer).
- bash → stdout, a stderr: section only when non-empty, an exit-code line only
when non-zero, (no output) when clean+empty; multiBash → labeled per-command
sections + a failing-command callout (shared renderer).
Typed execute outputs are unchanged bit-for-bit — programmatic callers
(commitOnBranch/commitFix reading exitCode) and submittedOutput are untouched,
and the tool<BashInput,BashOutput> fakes keep compiling. Composes with #106:
withReminders appends its envelope to this plain-text base (integration test).
Green: compat 232, aitm 882; typecheck + typecheck:tests + biome all clean.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 24 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai full review |
✅ Action performedFull review finished. You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 13 minutes. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai full review |
✅ Action performedFull review finished. You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 5 seconds. |
|
@coderabbitai full review |
✅ Action performedFull review finished. You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 24 minutes. |
Closes #127.
Problem
The eight fs/edit/search/bash tools mounted into every subagent tool set returned typed objects from
executeand defined notoModelOutput, so AI SDK v6 rendered each result as a JSON part — which the OpenRouter mapper serializes withJSON.stringify. The model saw everyReadas{"content":"…"}: file contents with every newline and quote escaped. Two costs: it taught the model to reproduce literal\n/\tineditFile'soldString(breaking the exact-match contract → wasted retries), and it added ~5–15% token overhead on precisely the results that dominate context volume. Claude Code presents these as raw text; this is trained-format parity at the model-visible layer only.Fix
Define
toModelOutputon each tool, returning a single{ type: 'text', value }part:readFilecontentverbatim (the #104cat -nwindow rides inside it)writeFileWrote <path>editFile/multiEditApplied N replacement(s) to <path>.+ the #104 numbered snippet (shared renderer)grep/globbashstderr:section only when non-empty; anexit code: Nline only when non-zero;(no output)when clean + emptymultiBash[command #k failed: …]callout fromfailedAt(shared renderer)Typed
executeoutputs are unchanged bit-for-bit —toModelOutputonly changes the model-visible layer, so programmatic callers (commitOnBranch/commitFixreadingexitCode) andsubmittedOutputobserve identical values, and thetool<BashInput, BashOutput>fakes inworker.test.ts/reviewer.test.tskeep compiling. Renderers apply no caps (bounding is #103's job at the typed layer, compaction is #102's).Composition with #106:
withRemindersuses the wrapped tool's owntoModelOutputas its base, so these plain-text renderers become the base its reminder envelope appends to — covered by an integration test (base text first, then<system-reminder>envelope).Tests
Per-tool
toModelOutputassertions across all four files (raw-content read with no escape sequences; write/edit confirmations; grep/glob join + truncation notice; bash conditional stderr/exit/(no output); multiBash sections + failing-command callout) + the #106 composition test.Scope
compat-only —
localEditTools/localReadToolsalready mount these objects and rendering travels with them; an MCP-filled slot's rendering is the server's concern.background-process.tstools are out of scope (not mounted in any production set; #111 owns them).Green gate
bun run typecheck(both) +typecheck:tests(compat) +bun run lint+bun run test:node— all green (compat 232, aitm 882).