
    #jN|                        d Z ddlZddlmZmZ ddlmZ ddlmZm	Z	 ddl
mZ ddlmZ ddlmZmZmZ d	ed
edz  dedz  fdZdedz  dedz  fdZdedefdZd	edefdZ G d de          ZddlmZ  ede           dS )u  OpenAI Chat Completions transport.

Handles the default api_mode ('chat_completions') used by ~16 OpenAI-compatible
providers (OpenRouter, Nous, NVIDIA, Qwen, Ollama, DeepSeek, xAI, Kimi, etc.).

Messages and tools are already in OpenAI format — convert_messages and
convert_tools are near-identity.  The complexity lives in build_kwargs
which has provider-specific conditionals for max_tokens defaults,
reasoning configuration, temperature handling, and extra_body assembly.
    N)AnyDict)resolve_lmstudio_effort)is_moonshot_modelsanitize_moonshot_tools)DEVELOPER_ROLE_MODELS)ProviderTransport)NormalizedResponseToolCallUsagemodelreasoning_configreturnc                    |t          |t                    sdS | pd                                                                }|                    d          r|                    dd          d         }|                    d          sdS |                    d          du rd	diS t          |                    d
d          pd                                                                          }|dk    rd	diS d	di}|                    d          r|S |dvrd}|                    d          r-d|v r|dv rd|d<   n|dv rd|d<   nd|d<   nd|v r|dv rdnd|d<   |S )zLTranslate Hermes/OpenRouter-style reasoning config to Gemini thinkingConfig.N zgoogle//   geminienabledFincludeThoughtseffortmediumnoneTzgemini-2.5->   lowhighxhighr   minimal)zgemini-3z
gemini-3.1flash>   r   r   r   thinkingLevel>   r   r   r   pro)
isinstancedictstriplower
startswithsplitgetstr)r   r   normalized_modelr   thinking_configs        F/home/ubuntu/.hermes/hermes-agent/agent/transports/chat_completions.py_build_gemini_thinking_configr,      s   z2BD'I'It**,,2244""9-- =+11#q99!< &&x00 tI&&%// "5))!%%h99EXFFLLNNTTVVF!5))'8$&?O
 ""=11 BBB
 ""#=>> &&&+++3800,,,39003;00&&& $5555 O,     configc                    t          | t                    r| sdS i }t          |                     d          t                    r| d         |d<   t          |                     d          t                    rI| d                                         r/| d                                                                         |d<   t          |                     d          t          t          f          rt          | d                   |d<   |pdS )zEConvert Gemini thinking config keys to the OpenAI-compat field names.Nr   include_thoughtsr   thinking_levelthinkingBudgetthinking_budget)	r!   r"   r'   boolr(   r#   r$   intfloat)r.   
translateds     r+   "_snake_case_gemini_thinking_configr8   N   s    fd## 6 t!#J&**.//66 C)/0A)B
%&&**_--s33 O8O8U8U8W8W O'-o'>'D'D'F'F'L'L'N'N
#$&**-..e== F(+F3C,D(E(E
$%r-   base_urlc                     t          | pd                                                              d                                          }|sdS d|vrdS |                    d          S )Nr   r   Fz!generativelanguage.googleapis.comz/openai)r(   r#   rstripr$   endswith)r9   
normalizeds     r+   !_is_gemini_openai_compat_base_urlr>   ]   sj    X^$$**,,33C88>>@@J u**<<uy)))r-   c                 X    t          | pd                                          }d|v pd|v S )u  True when the outgoing model is a Gemini family model that requires
    ``extra_content`` (thought_signature) to be replayed on tool calls.

    Gemini 3 thinking models attach ``extra_content`` to each tool call and
    reject subsequent requests with HTTP 400 if it is missing. Every other
    strict OpenAI-compatible provider (Fireworks, Mistral, ...) rejects the
    request with 400 if ``extra_content`` *is* present. So the field must be
    kept only when the target model is itself Gemini-family, and stripped
    otherwise — including when a non-Gemini model inherits stale Gemini
    ``extra_content`` from earlier in a mixed-provider session.
    r   r   gemma)r(   r$   )r   ms     r+   !_model_consumes_thought_signaturerB   f   s4     	EKR  Aq=(GqL(r-   c                   t   e Zd ZdZedefd            Zdeeee	f                  deeee	f                  fdZ
deeee	f                  deeee	f                  fdZ	 dd	edeeee	f                  deeee	f                  dz  deee	f         fd
Zd Zde	defdZde	defdZde	deeef         dz  fdZdS )ChatCompletionsTransportzfTransport for api_mode='chat_completions'.

    The default path for OpenAI-compatible providers.
    r   c                     dS )Nchat_completions )selfs    r+   api_modez!ChatCompletionsTransport.api_mode|   s    !!r-   messagesc                    t          |                    d                     }d}|D ]}t          |t                    sd|v sd|v sd|v rd} nxt	          d |D                       rd} n[|                    d          }t          |t
                    r0|D ])}t          |t                    rd	|v s
