mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-05 20:44:07 +08:00
fix(conversation): scope completed tool call ids to their round
A provider that reuses a tool call id across two rounds of one run
wedges the run permanently. `ToolDispatcher::start_batch` skips any call
whose id is in `ToolBatchState::completed`, so the second call is never
dispatched and never produces a `ToolCompletion`, while
`tool_round::execute` blocks waiting for `calls.len()` results with no
timeout on that path. The client sees the tool call appear and then
nothing: no completion, no further output, no end-stream frame.
The `completed` set is built once per run and never cleared, so it is
run-scoped. A tool call id is only unique within a round, which the
schema already states as `UNIQUE (round_id, call_id)`; the sibling
runtime completed-map is likewise already cleared per round at
output.rs:615.
Clear `completed` when `ExecuteToolRound` begins a new round, tracked
independently of `active_round` so it does not depend on the order in
which ToolRoundStarted and ExecuteToolRound are observed. Replaying a
round still skips the calls that round already committed.
The practical trigger is openai_chat.rs:196, which synthesizes
`call-{index}` from a per-stream index when a provider omits tool call
ids, so `call-0` recurs on every model call. That file is left alone
here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8fdcdd7f84
commit
a42f84cfe7
@@ -158,6 +158,7 @@ impl ConversationOutput {
|
||||
let mut streams = BTreeMap::<usize, ToolCallStream>::new();
|
||||
let mut completions = HashMap::<String, ToolCompletion>::new();
|
||||
let mut completed = HashSet::<String>::new();
|
||||
let mut completed_round = None::<ToolRoundId>;
|
||||
let mut response_text = String::new();
|
||||
let mut response_thinking = String::new();
|
||||
let mut active_round = None::<ToolRoundId>;
|
||||
@@ -425,6 +426,16 @@ impl ConversationOutput {
|
||||
round_id,
|
||||
calls: round_calls,
|
||||
} => {
|
||||
// `completed` exists so that replaying ExecuteToolRound for a round
|
||||
// does not dispatch a call this round already committed. Tool call ids
|
||||
// are only unique *within* a round -- the schema says as much with
|
||||
// `UNIQUE (round_id, call_id)` -- so an id retained from an earlier
|
||||
// round would make start_batch skip a fresh call, and tool_round wait
|
||||
// forever for a result nothing will ever produce.
|
||||
if completed_round.as_ref() != Some(&round_id) {
|
||||
completed.clear();
|
||||
completed_round = Some(round_id.clone());
|
||||
}
|
||||
active_round = Some(round_id.clone());
|
||||
active_tool_calls = round_calls
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user