§
    øžyj›T  ã                  óz  — U d Z ddlmZ ddlZddlmZ i dd“dd“d	d“d
d“dd“dd“dd“dd“dd“dd“dd“dd“dd“dd“dd“dd“dd“d dd!œ¥Zd"ed#<    eh d$£¦  «        Z	d%ed&<    edh¦  «        Z
d%ed'<    eh d(£¦  «        Zd%ed)<    ed*h¦  «        Zd%ed+<    eh d,£¦  «        Zd%ed-<    edh¦  «        Zd%ed.<    edh¦  «        Zd%ed/<    eh d0£¦  «        Zd%ed1<    ed2d3h¦  «        Zd%ed4<    ed5d6h¦  «        Zd%ed7<    ej        d8¦  «        ZdKd<„ZdKd=„ZdKd>„ZdLd@„ZdMdB„ZdNdD„ZdKdE„ZdOdG„ZdPdH„ZdQdJ„ZdS )Ruˆ  Per-provider model name normalization.

Different LLM providers expect model identifiers in different formats:

- **Aggregators** (OpenRouter, Nous, AI Gateway, Kilo Code) need
  ``vendor/model`` slugs like ``anthropic/claude-sonnet-4.6``.
- **Anthropic** native API expects bare names with dots replaced by
  hyphens: ``claude-sonnet-4-6``.
- **Copilot** expects bare names *with* dots preserved:
  ``claude-sonnet-4.6``.
- **OpenCode Zen** preserves dots for GPT/GLM/Gemini/Kimi/MiniMax-style
  model IDs, but Claude still uses hyphenated native names like
  ``claude-sonnet-4-6``.
- **OpenCode Go** preserves dots in model names: ``minimax-m2.7``.
- **DeepSeek** accepts only the first-class V-series IDs
  (``deepseek-v4-pro``, ``deepseek-v4-flash``, and any future
  ``deepseek-v<N>-*``).  The legacy aliases ``deepseek-chat`` and
  ``deepseek-reasoner`` were retired on 2026-07-24 and are remapped to
  ``deepseek-v4-flash`` (official non-thinking / thinking shims).  Older
  Hermes revisions folded every non-reasoner input into
  ``deepseek-chat``, which on aggregators routes to V3 â€” so a user
  picking V4 Pro was silently downgraded.
- **Custom** and remaining providers pass the name through as-is.

This module centralises that translation so callers can simply write::

    api_model = normalize_model_for_provider(user_input, provider)

