Skip to content

Commit e3908c6

Browse files
authored
feat: add support for auto-router plugin (#403)
## Description Closes #392 Adds the `auto-router` plugin type to the `plugins` configuration, enabling users to configure allowed models when using `openrouter/auto`. **Before:** Using `{ id: 'auto-router' }` in the plugins array was not accepted by the TypeScript types. **After:** ```ts const model = openrouter.chat('openrouter/auto', { plugins: [ { id: 'auto-router', allowed_models: ['anthropic/*', 'openai/gpt-5.1'] }, ], }); ``` ### Changes - Added `IdAutoRouter` type literal (`'auto-router'`) in `openrouter-api-types.ts` - Added auto-router variant to the `plugins` union type in `openrouter-chat-settings.ts` with optional `allowed_models: string[]` - Added three unit tests covering: with `allowed_models`, without `allowed_models`, and combined with other plugins - Added e2e regression test (`e2e/issues/issue-392-auto-router-plugin.test.ts`) — verified against live API with `generateText` and `streamText` No runtime behavior changes — plugins are passed through directly to the API via `getArgs`. ### Cross-referenced with `openrouter-web` Schema verified against `packages/llm-interfaces/plugins/auto-router/schemas.ts` in `openrouter-web`. The `id` and `allowed_models` fields match. The `enabled?: boolean` field present in `openrouter-web` is intentionally omitted here for consistency — no other plugin type in this SDK exposes `enabled`. ### Reviewer checklist - [ ] Confirm `allowed_models` property name matches the [OpenRouter auto-router API](https://openrouter.ai/docs/guides/routing/routers/auto-router) - [ ] Decide whether `enabled?: boolean` should be added (exists in `openrouter-web` for auto-router, web, file-parser, response-healing — but is absent from all plugin types in this SDK today) - [ ] Check if the auto-router plugin has any additional properties beyond `allowed_models` that should be included ## Checklist - [x] I have run `pnpm stylecheck` and `pnpm typecheck` - [x] I have run `pnpm test` and all tests pass - [x] I have added tests for my changes (if applicable) - [ ] I have updated documentation (if applicable) — N/A, types are self-documenting with JSDoc ### Changeset - [x] I have run `pnpm changeset` to create a changeset file --- [Link to Devin run](https://app.devin.ai/sessions/59f40b83460544bbbf789dabbc3ab1d8) | Requested by: @robert-j-y
1 parent 918eb4e commit e3908c6

File tree

5 files changed

+179
-0
lines changed

5 files changed

+179
-0
lines changed

.changeset/fluffy-buses-attend.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openrouter/ai-sdk-provider': patch
3+
---
4+
5+
Add support for `auto-router` plugin to configure allowed models when using `openrouter/auto`
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Regression test for GitHub issue #392
3+
* https://github.com/OpenRouterTeam/ai-sdk-provider/issues/392
4+
*
5+
* Issue: "Add support for auto-router plugin"
6+
*
7+
* Issue thread timeline:
8+
* - Feb 4, 2026: User requests support for the `auto-router` plugin
9+
* to configure allowed models when using `openrouter/auto`.
10+
*/
11+
import { generateText, streamText } from 'ai';
12+
import { describe, expect, it, vi } from 'vitest';
13+
import { createOpenRouter } from '@/src';
14+
15+
vi.setConfig({
16+
testTimeout: 60_000,
17+
});
18+
19+
describe('Issue #392: auto-router plugin support', () => {
20+
const openrouter = createOpenRouter({
21+
apiKey: process.env.OPENROUTER_API_KEY,
22+
baseUrl: `${process.env.OPENROUTER_API_BASE}/api/v1`,
23+
});
24+
25+
it('should generate text with openrouter/auto and auto-router plugin with allowed_models', async () => {
26+
const model = openrouter('openrouter/auto', {
27+
plugins: [
28+
{
29+
id: 'auto-router',
30+
allowed_models: ['anthropic/*', 'openai/*'],
31+
},
32+
],
33+
usage: { include: true },
34+
});
35+
36+
const response = await generateText({
37+
model,
38+
prompt: 'What is 2+2? Reply with just the number.',
39+
});
40+
41+
expect(response.text).toBeDefined();
42+
expect(response.text.length).toBeGreaterThan(0);
43+
expect(response.usage.totalTokens).toBeGreaterThan(0);
44+
expect(response.finishReason).toBeDefined();
45+
});
46+
47+
it('should generate text with openrouter/auto and auto-router plugin without allowed_models', async () => {
48+
const model = openrouter('openrouter/auto', {
49+
plugins: [{ id: 'auto-router' }],
50+
usage: { include: true },
51+
});
52+
53+
const response = await generateText({
54+
model,
55+
prompt: 'What is 2+2? Reply with just the number.',
56+
});
57+
58+
expect(response.text).toBeDefined();
59+
expect(response.text.length).toBeGreaterThan(0);
60+
expect(response.usage.totalTokens).toBeGreaterThan(0);
61+
expect(response.finishReason).toBeDefined();
62+
});
63+
64+
it('should stream text with openrouter/auto and auto-router plugin with allowed_models', async () => {
65+
const model = openrouter('openrouter/auto', {
66+
plugins: [
67+
{
68+
id: 'auto-router',
69+
allowed_models: ['anthropic/*', 'openai/*'],
70+
},
71+
],
72+
usage: { include: true },
73+
});
74+
75+
const response = streamText({
76+
model,
77+
prompt: 'What is 2+2? Reply with just the number.',
78+
});
79+
80+
const result = await response.text;
81+
expect(result).toBeDefined();
82+
expect(result.length).toBeGreaterThan(0);
83+
});
84+
});

src/chat/index.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,76 @@ describe('doGenerate', () => {
818818
});
819819
});
820820

