diff --git a/docs/refarch/sidecar-proxy.md b/docs/refarch/sidecar-proxy.md new file mode 100644 index 00000000..b41a70c1 --- /dev/null +++ b/docs/refarch/sidecar-proxy.md @@ -0,0 +1,103 @@ +# Reference Architecture: OAuth Sidecar Proxy + +> **Note:** For xAI/Grok models, OpenCode ≥1.15.0 supports native xAI OAuth +> (browser + device-code). The sidecar proxy is no longer required for OpenCode +> deployments. See [docs/xai-proxy.md](../xai-proxy.md) for the recommended +> approach. + +This document describes the **sidecar proxy pattern** used by `xai-proxy` to +provide OAuth-authenticated access to xAI's SuperGrok API for any +OpenAI-compatible agent. + +## When to use this pattern + +- Running agents that **don't** have built-in xAI OAuth (e.g. Hermes, custom agents) +- Centralizing token management across multiple containers in a pod +- Needing a single OAuth session shared by several processes + +## Architecture + +``` +┌─ Kubernetes Pod ──────────────────────────────────────────────┐ +│ │ +│ agent container (any OpenAI-compatible client) │ +│ │ POST /v1/chat/completions │ +│ │ (no auth header needed) │ +│ ▼ │ +│ xai-proxy :9090 │ +│ • Reads OAuth token from PVC │ +│ • Injects Authorization: Bearer header │ +│ • Auto-refreshes 120s before expiry │ +│ │ │ +│ PVC: /home/agent/.openab/xai-proxy/tokens.json │ +└───────────────┼───────────────────────────────────────────────┘ + ▼ + https://api.x.ai/v1 (SuperGrok) +``` + +## How it works + +1. **One-time login** — Run `xai-proxy login-device` on any machine with a + browser. This performs OAuth PKCE device-code flow against xAI and writes + tokens to `~/.xai-proxy/tokens.json`. + +2. **Token seeding** — An init container copies the token from a K8s Secret + into the PVC on first boot. + +3. **Proxy** — `xai-proxy serve` listens on `:9090`, reads the token file, + injects the Bearer header into every upstream request to `api.x.ai/v1`. + +4. **Auto-refresh** — The proxy refreshes the OAuth token 120s before expiry + and writes it back to the PVC (survives pod restarts). + +## Helm deployment + +```bash +# 1. Login locally +xai-proxy login-device + +# 2. Create K8s secret +kubectl create secret generic xai-proxy-tokens \ + --from-file=tokens.json=$HOME/.xai-proxy/tokens.json + +# 3. Deploy with sidecar +helm install openab openab/openab \ + --set agents.mybot.command=opencode \ + --set-json 'agents.mybot.args=["acp"]' \ + --set agents.mybot.image=ghcr.io/openabdev/openab-opencode \ + --set-json 'agents.mybot.extraContainers=[{"name":"xai-proxy","image":"ghcr.io/openabdev/xai-proxy:latest","args":["serve","--bind","0.0.0.0"],"env":[{"name":"XAI_PROXY_TOKEN_PATH","value":"/home/agent/.openab/xai-proxy/tokens.json"}],"ports":[{"containerPort":9090}],"volumeMounts":[{"name":"data","mountPath":"/home/agent"}]}]' \ + --set-json 'agents.mybot.extraInitContainers=[{"name":"copy-tokens","image":"busybox","command":["sh","-c","mkdir -p /dest/.openab/xai-proxy && cp /src/tokens.json /dest/.openab/xai-proxy/tokens.json"],"volumeMounts":[{"name":"xai-tokens-src","mountPath":"/src","readOnly":true},{"name":"data","mountPath":"/dest"}]}]' \ + --set-json 'agents.mybot.extraVolumes=[{"name":"xai-tokens-src","secret":{"secretName":"xai-proxy-tokens"}}]' +``` + +The agent's `opencode.json` points to the local proxy: + +```json +{ + "provider": { + "xai": { + "npm": "@ai-sdk/openai-compatible", + "options": { "baseURL": "http://localhost:9090/v1", "apiKey": "dummy" }, + "models": { "grok-4.3": { "name": "Grok 4.3" } } + } + } +} +``` + +## Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `XAI_PROXY_TOKEN_PATH` | `~/.xai-proxy/tokens.json` | Token file location | +| `RUST_LOG` | `xai_proxy=info` | Log verbosity | + +## Limitations + +- Only useful for agents without native xAI OAuth support +- Browser OAuth (`xai-proxy login`) may be blocked by Cloudflare — prefer `login-device` +- `codex-acp` and `claude-agent-acp` use proprietary auth and cannot use this proxy + +## See also + +- [xai-proxy source](../../xai-proxy/) — Rust implementation +- [docs/xai-proxy.md](../xai-proxy.md) — Quick-start guide (now recommends native OAuth) diff --git a/docs/xai-proxy.md b/docs/xai-proxy.md index 93bbbd44..687d11b0 100644 --- a/docs/xai-proxy.md +++ b/docs/xai-proxy.md @@ -1,140 +1,77 @@ -# xAI Proxy (SuperGrok Sidecar) +# xAI / SuperGrok Integration -xai-proxy is a lightweight Rust sidecar that lets any OpenAI-compatible agent use your **SuperGrok subscription** instead of per-token API credits. It authenticates via OAuth and proxies requests to `api.x.ai/v1`. +## Recommended: Native xAI OAuth (OpenCode ≥1.15.0) -## Architecture +OpenCode now has **built-in xAI OAuth support** — no sidecar proxy needed. -``` -┌─ Kubernetes Pod ──────────────────────────────────────────────┐ -│ │ -│ openab → opencode acp │ -│ │ POST /v1/chat/completions │ -│ ▼ │ -│ xai-proxy :9090 │ -│ • Injects OAuth Bearer token │ -│ • Auto-refreshes 120s before expiry │ -│ │ │ -│ PVC: /home/agent/.openab/xai-proxy/tokens.json │ -└───────────────┼───────────────────────────────────────────────┘ - ▼ - https://api.x.ai/v1 (SuperGrok) -``` +### Setup -## Prerequisites +1. Use `Dockerfile.opencode` with OpenCode ≥1.15.0 (which includes native xAI OAuth). +2. Run `/connect` inside OpenCode and select **xAI Grok OAuth (Headless / Remote / VPS)**. +3. Approve the device-code on any browser. +4. Select your model with `/models` (e.g. `grok-4.3`). -- Active SuperGrok subscription (any tier) -- A machine with browser access (or SSH tunnel) for initial login +OpenCode handles token storage and auto-refresh internally. -## Helm Install +### Helm deployment (native OAuth) ```bash -# 1. Login locally to get tokens -xai-proxy login-device +# 1. Run opencode interactively once to complete device-code login, +# then copy the auth file: +kubectl cp :/home/node/.local/share/opencode/auth.json ./auth.json -# 2. Create K8s secret from token file -kubectl create secret generic xai-proxy-tokens \ - --from-file=tokens.json=$HOME/.xai-proxy/tokens.json +# 2. Create secret from the auth file +kubectl create secret generic opencode-xai-auth \ + --from-file=auth.json=./auth.json -# 3. Deploy with opencode + xai-proxy sidecar +# 3. Deploy — no sidecar needed helm install openab openab/openab \ - --set agents.kiro.enabled=false \ - --set agents.mybot.discord.botToken="$DISCORD_BOT_TOKEN" \ - --set agents.mybot.discord.allowAllChannels=true \ --set agents.mybot.command=opencode \ --set-json 'agents.mybot.args=["acp"]' \ --set agents.mybot.image=ghcr.io/openabdev/openab-opencode \ - --set-json 'agents.mybot.extraVolumes=[{"name":"xai-tokens-src","secret":{"secretName":"xai-proxy-tokens"}},{"name":"opencode-config","configMap":{"name":"opencode-xai-config"}},{"name":"opencode-auth","configMap":{"name":"opencode-xai-auth"}}]' \ - --set-json 'agents.mybot.extraVolumeMounts=[{"name":"opencode-config","mountPath":"/home/agent/opencode.json","subPath":"opencode.json"},{"name":"opencode-config","mountPath":"/home/agent/.config/opencode/opencode.json","subPath":"opencode.json"},{"name":"opencode-auth","mountPath":"/home/agent/.local/share/opencode/auth.json","subPath":"auth.json"}]' \ - --set-json 'agents.mybot.extraInitContainers=[{"name":"copy-tokens","image":"busybox","command":["sh","-c","if [ ! -f /dest/.openab/xai-proxy/tokens.json ]; then mkdir -p /dest/.openab/xai-proxy && cp /src/tokens.json /dest/.openab/xai-proxy/tokens.json; fi"],"volumeMounts":[{"name":"xai-tokens-src","mountPath":"/src","readOnly":true},{"name":"data","mountPath":"/dest"}]}]' \ - --set-json 'agents.mybot.extraContainers=[{"name":"xai-proxy","image":"xai-proxy:latest","args":["serve","--bind","0.0.0.0"],"env":[{"name":"XAI_PROXY_TOKEN_PATH","value":"/home/agent/.openab/xai-proxy/tokens.json"}],"ports":[{"containerPort":9090}],"volumeMounts":[{"name":"data","mountPath":"/home/agent"}]}]' + --set-json 'agents.mybot.extraVolumes=[{"name":"xai-auth","secret":{"secretName":"opencode-xai-auth"}}]' \ + --set-json 'agents.mybot.extraVolumeMounts=[{"name":"xai-auth","mountPath":"/home/node/.local/share/opencode/auth.json","subPath":"auth.json"}]' ``` -## OpenCode Configuration - -Create a ConfigMap for the opencode provider config: +### opencode.json -```bash -kubectl create configmap opencode-xai-config --from-file=opencode.json=- <<'EOF' +```json { "$schema": "https://opencode.ai/config.json", - "model": "xai/grok-4.3", - "provider": { - "xai": { - "npm": "@ai-sdk/openai-compatible", - "name": "xAI (SuperGrok)", - "options": { - "baseURL": "http://localhost:9090/v1", - "apiKey": "dummy" - }, - "models": { - "grok-4.3": { "name": "Grok 4.3" } - } - } - } + "model": "xai/grok-4.3" } -EOF - -kubectl create configmap opencode-xai-auth --from-file=auth.json=- <<'EOF' -{ "xai": "dummy" } -EOF -``` - -## Authentication - -### Device-code flow (recommended for headless) - -```bash -xai-proxy login-device ``` -Prints a URL and code. Open the URL in any browser, enter the code, and authorize. +No custom provider block needed — OpenCode discovers xAI natively when auth is present. -### Browser OAuth (local machine) +--- -```bash -xai-proxy login -``` - -Opens your browser to `auth.x.ai`. Sign in and authorize. Callback is received on `127.0.0.1:56121`. - -### Token refresh - -xai-proxy auto-refreshes the OAuth token 120 seconds before expiry. The refreshed token is written back to the token file (persisted on PVC across pod restarts). +## Alternative: xai-proxy sidecar (legacy) -## Environment Variables +For agents **without** native xAI OAuth (Hermes, custom agents), or for +multi-container pods sharing a single OAuth session, the `xai-proxy` sidecar +is still available. -| Variable | Default | Description | -|----------|---------|-------------| -| `XAI_PROXY_TOKEN_PATH` | `~/.xai-proxy/tokens.json` | Custom token file path | -| `RUST_LOG` | `xai_proxy=info` | Log level | +See [docs/refarch/sidecar-proxy.md](refarch/sidecar-proxy.md) for the full +architecture and deployment guide. -## Token Persistence - -The init container seeds the token from the K8s secret on first boot only. After that, xai-proxy reads and writes the token directly on the PVC. This means: - -- Token refreshes survive pod restarts -- The K8s secret is only needed for initial bootstrap -- To force a token reset, delete `/home/agent/.openab/xai-proxy/tokens.json` from the PVC - -## Standalone Usage (no K8s) +### Quick start ```bash -# Build -cargo build --release - -# Login -./target/release/xai-proxy login-device - -# Serve -./target/release/xai-proxy serve --port 9090 +xai-proxy login-device # one-time device-code login +xai-proxy serve --port 9090 # start proxy -# Use with any OpenAI-compatible client +# Point any OpenAI-compatible client at the proxy export OPENAI_BASE_URL=http://127.0.0.1:9090/v1 export OPENAI_API_KEY=dummy -opencode ``` -## Limitations +## Comparison -- **codex-acp** and **claude-agent-acp** require their own proprietary auth and won't use `OPENAI_BASE_URL` — use opencode or hermes-acp instead -- Browser OAuth (`xai-proxy login`) requires Cloudflare to not block your IP — use `login-device` if blocked +| | Native OAuth | xai-proxy sidecar | +|---|---|---| +| **Requires** | OpenCode ≥1.15.0 | Any OpenAI-compatible agent | +| **Extra container** | No | Yes | +| **Token management** | Built into OpenCode | Proxy handles refresh | +| **Multi-agent sharing** | Each agent needs own auth | Single proxy serves all | +| **Complexity** | Low | Medium |