
    `gjH                        d 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	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       y
)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.
    )AnyDict)resolve_lmstudio_effort)is_moonshot_modelsanitize_moonshot_tools)DEVELOPER_ROLE_MODELS)ProviderTransport)NormalizedResponseToolCallUsagemodelreasoning_configNreturnc                     t        |t              s|S d| xs dj                         v rOt        |j	                  d      xs d      j                         j                         dk(  rt        |      }d|d<   |S |S )z4Return the model's wire-compatible reasoning config.zgpt-5.6 effortultramax)
isinstancedictlowerstrgetstrip)r   r   
normalizeds      T/root/.hermes/venv/lib/python3.12/site-packages/agent/transports/chat_completions.py_reasoning_config_for_modelr      sy    &-ekr((** $$X.4"5;;=CCEP*+
$
8    c                 R   |t        |t              sy| xs dj                         j                         }|j	                  d      r|j                  dd      d   }|j	                  d      sy|j                  d      du rd	diS t        |j                  d
d      xs d      j                         j                         }|dk(  rd	diS d	di}|j	                  d      r|S |dvrd}|j	                  d      r0d|v r|dv rd|d<   |S |dv rd|d<   |S d|d<   |S d|v r|dv rdnd|d<   |S )zLTranslate Hermes/OpenRouter-style reasoning config to Gemini thinkingConfig.Nr   zgoogle//   geminienabledFincludeThoughtsr   mediumnoneTzgemini-2.5->   lowr   highr   xhighr%   minimal)zgemini-3z
gemini-3.1flash>   r'   r*   r'   thinkingLevel>   r   r(   r   r)   r(   pro)r   r   r   r   
startswithsplitr   r   )r   r   normalized_modelr   thinking_configs        r   _build_gemini_thinking_configr2   #   s   z2BD'I**,224""9-+11#q9!< &&x0I&%/ "5))!%%h9EXFLLNTTVF!5))'8$&?O
 ""=1RR
 ""#=>&&++380  <<390  4<0  && $EE5 O, r   configc                    t        | t              r| syi }t        | j                  d      t              r| d   |d<   t        | j                  d      t              r7| d   j                         r$| d   j                         j                         |d<   t        | j                  d      t        t        f      rt        | d         |d<   |xs 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)r3   
translateds     r   "_snake_case_gemini_thinking_configr=   [   s    fd#6!#J&**./6)/0A)B
%&&**_-s38O8U8U8W'-o'>'D'D'F'L'L'N
#$&**-.e=(+F3C,D(E
$%r   base_urlc                     t        | xs d      j                         j                  d      j                         }|syd|vry|j	                  d      S )Nr   r    Fz!generativelanguage.googleapis.comz/openai)r   r   rstripr   endswith)r>   r   s     r   !_is_gemini_openai_compat_base_urlrB   j   sO    X^$**,33C8>>@J**<y))r   c                 P    t        | xs d      j                         }d|v xs 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_signaturerF   s   s.     	EKR Aq=(GqL(r   c                      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y)ChatCompletionsTransportzfTransport for api_mode='chat_completions'.

    The default path for OpenAI-compatible providers.
    r   c                      y)Nchat_completions )selfs    r   api_modez!ChatCompletionsTransport.api_mode   s    !r   messagesc                 r   t        |j                  d             }d}|D ]  t        t              sdv sdv sdv sdv sdv sdv rd	} nit	        d
 D              rd	} nSj                  d      }t        |t
              sh|D ]&  }t        |t              sd|v sd|v s|sd|v s$d	} n |s n |s|S t        |      t        |      D ]  \  t        t              sddt        t        t        f   ffd}dv sdv sdv sdv sdv sdv rs |       }|j                  dd       |j                  dd       |j                  dd       |j                  dd       |j                  dd       |j                  dd       D 	cg c]'  }	t        |	t              s|	j                  d      s&|	) }
}	|
r  |       }|
D ]  }|j                  |d        j                  d      }t        |t
              s8d}t        |      D ]  \  }}t        |t              sd|v xs d|v xs |xr d|v }|s.|t        |      }t        |      }|j                  dd       |j                  dd       |r|j                  dd       |||<    || |       d<    S c c}	w )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_nameeffect_disposition	timestampapi_contentTc              3   b   K   | ]'  }t        |t              xr |j                  d        ) yw)_N)r   r   r.   ).0ks     r   	<genexpr>z<ChatCompletionsTransport.convert_messages.<locals>.<genexpr>   s(     I:a%;!,,s*;;Is   -/
tool_callscall_idresponse_item_idextra_contentNr   c                  ,     t                <    S N)r   )
copied_msgmsgmsg_idx	sanitizeds   r   mutable_msgz>ChatCompletionsTransport.convert_messages.<locals>.mutable_msg   s"    %!%cJ)3Ig&!!r   rW   )rF   r   r   r   anylist	enumerater   r   popr.   )rL   rN   kwargsstrip_extra_contentneeds_sanitizer[   tcre   out_msgrY   internal_keyskeycopied_tool_callstc_idxshould_copy_tc	copied_tcra   rb   rc   rd   s                   @@@@r   convert_messagesz)ChatCompletionsTransport.convert_messages   s   H #DJJw#
 
  	Cc4('3.(C/#%'3.#% C'!%ISII!%.J*d+$ B!"d+!R-3/Or4I)- "7	: ON	%h/ ;	DLGSc4(04J"c3h " " (3.(C/#%'3.#% C'%-3T:148K.0$7K.M40 ),X1z!S/AallSVFWQXMX%-( +CKKT*+ .J*d+6:!"+J"7 BJFB!"d+%O O1R7O 3 M28M '
 *08484D 1(,RI%MM)T:%MM*<dC2 )ot D8A-f5B  %02CKM,/w;	Dx 7 Ys   J42J4J4toolsc                     |S )u0   Tools are already in OpenAI format — identity.rK   )rL   rv   s     r   convert_toolsz&ChatCompletionsTransport.convert_tools  s    r   Nr   c                 
  ) | j                  ||      }|j                  d      }|r| j                  |||||      S |j                  d|xs dj                               )|r[t	        |d   t
              rH|d   j                  d      dk(  r1t        )fdt        D              rt        |      }i |d   dd	i|d<   ||d
}|j                  d      }|||d<   |rt        |      rt        |      }||d<   |j                  d      }	|j                  d      }
|j                  d      }|j                  d      }|j                  dd      }|j                  dd      }|j                  dd      }t        ||j                  d            }|
|	r|j                   |	|
             n#||	r|j                   |	|             n|||d<   |rt        |xr% t	        |t
              xr |j                  d      du       }|sPd}|rGt	        |t
              r7|j                  d      xs dj                         j                         }|dv r|}||d<   |rt        |xr% t	        |t
              xr |j                  d      du       }|sPd}|rGt	        |t
              r7|j                  d      xs dj                         j                         }|dv r|}||d<   |j                  dd      r4|j                  dd      r"t        ||j                  d            }|||d<   i }|j                  d d      }|j                  d!d      }|j                  d"d      }t!        |j                  d#      xs d      j                         j                         }|j                  d$      }|j                  d%      }|r|r||d&<   |rB|d'k(  r=|j                  d(      }|*|dk7  r%	 t#        |      }|d)|cxk  rd*k  rn n	d+|d,g|d-<   |r4d.} |r%t	        |t
              r|j                  d      du rd} d/| rdnd0i|d1<   |j                  dd      r_|j                  dd      sM|r|j                  d2      }!|!8|!|d3<   n2d}"|r&t	        |t
              r|j                  dd      xs d}"d.|"d4|d3<   |d5k(  r_t)        ||      }#t+        |      rAt-        |#      }$|$r;|j                  d6i       }%|%j                  d7i       }&|$|&d8<   |&|%d7<   |%|d6<   n|#r|#|d8<   |j                  d9      }'|'r|j                  |'       |r||d6<   |j                  d:      }(|(r|j                  |(       |S # t$        t&        f$ r d}Y w xY w);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  
 y wr`   rK   )rX   pr{   s     r   rZ   z8ChatCompletionsTransport.build_kwargs.<locals>.<genexpr>a  s     DA$D   	developerr   rN   timeoutNrv   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_namer>   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googler1   extra_body_additionsrequest_overrides)ru   r   _build_kwargs_from_profiler   r   r   rf   r   rg   r   r   r   updater9   r   r   r   r;   	TypeError
