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. Rich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate, creating an engaging development experience.
Explore an engaging development experience through rich, convenient UI interactions.
- SmartOpenSpec workflows turn intent into an executable path, from proposal to design, tasks, and a shipped change.
- EfficientMulti-agent execution keeps research, implementation, and review moving in parallel, across several repositories and the Agent CLIs you already use.
- InterestingRich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate.
Download for Windows
Share HagiCode
Turn ideas into shipped software.
HagiCode is an agentic coding workspace. OpenSpec workflows plan the change, multi-agent execution carries it out, and Hero Scenario makes the long run visible, so ideas become shipped software.
- Smart
- OpenSpec workflows turn intent into a path from proposal to shipped change.
- Efficient
- Multi-agent execution runs research, build, and review in parallel.
- Fun
- Rich UI interactions make planning, agent work, and projects easier to follow.
© 2026 HagiCode | Powered By hagilight-starlight@0.21.0 | Branch: main | Commit: de60bcd | Built:

