Fork base: Grok provider, Supabase S3 path fix, JSONB bug, and per-user xAI keys
Custos's initial fork commit bundles four changes under a single "legalos" rebrand: a new xAI Grok provider, a Supabase-S3 compatibility fix, a JSONB query bug, and a per-user key extension. Unrelated changes, clean isolation, easy to split.
The xAI provider lives in backend/src/lib/llm/xai.ts (~212 LOC). It's OpenAI-compatible - streaming and tool calling - registered as grok-4.3 in models.ts. The routing logic in llm/index.ts dispatches on model prefix: grok-* routes to streamXai/completeXaiText, same pattern as claude-* and gemini-*. XAI_MID_MODELS = ["grok-4.3"] slots it into the tabular model tier, and the frontend model dropdown adds an "xAI" group. The OpenAI SDK was already a dependency upstream, so no new dependency.
The Supabase-S3 patch in lib/storage.ts reads region from env (R2_REGION, defaulting to "auto") and sets forcePathStyle: true on the S3 client. Supabase Storage exposes an S3-compatible endpoint that requires path-style requests; Cloudflare R2 works without it. If you've switched from R2 to Supabase Storage and hit credential errors, this is the fix.
The JSONB containment bug: GET /projects was passing a JS array directly to the Supabase ? containment operator, which needs the value JSON-stringified. Results in a 500 for any user with shared projects. Small fix, real bug.
d9295771 extends the per-user API key pattern to xAI. A new user_profiles.xai_api_key column (migration 005_user_xai_key.sql) mirrors the existing claude_api_key/gemini_api_key structure. getUserModelSettings and getUserApiKeys are updated to return xai. The env XAI_API_KEY stays as a tenant-level fallback - if neither is set, calls fail at runtime rather than at configuration time.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?