@@ -313,6 +313,26 @@ func (c *coordinator) run(ctx context.Context, accept *AcceptedRun, sessionID st
313 return result, originalErr
313 return result, originalErr
316+// effectiveReasoningEffort returns the reasoning effort to apply for provider calls.
317+// It prefers the user-selected effort when valid, otherwise the model default when
318+// valid, and finally falls back to the first configured reasoning level.
319+func effectiveReasoningEffort(model Model) string {
320+ if !model.CatwalkCfg.CanReason {
324+ if effort := model.ModelCfg.ReasoningEffort; effort != "" && slices.Contains(model.CatwalkCfg.ReasoningLevels, effort) {
327+ if effort := model.CatwalkCfg.DefaultReasoningEffort; effort != "" && slices.Contains(model.CatwalkCfg.ReasoningLevels, effort) {
330+ if len(model.CatwalkCfg.ReasoningLevels) > 0 {
331+ return model.CatwalkCfg.ReasoningLevels[0]
316func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.ProviderOptions {
336func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.ProviderOptions {
317 options := fantasy.ProviderOptions{}
337 options := fantasy.ProviderOptions{}
@@ -361,14 +381,16 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
361 return options
381 return options
384+ reasoningEffort := effectiveReasoningEffort(model)
364 shouldSetEffort := model.CatwalkCfg.CanReason &&
385 shouldSetEffort := model.CatwalkCfg.CanReason &&
386+ reasoningEffort != "" &&
365− slices.Contains(model.CatwalkCfg.ReasoningLevels, model.ModelCfg.ReasoningEffort)
387+ slices.Contains(model.CatwalkCfg.ReasoningLevels, reasoningEffort)
367 switch providerCfg.Type {
389 switch providerCfg.Type {
368 case openai.Name, azure.Name:
390 case openai.Name, azure.Name:
369 _, hasReasoningEffort := mergedOptions["reasoning_effort"]
391 _, hasReasoningEffort := mergedOptions["reasoning_effort"]
370 if !hasReasoningEffort && shouldSetEffort {
392 if !hasReasoningEffort && shouldSetEffort {
371− mergedOptions["reasoning_effort"] = model.ModelCfg.ReasoningEffort
393+ mergedOptions["reasoning_effort"] = reasoningEffort
373 if openai.IsResponsesModel(model.CatwalkCfg.ID) {
395 if openai.IsResponsesModel(model.CatwalkCfg.ID) {
374 if openai.IsResponsesReasoningModel(model.CatwalkCfg.ID) {
396 if openai.IsResponsesReasoningModel(model.CatwalkCfg.ID) {
@@ -396,7 +418,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
396 case string(catwalk.InferenceProviderAlibabaSingapore):
418 case string(catwalk.InferenceProviderAlibabaSingapore):
398 case !hasEffort && shouldSetEffort:
420 case !hasEffort && shouldSetEffort:
399− extraBody["reasoning_effort"] = model.ModelCfg.ReasoningEffort
421+ extraBody["reasoning_effort"] = reasoningEffort
400 case !hasThink && model.CatwalkCfg.CanReason:
422 case !hasThink && model.CatwalkCfg.CanReason:
401 if model.ModelCfg.Think {
423 if model.ModelCfg.Think {
402 extraBody["thinking"] = map[string]any{"type": "enabled"}
424 extraBody["thinking"] = map[string]any{"type": "enabled"}
@@ -409,7 +431,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
411 case !hasEffort && shouldSetEffort:
433 case !hasEffort && shouldSetEffort:
412− mergedOptions["effort"] = model.ModelCfg.ReasoningEffort
434+ mergedOptions["effort"] = reasoningEffort
413 case !hasThink && model.ModelCfg.Think:
435 case !hasThink && model.ModelCfg.Think:
414 mergedOptions["thinking"] = map[string]any{"budget_tokens": 2000}
436 mergedOptions["thinking"] = map[string]any{"budget_tokens": 2000}
@@ -425,7 +447,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
425 if !hasReasoning && shouldSetEffort {
447 if !hasReasoning && shouldSetEffort {
426 mergedOptions["reasoning"] = map[string]any{
448 mergedOptions["reasoning"] = map[string]any{
427 "enabled": true,
449 "enabled": true,
428− "effort": model.ModelCfg.ReasoningEffort,
450+ "effort": reasoningEffort,
431 parsed, err := openrouter.ParseOptions(mergedOptions)
453 parsed, err := openrouter.ParseOptions(mergedOptions)
@@ -437,7 +459,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
437 if !hasReasoning && shouldSetEffort {
459 if !hasReasoning && shouldSetEffort {
438 mergedOptions["reasoning"] = map[string]any{
460 mergedOptions["reasoning"] = map[string]any{
439 "enabled": true,
461 "enabled": true,
440− "effort": model.ModelCfg.ReasoningEffort,
462+ "effort": reasoningEffort,
443 parsed, err := vercel.ParseOptions(mergedOptions)
465 parsed, err := vercel.ParseOptions(mergedOptions)
@@ -454,7 +476,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
456 mergedOptions["thinking_config"] = map[string]any{
478 mergedOptions["thinking_config"] = map[string]any{
457− "thinking_level": model.ModelCfg.ReasoningEffort,
479+ "thinking_level": reasoningEffort,
458 "include_thoughts": true,
480 "include_thoughts": true,
@@ -470,9 +492,9 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
470 if !hasReasoningEffort && shouldSetEffort {
492 if !hasReasoningEffort && shouldSetEffort {
471 switch providerCfg.ID {
493 switch providerCfg.ID {
472 case string(catwalk.InferenceProviderIoNet):
494 case string(catwalk.InferenceProviderIoNet):
473− extraBody["reasoning"] = map[string]string{"effort": model.ModelCfg.ReasoningEffort}
495+ extraBody["reasoning"] = map[string]string{"effort": reasoningEffort}
475− mergedOptions["reasoning_effort"] = model.ModelCfg.ReasoningEffort
497+ mergedOptions["reasoning_effort"] = reasoningEffort
@@ -492,7 +514,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
494 case string(catwalk.InferenceProviderZAI), string(catwalk.InferenceProviderDeepSeek):
516 case string(catwalk.InferenceProviderZAI), string(catwalk.InferenceProviderDeepSeek):
495− if model.ModelCfg.Think || model.ModelCfg.ReasoningEffort != "" {
517+ if model.ModelCfg.Think || reasoningEffort != "" {
496 extraBody["thinking"] = map[string]any{
518 extraBody["thinking"] = map[string]any{
497 "type": "enabled",
519 "type": "enabled",
@@ -503,7 +525,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
504 case string(catwalk.InferenceProviderFireworks):
526 case string(catwalk.InferenceProviderFireworks):
505 // NOTE: Fireworks break if we set both `reasoning_effort` and `thinking`.
527 // NOTE: Fireworks break if we set both `reasoning_effort` and `thinking`.
506− if model.ModelCfg.ReasoningEffort == "" {
528+ if reasoningEffort == "" {
507 if model.ModelCfg.Think {
529 if model.ModelCfg.Think {
508 extraBody["thinking"] = map[string]any{"type": "enabled"}
530 extraBody["thinking"] = map[string]any{"type": "enabled"}