Expose the xAI Imagine image and video surfaces on top of the `xai` provider, and make the shared OpenAI video-task layer survive the production configuration they need. Native video requests live under /v1 (generations, edits, extensions, with /v1/videos as a creation alias that only selects xAI candidates); the OpenAI-compatible adapter stays under /openai/v1/videos and maps `seconds` / `size` onto numeric duration, aspect ratio and resolution. Clients receive an opaque Aether task ID scoped to the owning user; polling uses the upstream task ID and the original credential, and completed downloads fetch the returned media URL without forwarding provider authorization to the media host. Three fixes to the shared video layer are required for this to work outside tests: - OpenAI/xAI task persistence now supplies a stable 16-character short_id, which the PostgreSQL schema requires. Existing rows keep their original value across reconstruction, so no schema change or historical rewrite is needed. - Task retrieval and content downloads are admitted by the production GET execution gate, and reconstructed tasks resolve proxy nodes, system proxy defaults, tunnel affinity and transport profiles through the same deployment resolver used for creation. A configured proxy route no longer silently becomes a direct request after restart. - When the gateway also serves the frontend, /openai/v1/videos and its subpaths bypass the static SPA handler. Otherwise a video query returns HTTP 200 with text/html instead of the task JSON. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.3 KiB
xAI provider behavior
The following rules preserve the provider-specific behavior of the xai provider
across Aether's request and transport layers.
Responses and tools
- HTTP requests drop
previous_response_id. Clients must supply conversation history; this provider does not add an HTTP response-ID history store. metadata.user_idis removed. Claude clients copy it onto converted Responses bodies and xAI rejects the field.- Preserve requested
reasoning.encrypted_content. On a native Responses-to-Responses hop, keep provider-owned input items instead of rebuilding them through the canonical format. xAI encrypted reasoning may have IDs that do not use OpenAI'srsprefix. Aether's Gemini signature carriers remain excluded from xAI replay. - The replay policy is selected from the configured provider type. A model called
grok-*on another provider does not opt into that policy. WebSocket continuation metadata retains the selected policy across reconnects. - A regular client function called
web_searchremains a function. Claude hosted search choices are resolved against the original typed tool declaration, including declarations with a different name. - When only
image_generationis allowed, keep only that tool and retain the requestedautoorrequiredmode. For mixed allowed-tool lists, remove the image choice while preserving the other allowed entries, as required by xAI's tool-choice schema. - Reasoning effort is stripped for models that do not accept it.
- OpenAI-style image reference aliases in a request body are rewritten to xAI's shape without touching chat message parts.
Routing and credentials
OAuth requests default to https://cli-chat-proxy.grok.com/v1; API-key or
using_api=true requests default to https://api.x.ai/v1. Explicit custom gateways
are preserved. Compact remains on the official endpoint. CLI identity headers are
applied where the selected upstream requires them.
Account binding uses the xAI device code flow: the gateway requests a device code, the operator authorizes it out of band, and the gateway polls for the token set. There is no local callback listener, so headless deployments can bind accounts. Refresh tokens can also be imported individually or in batches, and are rotated on refresh.
Quota refresh reads /user and /billing?format=credits and stores a structured
usage snapshot. A prepaid balance keeps an account selectable after the weekly
allowance is exhausted. API-key accounts skip the subscription billing surface.
Images and videos
OAuth media requests default to https://cli-chat-proxy.grok.com/v1; API-key or
using_api=true requests default to https://api.x.ai/v1. Explicit custom gateways
are preserved. Compact remains on the official endpoint. CLI identity headers are
applied to media requests and restored when a persisted video task's polling transport
is reconstructed.
Aether's OpenAI-compatible task parser accepts xAI's request_id creation field,
status aliases such as pending and done, nested video.url and video.duration,
and failure payloads containing code / error without a status. Existing OpenAI
id takes precedence. The client receives Aether's local task ID; polling uses the
upstream task ID and selected credential. Completed video downloads use the returned
media URL without forwarding provider authentication headers to the media host.
Public video protocols
The xAI provider supports two video surfaces:
| Operation | xAI native | OpenAI compatible |
|---|---|---|
| Create | POST /v1/videos/generations |
POST /openai/v1/videos |
| Edit / extend | POST /v1/videos/edits, POST /v1/videos/extensions |
— |
| Retrieve | GET /v1/videos/{request_id} |
GET /openai/v1/videos/{id} |
| Download | use the returned video.url |
GET /openai/v1/videos/{id}/content |
For xAI, POST /v1/videos is a native creation alias. Other providers retain
Aether's existing OpenAI-compatible /v1/videos behavior. xAI callers using
OpenAI seconds / size parameters must use /openai/v1/videos. The adapter
maps these to numeric duration, aspect_ratio, and resolution; it also adapts
image references. This implementation defaults to 4 seconds, portrait, and 720p,
clamps duration to 1-15, and validates inputs. Explicit native requests retain
native parameters and additional provider fields.
Default xAI creation targets /videos/generations on the selected upstream host.
Explicit custom endpoint paths still take precedence. Native generation, editing,
and extension paths only select xAI provider candidates.
Native creation returns request_id; native retrieval preserves done, nested
video.url, and provider fields such as respect_moderation. The identifier is
an opaque Aether task ID so queries remain scoped to the owning user and pinned
to the original upstream task and credential. The explicit /openai/v1/videos
surface projects id, completed, and video_url.
The task row records the native client protocol as xai:video, while its provider
transport remains openai:video. This survives restart without storing request
bodies or credentials. Raw native responses are cached only in memory; after
reconstruction the gateway refreshes from the original provider to recover its
response fields, including for completed tasks. If refreshing is unavailable,
the stored task still provides the native status and media URL projection.
OpenAI/xAI task persistence supplies a stable 16-character short_id, as required
by the PostgreSQL schema. Existing rows retain their original short ID across
reconstruction, including legacy embedded snapshots. This internal identifier is
separate from the opaque local task ID returned to clients; no schema change or
historical row rewrite is needed.
Task retrieval and content downloads are admitted by the production GET execution gate. Reconstructed tasks resolve proxy nodes, system proxy defaults, tunnel affinity, and transport profiles through the same deployment resolver used for creation; configured proxy routes must not silently turn into direct requests after restart.
Runtime configuration
Standalone Rust deployments must set
AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=rust-authoritative and restart the
gateway to enable video task retrieval, polling, and content downloads. The CLI's
legacy default is python-sync-report: creation can return a task ID in that mode,
but the local task read/refresh paths are disabled and may return HTTP 503.
When the gateway also serves the frontend, /openai/v1/videos and its subpaths
must bypass the static SPA handler and be mounted as API routes. Otherwise a
successful-looking HTTP 200 response to a video query may contain text/html
instead of the task's JSON response. The lifecycle regression includes the static
frontend to cover this production configuration.
Regression coverage
The format tests cover client and hosted search choices, image-only and mixed tool restrictions, encrypted reasoning replay, image reference rewriting, and unchanged OpenAI replay restrictions. Transport tests cover OAuth/API-key/custom routing and media identity headers. Video-task tests exercise creation, polling, terminal projection, persistence fields, content-download planning, and status-less errors using local fixtures. They do not make paid generation requests.
The HTTP regression exercises all native creation paths and the compatibility
prefix through the public router and candidate planner, then checks polling,
cross-user denial, persistence, retrieval from a fresh gateway instance, and downloads
through both prefixes without leaking authorization to the media host. It uses the
real HTTP executor and a managed proxy node backed by a local test server, with no
execution-runtime override. The background poller also has a real HTTP proxy-node
regression, so production method guards and transport reconstruction are exercised.
CI also runs the same HTTP lifecycle with the PostgreSQL repository and the
production column constraints/indexes in an isolated temporary table. This catches
persistence failures that the in-memory repository cannot expose. The test uses
local initdb, postgres, and pg_ctl (already provided by the gateway CI job),
or an explicit AETHER_TEST_DATABASE_URL pointing to an isolated test database.
cargo test -p aether-ai-formats -p aether-provider-transport -p aether-video-tasks-core --lib
cargo test -p aether-gateway --lib xai