fix: reconstruct multimodal `ToolReturnPart.content` via a `ToolReturnContent` discriminator
- Closes #5811
A BinaryContent (or any MultiModalContent) placed in ToolReturnPart.content was silently demoted to a plain dict when messages round-tripped through ModelMessagesTypeAdapter.dump_python(mode='json') + validate_python (the boundary used by to_jsonable_python and many AG-UI / Vercel AI / OTel-instrumentation paths). After round-trip, content was a dict with base64 data as a str, so .data/.media_type/.files etc. no longer worked.
The cause: ToolReturnContent is a recursive TypeAliasType whose Mapping/Any arms won Pydantic's smart-union resolution over the Discriminator('kind')-tagged MultiModalContent branch on the validate_python path (the dump_json/validate_json path worked). This wires the union to an explicit Discriminator that routes a dumped MultiModalContent dict to the multimodal branch — gated on a matching kind plus a type-specific field (url/media_type/file_id) so a user dict that merely reuses one of our kind values stays a plain mapping.
Extracted from #5255 as the narrow bug fix. The UI-adapter multimodal support (preserve_file_data, sidecar activity messages, explicit rehydration) stays in that PR. Note this fix reconstructs BinaryContent but does not yet narrow image BinaryContent to BinaryImage on this path — that narrowing is single-sourced separately in #5801.
Checklist
- Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
- No breaking changes in accordance with the version policy.
- PR title is fit for the release changelog.