@neutrome/open-ai-router 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neutrome/open-ai-router",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -28,8 +28,8 @@ export type AnthropicMessagesHandlerOptions = ChatCompletionsHandlerOptions;
28
28
  export type GoogleGenAIHandlerOptions = ChatCompletionsHandlerOptions;
29
29
 
30
30
  export function listModelsHandler(runtime: RouterRuntime) {
31
- return async (c: Context) => {
32
- const modelIds = await listAvailableModels(runtime);
31
+ return (c: Context) => {
32
+ const modelIds = listAvailableModels(runtime);
33
33
  const models: OpenAiModel[] = modelIds.map((id) => ({
34
34
  id,
35
35
  object: "model",
@@ -40,60 +40,8 @@ export function listModelsHandler(runtime: RouterRuntime) {
40
40
  };
41
41
  }
42
42
 
43
- async function listAvailableModels(runtime: RouterRuntime): Promise<string[]> {
44
- const seen = new Set<string>();
45
- const models: string[] = [];
46
-
47
- for (const id of listConfiguredModels(runtime)) {
48
- addModel(id);
49
- }
50
-
51
- await Promise.all(runtime.providerOrder.map(async (namespace) => {
52
- const provider = runtime.providers.get(namespace);
53
- if (!provider || provider.catalog.length > 0) {
54
- return;
55
- }
56
-
57
- for (const model of await fetchProviderCatalog(runtime, provider.apiBaseUrl)) {
58
- if (provider.exportsMatcher(model)) {
59
- addModel(`${namespace}/${model}`);
60
- }
61
- }
62
- }));
63
-
64
- return models;
65
-
66
- function addModel(id: string): void {
67
- if (seen.has(id)) {
68
- return;
69
- }
70
- seen.add(id);
71
- models.push(id);
72
- }
73
- }
74
-
75
- async function fetchProviderCatalog(runtime: RouterRuntime, apiBaseUrl: string): Promise<string[]> {
76
- try {
77
- const response = await runtime.fetchImpl(`${apiBaseUrl}/models`, {
78
- method: "GET",
79
- headers: { accept: "application/json" },
80
- });
81
- if (!response.ok) {
82
- return [];
83
- }
84
-
85
- const raw = await response.json() as { data?: unknown };
86
- const data = raw.data;
87
- if (!Array.isArray(data)) {
88
- return [];
89
- }
90
-
91
- return data
92
- .map((model) => (model as { id?: unknown } | null | undefined)?.id)
93
- .filter((id): id is string => typeof id === "string");
94
- } catch {
95
- return [];
96
- }
43
+ function listAvailableModels(runtime: RouterRuntime): string[] {
44
+ return listConfiguredModels(runtime);
97
45
  }
98
46
 
99
47
  export function chatCompletionsHandler(
@@ -9,15 +9,14 @@ import { createApp } from "./example.ts";
9
9
  const decoder = new TextDecoder();
10
10
 
11
11
  describe("open-ai-router createApp", () => {
12
- it("lists configured provider catalog entries and executor aliases", async () => {
12
+ it("lists configured executor aliases", async () => {
13
13
  const app = createApp({
14
14
  config: {
15
15
  providers: {
16
16
  openrouter: {
17
17
  api_base_url: "https://openrouter.ai/api/v1",
18
18
  style: "chat-completions",
19
- exports: ["gpt-4o"],
20
- catalog: ["gpt-4o"],
19
+ exports: ["*"],
21
20
  },
22
21
  },
23
22
  executors: {
@@ -38,50 +37,6 @@ describe("open-ai-router createApp", () => {
38
37
  object: "list",
39
38
  data: [
40
39
  { id: "semantyka/enei-1", object: "model", created: 0, owned_by: "semantyka" },
41
- { id: "openrouter/gpt-4o", object: "model", created: 0, owned_by: "openrouter" },
42
- ],
43
- });
44
- });
45
-
46
- it("lists provider /models entries when provider catalog is not configured", async () => {
47
- const app = createApp({
48
- config: {
49
- providers: {
50
- openrouter: {
51
- api_base_url: "https://openrouter.ai/api/v1",
52
- style: "chat-completions",
53
- exports: ["*:free"],
54
- },
55
- },
56
- executors: {},
57
- },
58
- fetchImpl: vi.fn(async () =>
59
- new Response(
60
- JSON.stringify({
61
- data: [
62
- { id: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free" },
63
- { id: "openai/gpt-4.1-mini" },
64
- ],
65
- }),
66
- {
67
- headers: { "content-type": "application/json; charset=utf-8" },
68
- },
69
- )
70
- ),
71
- });
72
-
73
- const response = await app.request("/v1/models");
74
-
75
- expect(response.status).toBe(200);
76
- expect(await response.json()).toEqual({
77
- object: "list",
78
- data: [
79
- {
80
- id: "openrouter/nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
81
- object: "model",
82
- created: 0,
83
- owned_by: "openrouter",
84
- },
85
40
  ],
86
41
  });
87
42
  });
@@ -101,12 +56,17 @@ describe("open-ai-router createApp", () => {
101
56
  openrouter: {
102
57
  api_base_url: "https://openrouter.ai/api/v1",
103
58
  style: "chat-completions",
104
- exports: ["gpt-4o"],
105
- allow_anonymous: false,
106
- no_prefix: true,
59
+ exports: ["*"],
60
+ },
61
+ },
62
+ executors: {
63
+ default: {
64
+ exports: ["*"],
65
+ models: {
66
+ "gpt-4o": { provider: "openrouter", model: "gpt-4o" },
67
+ },
107
68
  },
108
69
  },
109
- executors: {},
110
70
  },
111
71
  fetchImpl: vi.fn(async (_input, init) => {
112
72
  capturedBody = JSON.parse(decoder.decode(new Uint8Array(init?.body as ArrayBuffer)));
@@ -172,12 +132,17 @@ describe("open-ai-router createApp", () => {
172
132
  openrouter: {
173
133
  api_base_url: "https://openrouter.ai/api/v1",
174
134
  style: "chat-completions",
175
- allow_anonymous: true,
176
- exports: ["gpt-4o"],
177
- no_prefix: true,
135
+ exports: ["*"],
136
+ },
137
+ },
138
+ executors: {
139
+ default: {
140
+ exports: ["*"],
141
+ models: {
142
+ "gpt-4o": { provider: "openrouter", model: "gpt-4o" },
143
+ },
178
144
  },
179
145
  },
180
- executors: {},
181
146
  },
182
147
  fetchImpl: vi.fn(async (_input, init) => {
183
148
  capturedBody = JSON.parse(decoder.decode(new Uint8Array(init?.body as ArrayBuffer)));
@@ -241,12 +206,17 @@ describe("open-ai-router createApp", () => {
241
206
  openrouter: {
242
207
  api_base_url: "https://openrouter.ai/api/v1",
243
208
  style: "chat-completions",
244
- allow_anonymous: true,
245
- exports: ["gpt-4o"],
246
- no_prefix: true,
209
+ exports: ["*"],
210
+ },
211
+ },
212
+ executors: {
213
+ default: {
214
+ exports: ["*"],
215
+ models: {
216
+ "gpt-4o": { provider: "openrouter", model: "gpt-4o" },
217
+ },
247
218
  },
248
219
  },
249
- executors: {},
250
220
  },
251
221
  fetchImpl: vi.fn(async (_input, init) => {
252
222
  capturedBody = JSON.parse(decoder.decode(new Uint8Array(init?.body as ArrayBuffer)));
@@ -9,11 +9,7 @@ export type ProviderTargetConfig = {
9
9
  api_base_url: string;
10
10
  style: ProviderApiStyle;
11
11
  exports?: string[] | null;
12
- endpoint_path?: string;
13
- allow_anonymous?: boolean;
14
- no_prefix?: boolean;
15
12
  headers?: Record<string, string>;
16
- catalog?: string[];
17
13
  };
18
14
 
19
15
  export type ExecutorRouteConfig =
@@ -19,12 +19,17 @@ describe("router execution", () => {
19
19
  openrouter: {
20
20
  api_base_url: "https://openrouter.ai/api/v1",
21
21
  style: "chat-completions",
22
- allow_anonymous: true,
23
- exports: ["gpt-4o"],
24
- no_prefix: true,
22
+ exports: ["*"],
23
+ },
24
+ },
25
+ executors: {
26
+ default: {
27
+ exports: ["*"],
28
+ models: {
29
+ "gpt-4o": { provider: "openrouter", model: "gpt-4o" },
30
+ },
25
31
  },
26
32
  },
27
- executors: {},
28
33
  },
29
34
  fetchImpl: vi.fn(async (input, init) => {
30
35
  capturedUrl = String(input);
@@ -98,12 +103,17 @@ describe("router execution", () => {
98
103
  openai: {
99
104
  api_base_url: "https://api.openai.com/v1",
100
105
  style: "responses",
101
- allow_anonymous: true,
102
- exports: ["gpt-5"],
103
- no_prefix: true,
106
+ exports: ["*"],
107
+ },
108
+ },
109
+ executors: {
110
+ default: {
111
+ exports: ["*"],
112
+ models: {
113
+ "gpt-5": { provider: "openai", model: "gpt-5" },
114
+ },
104
115
  },
105
116
  },
106
- executors: {},
107
117
  },
108
118
  fetchImpl: vi.fn(async (input, init) => {
109
119
  capturedUrl = String(input);
@@ -166,12 +176,17 @@ describe("router execution", () => {
166
176
  anthropic: {
167
177
  api_base_url: "https://api.anthropic.com",
168
178
  style: "anthropic-messages",
169
- allow_anonymous: true,
170
- exports: ["claude-*"],
171
- no_prefix: true,
179
+ exports: ["*"],
180
+ },
181
+ },
182
+ executors: {
183
+ default: {
184
+ exports: ["*"],
185
+ models: {
186
+ "claude-sonnet-4": { provider: "anthropic", model: "claude-sonnet-4" },
187
+ },
172
188
  },
173
189
  },
174
- executors: {},
175
190
  },
176
191
  fetchImpl: vi.fn(async (input, init) => {
177
192
  capturedUrl = String(input);
@@ -236,12 +251,17 @@ describe("router execution", () => {
236
251
  google: {
237
252
  api_base_url: "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
238
253
  style: "google-genai",
239
- allow_anonymous: true,
240
- exports: ["gemini-*"],
241
- no_prefix: true,
254
+ exports: ["*"],
255
+ },
256
+ },
257
+ executors: {
258
+ default: {
259
+ exports: ["*"],
260
+ models: {
261
+ "gemini-2.5-flash": { provider: "google", model: "gemini-2.5-flash" },
262
+ },
242
263
  },
243
264
  },
244
- executors: {},
245
265
  },
246
266
  fetchImpl: vi.fn(async (input, init) => {
247
267
  capturedUrl = String(input);
@@ -359,12 +379,17 @@ describe("router execution", () => {
359
379
  openrouter: {
360
380
  api_base_url: "https://openrouter.ai/api/v1",
361
381
  style: "chat-completions",
362
- allow_anonymous: true,
363
- exports: ["gpt-4o"],
364
- no_prefix: true,
382
+ exports: ["*"],
383
+ },
384
+ },
385
+ executors: {
386
+ default: {
387
+ exports: ["*"],
388
+ models: {
389
+ "gpt-4o": { provider: "openrouter", model: "gpt-4o" },
390
+ },
365
391
  },
366
392
  },
367
- executors: {},
368
393
  },
369
394
  fetchImpl: vi.fn(async () => {
370
395
  const stream = new ReadableStream<Uint8Array>({
@@ -428,12 +453,17 @@ describe("router execution", () => {
428
453
  openrouter: {
429
454
  api_base_url: "https://openrouter.ai/api/v1",
430
455
  style: "chat-completions",
431
- allow_anonymous: true,
432
- exports: ["gpt-4o"],
433
- no_prefix: true,
456
+ exports: ["*"],
457
+ },
458
+ },
459
+ executors: {
460
+ default: {
461
+ exports: ["*"],
462
+ models: {
463
+ "gpt-4o": { provider: "openrouter", model: "gpt-4o" },
464
+ },
434
465
  },
435
466
  },
436
- executors: {},
437
467
  },
438
468
  fetchImpl: vi.fn(async (_input, init) => {
439
469
  return await new Promise<Response>((_resolve, reject) => {
@@ -28,7 +28,7 @@ import type { RequestContext, TargetAuthContext } from "../auth/types.ts";
28
28
  import { cloneForwardHeaders, isSse } from "../util/headers.ts";
29
29
  import { eventDataLines, splitSseEvents } from "../util/sse.ts";
30
30
  import { resolveInvocationTarget, type ResolvedTarget } from "./resolve.ts";
31
- import type { RouterRuntime } from "./runtime.ts";
31
+ import type { ProviderRuntime, RouterRuntime } from "./runtime.ts";
32
32
 
33
33
  const encoder = new TextEncoder();
34
34
  const decoder = new TextDecoder();
@@ -114,9 +114,6 @@ export function createRouterExecutionRuntime(
114
114
  if (options.executors) {
115
115
  runtimeOptions.executors = options.executors;
116
116
  }
117
- if (options.resolveExecutor) {
118
- runtimeOptions.resolveExecutor = options.resolveExecutor;
119
- }
120
117
  if (options.transforms) {
121
118
  runtimeOptions.transforms = options.transforms;
122
119
  }
@@ -130,9 +127,41 @@ export function createRouterExecutionRuntime(
130
127
  runtimeOptions.executionIdFactory = options.executionIdFactory;
131
128
  }
132
129
 
130
+ const userResolveExecutor = options.resolveExecutor;
131
+ runtimeOptions.resolveExecutor = (name) => {
132
+ if (userResolveExecutor) {
133
+ const result = userResolveExecutor(name);
134
+ if (result) return result;
135
+ }
136
+ return resolveNamespaceExecutor(runtime, name);
137
+ };
138
+
133
139
  return createExecutionRuntime(runtimeOptions);
134
140
  }
135
141
 
142
+ function resolveNamespaceExecutor(
143
+ runtime: RouterRuntime,
144
+ name: string,
145
+ ): Executor | null {
146
+ const executor = runtime.executors.get(name);
147
+ if (!executor) return null;
148
+
149
+ return {
150
+ async execute(request, ctx) {
151
+ const model = getModel(request);
152
+ if (!model) throw new ExecutionError("resolution", "Request is missing model for namespace executor resolution", { stage: "target_resolution" });
153
+ const resolved = resolveInvocationTarget(runtime, `${name}/${model}`);
154
+ return ctx.invoke(request, { target: resolved.target });
155
+ },
156
+ async *stream(request, ctx) {
157
+ const model = getModel(request);
158
+ if (!model) throw new ExecutionError("resolution", "Request is missing model for namespace executor resolution", { stage: "target_resolution" });
159
+ const resolved = resolveInvocationTarget(runtime, `${name}/${model}`);
160
+ yield* ctx.invokeStream(request, { target: resolved.target });
161
+ },
162
+ };
163
+ }
164
+
136
165
  export async function handleChatCompletions(
137
166
  options: HandleChatCompletionsOptions,
138
167
  ): Promise<Response> {
@@ -271,19 +300,14 @@ export function createFetchProviderInvoker(
271
300
  ): ProviderInvoker {
272
301
  return {
273
302
  async execute(request, providerCtx) {
274
- const provider = getProviderOrThrow(runtime, providerCtx.target.provider, providerCtx.target.model);
303
+ const provider = getProviderOrThrow(runtime, providerCtx.target.provider);
275
304
  const timed = createUpstreamSignal(providerCtx.signal, upstreamTimeoutMs);
276
305
 
277
306
  try {
278
307
  const upstream = await fetchProvider(
279
308
  runtime,
280
309
  ctx,
281
- provider.name,
282
- provider.allowAnonymous,
283
- provider.headers,
284
- provider.apiBaseUrl,
285
- provider.endpointPath,
286
- provider.style,
310
+ provider,
287
311
  setModel(request, providerCtx.target.model),
288
312
  timed.signal,
289
313
  );
@@ -301,7 +325,7 @@ export function createFetchProviderInvoker(
301
325
  },
302
326
 
303
327
  async *stream(request, providerCtx) {
304
- const provider = getProviderOrThrow(runtime, providerCtx.target.provider, providerCtx.target.model);
328
+ const provider = getProviderOrThrow(runtime, providerCtx.target.provider);
305
329
  const timed = createUpstreamSignal(providerCtx.signal, upstreamTimeoutMs);
306
330
  let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
307
331
  let buffer = "";
@@ -310,12 +334,7 @@ export function createFetchProviderInvoker(
310
334
  const upstream = await fetchProvider(
311
335
  runtime,
312
336
  ctx,
313
- provider.name,
314
- provider.allowAnonymous,
315
- provider.headers,
316
- provider.apiBaseUrl,
317
- provider.endpointPath,
318
- provider.style,
337
+ provider,
319
338
  setModel(request, providerCtx.target.model),
320
339
  timed.signal,
321
340
  );
@@ -487,50 +506,31 @@ function timingHeaders(userTimeMs: number): Record<string, string> {
487
506
  async function fetchProvider(
488
507
  runtime: RouterRuntime,
489
508
  ctx: RequestContext,
490
- providerName: string,
491
- allowAnonymous: boolean,
492
- providerHeaders: Readonly<Record<string, string>>,
493
- apiBaseUrl: string,
494
- endpointPath: string,
495
- providerStyle: RouterRuntime["providers"] extends ReadonlyMap<string, infer T>
496
- ? T extends { style: infer S }
497
- ? S
498
- : never
499
- : never,
509
+ provider: ProviderRuntime,
500
510
  request: Program,
501
511
  signal: AbortSignal,
502
512
  ): Promise<Response> {
503
513
  const headers = cloneForwardHeaders(ctx.request.headers);
504
514
  headers.set("content-type", "application/json");
505
- for (const [key, value] of Object.entries(providerHeaders)) {
515
+ for (const [key, value] of Object.entries(provider.headers)) {
506
516
  headers.set(key, value);
507
517
  }
508
518
 
509
- let hasAuth = false;
510
519
  for (const driver of runtime.authChain) {
511
- const targetCtx: TargetAuthContext = { ...ctx, providerName };
520
+ const targetCtx: TargetAuthContext = { ...ctx, providerName: provider.name };
512
521
  const auth = await driver.collectTarget(targetCtx);
513
522
  if (!auth) {
514
523
  continue;
515
524
  }
516
525
  new Headers(auth.headers).forEach((value, key) => headers.set(key, value));
517
- hasAuth = true;
518
526
  break;
519
527
  }
520
528
 
521
- if (!hasAuth && !allowAnonymous) {
522
- throw new RouterError(
523
- `No auth available for provider ${providerName}`,
524
- 401,
525
- "provider_auth_missing",
526
- );
527
- }
528
-
529
- let body = emitProviderRequest(providerStyle, request);
530
- if (providerStyle === "chat-completions" && isStreaming(request)) {
529
+ let body = emitProviderRequest(provider.style, request);
530
+ if (provider.style === "chat-completions" && isStreaming(request)) {
531
531
  body = injectStreamUsage(body);
532
532
  }
533
- return runtime.fetchImpl(`${apiBaseUrl}${endpointPath}`, {
533
+ return runtime.fetchImpl(`${provider.apiBaseUrl}${provider.endpointPath}`, {
534
534
  method: "POST",
535
535
  headers,
536
536
  body: toBody(body),
@@ -538,32 +538,23 @@ async function fetchProvider(
538
538
  });
539
539
  }
540
540
 
541
- function getProviderOrThrow(runtime: RouterRuntime, providerName: string | undefined, model?: string) {
542
- if (providerName) {
543
- const provider = runtime.providers.get(providerName);
544
- if (!provider) {
545
- throw new RouterError(
546
- `Provider ${providerName} is not configured`,
547
- 500,
548
- "provider_not_configured",
549
- );
550
- }
551
- return provider;
541
+ function getProviderOrThrow(runtime: RouterRuntime, providerName: string | undefined) {
542
+ if (!providerName) {
543
+ throw new RouterError(
544
+ "No provider specified on execution target",
545
+ 500,
546
+ "provider_not_configured",
547
+ );
552
548
  }
553
-
554
- for (const name of runtime.providerOrder) {
555
- const provider = runtime.providers.get(name)!;
556
- if (!provider.noPrefix) continue;
557
- if (provider.exportsMatcher(model ?? "")) {
558
- return provider;
559
- }
549
+ const provider = runtime.providers.get(providerName);
550
+ if (!provider) {
551
+ throw new RouterError(
552
+ `Provider ${providerName} is not configured`,
553
+ 500,
554
+ "provider_not_configured",
555
+ );
560
556
  }
561
-
562
- throw new RouterError(
563
- `No provider found for model \`${model ?? "(unknown)"}\``,
564
- 404,
565
- "provider_not_found",
566
- );
557
+ return provider;
567
558
  }
568
559
 
569
560
  async function toProviderError(
@@ -10,8 +10,7 @@ describe("router resolution", () => {
10
10
  openrouter: {
11
11
  api_base_url: "https://openrouter.ai/api/v1",
12
12
  style: "chat-completions",
13
- exports: ["gemma-*"],
14
- catalog: ["gemma-4-31b-it"],
13
+ exports: ["*"],
15
14
  },
16
15
  },
17
16
  executors: {
@@ -45,7 +44,6 @@ describe("router resolution", () => {
45
44
  api_base_url: "https://openrouter.ai/api/v1",
46
45
  style: "chat-completions",
47
46
  exports: ["google/*"],
48
- catalog: ["google/gemma-4-31b-it"],
49
47
  },
50
48
  },
51
49
  executors: {},
@@ -71,7 +69,6 @@ describe("router resolution", () => {
71
69
  api_base_url: "https://openrouter.ai/api/v1",
72
70
  style: "chat-completions",
73
71
  exports: ["openai/gpt-4.1-mini"],
74
- catalog: ["openai/gpt-4.1-mini"],
75
72
  },
76
73
  },
77
74
  executors: {
@@ -96,15 +93,14 @@ describe("router resolution", () => {
96
93
  });
97
94
  });
98
95
 
99
- it("lists configured executor and provider models as qualified IDs", () => {
96
+ it("lists configured executor models as qualified IDs", () => {
100
97
  const runtime = createRouterRuntime({
101
98
  config: {
102
99
  providers: {
103
100
  openrouter: {
104
101
  api_base_url: "https://openrouter.ai/api/v1",
105
102
  style: "chat-completions",
106
- exports: ["google/*", "openai/*"],
107
- catalog: ["google/gemma-4-31b-it", "openai/gpt-4o", "anthropic/claude-sonnet-4"],
103
+ exports: ["*"],
108
104
  },
109
105
  },
110
106
  executors: {
@@ -122,34 +118,38 @@ describe("router resolution", () => {
122
118
  expect(listConfiguredModels(runtime)).toEqual([
123
119
  "enei/enei-1",
124
120
  "enei/enei-1-pro",
125
- "openrouter/google/gemma-4-31b-it",
126
- "openrouter/openai/gpt-4o",
127
121
  ]);
128
122
  });
129
123
 
130
- it("returns multiple candidates for unqualified provider matches", () => {
124
+ it("resolves unqualified model through executor routes", () => {
131
125
  const runtime = createRouterRuntime({
132
126
  config: {
133
127
  providers: {
134
128
  openrouter: {
135
129
  api_base_url: "https://openrouter.ai/api/v1",
136
130
  style: "chat-completions",
137
- exports: ["gpt-4o"],
138
- no_prefix: true,
131
+ exports: ["*"],
139
132
  },
140
- backup: {
141
- api_base_url: "https://backup.example/v1",
142
- style: "chat-completions",
143
- exports: ["gpt-4o"],
144
- no_prefix: true,
133
+ },
134
+ executors: {
135
+ default: {
136
+ exports: ["*"],
137
+ models: {
138
+ "gpt-4o": { provider: "openrouter", model: "openai/gpt-4o" },
139
+ },
145
140
  },
146
141
  },
147
- executors: {},
148
142
  },
149
143
  });
150
144
 
151
145
  const resolved = resolveInvocationTargets(runtime, "gpt-4o");
152
- expect(resolved).toHaveLength(2);
153
- expect(resolved.map((item) => item.namespace)).toEqual(["openrouter", "backup"]);
146
+ expect(resolved).toHaveLength(1);
147
+ expect(resolved[0]!.namespace).toBe("default");
148
+ expect(resolved[0]!.target).toEqual({
149
+ kind: "provider",
150
+ provider: "openrouter",
151
+ model: "openai/gpt-4o",
152
+ transforms: [],
153
+ });
154
154
  });
155
155
  });
@@ -48,31 +48,11 @@ export function resolveInvocationTargets(
48
48
  }
49
49
  }
50
50
 
51
- for (const namespace of runtime.providerOrder) {
52
- const provider = runtime.providers.get(namespace)!;
53
- if (!provider.noPrefix) continue;
54
- if (provider.exportsMatcher(parsed.baseModel)) {
55
- matches.push({
56
- requestedModel,
57
- namespace,
58
- source: "provider",
59
- suffixes: parsed.suffixes,
60
- target: {
61
- kind: "provider",
62
- provider: namespace,
63
- model: parsed.baseModel,
64
- transforms: parsed.suffixes.map((suffix) => suffix.raw),
65
- },
66
- });
67
- }
68
- }
69
-
70
51
  return matches;
71
52
  }
72
53
 
73
54
  export function listConfiguredModels(runtime: RouterRuntime): string[] {
74
55
  const models: string[] = [];
75
- const seen = new Set<string>();
76
56
 
77
57
  for (const namespace of runtime.executorOrder) {
78
58
  const executor = runtime.executors.get(namespace)!;
@@ -83,19 +63,6 @@ export function listConfiguredModels(runtime: RouterRuntime): string[] {
83
63
  }
84
64
  }
85
65
 
86
- for (const namespace of runtime.providerOrder) {
87
- const provider = runtime.providers.get(namespace)!;
88
- for (const model of provider.catalog) {
89
- if (provider.exportsMatcher(model)) {
90
- models.push(`${namespace}/${model}`);
91
- if (provider.noPrefix && !seen.has(model)) {
92
- seen.add(model);
93
- models.push(model);
94
- }
95
- }
96
- }
97
- }
98
-
99
66
  return models;
100
67
  }
101
68
 
@@ -14,11 +14,8 @@ export type ProviderRuntime = {
14
14
  style: ProviderTargetConfig["style"];
15
15
  apiBaseUrl: string;
16
16
  endpointPath: string;
17
- allowAnonymous: boolean;
18
- noPrefix: boolean;
19
17
  headers: Readonly<Record<string, string>>;
20
18
  exportsMatcher: (model: string) => boolean;
21
- catalog: readonly string[];
22
19
  };
23
20
 
24
21
  export type ExecutorRouteRuntime = {
@@ -84,12 +81,9 @@ function buildProviderRuntime(name: string, config: ProviderTargetConfig): Provi
84
81
  name,
85
82
  style: config.style,
86
83
  apiBaseUrl: config.api_base_url,
87
- endpointPath: config.endpoint_path ?? defaultEndpointPath(config.style),
88
- allowAnonymous: config.allow_anonymous ?? false,
89
- noPrefix: config.no_prefix ?? false,
84
+ endpointPath: defaultEndpointPath(config.style),
90
85
  headers: config.headers ?? {},
91
86
  exportsMatcher: createExportsMatcher(config.exports),
92
- catalog: config.catalog ?? [],
93
87
  };
94
88
  }
95
89
 
package/src/worker.ts CHANGED
@@ -13,7 +13,6 @@ const config: RouterConfig = {
13
13
  openrouter: {
14
14
  api_base_url: "https://openrouter.ai/api/v1",
15
15
  style: "chat-completions",
16
- allow_anonymous: false,
17
16
  exports: ["*:free"],
18
17
  },
19
18
  },