fix: tolerate partial Responses output items - #10108
Conversation
Some OpenAI-compatible Responses providers omit envelope fields such as output item id/status in the final response.completed event while still returning usable content, reasoning summaries, usage, and function call call_id values. Relax the final output item deserialization for message, reasoning, and function_call items to match the fields Goose actually consumes. Function calls still require either call_id or id before producing a tool request, so Goose does not synthesize empty tool call identifiers. Add regression coverage for message and function_call output items that omit id/status, plus a guard that rejects function calls missing both call_id and id.
DOsinga
left a comment
There was a problem hiding this comment.
This is a clean, well-scoped fix — thanks! Making the streaming ResponseOutputItemInfo fields optional to match the non-streaming ResponseOutputItem is the right call, and the tests exercise the real parser end-to-end.
One thing worth following up on (here or in a separate PR): process_streaming_output_items and responses_api_to_message are now structurally the same 3-variant match, and this PR improved the function-call id handling on the streaming side (call_id.unwrap_or(id) + proper error) but left the non-streaming path at responses_api_to_message still using call_id.clone().or_else(|| id.clone()).unwrap_or_default() — i.e. the empty-string-id footgun you're fixing here still exists on that twin. It'd be nice to either apply the same fix there or unify the two into a shared helper so they can't drift. Not a blocker for this PR.
Approving.
* main: fix: tolerate partial Responses output items (#10108) fix(databricks): always use OpenAI-compatible "parameters" for tools (#10101) fix(providers): unescape shell-escaped image paths (#10098) chore(deps-dev): bump js-yaml from 4.1.1 to 4.2.0 in /documentation (#10107) regenerate schema (#10166) chore(deps): bump actions/stale from 10.1.1 to 10.3.0 (#10050) chore(deps): bump actions/attest-build-provenance from 4.1.0 to 4.1.1 (#10049) chore(deps): bump pnpm/action-setup from 6.0.8 to 6.0.9 (#9892) Update EmpirioLabs provider display name to EmpirioLabs AI (#10126) add declarative provider support to goose-providers crate (#9992) fix(desktop): seed mac auto-update config (#10132) Update release testing instructions (#10122)
Summary
Some OpenAI-compatible Responses providers return usable
response.completedevents while omitting envelope fields on final output items, such asidandstatus.For example, a provider may return a final output item like:
{"type":"message","role":"assistant","content":[...]}or a function call like:
{"type":"function_call","call_id":"call_abc","name":"shell","arguments":"{...}"}The current streaming parser rejects those final
response.completedevents before Goose can consume the data that is actually needed. This causes the UI to display the streamed answer/tool call and then append a final stream decode error.This change relaxes only the final output item envelope fields that Goose does not require:
reasoning.idis optionalmessage.idandmessage.statusare optionalfunction_call.idandfunction_call.statusare optionalFunction calls still require an identifier before producing a tool request. Goose now uses
call_idfirst, then falls back toid, and returns an error if both are missing. This avoids synthesizing empty tool call identifiers.This mirrors the practical handling used by OpenCode / AI SDK style Responses streaming, where function call handling is keyed by
call_idand the final completed event is not treated as invalid solely because envelope fields are absent.Tests
cargo test -p goose-providersResult:
342 passedlocally.