This file provides guidelines to AI agents when working with code in this repository.
opencode-omniroute-auth is an OpenCode authentication plugin for the OmniRoute API. It provides a /connect omniroute command, API-key auth, dynamic model fetching from /v1/models, and combo model capability enrichment.
# Build (required before running tests)
npm run build
# Watch mode during development
npm run dev
# Run tests (builds first, then runs Node built-in test runner)
npm test
# Run a single test file
npm run build && node --test test/plugin.test.mjs
# Type-check a single file without emitting
npx tsc --noEmit src/plugin.ts
# Clean build output
npm run clean
# Validate dist exports satisfy plugin loader constraints
npm run check:exports
# Full publish prep
npm run prepublishOnlyindex.ts— Main plugin export (OmniRouteAuthPlugin). Required by OpenCode's plugin loader. All root exports must be functions.runtime.ts— Runtime utilities (fetchModels,clearModelCache, combo helpers, etc.) exported for programmatic use.
| File | Responsibility |
|---|---|
src/plugin.ts |
Plugin implementation: config hook (registers omniroute provider), auth hook (/connect command), loadProviderOptions (fetches models and returns a fetch interceptor). |
src/models.ts |
fetchModels() fetches /v1/models, manages an in-memory cache keyed by baseUrl:apiKey, falls back to defaults on failure. Orchestrates metadata enrichment via models-dev.ts and combo enrichment via omniroute-combos.ts. |
src/models-dev.ts |
Fetches https://models.dev/api.json, builds indexed lookup maps (exact/normalized, provider-specific and global), and maps OmniRoute provider keys to models.dev providers via aliases. |
src/omniroute-combos.ts |
Fetches combo definitions from /api/combos. Resolves underlying models and calculates lowest-common-denominator capabilities (min context/maxTokens, vision/tools only if ALL underlying models support them). |
src/constants.ts |
Endpoints, default models, TTLs, timeouts. |
src/types.ts |
Shared TypeScript interfaces. |
The loader returns a fetch function that:
- Adds
Authorization: Bearer <apiKey>andContent-Type: application/jsonheaders. - Only intercepts requests to the configured OmniRoute base URL (with safe prefix matching).
- Sanitizes Gemini tool schemas by stripping
$schema,$ref,ref, andadditionalPropertieskeywords when the model name includes "gemini".
Three independent in-memory caches:
- Model cache (
src/models.ts) — keyed bybaseUrl:apiKey, TTL defaults to 5 minutes. - models.dev cache (
src/models-dev.ts) — global singleton, TTL defaults to 24 hours. - Combo cache (
src/omniroute-combos.ts) — global singleton, TTL defaults to 5 minutes.
clearModelCache() also clears the combo cache.
- Target: ES2022, Module: NodeNext (ESM).
- Strict Mode: Enabled. Never disable strict checks.
- Formatting: 2 spaces, max 100 chars/line, semicolons required, single quotes for strings, trailing commas in multi-line objects/arrays.
- Constants:
UPPER_SNAKE_CASE(e.g.,OMNIROUTE_PROVIDER_ID) - Variables/Functions:
camelCase(e.g.,modelCache,fetchModels()) - Classes/Interfaces/Types:
PascalCase(e.g.,OmniRouteConfig) - Files:
kebab-case(e.g.,opencode-plugin.d.ts)
- CRITICAL: Always use explicit
.jsextensions for relative imports (e.g.,import { x } from './file.js'). - Group imports: external → internal → types.
- Use named exports only (no default exports).
- Never use
any. Useunknownif uncertain, then narrow. - Always type function parameters and return types.
- Prefer runtime validation over unsafe type assertions.
// ✅ Correct
const rawData = await response.json();
if (!rawData || typeof rawData !== 'object' || !Array.isArray(rawData.data)) {
throw new Error('Invalid response structure');
}
const data = rawData as OmniRouteModelsResponse;- Always use
try/catch/finallyfor resource cleanup (e.g.,clearTimeout). - Provide meaningful error messages.
- Security: Sanitize error logs. Never log full API responses or sensitive keys (e.g., log "Cache cleared for provided config" instead of logging the API key).
// ✅ Correct
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
return await response.json();
} finally {
clearTimeout(timeoutId);
}- Headers: Use the
Headersconstructor for proper normalization.const headers = new Headers(init?.headers); headers.set('Authorization', `Bearer ${apiKey}`); headers.set('Content-Type', 'application/json');
- URLs: Handle both
Requestobjects and string URLs safely.const url = input instanceof Request ? input.url : input.toString();
- Security: When intercepting requests, ensure
baseUrlends with a slash for safe prefix matching to prevent domain spoofing. Validate endpoint URLs strictly (requirehttp:orhttps:).
src/plugin.ts: Main plugin implementation &/connectcommand.src/models.ts: Model fetching, caching, and validation.src/constants.ts: Configuration constants (OMNIROUTE_ENDPOINTS, etc.).src/types.ts: TypeScript definitions.index.ts: Main exports.
- Adding Exports: Add in source file, re-export in
index.ts(with.js), runnpm run build. - Debugging: Look for
[OmniRoute]prefix in console logs.
- Update
package.jsonversion. - Update
CHANGELOG.mdwith a new section for the release. Include the date and credit contributors by GitHub username (e.g.,@username) when applicable. - If there are missing changelog sections for prior releases (e.g.,
1.1.0was released but never documented), add them retroactively so the changelog is complete. - Commit the changes:
git add package.json CHANGELOG.md git commit -m "chore: bump version to X.Y.Z"
Ensure the release PR is merged into main:
git checkout main
git pull origin mainCreate and push an annotated tag matching the version:
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.ZCreate a release from the tag using gh:
gh release create vX.Y.Z --title "vX.Y.Z" --notes "$(sed -n '/## \[X.Y.Z\]/,/^## /p' CHANGELOG.md | sed '$d')"Or use a prepared notes file if one exists in docs/:
gh release create vX.Y.Z --title "vX.Y.Z" --notes-file docs/release-notes-vX.Y.Z.md- Verify you are logged in:
npm whoami
- Run the publish prep (clean, build, and export validation):
npm run prepublishOnly
- Publish:
npm publish
If npm requires an MFA/2FA OTP, publish with:
npm publish --otp <CODE>- Confirm the package version on npm:
npm view opencode-omniroute-auth version
- Confirm the GitHub release exists:
gh release view vX.Y.Z