"""Apply HermesFace patches to upstream ``agent/auxiliary_client.py``.""" from __future__ import annotations from pathlib import Path from sync_hf import _replace_exact_once, _replace_required_once _PATCH_MARKER = "# HermesFace: configured fallback respects unhealthy cache" def _patch_payment_fallback_block( code: str, *, log_prefix: str, label: str, ) -> tuple[str, bool]: old = ( f"{log_prefix}\n" " task or \"call\", reason, resolved_provider, first_err)\n" "\n" " # Fallback order (#26882, #26803):\n" " # 1. User-configured fallback_chain (per-task) if set\n" " # 2. For auto: top-level main fallback_providers/fallback_model\n" " # 3. For auto: built-in auxiliary discovery chain\n" " # 4. For explicit aux providers: main agent model safety net\n" " fb_client, fb_model, fb_label = (None, None, \"\")\n" " if is_auto:\n" " fb_client, fb_model, fb_label = _try_configured_fallback_chain(\n" " task, resolved_provider or \"auto\", reason=reason)\n" " if fb_client is None:\n" " fb_client, fb_model, fb_label = _try_main_fallback_chain(\n" " task, resolved_provider or \"auto\", reason=reason)\n" " if fb_client is None:\n" " fb_client, fb_model, fb_label = _try_payment_fallback(\n" " resolved_provider, task, reason=reason)\n" " else:\n" " fb_client, fb_model, fb_label = _try_configured_fallback_chain(\n" " task, resolved_provider or \"auto\", reason=reason)\n" ) new = ( f"{log_prefix}\n" " task or \"call\", reason, resolved_provider, first_err)\n" "\n" " # Fallback order (#26882, #26803):\n" " # 1. User-configured fallback_chain (per-task) if set\n" " # 2. For auto: top-level main fallback_providers/fallback_model\n" " # 3. For auto: built-in auxiliary discovery chain\n" " # 4. For explicit aux providers: main agent model safety net\n" " fb_client, fb_model, fb_label = (None, None, \"\")\n" " _failed_chain_provider = (\n" " _recoverable_pool_provider(resolved_provider, client, main_runtime=main_runtime)\n" " or resolved_provider\n" " or \"auto\"\n" " )\n" " if is_auto:\n" " fb_client, fb_model, fb_label = _try_configured_fallback_chain(\n" " task, _failed_chain_provider, reason=reason)\n" " if fb_client is None:\n" " fb_client, fb_model, fb_label = _try_main_fallback_chain(\n" " task, _failed_chain_provider, reason=reason)\n" " if fb_client is None:\n" " fb_client, fb_model, fb_label = _try_payment_fallback(\n" " resolved_provider, task, reason=reason)\n" " else:\n" " fb_client, fb_model, fb_label = _try_configured_fallback_chain(\n" " task, _failed_chain_provider, reason=reason)\n" ) return _replace_exact_once(code, old, new, label=label) def apply_auxiliary_client_fallback_patch(aux_path: Path) -> list[str]: """Patch fallback selection to honor unhealthy providers and concrete failures.""" if not aux_path.is_file(): raise RuntimeError(f"required upstream auxiliary client missing: {aux_path}") code = aux_path.read_text(encoding="utf-8") if _PATCH_MARKER in code: return ["auxiliary_client fallback patch already applied"] changes: list[str] = [] unhealthy_block = ( ' label = f"fallback_chain[{i}]({fb_provider})"\n\n' " try:\n" ) unhealthy_replacement = ( ' label = f"fallback_chain[{i}]({fb_provider})"\n' f" {_PATCH_MARKER}\n" " fb_norm = fb_provider.lower()\n" " if _is_provider_unhealthy(fb_norm):\n" " _log_skip_unhealthy(fb_norm, task)\n" ' tried.append(f"{label} (unhealthy)")\n' " continue\n\n" " try:\n" ) code = _replace_required_once( code, unhealthy_block, unhealthy_replacement, label="auxiliary configured fallback unhealthy skip", ) changes.append("configured fallback_chain skips unhealthy providers") code, changed = _patch_payment_fallback_block( code, log_prefix=' logger.info("Auxiliary %s: %s on %s (%s), trying fallback",', label="auxiliary sync payment fallback failed-provider label", ) if changed: changes.append("sync payment fallback passes concrete failed provider") code, changed = _patch_payment_fallback_block( code, log_prefix=' logger.info("Auxiliary %s (async): %s on %s (%s), trying fallback",', label="auxiliary async payment fallback failed-provider label", ) if changed: changes.append("async payment fallback passes concrete failed provider") openrouter_guard_old = ( ' or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY")\n' " if not or_key:\n" ) openrouter_guard_new = ( ' or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY")\n' " chosen_model = model or _OPENROUTER_MODEL\n" ' if chosen_model and ":free" not in str(chosen_model).lower():\n' " logger.error(\n" ' "Auxiliary OpenRouter call refused: model %r lacks :free suffix "\n' ' "(set OPENROUTER_ANALYSIS_MODEL to a :free model)",\n' " chosen_model,\n" " )\n" " return None, None\n" " if not or_key:\n" ) code = _replace_required_once( code, openrouter_guard_old, openrouter_guard_new, label="auxiliary OpenRouter :free guard", ) changes.append("OpenRouter auxiliary calls require :free model suffix") resolve_openrouter_old = ( " if provider == \"openrouter\":\n" " client, default = _try_openrouter(explicit_api_key=explicit_api_key)\n" ) resolve_openrouter_new = ( " if provider == \"openrouter\":\n" " chosen_model = model or _OPENROUTER_MODEL\n" ' if chosen_model and ":free" not in str(chosen_model).lower():\n' " logger.error(\n" ' "resolve_provider_client: refusing paid OpenRouter model %r "\n' ' "(model id must include :free)",\n' " chosen_model,\n" " )\n" " return None, None\n" " client, default = _try_openrouter(explicit_api_key=explicit_api_key, model=model)\n" ) code = _replace_required_once( code, resolve_openrouter_old, resolve_openrouter_new, label="resolve_provider_client OpenRouter :free guard", ) changes.append("resolve_provider_client refuses paid OpenRouter models") aux_path.write_text(code, encoding="utf-8") return changes