{"id":50296938,"url":"https://github.com/bntvllnt/pi-gateway","last_synced_at":"2026-05-28T09:30:46.634Z","repository":{"id":360751263,"uuid":"1251419572","full_name":"bntvllnt/pi-gateway","owner":"bntvllnt","description":"OpenAI-compatible local API on top of pi.dev — LiteLLM-shape stateless protocol translator. Standalone CLI + pi extension.","archived":false,"fork":false,"pushed_at":"2026-05-27T17:43:52.000Z","size":166,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T19:24:57.922Z","etag":null,"topics":["ai","anthropic","chat-completions","claude","cline","codex","cursor","gateway","gpt","librechat","litellm","llm","openai","openai-api","openai-compatible","openrouter","openwebui","pi-dev","pi-extension","typescript"],"latest_commit_sha":null,"homepage":"https://github.com/bntvllnt/pi-gateway","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bntvllnt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-27T14:59:43.000Z","updated_at":"2026-05-27T17:43:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bntvllnt/pi-gateway","commit_stats":null,"previous_names":["bntvllnt/pi-gateway"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/bntvllnt/pi-gateway","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bntvllnt%2Fpi-gateway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bntvllnt%2Fpi-gateway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bntvllnt%2Fpi-gateway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bntvllnt%2Fpi-gateway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bntvllnt","download_url":"https://codeload.github.com/bntvllnt/pi-gateway/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bntvllnt%2Fpi-gateway/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33603473,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","anthropic","chat-completions","claude","cline","codex","cursor","gateway","gpt","librechat","litellm","llm","openai","openai-api","openai-compatible","openrouter","openwebui","pi-dev","pi-extension","typescript"],"created_at":"2026-05-28T09:30:45.095Z","updated_at":"2026-05-28T09:30:46.616Z","avatar_url":"https://github.com/bntvllnt.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pi-gateway\n\n[![npm version](https://img.shields.io/npm/v/pi-gateway.svg)](https://www.npmjs.com/package/pi-gateway)\n[![CI](https://github.com/bntvllnt/pi-gateway/actions/workflows/ci.yml/badge.svg)](https://github.com/bntvllnt/pi-gateway/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n\u003e **OpenAI-compatible local API on top of [pi.dev](https://github.com/badlogic/pi-mono).**\n\u003e LiteLLM-shape stateless protocol translator. Re-exposes every model `pi` can reach (Anthropic, OpenAI, Google, Mistral, Bedrock, Vertex, **plus OAuth subscriptions** like Claude Pro / ChatGPT Codex / GitHub Copilot / Gemini CLI) through `POST /v1/chat/completions` + `GET /v1/models`, so Open WebUI / LibreChat / Cursor / Continue.dev / Cline can use them without re-entering credentials.\n\n## Install\n\nTwo install paths from one package:\n\n### 1. Standalone CLI (no pi required)\n\n```bash\npnpm dlx pi-gateway --port 4000\n# or globally\nnpm i -g pi-gateway \u0026\u0026 pi-gateway --port 4000\n```\n\nRuns in the foreground; Ctrl+C to stop.\n\n### 2. Pi extension (recommended if you already use pi)\n\nAdd to `~/.pi/agent/settings.json`:\n\n```jsonc\n{\n  \"packages\": [\n    \"git:git@github.com:bntvllnt/pi-gateway\"\n  ]\n}\n```\n\nRestart `pi`. Then inside pi:\n\n```\n/gateway:start    # spawn the daemon (detached, survives pi exit)\n/gateway:status   # show url + pid + model count\n/gateway:stop     # SIGTERM the daemon\n```\n\n## Wire your client\n\nPoint any OpenAI-compatible client at `http://127.0.0.1:4000/v1`. The API key field accepts any non-empty string on loopback.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eOpen WebUI\u003c/b\u003e\u003c/summary\u003e\n\nSettings → Connections → OpenAI API:\n- **Base URL:** `http://127.0.0.1:4000/v1`\n- **API key:** `pi-gateway` (any non-empty string)\n\nModel dropdown populates from `GET /v1/models`.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eLibreChat\u003c/b\u003e\u003c/summary\u003e\n\n```yaml\n# librechat.yaml\nendpoints:\n  custom:\n    - name: \"pi-gateway\"\n      baseURL: \"http://127.0.0.1:4000/v1\"\n      apiKey: \"pi-gateway\"\n      models:\n        fetch: true\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eCursor\u003c/b\u003e\u003c/summary\u003e\n\nSettings → Models → \"Override OpenAI Base URL\" → `http://127.0.0.1:4000/v1`.\n\nCursor 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`.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eContinue.dev\u003c/b\u003e\u003c/summary\u003e\n\n```json\n{\n  \"models\": [{\n    \"provider\": \"openai\",\n    \"apiBase\": \"http://127.0.0.1:4000/v1\",\n    \"apiKey\": \"pi-gateway\",\n    \"model\": \"anthropic/claude-sonnet-4-5\",\n    \"title\": \"Claude via pi-gateway\"\n  }]\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eCline (VS Code)\u003c/b\u003e\u003c/summary\u003e\n\nAPI Provider → OpenAI Compatible.\n- **Base URL:** `http://127.0.0.1:4000/v1`\n- **API key:** any non-empty string\n- **Model:** `provider/model-id` (e.g., `openai/gpt-4o`, `anthropic/claude-sonnet-4-5`)\n\u003c/details\u003e\n\n## Endpoints\n\n| Method | Path | Notes |\n|--------|------|-------|\n| `GET`  | `/healthz` | Liveness; returns `{ ok, uptimeMs }` |\n| `GET`  | `/v1/models` | OpenAI list payload; `id: \"provider/model-id\"` |\n| `POST` | `/v1/chat/completions` | Stream + non-stream Chat Completions |\n\nOut of scope for v1: `/v1/responses`, `/v1/embeddings`, `/v1/images/generations`, `/v1/audio/*`, `/v1/messages` (Anthropic Messages format).\n\n## What pi-gateway is — and isn't\n\n**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).\n\n**Is not:**\n\n- ❌ A pi agent. No pi `AgentSession` is created.\n- ❌ A prompt injector. What the client sends is what pi-ai sees — no system prompt, no tools, no skills are added.\n- ❌ A tool runner. `tools` and `tool_choice` are forwarded; the client executes and posts `role: \"tool\"` results.\n- ❌ A conversation store. Each `POST /v1/chat/completions` is independent.\n\n## CLI flags\n\n| Flag | Default | Notes |\n|------|---------|-------|\n| `--port N` | `4000` | `0` = OS-assigned |\n| `--bind HOST` | `127.0.0.1` | Non-loopback requires `apiKey` in config file |\n| `--config PATH` | — | Extra JSON layered after `~/.pi/agent/gateway.json` |\n| `--auth-dir PATH` | `~/.pi/agent` | Where to find `auth.json` and `models.json` |\n| `--allow-origin ORIGIN` | empty | Repeatable; `\"*\"` = any |\n| `--log-level LEVEL` | `info` | `debug` / `info` / `warn` / `error` |\n| `--model-allowlist ID` | — | Repeatable; matches `provider/model-id` or bare id |\n| `--model-denylist ID` | — | Repeatable |\n| `--expose-oauth-subscriptions` | on loopback only | Expose Claude Pro / Codex / Copilot on non-loopback too |\n| `--require-key-on-loopback` | off | Force bearer auth even on `127.0.0.1` |\n| `--version` | — | Print version + exit |\n\n\u003e **No `--api-key` flag.** Argv leaks via `/proc/\u003cpid\u003e/cmdline` + `ps aux`. Set the key in `~/.pi/agent/gateway.json` (`{ \"apiKey\": \"...\" }`) or via `PI_GATEWAY_API_KEY` env var.\n\nSubcommands:\n\n```bash\npi-gateway models    # Print available models then exit\npi-gateway --help\npi-gateway --version\n```\n\n## Security defaults\n\n- **Default bind: `127.0.0.1`.** `server.address()` is asserted after `listening` so a refactor can't silently bind `0.0.0.0`.\n- **Non-loopback bind requires `apiKey` in the config file.** CLI flag is refused.\n- **PID lockfile** at `~/.pi/agent/gateway.pid` via atomic `O_CREAT|O_EXCL`. Single instance enforced; stale files cleaned automatically.\n- **OAuth subscriptions default-allow on loopback** (so Claude Pro / Codex work from Open WebUI), **default-deny on non-loopback** unless `--expose-oauth-subscriptions`.\n- **Access log** redacts everything outside a hardcoded allowlist (`content-type`, `content-length`, `user-agent`, `accept`, `accept-encoding`, `host`). No `authorization` / token / key headers in logs.\n\n## Programmatic SDK\n\n```ts\nimport { startServer, stopServer } from \"pi-gateway\";\nimport { DEFAULT_CONFIG } from \"pi-gateway/config\";  // future export\n\nconst handle = await startServer({\n  config: { ...DEFAULT_CONFIG, port: 0, bindAddress: \"127.0.0.1\" },\n});\n\nconst url = `http://${handle.address.address}:${handle.address.port}`;\nconst r = await fetch(`${url}/v1/chat/completions`, { method: \"POST\", /* ... */ });\n\nawait stopServer(handle);\n```\n\nUsed by the test suite to bind real listeners on `127.0.0.1:0` without invoking the binary.\n\n## Development\n\n```bash\ngit clone https://github.com/bntvllnt/pi-gateway.git\ncd pi-gateway\npnpm install\npnpm run check   # lint + typecheck + build + smoke + e2e + contract\n```\n\nQuality gates:\n\n| Gate | Command |\n|------|---------|\n| Lint | `pnpm run lint` |\n| Typecheck | `pnpm run typecheck` |\n| Build | `pnpm run build` |\n| Smoke | `node tests/smoke.mjs` |\n| E2E | `node tests/extension-e2e.mjs` |\n| **Contract** | `node tests/contract.mjs` — ajv field-by-field validation against the pinned OpenAPI doc + Chat Completions schemas |\n\nAll gates run on `pre-commit` and CI.\n\n## Schemas\n\n`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.\n\n## License\n\n[MIT](LICENSE) © [bntvllnt](https://github.com/bntvllnt)\n\n## See also\n\n- [llms.txt](llms.txt) — short hub for AI consumption\n- [llms-full.txt](llms-full.txt) — full reference for AI consumption\n- [CLAUDE.md](CLAUDE.md) — project rules + contract guarantees\n- [pi.dev](https://github.com/badlogic/pi-mono) — the underlying agent runtime\n- [pi-claude-code](https://github.com/bntvllnt/pi-claude-code), [pi-git-worktrees](https://github.com/bntvllnt/pi-git-worktrees) — sibling pi extensions\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbntvllnt%2Fpi-gateway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbntvllnt%2Fpi-gateway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbntvllnt%2Fpi-gateway/lists"}