
    (Gj$                    `    U d Z ddlmZ ddlZddlmZ dZded<   i Zded	<   ddZ	ddZ
ddZdS )u
  Per-reasoning-model stale-timeout floor for known reasoning models.

Reasoning models (those that emit extended thinking blocks before their
first content token) routinely exceed Hermes's default chat-model
stale detectors:

* Stream stale detector:   ``HERMES_STREAM_STALE_TIMEOUT``     default 180s
                           ``agent/chat_completion_helpers.py:2544``
* Non-stream stale detector: ``HERMES_API_CALL_STALE_TIMEOUT``  default 90s
                           ``run_agent.py:1140``

For NVIDIA Nemotron 3 Ultra on the hosted NIM gateway the empirical
upstream idle kill is ~120s (first-party reproduction at
NVIDIA/NemoClaw#4846 — TTFB ~31s, stream dies at 120s). The same
failure mode exists on OpenAI o1/o3, Anthropic Opus 4.x thinking,
DeepSeek R1, Qwen QwQ, xAI Grok reasoning — every cloud reasoning
model hits upstream-proxies / load-balancers with idle timeouts
shorter than the model's thinking phase. Result: the stale detector
kills the connection mid-think, surfacing as
``BrokenPipeError``/``RemoteProtocolError`` on the next read.

This module provides a floor that the existing stale-detector scaling
blocks consult via :func:`get_reasoning_stale_timeout_floor` and
apply as ``max(default, floor)``. It is a FLOOR:

* Never overrides explicit user config (``providers.<id>.models.<model>.stale_timeout_seconds``
  or ``request_timeout_seconds`` already wins — this code never runs
  in that branch).
* Never lowers an existing threshold.
* Has zero effect on non-reasoning models — they are not in the
  allowlist and the resolver returns ``None``.

Matching uses start-anchored regex on the slug-only component of
the model name (after stripping any aggregator prefix like
``openai/``, ``x-ai/``, ``anthropic/``).  The right-anchor matches
end-of-string or a ``-``/``.``/``_`` slug separator, so ``qwen3-235b``
matches the ``qwen3`` family entry (a future model slug would be
``qwen3-235b-instruct`` and would also match) but ``some-other-qwen3``
does NOT match ``qwen3`` (the ``-qwen3`` is not at start of slug).

The ``o1`` case is the most delicate: a model named
``llama-4-70b-o1-preview`` is a hypothetical community derivative that
should NOT trigger the reasoning-model floor for the user (the user
chose a non-OpenAI model, not a reasoning model).  The start-of-slug
anchor naturally excludes this — the matched ``o1-preview`` is at
position 11 of the slug, not at position 0.  The previous substring-
with-trailing-hyphen design would have over-matched here, which is
why start-of-slug anchoring is the right shape.

Fixes #52217.
    )annotationsN)Optional))znemotron-3-ultraX  )znemotron-3-superr   )znemotron-3-nano,  )zdeepseek-r1r   )zdeepseek-reasonerr   )zqwq-32br   )qwen3   )o1r   )zo1-minir   )zo1-pror   )z
o1-previewr   )o3r   )zo3-pror   )zo3-minir   )zo4-minir   )zclaude-opus-4   )zclaude-sonnet-4.5r   )zclaude-sonnet-4.6r   )zgrok-4-fast-reasoningr   )zgrok-4.20-reasoningr   )zgrok-4-fast-non-reasoningr   ztuple[tuple[str, int], ...]_REASONING_STALE_TIMEOUT_FLOORSzdict[str, re.Pattern[str]]_PATTERN_CACHEslugstrreturnre.Pattern[str]c                    t                               |           }|6t          j        dt          j        |           z   dz             }|t           | <   |S )N^z(?:$|[\-._]))r   getrecompileescape)r   compileds     >/home/rurouni/.hermes/hermes-agent/agent/reasoning_timeouts.py_get_patternr      sX    !!$''H:ioo
 

  (tO    model_lowerOptional[float]c                    t          t          d           }|D ]8\  }}t          |                              |           rt	          |          c S 9dS )a5  Return the floor for the first matching slug, else None.

    Each table entry is matched as a start-of-slug prefix with the
    slug-separator-or-end-of-string right-anchor.  Table iteration
    order is irrelevant: longest slug wins (so ``o3-mini`` beats
    ``o3`` on a model like ``openai/o3-mini``).
    c                .    t          | d                    S )Nr   )len)kvs    r   <lambda>z_match_any.<locals>.<lambda>   s    RU r   )keyN)sortedr   r   searchfloat)r   sorted_floorsr   floors       r   
_match_anyr)      sp     '-C-C  M %    e$$[11 	 <<	 4r   modelobjectc                    | rt          | t                    sdS |                                                                 }|sdS d|v r|                    dd          d         }t          |          S )u  Return the stale-timeout floor (seconds) for a known reasoning model.

    Returns ``None`` when the model is not in the allowlist or the
    argument is empty / not a string.  Matching uses
    word-boundary-anchored regex on the lowercased model name, so
    ``openai/o3-mini`` matches the ``o3-mini`` slug but
    ``olmo-1`` does NOT match ``o1`` (the ``o1`` substring is not
    at a word boundary inside ``olmo-1``).

    Aggregator prefixes (``openai/``, ``x-ai/``, ``anthropic/`` etc.)
    are preserved through matching — the ``/`` is itself a word
    boundary, so ``openai/o3-mini`` matches ``o3-mini`` because the
    ``/`` before ``o3-mini`` satisfies the left-anchor alternation.

    This is a FLOOR — callers must apply it as ``max(default, floor)``
    and only when no explicit user-configured per-model
    ``stale_timeout_seconds`` exists.

    >>> get_reasoning_stale_timeout_floor("nvidia/nemotron-3-ultra-550b-a55b")
    600.0
    >>> get_reasoning_stale_timeout_floor("openai/o3-mini")
    300.0
    >>> get_reasoning_stale_timeout_floor("deepseek/deepseek-r1")
    600.0
    >>> get_reasoning_stale_timeout_floor("qwen/qwen3-235b-a22b-thinking")
    180.0
    >>> get_reasoning_stale_timeout_floor("x-ai/grok-4-fast-reasoning")
    300.0
    >>> get_reasoning_stale_timeout_floor("anthropic/claude-opus-4-6")
    240.0
    >>> get_reasoning_stale_timeout_floor("gpt-4o") is None
    True
    >>> get_reasoning_stale_timeout_floor("olmo-1") is None
    True
    >>> get_reasoning_stale_timeout_floor(None) is None
    True
    N/   )
isinstancer   striplowerrsplitr)   )r*   names     r   !get_reasoning_stale_timeout_floorr4      sz    L  
5#.. t;;==  D t d{{{{3""1%dr   )r   r   r   r   )r   r   r   r   )r*   r+   r   r   )__doc__
__future__r   r   typingr   r   __annotations__r   r   r)   r4    r   r   <module>r:      s   2 2 2h # " " " " " 				      1@  1 1 1 1P .0 / / / /	 	 	 	   &0 0 0 0 0 0r   