Skip to content

feat(webview): add batchNearby utility for smarter tool ask batching - #1005

Open
easonLiangWorldedtech wants to merge 3 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:fix/batch-nearby-tool-asks
Open

feat(webview): add batchNearby utility for smarter tool ask batching#1005
easonLiangWorldedtech wants to merge 3 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:fix/batch-nearby-tool-asks

Conversation

@easonLiangWorldedtech

@easonLiangWorldedtech easonLiangWorldedtech commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

UI tool ask batching fails when models insert API rows between tool calls

Problem

When using models like qwen, the UI shows multiple separate prompts for the same type of tool call:

Zoo wants to read this file (a.ts)
Zoo wants to read this file (b.ts)
Zoo wants to edit this file (c.ts)

Instead of a single batched prompt:

Zoo wants to read these files:
- a.ts
- b.ts

Root Cause

The existing batchConsecutive() utility only merges tool asks that are truly adjacent in the message array. However, models like qwen insert low-information messages between tool calls during streaming:

  • say: "api_req_started" — API request metadata row
  • say: "api_req_finished" — API request completion row
  • say: "text" with empty content — partial streaming rows
  • say: "reasoning" — hidden reasoning text

These messages break the consecutive chain, so batchConsecutive stops merging.

Current Code (ChatView.tsx ~line 1272)

const readFileBatched = batchConsecutive(filtered, isReadFileAsk, synthesizeReadFileBatch)
const listFilesBatched = batchConsecutive(readFileBatched, isListFilesAsk, synthesizeListFilesBatch)
const result = batchConsecutive(listFilesBatched, isEditFileAsk, synthesizeEditFileBatch)

Proposed Solution: batchNearby() utility

A new utility that merges same-type tool asks even when separated by ignorable messages. It uses three predicates:

  1. isTarget — matches the target tool type (readFile, listFiles, editFile)
  2. isIgnorableBetweenTargets — skips over low-info messages without breaking the batch
  3. isBoundary — stops merging when hitting a semantic boundary

Ignorable Messages (skipped during batching)

  • api_req_started / api_req_finished
  • Empty text rows (say: "text" with no content)
  • Reasoning rows (say: "reasoning")

Semantic Boundaries (stop merging)

  • User feedback (user_feedback, user_feedback_diff)
  • Visible assistant text (text with content)
  • Completion result (completion_result)
  • Checkpoint saved (checkpoint_saved)
  • Errors (error)
  • Context condensation (condense_context)
  • Codebase search results (codebase_search_result)

New Usage in ChatView.tsx

const readFileBatched = batchNearby(filtered, {
  isTarget: isReadFileAsk,
  isIgnorableBetweenTargets, // api_req_started/finished, empty text, reasoning
  isBoundary,                // user_feedback, visible text, completion_result, error, etc.
  synthesize: synthesizeReadFileBatch,
})

Files Changed

  • New: webview-ui/src/utils/batchNearby.ts — the batching utility function
  • New: webview-ui/src/utils/__tests__/batchNearby.spec.ts — 23 comprehensive tests
  • Modified: webview-ui/src/components/chat/ChatView.tsx — replace batchConsecutive with batchNearby

Testing

All 23 unit tests pass, covering:

  • Empty input / no matches / single match passthrough
  • Consecutive vs non-consecutive matching
  • Ignorable messages between targets (api_req_started/finished, empty text, reasoning)
  • Semantic boundaries stopping the merge (user_feedback, visible text, completion_result, checkpoint_saved, error)
  • Multiple batches separated by boundaries
  • Realistic qwen scenarios with API rows between tool calls

Long-term Improvement (separate issue)

Add toolCallGroupId metadata to backend messages so UI can use explicit grouping instead of heuristic-based merging. This would make batching work consistently across all model providers regardless of streaming format differences.

Fixes #1004

Screen Cap

image

Summary by CodeRabbit

Summary by CodeRabbit

  • Improvements
    • Updated chat streaming behavior to batch related tool requests more reliably, even when low-information rows appear between them.
    • Strengthened separation at key semantic points (such as user feedback, visible assistant text, completion outputs, checkpoints, and errors), reducing unintended mixing across tool phases.
  • Tests
    • Added a new test suite covering nearby batching rules, including boundary and ignorable-row handling, multi-batch segmentation, and immutability expectations.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ecdbce37-fef7-4f9e-a01a-9ac6d6a7ea8e

📥 Commits

Reviewing files that changed from the base of the PR and between bbc9723 and 4ef7a8c.

📒 Files selected for processing (5)
  • webview-ui/src/components/chat/ChatView.tsx
  • webview-ui/src/utils/__tests__/batchConsecutive.spec.ts
  • webview-ui/src/utils/__tests__/batchNearby.spec.ts
  • webview-ui/src/utils/batchConsecutive.ts
  • webview-ui/src/utils/batchNearby.ts
💤 Files with no reviewable changes (2)
  • webview-ui/src/utils/tests/batchConsecutive.spec.ts
  • webview-ui/src/utils/batchConsecutive.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • webview-ui/src/utils/batchNearby.ts
  • webview-ui/src/components/chat/ChatView.tsx
  • webview-ui/src/utils/tests/batchNearby.spec.ts

📝 Walkthrough

Walkthrough

The chat view now batches same-type tool asks across ignorable streaming rows, while preserving semantic boundaries. A generic batchNearby utility replaces batchConsecutive, with comprehensive tests for grouping, interruption, ordering, and realistic streamed tool-call sequences.

Changes

Nearby tool-ask batching

