Skip to content

Repository files navigation

Stack-IDE AI Proxy Server

Server-side AI proxy for the Stack-IDE Android app, per docs/ai/. The server holds the base system prompt, the tool definitions, and the LLM-provider keys; the device sends messages + device_context and receives an SSE stream of delta / tool_call / done / error events. The wire never carries the system prompt or the tool schemas.

Run locally

cd ai-server
pip install -r requirements.txt
export AI_PROXY_HMAC_SECRET="dev-secret-change-me"
# The default provider (OpenCode Zen) works out of the box with the built-in
# defaults — no API key required (the endpoint is open).
export ZEN_BASE_URL="https://opencode.ai/zen/v1"
export ZEN_MODEL="mimo-v2.5-free"
# GLM is still available as an alternative provider:
export GLM_API_KEY="sk-..."                       # default is hardcoded below; override in prod
export GLM_BASE_URL="https://rx37rjd.abc-tunnel.us/v1"
export GLM_MODEL="Nx/zai-org/GLM-5.2"
python -m uvicorn app.main:app --port 8081

Health check:

curl http://127.0.0.1:8081/v1/health
# {"status":"ok","version":"v1","server_time":...}

Environment variables

Variable Default Purpose
AI_PROXY_PORT 8081 Listen port
AI_PROXY_DB_PATH ai_proxy.db SQLite database file
AI_PROXY_HMAC_SECRET change-me-in-production Server secret for key hashing (NOT the LLM key)
OPENAI_API_KEY OpenAI key (alternative provider; unset = unavailable)
ANTHROPIC_API_KEY Anthropic key (alternative provider; unset = unavailable)
ZEN_API_KEY "" Default provider key. Empty = no auth (endpoint is open).
ZEN_BASE_URL https://opencode.ai/zen/v1 Default provider base URL (OpenAI-compatible, 200k context / 16k output)
ZEN_MODEL mimo-v2.5-free Default provider model ID
GLM_API_KEY sk-8156528465a3c504-222nvw-24f7adf8 Alternative provider key. Hardcoded default works out of the box; OVERRIDE in production.
GLM_BASE_URL https://rx37rjd.abc-tunnel.us/v1 Alternative provider base URL (OpenAI-compatible)
GLM_MODEL Nx/zai-org/GLM-5.2 Alternative provider model ID
AI_PROXY_DEFAULT_MODEL mimo-v2.5-free Default model (used when the request omits options.model)
AI_PROXY_DEFAULT_PROVIDER zen zen (default), glm, openai, or anthropic
AI_PROXY_RATE_LIMIT_MAX 60 Max requests per key per window
AI_PROXY_RATE_LIMIT_WINDOW_SECS 60 Rate-limit window
AI_PROXY_TIMESTAMP_WINDOW_SECS 300 Replay timestamp skew
AI_PROXY_NONCE_TTL_SECS 600 Nonce replay cache TTL
AI_PROXY_MAX_BODY_BYTES 524288 Max request body size
AI_PROXY_DEFAULT_TOKEN_QUOTA 100000 Default per-key token quota
AI_PROXY_DEFAULT_REQUEST_QUOTA 1000 Default per-key request quota

API endpoints

GET /v1/health (unauthenticated)

Returns { status, version, server_time }.

POST /v1/chat/stream (authenticated)

Request body:

{
  "conversation_id": "string (opaque to server)",
  "messages": [ { "role": "user", "content": "..." } ],
  "device_context": {
    "active_file": { "name": "main.py", "content": "..." },
    "open_tabs": ["path/a.py"],
    "selection": { "text": "..." },
    "memories": ["The user prefers tabs."]
  },
  "options": { "model": "gpt-4o-mini", "temperature": 0.2 }
}

Response: SSE stream of data: <json>\n\n events:

  • {"type":"delta","text":"..."} — text fragment
  • {"type":"tool_call","tool_call_id":"call_1","tool_name":"file-edit","arguments":"{...}"} — tool-call envelope
  • {"type":"done","finish_reason":"stop"} — stream complete
  • {"type":"error","code":"...","message":"..."} — error (terminates stream)

GET /v1/usage (authenticated)

Returns the authenticated key's usage and quota.

POST /v1/keys (disabled in v1)

Returns 501 Not Implemented. v1 keys are admin-issued via SQL (see below).

Auth headers (required on all endpoints except /v1/health)

Header Purpose
Authorization: Bearer sk-stack-<...> API key
X-Timestamp Epoch ms, ±300s window
X-Nonce Random UUID, single-use within TTL
X-Signature HMAC-SHA256(timestamp:nonce:body, <api-key>) hex
User-Agent: Stack-IDE/<version> Client identification

Creating a key (v1: admin-issued via SQL)

KEY="sk-stack-$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
HASH=$(python3 -c "import hmac, hashlib, os; \
  print(hmac.new(os.environ['AI_PROXY_HMAC_SECRET'].encode(), '$KEY'.encode(), hashlib.sha256).hexdigest())")
sqlite3 ai_proxy.db "INSERT INTO api_keys \
  (user_id, key_prefix, key_hash, active, plan, token_quota, request_quota, created_at, revoked_at) \
  VALUES (1, '${KEY:0:12}', '$HASH', 1, 'free', 100000, 1000, datetime('now'), NULL);"
echo "Give this key to the user (it is never re-derivable): $KEY"

Tests

cd ai-server
pip install -r requirements.txt
pip install pytest pytest-asyncio
pytest

Docker

docker build -t stack-ai-proxy .
docker run -p 8081:8081 \
  -e AI_PROXY_HMAC_SECRET=... \
  stack-ai-proxy

The image defaults to the OpenCode Zen provider (mimo-v2.5-free, https://opencode.ai/zen/v1) — no API key required. To opt back into GLM, override AI_PROXY_DEFAULT_PROVIDER=glm and set the GLM env vars:

docker run -p 8081:8081 \
  -e AI_PROXY_HMAC_SECRET=... \
  -e AI_PROXY_DEFAULT_PROVIDER=glm \
  -e GLM_API_KEY=sk-... \
  -e GLM_BASE_URL=https://rx37rjd.abc-tunnel.us/v1 \
  -e GLM_MODEL=Nx/zai-org/GLM-5.2 \
  stack-ai-proxy

TLS is terminated by a reverse proxy (nginx/Caddy) in front of the FastAPI app.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages