neoagent 2.4.1-beta.42 → 2.4.1-beta.44

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.
@@ -231,7 +231,7 @@ async function getProviderForUser(userId, task = '', isSubagent = false, modelOv
231
231
  };
232
232
  }
233
233
 
234
- async function getFailureFallbackModelId(userId, agentId, currentModelId, preferredFallbackId = null) {
234
+ async function getFailureFallbackModelId(userId, agentId, currentModelId, preferredFallbackId = null, failureError = null) {
235
235
  const { getSupportedModels } = require('./models');
236
236
  const aiSettings = getAiSettings(userId, agentId);
237
237
  const models = await getSupportedModels(userId, agentId);
@@ -247,7 +247,12 @@ async function getFailureFallbackModelId(userId, agentId, currentModelId, prefer
247
247
  || availableModels.find((model) => model.id === currentModelId)
248
248
  || null;
249
249
 
250
- if (preferredFallbackId && preferredFallbackId !== currentModelId) {
250
+ // When the failure is a provider-level rate limit, the preferred fallback is
251
+ // likely on the same provider and will hit the same limit. Skip it and prefer
252
+ // a fallback from a different provider instead.
253
+ const isProviderRateLimit = /429|rate.?limit|free-models-per/i.test(String(failureError?.message || ''));
254
+
255
+ if (preferredFallbackId && preferredFallbackId !== currentModelId && !isProviderRateLimit) {
251
256
  const preferred = pool.find((model) => model.id === preferredFallbackId)
252
257
  || availableModels.find((model) => model.id === preferredFallbackId);
253
258
  if (preferred) return preferred.id;
@@ -259,6 +264,14 @@ async function getFailureFallbackModelId(userId, agentId, currentModelId, prefer
259
264
  if (differentProvider) return differentProvider.id;
260
265
  }
261
266
 
267
+ // If no different-provider model exists, still try the preferred fallback
268
+ // even on rate limits (it's better than nothing).
269
+ if (preferredFallbackId && preferredFallbackId !== currentModelId) {
270
+ const preferred = pool.find((model) => model.id === preferredFallbackId)
271
+ || availableModels.find((model) => model.id === preferredFallbackId);
272
+ if (preferred) return preferred.id;
273
+ }
274
+
262
275
  const differentModel = pool.find((model) => model.id !== currentModelId)
263
276
  || availableModels.find((model) => model.id !== currentModelId);
264
277
  return differentModel?.id || null;
@@ -1093,7 +1106,9 @@ class AgentEngine {
1093
1106
  }
1094
1107
 
1095
1108
  getMessagingRetryLimit(maxIterations) {
1096
- return Math.max(1, maxIterations);
1109
+ // Cap at 3: more than 3 autonomous messaging retries indicates a structural
1110
+ // problem (model unavailable, bad config) that more retries won't solve.
1111
+ return Math.min(3, Math.max(1, maxIterations));
1097
1112
  }
1098
1113
 
1099
1114
  buildContextMessages(systemPrompt, summaryMessage, historyMessages, recallMsg) {
@@ -1234,7 +1249,7 @@ class AgentEngine {
1234
1249
  let model = selectedProvider.model;
1235
1250
  let providerName = selectedProvider.providerName;
1236
1251
  const switchToFallbackModel = async (failedModel, error, phase) => {
1237
- const fallbackModelId = await getFailureFallbackModelId(userId, agentId, failedModel, aiSettings.fallback_model_id);
1252
+ const fallbackModelId = await getFailureFallbackModelId(userId, agentId, failedModel, aiSettings.fallback_model_id, error);
1238
1253
  if (!fallbackModelId || fallbackModelId === failedModel) return false;
1239
1254
  console.log(`[Engine] ${phase} failed on ${failedModel}; attempting fallback to: ${fallbackModelId}`);
1240
1255
  this.emit(userId, 'run:interim', {
@@ -1700,7 +1715,7 @@ class AgentEngine {
1700
1715
  } catch (err) {
1701
1716
  console.error(`[Engine] Model call failed (${model}):`, err.message);
1702
1717
  const fallbackModelId = retryForFallback
1703
- ? await getFailureFallbackModelId(userId, agentId, model, aiSettings.fallback_model_id)
1718
+ ? await getFailureFallbackModelId(userId, agentId, model, aiSettings.fallback_model_id, err)
1704
1719
  : null;
1705
1720
  if (fallbackModelId) {
1706
1721
  const failedModel = model;
@@ -2503,11 +2518,16 @@ class AgentEngine {
2503
2518
 
2504
2519
  const runMeta = this.activeRuns.get(runId);
2505
2520
  const retryCount = Number(options.messagingAutonomousRetryCount || 0);
2521
+ // Rate-limit errors (429) must not trigger messaging retries: the model
2522
+ // won't be available in the milliseconds between retries, so spawning new
2523
+ // runs just compounds the rate-limit pressure with no benefit.
2524
+ const isRateLimitError = /429|rate.?limit|free-models-per/i.test(String(err?.message || ''));
2506
2525
  const canRetryMessagingRun = (
2507
2526
  triggerSource === 'messaging'
2508
2527
  && options.source
2509
2528
  && options.chatId
2510
2529
  && err?.disableAutonomousRetry !== true
2530
+ && !isRateLimitError
2511
2531
  && retryCount < this.getMessagingRetryLimit(maxIterations)
2512
2532
  );
2513
2533