@@ -38,9 +38,10 @@ type AgentInstance struct {
3838 SkillsFilter []string
3939 IsLocalModel bool
4040 PurposePrompt string
41- Candidates []providers.FallbackCandidate
42- Summarization config.SummarizationConfig
43- ThinkingBudget int
41+ Candidates []providers.FallbackCandidate
42+ CandidateProviders map [string ]providers.LLMProvider // "provider/model" → provider
43+ Summarization config.SummarizationConfig
44+ ThinkingBudget int
4445}
4546
4647// NewAgentInstance creates an agent instance from config.
@@ -170,13 +171,38 @@ func NewAgentInstance(
170171 temperature = * defaults .Temperature
171172 }
172173
173- // Resolve fallback candidates
174+ // Resolve fallback candidates.
175+ // Use full "protocol/model" strings (not aliases) so ResolveCandidates
176+ // can extract the provider from the model string.
174177 modelCfg := providers.ModelConfig {
175- Primary : model ,
176- Fallbacks : fallbacks ,
178+ Primary : resolveModelFullString ( model , cfg ) ,
179+ Fallbacks : resolveModelFullStrings ( fallbacks , cfg ) ,
177180 }
178181 candidates := providers .ResolveCandidates (modelCfg , defaults .Provider )
179182
183+ // Build per-candidate providers so the fallback chain can switch between
184+ // different API endpoints (e.g. Ollama Cloud primary → OpenRouter fallback).
185+ candidateProviders := make (map [string ]providers.LLMProvider )
186+ for _ , c := range candidates {
187+ key := providers .ModelKey (c .Provider , c .Model )
188+ fullModel := c .Provider + "/" + c .Model
189+ mc := findModelConfigByModel (cfg , fullModel )
190+ if mc == nil {
191+ // Try lookup by alias as fallback
192+ if found , err := cfg .GetModelConfig (c .Model ); err == nil {
193+ mc = found
194+ }
195+ }
196+ if mc != nil {
197+ if mc .Workspace == "" {
198+ mc .Workspace = cfg .WorkspacePath ()
199+ }
200+ if p , _ , err := providers .CreateProviderFromConfig (mc ); err == nil && p != nil {
201+ candidateProviders [key ] = p
202+ }
203+ }
204+ }
205+
180206 // If this agent has a custom model that differs from the default, create a
181207 // per-agent provider from its model config. This allows different agents to
182208 // use different API keys or providers without sharing the global provider.
@@ -251,15 +277,54 @@ func NewAgentInstance(
251277 SkillsFilter : skillsFilter ,
252278 IsLocalModel : isLocal ,
253279 PurposePrompt : contextBuilder .purposeInstructions ,
254- Candidates : candidates ,
255- Summarization : summarization ,
256- ThinkingBudget : thinkingBudget ,
280+ Candidates : candidates ,
281+ CandidateProviders : candidateProviders ,
282+ Summarization : summarization ,
283+ ThinkingBudget : thinkingBudget ,
257284 }
258285}
259286
260287// resolveAgentModelID resolves the raw model ID (without protocol prefix) for a given alias.
261288// It looks up the alias in cfg.ModelList; if found, it extracts the model ID from the
262289// Model field (e.g. "openai/gpt-4o" -> "gpt-4o"). Falls back to the alias itself if not found.
290+ // findModelConfigByModel searches ModelList by the Model field (protocol/model-id)
291+ // rather than the ModelName alias.
292+ func findModelConfigByModel (cfg * config.Config , model string ) * config.ModelConfig {
293+ for i := range cfg .ModelList {
294+ if cfg .ModelList [i ].Model == model {
295+ mc := cfg .ModelList [i ] // copy
296+ return & mc
297+ }
298+ }
299+ return nil
300+ }
301+
302+ // resolveModelFullString resolves a model alias to its full "protocol/model"
303+ // string from the model list. If the alias is not found, it's returned as-is
304+ // (it may already be a full model string).
305+ func resolveModelFullString (alias string , cfg * config.Config ) string {
306+ if alias == "" {
307+ return alias
308+ }
309+ mc , err := cfg .GetModelConfig (alias )
310+ if err == nil && mc != nil && mc .Model != "" {
311+ return mc .Model
312+ }
313+ return alias
314+ }
315+
316+ // resolveModelFullStrings resolves a slice of model aliases.
317+ func resolveModelFullStrings (aliases []string , cfg * config.Config ) []string {
318+ if len (aliases ) == 0 {
319+ return aliases
320+ }
321+ out := make ([]string , len (aliases ))
322+ for i , a := range aliases {
323+ out [i ] = resolveModelFullString (a , cfg )
324+ }
325+ return out
326+ }
327+
263328func resolveAgentModelID (alias string , cfg * config.Config ) string {
264329 if alias == "" {
265330 return ""
0 commit comments