@@ -11,33 +11,77 @@ export const CUSTOM_MODEL_VALUE = '__custom_model__'
1111/** Sentinel Select value: clear the route so the agent inherits the parent model. */
1212export const CLEAR_ROUTE_VALUE = '__clear_route__'
1313
14- /** The route currently assigned to an agent type in user settings. */
14+ /**
15+ * The route currently assigned to an agent type in user settings. `viaDefault`
16+ * marks a route the agent only gets through the `default` fallback (it has no
17+ * own routing key), so the menu can show it without claiming it as the agent's
18+ * own assignment.
19+ */
1520export type CurrentAgentRoute =
1621 | { kind : 'none' }
17- | { kind : 'model-only' ; routeKey : string ; model : string }
18- | { kind : 'cross-provider' ; routeKey : string ; model : string ; baseURL : string }
19- | { kind : 'dangling' ; routeKey : string }
22+ | { kind : 'model-only' ; routeKey : string ; model : string ; viaDefault ?: boolean }
23+ | { kind : 'cross-provider' ; routeKey : string ; model : string ; baseURL : string ; viaDefault ?: boolean }
24+ | { kind : 'dangling' ; routeKey : string ; viaDefault ?: boolean }
2025
21- /** Read the route assigned to `agentType` from a settings value. Pure. */
22- export function readAgentRoute (
23- settings : SettingsJson | null ,
26+ /** Normalize a routing key the same way the runtime resolver does. */
27+ function normalizeAgentKey ( key : string ) : string {
28+ return key . toLowerCase ( ) . replace ( / [ - _ ] / g, '' )
29+ }
30+
31+ /**
32+ * The existing `agentRouting` key (original spelling) that the runtime resolver
33+ * would match for `agentType`, or undefined if none. Mirrors
34+ * resolveAgentProvider's case-insensitive, hyphen/underscore-insensitive,
35+ * first-wins lookup. Pure.
36+ */
37+ function findOwnRouteKey (
38+ routing : Record < string , string > | undefined ,
2439 agentType : string ,
40+ ) : string | undefined {
41+ if ( ! routing ) return undefined
42+ const target = normalizeAgentKey ( agentType )
43+ for ( const key of Object . keys ( routing ) ) {
44+ if ( normalizeAgentKey ( key ) === target ) return key
45+ }
46+ return undefined
47+ }
48+
49+ /** Build the route descriptor for a resolved model key. Pure. */
50+ function describeModelKey (
51+ settings : SettingsJson | null ,
52+ modelKey : string ,
53+ viaDefault : boolean ,
2554) : CurrentAgentRoute {
26- const routeKey = settings ?. agentRouting ?. [ agentType ]
27- if ( ! routeKey ) return { kind : 'none' }
28- const entry = settings ?. agentModels ?. [ routeKey ]
29- if ( ! entry ) return { kind : 'dangling' , routeKey }
30- const model = entry . model ?. trim ( ) || routeKey
55+ const entry = settings ?. agentModels ?. [ modelKey ]
56+ if ( ! entry ) return { kind : 'dangling' , routeKey : modelKey , ...( viaDefault ? { viaDefault } : { } ) }
57+ const model = entry . model ?. trim ( ) || modelKey
3158 // Mirror the runtime resolver (toAgentRoute): cross-provider needs BOTH
3259 // base_url and api_key. A partial entry is skipped at runtime and inherits,
3360 // so surface it as unconfigured rather than claiming a route that won't run.
3461 const baseURL = entry . base_url ?. trim ( )
3562 const apiKey = entry . api_key ?. trim ( )
36- if ( ! baseURL && ! apiKey ) return { kind : 'model-only' , routeKey, model }
63+ if ( ! baseURL && ! apiKey ) return { kind : 'model-only' , routeKey : modelKey , model, ... ( viaDefault ? { viaDefault } : { } ) }
3764 if ( baseURL && apiKey ) {
38- return { kind : 'cross-provider' , routeKey, model, baseURL }
65+ return { kind : 'cross-provider' , routeKey : modelKey , model, baseURL, ... ( viaDefault ? { viaDefault } : { } ) }
3966 }
40- return { kind : 'dangling' , routeKey }
67+ return { kind : 'dangling' , routeKey : modelKey , ...( viaDefault ? { viaDefault } : { } ) }
68+ }
69+
70+ /**
71+ * Read the route assigned to `agentType` from a settings value, mirroring the
72+ * runtime resolver: a normalized per-agent key wins, otherwise the `default`
73+ * fallback applies (surfaced with `viaDefault`). Pure.
74+ */
75+ export function readAgentRoute (
76+ settings : SettingsJson | null ,
77+ agentType : string ,
78+ ) : CurrentAgentRoute {
79+ const routing = settings ?. agentRouting
80+ const ownKey = findOwnRouteKey ( routing , agentType )
81+ if ( ownKey ) return describeModelKey ( settings , routing ! [ ownKey ] , false )
82+ const defaultModelKey = routing ?. default
83+ if ( defaultModelKey ) return describeModelKey ( settings , defaultModelKey , true )
84+ return { kind : 'none' }
4185}
4286
4387/** The Select value representing the current route, if any. Pure. */
@@ -59,29 +103,39 @@ export function computeSetRouteUpdate(
59103 if ( ! agentModels [ modelKey ] ) {
60104 agentModels [ modelKey ] = { model : modelKey }
61105 }
62- const agentRouting = { ...( settings ?. agentRouting ?? { } ) , [ agentType ] : modelKey }
106+ // Reuse the existing routing key the runtime would match so we overwrite it
107+ // in place instead of writing a normalized sibling the resolver's first-wins
108+ // lookup would ignore (e.g. "general-purpose" beside "general_purpose").
109+ const routingKey = findOwnRouteKey ( settings ?. agentRouting , agentType ) ?? agentType
110+ const agentRouting = { ...( settings ?. agentRouting ?? { } ) , [ routingKey ] : modelKey }
63111 return { agentModels, agentRouting } as unknown as SettingsJson
64112}
65113
66114/**
67- * Next settings to clear `agentType`'s route. The explicit `undefined` is what
68- * makes updateSettingsForSource delete the key on merge. Pure.
115+ * Next settings to clear `agentType`'s route. Clears the effective routing key
116+ * the runtime would match (not a normalized sibling). The explicit `undefined`
117+ * is what makes updateSettingsForSource delete the key on merge. Pure.
69118 */
70- export function computeClearRouteUpdate ( agentType : string ) : SettingsJson {
71- return { agentRouting : { [ agentType ] : undefined } } as unknown as SettingsJson
119+ export function computeClearRouteUpdate (
120+ settings : SettingsJson | null ,
121+ agentType : string ,
122+ ) : SettingsJson {
123+ const routingKey = findOwnRouteKey ( settings ?. agentRouting , agentType ) ?? agentType
124+ return { agentRouting : { [ routingKey ] : undefined } } as unknown as SettingsJson
72125}
73126
74127/** Human-readable one-line route summary for the AgentDetail view. Pure. */
75128export function describeRouteLine ( current : CurrentAgentRoute ) : string {
129+ const viaDefault = current . kind !== 'none' && current . viaDefault ? ' (via default)' : ''
76130 switch ( current . kind ) {
77131 case 'none' :
78132 return 'Route: inherits parent model'
79133 case 'model-only' :
80- return `Route: ${ current . model } (current provider)`
134+ return `Route: ${ current . model } (current provider)${ viaDefault } `
81135 case 'cross-provider' :
82- return `Route: ${ current . model } (cross-provider)`
136+ return `Route: ${ current . model } (cross-provider)${ viaDefault } `
83137 case 'dangling' :
84- return `Route: ${ current . routeKey } (unconfigured, inherits)`
138+ return `Route: ${ current . routeKey } (unconfigured, inherits)${ viaDefault } `
85139 }
86140}
87141
@@ -111,7 +165,11 @@ export function buildRouteOptions(
111165 }
112166 } )
113167
114- if ( current . kind !== 'none' ) {
168+ // Only offer "clear" when the agent has its OWN routing key. A route inherited
169+ // via `default` has nothing agent-specific to remove, and clearing wouldn't
170+ // make it inherit the parent (default would still apply), so the option would
171+ // claim a change the runtime ignores.
172+ if ( current . kind !== 'none' && ! current . viaDefault ) {
115173 modelOptions . push ( {
116174 value : CLEAR_ROUTE_VALUE ,
117175 label : 'Clear route (inherit from parent)' ,
@@ -139,5 +197,8 @@ export function setAgentRoute(
139197
140198/** Remove `agentType`'s route in user-global settings. */
141199export function clearAgentRoute ( agentType : string ) : { error : Error | null } {
142- return updateSettingsForSource ( 'userSettings' , computeClearRouteUpdate ( agentType ) )
200+ return updateSettingsForSource (
201+ 'userSettings' ,
202+ computeClearRouteUpdate ( getSettingsForSource ( 'userSettings' ) , agentType ) ,
203+ )
143204}
0 commit comments