OmniRoute Plugin SDK
Quick Start
Section titled “Quick Start”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
Section titled “API Reference”definePlugin(def: PluginDefinition): Plugin
Section titled “definePlugin(def: PluginDefinition): Plugin”Factory function that creates a Plugin object with defaults.
Parameters:
name(string, required) — Plugin name in kebab-casepriority(number, optional, default: 100) — Lower runs firstenabled(boolean, optional, default: true) — Start enabled?onRequest(function, optional) — Runs before chat handleronResponse(function, optional) — Runs after chat handleronError(function, optional) — Runs on handler error
blockRequest(response?): BlockingHookResult
Section titled “blockRequest(response?): BlockingHookResult”Block the request and optionally return a custom response.
onRequest: (ctx) => { if (!ctx.headers["authorization"]) { return blockRequest({ error: "Unauthorized", status: 401 }); }};modifyBody(body): PluginResult
Section titled “modifyBody(body): PluginResult”Modify the request body before it reaches the provider.
onRequest: (ctx) => { return modifyBody({ ...ctx.body, temperature: 0.7 });};addMetadata(metadata): PluginResult
Section titled “addMetadata(metadata): PluginResult”Attach metadata to the request context.
onRequest: (ctx) => { return addMetadata({ source: "my-plugin", version: "1.0.0" });};Plugin Context (PluginContext)
Section titled “Plugin Context (PluginContext)”| Field | Type | Description |
|---|---|---|
requestId |
string |
Unique request identifier |
model |
string |
Requested model name |
provider |
string |
Target provider ID |
body |
Record<string, unknown> |
Request body |
headers |
Record<string, string> |
Request headers |
metadata |
Record<string, unknown> |
Mutable metadata |
timestamp |
number |
Request timestamp |
Manifest (plugin.json)
Section titled “Manifest (plugin.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
Section titled “Hook Priority”Hooks can be configured with priority (lower = runs first):
{ "hooks": { "onRequest": { "enabled": true, "priority": 10 }, "onResponse": { "enabled": true, "priority": 100 } }}Or as simple booleans (default priority 100):
{ "hooks": { "onRequest": true, "onResponse": true }}Permission System
Section titled “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
Section titled “Config Schema”Define configurable settings in configSchema:
{ "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
Section titled “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
Section titled “Examples”Request Logger
Section titled “Request Logger”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
Section titled “Rate Limiter”import { definePlugin, blockRequest } from "omniroute/plugins/sdk";
const requests = new Map<string, number[]>();
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
Section titled “Response Transformer”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; },});HagiCode
HagiCode is an agentic coding workspace: structured workflows, multi-agent execution, and Hero Dungeon views turn ideas into shipped software.
Turn ideas into polished, usable software with a smarter, faster, and more enjoyable agentic coding workflow.

- SmartStructured workflows turn intent into an executable path from idea to shipped change.
- EfficientMulti-agent workflows keep research, implementation, and review moving in parallel.
- FunHero Dungeon interfaces make long coding sessions visual, collaborative, and rewarding.