
    i?                    &   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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dCd7ZdCd8ZdCd9ZdDd;ZdEd=ZdFd?ZdCd@ZdGdBZdS )Hu  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>   
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zaiarceecustomr   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+([-.].+)?$
model_namestrreturnc                    t          |                                           }|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:
    - 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<   )rB   barekeywords      ?/home/ubuntu/.hermes/hermes-agent/hermes_cli/model_normalize.py_normalize_for_deepseekrM      sz    $  
++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rB   s    rL   rF   rF      s0     jQ''**rN   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``.
    .-)replacerS   s    rL   _dots_to_hyphensrX      s     c3'''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          |          }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.
    rP   rQ   )rR   r]   ra   )rB   rb   prefix	remaindernormalized_prefixnormalized_targets         rL   _strip_matching_provider_prefixrh      s     *"((a00FI<<>> !2!2 1&991/BB !.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{{zz#q!!!$**,,44J ""3''*K&&&,, +0022    (( 	MMM	 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   )  sD    " j:&&F ('':'''rN   model_inputc                b   | pd                                 }|s|S t          |          }|t          v rt          |          S |dk    rNt	          ||          }d|v r|S |                                                    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-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'
    r[   zopencode-zenrP   zclaude->   r&   r'   r   )normalize_copilot_model_idzopenai/rQ   r   )r]   ra   r#   rs   rh   rG   rl   rX   r$   r^   rv   r_   r(   rR   rM   r5   r6   r*   )	rt   rb   rm   providerrJ   rv   
normalizedstrippedresults	            rL   normalize_model_for_providerr{   G  s5   v 2$$&&D (99H (((t$$$ >!!.tX>>$;;K::<<""9-- 	*#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   	C 
C,+C,)rB   rC   rD   rC   )rY   rC   rD   rC   )rB   rC   rb   rC   rD   rC   )rB   rC   rD   ri   )rt   rC   rb   rC   rD   rC   )__doc__
__future__r   retypingr   r   __annotations__	frozensetr#   r$   r(   r*   r5   r6   r<   rA   compilerH   rM   rF   rX   ra   rh   rq   rs   r{    rN   rL   <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 / /      .7Y 8 8 8 . .      #
#>??        N   "( ( ( (
 
 
 
   */ / / /d   <E E E E E ErN   