neoagent 2.4.1-beta.38 → 2.4.1-beta.40
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/.env.example +11 -0
- package/docs/configuration.md +2 -0
- package/flutter_app/lib/main_model_picker.dart +55 -1
- package/flutter_app/lib/main_models.dart +4 -0
- package/flutter_app/lib/main_settings.dart +1 -318
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +41021 -41183
- package/server/services/ai/models.js +141 -97
- package/server/services/ai/providers/anthropic.js +15 -4
- package/server/services/ai/providers/google.js +34 -7
- package/server/services/ai/providers/grok.js +14 -1
- package/server/services/ai/providers/nvidia.js +12 -0
- package/server/services/ai/providers/ollama.js +19 -6
- package/server/services/ai/providers/openai.js +18 -4
- package/server/services/ai/providers/openrouter.js +139 -0
- package/server/services/ai/settings.js +10 -0
- package/server/services/ai/systemPrompt.js +36 -20
|
@@ -8,6 +8,7 @@ const { OpenAICodexProvider } = require('./providers/openaiCodex');
|
|
|
8
8
|
const { ClaudeCodeProvider } = require('./providers/claudeCode');
|
|
9
9
|
const { GrokOAuthProvider } = require('./providers/grokOauth');
|
|
10
10
|
const { NvidiaProvider } = require('./providers/nvidia');
|
|
11
|
+
const { OpenRouterProvider } = require('./providers/openrouter');
|
|
11
12
|
const {
|
|
12
13
|
AI_PROVIDER_DEFINITIONS,
|
|
13
14
|
getProviderConfigs,
|
|
@@ -15,24 +16,21 @@ const {
|
|
|
15
16
|
} = require('./settings');
|
|
16
17
|
|
|
17
18
|
const STATIC_MODELS = [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
label: 'Grok 4.1 (Personality / Default)',
|
|
21
|
-
provider: 'grok',
|
|
22
|
-
purpose: 'general'
|
|
23
|
-
},
|
|
19
|
+
// — xAI OAuth — fallback entries shown when grok-oauth token is invalid/exhausted.
|
|
20
|
+
// When the token is valid, DYNAMIC_PROVIDERS will replace these with the live list.
|
|
24
21
|
{
|
|
25
22
|
id: 'grok-4',
|
|
26
|
-
label: 'Grok 4 (xAI OAuth
|
|
23
|
+
label: 'Grok 4 (xAI OAuth)',
|
|
27
24
|
provider: 'grok-oauth',
|
|
28
|
-
purpose: 'general'
|
|
25
|
+
purpose: 'general',
|
|
29
26
|
},
|
|
30
27
|
{
|
|
31
28
|
id: 'grok-4-mini',
|
|
32
|
-
label: 'Grok 4 Mini (xAI OAuth
|
|
29
|
+
label: 'Grok 4 Mini (xAI OAuth)',
|
|
33
30
|
provider: 'grok-oauth',
|
|
34
|
-
purpose: '
|
|
31
|
+
purpose: 'fast',
|
|
35
32
|
},
|
|
33
|
+
// — GitHub Copilot (subscription; no public /models endpoint) ————————
|
|
36
34
|
{
|
|
37
35
|
id: 'gpt-5.3',
|
|
38
36
|
label: 'GPT-5.3 (Copilot Default)',
|
|
@@ -45,6 +43,7 @@ const STATIC_MODELS = [
|
|
|
45
43
|
provider: 'github-copilot',
|
|
46
44
|
purpose: 'coding'
|
|
47
45
|
},
|
|
46
|
+
// — OpenAI Codex ——————————————————————————————————————————————————————
|
|
48
47
|
{
|
|
49
48
|
id: 'gpt-5.5',
|
|
50
49
|
label: 'GPT-5.5 (Codex Default)',
|
|
@@ -63,80 +62,13 @@ const STATIC_MODELS = [
|
|
|
63
62
|
provider: 'openai-codex',
|
|
64
63
|
purpose: 'general'
|
|
65
64
|
},
|
|
66
|
-
// —
|
|
67
|
-
{
|
|
68
|
-
id: 'nvidia/nemotron-3-super-120b-a12b',
|
|
69
|
-
label: 'Nemotron Super 120B (NVIDIA / Free)',
|
|
70
|
-
provider: 'nvidia',
|
|
71
|
-
purpose: 'general'
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: 'moonshotai/kimi-k2.5',
|
|
75
|
-
label: 'Kimi K2.5 (NVIDIA NIM / Free)',
|
|
76
|
-
provider: 'nvidia',
|
|
77
|
-
purpose: 'coding'
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
id: 'minimaxai/minimax-m2.5',
|
|
81
|
-
label: 'MiniMax M2.5 (NVIDIA NIM / Free)',
|
|
82
|
-
provider: 'nvidia',
|
|
83
|
-
purpose: 'planning'
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
id: 'z-ai/glm5',
|
|
87
|
-
label: 'GLM 5 (NVIDIA NIM / Free)',
|
|
88
|
-
provider: 'nvidia',
|
|
89
|
-
purpose: 'fast'
|
|
90
|
-
},
|
|
91
|
-
// — NVIDIA NIM — notable models ————————————————————————————————————————
|
|
92
|
-
{
|
|
93
|
-
id: 'meta/llama-4-maverick-17b-128e-instruct',
|
|
94
|
-
label: 'Llama 4 Maverick 17B (NVIDIA NIM)',
|
|
95
|
-
provider: 'nvidia',
|
|
96
|
-
purpose: 'general'
|
|
97
|
-
},
|
|
65
|
+
// — Claude Code ————————————————————————————————————————————————————————
|
|
98
66
|
{
|
|
99
|
-
id: '
|
|
100
|
-
label: '
|
|
101
|
-
provider: '
|
|
102
|
-
purpose: 'planning'
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: 'deepseek-ai/deepseek-r1-0528',
|
|
106
|
-
label: 'DeepSeek R1 0528 (NVIDIA NIM)',
|
|
107
|
-
provider: 'nvidia',
|
|
67
|
+
id: 'claude-opus-4-8',
|
|
68
|
+
label: 'Claude Opus 4.8 (Claude Code / Flagship)',
|
|
69
|
+
provider: 'claude-code',
|
|
108
70
|
purpose: 'general'
|
|
109
71
|
},
|
|
110
|
-
{
|
|
111
|
-
id: 'qwen/qwq-32b',
|
|
112
|
-
label: 'QwQ 32B (NVIDIA NIM)',
|
|
113
|
-
provider: 'nvidia',
|
|
114
|
-
purpose: 'planning'
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
id: 'gpt-5-nano',
|
|
118
|
-
label: 'GPT-5 Nano (Fast / Subagents)',
|
|
119
|
-
provider: 'openai',
|
|
120
|
-
purpose: 'fast'
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
id: 'gpt-5-mini',
|
|
124
|
-
label: 'GPT-5 Mini (Planning / Complex)',
|
|
125
|
-
provider: 'openai',
|
|
126
|
-
purpose: 'planning'
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
id: 'claude-sonnet-4-20250514',
|
|
130
|
-
label: 'Claude Sonnet 4 (Analysis / Writing)',
|
|
131
|
-
provider: 'anthropic',
|
|
132
|
-
purpose: 'planning'
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
id: 'claude-3-5-haiku-20241022',
|
|
136
|
-
label: 'Claude 3.5 Haiku (Fast)',
|
|
137
|
-
provider: 'anthropic',
|
|
138
|
-
purpose: 'fast'
|
|
139
|
-
},
|
|
140
72
|
{
|
|
141
73
|
id: 'claude-opus-4-7',
|
|
142
74
|
label: 'Claude Opus 4.7 (Claude Code / Default)',
|
|
@@ -155,35 +87,25 @@ const STATIC_MODELS = [
|
|
|
155
87
|
provider: 'claude-code',
|
|
156
88
|
purpose: 'fast'
|
|
157
89
|
},
|
|
158
|
-
|
|
159
|
-
id: 'gemini-3.1-flash-lite-preview',
|
|
160
|
-
label: 'Gemini 3.1 Flash Lite (Preview)',
|
|
161
|
-
provider: 'google',
|
|
162
|
-
purpose: 'general'
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
id: 'gemini-3.1-pro',
|
|
166
|
-
label: 'Gemini 3.1 Pro (High)',
|
|
167
|
-
provider: 'google',
|
|
168
|
-
purpose: 'general'
|
|
169
|
-
},
|
|
90
|
+
// — MiniMax ————————————————————————————————————————————————————————————
|
|
170
91
|
{
|
|
171
92
|
id: 'MiniMax-M2.7',
|
|
172
93
|
label: 'MiniMax M2.7 (Coding Plan)',
|
|
173
94
|
provider: 'minimax',
|
|
174
95
|
purpose: 'coding'
|
|
175
96
|
},
|
|
97
|
+
// — Ollama — default suggestions (full list loaded dynamically) ————————
|
|
176
98
|
{
|
|
177
99
|
id: 'qwen3.5:4b',
|
|
178
100
|
label: 'Qwen 3.5 4B (Local / Ollama)',
|
|
179
101
|
provider: 'ollama',
|
|
180
|
-
purpose: 'general'
|
|
102
|
+
purpose: 'general',
|
|
181
103
|
},
|
|
182
104
|
{
|
|
183
105
|
id: 'gemma4:12b',
|
|
184
106
|
label: 'Gemma 4 12B (Local / Ollama)',
|
|
185
107
|
provider: 'ollama',
|
|
186
|
-
purpose: 'general'
|
|
108
|
+
purpose: 'general',
|
|
187
109
|
}
|
|
188
110
|
];
|
|
189
111
|
|
|
@@ -205,11 +127,107 @@ const PROVIDER_FACTORIES = Object.freeze({
|
|
|
205
127
|
'claude-code': { Provider: ClaudeCodeProvider, apiKey: true, baseUrl: false },
|
|
206
128
|
'grok-oauth': { Provider: GrokOAuthProvider, apiKey: true, baseUrl: false },
|
|
207
129
|
nvidia: { Provider: NvidiaProvider, apiKey: true, baseUrl: true },
|
|
130
|
+
openrouter: { Provider: OpenRouterProvider, apiKey: true, baseUrl: true },
|
|
208
131
|
});
|
|
209
132
|
|
|
210
133
|
const dynamicModelsByBaseUrl = new Map();
|
|
211
134
|
const REFRESH_INTERVAL = 30000; // 30 seconds
|
|
212
135
|
|
|
136
|
+
// Unified dynamic model cache for all API-backed providers.
|
|
137
|
+
// Keyed by `${providerId}:${apiKey.slice(0,8)}` to handle per-user keys.
|
|
138
|
+
const providerModelCache = new Map();
|
|
139
|
+
const DYNAMIC_REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|
140
|
+
|
|
141
|
+
// Populated from OpenRouter's /models response; used to price-classify models
|
|
142
|
+
// from all providers. Keyed by both the full OpenRouter ID ("openai/gpt-5-mini")
|
|
143
|
+
// and the bare model ID ("gpt-5-mini") for cross-provider lookup.
|
|
144
|
+
const openrouterPricingCache = new Map();
|
|
145
|
+
|
|
146
|
+
// Providers whose full model list is fetched from their API at runtime.
|
|
147
|
+
// grok-oauth inherits listModels() from GrokProvider and uses the same xAI endpoint.
|
|
148
|
+
const DYNAMIC_PROVIDERS = ['openai', 'anthropic', 'google', 'nvidia', 'grok', 'grok-oauth', 'openrouter'];
|
|
149
|
+
|
|
150
|
+
function inferModelPurpose(id) {
|
|
151
|
+
const s = id.toLowerCase();
|
|
152
|
+
if (/flash|nano|lite|tiny|haiku|scout|mini(?!max)|small/.test(s)) return 'fast';
|
|
153
|
+
if (/r1|qwq|o[0-9]|reasoning|thinking/.test(s)) return 'planning';
|
|
154
|
+
if (/code|coder|starcoder|devstral|codex|codegemma/.test(s)) return 'coding';
|
|
155
|
+
return 'general';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Pricing tiers: free=$0 cheap<$0.50/1M medium=$0.50–$5/1M expensive>$5/1M
|
|
159
|
+
// Uses live prices from openrouterPricingCache; returns null when unknown.
|
|
160
|
+
function classifyPriceTier(modelId) {
|
|
161
|
+
const costPerM = openrouterPricingCache.get(modelId);
|
|
162
|
+
if (costPerM === undefined) return null;
|
|
163
|
+
if (costPerM === 0) return 'free';
|
|
164
|
+
if (costPerM < 0.5) return 'cheap';
|
|
165
|
+
if (costPerM < 5) return 'medium';
|
|
166
|
+
return 'expensive';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Per-provider functions that turn a raw model object from listModels() into a display label.
|
|
170
|
+
const PROVIDER_LABEL_FN = {
|
|
171
|
+
openai: (m) => `${m.id} (OpenAI)`,
|
|
172
|
+
anthropic: (m) => `${m.name || m.id} (Anthropic)`,
|
|
173
|
+
google: (m) => `${m.name || m.id} (Google)`,
|
|
174
|
+
nvidia: (m) => `${m.id} (NVIDIA NIM)`,
|
|
175
|
+
grok: (m) => `${m.id} (xAI)`,
|
|
176
|
+
openrouter:(m) => `${m.name || m.id} (OpenRouter)`,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
async function refreshProviderModelList(providerId, apiKey, baseUrl) {
|
|
180
|
+
const cacheKey = `${providerId}:${(apiKey || '').slice(0, 8)}`;
|
|
181
|
+
const existing = providerModelCache.get(cacheKey);
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
|
|
184
|
+
if (existing && now - existing.lastRefresh <= DYNAMIC_REFRESH_INTERVAL) {
|
|
185
|
+
return existing.models;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const factory = PROVIDER_FACTORIES[providerId];
|
|
190
|
+
const config = {};
|
|
191
|
+
if (factory.apiKey) config.apiKey = apiKey;
|
|
192
|
+
if (factory.baseUrl) config.baseUrl = baseUrl;
|
|
193
|
+
const provider = new factory.Provider(config);
|
|
194
|
+
|
|
195
|
+
const raw = await provider.listModels();
|
|
196
|
+
|
|
197
|
+
// OpenRouter returns live pricing — populate the shared cache so all
|
|
198
|
+
// other providers can resolve their price tier without a lookup table.
|
|
199
|
+
if (providerId === 'openrouter') {
|
|
200
|
+
for (const m of raw) {
|
|
201
|
+
if (m.pricing?.prompt == null) continue;
|
|
202
|
+
const inputPerM = parseFloat(m.pricing.prompt) * 1_000_000;
|
|
203
|
+
openrouterPricingCache.set(m.id, inputPerM);
|
|
204
|
+
// Also index by the bare model ID (everything after the first "/")
|
|
205
|
+
// so that e.g. "gpt-5-mini" resolves from "openai/gpt-5-mini".
|
|
206
|
+
if (m.id.includes('/')) {
|
|
207
|
+
const bareId = m.id.slice(m.id.indexOf('/') + 1);
|
|
208
|
+
if (!openrouterPricingCache.has(bareId)) {
|
|
209
|
+
openrouterPricingCache.set(bareId, inputPerM);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const labelFn = PROVIDER_LABEL_FN[providerId] || ((m) => m.id);
|
|
216
|
+
const models = raw.map((m) => ({
|
|
217
|
+
id: m.id,
|
|
218
|
+
label: labelFn(m),
|
|
219
|
+
provider: providerId,
|
|
220
|
+
purpose: inferModelPurpose(m.id),
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
providerModelCache.set(cacheKey, { models, lastRefresh: now });
|
|
224
|
+
return models;
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.warn(`[Models] Failed to refresh ${providerId} models:`, err.message);
|
|
227
|
+
return existing?.models || [];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
213
231
|
async function probeOllama(baseUrl, timeoutMs = 1500) {
|
|
214
232
|
const controller = new AbortController();
|
|
215
233
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -373,8 +391,9 @@ async function getSupportedModels(userId, agentId = null) {
|
|
|
373
391
|
|
|
374
392
|
const all = [...STATIC_MODELS];
|
|
375
393
|
const staticIds = new Set(STATIC_MODELS.map((model) => model.id));
|
|
376
|
-
const ollama = providerById.get('ollama');
|
|
377
394
|
|
|
395
|
+
// Ollama: dynamic list from local server
|
|
396
|
+
const ollama = providerById.get('ollama');
|
|
378
397
|
if (ollama?.enabled) {
|
|
379
398
|
const dynamicModels = await refreshDynamicModels(ollama.baseUrl);
|
|
380
399
|
for (const model of dynamicModels) {
|
|
@@ -384,10 +403,35 @@ async function getSupportedModels(userId, agentId = null) {
|
|
|
384
403
|
}
|
|
385
404
|
}
|
|
386
405
|
|
|
406
|
+
// API-backed providers: fetch model lists in parallel
|
|
407
|
+
const dynamicFetches = DYNAMIC_PROVIDERS
|
|
408
|
+
.filter((id) => providerById.get(id)?.available)
|
|
409
|
+
.map(async (id) => {
|
|
410
|
+
const runtime = getProviderRuntimeConfig(userId, id, agentId);
|
|
411
|
+
return refreshProviderModelList(id, runtime.apiKey, runtime.baseUrl);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const dynamicResults = await Promise.allSettled(dynamicFetches);
|
|
415
|
+
for (const result of dynamicResults) {
|
|
416
|
+
if (result.status === 'fulfilled') {
|
|
417
|
+
for (const model of result.value) {
|
|
418
|
+
if (!staticIds.has(model.id)) {
|
|
419
|
+
all.push(model);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
387
425
|
return all.map((model) => {
|
|
388
426
|
const provider = providerById.get(model.provider);
|
|
427
|
+
// Ollama models are always local/free; all others look up the OpenRouter
|
|
428
|
+
// pricing cache (populated above by Promise.allSettled).
|
|
429
|
+
const priceTier = model.provider === 'ollama'
|
|
430
|
+
? 'free'
|
|
431
|
+
: (model.priceTier ?? classifyPriceTier(model.id));
|
|
389
432
|
return {
|
|
390
433
|
...model,
|
|
434
|
+
priceTier,
|
|
391
435
|
available: provider?.available !== false,
|
|
392
436
|
providerStatus: provider?.status || 'unknown',
|
|
393
437
|
providerStatusLabel: provider?.statusLabel || 'Unknown'
|
|
@@ -411,7 +455,7 @@ async function refreshDynamicModels(baseUrl) {
|
|
|
411
455
|
id: name,
|
|
412
456
|
label: `${name} (Ollama / Local)`,
|
|
413
457
|
provider: 'ollama',
|
|
414
|
-
purpose: 'general'
|
|
458
|
+
purpose: 'general',
|
|
415
459
|
}));
|
|
416
460
|
|
|
417
461
|
dynamicModelsByBaseUrl.set(cacheKey, {
|
|
@@ -6,18 +6,24 @@ class AnthropicProvider extends BaseProvider {
|
|
|
6
6
|
super(config);
|
|
7
7
|
this.name = 'anthropic';
|
|
8
8
|
this.models = [
|
|
9
|
+
'claude-opus-4-8',
|
|
10
|
+
'claude-opus-4-7',
|
|
11
|
+
'claude-sonnet-4-6',
|
|
9
12
|
'claude-sonnet-4-20250514',
|
|
10
|
-
'claude-
|
|
13
|
+
'claude-haiku-4-5-20251001',
|
|
11
14
|
'claude-3-5-haiku-20241022',
|
|
15
|
+
'claude-3-5-sonnet-20241022',
|
|
12
16
|
'claude-3-opus-20240229',
|
|
13
|
-
'MiniMax-M2.7'
|
|
14
17
|
];
|
|
15
18
|
this.contextWindows = {
|
|
19
|
+
'claude-opus-4-8': 1000000,
|
|
20
|
+
'claude-opus-4-7': 200000,
|
|
21
|
+
'claude-sonnet-4-6': 200000,
|
|
16
22
|
'claude-sonnet-4-20250514': 200000,
|
|
17
|
-
'claude-
|
|
23
|
+
'claude-haiku-4-5-20251001': 200000,
|
|
18
24
|
'claude-3-5-haiku-20241022': 200000,
|
|
25
|
+
'claude-3-5-sonnet-20241022': 200000,
|
|
19
26
|
'claude-3-opus-20240229': 200000,
|
|
20
|
-
'MiniMax-M2.7': 204800
|
|
21
27
|
};
|
|
22
28
|
this.client = new Anthropic({
|
|
23
29
|
apiKey: config.apiKey || process.env.ANTHROPIC_API_KEY,
|
|
@@ -25,6 +31,11 @@ class AnthropicProvider extends BaseProvider {
|
|
|
25
31
|
});
|
|
26
32
|
}
|
|
27
33
|
|
|
34
|
+
async listModels() {
|
|
35
|
+
const res = await this.client.models.list({ limit: 100 });
|
|
36
|
+
return (res.data || []).map((m) => ({ id: m.id, name: m.display_name || m.id }));
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
getContextWindow(model) {
|
|
29
40
|
return this.contextWindows[model] || 200000;
|
|
30
41
|
}
|
|
@@ -6,22 +6,49 @@ class GoogleProvider extends BaseProvider {
|
|
|
6
6
|
super(config);
|
|
7
7
|
this.name = 'google';
|
|
8
8
|
this.models = [
|
|
9
|
+
'gemini-3.5-pro',
|
|
10
|
+
'gemini-3.5-flash',
|
|
11
|
+
'gemini-3.1-pro',
|
|
12
|
+
'gemini-3.1-flash-lite-preview',
|
|
13
|
+
'gemini-2.5-pro',
|
|
14
|
+
'gemini-2.5-flash',
|
|
9
15
|
'gemini-2.0-flash',
|
|
10
|
-
'gemini-2.0-pro',
|
|
11
16
|
'gemini-1.5-pro',
|
|
12
17
|
'gemini-1.5-flash',
|
|
13
|
-
'gemini-3.1-flash-lite-preview',
|
|
14
|
-
'gemini-3.1-pro'
|
|
15
18
|
];
|
|
16
19
|
this.contextWindows = {
|
|
20
|
+
'gemini-3.5-pro': 2097152,
|
|
21
|
+
'gemini-3.5-flash': 1048576,
|
|
22
|
+
'gemini-3.1-pro': 2097152,
|
|
23
|
+
'gemini-3.1-flash-lite-preview': 1048576,
|
|
24
|
+
'gemini-2.5-pro': 1048576,
|
|
25
|
+
'gemini-2.5-flash': 1048576,
|
|
17
26
|
'gemini-2.0-flash': 1048576,
|
|
18
|
-
'gemini-2.0-pro': 2097152,
|
|
19
27
|
'gemini-1.5-pro': 2097152,
|
|
20
28
|
'gemini-1.5-flash': 1048576,
|
|
21
|
-
'gemini-3.1-flash-lite-preview': 1048576,
|
|
22
|
-
'gemini-3.1-pro': 2097152
|
|
23
29
|
};
|
|
24
|
-
this.
|
|
30
|
+
this.apiKey = config.apiKey || process.env.GOOGLE_AI_KEY;
|
|
31
|
+
this.genAI = new GoogleGenerativeAI(this.apiKey);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async listModels() {
|
|
35
|
+
const DROP = /tts|lyria|robotics|deep-research|antigravity|computer-use|-image(?!.*it)/i;
|
|
36
|
+
const res = await fetch(
|
|
37
|
+
`https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}&pageSize=200`
|
|
38
|
+
);
|
|
39
|
+
if (!res.ok) throw new Error(`Google models API returned ${res.status}`);
|
|
40
|
+
const { models = [] } = await res.json();
|
|
41
|
+
return models
|
|
42
|
+
.filter((m) => {
|
|
43
|
+
const id = m.name.replace('models/', '');
|
|
44
|
+
return (m.supportedGenerationMethods || []).includes('generateContent')
|
|
45
|
+
&& !DROP.test(id);
|
|
46
|
+
})
|
|
47
|
+
.map((m) => {
|
|
48
|
+
const id = m.name.replace('models/', '');
|
|
49
|
+
this.contextWindows[id] = m.inputTokenLimit || 1048576;
|
|
50
|
+
return { id, name: m.displayName || id };
|
|
51
|
+
});
|
|
25
52
|
}
|
|
26
53
|
|
|
27
54
|
getContextWindow(model) {
|
|
@@ -11,8 +11,21 @@ class GrokProvider extends OpenAICompatibleProvider {
|
|
|
11
11
|
});
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
async listModels() {
|
|
15
|
+
try {
|
|
16
|
+
const res = await this.client.models.list();
|
|
17
|
+
const DROP = /imagine|diffus|embed|-tts/i;
|
|
18
|
+
return res.data
|
|
19
|
+
.filter((m) => !DROP.test(m.id))
|
|
20
|
+
.map((m) => ({ id: m.id, name: m.id }));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
throw new Error(`Failed to list Grok models: ${err.message || String(err)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
14
26
|
getContextWindow(model) {
|
|
15
|
-
|
|
27
|
+
if (model === 'grok-4.3') return 1000000;
|
|
28
|
+
return 131072;
|
|
16
29
|
}
|
|
17
30
|
|
|
18
31
|
supportsVision() {
|
|
@@ -32,6 +32,18 @@ class NvidiaProvider extends OpenAICompatibleProvider {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
async listModels() {
|
|
36
|
+
try {
|
|
37
|
+
const res = await this.client.models.list();
|
|
38
|
+
const DROP = /embed|bge|e5-|rerank|guard|safety|moderat|diffus|flux|stable|imagen|vision-enc|whisper|tts|speech|paraphrase|classif/i;
|
|
39
|
+
return res.data
|
|
40
|
+
.filter((m) => !DROP.test(m.id))
|
|
41
|
+
.map((m) => ({ id: m.id, name: m.id }));
|
|
42
|
+
} catch (err) {
|
|
43
|
+
throw new Error(`NVIDIA NIM request failed: ${err?.message || String(err)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
getContextWindow(model) {
|
|
36
48
|
return CONTEXT_WINDOWS[model] ?? 131072;
|
|
37
49
|
}
|
|
@@ -84,12 +84,25 @@ class OllamaProvider extends BaseProvider {
|
|
|
84
84
|
buildChatBody(messages, tools, options, stream) {
|
|
85
85
|
const body = {
|
|
86
86
|
model: options.model || this.config.model || 'llama3.1',
|
|
87
|
-
messages: messages.map(m =>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
messages: messages.map(m => {
|
|
88
|
+
const msg = {
|
|
89
|
+
role: m.role,
|
|
90
|
+
content: m.content || '',
|
|
91
|
+
...(m.tool_call_id ? { tool_call_id: m.tool_call_id } : {})
|
|
92
|
+
};
|
|
93
|
+
if (m.tool_calls) {
|
|
94
|
+
msg.tool_calls = m.tool_calls.map(tc => ({
|
|
95
|
+
...tc,
|
|
96
|
+
function: {
|
|
97
|
+
...tc.function,
|
|
98
|
+
arguments: typeof tc.function.arguments === 'string'
|
|
99
|
+
? (function() { try { return JSON.parse(tc.function.arguments || '{}'); } catch(e) { return {}; } })()
|
|
100
|
+
: tc.function.arguments
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
return msg;
|
|
105
|
+
}),
|
|
93
106
|
stream,
|
|
94
107
|
options: {
|
|
95
108
|
temperature: options.temperature ?? 0.7,
|
|
@@ -6,24 +6,26 @@ class OpenAIProvider extends OpenAICompatibleProvider {
|
|
|
6
6
|
super(config);
|
|
7
7
|
this.name = 'openai';
|
|
8
8
|
this.models = [
|
|
9
|
+
'gpt-5.5',
|
|
9
10
|
'gpt-5',
|
|
10
11
|
'gpt-5-mini',
|
|
11
12
|
'gpt-5-nano',
|
|
12
13
|
'gpt-5.2',
|
|
13
|
-
'gpt-4.1',
|
|
14
14
|
'o3',
|
|
15
|
+
'o3-pro',
|
|
15
16
|
'o4-mini'
|
|
16
17
|
];
|
|
17
18
|
// Reasoning models: no temperature, use max_completion_tokens, support reasoning_effort
|
|
18
|
-
this.reasoningModels = new Set(['gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5.2', 'gpt-5.1', 'o1', 'o3', 'o4-mini', 'o3-mini']);
|
|
19
|
+
this.reasoningModels = new Set(['gpt-5.5', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5.2', 'gpt-5.1', 'o1', 'o3', 'o3-pro', 'o4-mini', 'o3-mini']);
|
|
19
20
|
this.contextWindows = {
|
|
21
|
+
'gpt-5.5': 1000000,
|
|
20
22
|
'gpt-5': 400000,
|
|
21
|
-
'gpt-5-mini':
|
|
23
|
+
'gpt-5-mini': 400000,
|
|
22
24
|
'gpt-5-nano': 128000,
|
|
23
25
|
'gpt-5.2': 400000,
|
|
24
26
|
'gpt-5.1': 400000,
|
|
25
|
-
'gpt-4.1': 1047576,
|
|
26
27
|
'o3': 200000,
|
|
28
|
+
'o3-pro': 200000,
|
|
27
29
|
'o4-mini': 200000,
|
|
28
30
|
'o3-mini': 200000
|
|
29
31
|
};
|
|
@@ -34,6 +36,18 @@ class OpenAIProvider extends OpenAICompatibleProvider {
|
|
|
34
36
|
});
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
async listModels() {
|
|
40
|
+
try {
|
|
41
|
+
const res = await this.client.models.list();
|
|
42
|
+
const DROP = /dall-e|whisper|tts|embed|moderat|realtime|audio|transcribe|search-api|-image-|babbage|davinci-002|^sora|-instruct/i;
|
|
43
|
+
return res.data
|
|
44
|
+
.filter((m) => !DROP.test(m.id))
|
|
45
|
+
.map((m) => ({ id: m.id, name: m.id }));
|
|
46
|
+
} catch (err) {
|
|
47
|
+
throw new Error(`Failed to list OpenAI models: ${err.message || String(err)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
isReasoningModel(model) {
|
|
38
52
|
// Match exact IDs and prefix variants (gpt-5-2025-08-07 etc)
|
|
39
53
|
for (const id of this.reasoningModels) {
|