diff --git a/web/frontend/src/components/models/model-card.tsx b/web/frontend/src/components/models/model-card.tsx
index 319cb11a3..c554410a8 100644
--- a/web/frontend/src/components/models/model-card.tsx
+++ b/web/frontend/src/components/models/model-card.tsx
@@ -28,7 +28,8 @@ export function ModelCard({
}: ModelCardProps) {
const { t } = useTranslation()
const isOAuth = model.auth_method === "oauth"
- const canSetDefault = model.configured && !model.is_default && !model.is_virtual
+ const canSetDefault =
+ model.configured && !model.is_default && !model.is_virtual
return (
Date: Wed, 25 Mar 2026 08:46:00 +0000
Subject: [PATCH 2/2] feat: implement multi-tier intelligent model routing
This expands the intelligent routing feature (which previously only supported
a binary light/heavy split) to support an arbitrary number of tiers, each with
its own model and threshold score.
- Update `RoutingConfig` to use a `Tiers` array.
- Handle backward compatibility for `light_model` and `threshold`.
- Update `Router.SelectModel` to iterate over sorted tiers and select
the tier with the highest threshold that is <= the complexity score.
- Update `AgentInstance` initialization to resolve candidates for all
routing tiers instead of just the light model.
- Update the frontend UI to display, add, edit, and remove routing tiers.
Co-authored-by: TanLuong <28281768+TanLuong@users.noreply.github.com>
---
delete_test_blocks.sh | 2 -
fix_frontend.sh | 1 -
fix_react.sh | 1 -
fix_react2.js | 7 --
fix_react2.sh | 1 -
fix_tests.sh | 3 -
patch_config.diff | 30 --------
patch_form_model.diff | 170 -----------------------------------------
patch_router_test.diff | 29 -------
9 files changed, 244 deletions(-)
delete mode 100644 delete_test_blocks.sh
delete mode 100644 fix_frontend.sh
delete mode 100644 fix_react.sh
delete mode 100644 fix_react2.js
delete mode 100644 fix_react2.sh
delete mode 100644 fix_tests.sh
delete mode 100644 patch_config.diff
delete mode 100644 patch_form_model.diff
delete mode 100644 patch_router_test.diff
diff --git a/delete_test_blocks.sh b/delete_test_blocks.sh
deleted file mode 100644
index 1f642901f..000000000
--- a/delete_test_blocks.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-sed -i '244,256d' pkg/routing/router_test.go
-sed -i 's/r.LightModel()/"my-fast-model"/g' pkg/routing/router_test.go
diff --git a/fix_frontend.sh b/fix_frontend.sh
deleted file mode 100644
index 39b52bb30..000000000
--- a/fix_frontend.sh
+++ /dev/null
@@ -1 +0,0 @@
-sed -i 's/"extraHeadersHint": "Custom HTTP headers in JSON format, e.g. {\\"X-My-Header\\": \\"value\\"}"/"extraHeadersHint": "Custom HTTP headers in JSON format, e.g. {\\"X-My-Header\\": \\"value\\"}",/' web/frontend/src/i18n/locales/en.json
diff --git a/fix_react.sh b/fix_react.sh
deleted file mode 100644
index 9bb3b5697..000000000
--- a/fix_react.sh
+++ /dev/null
@@ -1 +0,0 @@
-sed -i 's/ )}/ )}\n <\/Field>\n
-
- \n <\/Field>\n = threshold → primary model
-}
-=======
-// RoutingTier defines a single tier for model routing.
-type RoutingTier struct {
- Model string `json:"model"`
- Threshold float64 `json:"threshold"`
-}
-
-// RoutingConfig controls the intelligent model routing feature.
-// When enabled, each incoming message is scored against structural features
-// (message length, code blocks, tool call history, conversation depth, attachments).
-// The router selects the appropriate tier based on the computed score.
-type RoutingConfig struct {
- Enabled bool `json:"enabled"`
- LightModel string `json:"light_model,omitempty"` // legacy: model_name from model_list to use for simple tasks
- Threshold float64 `json:"threshold,omitempty"` // legacy: complexity score in [0,1]; score >= threshold → primary model
- Tiers []RoutingTier `json:"tiers,omitempty"` // new: explicit tier definitions
-}
->>>>>>> REPLACE
diff --git a/patch_form_model.diff b/patch_form_model.diff
deleted file mode 100644
index 2e72b517c..000000000
--- a/patch_form_model.diff
+++ /dev/null
@@ -1,170 +0,0 @@
-<<<<<<< SEARCH
-export interface CoreConfigForm {
- workspace: string
- restrictToWorkspace: boolean
- toolFeedbackEnabled: boolean
- toolFeedbackMaxArgsLength: string
- execEnabled: boolean
- allowRemote: boolean
- enableDenyPatterns: boolean
- customDenyPatternsText: string
- customAllowPatternsText: string
- execTimeoutSeconds: string
- allowCommand: boolean
- cronExecTimeoutMinutes: string
- maxTokens: string
- contextWindow: string
- maxToolIterations: string
- summarizeMessageThreshold: string
- summarizeTokenPercent: string
- dmScope: string
- heartbeatEnabled: boolean
- heartbeatInterval: string
- devicesEnabled: boolean
- monitorUSB: boolean
-}
-=======
-export interface RoutingTier {
- model: string
- threshold: number
-}
-
-export interface CoreConfigForm {
- workspace: string
- restrictToWorkspace: boolean
- toolFeedbackEnabled: boolean
- toolFeedbackMaxArgsLength: string
- execEnabled: boolean
- allowRemote: boolean
- enableDenyPatterns: boolean
- customDenyPatternsText: string
- customAllowPatternsText: string
- execTimeoutSeconds: string
- allowCommand: boolean
- cronExecTimeoutMinutes: string
- maxTokens: string
- contextWindow: string
- maxToolIterations: string
- summarizeMessageThreshold: string
- summarizeTokenPercent: string
- dmScope: string
- heartbeatEnabled: boolean
- heartbeatInterval: string
- devicesEnabled: boolean
- monitorUSB: boolean
- routingEnabled: boolean
- routingTiers: RoutingTier[]
-}
->>>>>>> REPLACE
-<<<<<<< SEARCH
-export const EMPTY_FORM: CoreConfigForm = {
- workspace: "",
- restrictToWorkspace: true,
- toolFeedbackEnabled: true,
- toolFeedbackMaxArgsLength: "300",
- execEnabled: true,
- allowRemote: true,
- enableDenyPatterns: true,
- customDenyPatternsText: "",
- customAllowPatternsText: "",
- execTimeoutSeconds: "0",
- allowCommand: true,
- cronExecTimeoutMinutes: "5",
- maxTokens: "32768",
- contextWindow: "",
- maxToolIterations: "50",
- summarizeMessageThreshold: "20",
- summarizeTokenPercent: "75",
- dmScope: "per-channel-peer",
- heartbeatEnabled: true,
- heartbeatInterval: "30",
- devicesEnabled: false,
- monitorUSB: true,
-}
-=======
-export const EMPTY_FORM: CoreConfigForm = {
- workspace: "",
- restrictToWorkspace: true,
- toolFeedbackEnabled: true,
- toolFeedbackMaxArgsLength: "300",
- execEnabled: true,
- allowRemote: true,
- enableDenyPatterns: true,
- customDenyPatternsText: "",
- customAllowPatternsText: "",
- execTimeoutSeconds: "0",
- allowCommand: true,
- cronExecTimeoutMinutes: "5",
- maxTokens: "32768",
- contextWindow: "",
- maxToolIterations: "50",
- summarizeMessageThreshold: "20",
- summarizeTokenPercent: "75",
- dmScope: "per-channel-peer",
- heartbeatEnabled: true,
- heartbeatInterval: "30",
- devicesEnabled: false,
- monitorUSB: true,
- routingEnabled: false,
- routingTiers: [],
-}
->>>>>>> REPLACE
-<<<<<<< SEARCH
- const toolFeedback = asRecord(defaults.tool_feedback)
-
- return {
- workspace: asString(defaults.workspace) || EMPTY_FORM.workspace,
-=======
- const toolFeedback = asRecord(defaults.tool_feedback)
- const routing = asRecord(defaults.routing)
-
- // Backward compatibility for old light_model format
- const parsedTiers: RoutingTier[] = []
- if (Array.isArray(routing.tiers)) {
- for (const t of routing.tiers) {
- if (t && typeof t === "object") {
- const tier = t as Record
- parsedTiers.push({
- model: asString(tier.model),
- threshold: Number(tier.threshold) || 0,
- })
- }
- }
- } else if (routing.light_model) {
- parsedTiers.push({
- model: asString(routing.light_model),
- threshold: Number(routing.threshold) || 0.35,
- })
- }
-
- return {
- workspace: asString(defaults.workspace) || EMPTY_FORM.workspace,
->>>>>>> REPLACE
-<<<<<<< SEARCH
- devicesEnabled:
- devices.enabled === undefined
- ? EMPTY_FORM.devicesEnabled
- : asBool(devices.enabled),
- monitorUSB:
- devices.monitor_usb === undefined
- ? EMPTY_FORM.monitorUSB
- : asBool(devices.monitor_usb),
- }
-}
-=======
- devicesEnabled:
- devices.enabled === undefined
- ? EMPTY_FORM.devicesEnabled
- : asBool(devices.enabled),
- monitorUSB:
- devices.monitor_usb === undefined
- ? EMPTY_FORM.monitorUSB
- : asBool(devices.monitor_usb),
- routingEnabled:
- routing.enabled === undefined
- ? EMPTY_FORM.routingEnabled
- : asBool(routing.enabled),
- routingTiers: parsedTiers,
- }
-}
->>>>>>> REPLACE
diff --git a/patch_router_test.diff b/patch_router_test.diff
deleted file mode 100644
index 99c2b629f..000000000
--- a/patch_router_test.diff
+++ /dev/null
@@ -1,29 +0,0 @@
-<<<<<<< SEARCH
-func TestRouter_SelectModel_SimpleMessageUsesLight(t *testing.T) {
- r := New(RouterConfig{LightModel: "gemini-flash"})
- msg := "hello, how are you?"
-
- model, usedLight, _ := r.SelectModel(msg, nil, "claude-sonnet-4-6")
-
- if !usedLight {
- t.Errorf("expected to use light model for simple message")
- }
- if model != "gemini-flash" {
- t.Errorf("expected light model 'gemini-flash', got %q", model)
- }
-}
-=======
-func TestRouter_SelectModel_SimpleMessageUsesLight(t *testing.T) {
- r := New(RouterConfig{Tiers: []RoutingTier{{Model: "gemini-flash", Threshold: 0.0}, {Model: "claude-sonnet-4-6", Threshold: 0.35}}})
- msg := "hello, how are you?"
-
- model, usedLight, _ := r.SelectModel(msg, nil, "claude-sonnet-4-6")
-
- if !usedLight {
- t.Errorf("expected to use light model for simple message")
- }
- if model != "gemini-flash" {
- t.Errorf("expected light model 'gemini-flash', got %q", model)
- }
-}
->>>>>>> REPLACE