diff --git a/src/cai/sdk/agents/models/chatcompletions/message_builder.py b/src/cai/sdk/agents/models/chatcompletions/message_builder.py index e75f32ef..a1f8968f 100644 --- a/src/cai/sdk/agents/models/chatcompletions/message_builder.py +++ b/src/cai/sdk/agents/models/chatcompletions/message_builder.py @@ -424,6 +424,14 @@ class Converter: arguments = "{}" elif isinstance(arguments, dict): arguments = json.dumps(arguments) + else: + # Truncated/streamed-then-cut function call args (e.g. "{") + # are not valid JSON and the upstream proxy rejects the whole + # request with HTTP 400, wedging the conversation. + try: + json.loads(arguments) + except (TypeError, ValueError): + arguments = "{}" tool_calls_param.append( ChatCompletionMessageToolCallParam( id=tc.get("id", "")[:40], diff --git a/src/cai/sdk/agents/models/openai_chatcompletions.py b/src/cai/sdk/agents/models/openai_chatcompletions.py index 47d871b0..7156de4d 100644 --- a/src/cai/sdk/agents/models/openai_chatcompletions.py +++ b/src/cai/sdk/agents/models/openai_chatcompletions.py @@ -2676,6 +2676,19 @@ class OpenAIChatCompletionsModel(Model): type="response.content_part.done", ) + # Repair partially-streamed function calls before emission so a + # truncated args string like "{" cannot poison conversation history + # and trigger HTTP 400 on the next request. + for _fc in state.function_calls.values(): + _args = _fc.arguments or "" + if not _args.strip(): + _fc.arguments = "{}" + else: + try: + json.loads(_args) + except (TypeError, ValueError): + _fc.arguments = "{}" + # Actually send events for the function calls for function_call in state.function_calls.values(): # First, a ResponseOutputItemAdded for the function call