Inspired by Clawdbot's ``normalizeAnthropicModelId`` pattern.
é    )ÚannotationsN)ÚOptionalÚclaudeÚ	anthropicÚgptÚopenaiÚo1Úo3Úo4ÚgeminiÚgoogleÚgemmaÚdeepseekÚglmzz-aiÚkimiÚ
moonshotaiÚminimaxÚgrokzx-aiÚqwenÚmimoÚxiaomiÚtrinityzarcee-aiÚnemotronÚnvidiaÚllamaz
meta-llamaÚstepfun)Ústepr   zdict[str, str]Ú_VENDOR_PREFIXES>   ú
ai-gatewayÚnousÚkilocodeÚ
openrouterzfrozenset[str]Ú_AGGREGATOR_PROVIDERSÚ_DOT_TO_HYPHEN_PROVIDERS>   úopenai-codexÚcopilotúcopilot-acpÚ_STRIP_VENDOR_ONLY_PROVIDERSÚhuggingfaceÚ_AUTHORITATIVE_NATIVE_PROVIDERS>   ú
minimax-cnú
qwen-oauthúkimi-codingúollama-cloudúminimax-oauthúkimi-coding-cnÚxaiÚzaiÚarceeÚcustomr   r   Úalibabar   Ú _MATCHING_PREFIX_STRIP_PROVIDERSÚ"_CATALOGUE_PREFIX_REPAIR_PROVIDERSÚ_LOWERCASE_MODEL_PROVIDERS>   Úr1ÚcotÚthinkÚreasonerÚ	reasoningÚ_DEEPSEEK_REASONER_KEYWORDSzdeepseek-chatzdeepseek-reasonerÚ_DEEPSEEK_RETIRED_ALIASESzdeepseek-v4-proúdeepseek-v4-flashÚ_DEEPSEEK_CANONICAL_MODELSz^deepseek-v\d+([-.].+)?$Ú
model_nameÚstrÚreturnc                óÎ   — t          | ¦  «                             ¦   «         }|t          v rdS |t          v r|S t                               |¦  «        r|S t          D ]	}||v r dS Œ
dS )aà  Map a model input to a DeepSeek-accepted identifier.

    Rules:
    - Retired aliases ``deepseek-chat`` / ``deepseek-reasoner`` (cut off
      2026-07-24) -> ``deepseek-v4-flash``.
    - Already a known canonical (``deepseek-v4-pro``/``deepseek-v4-flash``)
      -> pass through.
    - Matches the V-series pattern ``deepseek-v<digit>...`` -> pass through
      (covers future ``deepseek-v5-*`` and dated variants without a release).
    - Contains a reasoner keyword (r1, think, reasoning, cot, reasoner)
      -> ``deepseek-v4-flash``.
    - Everything else -> ``deepseek-v4-flash``.

    Args:
        model_name: The bare model name (vendor prefix already stripped).

    Returns:
        A DeepSeek-accepted model identifier.
    r@   )Ú_strip_vendor_prefixÚlowerr?   rA   Ú_DEEPSEEK_V_SERIES_REÚmatchr>   )rB   ÚbareÚkeywords      ú@/home/ragecks/.hermes/hermes-agent/hermes_cli/model_normalize.pyÚ_normalize_for_deepseekrM   «   s�   € õ(   
Ñ+Ô+×1Ò1Ñ3Ô3€Dð Õ(Ð(Ð(Ø"Ð"àÕ)Ð)Ð)Øˆõ ×"Ò" 4Ñ(Ô(ð Øˆõ /ð 'ð 'ˆØ�dˆ?ˆ?Ø&Ð&Ð&ð ð Ðó    c                óF   — d| v r|                       dd¦  «        d         S | S )a@  Remove a ``vendor/`` prefix if present.

    Examples::

        >>> _strip_vendor_prefix("anthropic/claude-sonnet-4.6")
        'claude-sonnet-4.6'
        >>> _strip_vendor_prefix("claude-sonnet-4.6")
        'claude-sonnet-4.6'
        >>> _strip_vendor_prefix("meta-llama/llama-4-scout")
        'llama-4-scout'
    ú/é   )Úsplit©rB   s    rL   rF   rF   Ù   s0   € ð ˆjÐÐØ×Ò  QÑ'Ô'¨Ô*Ð*ØÐrN   c                ó.   — |                       dd¦  «        S )z¬Replace dots with hyphens in a model name.

    Anthropic's native API uses hyphens where marketing names use dots:
    ``claude-sonnet-4.6`` -> ``claude-sonnet-4-6``.
    ú.Ú-)ÚreplacerS   s    rL   Ú_dots_to_hyphensrX   ê   s   € ð ×Ò˜c 3Ñ'Ô'Ð'rN   Úprovider_namec                ó¢   — | pd                      ¦   «                              ¦   «         }|s|S 	 ddlm}  ||¦  «        S # t          $ r |cY S w xY w)z2Resolve provider aliases to Hermes' canonical ids.Ú r   )Únormalize_provider)ÚstriprG   Úhermes_cli.modelsr\   Ú	Exception)rY   Úrawr\   s      rL   Ú_normalize_provider_aliasra   ó   s}   € àÐ˜B×
%Ò
%Ñ
'Ô
'×
-Ò
-Ñ
/Ô
/€CØð Øˆ
ðØ8Ð8Ð8Ð8Ð8Ð8à!Ð! #Ñ&Ô&Ð&øÝð ð ð Øˆ
ˆ
ˆ
ðøøøs   ®? ¿AÁAÚtarget_providerc                ó˜  — d| vr| S |                       dd¦  «        \  }}|                     ¦   «         r|                     ¦   «         s| S t          |¦  «        }|dk    r@|                     ¦   «                              ¦   «         dk    r|                     ¦   «         S | S t          |¦  «        }|r||k    r|                     ¦   «         S | S )a
  Strip ``provider/`` only when the prefix matches the target provider.

    This prevents arbitrary slash-bearing model IDs from being mangled on
    native providers while still repairing manual config values like
    ``zai/glm-5.1`` for the ``zai`` provider.

    ``custom`` is a generic bucket for arbitrary user-defined endpoints, not
    a vendor identity like ``zai``/``gemini``/``xai``. An alias that merely
    *resolves to* ``custom`` (e.g. ``ollama``, via ``_PROVIDER_ALIASES``)
    does not mean a ``ollama/`` prefix is redundant -- it may be the actual
    routing prefix a proxy in front of the custom endpoint (e.g. LiteLLM)
    requires, as in ``ollama/glm-5.2``. Only a literal ``custom/`` prefix --
    the bucket's own name -- is treated as redundant here.
    rP   rQ   r4   )rR   r]   ra   rG   )rB   rb   ÚprefixÚ	remainderÚnormalized_targetÚnormalized_prefixs         rL   Ú_strip_matching_provider_prefixrh      sÜ   € ð �*ÐÐØÐà"×(Ò(¨¨aÑ0Ô0Ñ€FˆIØ�<Š<‰>Œ>ð  §¢Ñ!2Ô!2ð ØÐå1°/ÑBÔBÐØ˜HÒ$Ð$Ø�<Š<‰>Œ>×ÒÑ!Ô! XÒ-Ð-Ø—?’?Ñ$Ô$Ð$ØÐå1°&Ñ9Ô9ÐØð !Ð.Ð2CÒCÐCØ�ŠÑ Ô Ð ØÐrN   úOptional[str]c                ó˜  — |                       ¦   «         }|sdS d|v r0|                     dd¦  «        d                              ¦   «         pdS |                     ¦   «         }|                     d¦  «        d         }|t          v rt          |         S t                               ¦   «         D ]\  }}|                     |¦  «        r|c S ŒdS )a*  Detect the vendor slug from a bare model name.

    Uses the first hyphen-delimited token of the model name to look up
    the corresponding vendor in ``_VENDOR_PREFIXES``.  Also handles
    case-insensitive matching and special patterns.

    Args:
        model_name: A model name, optionally already including a
            ``vendor/`` prefix.  If a prefix is present it is used
            directly.

    Returns:
        The vendor slug (e.g. ``"anthropic"``, ``"openai"``) or ``None``
        if no vendor can be confidently detected.

    Examples::

        >>> detect_vendor("claude-sonnet-4.6")
        'anthropic'
        >>> detect_vendor("gpt-5.4-mini")
        'openai'
        >>> detect_vendor("anthropic/claude-sonnet-4.6")
        'anthropic'
        >>> detect_vendor("my-custom-model")
    NrP   rQ   r   rV   )r]   rR   rG   r   ÚitemsÚ
startswith)rB   ÚnameÚ
name_lowerÚfirst_tokenrd   Úvendors         rL   Údetect_vendorrq   "  sá   € ð4 ×ÒÑÔ€DØð Øˆtð ˆd€{€{Ø�zŠz˜#˜qÑ!Ô! !Ô$×*Ò*Ñ,Ô,Ð4°Ð4à—’‘”€Jð ×"Ò" 3Ñ'Ô'¨Ô*€KØÕ&Ð&Ð&Ý Ô,Ð,õ +×0Ò0Ñ2Ô2ð ð ‰ˆ�Ø× Ò  Ñ(Ô(ð 	ØˆMˆMˆMð	ð ˆ4rN   c                óB   — d| v r| S t          | ¦  «        }|r|› d| › �S | S )aN  Prepend the detected ``vendor/`` prefix if missing.

    Used for aggregator providers that require ``vendor/model`` format.
    If the name already contains a ``/``, it is returned as-is.
    If no vendor can be detected, the name is returned unchanged
    (aggregators may still accept it or return an error).

    Examples::

        >>> _prepend_vendor("claude-sonnet-4.6")
        'anthropic/claude-sonnet-4.6'
        >>> _prepend_vendor("anthropic/claude-sonnet-4.6")
        'anthropic/claude-sonnet-4.6'
        >>> _prepend_vendor("my-custom-thing")
        'my-custom-thing'
    rP   )rq   )rB   rp   s     rL   Ú_prepend_vendorrs   T  sD   € ð" ˆjÐÐØÐå˜:Ñ&Ô&€FØð (ØÐ'Ð'˜:Ð'Ð'Ð'ØÐrN   Úproviderc                ó,  ‡— d| v r| S 	 ddl m} n# t          $ r | cY S w xY w|                     |¦  «        pg }|                      ¦   «                              ¦   «         Šˆfd„|D ¦   «         }t          |¦  «        dk    r|                     ¦   «         S | S )uŽ  Restore a dropped ``vendor/`` prefix using the provider's catalogue.

    Unlike :func:`_prepend_vendor`, this never guesses from the model's name
    shape â€” it only repairs a bare id that matches **exactly one** curated
    entry for this provider modulo the prefix. That keeps self-hosted models
    behind the same provider id (local NIM containers, proxies) untouched,
    since they aren't in the catalogue.

    Examples::

        >>> _repair_prefix_from_catalogue("nemotron-3-ultra-550b-a55b", "nvidia")
        'nvidia/nemotron-3-ultra-550b-a55b'
        >>> _repair_prefix_from_catalogue("my-local-nim", "nvidia")
        'my-local-nim'
    rP   r   )Ú_PROVIDER_MODELSc                ó¤   •— h | ]L}d |v ¯|                      d d¦  «        d                              ¦   «                              ¦   «         ‰k    ¯J|’ŒMS )rP   rQ   )rR   r]   rG   )Ú.0ÚentryÚneedles     €rL   ú	<setcomp>z0_repair_prefix_from_catalogue.<locals>.<setcomp>‰  sa   ø€ ð ð ð àØ�%ˆ<ˆ<˜EŸKšK¨¨QÑ/Ô/°Ô2×8Ò8Ñ:Ô:×@Ò@ÑBÔBÀfÒLÐLð 	àLÐLÐLrN   rQ   )r^   rv   r_   Úgetr]   rG   ÚlenÚpop)rB   rt   rv   Ú	catalogueÚmatchesrz   s        @rL   Ú_repair_prefix_from_cataloguer�   n  sß   ø€ ð  ˆjÐÐØÐðØ6Ð6Ð6Ð6Ð6Ð6Ð6øÝð ð ð ØÐÐÐðøøøð !×$Ò$ XÑ.Ô.Ð4°"€Ið ×ÒÑÔ×%Ò%Ñ'Ô'€Fðð ð ð àðñ ô €Gõ
 ˆ7�|„|�qÒÐØ�{Š{‰}Œ}ÐØÐs   ‰ �žc                ó¶   — |pd                      ¦   «         }|rd|v rdS 	 t          | ¦  «        }n# t          $ r Y dS w xY wt          ||¦  «        }||k    r|ndS )u´  Return the prefixed catalogue id for a bare *model_name*, if unambiguous.

    The diagnostic counterpart to :func:`_repair_prefix_from_catalogue`: used
    to explain a provider's content-free 404 when the configured id lost its
    ``vendor/`` prefix. Returns ``None`` when the name already has a prefix,
    the provider has no curated catalogue, or nothing matches â€” so callers can
    stay silent rather than guess (#78796).
    r[   rP   N)r]   ra   r_   r�   )rt   rB   rm   Ú	canonicalÚrepaireds        rL   Úsuggest_prefixed_model_idr…   “  sˆ   € ð Ð˜"×#Ò#Ñ%Ô%€DØð �3˜$�;�;ØˆtðÝ-¨hÑ7Ô7ˆ	ˆ	øÝð ð ð Øˆtˆtðøøøå,¨T°9Ñ=Ô=€HØ 4Ò'Ð'ˆ8ˆ8¨TÐ1s    0 °
