
    `gjA                       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h d1      Zd%ed2<    ej"                  d3      Zd<d4Zd<d5Zd<d6Zd=d7Zd>d8Zd?d9Zd<d:Zd@d;Zy)Au  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 ``deepseek-chat`` (V3), ``deepseek-reasoner``
  (R1-family), and the first-class V-series IDs (``deepseek-v4-pro``,
  ``deepseek-v4-flash``, and any future ``deepseek-v<N>-*``).  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>   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_KEYWORDS>   deepseek-v4-prodeepseek-v4-flashdeepseek-chatdeepseek-reasoner_DEEPSEEK_CANONICAL_MODELSz^deepseek-v\d+([-.].+)?$c                    t        |       j                         }|t        v r|S t        j	                  |      r|S t
        D ]  }||v s y y)a  Map a model input to a DeepSeek-accepted identifier.

    Rules:
    - Already a known canonical (``deepseek-chat``/``deepseek-reasoner``/
      ``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-reasoner``.
    - Everything else -> ``deepseek-chat``.

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

    Returns:
        A DeepSeek-accepted model identifier.
    r@   r?   )_strip_vendor_prefixlowerrA   _DEEPSEEK_V_SERIES_REmatchr<   )
model_namebarekeywords      M/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/model_normalize.py_normalize_for_deepseekrK      sZ    $  
+113D)) ""4( / 'd?&'     c                8    d| v r| j                  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rG   s    rJ   rC   rC      s*     jQ'**rL   c                &    | j                  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``.
    .-)replacerQ   s    rJ   _dots_to_hyphensrV      s     c3''rL   c                    | xs dj                         j                         }|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)striprD   hermes_cli.modelsrY   	Exception)provider_namerawrY   s      rJ   _normalize_provider_aliasr_      sM    B
%
%
'
-
-
/C
8!#&& 
s   6 AAc                    d| vr| S | j                  dd      \  }}|j                         r|j                         s| S t        |      }t        |      }|r||k(  r|j                         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.
    rN   rO   )rP   rZ   r_   )rG   target_providerprefix	remaindernormalized_prefixnormalized_targets         rJ   _strip_matching_provider_prefixrf      sv     *"((a0FI<<>!21&91/B.2CC  rL   c                L   | j                         }|syd|v r'|j                  dd      d   j                         xs dS |j                         }|j                  d      d   }|t        v r	t        |   S t        j	                         D ]  \  }}|j                  |      s|c S  y)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")
    NrN   rO   r   rT   )rZ   rP   rD   r   items
startswith)rG   name
name_lowerfirst_tokenrb   vendors         rJ   detect_vendorrn      s    4 D d{zz#q!!$**,44J ""3'*K&&,, +002   (M rL   c                :    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'
    rN   )rn   )rG   rm   s     rJ   _prepend_vendorrp   )  s6    " j:&F:,''rL   c                   | xs dj                         }|s|S t        |      }|t        v rt        |      S |dv r^d|v r)|j	                  dd      \  }}|j                         xs |}|dk(  r*|j                         j                  d      rt        |      S |S |t        v rt        ||      }d|v r|S t        |      S |dv r	 dd	l
m}  ||      }|r|S 	 |t        v r9t        ||      }	|	|k(  r&|j                  d
      r|j	                  dd      d   S |	S |dk(  rt        ||      }d|v r|S t        |      S |t        v r&t        ||      }
|t         v r|
j                         }
|
S |t"        v r|S |S # t        $ r Y w xY w)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-chat'

        >>> normalize_model_for_provider("deepseek-r1", "deepseek")
        'deepseek-reasoner'

        >>> 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'
    rX   >   opencode-goopencode-zenrN   rO   rs   zclaude->   r%   r&   r   )normalize_copilot_model_idzopenai/r   )rZ   r_   r"   rp   rP   rD   ri   rV   r#   rf   r[   rt   r\   r'   rK   r5   r6   r)   )model_inputra   rj   provider_bare_after_slashrH   rt   
normalizedstrippedresults              rJ   normalize_model_for_providerr|   G  s   v 2$$&D(9H ((t$$ 22$;"&**S!"4A#))+3tD~%$**,*A*A)*L#D)) ++.tX>$;K%% --		D3D9J!!  //24Bt	 :::c1%a(( :.tX>$;K&t,, 330x@ 11\\^F 22 KG  	 	s   E3 3	E?>E?)rG   strreturnr}   )r]   r}   r~   r}   )rG   r}   ra   r}   r~   r}   )rG   r}   r~   zOptional[str])ru   r}   ra   r}   r~   r}   )__doc__
__future__r   retypingr   r   __annotations__	frozensetr"   r#   r'   r)   r5   r6   r<   rA   compilerE   rK   rC   rV   r_   rf   rn   rp   r|    rL   rJ   <module>r      s	  : # 	 $k$	8$ 	($ 	(	$
 	($ h$ X$ 
$ 
6$ L$ y$ F$ F$ H$ z$  !$" \#$$ '$ . . )2 3 ) ~  ,56 , . 
 09 : 0 n  3<= 3   4= > 4  . , .78 . N  /8 9 / ^  .7 8 . N  #

#>?  N"(
*/d<LrL   