Skip to content
Night Owls.dev
Jump to a page

@nightowlsdev/provider-openrouter

Adapter/Model

Any model via OpenRouter for Night Owls swarms, using `provider/model` ids.

What it does

Exposes `openrouterModels(opts?)`, a model factory for `defineSwarm({ modelFactory })`. It wraps `@openrouter/ai-sdk-provider`'s `createOpenRouter`, mapping a `provider/model` id (e.g. `anthropic/claude-3.5-sonnet`) to an AI SDK `LanguageModelV3`, so one provider reaches many model vendors. Reads `OPENROUTER_API_KEY` by default; pass `{ apiKey }` to override. Ships a `nightOwlsPlugin` manifest for CLI scaffolding; it composes with the other `provider-*` adapters, which `createModelFactory` routes between per agent.

Install

pnpm add @nightowlsdev/provider-openrouter

Key exports

  • openrouterModels
  • nightOwlsPlugin

Usage

provider-openrouter.ts
import { defineSwarm } from "@nightowlsdev/core";
import { openrouterModels } from "@nightowlsdev/provider-openrouter";

// One provider, many vendors via provider/model ids. Reads OPENROUTER_API_KEY.
const swarm = defineSwarm({ agents, modelFactory: openrouterModels() });

What it provides

A one-line model provider that reaches many vendors through one key: openrouterModels() returns a modelFactory mapping a provider/model id (anthropic/claude-3.5-sonnet, openai/gpt-4o, google/gemini-2.5-pro, meta-llama/…) to an AI SDK LanguageModelV3 via @openrouter/ai-sdk-provider. It reads OPENROUTER_API_KEY (pass { apiKey } to override). It also ships openrouterEmbeddings() (an embedder from the same adapter, dimension-pinned) and openrouterProvider() (the adapter-object form with a small headline catalog — deliberately UNPRICED). One of six interchangeable provider-* packages that compose via createModelFactory.

When to use it

  • You want breadth from one key: prototype across Claude, GPT, Gemini, Llama, Mistral, and hundreds more without opening N vendor accounts.
  • You want to switch models by editing a provider/model string in models.allow rather than swapping adapters.
  • You want an embedding model from the same adapter you already installed (openrouterEmbeddings, dimension-pinned to your vector column).

When not to

  • You bill on cost caps and need reliable per-model prices — OpenRouter's rate depends on the upstream it routes to and shifts, so the catalog ships NO prices and openrouter:* expands to nothing unless you supply rates or pass allowUnpriced.
  • You need the lowest latency or cost for one specific model — go direct (provider-groq, or a native provider) and skip the routing hop.
  • You need a runtime model listing in the picker — a live models() is deliberately not wired here (FR-045 gives that to the gateway adapter), so this is catalog-only.
  • You need air-gapped / offline inference — use provider-ollama.

Alternatives

  • provider-vercel-gatewayYou want the same one-key breadth but WITH a live model listing and per-model pricing, so cost caps actually enforce.
  • A native provider (anthropic / openai)You want one specific vendor directly — direct billing, a priced catalog, and no routing hop.
  • provider-ollamaYou want local, free, offline open models instead of a hosted aggregator.

Strengths

  • Breadth in one line: hundreds of models across every major vendor behind a single key.
  • Vendor switch is a string change — edit the provider/model id in models.allow, no code change.
  • Ships an embedder (openrouterEmbeddings) and an adapter object, so it slots into RAG and the model registry too.
  • Engine-wall clean: one dependency, zero @mastra and zero @nightowlsdev/core.

Limits & trade-offs

  • NO catalog prices, on purpose: an OpenRouter id's rate depends on which upstream it routes to and changes often, and a wrong price is worse than none (it makes cost.maxCostUsd look enforced while it is not). So openrouter:* expands to nothing unless you supply prices or pass allowUnpriced.
  • No live models() wired here — the catalog is a small headline starting point, not the full list.
  • provider/model ids contain a slash: never recover a provider by prefix-parsing a stored id — anthropic/claude-... under OpenRouter is the openrouter provider, not anthropic.
  • An extra routing hop versus going direct to a vendor.

How it works

openrouterModels(opts) calls createOpenRouter and returns a (modelId) => provider(modelId) factory, where modelId is a provider/model string; it reads OPENROUTER_API_KEY when no apiKey is passed. openrouterProvider() is the adapter-object form for createModelProviderRegistry: the same .model factory plus a small headline catalog and no models() (canList is honestly false). Because a rate depends on the routed upstream and shifts over time, the catalog omits prices entirely — you supply them via cost.prices / resolveModelAllowList({ prices }) or opt out of the price gate with allowUnpriced. openrouterEmbeddings(opts) builds provider.textEmbeddingModel(model) and, when dimensions is set, wraps it with pinEmbeddingDimensions so every embed call pins the output size to your column.

Examples

One key, many vendors

modelId is a provider/model string; the key comes from OPENROUTER_API_KEY.

provider-openrouter-example-1.ts
import { defineSwarm } from "@nightowlsdev/core";
import { openrouterModels } from "@nightowlsdev/provider-openrouter";

export default defineSwarm({
  modelFactory: openrouterModels(),                 // reads OPENROUTER_API_KEY
  models: { allow: ["anthropic/claude-3.5-sonnet"] },
  agents,
});

Enforce cost caps by supplying rates

OpenRouter ships no prices; feed them to resolveModelAllowList so openrouter:* expands and cost.maxCostUsd meters.

provider-openrouter-example-2.ts
import { createModelProviderRegistry, resolveModelAllowList } from "@nightowlsdev/core";
import { openrouterProvider } from "@nightowlsdev/provider-openrouter";

const registry = createModelProviderRegistry({ providers: [openrouterProvider()] });

const resolved = await resolveModelAllowList(registry, ["openrouter:*"], {
  prices: { "openrouter:anthropic/claude-3.5-sonnet": { inUsdPerMtok: 3, outUsdPerMtok: 15 } },
});
// defineSwarm({ models: { allow: ["openrouter:*"], resolved, registry } })

An embedder from the same adapter

openrouterEmbeddings pins the dimension to your pgvector column.

provider-openrouter-example-3.ts
import { openrouterEmbeddings } from "@nightowlsdev/provider-openrouter";

const embedder = openrouterEmbeddings({
  model: "openai/text-embedding-3-small",
  dimensions: 1536,
});

Doing the parts it doesn't support

  • Enforced cost caps on OpenRouterSupply rates via cost.prices or resolveModelAllowList({ prices }); without them openrouter:* is empty and cost.maxCostUsd cannot meter (the catalog is intentionally unpriced because routes vary).
  • A runtime model listing in the pickerNot wired for OpenRouter. Use the small headline catalog, or front models through provider-vercel-gateway, whose adapter has a genuine live models() with per-model pricing.
  • Mixing OpenRouter with native providers per agentWrap openrouterModels() and the native factories in createModelFactory with a resolve() mapping each agent slug to a { provider, modelId } route.

Related

  • coredefineSwarm / modelFactory consume the factory; resolveModelAllowList supplies the prices OpenRouter omits.
  • provider-vercel-gatewayThe other one-key-many-vendors adapter — but with a live listing and per-model pricing.
  • provider-anthropicGo direct to Claude instead of routing through OpenRouter (priced catalog, no hop).
  • model-providersThe guide to provider-qualified allow-lists and per-provider pricing.
  • cliowl install provider-openrouter scaffolds the env vars and the modelFactory config marker.