ValueErrorr2   rB   r=   )*rL   r   rN   rv   paramsrd   _profile
api_kwargsr   max_tokens_fn	ephemeralr   anthropic_max_outr   r   r   r   _kimi_thinking_off_kimi_effort_e_tokenhub_thinking_off_tokenhub_effort
_lm_effortr   r   r   r   r   r>   provider_prefs_pareto_score_pareto_score_f_kimi_thinking_enabledgh_reasoning_effortraw_thinking_configr1   openai_compat_extragoogle_extra	additions	overridesr{   s*                                            @r   build_kwargsz%ChatCompletionsTransport.build_kwargs  s   l ))(%)@	 ::0122%E6  jj"0C0C0EF9Q<.!  (H4D.CDDYI@il@FK@IaL !&


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

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

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

;OQV0W0 

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

?E:**Y.!::&8%@FJJ7=2>DDFLLN::j)$:;m%3Jz"
 U&>>"JJ'DEM(]b-@+&+M&:O #.3/3PS3P.OT-Jy)
 %)"J/?$F#''	2e;-2*%;	&Jz" ::*E26::mUZ;[%zz*BC+.:J{+"#
3CT(J.228XFR(G6:g*N
;'H$"?GW"X0:"DEX"Y"*4..r*J'#6#:#:8R#HL6EL!234@'1/BJ|,$0C
,- JJ56	i('1J|$ JJ23	i(q ":. +&*O+s   U U0/U0c                 R   ddl m} |j                  |      }|xs dj                         |r[t	        |d   t
              rH|d   j                  d      dk(  r1t        fdt        D              rt        |      }i |d   ddi|d<   ||d}|j                  |u rn4|j                  |j                  |d	<   n|j                  d	      }|||d	<   |j                  d
      }	|	|	|d
<   |rt        |      rt        |      }||d<   |j                  d      }
|j                  d      }|j                  d      }|j                  d      }|j                  |      }||
r|j                   |
|             n?||
r|j                   |
|             n#|r|
r|j                   |
|             n|||d<   t        ||j                  d            }|j!                  ||j                  dd      |j                  d      ||j                  d      |j                  d      |j                  d            \  }}|j                  |       i }|j#                  |j                  d      |j                  d      ||j                  d      ||j                  d            }|r|j                  |       |r|j                  |       |j                  d      }|r|j                  |       |j                  d      }|rD|j%                         D ]1  \  }}|dk(  r"t	        |t
              r|j                  |       -|||<   3 |rN	 ddlm}  ||j                  d            }|r'|j%                         D ci c]  \  }}|dv r|| }}}|r||d<   |S # t*        $ r d}Y ?w xY wc c}}w ) 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   r|   r}   c              3   &   K   | ]  }|v  
 y wr`   rK   )rX   r   _model_lowers     r   rZ   zFChatCompletionsTransport._build_kwargs_from_profile.<locals>.<genexpr>  s     E!A%Er   r   r   temperaturer   rv   r   r   r   r   r   r   Fqwen_session_metadatar>   ollama_num_ctx
session_id)r   r   r   r   r>   r   r   r   r   )r   r   r   r>   r   r   r   r   r   )is_native_gemini_base_url)r1   thinkingConfig)providers.baser   prepare_messagesr   r   r   r   rf   r   rg   fixed_temperaturer   r   get_max_tokensr   r   build_api_kwargs_extrasbuild_extra_bodyitemsagent.gemini_native_adapterr   	Exception)rL   profiler   rd   rv   r   r   r   tempr   r   r   user_maxanthropic_maxprofile_maxr   extra_body_from_profiletop_level_from_profiler   profile_bodyr   r   rY   vr   _native_geminir   s                             @r   r   z3ChatCompletionsTransport._build_kwargs_from_profile  s    	4 ,,Y7	 **,9Q<.!  (H4E/DEEYI@il@FK@IaL !&

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

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

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

3P(Q 0 
 l+ #56 JJ56	i( JJ23	!) &1$At)<%%a($%JqM	& 'Q!:6::j;Q!R %/%5%5%7!QAA qD
  +5
<(  '!&'s   N 5N#N N responsec           
         |j                   d   }|j                  }|j                  }t        |t              rt        |      }|xs d}d}|j                  rg }|j                  D ]  }i }	t        |dd      }
|
Ct        |d      r7t        |j                  t              r|j                  ni j                  d      }
|
"t        |
d      r	 |
j                         }
|
|	d<   |j                  t        |j                   |j"                  j$                  |j"                  j&                  |	xs d              d}t        |d      rS|j(                  rG|j(                  }t+        t        |d	d      xs dt        |d
d      xs dt        |dd      xs d      }t        |dd      }t        |dd      }|6t        |d      r*t        |dd      xs i }t        |t              r	d|v r|d   }i }|||d<   t        |dd      }|r||d<   |j,                  }t        |dd      }|>t        |d      r2t        |dd      xs i }t        |t              r|j                  d      }t        |t
              rN|j/                         r>||d<   t        |t
              xr |j/                         }t1        |      }|s
|s|}|dv rd}t3        ||||||xs d      S # t        $ r Y w xY w)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   stopNr^   model_extra
model_dump)r   name	argumentsprovider_datausageprompt_tokenscompletion_tokenstotal_tokens)r   r   r   r   reasoning_contentreasoning_detailsrefusal)Nr   content_filter)contentr[   finish_reasonr   r   r   )choicesmessager   r   r:   r   r[   getattrhasattrr   r   r   r   r   appendr   r   functionr   r   r   r   r   r   r9   r
   )rL   r   rj   choicerb   _frr   r[   rm   tc_provider_dataextrar   ur   r   r   r   rdr   r   
_msg_extra	_has_text_has_tool_callss                          r   normalize_responsez+ChatCompletionsTransport.normalize_response  s    !!!$nn""c3c(Cv
>>Jnn 
 46 OT:=WR%?/9"..$/OR^^UW\\]lmE$ul3!$)$4$4$6E 9>$_5!!55[[--"$++"7"7&6&>$	!2 8W%(..A%a!<A")!-@!"D"I$Q:?aE Cd3	#C)<dC$m)D!#}d;ArK+t,1D1S$/0C$D!(*(1BM-.S-t413M-. ++#y$/?wsM: mT:@bJ*d+$..3gs# (/M)$"7C0DW]]_I":.O _! N2$4M!!''/4
 	
S  ) ! !s   K	KKc                 V    |yt        |d      r|j                  y|j                  syy)z&Check that response has valid choices.Fr   T)r   r   )rL   r   s     r   validate_responsez*ChatCompletionsTransport.validate_response  s2    x+x/?/?/Gr   c                     t        |dd      }|yt        |dd      }|rt        |dd      xs dnd}|rt        |dd      xs dnd}|st        |dd      xs d}|s|r||dS y)	zExtract cache stats from prompt_tokens_details (OpenRouter/OpenAI)
        or DeepSeek's native top-level prompt_cache_hit_tokens field.r   Nprompt_tokens_detailscached_tokensr   cache_write_tokensprompt_cache_hit_tokens)r  creation_tokens)r   )rL   r   r   detailscachedwrittens         r   extract_cache_statsz,ChatCompletionsTransport.extract_cache_stats  s     '40=%!8$?>E/15:1DK''#7;@qQR U$=qAFQFW%+HHr   r`   )__name__
__module____qualname____doc__propertyr   rM   rg   r   r   ru   rx   r   r   r
   r   r9   r   r:   r  rK   r   r   rH   rH      s1   
 "# " "ET#s(^,E	d38n	EN4S#X#7 Dc3h<P  .2	hh tCH~&h DcN#d*	h 
c3hhTL\q
3 q
=O q
f# $ C DcNT4I r   rH   )register_transportrJ   )r  typingr   r   agent.lmstudio_reasoningr   agent.moonshot_schemar   r   agent.prompt_builderr   agent.transports.baser	   agent.transports.typesr
   r   r   r   r   r   r2   r=   r9   rB   rF   rH   agent.transportsr  rK   r   r   <module>r     s   	  < L 6 3 F Fs dTk dUYk 5 5t 5PTW[P[ 5ptd{ td{ * * *)S )T ) Y
0 Y
z 0 %'? @r   