@riskihajar/opencode-omniroute-auth
v1.3.23
Published
OpenCode authentication plugin for OmniRoute API with /connect command and dynamic model fetching
Maintainers
Readme
OpenCode OmniRoute Auth
What This Is
@riskihajar/opencode-omniroute-auth makes OmniRoute usable as a first-class OpenCode provider.
It is not just an API-key wrapper. The plugin patches the practical compatibility gaps between OpenCode, OmniRoute, OpenAI-compatible routes, Anthropic-compatible routes, Cursor Composer models, Responses API payloads, and incomplete model metadata.
Highlights
| Area | What it does |
|---|---|
| Auth | Adds /connect omniroute and injects OmniRoute API keys safely. |
| Models | Fetches /v1/models, caches with TTL, supports fallback defaults. |
| Runtime modes | Supports chat, responses, and anthropic. |
| Anthropic path | Uses @ai-sdk/anthropic against OmniRoute /v1/messages. |
| Composer | Routes cu/composer-2.5 through Anthropic Messages where tool calls work reliably. |
| Responses | Normalizes OpenCode/OpenAI payloads before forwarding to /v1/responses. |
| Reasoning | Adds reasoning variants and requests visible OpenAI/Codex reasoning summaries where supported. |
| Vision | Hydrates image capability for GPT-5/Codex-style routed models when metadata is incomplete. |
| Metadata | Enriches context, output, tools, reasoning, and image support from models.dev. |
| Combos | Optionally resolves OmniRoute /api/combos with conservative capability merging. |
Install
npm install -g @riskihajar/opencode-omniroute-authThen install the OpenCode server and TUI entries:
npx @riskihajar/opencode-omniroute-auth installThe installer prompts for the OmniRoute baseURL and apiMode, prefilled from your
existing opencode.json (or opencode.jsonc) when present. Press Enter to accept the
defaults (http://localhost:20128/v1, chat, and OpenCode system prompt stripping
enabled). For unattended setups:
npx @riskihajar/opencode-omniroute-auth install \
--base-url=http://192.168.1.10:20128/v1 \
--api-mode=responses
# or accept whatever is already configured
npx @riskihajar/opencode-omniroute-auth install --yesThe installer creates opencode.json and tui.json if they do not exist, appends missing
plugin entries without duplicating existing ones, and updates provider.omniroute.options
with the values you confirmed. It detects opencode.jsonc / tui.jsonc and rewrites the
same file (note: comments are dropped on rewrite; the installer warns when this happens).
The TUI entry is written as an absolute path to dist/tui.js. When the installer is run
through npx, it first installs the TUI package into a persistent OpenCode config
subdirectory, so the generated tui.json does not point at a temporary npx cache. The
installer also migrates any legacy subpath entry from older installs.
Or add the plugin to your OpenCode config manually:
{
"plugin": [
"@riskihajar/opencode-omniroute-auth"
]
}For local development:
{
"plugin": [
"/absolute/path/to/opencode-omniroute-auth"
]
}Quick Start
- Start OmniRoute locally or point
baseURLto your OmniRoute endpoint. - Run
/connect omnirouteinside OpenCode. - Paste your OmniRoute API key.
- Pick a model from
/models. - Run OpenCode with
omniroute/<model-id>.
CLI example:
opencode run "explore project ini" --model omniroute/cu/composer-2.5Recommended Configs
Balanced default
Use Chat Completions compatibility as the global default:
{
"plugin": ["@riskihajar/opencode-omniroute-auth"],
"provider": {
"omniroute": {
"options": {
"baseURL": "http://localhost:20128/v1",
"apiMode": "chat"
}
}
}
}Responses-first OpenAI/Codex setup
Use this when you want GPT/Codex-style models to go through Responses API:
{
"plugin": ["@riskihajar/opencode-omniroute-auth"],
"provider": {
"omniroute": {
"options": {
"baseURL": "http://localhost:20128/v1",
"apiMode": "responses"
}
}
}
}In this mode the plugin still protects known non-Responses-safe models by switching them per-model to a safer runtime.
Anthropic-first setup
Use this for Claude/Composer-style workloads through OmniRoute Messages API:
{
"plugin": ["@riskihajar/opencode-omniroute-auth"],
"provider": {
"omniroute": {
"options": {
"baseURL": "http://localhost:20128/v1",
"apiMode": "anthropic"
}
}
}
}API Modes
chat
Uses @ai-sdk/openai against OmniRoute OpenAI-compatible chat routes.
Best for broadly compatible models and routes that stream chat.completion.chunk events.
responses
Uses @ai-sdk/openai against OmniRoute /v1/responses.
The plugin normalizes payloads for real OmniRoute behavior:
- removes unsupported token limit fields from Responses requests
- strips rejected aliases such as
temperature,reasoning_summary,reasoning_effort, andtextVerbosity - converts
reasoningEffortintoreasoning.effort - preserves accepted Responses fields such as
store,prompt_cache_key,parallel_tool_calls,truncation,service_tier,metadata, andinclude - for OpenAI/Codex-like models, requests reasoning summary support with
reasoning.summary = "auto"andinclude = ["reasoning.encrypted_content"]
Note: requesting reasoning summaries does not force OmniRoute/upstream to emit visible reasoning text. OpenCode can show Thinking: ... only when the stream contains the expected reasoning summary events.
anthropic
Uses @ai-sdk/anthropic against OmniRoute /v1/messages.
This is the important path for Anthropic-family models and Cursor Composer models:
- sets both
api.npmandprovider.npmso OpenCode actually loads the Anthropic SDK - sends Anthropic-compatible headers, including Claude Code and interleaved/fine-grained tool streaming betas
- sanitizes invalid empty SSE events such as
data: {}before the Anthropic parser sees them - preserves valid Anthropic message/content/tool/ping/error stream events
- supports Anthropic
thinkingpayloads when OpenCode provides them - defaults Composer tool-choice handling to
composer-anyfor more reliable tool execution
Automatic Runtime Routing
When global apiMode is responses, the plugin still makes per-model routing decisions where OmniRoute behavior is known to differ from the advertised mode.
| Model family | Runtime selected |
|---|---|
| OpenAI/Codex/GPT-style | responses unless overridden |
| Claude / Opus / Sonnet / Haiku | anthropic |
| cu/composer-2.5 | anthropic |
| cu/default / cursor/default | chat |
| Gemini routed models | chat fallback |
| MLX/Qwen-style routed models | chat fallback |
Suffixes such as -thinking, -reasoning, -high, -medium, -low, -minimal, -max, -xhigh, and -none are normalized before routing decisions.
Per-Model Overrides
Pin a model to a specific runtime when the default router is not what you want:
{
"provider": {
"omniroute": {
"options": {
"apiMode": "responses",
"modelMetadata": {
"minimax/minimax-m1": {
"apiMode": "chat"
},
"some-provider/some-model": {
"apiMode": "responses"
}
}
}
}
}
}Restore normal reasoning variant selection for models that already contain a suffix such as -high:
{
"provider": {
"omniroute": {
"options": {
"modelMetadata": {
"antigravity/gemini-3.1-pro-high": {
"resetEmbeddedReasoningVariant": true,
"reasoning": true
}
}
}
}
}
}Reasoning Support
For reasoning-capable models, the plugin can expose OpenCode variants such as:
lowmediumhigh- custom variants like
xhigh
For Responses-mode OpenAI/Codex-like models, the plugin now also keeps OpenAI progress fields and asks for reasoning summary material in the shape OpenCode expects.
Expected outbound payload shape:
{
"model": "cx/gpt-5.5",
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"include": ["reasoning.encrypted_content"]
}Important distinction:
tokens_reasoning > 0means the model spent reasoning tokens.- Visible
Thinking: ...in OpenCode requires upstream stream events carrying reasoning summary text. - If OmniRoute returns encrypted reasoning only, OpenCode may record reasoning tokens without showing summary text.
Vision / Image Input
OmniRoute model listings often lack enough metadata for OpenCode to know whether a model accepts images. That can make OpenCode reject an image before the request is sent.
The plugin fixes this for supported GPT-5/Codex-style routed models by hydrating both metadata fields OpenCode checks:
capabilities.attachment = truemodalities.input = ["text", "image"]
Detection order:
- explicit
provider.omniroute.options.modelMetadata - OmniRoute model metadata from
/v1/models models.devenrichment- conservative GPT-5/Codex-family fallback heuristics
If a model should stay text-only, pin it explicitly:
{
"provider": {
"omniroute": {
"options": {
"modelMetadata": {
"some-provider/some-text-only-model": {
"supportsVision": false
}
}
}
}
}
}models.dev Enrichment
The plugin can enrich OmniRoute models using models.dev data for:
- context window
- output token limit
- tool support
- reasoning support
- image support
It also resolves routed names more aggressively by checking model id, OmniRoute root, provider aliases, and normalized suffix-free names.
{
"provider": {
"omniroute": {
"options": {
"modelsDev": {
"enabled": true,
"url": "https://models.dev/api.json",
"timeoutMs": 1000,
"cacheTtl": 86400000,
"providerAliases": {
"cx": "openai",
"antigravity": "anthropic"
}
}
}
}
}
}Combo Models
Combo model enrichment is available through OmniRoute /api/combos.
When enabled, the plugin calculates capabilities conservatively from backing models so OpenCode does not over-advertise tools, reasoning, or image support.
{
"provider": {
"omniroute": {
"options": {
"enableCombos": true
}
}
}
}Configuration
| Option | Type | Default | Description |
|---|---|---|---|
| baseURL | string | http://localhost:20128/v1 | OmniRoute base URL. |
| apiMode | chat | responses | anthropic | chat | Global runtime mode. |
| anthropicToolChoice | auto | composer-any | any | composer-any | Anthropic tool-choice policy. |
| stripOpenCodeSystemPrompt | boolean | true | Remove OpenCode's built-in system prompt before forwarding. |
| refreshOnList | boolean | true | Refresh models when provider is loaded. |
| modelCacheTtl | number | package default | Model cache TTL in milliseconds. |
| modelsDev | object | enabled defaults | Configure models.dev enrichment. |
| modelMetadata | object | array | none | Manual metadata overrides and matchers. |
| enableCombos | boolean | false | Fetch and enrich OmniRoute combo models. |
| enableFullGpt55Context | boolean | false | Trust OmniRoute's advertised GPT-5.5 1M context instead of using the safer clamped budget. |
OpenCode system prompt toggle
The plugin exposes stripOpenCodeSystemPrompt as a visible OpenCode TUI toggle when the
TUI subpath is enabled. OpenCode does not currently expose a dedicated plugin statusline
slot, so OmniRoute registers the status in the TUI footer slots (home_footer and
sidebar_footer) as:
OmniRoute system prompt ONEnable the server provider in opencode.json:
{
"plugin": [
"@riskihajar/opencode-omniroute-auth"
]
}Enable the visual TUI extension in tui.json. Use the installer:
npx @riskihajar/opencode-omniroute-auth installThis writes an absolute path entry. For a global install it points at the global package;
for npx it points at the persistent package copy under your OpenCode config directory.
Example:
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
"/usr/lib/node_modules/@riskihajar/opencode-omniroute-auth/dist/tui.js"
]
}Do not edit tui.json to use the subpath spec @riskihajar/opencode-omniroute-auth/tui.
OpenCode 1.15.x's TUI loader runs npm install <spec> per entry, and npm cannot resolve
subpath specs as packages.
Use ctrl+p and select OmniRoute system prompt: ON/OFF, or use the TUI slash commands
/omniroute-system-prompt-toggle, /omniroute-system-prompt-on, and
/omniroute-system-prompt-off. These are TUI commands, not Markdown prompt commands. The
toggle writes:
{
"provider": {
"omniroute": {
"options": {
"stripOpenCodeSystemPrompt": true
}
}
}
}Restart OpenCode or reload the provider config after changing the flag so new requests use it.
Custom Metadata
JSON config:
{
"provider": {
"omniroute": {
"options": {
"modelMetadata": {
"virtual/my-custom-model": {
"contextWindow": 50000,
"maxTokens": 2048,
"apiMode": "chat",
"reasoning": true,
"supportsVision": false
}
}
}
}
}
}JavaScript config with matchers:
export default {
provider: {
omniroute: {
options: {
modelMetadata: [
{ match: /gpt-5\.4/i, reasoning: true },
{ match: /gpt-5\.3-codex$/i, contextWindow: 200000, maxTokens: 8192 },
],
},
},
},
};Runtime Helpers
import {
fetchModels,
clearModelCache,
refreshModels,
} from '@riskihajar/opencode-omniroute-auth/runtime';Debugging
Inspect discovered provider models:
OMNIROUTE_PLUGIN_DEBUG=1 opencode models omniroute --print-logs --log-level DEBUGRun a real Composer turn through OmniRoute:
OMNIROUTE_PLUGIN_DEBUG=1 opencode run "explore project ini" --model omniroute/cu/composer-2.5Run a Responses-mode GPT/Codex turn:
OPENCODE_CONFIG_CONTENT='{"provider":{"omniroute":{"options":{"apiMode":"responses"}}}}' \
OMNIROUTE_PLUGIN_DEBUG=1 \
opencode run "jawab singkat: sebutkan 3 file utama project ini" --model omniroute/cx/gpt-5.5Useful things to verify:
- the selected model uses the intended SDK/runtime (
@ai-sdk/openaior@ai-sdk/anthropic) - Composer calls use
/v1/messages, not/v1/responses - image-capable models expose
attachment=trueandinput.image=true - Responses payloads do not contain unsupported aliases rejected by OmniRoute
- reasoning text appears only when OmniRoute emits reasoning summary stream events
Development
npm install
npm run build
npm testRelease preflight:
npm run prepublishOnlyWhy This Package Exists
OmniRoute and OpenCode both move fast, and provider behavior is not uniform across routed model families. This package ships targeted OmniRoute compatibility fixes without waiting for generic upstream plugin behavior to catch up.
Priority order:
- real OpenCode behavior works
- OmniRoute-specific UX is preserved
- model metadata is conservative instead of misleading
- public installation stays simple
License
MIT
