
    PmjG                    H   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h d/          Zd%ed0<    ed1d2h          Zd%ed3<    ed4d5h          Zd%ed6<    ej        d7          ZdGd;ZdGd<ZdGd=ZdHd?ZdIdAZdJdCZdGdDZdKdFZdS )Lu  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_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>   r@   _DEEPSEEK_V_SERIES_REmatchr=   )rA   barekeywords      @/home/thesage/.hermes/hermes-agent/hermes_cli/model_normalize.py_normalize_for_deepseekrL      s    (  
++1133D (((""))) ""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rA   s    rK   rE   rE      s0     jQ''**rM   c                .    |                      dd          S )zReplace 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``.
    .-)replacerR   s    rK   _dots_to_hyphensrW      s     c3'''rM   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)striprF   hermes_cli.modelsr[   	Exception)rX   rawr[   s      rK   _normalize_provider_aliasr`      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.
    rO   rP   r4   )rQ   r\   r`   rF   )rA   ra   prefix	remaindernormalized_targetnormalized_prefixs         rK   _strip_matching_provider_prefixrg      s     *"((a00FI<<>> !2!2 1/BBH$$<<>>!!X--??$$$1&99 !.2CCC   rM   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")
    NrO   rP   r   rU   )r\   rQ   rF   r   items
startswith)rA   name
name_lowerfirst_tokenrc   vendors         rK   detect_vendorrp     s    4 D t d{{zz#q!!!$**,,44J ""3''*K&&&,, +0022    (( 	MMM	 4rM   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'
    rO   )rp   )rA   ro   s     rK   _prepend_vendorrr   G  sD    " j:&&F ('':'''rM   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|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'
    rZ   >   opencode-goopencode-zenrO   rP   rv   zclaude->   r&   r'   r   )normalize_copilot_model_idzopenai/r   )r\   r`   r#   rr   rQ   rF   rk   rW   r$   rg   r]   rw   r^   r(   rL   r6   r7   r*   )rs   ra   rl   provider_bare_after_slashrI   rw   
normalizedstrippedresults              rK   normalize_model_for_providerr~   e  sY   | 2$$&&D (99H (((t$$$ 222$;;"&**S!"4"4A#))++3tD~%%$**,,*A*A)*L*L%#D))) +++.tX>>$;;K%%% ---		DDDDDD33D99J "!!" 	 	 	 D	 ///24BBt	 : :::c1%%a(( :.tX>>$;;K&t,,, 3330x@@ 111\\^^F 222 Ks   *D   
DD)rA   rB   rC   rB   )rX   rB   rC   rB   )rA   rB   ra   rB   rC   rB   )rA   rB   rC   rh   )rs   rB   ra   rB   rC   rB   )__doc__
__future__r   retypingr   r   __annotations__	frozensetr#   r$   r(   r*   r6   r7   r=   r>   r@   compilerG   rL   rE   rW   r`   rg   rp   rr   r~    rM   rK   <module>r      s    > # " " " " " 				      $k$	8$ 	($ 	(	$
 	($ h$ X$ 
$ 
6$ L$ y$ F$ F$ H$ z$  !$" \#$$ '$ $ $     . )2	 3 3 3 ) )      ,596 , ,     
 09y : : : 0 0      3<)= 3 3      4=9 > > > 4 4      , .7Y8 . .      /8i 9 9 9 / /      -6I7 - -     
 .7Y8 . .      #
#>?? ' ' ' '\   "( ( ( (
 
 
 
   D/ / / /d   <O O O O O OrM   