neoagent 2.4.1-beta.40 → 2.4.1-beta.42
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/flutter_app/lib/main_settings.dart +649 -37
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +58177 -57815
- package/server/services/ai/models.js +8 -0
- package/server/services/ai/providers/openrouter.js +23 -0
|
@@ -224,6 +224,14 @@ async function refreshProviderModelList(providerId, apiKey, baseUrl) {
|
|
|
224
224
|
return models;
|
|
225
225
|
} catch (err) {
|
|
226
226
|
console.warn(`[Models] Failed to refresh ${providerId} models:`, err.message);
|
|
227
|
+
// Always record a lastRefresh so we don't hammer the API on every request.
|
|
228
|
+
// Permanent errors (auth/billing/credits) get a longer backoff.
|
|
229
|
+
const isPermanent = /401|403|unauthorized|forbidden|credits|spending/i.test(err.message);
|
|
230
|
+
const backoff = isPermanent ? 30 * 60 * 1000 : DYNAMIC_REFRESH_INTERVAL;
|
|
231
|
+
providerModelCache.set(cacheKey, {
|
|
232
|
+
models: existing?.models || [],
|
|
233
|
+
lastRefresh: now - DYNAMIC_REFRESH_INTERVAL + backoff,
|
|
234
|
+
});
|
|
227
235
|
return existing?.models || [];
|
|
228
236
|
}
|
|
229
237
|
}
|
|
@@ -16,6 +16,8 @@ class OpenRouterProvider extends OpenAICompatibleProvider {
|
|
|
16
16
|
this.client = new OpenAI({
|
|
17
17
|
apiKey: config.apiKey || process.env.OPENROUTER_API_KEY,
|
|
18
18
|
baseURL: this.baseURL,
|
|
19
|
+
timeout: 90_000, // 90 s — free models can queue; avoids hanging forever
|
|
20
|
+
maxRetries: 0, // engine handles retries; avoid SDK silently re-queuing slow models
|
|
19
21
|
defaultHeaders: {
|
|
20
22
|
'HTTP-Referer': 'https://github.com/NeoLabs-Systems/NeoAgent',
|
|
21
23
|
'X-Title': 'NeoAgent',
|
|
@@ -57,6 +59,14 @@ class OpenRouterProvider extends OpenAICompatibleProvider {
|
|
|
57
59
|
return params;
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
_extractOpenRouterError(obj) {
|
|
63
|
+
if (!obj) return null;
|
|
64
|
+
const e = obj.error;
|
|
65
|
+
if (!e) return null;
|
|
66
|
+
const msg = e.message || (typeof e === 'string' ? e : JSON.stringify(e));
|
|
67
|
+
return `OpenRouter: ${msg}${e.code ? ` (code ${e.code})` : ''}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
async chat(messages, tools = [], options = {}) {
|
|
61
71
|
const model = options.model || this.getDefaultModel();
|
|
62
72
|
const params = this._buildParams(model, messages, tools, options);
|
|
@@ -66,6 +76,12 @@ class OpenRouterProvider extends OpenAICompatibleProvider {
|
|
|
66
76
|
} catch (err) {
|
|
67
77
|
throw new Error(`OpenRouter request failed: ${err?.message || String(err)}`);
|
|
68
78
|
}
|
|
79
|
+
// OpenRouter returns HTTP 200 even for errors (rate limits, model unavailable, etc.)
|
|
80
|
+
const orErr = this._extractOpenRouterError(response);
|
|
81
|
+
if (orErr) throw new Error(orErr);
|
|
82
|
+
if (!response?.choices?.length) {
|
|
83
|
+
throw new Error(`OpenRouter: model returned no choices (may be rate-limited or unavailable)`);
|
|
84
|
+
}
|
|
69
85
|
return this.normalizeResponse(response);
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -88,6 +104,9 @@ class OpenRouterProvider extends OpenAICompatibleProvider {
|
|
|
88
104
|
let content = '';
|
|
89
105
|
let finalUsage = null;
|
|
90
106
|
|
|
107
|
+
// The OpenAI SDK converts OpenRouter's SSE error events (data.error) into
|
|
108
|
+
// APIErrors before yielding — so we wrap the loop to add context.
|
|
109
|
+
try {
|
|
91
110
|
for await (const chunk of stream) {
|
|
92
111
|
if (chunk.usage && (!chunk.choices || chunk.choices.length === 0)) {
|
|
93
112
|
finalUsage = this.normalizeUsage(chunk.usage);
|
|
@@ -127,6 +146,10 @@ class OpenRouterProvider extends OpenAICompatibleProvider {
|
|
|
127
146
|
return;
|
|
128
147
|
}
|
|
129
148
|
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
// Re-throw SDK APIErrors (from OpenRouter SSE error events) with OpenRouter context.
|
|
151
|
+
throw new Error(`OpenRouter: ${err?.message || String(err)}`);
|
|
152
|
+
}
|
|
130
153
|
|
|
131
154
|
if (toolCalls.length > 0) {
|
|
132
155
|
yield { type: 'tool_calls', toolCalls, content, usage: finalUsage };
|