821+
it('should pass auto-router plugin with allowed_models in request payload', async () => {
822+
prepareJsonResponse({ content: 'Hello from auto-selected model' });
823+
824+
const autoModel = provider.chat('openrouter/auto', {
825+
plugins: [
826+
{
827+
id: 'auto-router',
828+
allowed_models: ['anthropic/*', 'openai/gpt-5.1'],
829+
},
830+
],
831+
});
832+
833+
await autoModel.doGenerate({
834+
prompt: TEST_PROMPT,
835+
});
836+
837+
expect(await server.calls[0]!.requestBodyJson).toStrictEqual({
838+
model: 'openrouter/auto',
839+
messages: [{ role: 'user', content: 'Hello' }],
840+
plugins: [
841+
{
842+
id: 'auto-router',
843+
allowed_models: ['anthropic/*', 'openai/gpt-5.1'],
844+
},
845+
],
846+
});
847+
});
848+
849+
it('should pass auto-router plugin without allowed_models in request payload', async () => {
850+
prepareJsonResponse({ content: 'Hello from auto-selected model' });
851+
852+
const autoModel = provider.chat('openrouter/auto', {
853+
plugins: [{ id: 'auto-router' }],
854+
});
855+
856+
await autoModel.doGenerate({
857+
prompt: TEST_PROMPT,
858+
});
859+
860+
expect(await server.calls[0]!.requestBodyJson).toStrictEqual({
861+
model: 'openrouter/auto',
862+
messages: [{ role: 'user', content: 'Hello' }],
863+
plugins: [{ id: 'auto-router' }],
864+
});
865+
});
866+
867+
it('should pass auto-router plugin combined with other plugins', async () => {
868+
prepareJsonResponse({ content: 'Hello from auto-selected model' });
869+
870+
const autoModel = provider.chat('openrouter/auto', {
871+
plugins: [
872+
{ id: 'auto-router', allowed_models: ['anthropic/*'] },
873+
{ id: 'web' },
874+
],
875+
});
876+
877+
await autoModel.doGenerate({
878+
prompt: TEST_PROMPT,
879+
});
880+
881+
expect(await server.calls[0]!.requestBodyJson).toStrictEqual({
882+
model: 'openrouter/auto',
883+
messages: [{ role: 'user', content: 'Hello' }],
884+
plugins: [
885+
{ id: 'auto-router', allowed_models: ['anthropic/*'] },
886+
{ id: 'web' },
887+
],
888+
});
889+
});
890+
821891
it('should pass images', async () => {
822892
prepareJsonResponse({
823893
content: '',

src/types/openrouter-api-types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export type IdModeration = 'moderation';
3535
*/
3636
export type IdResponseHealing = 'response-healing';
3737

38+
/**
39+
* Plugin identifier for auto-router model selection.
40+
* Configures allowed models when using the openrouter/auto model.
41+
* @see https://openrouter.ai/docs/guides/routing/routers/auto-router
42+
*/
43+
export type IdAutoRouter = 'auto-router';
44+
3845
/**
3946
* Search engine options for web search.
4047
* Open enum - accepts known values or any string for forward compatibility.

src/types/openrouter-chat-settings.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { OpenRouterSharedSettings } from '..';
22
import type {
33
DataCollection,
44
Engine,
5+
IdAutoRouter,
56
IdFileParser,
67
IdModeration,
78
IdResponseHealing,
@@ -87,6 +88,18 @@ monitor and detect abuse. Learn more.
8788
*/
8889
id: IdResponseHealing;
8990
}
91+
| {
92+
/**
93+
* Auto-router plugin - configures allowed models when using `openrouter/auto`.
94+
*
95+
* Use wildcard patterns to restrict which models the auto router can select from.
96+
* When no `allowed_models` are specified, the auto router uses all supported models.
97+
*
98+
* @see https://openrouter.ai/docs/guides/routing/routers/auto-router
99+
*/
100+
id: IdAutoRouter;
101+
allowed_models?: string[];
102+
}
90103
>;
91104

92105
/**

0 commit comments

Comments
 (0)