d
|v s|rd|v rd} n*|r n|s|S t          j        |          }|D ]}t          |t                    s|                    dd           |                    dd           |                    dd           d |D             D ]}	|                    |	d           |                    d          }t          |t
                    r^|D ][}t          |t                    rD|                    d	d           |                    d
d           |r|                    dd           \
|S )u  Messages are already in OpenAI format — strip internal fields
        that strict chat-completions providers reject with HTTP 400/422
        (or, in the case of some OpenAI-compatible gateways, 5xx):

        - Codex Responses API fields: ``codex_reasoning_items`` /
          ``codex_message_items`` on the message, ``call_id`` /
          ``response_item_id`` on ``tool_calls`` entries.
        - ``extra_content`` on ``tool_calls`` (Gemini thought_signature) —
          stripped unless the outgoing ``model`` is itself Gemini-family.
          Gemini 3 thinking models attach it for replay, but strict providers
          (Fireworks, Mistral) reject any payload containing it with
          ``Extra inputs are not permitted, field: 'messages[N].tool_calls[M].extra_content'``.
          It must be kept for Gemini targets (replay required) and dropped for
          everyone else, including non-Gemini models that inherited stale
          Gemini ``extra_content`` earlier in a mixed-provider session.
        - ``tool_name`` on tool-result messages — written by
          ``make_tool_result_message()`` for the SQLite FTS index, but not
          part of the Chat Completions schema. Strict providers (Fireworks,
          Moonshot/Kimi) reject any payload containing it with
          ``Extra inputs are not permitted, field: 'messages[N].tool_name'``.
          Permissive providers (OpenRouter, MiniMax) silently ignore the
          field, which masked the bug for months.
        - Hermes-internal scaffolding markers — any top-level message key
          starting with ``_`` (e.g. ``_empty_recovery_synthetic``,
          ``_empty_terminal_sentinel``, ``_thinking_prefill``). These are
          bookkeeping flags the agent loop attaches to messages so the
          persistence layer can later strip its own scaffolding; they must
          never reach the wire. Permissive providers (real OpenAI,
          Anthropic) silently drop unknown message keys, but strict
          gateways (e.g. opencode-go, codex.nekos.me) reject with
          ``Extra inputs are not permitted, field: 'messages[N]._empty_recovery_synthetic'``,
          which then poisons every subsequent request in the session.
        r   Fcodex_reasoning_itemscodex_message_items	tool_nameTc              3   j   K   | ].}t          |t                    o|                    d           V  /dS )_Nr!   r(   r%   .0ks     r+   	<genexpr>z<ChatCompletionsTransport.convert_messages.<locals>.<genexpr>   s=      II:a%%;!,,s*;*;IIIIIIr-   
tool_callscall_idresponse_item_idextra_contentNc                 f    g | ].}t          |t                    |                    d           ,|/S )rP   rQ   rR   s     r+   
<listcomp>z=ChatCompletionsTransport.convert_messages.<locals>.<listcomp>   s8    SSSa*Q*<*<ScARARSSSSr-   )	rB   r'   r!   r"   anylistcopydeepcopypop)
rH   rJ   kwargsstrip_extra_contentneeds_sanitizemsgrV   tc	sanitizedkeys
             r+   convert_messagesz)ChatCompletionsTransport.convert_messages   ss   H #DJJw#
 #
 
  	 	Cc4(( '3..(C//#%%!%IISIIIII !%..J*d++ 
$  B!"d++ !R-33/ 44Cr4I4I)-! E 	OM(++	 	: 	:Cc4(( GG+T222GG)4000GGK&&& TS3SSS # #T""""..J*d++ :$ : :B!"d++ :y$///14888. :FF?D999r-   toolsc                     |S )u0   Tools are already in OpenAI format — identity.rG   )rH   ri   s     r+   convert_toolsz&ChatCompletionsTransport.convert_tools   s    r-   Nr   c                   ( |                      ||          }|                    d          }|r|                     |||||          S |                    d|pd                                          (|rzt	          |d         t
                    r_|d                             d          dk    r@t          (fdt          D                       r t          |          }i |d         dd	i|d<   ||d
}|                    d          }|||d<   |r#t          |          rt          |          }||d<   |                    d          }	|                    d          }
|                    d          }|                    d          }|                    dd          }|                    dd          }|                    dd          }|                    d          }|
!|	r|                     |	|
                     n*|!|	r|                     |	|                     n|||d<   |rt          |o+t	          |t
                    o|                    d          du           }|s_d}|rVt	          |t
                    rA|                    d          pd                                                                }|dv r|}||d<   |rt          |o+t	          |t
                    o|                    d          du           }|s_d}|rVt	          |t
                    rA|                    d          pd                                                                }|dv r|}||d<   |                    dd          r@|                    dd          r*t          ||                    d                    }|||d<   i }|                    d d          }|                    d!d          }|                    d"d          }t          |                    d#          pd                                                                          }|                    d$          }|                    d%          }|r|r||d&<   |rh|d'k    rb|                    d(          }|K|dk    rE	 t!          |          }n# t"          t$          f$ r d}Y nw xY w|d)|cxk    rd*k    rn n	d+|d,g|d-<   |r=d.} |r.t	          |t
                    r|                    d          du rd} d/| rdnd0i|d1<   |                    dd          r=|                    dd          s'|r|                    d2          }!|!|!|d3<   nd.dd4|d3<   |d5k    rtt'          ||          }"t)          |          rMt+          |"          }#|#r;|                    d6i           }$|$                    d7i           }%|#|%d8<   |%|$d7<   |$|d6<   n%|"r|"|d8<   n|d9k    rt'          ||          }#|#r|#|d8<   |                    d:          }&|&r|                    |&           |r||d6<   |                    d;          }'|'r|                    |'           |S )<uX  Build chat.completions.create() kwargs.

        params (all optional):
            timeout: float — API call timeout
            max_tokens: int | None — user-configured max tokens
            ephemeral_max_output_tokens: int | None — one-shot override
            max_tokens_param_fn: callable — returns {max_tokens: N} or {max_completion_tokens: N}
            reasoning_config: dict | None
            request_overrides: dict | None
            session_id: str | None
            model_lower: str — lowercase model name for pattern matching
            # Provider profile path (all per-provider quirks live in providers/)
            provider_profile: ProviderProfile | None — when present, delegates to
                _build_kwargs_from_profile(); all flag params below are bypassed.
            # Legacy-path flags — only used when provider_profile is None
            # (i.e. custom / unregistered providers). Known providers all go
            # through provider_profile.
            is_openrouter: bool
            is_nous: bool
            is_qwen_portal: bool
            is_github_models: bool
            is_nvidia_nim: bool
            is_kimi: bool
            is_tokenhub: bool
            is_lmstudio: bool
            is_custom_provider: bool
            ollama_num_ctx: int | None
            # Provider routing
            provider_preferences: dict | None
            # Qwen-specific
            qwen_prepare_fn: callable | None — runs AFTER codex sanitization
            qwen_prepare_inplace_fn: callable | None — in-place variant for deepcopied lists
            qwen_session_metadata: dict | None
            # Temperature
            fixed_temperature: Any — from _fixed_temperature_for_model()
            omit_temperature: bool
            # Reasoning
            supports_reasoning: bool
            github_reasoning_extra: dict | None
            lmstudio_reasoning_options: list[str] | None  # raw allowed_options from /api/v1/models
            # Claude on OpenRouter/Nous max output
            anthropic_max_output: int | None
            extra_body_additions: dict | None
        )r   provider_profilemodel_lowerr   r   rolesystemc              3       K   | ]}|v V  	d S NrG   )rS   prn   s     r+   rU   z8ChatCompletionsTransport.build_kwargs.<locals>.<genexpr>(  s(      DDA$DDDDDDr-   	developerr   rJ   timeoutNri   max_tokens_param_fnephemeral_max_output_tokens
max_tokensanthropic_max_outputis_nvidia_nimFis_kimiis_tokenhubr   r   r   r   >   r   r   r   reasoning_effortr   is_lmstudiosupports_reasoninglmstudio_reasoning_optionsis_openrouteris_nousis_github_modelsprovider_namer9   provider_preferencesproviderzopenrouter/pareto-codeopenrouter_min_coding_scoreg        g      ?zpareto-router)idmin_coding_scorepluginsTtypedisabledthinkinggithub_reasoning_extra	reasoning)r   r   r   
extra_bodygoogler*   zgoogle-gemini-cliextra_body_additionsrequest_overrides)rh   r'   _build_kwargs_from_profiler$   r!   r"   r\   r   r]   r   r   updater4   r#   r   r(   r6   	TypeError
ValueErrorr,   r>   r8   ))rH   r   rJ   ri   paramsrf   _profile
api_kwargsrv   max_tokens_fn	ephemeralry   anthropic_max_outr{   r|   r}   r   _kimi_thinking_off_kimi_effort_e_tokenhub_thinking_off_tokenhub_effort
_lm_effortr   r   r   r   r   r9   provider_prefs_pareto_score_pareto_score_f_kimi_thinking_enabledgh_reasoningraw_thinking_configr*   openai_compat_extragoogle_extra	additions	overridesrn   s)                                           @r+   build_kwargsz%ChatCompletionsTransport.build_kwargs   s   l ))(%)@@	 ::011 	22%E6   jj"0C0C0E0EFF	A9Q<..	A !  ((H44DDDD.CDDDDD 5 YI@il@FK@@IaL !&
 &


 **Y''$+Jy!  	( !'' 7/66"'Jw 

#899JJ<==	ZZ--
"JJ'=>>

?E::**Y..jj66!::&899 ] mmI667777##mmJ778888*'8J|$  	>!%  =/66=$((33u<" "
 & >'# *
3CT(J(J **..x88>BEEGGMMOOB666')1=
-.  	B%)  =/66=$((33u<& &"
 * B#) # .
3CT(J(J .*..x88>BEEGGMMOOB666+-(1A
-. ::mU++ 	<

;OQV0W0W 	<0 

788 J %1;
-. &(


?E::**Y..!::&8%@@FJJ77=2>>DDFFLLNN::j))$:;; 	4m 	4%3Jz"
  
	U&>>>"JJ'DEEM(]b-@-@+&+M&:&:OO!:. + + +&*OOO+".3/3P3P3P3PS3P3P3P3P3P.OTT-Jy)
  	%)" 3J/?$F$F 3#''	22e;;-2*%;K		&Jz" ::*E22 	P6::mUZ;[;[ 	P P%zz*BCC+.:J{+6:h*O*O
;'H$$"?GW"X"X0:: 	D"DEX"Y"Y" C*4..r*J*J'#6#:#:8R#H#HL6EL!234@'1/BJ|,$ D0C
,-111;ECSTTO @0?
,- JJ566	 	)i((( 	2'1J|$ JJ233	 	)i(((s   $R4 4S
	S
c           
      P   ddl m} |                    |          }|pd                                |rzt	          |d         t
                    r_|d                             d          dk    r@t          fdt          D                       r t          |          }i |d         ddi|d<   ||d}|j
        |u rn.|j
        |j
        |d
<   n|                    d
          }|||d
<   |                    d          }	|	|	|d<   |r#t          |          rt          |          }||d<   |                    d          }
|                    d          }|                    d          }|                    d          }|                    |          }|!|
r|                     |
|                     nM|!|
r|                     |
|                     n*|r!|
r|                     |
|                     n|||d<   |                    d          }|                    ||                    dd          |                    d          ||                    d          |                    d                    \  }}|                    |           i }|                    |                    d          |                    d          ||                    d          ||                    d                    }|r|                    |           |r|                    |           |                    d          }|r|                    |           |                    d          }|rP|                                D ];\  }}|dk    r+t	          |t
                    r|                    |           6|||<   <|r_	 ddlm}  ||                    d                    }n# t(          $ r d}Y nw xY w|rd  |                                D             }|r||d<   |S )!u   Build API kwargs using a ProviderProfile — single path, no legacy flags.

        This method replaces the entire flag-based kwargs assembly when a
        provider_profile is passed. Every quirk comes from the profile object.
        r   )OMIT_TEMPERATUREr   ro   rp   c              3       K   | ]}|v V  	d S rr   rG   )rS   rs   _model_lowers     r+   rU   zFChatCompletionsTransport._build_kwargs_from_profile.<locals>.<genexpr>  s(      EE!A%EEEEEEr-   rt   ru   Ntemperaturerv   ri   rw   rx   ry   rz   r   r   Fqwen_session_metadataollama_num_ctx
session_id)r   r   r   r   r   r   r   r9   r   )r   r   r   r9   r   r   r   r   r   )is_native_gemini_base_urlc                 "    i | ]\  }}|d v 	||S ))r*   thinkingConfigrG   )rS   rT   vs      r+   
<dictcomp>zGChatCompletionsTransport._build_kwargs_from_profile.<locals>.<dictcomp>N  s4       !QAAA qAAAr-   )providers.baser   prepare_messagesr$   r!   r"   r'   r\   r   r]   fixed_temperaturer   r   get_max_tokensr   build_api_kwargs_extrasbuild_extra_bodyitemsagent.gemini_native_adapterr   	Exception)rH   profiler   rf   ri   r   r   r   temprv   r   r   user_maxanthropic_maxprofile_maxr   extra_body_from_profiletop_level_from_profiler   profile_bodyr   r   rT   r   r   _native_geminir   s                             @r+   r   z3ChatCompletionsTransport._build_kwargs_from_profile  s    	433333 ,,Y77	 **,,	A9Q<..	A !  ((H44EEEE/DEEEEE 5 YI@il@FK@@IaL !&
 &

 $(888&2(/(AJ}%% ::m,,D,0
=) **Y''$+Jy!  	( '' 7/66"'Jw 

#899JJ<==	::l++

#9:: ,,U33 ] mmI667777!m!mmH556666 	5] 	5mmK889999&'4J|$ "::&899++!1#)::.BE#J#J&,jj1H&I&I%zz*:;;!::l33 ,   	8!7 	0111 &(
 //zz,//!',B!C!CZZ
++-(.

3P(Q(Q 0 
 
  	,l+++ # 	75666 JJ566	 	)i((( JJ233	 	&!)) & &1$$At)<)<$%%a(((($%JqMM 	6'QQQQQQ!:!:6::j;Q;Q!R!R ' ' '!&'  %/%5%5%7%7  
  6+5
<(s   $O- -O<;O<responsec           	         |j         d         }|j        }|j        pd}d}|j        rg }|j        D ]}i }t	          |dd          }	|	,t          |d          r|j        pi                     d          }	|	;t          |	d          r&	 |	                                }	n# t          $ r Y nw xY w|	|d<   |
                    t          |j        |j        j        |j        j        |pd                     d}
t          |d          rS|j        rL|j        }t#          t	          |d	d          pdt	          |d
d          pdt	          |dd          pd          }
t	          |dd          }t	          |dd          }|Dt          |d          r4t	          |dd          pi }t%          |t&                    rd|v r|d         }i }|||d<   t	          |dd          }|r||d<   t)          |j        ||||
|pd          S )u  Normalize OpenAI ChatCompletion to NormalizedResponse.

        For chat_completions, this is near-identity — the response is already
        in OpenAI format.  extra_content on tool_calls (Gemini thought_signature)
        is preserved via ToolCall.provider_data.  reasoning_details (OpenRouter
        unified format) and reasoning_content (DeepSeek/Moonshot) are also
        preserved for downstream replay.
        r   stopNrY   model_extra
model_dump)r   name	argumentsprovider_datausageprompt_tokenscompletion_tokenstotal_tokens)r   r   r   r   reasoning_contentreasoning_details)contentrV   finish_reasonr   r   r   )choicesmessager   rV   getattrhasattrr   r'   r   r   appendr   r   functionr   r   r   r   r!   r"   r
   r   )rH   r   ra   choicerd   r   rV   re   tc_provider_dataextrar   ur   r   r   r   rds                    r+   normalize_responsez+ChatCompletionsTransport.normalize_responseW  s    !!$n,6
> 	Jn  
 46 OT::=WR%?%?=^1r66GGE$ul33 !!$)$4$4$6$6EE( ! ! ! D!8=$_5!!5[-"$+"7&6&>$	      8W%% 	(. 	A%a!<<A")!-@!"D"D"I$Q::?a  E Cd33	#C)<dCC$m)D)D$!#}d;;ArK+t,, E1D1S1S$/0C$D!(*(1BM-.S-t44 	413M-.!K!''/4
 
 
 	
s   B
B('B(c                 R    |dS t          |d          r|j        dS |j        sdS dS )z&Check that response has valid choices.NFr   T)r   r   )rH   r   s     r+   validate_responsez*ChatCompletionsTransport.validate_response  sA    5x++ 	x/?/G5 	5tr-   c                     t          |dd          }|dS t          |dd          }|dS t          |dd          pd}t          |dd          pd}|s|r||dS dS )zAExtract OpenRouter/OpenAI cache stats from prompt_tokens_details.r   Nprompt_tokens_detailscached_tokensr   cache_write_tokens)r   creation_tokens)r   )rH   r   r   detailscachedwrittens         r+   extract_cache_statsz,ChatCompletionsTransport.extract_cache_stats  s    '400=4%!8$???4/155:'#7;;@q 	IW 	I%+HHHtr-   rr   )__name__
__module____qualname____doc__propertyr(   rI   r]   r"   r   rh   rk   r   r   r
   r   r4   r   r5   r   rG   r-   r+   rD   rD   v   s        
 "# " " " X"YT#s(^,Y	d38n	Y Y Y Yv4S#X#7 Dc3h<P     .2	i ii tCH~&i DcN#d*	i 
c3hi i i iVK K KZK
3 K
=O K
 K
 K
 K
Z# $    C DcNT4I      r-   rD   )register_transportrF   )r   r^   typingr   r   agent.lmstudio_reasoningr   agent.moonshot_schemar   r   agent.prompt_builderr   agent.transports.baser	   agent.transports.typesr
   r   r   r(   r"   r,   r8   r4   r>   rB   rD   agent.transportsr   rG   r-   r+   <module>r     s  	 	          < < < < < < L L L L L L L L 6 6 6 6 6 6 3 3 3 3 3 3 F F F F F F F F F F5 5t 5PTW[P[ 5 5 5 5ptd{ td{    * * * * * *)S )T ) ) ) ) D	 D	 D	 D	 D	0 D	 D	 D	P 0 / / / / /  %'? @ @ @ @ @r-   