@blockrun/franklin 3.15.11 → 3.15.12

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.
@@ -24,7 +24,7 @@ import { logger, setDebugMode } from '../logger.js';
24
24
  import { estimateCost, OPUS_PRICING } from '../pricing.js';
25
25
  import { maybeMidSessionExtract } from '../learnings/extractor.js';
26
26
  import { extractMentions, buildEntityContext, loadEntities } from '../brain/store.js';
27
- import { routeRequestAsync, resolveTierToModel, parseRoutingProfile, getFallbackChain } from '../router/index.js';
27
+ import { routeRequestAsync, resolveTierToModel, parseRoutingProfile, getFallbackChain, pickFreeFallback } from '../router/index.js';
28
28
  import { recordOutcome } from '../router/local-elo.js';
29
29
  import { shouldPlan, getPlanningPrompt, getExecutorModel, isExecutorStuck, toolCallSignature } from './planner.js';
30
30
  import { shouldVerify, runVerification } from './verification.js';
@@ -1098,8 +1098,7 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
1098
1098
  if (lastRoutedCategory) {
1099
1099
  recordOutcome(lastRoutedCategory, config.model, 'payment');
1100
1100
  }
1101
- const FREE_MODELS = ['nvidia/qwen3-coder-480b', 'nvidia/llama-4-maverick', 'nvidia/glm-4.7'];
1102
- const nextFree = FREE_MODELS.find(m => !turnFailedModels.has(m));
1101
+ const nextFree = pickFreeFallback(lastRoutedCategory, turnFailedModels);
1103
1102
  if (nextFree) {
1104
1103
  const oldModel = config.model;
1105
1104
  config.model = nextFree;
@@ -1120,8 +1119,7 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
1120
1119
  if (lastRoutedCategory) {
1121
1120
  recordOutcome(lastRoutedCategory, config.model, 'rate_limit');
1122
1121
  }
1123
- const FREE_MODELS = ['nvidia/qwen3-coder-480b', 'nvidia/llama-4-maverick', 'nvidia/glm-4.7'];
1124
- const nextFree = FREE_MODELS.find(m => !turnFailedModels.has(m));
1122
+ const nextFree = pickFreeFallback(lastRoutedCategory, turnFailedModels);
1125
1123
  if (nextFree) {
1126
1124
  const oldModel = config.model;
1127
1125
  config.model = nextFree;
@@ -49,6 +49,12 @@ export declare function routeRequest(prompt: string, profile?: RoutingProfile):
49
49
  * Get fallback models for a tier
50
50
  */
51
51
  export declare function getFallbackChain(tier: Tier, profile?: RoutingProfile): string[];
52
+ /**
53
+ * Pick the next free model to try given the question category and which
54
+ * free models have already failed this turn. Returns undefined when every
55
+ * candidate has been exhausted (caller should surface an error to user).
56
+ */
57
+ export declare function pickFreeFallback(category: string, alreadyFailed: Set<string>): string | undefined;
52
58
  /**
53
59
  * Parse routing profile from model string
54
60
  */
@@ -483,10 +483,45 @@ function computeSavings(model) {
483
483
  */
484
484
  export function getFallbackChain(tier, profile = 'auto') {
485
485
  if (profile === 'free')
486
- return ['nvidia/qwen3-coder-480b'];
486
+ return FREE_MODELS_BY_CATEGORY.chat;
487
487
  const config = AUTO_TIERS[tier];
488
488
  return [config.primary, ...config.fallback];
489
489
  }
490
+ // ─── Free-tier fallback (used when paid models 402 / rate-limit) ───
491
+ // Free fallback chains by question category. Used when a paid model fails
492
+ // mid-turn (402 payment, rate-limit) and we need a zero-cost replacement
493
+ // to keep the user moving without waiting for funding.
494
+ //
495
+ // The lists are ordered: best-fit free model first, then degraded fallbacks.
496
+ // Coding goes to qwen3-coder; everything else (chat / trading / research /
497
+ // reasoning / creative) prefers general-purpose free models that aren't
498
+ // coder-tuned. Without this split, a BTC question that exhausted paid
499
+ // models was being handed to qwen3-coder-480b — a coder model trying to
500
+ // do technical analysis. Reported 2026-05-03 with a markets question
501
+ // routed to a coder model on Sonnet failure.
502
+ const FREE_MODELS_BY_CATEGORY = {
503
+ coding: ['nvidia/qwen3-coder-480b', 'nvidia/glm-4.7', 'nvidia/llama-4-maverick'],
504
+ trading: ['nvidia/glm-4.7', 'nvidia/llama-4-maverick', 'nvidia/qwen3-coder-480b'],
505
+ research: ['nvidia/glm-4.7', 'nvidia/llama-4-maverick', 'nvidia/qwen3-coder-480b'],
506
+ reasoning: ['nvidia/glm-4.7', 'nvidia/qwen3-coder-480b', 'nvidia/llama-4-maverick'],
507
+ chat: ['nvidia/llama-4-maverick', 'nvidia/glm-4.7', 'nvidia/qwen3-coder-480b'],
508
+ creative: ['nvidia/llama-4-maverick', 'nvidia/glm-4.7', 'nvidia/qwen3-coder-480b'],
509
+ };
510
+ const DEFAULT_FREE_CHAIN = [
511
+ 'nvidia/glm-4.7',
512
+ 'nvidia/llama-4-maverick',
513
+ 'nvidia/qwen3-coder-480b',
514
+ ];
515
+ /**
516
+ * Pick the next free model to try given the question category and which
517
+ * free models have already failed this turn. Returns undefined when every
518
+ * candidate has been exhausted (caller should surface an error to user).
519
+ */
520
+ export function pickFreeFallback(category, alreadyFailed) {
521
+ const chain = FREE_MODELS_BY_CATEGORY[category]
522
+ ?? DEFAULT_FREE_CHAIN;
523
+ return chain.find(m => !alreadyFailed.has(m));
524
+ }
490
525
  /**
491
526
  * Parse routing profile from model string
492
527
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.11",
3
+ "version": "3.15.12",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {