{"_id":"@blackbelt-technology/pi-model-proxy","_rev":"4-413d556621053ff3f58d28ab03c68457","name":"@blackbelt-technology/pi-model-proxy","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@blackbelt-technology/pi-model-proxy","version":"0.1.0","keywords":["pi-package","pi-extension","openai-proxy","anthropic-proxy","llm-proxy","model-proxy"],"license":"MIT","_id":"@blackbelt-technology/pi-model-proxy@0.1.0","maintainers":[{"name":"robertcsakany","email":"robert.csakany@blackbelt.hu"},{"name":"norbert.herczeg","email":"norbert.herczeg@blackbelt.hu"}],"homepage":"https://github.com/BlackBeltTechnology/pi-model-proxy#readme","bugs":{"url":"https://github.com/BlackBeltTechnology/pi-model-proxy/issues"},"dist":{"shasum":"cc7462bc6710b8275c87610776489f3a4bf9371f","tarball":"https://registry.npmjs.org/@blackbelt-technology/pi-model-proxy/-/pi-model-proxy-0.1.0.tgz","fileCount":16,"integrity":"sha512-JYHyUf4OeKaE3zTQZOzGQ/xLDIDKJbr/gGO7E/FXQGUw2zc3ZFbK2qtdfw8t+tvCLVpG+bJbXqNotNc+28fddg==","signatures":[{"sig":"MEQCIDWyAsToIkxKbI9kKnckoOjLArXMkTPr4D7fN3s9ymgZAiAVNpmSkC+Yy1Y1098RdRxxuO5qKG4vMxfmTXDDJIY6nA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":58263},"main":"index.ts","type":"module","gitHead":"00b25c34747ac20e98b4ef653c03806fd3d910bb","scripts":{"test":"vitest run","typecheck":"npx -p typescript tsc --noEmit --esModuleInterop --moduleResolution bundler --module esnext --target esnext --skipLibCheck index.ts"},"_npmUser":{"name":"robertcsakany","email":"robert.csakany@blackbelt.hu"},"repository":{"url":"git+https://github.com/BlackBeltTechnology/pi-model-proxy.git","type":"git"},"_npmVersion":"10.9.4","description":"Pi extension that exposes pi's authenticated models as a local OpenAI-compatible and Anthropic-compatible API server","directories":{},"_nodeVersion":"22.22.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.2","@mariozechner/pi-ai":"*","@mariozechner/pi-coding-agent":"*"},"peerDependencies":{"@mariozechner/pi-ai":">=0.60.0","@mariozechner/pi-coding-agent":">=0.60.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-model-proxy_0.1.0_1775163646777_0.28977272926624087","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@blackbelt-technology/pi-model-proxy","version":"0.2.0","keywords":["pi-package","pi-extension","openai-proxy","anthropic-proxy","llm-proxy","model-proxy"],"license":"MIT","_id":"@blackbelt-technology/pi-model-proxy@0.2.0","maintainers":[{"name":"robertcsakany","email":"robert.csakany@blackbelt.hu"},{"name":"norbert.herczeg","email":"norbert.herczeg@blackbelt.hu"}],"homepage":"https://github.com/BlackBeltTechnology/pi-model-proxy#readme","bugs":{"url":"https://github.com/BlackBeltTechnology/pi-model-proxy/issues"},"dist":{"shasum":"d3c582b54863ab5ca0031c11ca83697ff9e7e23f","tarball":"https://registry.npmjs.org/@blackbelt-technology/pi-model-proxy/-/pi-model-proxy-0.2.0.tgz","fileCount":16,"integrity":"sha512-5oxsGH/RmRIgRWQaqFSftS2OxYro14wANTwZPn9YjkK+Bha7qsrOM6ultwZ3rY80ShCMVP231ZEUfpQhlZr8lA==","signatures":[{"sig":"MEYCIQDjl81E+OudnwmOBF8WN4pujs9qqcKiXeWLTKUUEWJCyQIhAJtwwIdXD/INcZU1zJqaJ8PHWSaatOr4HcvYA8vW0A6G","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@blackbelt-technology%2fpi-model-proxy@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":58263},"main":"index.ts","type":"module","gitHead":"81977278c51968a33e66f3752f3a6b9f23ec1244","scripts":{"test":"vitest run","typecheck":"npx -p typescript tsc --noEmit --esModuleInterop --moduleResolution bundler --module esnext --target esnext --skipLibCheck index.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:45674987-4bae-4759-8642-3d0e3a4761b9"}},"repository":{"url":"git+https://github.com/BlackBeltTechnology/pi-model-proxy.git","type":"git"},"_npmVersion":"11.9.0","description":"Pi extension that exposes pi's authenticated models as a local OpenAI-compatible and Anthropic-compatible API server","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.2","@mariozechner/pi-ai":"*","@mariozechner/pi-coding-agent":"*"},"peerDependencies":{"@mariozechner/pi-ai":">=0.60.0","@mariozechner/pi-coding-agent":">=0.60.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-model-proxy_0.2.0_1775165383851_0.04229628075872127","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-04-02T21:00:46.523Z","modified":"2026-04-30T16:33:05.803Z","0.1.0":"2026-04-02T21:00:46.948Z","0.2.0":"2026-04-02T21:29:44.014Z"},"bugs":{"url":"https://github.com/BlackBeltTechnology/pi-model-proxy/issues"},"license":"MIT","homepage":"https://github.com/BlackBeltTechnology/pi-model-proxy#readme","keywords":["pi-package","pi-extension","openai-proxy","anthropic-proxy","llm-proxy","model-proxy"],"repository":{"url":"git+https://github.com/BlackBeltTechnology/pi-model-proxy.git","type":"git"},"description":"Pi extension that exposes pi's authenticated models as a local OpenAI-compatible and Anthropic-compatible API server","maintainers":[{"email":"botond.molnar@blackbelt.hu","name":"mbotond"},{"email":"dbence10@gmail.com","name":"mrbence"},{"email":"robert.csakany@blackbelt.hu","name":"robertcsakany"},{"email":"norbert.herczeg@blackbelt.hu","name":"norbert.herczeg"}],"readme":"# pi-model-proxy\n\n[![CI](https://github.com/BlackBeltTechnology/pi-model-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/BlackBeltTechnology/pi-model-proxy/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@blackbelt-technology/pi-model-proxy)](https://www.npmjs.com/package/@blackbelt-technology/pi-model-proxy)\n![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n\nA [pi](https://github.com/badlogic/pi-mono) extension that exposes pi's authenticated models as a local OpenAI-compatible and Anthropic-compatible API server. External services (Honcho, LangChain, custom apps) can call `http://localhost:9876/v1/chat/completions` or `/v1/messages` to use any model pi has access to — including OAuth-authenticated subscriptions.\n\nInspired by [9router](https://github.com/decolua/9router) — a full-featured LLM proxy with provider pools, round-robin routing, tunnels, and usage tracking. pi-model-proxy takes a different approach: instead of managing provider credentials and routing itself, it leverages pi's built-in model registry and OAuth authentication, giving you a lightweight zero-config local proxy.\n\n## How it works\n\n```\n┌─────────────────┐     ┌─────────────────────┐     ┌──────────────────┐\n│  External App   │────▶│  pi-model-proxy   │────▶│  AI Provider     │\n│  (Honcho, etc.) │     │  localhost:9876      │     │  (Anthropic,     │\n│                 │◀────│                      │◀────│   OpenAI, etc.)  │\n│  OpenAI format  │     │  pi-ai stream fns   │     │                  │\n│  Anthropic fmt  │     │                      │     │                  │\n└─────────────────┘     └─────────────────────┘     └──────────────────┘\n```\n\n1. Extension starts a local HTTP server inside pi\n2. External services send OpenAI-format or Anthropic-format requests\n3. The proxy resolves the model + API key from pi's model registry (including OAuth tokens)\n4. pi-ai's built-in streaming functions handle the actual provider call\n5. Response is translated back to the caller's format\n\n## Installation\n\nInstall as a [pi package](https://www.npmjs.com/package/@mariozechner/pi-coding-agent#pi-packages) — this is the recommended way:\n\n```bash\n# From npm (recommended)\npi install npm:@blackbelt-technology/pi-model-proxy\n\n# Or from GitHub\npi install https://github.com/BlackBeltTechnology/pi-model-proxy\n```\n\nThen start pi as usual — the extension loads automatically:\n\n```bash\npi\n```\n\nThat's it — **zero configuration required**. The proxy auto-discovers all models from pi's registry (providers, OAuth logins, custom models). No config file needed.\n\n> **Tip:** Use `pi list` to verify the package is installed, and `pi config` to enable/disable it.\n\n### Test it\n\n```bash\n# List available models\ncurl http://localhost:9876/v1/models\n\n# OpenAI-compatible chat completion\ncurl http://localhost:9876/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"anthropic/claude-sonnet-4-5-20250929\",\n    \"messages\": [{\"role\": \"user\", \"content\": \"Hello!\"}],\n    \"stream\": true\n  }'\n\n# Anthropic-compatible messages\ncurl http://localhost:9876/v1/messages \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"anthropic/claude-sonnet-4-5-20250929\",\n    \"messages\": [{\"role\": \"user\", \"content\": \"Hello!\"}],\n    \"max_tokens\": 1024\n  }'\n\n# Use model aliases (if configured)\ncurl http://localhost:9876/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"sonnet\",\n    \"messages\": [{\"role\": \"user\", \"content\": \"Hello!\"}]\n  }'\n```\n\n### 3. Point your service to it\n\n```bash\n# Honcho or any OpenAI-compatible client\nexport OPENAI_BASE_URL=http://localhost:9876/v1\nexport OPENAI_API_KEY=your-proxy-key  # if configured\n```\n\n## API Endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/v1/models` | List all available pi models |\n| `POST` | `/v1/chat/completions` | OpenAI-compatible chat completions |\n| `POST` | `/v1/messages` | Anthropic Messages API compatible |\n| `GET` | `/health` | Health check |\n\n### Model naming\n\nModels use `provider/model-id` format matching pi's internal naming:\n\n- `anthropic/claude-sonnet-4-5-20250929`\n- `openai/gpt-5.1-2025-11-13`\n- `google/gemini-2.5-pro-preview-06-05`\n- Custom models from `~/.pi/agent/models.json`\n\nYou can also configure short **aliases** (see Configuration below):\n- `sonnet` → `anthropic/claude-sonnet-4-5-20250929`\n- `gpt4` → `openai/gpt-4o`\n\nUse `GET /v1/models` to see all available models with their exact IDs.\n\n### Supported features\n\n- ✅ Streaming (`stream: true`) and non-streaming responses\n- ✅ System messages\n- ✅ Multi-modal (text + images via base64 data URIs)\n- ✅ Tool calls / function calling (with correct multi-tool indices)\n- ✅ Tool results\n- ✅ Thinking/reasoning (mapped to `reasoning_content` in OpenAI SSE, `thinking_delta` in Anthropic SSE)\n- ✅ Token usage reporting\n- ✅ CORS headers\n- ✅ Model aliasing\n- ✅ Rate limiting\n- ✅ Request logging (JSON Lines)\n- ✅ Request timeout + client disconnect → AbortSignal propagation\n- ✅ Graceful startup (503 before model registry available)\n\n## Configuration (optional)\n\nThe proxy works out of the box with no config. All models are auto-discovered from pi's model registry. An optional config file at `~/.pi/model-proxy.json` enables additional features:\n\n```json\n{\n  \"port\": 9876,\n  \"defaultModel\": \"anthropic/claude-sonnet-4-5-20250929\",\n  \"apiKey\": \"my-secret-key\",\n  \"allowedOrigins\": [\"*\"],\n  \"aliases\": {\n    \"sonnet\": \"anthropic/claude-sonnet-4-5-20250929\",\n    \"gpt4\": \"openai/gpt-4o\"\n  },\n  \"rateLimit\": 60,\n  \"requestTimeoutMs\": 120000,\n  \"logPath\": \"~/.pi/model-proxy-log.jsonl\"\n}\n```\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `port` | `9876` | Port for the local API server |\n| `defaultModel` | — | Default model when request omits `model` field |\n| `apiKey` | — | Optional API key to protect the proxy (sent as `Bearer` token or `x-api-key` header) |\n| `allowedOrigins` | `[\"*\"]` | CORS allowed origins |\n| `aliases` | — | Short model names → full `provider/model-id` strings |\n| `rateLimit` | — | Per-minute request cap (0 or omitted = disabled) |\n| `requestTimeoutMs` | `120000` | Request timeout in milliseconds |\n| `logPath` | `~/.pi/model-proxy-log.jsonl` | Path to JSON Lines log file |\n\n## Development\n\nFor contributing or running from source:\n\n```bash\ngit clone https://github.com/BlackBeltTechnology/pi-model-proxy.git\ncd pi-model-proxy\nnpm install\nnpm test              # Run unit + integration tests (~500ms)\nnpm run typecheck     # TypeScript type checking\n./test/e2e.sh         # Run E2E tests against real pi instance (~30s)\n./test/e2e.sh 9876 --no-start  # E2E against already-running pi\n```\n\nTo load the extension from source during development:\n\n```bash\npi -e /path/to/pi-model-proxy\n```\n\n### Test layers\n\n| Layer | What it tests |\n|-------|---------------|\n| **Unit** (`npm test`) | Message conversion, config loading, rate limiter, logging |\n| **Integration** (`npm test`) | Full HTTP request→response pipeline with mocked provider |\n| **E2E** (`./test/e2e.sh`) | Real pi instance, real API calls, all endpoints (20 assertions) |\n\n## Releasing\n\nReleases are automated via GitHub Actions. To publish a new version:\n\n```bash\ngit tag v1.0.0\ngit push origin v1.0.0\n```\n\nThis triggers the release workflow which:\n1. Extracts the version from the git tag\n2. Runs typecheck and tests\n3. Publishes to npm as `@blackbelt-technology/pi-model-proxy`\n4. Creates a GitHub Release with auto-generated release notes\n\n### Version convention\n\nUse [semantic versioning](https://semver.org/):\n- `v1.0.0` → `v1.0.1` — bug fixes\n- `v1.0.0` → `v1.1.0` — new features (backward compatible)\n- `v1.0.0` → `v2.0.0` — breaking changes\n\n> **Note:** The version in `package.json` is set automatically by CI from the git tag. You don't need to update it manually.\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `/proxy-status` | Show proxy server status and model count |\n\n## Use Cases\n\n### Honcho memory service\n\nPoint Honcho's LLM config to your local proxy:\n\n```python\nimport openai\n\nclient = openai.OpenAI(\n    base_url=\"http://localhost:9876/v1\",\n    api_key=\"your-proxy-key\",\n)\n\nresponse = client.chat.completions.create(\n    model=\"sonnet\",  # uses alias\n    messages=[{\"role\": \"user\", \"content\": \"Hello\"}],\n)\n```\n\n### Anthropic SDK\n\n```python\nimport anthropic\n\nclient = anthropic.Anthropic(\n    base_url=\"http://localhost:9876/v1\",\n    api_key=\"your-proxy-key\",\n)\n\nmessage = client.messages.create(\n    model=\"sonnet\",\n    max_tokens=1024,\n    messages=[{\"role\": \"user\", \"content\": \"Hello!\"}],\n)\n```\n\n### LangChain / LangGraph\n\n```python\nfrom langchain_openai import ChatOpenAI\n\nllm = ChatOpenAI(\n    base_url=\"http://localhost:9876/v1\",\n    api_key=\"your-proxy-key\",\n    model=\"sonnet\",\n)\n```\n\n### Any OpenAI-compatible SDK\n\n```typescript\nimport OpenAI from \"openai\";\n\nconst client = new OpenAI({\n  baseURL: \"http://localhost:9876/v1\",\n  apiKey: \"your-proxy-key\",\n});\n\nconst completion = await client.chat.completions.create({\n  model: \"anthropic/claude-sonnet-4-5-20250929\",\n  messages: [{ role: \"user\", content: \"Hello!\" }],\n});\n```\n\n### Use pi's OAuth subscriptions\n\nIf you're logged into Claude Pro/Max via `/login anthropic` in pi, the proxy automatically uses those OAuth tokens. External services get access to your subscription without needing API keys.\n\n## Security\n\n- **Local only by default** — The server binds to `localhost`\n- **Optional API key** — Set `apiKey` in config to require authentication\n- **No credential exposure** — API keys and OAuth tokens stay in pi's auth storage\n- **CORS configurable** — Restrict origins for browser-based clients\n- **Rate limiting** — Prevent runaway external services from burning API quota\n\n## Architecture\n\nThe extension uses a modular `src/` structure:\n\n- `src/extension.ts` — Pi extension lifecycle (session_start, session_shutdown)\n- `src/server.ts` — HTTP server with middleware pipeline (CORS → auth → rate limit → routing → logging)\n- `src/routes/` — Request handlers for each endpoint\n- `src/convert/` — Bidirectional format conversion (OpenAI ↔ pi-ai ↔ Anthropic)\n- `src/config.ts` — Configuration loading and model alias resolution\n- `src/rate-limiter.ts` — Sliding window rate limiter\n- `src/logging.ts` — JSON Lines request logging\n\nFor each incoming request:\n1. Resolves the model from pi's `ModelRegistry` (with alias support)\n2. Resolves API key/headers via `ModelRegistry.getApiKeyAndHeaders()`\n3. Calls `streamSimple()` from pi-ai with the resolved credentials and an AbortSignal\n4. Converts pi-ai's event stream back to the client's format\n\nNo custom translation code is needed — pi-ai handles all provider-specific format conversion internally.\n\n## Inspiration\n\nThis project is inspired by [9router](https://github.com/decolua/9router), a standalone LLM proxy service. While 9router is a full-featured production proxy, pi-model-proxy is designed as a lightweight pi extension that reuses pi's existing infrastructure:\n\n| | [9router](https://github.com/decolua/9router) | pi-model-proxy |\n|---|---------|------------------|\n| **Type** | Standalone Docker service (Next.js) | Pi extension (Node.js, no framework) |\n| **Setup** | Docker compose, provider config, API keys | `pi -e .` — zero config |\n| **Auth** | Own API key management + provider keys | Pi's ModelRegistry + OAuth tokens |\n| **Models** | Manual provider connections + pools | Auto-discovered from pi registry |\n| **Routing** | Round-robin, sticky sessions, strategies | Direct pass-through via pi-ai |\n| **Features** | Tunnels, MITM, pricing, usage dashboard | Lightweight local proxy |\n| **Format translation** | Custom ~50 file translator layer | Handled by pi-ai's `streamSimple()` |\n| **Deployment** | Traefik, Cloudflare tunnels, multi-user | Single-user, localhost |\n\n9router is the right choice when you need a shared, multi-user LLM gateway with provider pooling and usage tracking. pi-model-proxy is for when you want to quickly expose your pi models to local tools and services with no setup.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}