Skip to content
This repository was archived by the owner on Sep 27, 2026. It is now read-only.

Latest commit

 

History

History
267 lines (191 loc) · 11.7 KB

AGENTS.md

File metadata and controls

267 lines (191 loc) · 11.7 KB

AGENTS.md

Guidance for AI coding agents working in supergrok-zed-rs.

Read this file, RESEARCH.md, and the epic #1 before changing architecture, auth, HTTP contracts, or process lifecycle.

This file is for agents. Humans start with README.md.


Purpose

macOS menubar app that runs an in-process OpenAI-compatible HTTP proxy so Zed can use SuperGrok (OAuth subscription) or an xAI API key.

Protocol reference (behavior to match): andypduk/supergrok-zed (Python FastAPI + Swift menubar).

Process model is different (do not copy LaunchAgent lifecycle):

Topic Python reference This repo (locked)
Proxy process LaunchAgent / separate serve In-process with the app
Quit app Connector often keeps running Quit stops the proxy
UI Swift LSUIElement GPUI settings + tray-icon (or NSStatusItem)
Config paths ~/.config/supergrok-zed/ Same (drop-in)
Platform macOS macOS only first release

Workspace map

Target Cargo workspace (create as work lands; see issues #9+):

Crate Owns Must not own
supergrok-config Paths, TOML schema, defaults, env overrides, aliases, timeouts, auth_mode, completions_backend, completions_enabled Network, GPUI, Keychain
supergrok-auth api_key / oauth, refresh, Keychain + env file, server Bearer helpers Axum routes, GPUI widgets
supergrok-proxy Axum router, bind policy, /healthz, /v1/models, /v1/chat/completions, upstream client, SSE stream, retries Tray / GPUI
supergrok-completions Experimental POST /v1/completions (FIM → Responses, optional chat) Auth storage, UI
supergrok-zed (bin) Entry, Tokio + GPUI lifecycle, tray, settings UI, start/stop proxy, graceful quit Domain logic that belongs in libs

Dependency direction:

supergrok-zed → proxy, completions, auth, config, GPUI/tray
supergrok-proxy → config, auth, (completions for route mount)
supergrok-completions → config (+ thin types); no UI
supergrok-auth → config
supergrok-config → thin (serde/toml/dirs only)

Where to put code

  • New OpenAI HTTP surface → supergrok-proxy (or supergrok-completions if FIM-only).
  • Secrets / refresh → supergrok-auth.
  • New TOML field → supergrok-config first, then wire consumers.
  • Menu / tray / settings windows → supergrok-zed only.
  • Prefer injected Arc state / traits; no global mutable credential statics.
  • Libraries must not depend on GPUI or tray-icon.

Commands

Run from the workspace root. Prefer stable Rust.

cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build -p supergrok-zed   # macOS binary (when present)

Scoped work:

cargo test -p supergrok-config
cargo test -p supergrok-proxy

Do not require a real XAI_API_KEY for automated tests. Mock upstream.

Manual smoke (after binary exists):

curl -s http://127.0.0.1:8787/healthz   # → {"status":"ok"}
# Quit app → connection refused (proxy must die with the process)

If port 8787 is busy, the Python connector may still be running — stop it.


Config and paths

Drop-in compatibility with the Python connector:

Artifact Path
Config ~/.config/supergrok-zed/config.toml
Secrets ~/.config/supergrok-zed/env (mode 0600 on write)

In-process must load the env file (Python often relied on LaunchAgent source). Process env still overrides after load.

Defaults (paths, env names, and HTTP contracts match the Python connector; default_model / aliases are grok-4.6 — documented delta):

host = "127.0.0.1"
port = 8787
default_model = "grok-4.6"
upstream = "https://api.x.ai/v1"
request_timeout_s = 600
connect_timeout_s = 10
max_retries = 2
log_level = "info"
log_prompts = false
auth_mode = "api_key"   # or "oauth"
completions_backend = "responses"   # or "chat"
completions_enabled = true          # false → local POST /v1/completions is 404

[aliases]
supergrok = "grok-4.6"
grok = "grok-4.6"

Env overrides (same names as Python):

SUPERGROK_ZED_HOST, SUPERGROK_ZED_PORT, SUPERGROK_ZED_DEFAULT_MODEL, SUPERGROK_ZED_UPSTREAM, SUPERGROK_ZED_LOG_LEVEL, SUPERGROK_ZED_CONFIG, SUPERGROK_ZED_ALLOW_REMOTE, SUPERGROK_ZED_AUTH_MODE, SUPERGROK_ZED_COMPLETIONS_BACKEND, SUPERGROK_ZED_COMPLETIONS_ENABLED, SUPERGROK_ZED_ENV.

  • Secrets never go in config.toml.
  • Config tests use temp dirs (tempfile), never the developer’s real home config.

HTTP API contract

Method Path Notes
GET /healthz {"status":"ok"}; no auth, no upstream
GET /v1/models Local catalog only
POST /v1/chat/completions Proxy to {upstream}/chat/completions; stream + non-stream
POST /v1/completions Experimental FIM/prompt → Responses (default) or chat fallback → text_completion JSON. 404 not_found_error when completions_enabled is false

Non-negotiables

  1. Ignore client Authorization (and similar). Inject only server Bearer for the active auth mode.
  2. Model alias rewrite server-side (supergrok → configured target).
  3. Streaming: forward raw bytes for chat SSE; keep client/upstream open until body fully drained; no full-body buffering; do not retry mid-stream after first byte to client.
  4. Retries: statuses 429, 502, 503, 504 + connect/timeout; backoff with jitter (match Python: min(0.5 * 2^n, 8) * (0.5 + rand)); honor max_retries.
  5. Missing auth on chat/completions → 401 OpenAI-shaped auth_error (not on /healthz).
  6. Invalid JSON body → 400 invalid_request_error.
  7. /v1/completions: do not call xAI legacy /completions. Default is {upstream}/responses (store: false). Search off means omit search_parameters and do not send Agent Tools / web_search (or tools: []). Optional completions_backend = "chat" maps message.content → choices[0].text. Never fail over between Responses and chat; same-URL retries only. Always return object: "text_completion". Quality is experimental.

Auth contract

Exactly one active mode: auth_mode = "api_key" | "oauth" (invalid → api_key).

Mode Bearer source Other mode’s secrets
api_key XAI_API_KEY Ignore OAuth vars
oauth XAI_ACCESS_TOKEN Ignore API key

OAuth (parity with reference):

  • Token URL: https://auth.x.ai/oauth2/token
  • Public client id: b1a00492-073a-47ea-816f-4c329264a828
  • Refresh skew ~60s; single-flight lock; one 401 → refresh → retry; on hard failure clear tokens
  • Persist access/refresh/expiry to Keychain (preferred) and env file write-through (0600, bash-safe quoting)

Subscription OAuth and developer API keys are separate billing. Some tiers return 403 on OAuth-backed API — surface clearly; allow fallback to api_key.

Browser OAuth sign-in lives in the app UI (issue #7), not in headless unit tests.


Security non-negotiables

  • Default bind 127.0.0.1 only. Refuse 0.0.0.0 / :: unless SUPERGROK_ZED_ALLOW_REMOTE is truthy (1/true/yes/on).
  • No connector auth on the local socket — loopback is the control plane.
  • log_prompts default false. Never log API keys, access/refresh tokens, or Authorization headers.
  • Do not commit secrets, real env files, or credential-bearing configs.
  • Prefer secrecy / careful redaction for in-memory secrets where practical.

Runtime architecture

  • Axum on a dedicated multi-thread Tokio runtime owned by the binary.
  • Never run the accept loop or long blocking I/O on the GPUI main/UI thread.
  • Shared long-lived reqwest::Client for upstream.
  • UI ↔ proxy: channels / Arc state / cancellation token; graceful shutdown on Quit.
  • Quit = cancel proxy + unbind port + exit. No orphan listeners.

GPUI + tray pitfalls

  • Upstream GPUI has no merged system tray API (PR #44047 closed). Hybrid: tray-icon (or NSStatusItem) + GPUI windows.
  • Create tray on the main thread; do not run a second competing NSApp loop.
  • Tray callbacks must marshal safely into GPUI state updates.
  • Process must stay alive with zero windows until Quit (menubar app / LSUIElement pattern).
  • Tray health = local /healthz (or bind state), not upstream model calls.
  • Spike tray + GPUI early (issue #6) before deep UI features. If hybrid fails, document plan B (tray shell + GPUI settings only) — do not invent multi-OS tray.

Testing policy

Layer Rules
Lib unit/integration No GPUI, no real tray, no Keychain UI dialogs
Upstream Mock only (wiremock / httpmock / local Axum) — never hit api.x.ai in CI
Auth Fake credential provider; mode isolation; refresh lock; 401 retry once
Streaming Multi-chunk delayed SSE; early-close regression; client cancel aborts upstream
Completions Table-driven FIM→Responses/chat mapping in supergrok-completions
Config Temp dirs only
Binary/UI Manual smoke or #[ignore] — default CI is headless lib tests

Match Python behavioral contracts where documented in RESEARCH.md and issues. Prefer red-green tests for proxy/auth before wiring UI.


Do not

  • Reintroduce LaunchAgent / launchd as the default product path.
  • Make quit leave the proxy running.
  • Trust or forward client Authorization.
  • Bind all interfaces by default or for “demo convenience.”
  • Put GPUI/tray dependencies in library crates.
  • Buffer entire streamed chat bodies.
  • Treat experimental /v1/completions as full OpenAI Completions parity without fixtures/docs.
  • Add Windows/Linux tray “support” in the first release without a product decision.
  • Use nightly-only features unless the repo explicitly opts in.
  • Commit secrets, tokens, or private keys.
  • Log tokens or prompt bodies when log_prompts is false.
  • Invent a second top-level JSON object in Zed settings.json docs (Zed loads the first object only) — merge keys into the existing root.

PR / change checklist

  • cargo fmt --check and clippy -D warnings clean for touched crates
  • Code lives in the correct crate (boundary table)
  • Contracts preserved: loopback, ignore client auth, mode isolation, SSE lifetime
  • No secret logging; no secrets in git
  • Tests with mocked upstream for proxy/auth changes
  • Product deltas vs Python still accurate if you touch lifecycle docs

Work tracking

Priority Issue Topic
Epic #1 Full plan
Start #9 Workspace + config
Parallel #6 Tray + GPUI spike
Then #10 → #2 → #8 → #4 → #5 → #7 → #3 Proxy, stream, auth, completions, app, OAuth UI, packaging

Implement tickets in dependency order. Prefer implement-ticket / focused PRs over large mixed diffs.


Experts / research

  • RESEARCH.md — primary sources (Python connector, Zed Edit Prediction, GPUI tray status, xAI notes)
  • Plan expert input: rust-expert (crate layout, Tokio+GPUI, streaming), python-expert (drop-in contracts)
  • Do not reverse locked product decisions without updating RESEARCH.md / issue #1 and this file together