Files
OpenMAIC/lib
HEOJUNFOandClaude Opus 5.5 1c70e86a13 fix(media): poll and download Veo videos the Gemini API way (#1695)
* fix(media): poll and download Veo videos the Gemini API way

The Veo adapter targets generativelanguage.googleapis.com but polled
with Vertex AI's models/{model}:fetchPredictOperation and read inline
response.videos[].bytesBase64Encoded. The Gemini API reads operations
with GET v1beta/{name} and returns
response.generateVideoResponse.generatedSamples[].video.uri, fetched
with the same API key.

- Poll with GET v1beta/{operationName}.
- Download the sample URI with x-goog-api-key into a data URL, always
  through the configured base URL's origin and without following
  redirects, like the other adapter calls. Inline bytes are still
  accepted when present.
- Report RAI-filtered operations as a failure with their reasons.
- Catalog: Gemini API model IDs for Veo 3.1 (-preview, plus Lite) and
  only the 16:9 / 9:16 aspect ratios the API accepts.

Fixes #1694

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(media): follow the Veo download redirect through a validated transport

The Gemini API file URI can answer with a redirect to storage (Google's
own example downloads it with `curl -L`), which the download refused, so
a generation could fail after a successful poll.

- VideoGenerationConfig gets an optional downloadFetchImpl. fetchImpl is
  the pinned transport that refuses every 3xx, so the download needs its
  own one.
- lib/server/media-provider-fetch.ts adds mediaDownloadFetch and
  managedMediaDownloadFetch: providerFetch with redirects followed, each
  hop re-validated under the operator address policy, pinned to the
  vetted DNS answers, and stripped of x-goog-api-key once it leaves the
  origin (fetchWithRedirectValidation). withVideoProviderFetch installs
  both transports.
- The server video call sites (the generate route, classroom media
  generation, the agent runtime) inject it; the adapter still does not
  import server code.
- downloadVideo follows redirects only through the injected transport and
  keeps redirect: 'manual' + assertNotRedirected without it.
- Test: a 302 to another origin yields the data URL and the second hop
  carries no x-goog-api-key; without the transport the 302 is refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:00:04 +08:00
..
…