OpenAI-compatible local API on top of pi.dev. LiteLLM-shape stateless protocol translator. Re-exposes every model
pican reach (Anthropic, OpenAI, Google, Mistral, Bedrock, Vertex, plus OAuth subscriptions like Claude Pro / ChatGPT Codex / GitHub Copilot / Gemini CLI) throughPOST /v1/chat/completions+GET /v1/models, so Open WebUI / LibreChat / Cursor / Continue.dev / Cline can use them without re-entering credentials.
Two install paths from one package:
pnpm dlx pi-gateway --port 4000
# or globally
npm i -g pi-gateway && pi-gateway --port 4000Runs in the foreground; Ctrl+C to stop.
Add to ~/.pi/agent/settings.json:
Restart pi. Then inside pi:
/gateway:start # spawn the daemon (detached, survives pi exit)
/gateway:status # show url + pid + model count
/gateway:stop # SIGTERM the daemon
Point any OpenAI-compatible client at http://127.0.0.1:4000/v1. The API key field accepts any non-empty string on loopback.
Open WebUI
Settings → Connections → OpenAI API:
- Base URL:
http://127.0.0.1:4000/v1 - API key:
pi-gateway(any non-empty string)
Model dropdown populates from GET /v1/models.
LibreChat
# librechat.yaml
endpoints:
custom:
- name: "pi-gateway"
baseURL: "http://127.0.0.1:4000/v1"
apiKey: "pi-gateway"
models:
fetch: trueCursor
Settings → Models → "Override OpenAI Base URL" → http://127.0.0.1:4000/v1.
Cursor may need a bare model id alias (it can be strict about slashes). Add aliases under Settings → Models → Add Model, e.g. claude-sonnet-4-5 → anthropic/claude-sonnet-4-5.
Continue.dev
{
"models": [{
"provider": "openai",
"apiBase": "http://127.0.0.1:4000/v1",
"apiKey": "pi-gateway",
"model": "anthropic/claude-sonnet-4-5",
"title": "Claude via pi-gateway"
}]
}Cline (VS Code)
API Provider → OpenAI Compatible.
- Base URL:
http://127.0.0.1:4000/v1 - API key: any non-empty string
- Model:
provider/model-id(e.g.,openai/gpt-4o,anthropic/claude-sonnet-4-5)
| Method | Path | Notes |
|---|---|---|
GET |
/healthz |
Liveness; returns { ok, uptimeMs } |
GET |
/v1/models |
OpenAI list payload; id: "provider/model-id" |
POST |
/v1/chat/completions |
Stream + non-stream Chat Completions |
Out of scope for v1: /v1/responses, /v1/embeddings, /v1/images/generations, /v1/audio/*, /v1/messages (Anthropic Messages format).
Is: a stateless HTTP frontend. Validates → resolves model → resolves auth → calls pi-ai.complete() or pi-ai.stream() → translates back into OpenAI Chat Completions shape (JSON or SSE).
Is not:
- ❌ A pi agent. No pi
AgentSessionis created. - ❌ A prompt injector. What the client sends is what pi-ai sees — no system prompt, no tools, no skills are added.
- ❌ A tool runner.
toolsandtool_choiceare forwarded; the client executes and postsrole: "tool"results. - ❌ A conversation store. Each
POST /v1/chat/completionsis independent.
| Flag | Default | Notes |
|---|---|---|
--port N |
4000 |
0 = OS-assigned |
--bind HOST |
127.0.0.1 |
Non-loopback requires apiKey in config file |
--config PATH |
— | Extra JSON layered after ~/.pi/agent/gateway.json |
--auth-dir PATH |
~/.pi/agent |
Where to find auth.json and models.json |
--allow-origin ORIGIN |
empty | Repeatable; "*" = any |
--log-level LEVEL |
info |
debug / info / warn / error |
--model-allowlist ID |
— | Repeatable; matches provider/model-id or bare id |
--model-denylist ID |
— | Repeatable |
--expose-oauth-subscriptions |
on loopback only | Expose Claude Pro / Codex / Copilot on non-loopback too |
--require-key-on-loopback |
off | Force bearer auth even on 127.0.0.1 |
--version |
— | Print version + exit |
No
--api-keyflag. Argv leaks via/proc/<pid>/cmdline+ps aux. Set the key in~/.pi/agent/gateway.json({ "apiKey": "..." }) or viaPI_GATEWAY_API_KEYenv var.
Subcommands:
pi-gateway models # Print available models then exit
pi-gateway --help
pi-gateway --version- Default bind:
127.0.0.1.server.address()is asserted afterlisteningso a refactor can't silently bind0.0.0.0. - Non-loopback bind requires
apiKeyin the config file. CLI flag is refused. - PID lockfile at
~/.pi/agent/gateway.pidvia atomicO_CREAT|O_EXCL. Single instance enforced; stale files cleaned automatically. - OAuth subscriptions default-allow on loopback (so Claude Pro / Codex work from Open WebUI), default-deny on non-loopback unless
--expose-oauth-subscriptions. - Loopback
Host-header guard — requests to a loopback bind with an unexpectedHostreturn HTTP 403, blocking DNS-rebinding from browser-based clients on the same machine. - Request body cap — payloads over 16 MB return HTTP 413.
- Access log redacts everything outside a hardcoded allowlist (
content-type,content-length,user-agent,accept,accept-encoding,host). Noauthorization/ token / key headers in logs.
import { startServer, stopServer } from "pi-gateway";
import { DEFAULT_CONFIG } from "pi-gateway/config"; // future export
const handle = await startServer({
config: { ...DEFAULT_CONFIG, port: 0, bindAddress: "127.0.0.1" },
});
const url = `http://${handle.address.address}:${handle.address.port}`;
const r = await fetch(`${url}/v1/chat/completions`, { method: "POST", /* ... */ });
await stopServer(handle);Used by the test suite to bind real listeners on 127.0.0.1:0 without invoking the binary.
git clone https://github.com/bntvllnt/pi-gateway.git
cd pi-gateway
pnpm install
pnpm run check # lint + typecheck + build + smoke + e2e + contractQuality gates:
| Gate | Command |
|---|---|
| Lint | pnpm run lint |
| Typecheck | pnpm run typecheck |
| Build | pnpm run build |
| Smoke | node tests/smoke.mjs |
| E2E | node tests/extension-e2e.mjs |
| Contract | node tests/contract.mjs — ajv field-by-field validation against the pinned OpenAPI doc + Chat Completions schemas |
All gates run on pre-commit and CI.
schemas/openresponses.openapi.json pins https://www.openresponses.org/openapi/openapi.json (OpenAI API v2.3.0, 108 component schemas) in-repo to avoid drift. The contract test loads this file and rejects any response that doesn't match.
- CHANGELOG.md — release notes; tags link to the matching section
- llms.txt — short hub for AI consumption
- llms-full.txt — full reference for AI consumption
- CLAUDE.md — project rules + contract guarantees
- pi.dev — the underlying agent runtime
- pi-claude-code, pi-git-worktrees — sibling pi extensions

{ "packages": [ "git:git@github.com:bntvllnt/pi-gateway" ] }