--- title: "OmniRoute Plugin SDK" version: 3.8.40 lastUpdated: 2026-06-28 --- # OmniRoute Plugin SDK ## Quick Start ```ts import { definePlugin } from "omniroute/plugins/sdk"; export default definePlugin({ name: "my-plugin", priority: 50, onRequest: async (ctx) => { console.log(`Request ${ctx.requestId} for ${ctx.model}`); }, onResponse: async (ctx, response) => { console.log(`Response for ${ctx.requestId}`); return response; }, onError: async (ctx, error) => { console.error(`Error: ${error.message}`); }, }); ``` ## API Reference ### `definePlugin(def: PluginDefinition): Plugin` Factory function that creates a Plugin object with defaults. **Parameters:** - `name` (string, required) — Plugin name in kebab-case - `priority` (number, optional, default: 100) — Lower runs first - `enabled` (boolean, optional, default: true) — Start enabled? - `onRequest` (function, optional) — Runs before chat handler - `onResponse` (function, optional) — Runs after chat handler - `onError` (function, optional) — Runs on handler error ### `blockRequest(response?): BlockingHookResult` Block the request and optionally return a custom response. ```ts onRequest: (ctx) => { if (!ctx.headers["authorization"]) { return blockRequest({ error: "Unauthorized", status: 401 }); } }; ``` ### `modifyBody(body): PluginResult` Modify the request body before it reaches the provider. ```ts onRequest: (ctx) => { return modifyBody({ ...ctx.body, temperature: 0.7 }); }; ``` ### `addMetadata(metadata): PluginResult` Attach metadata to the request context. ```ts onRequest: (ctx) => { return addMetadata({ source: "my-plugin", version: "1.0.0" }); }; ``` ## Plugin Context (`PluginContext`) | Field | Type | Description | | ----------- | ------------------------- | ------------------------- | | `requestId` | `string` | Unique request identifier | | `model` | `string` | Requested model name | | `provider` | `string` | Target provider ID | | `body` | `Record` | Request body | | `headers` | `Record` | Request headers | | `metadata` | `Record` | Mutable metadata | | `timestamp` | `number` | Request timestamp | ## Manifest (`plugin.json`) ```json { "name": "my-plugin", "version": "1.0.0", "description": "A sample plugin", "author": "your-name", "main": "index.js", "hooks": { "onRequest": { "enabled": true, "priority": 50 }, "onResponse": true, "onError": false }, "requires": { "permissions": ["network", "file-read"] }, "enabledByDefault": false, "configSchema": { "apiKey": { "type": "string", "description": "API key for external service" }, "maxRetries": { "type": "number", "min": 1, "max": 10, "default": 3 }, "debug": { "type": "boolean", "default": false }, "mode": { "type": "string", "enum": ["fast", "slow"], "default": "fast" } } } ``` ### Hook Priority Hooks can be configured with priority (lower = runs first): ```json { "hooks": { "onRequest": { "enabled": true, "priority": 10 }, "onResponse": { "enabled": true, "priority": 100 } } } ``` Or as simple booleans (default priority 100): ```json { "hooks": { "onRequest": true, "onResponse": true } } ``` ## Permission System Plugins run in a sandboxed VM context. Access to external resources requires explicit permissions: | Permission | Grants | | ------------ | ------------------------------------------------------------ | | `network` | `fetch`, `AbortController`, `Headers`, `Request`, `Response` | | `file-read` | `fs.readFile`, `fs.readdir`, `fs.stat` | | `file-write` | `fs.writeFile`, `fs.mkdir`, `fs.rm` | | `env` | Read-only `process.env` proxy | | `exec` | `child_process.exec`, `child_process.execSync` | Without a permission, the corresponding globals are simply not available in the sandbox. ## Config Schema Define configurable settings in `configSchema`: ```json { "configSchema": { "apiKey": { "type": "string", "description": "External API key" }, "maxRetries": { "type": "number", "min": 1, "max": 10, "default": 3 }, "debug": { "type": "boolean", "default": false }, "mode": { "type": "string", "enum": ["fast", "slow"], "default": "fast" } } } ``` Field types: `string`, `number`, `boolean`, `select` Field options: `default`, `min`, `max`, `enum`, `description` Config values are persisted in the database and accessible via the dashboard config page. ## Built-in Events | Event | When | Payload | | ----------------- | ----------------------------------------- | ----------------------------- | | `onRequest` | Before chat handler | Request context | | `onResponse` | After chat handler | Response data | | `onError` | On handler error | Error object | | `onModelSelect` | Model selected for routing | Model info | | `onComboResolve` | Combo routing resolved | Combo targets | | `onRateLimit` | Rate limit hit | Limit info | | `onQuotaExhaust` | Quota exhausted | Quota info | | `onProviderError` | Provider returned error | Error details | | `onStreamStart` | SSE stream started | Stream info | | `onStreamEnd` | SSE stream ended | Stream stats | | `onInstall` | Plugin installed | `{ name, version, manifest }` | | `onActivate` | Plugin activated | `{ name, version, manifest }` | | `onDeactivate` | Plugin deactivated | `{ name, version, manifest }` | | `onUninstall` | Plugin uninstalled (before files deleted) | `{ name, version, manifest }` | ## Examples ### Request Logger ```ts import { definePlugin } from "omniroute/plugins/sdk"; export default definePlugin({ name: "request-logger", onRequest: async (ctx) => { console.log(`[${new Date().toISOString()}] ${ctx.method} ${ctx.model} -> ${ctx.provider}`); }, }); ``` ### Rate Limiter ```ts import { definePlugin, blockRequest } from "omniroute/plugins/sdk"; const requests = new Map(); export default definePlugin({ name: "rate-limiter", priority: 10, onRequest: async (ctx) => { const key = ctx.headers["x-api-key"] || "anonymous"; const now = Date.now(); const window = 60000; // 1 minute const maxRequests = 100; const timestamps = (requests.get(key) || []).filter((t) => t > now - window); timestamps.push(now); requests.set(key, timestamps); if (timestamps.length > maxRequests) { return blockRequest({ error: "Rate limit exceeded", status: 429 }); } }, }); ``` ### Response Transformer ```ts import { definePlugin } from "omniroute/plugins/sdk"; export default definePlugin({ name: "response-transformer", onResponse: async (ctx, response) => { if (response.choices) { response.choices = response.choices.map((c: any) => ({ ...c, message: { ...c.message, content: c.message.content.trim() }, })); } return response; }, }); ```