Add GIF to the view_image allowlist: map the .gif extension to
image/gif and detect the GIF87a/GIF89a magic bytes so the existing
extension/content cross-check accepts GIFs instead of rejecting them
as an unsupported format. Covered by a new success test.
* fix(agents): stop persisting base64 image data in checkpoint state (#4138)
The viewed_images state field stored full base64-encoded image data,
which was duplicated across every subsequent checkpoint (O(n * steps)
growth). A single 1MB image viewed early in a conversation would be
re-stored in every checkpoint for the rest of the session.
Changes:
- ViewedImageData: replace base64 field with lightweight metadata
(mime_type, size, actual_path)
- view_image_tool: store only metadata in state, no base64 encoding
- ViewImageMiddleware: read image files from disk on-demand in
before_model and encode base64 temporarily for the model call
- Update all tests to use the new metadata-only format
This is the first step of #4138. The base64 data is no longer in
persistent state, but the injected HumanMessage (with base64 content)
still appears in the checkpoint for the step where it was injected.
Checkpoint retention policies and large tool result dedup are separate
follow-up items.
* fix(agents): address review feedback on #4140
- view_image_tool: remove stale 'convert to base64' comment, replace with
'validate contents'; drop redundant image_size reassignment and add a
TOCTOU guard that rejects files changed between stat() and read().
- view_image_middleware: extract _read_image_as_data_url helper that
re-checks size against the recorded value AND the absolute cap
(_MAX_IMAGE_BYTES). Document the trust assumption for actual_path
(server-set, not client-settable) in the helper docstring.
- view_image_middleware: abefore_model now runs the blocking read+encode
via asyncio.to_thread to avoid stalling the event loop on up to 20MB
images.
- tests: add coverage for OSError during read, file-changed-since-view
(TOCTOU), and size-exceeds-cap branches.