Files
AstrBot/tests
lingyun14 74465625ed Fix/reject unresolvable image refs (#10434)
* fix: reject unresolvable image references instead of faking a local path

A plugin can hand over a media reference such as
`/api/v1/files/tokens/<token>`. Neither an existing file nor a valid
payload, it was still returned as a local file, so `to_path()` resolved it
into a path that does not exist on disk (on Windows
`C:\api\v1\files\tokens\<token>`). That fabricated path reached
`req.image_urls` and the prompt, and the real failure only surfaced later
as a warning while preparing model images.

`_materialize_media_ref()` now enforces its own contract:

- A reference that exists but is not a regular file (directory, FIFO,
  socket, device) raises ValueError for every media family.
- Image references must resolve to an existing file, URL or base64 payload.
  Images are read right after resolution, so a missing source is a failure
  rather than a file that may appear later.
- Audio, video and generic file references keep returning a local path that
  does not exist yet: `ensure_wav()` and the STT retry loop rely on that for
  platform races such as NapCat handing over a voice path before it is
  readable.

Supporting changes:

- `to_base64_data(strict=False)` still degrades to `None` when an image ref
  cannot be materialized, which payload assembly relies on to skip bad refs.
- `describe_media_ref()` reports unresolved refs as such instead of calling
  them a local media path.
- Unavailable image warnings now include the original reference.

Refs #10264

* fix: keep pathlib tolerance for over-long and non-regular media refs

`_materialize_media_ref()` replaced `path.exists()` with a bare `path.stat()`
and caught only OSError. pathlib's `exists()` also swallows ValueError, which
Windows raises for paths past the 32767 character limit and for embedded NUL
bytes. A bare base64 payload longer than that limit failed with
`ValueError: stat: path too long for Windows` instead of being decoded, and
audio/video/file references lost the pending-path behaviour for the same input.

`to_base64_data()` had the matching gap: it caught only OSError around
resolution, so a directory or empty reference escaped as ValueError even in
non-strict mode. Payload assembly (openai/anthropic/gemini sources) relies on
`None` there to skip one bad image instead of failing the whole request, and
`is_recoverable_image_error()` already classifies ValueError as recoverable.

Both sites now catch what pathlib and the module's own recovery predicate treat
as a missing or unusable local file. Regression tests cover an oversized bare
base64 image, an oversized pending audio path, and directory/empty references
through the non-strict image path.

* docs: describe the media reference error contract precisely

Review feedback on the pull request:

- `_materialize_media_ref()` only raises FileNotFoundError for a missing file
  URI when the reference is an image. Audio, video and file references return
  the pending path instead, which the `Raises:` section now states instead of
  reading as if it applied to every media family.
- `Image.convert_to_file_path()` can raise ValueError as well: for an existing
  non-regular path, and already for an empty url/file before this branch.

Docstrings only, no behaviour change.
2026-10-09 13:11:50 +08:00
..