Skip to content

fix(agent): broadcast tool.result per call as it settles, not after the round - #117

Open
w0nche0l wants to merge 1 commit into
mainfrom
dennisjeong/dev-1067-broadcast-tool-result-per-call
Open

fix(agent): broadcast tool.result per call as it settles, not after the round#117
w0nche0l wants to merge 1 commit into
mainfrom
dennisjeong/dev-1067-broadcast-tool-result-per-call

Conversation

@w0nche0l

Copy link
Copy Markdown
Contributor
Summary, root cause, and test plan (AGENT-WRITTEN)

Fixes DEV-1067

Problem

When a model emits multiple tool calls in one turn, executeToolRound awaited Promise.allSettled over the entire round before broadcasting any tool.result / tool.call_output events. With serial execution (maxConcurrency: 1), an early call could be finished for minutes while its chip in the chatroom still showed "Running...", and then every chip flipped to completed at once when the last call finished.

Change

  • executeToolRound now attaches a settle-time finalizer to each tool-call promise (finalizeSettledRoundCall / finalizeRejectedRoundCall): the tool.result, legacy tool_result, and tool.call_output broadcasts — and computeToolOutputForModel — run the moment that call settles.
  • Model-facing output assembly is unchanged: after Promise.all, an ordered loop collects the pre-finalized function_call_outputs in call order, so the follow-up request input and prompt-cache behavior are byte-identical.
  • Async (background/deferred) invocations now start their grace-window race at their own settle time — at least as early as the previous post-allSettled parallel kickoff, so no (N-1)×graceMs stagger is reintroduced.
  • Extracted the executeSingleToolCall outcome union into a named SingleToolCallOutcome type and added a pushToolCallOutputEvent helper (the push was previously duplicated at four sites).

Behavior notes

  • tool.call_output events on the wire are now emitted in settlement order (previously call order). tool.result events were already only meaningful per call. The model-facing function_call_output array order is unchanged.
  • If computeToolOutputForModel (toModelOutput) throws for one call, executeToolRound still rejects, as before; sibling calls' broadcasts now still fire first since they finalize independently.

Tests

New tests/unit/tool-round-settle-broadcast.test.ts:

  • A fast call's tool.result + tool.call_output appear on the turn broadcaster while a slow sibling in the same round is still gated, and the follow-up request's function_call_outputs stay in call order (slow first) despite settling second.
  • A throwing call broadcasts its error result as it settles, without waiting for the slow sibling.

Full suite: 103 files / 1219 tests pass; lint and typecheck clean.

Release

Changeset included (patch). Follow-up after release: bump @openrouter/agent in openrouter-web (the existing patches/@openrouter%2Fagent@0.10.0.patch there will need re-basing onto the new version).

Made with Cursor

…he round (DEV-1067)

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant