File size: 7,679 Bytes
cebe6c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""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