Python: fix: add missing status field on function_call / function_call_output items#4704
Python: fix: add missing status field on function_call / function_call_output items#4704ltwlf wants to merge 1 commit intomicrosoft:mainfrom
Conversation
…ut items The Responses API requires a status field on function_call and function_call_output items but we weren't always sending it. This caused 400 errors when the API rejected requests without it. Now we default status to 'completed' for both item types while still allowing explicit overrides via additional_properties. Also passes through the filename property from AG-UI media parts so binary attachments don't lose their original filenames. Fixes microsoft#4701
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 86%
✓ Correctness
The diff adds filename passthrough for AG-UI media parts via additional_properties and fixes a latent bug where accessing
content.additional_properties.get("status")would crash with AttributeError when additional_properties is None. The status field for function_call and function_call_output is now always emitted, defaulting to "completed". The changes and tests are correct.
✓ Security Reliability
The diff adds filename passthrough for multimodal media parts and fixes a latent AttributeError in the responses client where
content.additional_properties.get('status')would crash whenadditional_propertiesis None. The fix correctly guards the attribute access. One minor concern is that filenames from untrusted input are passed through without sanitization, and theor "completed"pattern silently coerces any falsy status value (including an explicit empty string) to "completed", which may mask unexpected upstream values. No blocking security or reliability issues found.
✓ Test Coverage
Test coverage for both changes is thorough. The filename passthrough feature is tested across all major code paths (base64, URL, data URI, binary ID) plus the absent-filename case. The status-default change is covered with both default and explicit-status tests for function_call and function_call_output. One minor gap: the base64 strict-decode-failure fallback paths (lines 310–330) also thread
additional_propsthrough but have no filename-specific test, though the logic is mechanically identical to the tested paths.
✗ Design Approach
The filename passthrough via
additional_propertiesis acceptable given thatContenthas no nativefilenamefield andadditional_propertiesis the designed extension mechanism. The more significant concern is in_responses_client.py: the diff changes two independent behaviors in one stroke — (1) it fixes a realAttributeErrorbug (calling.get()on a possibly-Noneadditional_properties) and (2) it hardcodes'completed'as the default status for bothfunction_callandfunction_call_output. Thefunction_call_outputcase is particularly risky: previouslystatuswas never emitted for this type; now it is always emitted. If the OpenAI Responses API does not accept astatusfield onfunction_call_outputobjects, this silently changes a working serialization into one that may be rejected or misinterpreted. The null-safety fix and the behavioral default are conflated in a way that makes the change harder to reason about and revert.
Flagged Issues
- In
_responses_client.py, thefunction_call_outputserialization previously omittedstatusentirely; the diff now unconditionally emits"status": "completed". This behavioral change goes beyond theNone-safety fix. If the OpenAI Responses API does not accept astatusfield onfunction_call_output(unlikefunction_call), this will silently introduce a field the API may reject or misinterpret. The null-safety fix should be separated from the decision to default to"completed", and the default should only be added once the API contract forfunction_call_outputis confirmed to accept it.
Suggestions
- The
filenameextracted from user-suppliedpartdict is stored inadditional_propertieswithout validation. If downstream code ever uses this filename for filesystem operations, it could be susceptible to path-traversal attacks (e.g.,../../etc/passwd). Consider validating or sanitizing the filename (e.g., stripping path separators, limiting length) at this trust boundary. - The
or "completed"pattern on lines 1189 and 1237 coerces any falsy status value (empty string,None,0) to"completed", silently masking unexpected upstream values. Consider using an explicitif status is Nonecheck instead for stricter semantics. - For the
function_callandfunction_resultcases, consider extracting a_get_status(content)helper to avoid duplicating the status-extraction-and-fallback expression and to make the fallback policy easy to find and change. - In
_message_adapters.py, the string key"filename"used to stash the value inadditional_propertiesis referenced only by convention. Defining a module-level constant (e.g.,_FILENAME_KEY = "filename") would make the coupling between writer and reader explicit and refactor-safe. - Consider adding a test for the base64 lenient-decode fallback path (strict decode fails, lenient succeeds) with a filename, to cover the
additional_propertiesthreading on lines 310–316. This is a low-risk gap since the pattern is identical to the tested paths. - Consider a test with an empty-string filename (e.g.,
"filename": "") to verify it is treated the same as absent (theif filenameguard filters it out, resulting inadditional_properties=None).
Automated review by moonbox3's agents
| return { | ||
| "call_id": content.call_id, | ||
| "id": fc_id, | ||
| "type": "function_call", |
There was a problem hiding this comment.
Using or "completed" means any falsy status value (e.g., "") is silently replaced with "completed". Consider an explicit None check for stricter semantics.
| "type": "function_call", | |
| status = ( | |
| content.additional_properties.get("status") if content.additional_properties else None | |
| ) | |
| if status is None: | |
| status = "completed" |
| "status": fn_result_status, | ||
| } | ||
| case "function_approval_request": | ||
| return { |
There was a problem hiding this comment.
function_call_output did not previously emit a status field at all. Unconditionally adding "status": "completed" is a separate behavioral change from the None-safety fix, and the or "completed" pattern also silently coerces falsy values. Only emit status here if the API contract is confirmed to accept it and the field was explicitly supplied, and use an explicit None check rather than or.
| return { | |
| fn_result_status = content.additional_properties.get("status") if content.additional_properties else None | |
| result: dict[str, Any] = { | |
| "call_id": content.call_id, | |
| "type": "function_call_output", | |
| "output": output, | |
| } | |
| if fn_result_status: | |
| result["status"] = fn_result_status | |
| return result |
Fixes #4701
Hey! So the Responses API expects a `status` field on both `function_call` and `function_call_output` items, but we weren't always including it. This leads to 400 errors when the API rejects the request.
What changed
Tests
Added 9 new tests covering:
All existing tests still pass. Ran ruff, pyright, and the full test suite — everything's clean.