Skip to content

Commit 79ffb5d

Browse files
feat: add routing and model discovery for more API-key providers
1 parent c7b396f commit 79ffb5d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1348
-378
lines changed

.changeset/free-tier-providers.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"manifest": patch
3+
---
4+
5+
Add dynamic discovery and routing support for Cohere, Cerebras, Cloudflare Workers AI, GitHub Models, Groq, Hugging Face, LLM7.io, and Ollama Cloud.

packages/backend/src/common/constants/providers.spec.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
} from './providers';
1010

1111
describe('PROVIDER_REGISTRY', () => {
12-
it('should contain exactly 13 provider entries', () => {
13-
expect(PROVIDER_REGISTRY).toHaveLength(13);
12+
it('should contain exactly 21 provider entries', () => {
13+
expect(PROVIDER_REGISTRY).toHaveLength(21);
1414
});
1515

1616
it('every entry has all required fields', () => {
@@ -72,11 +72,11 @@ describe('PROVIDER_REGISTRY', () => {
7272
});
7373

7474
describe('PROVIDER_BY_ID', () => {
75-
it('resolves all 13 provider IDs', () => {
75+
it('resolves all 21 provider IDs', () => {
7676
for (const entry of PROVIDER_REGISTRY) {
7777
expect(PROVIDER_BY_ID.get(entry.id)).toBe(entry);
7878
}
79-
expect(PROVIDER_BY_ID.size).toBe(13);
79+
expect(PROVIDER_BY_ID.size).toBe(21);
8080
});
8181

8282
it('returns undefined for an unknown ID', () => {
@@ -118,6 +118,13 @@ describe('PROVIDER_BY_ID_OR_ALIAS', () => {
118118
expect(entry.displayName).toBe('Z.ai');
119119
});
120120

121+
it('resolves github models alias to github-models entry', () => {
122+
const entry = PROVIDER_BY_ID_OR_ALIAS.get('github models') as ProviderRegistryEntry;
123+
expect(entry).toBeDefined();
124+
expect(entry.id).toBe('github-models');
125+
expect(entry.displayName).toBe('GitHub Models');
126+
});
127+
121128
it('returns undefined for an unknown alias', () => {
122129
expect(PROVIDER_BY_ID_OR_ALIAS.get('nonexistent')).toBeUndefined();
123130
});

packages/backend/src/common/constants/providers.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,30 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
4545
requiresApiKey: true,
4646
localOnly: false,
4747
},
48+
{
49+
id: 'cerebras',
50+
displayName: 'Cerebras',
51+
aliases: [],
52+
openRouterPrefixes: [],
53+
requiresApiKey: true,
54+
localOnly: false,
55+
},
56+
{
57+
id: 'cloudflare',
58+
displayName: 'Cloudflare Workers AI',
59+
aliases: ['cloudflare workers ai'],
60+
openRouterPrefixes: [],
61+
requiresApiKey: true,
62+
localOnly: false,
63+
},
64+
{
65+
id: 'cohere',
66+
displayName: 'Cohere',
67+
aliases: [],
68+
openRouterPrefixes: ['cohere'],
69+
requiresApiKey: true,
70+
localOnly: false,
71+
},
4872
{
4973
id: 'openai',
5074
displayName: 'OpenAI',
@@ -53,6 +77,14 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
5377
requiresApiKey: true,
5478
localOnly: false,
5579
},
80+
{
81+
id: 'github-models',
82+
displayName: 'GitHub Models',
83+
aliases: ['github models', 'githubmodels'],
84+
openRouterPrefixes: [],
85+
requiresApiKey: true,
86+
localOnly: false,
87+
},
5688
{
5789
id: 'gemini',
5890
displayName: 'Google',
@@ -69,6 +101,30 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
69101
requiresApiKey: true,
70102
localOnly: false,
71103
},
104+
{
105+
id: 'groq',
106+
displayName: 'Groq',
107+
aliases: [],
108+
openRouterPrefixes: [],
109+
requiresApiKey: true,
110+
localOnly: false,
111+
},
112+
{
113+
id: 'huggingface',
114+
displayName: 'Hugging Face',
115+
aliases: ['hugging face', 'hf'],
116+
openRouterPrefixes: [],
117+
requiresApiKey: true,
118+
localOnly: false,
119+
},
120+
{
121+
id: 'llm7',
122+
displayName: 'LLM7.io',
123+
aliases: ['llm7.io'],
124+
openRouterPrefixes: [],
125+
requiresApiKey: true,
126+
localOnly: false,
127+
},
72128
{
73129
id: 'mistral',
74130
displayName: 'Mistral',
@@ -141,6 +197,14 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
141197
requiresApiKey: false,
142198
localOnly: true,
143199
},
200+
{
201+
id: 'ollama-cloud',
202+
displayName: 'Ollama Cloud',
203+
aliases: ['ollama cloud'],
204+
openRouterPrefixes: [],
205+
requiresApiKey: true,
206+
localOnly: false,
207+
},
144208
] as const;
145209

146210
/* ── Derived lookup maps (computed once at import time) ── */

packages/backend/src/model-discovery/model-discovery.service.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,19 @@ export class ModelDiscoveryService {
178178
if (!Array.isArray(cached)) continue;
179179
const providerAuthType = p.auth_type === 'subscription' ? 'subscription' : 'api_key';
180180
for (const m of cached) {
181-
const effectiveAuthType = m.authType ?? providerAuthType;
181+
const normalized = {
182+
...m,
183+
authType: m.authType ?? providerAuthType,
184+
};
185+
const effectiveAuthType = normalized.authType ?? providerAuthType;
182186
if (!seen.has(m.id)) {
183187
seen.set(m.id, models.length);
184-
models.push(m);
188+
models.push(normalized);
185189
} else if (
186190
effectiveAuthType === 'subscription' &&
187191
models[seen.get(m.id)!]?.authType !== 'subscription'
188192
) {
189-
models[seen.get(m.id)!] = m;
193+
models[seen.get(m.id)!] = normalized;
190194
}
191195
}
192196
}

0 commit comments

Comments
 (0)