# pi-gateway Skill A comprehensive guide for AI agents to understand, install, configure, and use pi-gateway. --- ## Overview pi-gateway is a multi-channel AI gateway that connects the pi coding agent to messaging platforms (Telegram, Discord, Feishu, WebChat). It handles RPC process management, message routing, session isolation, and provides a plugin system for extensibility. ``` +-------------------------------------------------------------------------+ | pi-gateway | | | | Telegram ----+ | | | +------------+ +-------------+ +-------+ | | Discord ------+--> | Plugin | --> | Session | --> | RPC | | | | | System | | Router | | Pool | | | Feishu ------+ +------------+ +-------------+ +-------+ | | | | | | | | WebChat -----+ Hooks/Tools Message Queue pi --mode rpc | | | | | | | Auth/Transform Backpressure stdin/stdout | | | +-------------------------------------------------------------------------+ ``` **Key Differentiator**: All pi skills work seamlessly. No modification needed. --- ## Installation ### Prerequisites - Bun >= 1.1.0 - Node.js >= 18 (for tooling) - pi CLI (`npm install -g @mariozechner/pi-coding-agent`) - LLM provider (API key or OAuth via `pi /login`) ### Steps ```bash # Clone repository git clone https://github.com/Dwsy/pi-gateway.git cd pi-gateway # Install dependencies bun install # Copy example config cp pi-gateway.jsonc.example pi-gateway.jsonc # Edit config with your tokens # Set environment variables or edit pi-gateway.jsonc directly export TELEGRAM_BOT_TOKEN="your-bot-token" export DISCORD_TOKEN="your-discord-token" # Start gateway bun run start # Or with verbose logging bun run src/cli.ts gateway --verbose # Health check bun run src/cli.ts doctor ``` ### Build Standalone Binary ```bash bun run build # Output: dist/pi-gw ``` --- ## Configuration Configuration file: `pi-gateway.jsonc` (JSONC format with comments) ### Minimal Configuration ```jsonc { "gateway": { "port": 52134 }, "agent": { "model": "claude-sonnet-4" }, "channels": { "telegram": { "enabled": true, "botToken": "${TELEGRAM_BOT_TOKEN}" } } } ``` ### Full Configuration Schema ```jsonc { // Gateway settings "gateway": { "port": 52134, // HTTP/WebSocket port "bind": "loopback", // "loopback" | "lan" | "auto" "auth": { "mode": "token", // "off" | "token" | "password" "token": "secret" // Auth token (auto-generated if omitted) } }, // Agent settings "agent": { "model": "claude-sonnet-4", "modelFailover": { "primary": "claude-sonnet-4", "fallbacks": ["gpt-4o", "gemini-pro"], "maxRetries": 2, "cooldownMs": 60000 }, "piCliPath": "pi", "thinkingLevel": "high", // "off" | "minimal" | "low" | "medium" | "high" "runtime": { "agentDir": "~/.pi/gateway/runtime-agent", "packageDir": "~/.pi/gateway/runtime-package" }, "skillsBase": ["~/.pi/agent/skills"], "skillsGateway": ["~/.pi/gateway/skills"], "extensions": ["~/.pi/agent/extensions/role-persona/index.ts"], "pool": { "min": 1, "max": 4, "idleTimeoutMs": 300000 }, "appendSystemPrompt": "~/.pi/agent/APPEND_SYSTEM.md" }, // Role capabilities (merge mode) "roles": { "mergeMode": "append", "capabilities": { "mentor": { "skills": ["~/.pi/gateway/skills/mentor"], "extensions": ["~/.pi/gateway/pi-extensions/mentor-tools/index.ts"], "promptTemplates": ["~/.pi/gateway/prompts/mentor"] } } }, // Channel configurations "channels": { "telegram": { "enabled": true, "botToken": "${TELEGRAM_BOT_TOKEN}", "dmPolicy": "pairing", // "pairing" | "allowlist" | "open" | "disabled" "allowlist": ["123456789"], "streamingMode": "partial", // "off" | "partial" | "block" | "draft" "accounts": { "default": { "enabled": true }, "work": { "enabled": true, "botToken": "${WORK_BOT_TOKEN}", "webhookUrl": "https://example.com/telegram/work", "webhookSecret": "secret" } }, "groups": { "123456789": { "requireMention": true, "role": "assistant" } } }, "discord": { "enabled": true, "token": "${DISCORD_TOKEN}", "streamingMode": "partial" }, "webchat": { "enabled": true, "auth": { "mode": "token", "token": "webchat-secret" } } }, // Cron scheduled tasks "cron": { "enabled": true, "jobs": [ { "id": "daily-report", "schedule": { "kind": "cron", "expr": "0 9 * * *", "timezone": "Asia/Shanghai" }, "task": "Generate daily report", "delivery": "announce", // "announce" | "direct" | "silent" "resumeContext": false } ] }, // Plugin settings "plugins": ["./plugins/my-plugin/index.ts"], // Hooks "hooks": { "enabled": true, "token": "webhook-secret", "path": "/hooks" } } ``` ### Configuration Priority Skills: `roles.capabilities[role].skills` -> `agent.skillsGateway` -> `agent.skillsBase` Extensions: `roles.capabilities[role].extensions` -> `agent.extensions` Prompt Templates: `roles.capabilities[role].promptTemplates` -> `agent.promptTemplates` ### Environment Variables Use `${VAR_NAME}` syntax in config: ```jsonc { "channels": { "telegram": { "botToken": "${TELEGRAM_BOT_TOKEN}" } } } ``` --- ## Channel Setup ### Telegram 1. Create bot via [@BotFather](https://t.me/botfather): ``` /newbot # Follow instructions, copy token ``` 2. Configure: ```jsonc { "channels": { "telegram": { "enabled": true, "botToken": "${TELEGRAM_BOT_TOKEN}", "dmPolicy": "pairing" } } } ``` 3. Approve DM pairing: ```bash pi-gw pairing approve telegram ABCD1234 ``` ### Discord 1. Create bot at [Discord Developer Portal](https://discord.com/developers/applications) 2. Enable intents: Message Content, Guild Messages, DMs 3. Generate invite URL with bot scope and permissions 4. Configure: ```jsonc { "channels": { "discord": { "enabled": true, "token": "${DISCORD_TOKEN}" } } } ``` ### Feishu (Lark) 1. Create app at [Feishu Open Platform](https://open.feishu.cn/) 2. Configure permissions and event subscriptions 3. Configure: ```jsonc { "channels": { "feishu": { "enabled": true, "appId": "${FEISHU_APP_ID}", "appSecret": "${FEISHU_APP_SECRET}" } } } ``` ### WebChat Built-in web interface at `http://localhost:52134`: ```jsonc { "channels": { "webchat": { "enabled": true, "auth": { "mode": "token", "token": "webchat-secret" } } } } ``` --- ## CLI Commands ```bash # Start gateway pi-gw gateway [--port N] [--verbose] # Health check pi-gw doctor # Send message pi-gw send --to telegram:123456789 --message "Hello" pi-gw send --to telegram:default:123456789:topic:456 --message "Topic message" pi-gw send --to discord:789012345 --message "Hello Discord" # Show config pi-gw config show # Interactive setup pi-gw onboard [--install-daemon] # Pairing management pi-gw pairing list pi-gw pairing approve telegram ABCD1234 --account default pi-gw pairing deny telegram ABCD1234 ``` --- ## Agent Tools pi-gateway provides these tools for pi agents: ### send_message Send text message to channel: ```typescript await send_message({ text: "Hello!", replyTo: "message-id" // optional, for threaded reply }); ``` ### send_media Send media file to channel: ```typescript await send_media({ path: "./image.png", type: "photo", // "photo" | "audio" | "document" | "video" | "sticker" caption: "Optional caption" }); ``` ### message React, edit, delete, pin messages: ```typescript // React with emoji await message({ action: "react", messageId: "123", emoji: "👍" }); // Edit message await message({ action: "edit", messageId: "123", text: "Updated text" }); // Delete message await message({ action: "delete", messageId: "123" }); // Pin message await message({ action: "pin", messageId: "123" }); ``` ### cron Manage scheduled tasks: ```typescript // List jobs await cron({ action: "list" }); // Add job await cron({ action: "add", id: "reminder", schedule: { kind: "every", expr: "1h" }, task: "Check for pending items" }); // Run job await cron({ action: "run", id: "reminder" }); // Pause/resume await cron({ action: "pause", id: "reminder" }); await cron({ action: "resume", id: "reminder" }); ``` ### gateway Gateway management: ```typescript // Reload config await gateway({ action: "reload" }); // Restart gateway await gateway({ action: "restart" }); ``` ### session_status Query current session's runtime status: ```typescript const status = await session_status(); // Returns: token usage, cost, model, context utilization ``` --- ## Plugin Development ### Plugin Structure ``` my-plugin/ ├── index.ts # Plugin entry point ├── types.ts # Type definitions (optional) └── utils.ts # Helper functions (optional) ``` ### Plugin Entry Point ```typescript // my-plugin/index.ts import type { GatewayPluginApi } from "pi-gateway"; export default function myPlugin(api: GatewayPluginApi) { // Register channel api.registerChannel({ name: "my-channel", // ... channel implementation }); // Register tool api.registerTool({ name: "my_tool", description: "My custom tool", parameters: { /* JSON Schema */ }, execute: async (params, context) => { return { result: "success" }; } }); // Register hook api.registerHook(["message_received"], async (context) => { console.log("Message received:", context.message); }); // Register HTTP route api.registerHttpRoute("GET", "/my-endpoint", async (req, res) => { res.json({ status: "ok" }); }); // Register WebSocket method api.registerGatewayMethod("my.rpc", async (params) => { return { result: "ok" }; }); // Register slash command api.registerCommand("mycommand", async (context) => { await context.reply("Command executed!"); }); // Register background service api.registerService({ name: "my-service", start: async () => { /* ... */ }, stop: async () => { /* ... */ } }); } ``` ### Available Hooks | Hook | Context | Description | |------|---------|-------------| | `before_agent_start` | `{ sessionKey, message, config }` | Inject context before agent run | | `agent_end` | `{ sessionKey, messages, usage }` | Inspect final messages after run | | `message_received` | `{ sessionKey, message, channel }` | Inbound message from channel | | `message_sending` | `{ sessionKey, message, channel }` | Outbound before send (mutable) | | `message_sent` | `{ sessionKey, message, channel }` | Outbound after send | | `before_tool_call` | `{ sessionKey, tool, params }` | Intercept tool parameters | | `after_tool_call` | `{ sessionKey, tool, params, result }` | Intercept tool results | | `tool_result_persist` | `{ sessionKey, tool, result }` | Transform before persist | | `session_start` | `{ sessionKey }` | Session created | | `session_end` | `{ sessionKey }` | Session ended | | `before_compaction` | `{ sessionKey }` | Before context compact | | `after_compaction` | `{ sessionKey }` | After context compact | | `gateway_start` | `{ config }` | Gateway started | | `gateway_stop` | `{}` | Gateway stopping | ### Plugin Configuration Add to `pi-gateway.jsonc`: ```jsonc { "plugins": [ "./plugins/my-plugin/index.ts" ] } ``` --- ## Architecture ### Components ``` +------------------+ | Channels | Telegram, Discord, Feishu, WebChat +--------+---------+ | v +--------+---------+ | Plugin System | 14 hooks, 8 registration APIs +--------+---------+ | v +--------+---------+ | Session Router | agent:{id}:{channel}:{chat} +--------+---------+ | v +--------+---------+ | Message Queue | Per-session serial, backpressure +--------+---------+ | v +--------+---------+ | RPC Pool | Manage pi --mode rpc subprocesses +--------+---------+ | v +--------+---------+ | pi agent | + skills +------------------+ ``` ### Session Key Format ``` agent:{agentId}:{channel}:{scope}:{id} Examples: agent:main:telegram:dm:123456789 agent:main:telegram:group:-100123456789 agent:main:telegram:topic:-100123456789:456 agent:main:discord:channel:789012345 agent:mentor:telegram:dm:123456789 ``` ### Message Flow ``` 1. User sends message on channel 2. Channel plugin receives and normalizes 3. Plugin hooks process (auth, transform) 4. Session router determines target session 5. Message queue serializes per-session 6. RPC pool routes to agent process 7. Agent processes with pi skills 8. Response flows back through queue 9. Plugin hooks process response 10. Channel sends to platform ``` --- ## Security ### DM Policies | Policy | Behavior | |--------|----------| | `pairing` | Unknown users get 8-char code, admin approves | | `allowlist` | Only configured user IDs can interact | | `open` | Anyone can interact | | `disabled` | Block all DMs | ### SSRF Guard Prevents agents from accessing internal network resources: ```jsonc { "security": { "ssrfGuard": { "enabled": true, "blockPrivateIPs": true, "blockLocalhost": true, "allowedHosts": ["api.example.com"] } } } ``` ### Authentication ```jsonc { "gateway": { "auth": { "mode": "token", "token": "your-secret-token" } } } ``` --- ## HTTP API | Endpoint | Method | Description | |----------|--------|-------------| | `/health` | GET | Health check | | `/api/sessions` | GET | List active sessions | | `/api/session/:key` | GET | Get session details | | `/api/cron/jobs` | GET | List cron jobs | | `/api/cron/jobs/:id` | GET | Get cron job | | `/api/cron/jobs/:id/run` | POST | Trigger cron job | | `/api/config` | GET | Get config (secrets redacted) | | `/hooks/wake` | POST | Webhook trigger | --- ## WebSocket API Connect to `ws://localhost:52134/ws` ### Methods ```javascript // List sessions ws.send(JSON.stringify({ method: "session.list", params: {} })); // Get session ws.send(JSON.stringify({ method: "session.get", params: { key: "agent:main:telegram:dm:123" } })); // Kill session ws.send(JSON.stringify({ method: "session.kill", params: { key: "..." } })); // Cron operations ws.send(JSON.stringify({ method: "cron.list", params: {} })); ws.send(JSON.stringify({ method: "cron.run", params: { id: "daily-report" } })); ws.send(JSON.stringify({ method: "cron.pause", params: { id: "daily-report" } })); // Config reload ws.send(JSON.stringify({ method: "config.reload", params: {} })); ``` --- ## Troubleshooting ### Cannot connect to Telegram ```bash # Verify bot token curl https://api.telegram.org/bot/getMe # Check environment variable echo $TELEGRAM_BOT_TOKEN # Check if bot is blocked by privacy mode # BotFather -> Bot Privacy -> Disable ``` ### RPC pool exhausted ```jsonc // Increase pool size in pi-gateway.jsonc { "agent": { "pool": { "min": 2, "max": 8 } } } ``` ### Model rate limited ```jsonc // Configure failover chain { "agent": { "modelFailover": { "primary": "claude-sonnet-4", "fallbacks": ["gpt-4o", "gemini-pro"], "maxRetries": 2, "cooldownMs": 60000 } } } ``` ### Session not found Check session key format: - Telegram DM: `agent:main:telegram:dm:123456789` - Telegram Group: `agent:main:telegram:group:-100123456789` - Discord: `agent:main:discord:channel:789012345` ### WebSocket connection failed ```bash # Check gateway is running pi-gw doctor # Check port lsof -i :52134 # Use wss:// for HTTPS wss://your-domain.com/ws ``` ### Authentication failed ```bash # Check token in config pi-gw config show # For development, disable auth # In pi-gateway.jsonc: { "gateway": { "auth": { "mode": "off" } } } ``` --- ## Best Practices ### Production Deployment 1. Use process manager (systemd, pm2) 2. Enable HTTPS with reverse proxy 3. Set `dmPolicy: "pairing"` or `"allowlist"` 4. Configure model failover 5. Set up monitoring for RPC pool health 6. Rotate tokens regularly ### Performance 1. Set appropriate pool size (`agent.pool.min/max`) 2. Use webhook mode for Telegram (not polling) 3. Configure message queue limits 4. Enable caching for frequently used data ### Security 1. Never commit `pi-gateway.jsonc` with real tokens 2. Use environment variables for secrets 3. Enable auth for WebChat and API 4. Configure SSRF guard 5. Use `dmPolicy: "pairing"` for DM access control --- ## File Structure ``` pi-gateway/ ├── src/ │ ├── cli.ts # CLI entry point │ ├── server.ts # Gateway core │ ├── core/ │ │ ├── config.ts # Configuration system │ │ ├── rpc-pool.ts # RPC process pool │ │ ├── session-router.ts │ │ ├── message-queue.ts │ │ └── cron.ts # Cron engine │ ├── plugins/ │ │ ├── types.ts # Plugin API types │ │ ├── loader.ts # Plugin loader │ │ └── builtin/ # Built-in channels │ │ ├── telegram/ │ │ ├── discord/ │ │ ├── feishu/ │ │ └── webchat.ts │ └── security/ │ ├── allowlist.ts │ └── pairing.ts ├── docs/ # Documentation ├── docs-site/ # Nextra docs site ├── pi-gateway.jsonc.example └── package.json ``` --- ## Related Resources - GitHub: https://github.com/Dwsy/pi-gateway - Documentation: https://dwsy.github.io/pi-gateway - OpenClaw: https://github.com/openclaw/openclaw - pi-mono: https://github.com/badlogic/pi-mono - Bun: https://bun.sh