Skip to content

Commit cf55f3c

Browse files
committed
fix: improve OpenAI stream handling for anomalous tool-call chunks
- filter id-only orphan tool-call chunks without dropping finish metadata - reattach pending tool-call ids to subsequent payload chunks that omit ids - detect snapshot-style tool argument chunks and replace buffered arguments - add unit tests for OpenAI normalization and tool-call argument replacement - add OpenAI stream integration fixtures for orphan, mixed-chunk, and snapshot cases
1 parent 7b7ca91 commit cf55f3c

16 files changed

Lines changed: 798 additions & 12 deletions

src/crates/ai-adapters/src/client/response_aggregator.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub(crate) async fn aggregate_stream_response(
4747
id,
4848
name,
4949
arguments,
50+
arguments_is_snapshot,
5051
} = tool_call;
5152

5253
if let Some(tool_call_id) = id {
@@ -85,7 +86,11 @@ pub(crate) async fn aggregate_stream_response(
8586

8687
if let Some(tool_call_arguments) = arguments {
8788
if pending_tool_call.has_pending() {
88-
pending_tool_call.append_arguments(&tool_call_arguments);
89+
if arguments_is_snapshot {
90+
pending_tool_call.replace_arguments(&tool_call_arguments);
91+
} else {
92+
pending_tool_call.append_arguments(&tool_call_arguments);
93+
}
8994
}
9095
}
9196
}

src/crates/ai-adapters/src/stream/stream_handler/gemini.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,13 +200,15 @@ mod tests {
200200
id: None,
201201
name: Some("get_weather".to_string()),
202202
arguments: Some("{\"city\":".to_string()),
203+
arguments_is_snapshot: false,
203204
};
204205
state.assign_id(&mut first);
205206

206207
let mut second = UnifiedToolCall {
207208
id: None,
208209
name: Some("get_weather".to_string()),
209210
arguments: Some("\"Paris\"}".to_string()),
211+
arguments_is_snapshot: false,
210212
};
211213
state.assign_id(&mut second);
212214

@@ -225,6 +227,7 @@ mod tests {
225227
id: None,
226228
name: Some("get_weather".to_string()),
227229
arguments: Some("{}".to_string()),
230+
arguments_is_snapshot: false,
228231
};
229232
state.assign_id(&mut first);
230233
state.on_non_tool_response();
@@ -233,6 +236,7 @@ mod tests {
233236
id: None,
234237
name: Some("get_weather".to_string()),
235238
arguments: Some("{}".to_string()),
239+
arguments_is_snapshot: false,
236240
};
237241
state.assign_id(&mut second);
238242

@@ -252,11 +256,13 @@ mod tests {
252256
id: None,
253257
name: Some("grep".to_string()),
254258
arguments: Some("{}".to_string()),
259+
arguments_is_snapshot: false,
255260
};
256261
let mut second = UnifiedToolCall {
257262
id: None,
258263
name: Some("read".to_string()),
259264
arguments: Some("{}".to_string()),
265+
arguments_is_snapshot: false,
260266
};
261267

262268
first_state.assign_id(&mut first);

0 commit comments

Comments
 (0)