Split `preserve_file_data` into `allow_uploaded_files` (inbound security) and an AG-UI representation opt-in

Merged
DO
4mo ago
+303-63

Summary

preserve_file_data on the UI adapters had quietly grown to cover two unrelated concerns under one name, with a security framing that only ever applied to one of them:

  1. Inbound (security): whether to honor client-submitted [UploadedFile][pydantic_ai.messages.UploadedFile] references. These are dangerous from an untrusted client because the model provider resolves the file_id using the server's own credentials (like a non-HTTP FileUrl), so they're dropped by default.
  2. AG-UI representation: whether to round-trip FilePart/UploadedFile through reserved pydantic_ai_* sidecar activity messages, since the AG-UI protocol has no native slot for them.

It also implied an outbound "files stay on the server" trust model that never actually applied — file content the agent produces is always sent to the client (that's the point of a multimodal UI).

This PR untangles the two:

  • UIAdapter.allow_uploaded_files (new) is the inbound security gate, replacing the base preserve_file_data. Default False; client UploadedFiles are dropped by sanitize_messages unless it's set.
  • preserve_file_data is now a deprecated alias on from_request/dispatch_request — it maps onto allow_uploaded_files and emits PydanticAIDeprecationWarning.
  • AGUIAdapter.preserve_file_data keeps its original representation meaning (the FilePart/UploadedFile sidecar round-trip). It no longer gates inbound security: an UploadedFile reconstructed from a round-tripped activity message is still subject to allow_uploaded_files via the shared sanitize_messages step, so representation and security compose cleanly and independently.

Behavior / breaking notes

  • AG-UI behavior change: honoring client-submitted uploaded files now requires allow_uploaded_files=True in addition to preserve_file_data=True (previously the single flag did both). Emitting the outbound sidecars is unchanged (preserve_file_data=True).
  • Deprecation: passing preserve_file_data= to VercelAIAdapter — via the constructor, from_request, or dispatch_request — still works and emits PydanticAIDeprecationWarning, mapping onto allow_uploaded_files. The alias lives on the concrete VercelAIAdapter (not the base UIAdapter), because preserve_file_data is a real field on AGUIAdapter and can't also be a deprecated member on the base without breaking typing. A custom UIAdapter subclass that used the old base field should switch to allow_uploaded_files.

Notes

  • This work was split out while reconciling #5255; it lands on main first so #5255 (and #6169) can rebase onto the clarified flags.
  • Tests: tests/test_ui.py, tests/test_ag_ui.py, tests/test_vercel_ai.py all green (387 passed); pyright clean on the UI package + test files.

šŸ¤– Generated with Claude Code