Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

bntvllnt/pi-gateway

Repository files navigation

pi-gateway

pi-gateway: point any OpenAI client at one local URL (127.0.0.1:4000) and reach every model pi can route to — Anthropic, OpenAI, Google, Mistral, Bedrock, Vertex, plus OAuth subscriptions like Claude Pro, ChatGPT Codex, GitHub Copilot, and Gemini CLI

npm version CI License: MIT

OpenAI-compatible local API on top of pi.dev. 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.

Install

Two install paths from one package:

1. Standalone CLI (no pi required)

pnpm dlx pi-gateway --port 4000
# or globally
npm i -g pi-gateway && pi-gateway --port 4000

Runs in the foreground; Ctrl+C to stop.

2. Pi extension (recommended if you already use pi)

Add to ~/.pi/agent/settings.json:

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

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

Wire your client

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: true
Cursor

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-5anthropic/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)

Endpoints

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).

What pi-gateway is — and isn't

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 AgentSession is 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. tools and tool_choice are forwarded; the client executes and posts role: "tool" results.
  • ❌ A conversation store. Each POST /v1/chat/completions is independent.

CLI flags

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-key flag. Argv leaks via /proc/<pid>/cmdline + ps aux. Set the key in ~/.pi/agent/gateway.json ({ "apiKey": "..." }) or via PI_GATEWAY_API_KEY env var.

Subcommands:

pi-gateway models # Print available models then exit
pi-gateway --help
pi-gateway --version

Security defaults

  • Default bind: 127.0.0.1. server.address() is asserted after listening so a refactor can't silently bind 0.0.0.0.
  • Non-loopback bind requires apiKey in the config file. CLI flag is refused.
  • PID lockfile at ~/.pi/agent/gateway.pid via atomic O_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 unexpected Host return 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). No authorization / token / key headers in logs.

Programmatic SDK

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.

Development

git clone https://github.com/bntvllnt/pi-gateway.git
cd pi-gateway
pnpm install
pnpm run check # lint + typecheck + build + smoke + e2e + contract

Quality 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

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.

License

MIT © bntvllnt

See also

Packages

Contributors

AltStyle によって変換されたページ (->オリジナル) /