Layer / File(s) Summary
Nearby batching utility
webview-ui/src/utils/batchNearby.ts
Adds configurable target, ignorable-row, boundary, and synthesis callbacks, then groups nearby targets while preserving interrupted and boundary rows.
Chat tool-ask integration
webview-ui/src/components/chat/ChatView.tsx
Uses nearby batching for read, list, and edit tool asks, allowing low-information streaming rows between matching requests.
Batching behavior validation
webview-ui/src/utils/__tests__/batchNearby.spec.ts
Tests passthrough, synthesis, separators, boundaries, interruption behavior, immutability, callback grouping, and realistic streamed tool-call sequences.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: awaiting-review

Suggested reviewers: navedmerchant

Sequence Diagram(s)

sequenceDiagram
  participant ChatView
  participant batchNearby
  participant ToolBatchSynthesizer
  ChatView->>batchNearby: classify streamed tool-ask rows
  batchNearby->>batchNearby: skip ignorable rows and stop at boundaries
  batchNearby->>ToolBatchSynthesizer: synthesize grouped tool asks
  ToolBatchSynthesizer-->>batchNearby: return batched tool-ask row
  batchNearby-->>ChatView: return processed chat rows
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the main change: adding batchNearby for smarter tool ask batching.
Description check ✅ Passed The description is detailed and covers the problem, solution, files changed, and testing, though it doesn't follow the exact template headings.
Linked Issues check ✅ Passed The changes implement the batching behavior, boundary handling, and test coverage required by issue #1004.
Out of Scope Changes check ✅ Passed The deletions and test changes are directly tied to replacing batchConsecutive with batchNearby; no unrelated scope is evident.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@easonLiangWorldedtech
easonLiangWorldedtech marked this pull request as draft July 24, 2026 15:29
@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
webview-ui/src/components/chat/ChatView.tsx 80.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webview-ui/src/utils/batchNearby.ts`:
- Around line 42-67: Update batchNearby in webview-ui/src/utils/batchNearby.ts
(lines 42-67) to track skipped isIgnorableBetweenTargets items in
pendingIgnorable, clear them only when a subsequent target is found, and restore
remaining items to result after finalizing the batch. Make no code change in
webview-ui/src/components/chat/ChatView.tsx (lines 1311-1328). Update the
boundary-message test in webview-ui/src/utils/__tests__/batchNearby.spec.ts
(lines 135-152) to expect four items, including the restored api_req_started
item between match-1 and visible text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 421939d2-6c08-49b5-989a-af52340d01f0

📥 Commits

Reviewing files that changed from the base of the PR and between 78c1410 and bd3efaa.

📒 Files selected for processing (3)
  • webview-ui/src/components/chat/ChatView.tsx
  • webview-ui/src/utils/__tests__/batchNearby.spec.ts
  • webview-ui/src/utils/batchNearby.ts

Comment thread webview-ui/src/utils/batchNearby.ts
@easonLiangWorldedtech
easonLiangWorldedtech marked this pull request as ready for review July 24, 2026 15:54
@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 24, 2026
expect(result[1].text).toBe("done")
})

test("non-ignorable non-target message stops batching", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test has no ignorable message before command_output, so pendingIgnorable is empty when the else break fires — the restore path at batchNearby.ts:68–71 is never actually exercised here. What happens with input like [match-1, api_req_started, command_output, match-2]? The api_req_started should be restored to the output but no test currently covers that shape.

if (msg.type !== "say") return false
return (
msg.say === "api_req_started" ||
msg.say === "api_req_finished" ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is api_req_finished reachable here? combineApiRequests (applied when building modifiedMessages) absorbs every api_req_finished into its paired api_req_started or drops it as an orphan, and visibleMessages adds a second filter at lines 1047–1050. If that's right, this arm can never fire and could be removed.

m.say === "checkpoint_saved" ||
m.say === "error" ||
m.say === "condense_context"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The production isBoundary in ChatView.tsx includes codebase_search_result but this fixture doesn't. Worth keeping them in sync — and a test with [readFileAsk, codebase_search_result_msg, readFileAsk] would confirm it stops the batch.

Comment thread webview-ui/src/utils/batchNearby.ts Outdated
/**
* Batch tool asks that are near each other, allowing ignorable messages in between.
*
* Unlike `batchConsecutive` which only merges truly adjacent items, this function

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since ChatView.tsx was the only caller of batchConsecutive, that file and its spec are now dead code. Can you delete them in this PR to avoid confusing future readers. Dead code will start being enforced via knip soon.

@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Jul 26, 2026
taltas and others added 2 commits July 27, 2026 20:34
Replace batchConsecutive with batchNearby in ChatView.tsx to allow
merging same-type tool asks even when separated by ignorable messages
(api_req_started/finished, empty text rows, reasoning).

Semantic boundaries (user_feedback, visible text, completion_result,
checkpoint_saved, error) stop the merge, preserving correct ordering.

This fixes UX where models like qwen insert API request rows between
tool calls, causing UI to show multiple 'Zoo wants to read this file'
instead of a single batched prompt.
When a single target is followed by ignorable messages and then a
boundary (no second target found), the original code silently dropped
those ignorable items — contradicting the documented contract that all
items are preserved in-order.

Fix: track skipped ignorable items in pendingIgnorable, flush them after
the batch result so they're never lost. When bridge succeeds (second
target found), pending items are consumed into the synthesized batch as
expected.
@easonLiangWorldedtech
easonLiangWorldedtech force-pushed the fix/batch-nearby-tool-asks branch from 9d4172a to bbc9723 Compare July 27, 2026 12:37
@github-actions github-actions Bot removed the awaiting-author PR is waiting for the author to address requested changes label Jul 27, 2026
@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI tool ask batching fails when models insert API rows between tool calls

4 participants