Files
Aether/docs/operations/xai-provider.md
T
stabeyandClaude Opus 5 04c4a97766 feat(xai): add native image and video endpoints
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>
2026-09-14 21:17:21 +08:00

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_id is 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's rs prefix. 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_search remains a function. Claude hosted search choices are resolved against the original typed tool declaration, including declarations with a different name.
  • When only image_generation is allowed, keep only that tool and retain the requested auto or required mode. 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