@@ -2392,6 +2392,9 @@ def set_hermes_default_model(model_id: str) -> dict:
23922392 "mcp" ,
23932393 "title_generation" ,
23942394 "curator" ,
2395+ "kanban_decomposer" ,
2396+ "profile_describer" ,
2397+ "triage_specifier" ,
23952398)
23962399
23972400
@@ -2428,6 +2431,11 @@ def get_auxiliary_models() -> dict:
24282431 "provider" : str (entry .get ("provider" ) or "auto" ).strip (),
24292432 "model" : str (entry .get ("model" ) or "" ).strip (),
24302433 "base_url" : str (entry .get ("base_url" ) or "" ).strip (),
2434+ "timeout" : entry .get ("timeout" , "" ),
2435+ "download_timeout" : entry .get ("download_timeout" , "" ),
2436+ "max_concurrency" : entry .get ("max_concurrency" , "" ),
2437+ "extra_body" : entry .get ("extra_body" ) if isinstance (entry .get ("extra_body" ), dict ) else {},
2438+ "api_key_set" : bool (str (entry .get ("api_key" ) or "" ).strip ()),
24312439 })
24322440
24332441 return {
@@ -2436,10 +2444,29 @@ def get_auxiliary_models() -> dict:
24362444 }
24372445
24382446
2439- def set_auxiliary_model (task : str , provider : str , model : str ) -> dict :
2447+ def _coerce_optional_positive_int (value , field : str ):
2448+ if value is None :
2449+ return None
2450+ if isinstance (value , str ):
2451+ value = value .strip ()
2452+ if value == "" :
2453+ return ""
2454+ try :
2455+ number = int (value )
2456+ except (TypeError , ValueError ) as exc :
2457+ raise ValueError (f"{ field } must be a positive integer" ) from exc
2458+ if number < 1 :
2459+ raise ValueError (f"{ field } must be a positive integer" )
2460+ return number
2461+
2462+
2463+ def set_auxiliary_model (task : str , provider : str , model : str , advanced : dict | None = None ) -> dict :
24402464 """Persist an auxiliary model assignment in config.yaml.
24412465
24422466 Special case: task='__reset__' clears all auxiliary slots.
2467+ ``advanced`` may update per-slot fields surfaced behind the WebUI gear menu.
2468+ Sensitive api_key values are write-only: get_auxiliary_models() only reports
2469+ whether one is set.
24432470 """
24442471 if task != "__reset__" and task not in AUX_TASK_SLOTS :
24452472 raise ValueError (
@@ -2472,6 +2499,41 @@ def set_auxiliary_model(task: str, provider: str, model: str) -> dict:
24722499 slot_cfg = {}
24732500 slot_cfg ["provider" ] = provider or "auto"
24742501 slot_cfg ["model" ] = model or ""
2502+ if advanced is not None :
2503+ if not isinstance (advanced , dict ):
2504+ raise ValueError ("advanced auxiliary options must be an object" )
2505+ if "base_url" in advanced :
2506+ base_url = str (advanced .get ("base_url" ) or "" ).strip ().rstrip ("/" )
2507+ if base_url :
2508+ slot_cfg ["base_url" ] = base_url
2509+ else :
2510+ slot_cfg .pop ("base_url" , None )
2511+ for field in ("timeout" , "download_timeout" , "max_concurrency" ):
2512+ if field in advanced :
2513+ coerced = _coerce_optional_positive_int (advanced .get (field ), field )
2514+ if coerced == "" :
2515+ slot_cfg .pop (field , None )
2516+ elif coerced is not None :
2517+ slot_cfg [field ] = coerced
2518+ if "extra_body" in advanced :
2519+ extra_body = advanced .get ("extra_body" )
2520+ if isinstance (extra_body , str ):
2521+ text = extra_body .strip ()
2522+ extra_body = json .loads (text ) if text else {}
2523+ if extra_body in (None , "" ):
2524+ slot_cfg .pop ("extra_body" , None )
2525+ elif isinstance (extra_body , dict ):
2526+ if extra_body :
2527+ slot_cfg ["extra_body" ] = extra_body
2528+ else :
2529+ slot_cfg .pop ("extra_body" , None )
2530+ else :
2531+ raise ValueError ("extra_body must be a JSON object" )
2532+ if advanced .get ("api_key_clear" ):
2533+ slot_cfg ["api_key" ] = ""
2534+ api_key = str (advanced .get ("api_key" ) or "" ).strip ()
2535+ if api_key :
2536+ slot_cfg ["api_key" ] = api_key
24752537 if provider and (provider .startswith ("custom:" ) or provider == "custom" ):
24762538 try :
24772539 _ , _ , resolved_base_url = resolve_model_provider (model )
0 commit comments