This file provides guidance to Claude Code (claude.ai/code) 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.
- ESM only with
NodeNextmodule resolution. Every relative import must use an explicit.jsextension (e.g.,import { x } from './file.js'). - Import grouping: external → internal → types.
- Strict TypeScript — never use
any. Preferunknownwith runtime narrowing. - Runtime validation before type assertions for external API responses (e.g., validate
typeof rawData === 'object' && Array.isArray(rawData.data)before casting toOmniRouteModelsResponse). - Security/logging — never log API keys or full response bodies. Use the
[OmniRoute]log prefix. - Resource cleanup — always wrap
fetch+AbortControllertimeouts intry/finallywithclearTimeout. - Naming:
PascalCasefor types/interfaces,camelCasefor functions/variables,UPPER_SNAKE_CASEfor constants,kebab-casefor files. - Formatting: 2 spaces, max 100 chars/line, semicolons required, single quotes for strings, trailing commas in multi-line objects/arrays.
- Headers/URLs: Use the
Headersconstructor for normalization. Handle bothRequestobjects and string URLs (input instanceof Request ? input.url : input.toString()). When intercepting requests, ensurebaseUrlends with/for safe prefix matching to prevent domain spoofing.
Tests use Node.js's built-in test runner (node:test, node:assert/strict) and import from dist/. They mock global.fetch.
- Tests live in
test/*.test.mjs. npm testalways rebuilds first; if you edit source and rerun a single test file manually, runnpm run buildfirst.
tsconfig.jsoncompiles from the root (rootDir: ".") and outputs to./dist.package.jsonexportsmaps"."todist/index.jsand"./runtime"todist/runtime.js.
- Adding exports: Add the export in the source file, re-export it from
index.tsorruntime.ts(with.jsextension), then runnpm run build. - Debugging: Look for the
[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