>½>Úmodel_inputc                óÖ  — | pd                      ¦   «         }|s|S t          |¦  «        }|t          v rt          |¦  «        S |dv rqd|v r/|                     dd¦  «        \  }}|                      ¦   «         p|}|dk    r6|                     ¦   «                              d¦  «        rt          |¦  «        S |S |t          v r%t          ||¦  «        }d|v r|S t          |¦  «        S |dv r'	 dd	l
m}  ||¦  «        }|r|S n# t          $ r Y nw xY w|t          v rIt          ||¦  «        }	|	|k    r1|                     d
¦  «        r|                     dd¦  «        d         S |	S |dk    r%t          ||¦  «        }d|v r|S t          |¦  «        S |t          v r/t          ||¦  «        }
|t           v r|
                     ¦   «         }
|
S |t"          v rt%          ||¦  «        S |t&          v r|S |S )a™  Translate a model name into the format the target provider's API expects.

    This is the primary entry point for model name normalisation.  It
    accepts any user-facing model identifier and transforms it for the
    specific provider that will receive the API call.

    Args:
        model_input: The model name as provided by the user or config.
            Can be bare (``"claude-sonnet-4.6"``), vendor-prefixed
            (``"anthropic/claude-sonnet-4.6"``), or already in native
            format (``"claude-sonnet-4-6"``).
        target_provider: The canonical Hermes provider id, e.g.
            ``"openrouter"``, ``"anthropic"``, ``"copilot"``,
            ``"deepseek"``, ``"custom"``.  Should already be normalised
            via ``hermes_cli.models.normalize_provider()``.

    Returns:
        The model identifier string that the target provider's API
        expects.

    Raises:
        No exceptions -- always returns a best-effort string.

    Examples::

        >>> normalize_model_for_provider("claude-sonnet-4.6", "openrouter")
        'anthropic/claude-sonnet-4.6'

        >>> normalize_model_for_provider("anthropic/claude-sonnet-4.6", "anthropic")
        'claude-sonnet-4-6'

        >>> normalize_model_for_provider("anthropic/claude-sonnet-4.6", "copilot")
        'claude-sonnet-4.6'

        >>> normalize_model_for_provider("openai/gpt-5.4", "copilot")
        'gpt-5.4'

        >>> normalize_model_for_provider("claude-sonnet-4.6", "opencode-zen")
        'claude-sonnet-4-6'

        >>> normalize_model_for_provider("minimax-m2.5-free", "opencode-zen")
        'minimax-m2.5-free'

        >>> normalize_model_for_provider("deepseek-v3", "deepseek")
        'deepseek-v4-flash'

        >>> normalize_model_for_provider("deepseek-r1", "deepseek")
        'deepseek-v4-flash'

        >>> normalize_model_for_provider("deepseek-reasoner", "deepseek")
        'deepseek-v4-flash'

        >>> normalize_model_for_provider("my-model", "custom")
        'my-model'

        >>> normalize_model_for_provider("claude-sonnet-4.6", "zai")
        'claude-sonnet-4.6'

        >>> normalize_model_for_provider("MiMo-V2.5-Pro", "xiaomi")
        'mimo-v2.5-pro'
    r[   >   úopencode-goúopencode-zenrP   rQ   r‰   zclaude->   r&   r'   r   )Únormalize_copilot_model_idzopenai/r   )r]   ra   r#   rs   rR   rG   rl   rX   r$   rh   r^   rŠ   r_   r(   rM   r6   r8   r7   r�   r*   )r†   rb   rm   rt   Ú_Úbare_after_slashrJ   rŠ   Ú
normalizedÚstrippedÚresults              rL   Únormalize_model_for_providerr�   «  sw  € ð| Ð˜2×$Ò$Ñ&Ô&€DØð Øˆå(¨Ñ9Ô9€Hð Õ(Ð(Ð(Ý˜tÑ$Ô$Ð$ð Ð2Ð2Ð2Ø�$ˆ;ˆ;Ø"&§*¢*¨S°!Ñ"4Ô"4ÑˆAÐØ#×)Ò)Ñ+Ô+Ð3¨tˆDØ�~Ò%Ð%¨$¯*ª*©,¬,×*AÒ*AÀ)Ñ*LÔ*LÐ%Ý# DÑ)Ô)Ð)Øˆð Õ+Ð+Ð+Ý.¨t°XÑ>Ô>ˆØ�$ˆ;ˆ;ØˆKÝ Ñ%Ô%Ð%ð Ð-Ð-Ð-ð		ØDÐDÐDÐDÐDÐDà3Ð3°DÑ9Ô9ˆJØð "Ø!Ð!ð"øåð 	ð 	ð 	ð ˆDð	øøøð Õ/Ð/Ð/Ý2°4¸ÑBÔBˆØ�tÒÐ §¢°	Ñ :Ô :Ðà—:’:˜c 1Ñ%Ô% aÔ(Ð(Øˆð �:ÒÐÝ.¨t°XÑ>Ô>ˆØ�$ˆ;ˆ;ØˆKÝ& tÑ,Ô,Ð,ð Õ3Ð3Ð3Ý0°°xÑ@Ô@ˆð Õ1Ð1Ð1Ø—\’\‘^”^ˆFØˆð
 Õ5Ð5Ð5Ý,¨T°8Ñ<Ô<Ð<ð Õ2Ð2Ð2Øˆð €Ks   Ã*D  Ä 
DÄD)rB   rC   rD   rC   )rY   rC   rD   rC   )rB   rC   rb   rC   rD   rC   )rB   rC   rD   ri   )rB   rC   rt   rC   rD   rC   )rt   rC   rB   rC   rD   ri   )r†   rC   rb   rC   rD   rC   )Ú__doc__Ú
__future__r   ÚreÚtypingr   r   Ú__annotations__Ú	frozensetr#   r$   r(   r*   r6   r7   r8   r>   r?   rA   ÚcompilerH   rM   rF   rX   ra   rh   rq   rs   r�   r…   r�   © rN   rL   ú<module>r™      s2  ððð ð ð> #Ð "Ð "Ð "Ð "Ð "à 	€	€	€	Ø Ð Ð Ð Ð Ð ð$Øˆkð$à	ˆ8ð$ð 	ˆ(ð$ð 	ˆ(ð	$ð
 	ˆ(ð$ð ˆhð$ð ˆXð$ð �
ð$ð 
ˆ6ð$ð ˆLð$ð ˆyð$ð ˆFð$ð ˆFð$ð ˆHð$ð ˆzð$ð  �ð!$ð" ˆ\ð#$ð$ Øð'$ð $ð $Ð ð ð ð ñ ð. )2¨	ð 3ð 3ð 3ñ )ô )Ð ð ð ð ñ ð ,5¨9Øð6ñ ,ô ,Ð ð ð ð ñ ð
 09¨yð :ð :ð :ñ 0ô 0Ð ð ð ð ñ ð 3<°)Øð=ñ 3ô 3Ð ð ð ð ñ ð 4=°9ð >ð >ð >ñ 4ô 4Ð  ð ð ð ñ ð4 6?°YØð@ñ 6ô 6Ð "ð ð ð ñ ð .7¨YØð8ñ .ô .Ð ð ð ð ñ ð /8¨ið 9ð 9ð 9ñ /ô /Ð ð ð ð ñ ð -6¨IØØð7ñ -ô -Ð ð ð ð ñ ð
 .7¨YØØð8ñ .ô .Ð ð ð ð ñ ð #˜œ
Ð#>Ñ?Ô?Ð ð'ð 'ð 'ð 'ð\ð ð ð ð"(ð (ð (ð (ð
ð 
ð 
ð 
ðð ð ð ðD/ð /ð /ð /ðdð ð ð ð4"ð "ð "ð "ðJ2ð 2ð 2ð 2ð0Uð Uð Uð Uð Uð UrN   