@blockrun/clawrouter 0.12.38 → 0.12.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/README.md +129 -115
- package/dist/cli.js +126 -105
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +48 -50
- package/dist/index.js +123 -102
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -128,6 +128,42 @@ type OpenClawPluginDefinition = {
|
|
|
128
128
|
activate?: (api: OpenClawPluginApi) => void | Promise<void>;
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Tier → Model Selection
|
|
133
|
+
*
|
|
134
|
+
* Maps a classification tier to the cheapest capable model.
|
|
135
|
+
* Builds RoutingDecision metadata with cost estimates and savings.
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
type ModelPricing = {
|
|
139
|
+
inputPrice: number;
|
|
140
|
+
outputPrice: number;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Get the ordered fallback chain for a tier: [primary, ...fallbacks].
|
|
144
|
+
*/
|
|
145
|
+
declare function getFallbackChain(tier: Tier, tierConfigs: Record<Tier, TierConfig>): string[];
|
|
146
|
+
/**
|
|
147
|
+
* Calculate cost for a specific model (used when fallback model is used).
|
|
148
|
+
* Returns updated cost fields for RoutingDecision.
|
|
149
|
+
*/
|
|
150
|
+
declare function calculateModelCost(model: string, modelPricing: Map<string, ModelPricing>, estimatedInputTokens: number, maxOutputTokens: number, routingProfile?: "free" | "eco" | "auto" | "premium"): {
|
|
151
|
+
costEstimate: number;
|
|
152
|
+
baselineCost: number;
|
|
153
|
+
savings: number;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Get the fallback chain filtered by context length.
|
|
157
|
+
* Only returns models that can handle the estimated total context.
|
|
158
|
+
*
|
|
159
|
+
* @param tier - The tier to get fallback chain for
|
|
160
|
+
* @param tierConfigs - Tier configurations
|
|
161
|
+
* @param estimatedTotalTokens - Estimated total context (input + output)
|
|
162
|
+
* @param getContextWindow - Function to get context window for a model ID
|
|
163
|
+
* @returns Filtered list of models that can handle the context
|
|
164
|
+
*/
|
|
165
|
+
declare function getFallbackChainFiltered(tier: Tier, tierConfigs: Record<Tier, TierConfig>, estimatedTotalTokens: number, getContextWindow: (modelId: string) => number | undefined): string[];
|
|
166
|
+
|
|
131
167
|
/**
|
|
132
168
|
* Smart Router Types
|
|
133
169
|
*
|
|
@@ -148,6 +184,16 @@ type RoutingDecision = {
|
|
|
148
184
|
baselineCost: number;
|
|
149
185
|
savings: number;
|
|
150
186
|
agenticScore?: number;
|
|
187
|
+
/** Which tier configs were used (auto/eco/premium/agentic) — avoids re-derivation in proxy */
|
|
188
|
+
tierConfigs?: Record<Tier, TierConfig>;
|
|
189
|
+
/** Which routing profile was applied */
|
|
190
|
+
profile?: "auto" | "eco" | "premium" | "agentic";
|
|
191
|
+
};
|
|
192
|
+
type RouterOptions = {
|
|
193
|
+
config: RoutingConfig;
|
|
194
|
+
modelPricing: Map<string, ModelPricing>;
|
|
195
|
+
routingProfile?: "free" | "eco" | "auto" | "premium";
|
|
196
|
+
hasTools?: boolean;
|
|
151
197
|
};
|
|
152
198
|
type TierConfig = {
|
|
153
199
|
primary: string;
|
|
@@ -211,42 +257,6 @@ type RoutingConfig = {
|
|
|
211
257
|
overrides: OverridesConfig;
|
|
212
258
|
};
|
|
213
259
|
|
|
214
|
-
/**
|
|
215
|
-
* Tier → Model Selection
|
|
216
|
-
*
|
|
217
|
-
* Maps a classification tier to the cheapest capable model.
|
|
218
|
-
* Builds RoutingDecision metadata with cost estimates and savings.
|
|
219
|
-
*/
|
|
220
|
-
|
|
221
|
-
type ModelPricing = {
|
|
222
|
-
inputPrice: number;
|
|
223
|
-
outputPrice: number;
|
|
224
|
-
};
|
|
225
|
-
/**
|
|
226
|
-
* Get the ordered fallback chain for a tier: [primary, ...fallbacks].
|
|
227
|
-
*/
|
|
228
|
-
declare function getFallbackChain(tier: Tier, tierConfigs: Record<Tier, TierConfig>): string[];
|
|
229
|
-
/**
|
|
230
|
-
* Calculate cost for a specific model (used when fallback model is used).
|
|
231
|
-
* Returns updated cost fields for RoutingDecision.
|
|
232
|
-
*/
|
|
233
|
-
declare function calculateModelCost(model: string, modelPricing: Map<string, ModelPricing>, estimatedInputTokens: number, maxOutputTokens: number, routingProfile?: "free" | "eco" | "auto" | "premium"): {
|
|
234
|
-
costEstimate: number;
|
|
235
|
-
baselineCost: number;
|
|
236
|
-
savings: number;
|
|
237
|
-
};
|
|
238
|
-
/**
|
|
239
|
-
* Get the fallback chain filtered by context length.
|
|
240
|
-
* Only returns models that can handle the estimated total context.
|
|
241
|
-
*
|
|
242
|
-
* @param tier - The tier to get fallback chain for
|
|
243
|
-
* @param tierConfigs - Tier configurations
|
|
244
|
-
* @param estimatedTotalTokens - Estimated total context (input + output)
|
|
245
|
-
* @param getContextWindow - Function to get context window for a model ID
|
|
246
|
-
* @returns Filtered list of models that can handle the context
|
|
247
|
-
*/
|
|
248
|
-
declare function getFallbackChainFiltered(tier: Tier, tierConfigs: Record<Tier, TierConfig>, estimatedTotalTokens: number, getContextWindow: (modelId: string) => number | undefined): string[];
|
|
249
|
-
|
|
250
260
|
/**
|
|
251
261
|
* Default Routing Config
|
|
252
262
|
*
|
|
@@ -262,24 +272,12 @@ declare const DEFAULT_ROUTING_CONFIG: RoutingConfig;
|
|
|
262
272
|
* Smart Router Entry Point
|
|
263
273
|
*
|
|
264
274
|
* Classifies requests and routes to the cheapest capable model.
|
|
265
|
-
*
|
|
266
|
-
* Ambiguous cases default to configurable tier (MEDIUM by default).
|
|
275
|
+
* Delegates to pluggable RouterStrategy (default: RulesStrategy, <1ms).
|
|
267
276
|
*/
|
|
268
277
|
|
|
269
|
-
type RouterOptions = {
|
|
270
|
-
config: RoutingConfig;
|
|
271
|
-
modelPricing: Map<string, ModelPricing>;
|
|
272
|
-
routingProfile?: "free" | "eco" | "auto" | "premium";
|
|
273
|
-
hasTools?: boolean;
|
|
274
|
-
};
|
|
275
278
|
/**
|
|
276
279
|
* Route a request to the cheapest capable model.
|
|
277
|
-
*
|
|
278
|
-
* 1. Check overrides (large context, structured output)
|
|
279
|
-
* 2. Run rule-based classifier (14 weighted dimensions, <1ms)
|
|
280
|
-
* 3. If ambiguous, default to configurable tier (no external API calls)
|
|
281
|
-
* 4. Select model for tier
|
|
282
|
-
* 5. Return RoutingDecision with metadata
|
|
280
|
+
* Delegates to the registered "rules" strategy by default.
|
|
283
281
|
*/
|
|
284
282
|
declare function route(prompt: string, systemPrompt: string | undefined, maxOutputTokens: number, options: RouterOptions): RoutingDecision;
|
|
285
283
|
|
package/dist/index.js
CHANGED
|
@@ -2022,6 +2022,99 @@ function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getCo
|
|
|
2022
2022
|
return filtered;
|
|
2023
2023
|
}
|
|
2024
2024
|
|
|
2025
|
+
// src/router/strategy.ts
|
|
2026
|
+
var RulesStrategy = class {
|
|
2027
|
+
name = "rules";
|
|
2028
|
+
route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
2029
|
+
const { config, modelPricing } = options;
|
|
2030
|
+
const fullText = `${systemPrompt ?? ""} ${prompt}`;
|
|
2031
|
+
const estimatedTokens = Math.ceil(fullText.length / 4);
|
|
2032
|
+
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
2033
|
+
const { routingProfile } = options;
|
|
2034
|
+
let tierConfigs;
|
|
2035
|
+
let profileSuffix;
|
|
2036
|
+
let profile;
|
|
2037
|
+
if (routingProfile === "eco" && config.ecoTiers) {
|
|
2038
|
+
tierConfigs = config.ecoTiers;
|
|
2039
|
+
profileSuffix = " | eco";
|
|
2040
|
+
profile = "eco";
|
|
2041
|
+
} else if (routingProfile === "premium" && config.premiumTiers) {
|
|
2042
|
+
tierConfigs = config.premiumTiers;
|
|
2043
|
+
profileSuffix = " | premium";
|
|
2044
|
+
profile = "premium";
|
|
2045
|
+
} else {
|
|
2046
|
+
const agenticScore = ruleResult.agenticScore ?? 0;
|
|
2047
|
+
const isAutoAgentic = agenticScore >= 0.5;
|
|
2048
|
+
const isExplicitAgentic = config.overrides.agenticMode ?? false;
|
|
2049
|
+
const hasToolsInRequest = options.hasTools ?? false;
|
|
2050
|
+
const useAgenticTiers = (hasToolsInRequest || isAutoAgentic || isExplicitAgentic) && config.agenticTiers != null;
|
|
2051
|
+
tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
|
|
2052
|
+
profileSuffix = useAgenticTiers ? ` | agentic${hasToolsInRequest ? " (tools)" : ""}` : "";
|
|
2053
|
+
profile = useAgenticTiers ? "agentic" : "auto";
|
|
2054
|
+
}
|
|
2055
|
+
const agenticScoreValue = ruleResult.agenticScore;
|
|
2056
|
+
if (estimatedTokens > config.overrides.maxTokensForceComplex) {
|
|
2057
|
+
const decision2 = selectModel(
|
|
2058
|
+
"COMPLEX",
|
|
2059
|
+
0.95,
|
|
2060
|
+
"rules",
|
|
2061
|
+
`Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`,
|
|
2062
|
+
tierConfigs,
|
|
2063
|
+
modelPricing,
|
|
2064
|
+
estimatedTokens,
|
|
2065
|
+
maxOutputTokens,
|
|
2066
|
+
routingProfile,
|
|
2067
|
+
agenticScoreValue
|
|
2068
|
+
);
|
|
2069
|
+
return { ...decision2, tierConfigs, profile };
|
|
2070
|
+
}
|
|
2071
|
+
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
2072
|
+
let tier;
|
|
2073
|
+
let confidence;
|
|
2074
|
+
const method = "rules";
|
|
2075
|
+
let reasoning = `score=${ruleResult.score.toFixed(2)} | ${ruleResult.signals.join(", ")}`;
|
|
2076
|
+
if (ruleResult.tier !== null) {
|
|
2077
|
+
tier = ruleResult.tier;
|
|
2078
|
+
confidence = ruleResult.confidence;
|
|
2079
|
+
} else {
|
|
2080
|
+
tier = config.overrides.ambiguousDefaultTier;
|
|
2081
|
+
confidence = 0.5;
|
|
2082
|
+
reasoning += ` | ambiguous -> default: ${tier}`;
|
|
2083
|
+
}
|
|
2084
|
+
if (hasStructuredOutput) {
|
|
2085
|
+
const tierRank = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };
|
|
2086
|
+
const minTier = config.overrides.structuredOutputMinTier;
|
|
2087
|
+
if (tierRank[tier] < tierRank[minTier]) {
|
|
2088
|
+
reasoning += ` | upgraded to ${minTier} (structured output)`;
|
|
2089
|
+
tier = minTier;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
reasoning += profileSuffix;
|
|
2093
|
+
const decision = selectModel(
|
|
2094
|
+
tier,
|
|
2095
|
+
confidence,
|
|
2096
|
+
method,
|
|
2097
|
+
reasoning,
|
|
2098
|
+
tierConfigs,
|
|
2099
|
+
modelPricing,
|
|
2100
|
+
estimatedTokens,
|
|
2101
|
+
maxOutputTokens,
|
|
2102
|
+
routingProfile,
|
|
2103
|
+
agenticScoreValue
|
|
2104
|
+
);
|
|
2105
|
+
return { ...decision, tierConfigs, profile };
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
var registry = /* @__PURE__ */ new Map();
|
|
2109
|
+
registry.set("rules", new RulesStrategy());
|
|
2110
|
+
function getStrategy(name) {
|
|
2111
|
+
const strategy = registry.get(name);
|
|
2112
|
+
if (!strategy) {
|
|
2113
|
+
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2114
|
+
}
|
|
2115
|
+
return strategy;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2025
2118
|
// src/router/config.ts
|
|
2026
2119
|
var DEFAULT_ROUTING_CONFIG = {
|
|
2027
2120
|
version: "2.0",
|
|
@@ -3114,7 +3207,11 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
3114
3207
|
SIMPLE: {
|
|
3115
3208
|
primary: "nvidia/gpt-oss-120b",
|
|
3116
3209
|
// FREE! $0.00/$0.00
|
|
3117
|
-
fallback: [
|
|
3210
|
+
fallback: [
|
|
3211
|
+
"google/gemini-2.5-flash-lite",
|
|
3212
|
+
"google/gemini-2.5-flash",
|
|
3213
|
+
"deepseek/deepseek-chat"
|
|
3214
|
+
]
|
|
3118
3215
|
},
|
|
3119
3216
|
MEDIUM: {
|
|
3120
3217
|
primary: "google/gemini-2.5-flash-lite",
|
|
@@ -3239,77 +3336,8 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
3239
3336
|
|
|
3240
3337
|
// src/router/index.ts
|
|
3241
3338
|
function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
3242
|
-
const
|
|
3243
|
-
|
|
3244
|
-
const estimatedTokens = Math.ceil(fullText.length / 4);
|
|
3245
|
-
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
3246
|
-
const { routingProfile } = options;
|
|
3247
|
-
let tierConfigs;
|
|
3248
|
-
let profileSuffix;
|
|
3249
|
-
if (routingProfile === "eco" && config.ecoTiers) {
|
|
3250
|
-
tierConfigs = config.ecoTiers;
|
|
3251
|
-
profileSuffix = " | eco";
|
|
3252
|
-
} else if (routingProfile === "premium" && config.premiumTiers) {
|
|
3253
|
-
tierConfigs = config.premiumTiers;
|
|
3254
|
-
profileSuffix = " | premium";
|
|
3255
|
-
} else {
|
|
3256
|
-
const agenticScore = ruleResult.agenticScore ?? 0;
|
|
3257
|
-
const isAutoAgentic = agenticScore >= 0.5;
|
|
3258
|
-
const isExplicitAgentic = config.overrides.agenticMode ?? false;
|
|
3259
|
-
const hasToolsInRequest = options.hasTools ?? false;
|
|
3260
|
-
const useAgenticTiers = (hasToolsInRequest || isAutoAgentic || isExplicitAgentic) && config.agenticTiers != null;
|
|
3261
|
-
tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
|
|
3262
|
-
profileSuffix = useAgenticTiers ? ` | agentic${hasToolsInRequest ? " (tools)" : ""}` : "";
|
|
3263
|
-
}
|
|
3264
|
-
const agenticScoreValue = ruleResult.agenticScore;
|
|
3265
|
-
if (estimatedTokens > config.overrides.maxTokensForceComplex) {
|
|
3266
|
-
return selectModel(
|
|
3267
|
-
"COMPLEX",
|
|
3268
|
-
0.95,
|
|
3269
|
-
"rules",
|
|
3270
|
-
`Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`,
|
|
3271
|
-
tierConfigs,
|
|
3272
|
-
modelPricing,
|
|
3273
|
-
estimatedTokens,
|
|
3274
|
-
maxOutputTokens,
|
|
3275
|
-
routingProfile,
|
|
3276
|
-
agenticScoreValue
|
|
3277
|
-
);
|
|
3278
|
-
}
|
|
3279
|
-
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
3280
|
-
let tier;
|
|
3281
|
-
let confidence;
|
|
3282
|
-
const method = "rules";
|
|
3283
|
-
let reasoning = `score=${ruleResult.score.toFixed(2)} | ${ruleResult.signals.join(", ")}`;
|
|
3284
|
-
if (ruleResult.tier !== null) {
|
|
3285
|
-
tier = ruleResult.tier;
|
|
3286
|
-
confidence = ruleResult.confidence;
|
|
3287
|
-
} else {
|
|
3288
|
-
tier = config.overrides.ambiguousDefaultTier;
|
|
3289
|
-
confidence = 0.5;
|
|
3290
|
-
reasoning += ` | ambiguous -> default: ${tier}`;
|
|
3291
|
-
}
|
|
3292
|
-
if (hasStructuredOutput) {
|
|
3293
|
-
const tierRank = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };
|
|
3294
|
-
const minTier = config.overrides.structuredOutputMinTier;
|
|
3295
|
-
if (tierRank[tier] < tierRank[minTier]) {
|
|
3296
|
-
reasoning += ` | upgraded to ${minTier} (structured output)`;
|
|
3297
|
-
tier = minTier;
|
|
3298
|
-
}
|
|
3299
|
-
}
|
|
3300
|
-
reasoning += profileSuffix;
|
|
3301
|
-
return selectModel(
|
|
3302
|
-
tier,
|
|
3303
|
-
confidence,
|
|
3304
|
-
method,
|
|
3305
|
-
reasoning,
|
|
3306
|
-
tierConfigs,
|
|
3307
|
-
modelPricing,
|
|
3308
|
-
estimatedTokens,
|
|
3309
|
-
maxOutputTokens,
|
|
3310
|
-
routingProfile,
|
|
3311
|
-
agenticScoreValue
|
|
3312
|
-
);
|
|
3339
|
+
const strategy = getStrategy("rules");
|
|
3340
|
+
return strategy.route(prompt, systemPrompt, maxOutputTokens, options);
|
|
3313
3341
|
}
|
|
3314
3342
|
|
|
3315
3343
|
// src/logger.ts
|
|
@@ -5454,6 +5482,12 @@ var ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
|
5454
5482
|
"premium"
|
|
5455
5483
|
]);
|
|
5456
5484
|
var FREE_MODEL = "nvidia/gpt-oss-120b";
|
|
5485
|
+
var FREE_TIER_CONFIGS = {
|
|
5486
|
+
SIMPLE: { primary: FREE_MODEL, fallback: [] },
|
|
5487
|
+
MEDIUM: { primary: FREE_MODEL, fallback: [] },
|
|
5488
|
+
COMPLEX: { primary: FREE_MODEL, fallback: [] },
|
|
5489
|
+
REASONING: { primary: FREE_MODEL, fallback: [] }
|
|
5490
|
+
};
|
|
5457
5491
|
var freeRequestCount = 0;
|
|
5458
5492
|
var MAX_MESSAGES = 200;
|
|
5459
5493
|
var CONTEXT_LIMIT_KB = 5120;
|
|
@@ -7328,16 +7362,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7328
7362
|
|
|
7329
7363
|
`;
|
|
7330
7364
|
}
|
|
7331
|
-
|
|
7332
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7365
|
+
routingDecision = {
|
|
7333
7366
|
model: freeModel,
|
|
7334
7367
|
tier: "SIMPLE",
|
|
7335
|
-
|
|
7368
|
+
confidence: 1,
|
|
7369
|
+
method: "rules",
|
|
7370
|
+
reasoning: "free profile",
|
|
7371
|
+
costEstimate: 0,
|
|
7336
7372
|
baselineCost: 0,
|
|
7337
7373
|
savings: 1,
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
});
|
|
7374
|
+
tierConfigs: FREE_TIER_CONFIGS
|
|
7375
|
+
};
|
|
7341
7376
|
} else {
|
|
7342
7377
|
effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
|
|
7343
7378
|
const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
|
|
@@ -7428,18 +7463,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7428
7463
|
const contentHash = hashRequestContent(prompt, toolCallNames);
|
|
7429
7464
|
const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
|
|
7430
7465
|
if (shouldEscalate) {
|
|
7431
|
-
const activeTierConfigs =
|
|
7432
|
-
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
7433
|
-
return routerOpts.config.agenticTiers;
|
|
7434
|
-
}
|
|
7435
|
-
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
7436
|
-
return routerOpts.config.ecoTiers;
|
|
7437
|
-
}
|
|
7438
|
-
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
7439
|
-
return routerOpts.config.premiumTiers;
|
|
7440
|
-
}
|
|
7441
|
-
return routerOpts.config.tiers;
|
|
7442
|
-
})();
|
|
7466
|
+
const activeTierConfigs = routingDecision.tierConfigs ?? routerOpts.config.tiers;
|
|
7443
7467
|
const escalation = sessionStore.escalateSession(
|
|
7444
7468
|
effectiveSessionId,
|
|
7445
7469
|
activeTierConfigs
|
|
@@ -7655,18 +7679,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7655
7679
|
if (routingDecision) {
|
|
7656
7680
|
const estimatedInputTokens = Math.ceil(body.length / 4);
|
|
7657
7681
|
const estimatedTotalTokens = estimatedInputTokens + maxTokens;
|
|
7658
|
-
const tierConfigs =
|
|
7659
|
-
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
7660
|
-
return routerOpts.config.agenticTiers;
|
|
7661
|
-
}
|
|
7662
|
-
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
7663
|
-
return routerOpts.config.ecoTiers;
|
|
7664
|
-
}
|
|
7665
|
-
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
7666
|
-
return routerOpts.config.premiumTiers;
|
|
7667
|
-
}
|
|
7668
|
-
return routerOpts.config.tiers;
|
|
7669
|
-
})();
|
|
7682
|
+
const tierConfigs = routingDecision.tierConfigs ?? routerOpts.config.tiers;
|
|
7670
7683
|
const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);
|
|
7671
7684
|
const contextFiltered = getFallbackChainFiltered(
|
|
7672
7685
|
routingDecision.tier,
|
|
@@ -7746,6 +7759,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7746
7759
|
status: result.errorStatus || 500
|
|
7747
7760
|
};
|
|
7748
7761
|
if (result.isProviderError && !isLastAttempt) {
|
|
7762
|
+
const isExplicitModelError = !routingDecision;
|
|
7763
|
+
const isUnknownExplicitModel = isExplicitModelError && /unknown.*model|invalid.*model/i.test(result.errorBody || "");
|
|
7764
|
+
if (isUnknownExplicitModel) {
|
|
7765
|
+
console.log(
|
|
7766
|
+
`[ClawRouter] Explicit model error from ${tryModel}, not falling back: ${result.errorBody?.slice(0, 100)}`
|
|
7767
|
+
);
|
|
7768
|
+
break;
|
|
7769
|
+
}
|
|
7749
7770
|
if (result.errorStatus === 429) {
|
|
7750
7771
|
markRateLimited(tryModel);
|
|
7751
7772
|
try {
|