
    sj                     x   d Z ddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
mZ ddlZddlZ ej                    dz  dz  Z ej                    dz  dz  Z ej                    dz  dz  Ze                                s ej        d	e             ej        e                                          ZdZe                                                                D ]GZe                    d
          r0e                    dd          d                                         Z nHes ej        d           di iZe                                r3	  ej        e                                          Zn# e$ r di iZY nw xY we                     dd          Z!ed         ed         z   Z" ej#        ej$        d            ej%        d          Z&de'de(fdZ)de'fdZ*d Z+de'de,de-e.e,f         fdZ/de,de-e.e,f         fdZ0de,de-e.e,f         fdZ1 G d d e          Z2d! Z3e4d"k    r e3             dS dS )#a  
OpenRouter -> Ollama Fallback Proxy
Listens on 127.0.0.1:11435, exposes an OpenAI-compatible /v1/chat/completions endpoint.

Strategy:
  1. Try OpenRouter models in order (primary -> secondary)
  2. On 429 (rate limit) or other transient error -> try next model
  3. If ALL OpenRouter models fail -> fall back to local Ollama
  4. Cooldown tracking to avoid hammering rate-limited models
    N)Path)
HTTPServerBaseHTTPRequestHandlerz.hermeszopenrouter_proxy.jsonzopenrouter.envzopenrouter_proxy_state.jsonzConfig not found: zOPENROUTER_API_KEY==   z(OPENROUTER_API_KEY not found in env file	cooldownsrate_limit_cooldown_seconds<   openrouter_models_primaryopenrouter_models_secondaryz&%(asctime)s %(levelname)s: %(message)s)levelformatopenrouter_proxymodelreturnc                 r    t           d                             | d          }t          j                    |k     S )Nr   r   )stategettime)r   untils     3C:\Users\server\.hermes\scripts\openrouter_proxy.pyis_cooling_downr   :   s-    +""5!,,E9;;    c                     t          j                     t          z   t          d         | <   t                              t          j        t          d                     d S )Nr      indent)r   cooldown_secondsr   
STATE_FILE
write_textjsondumps)r   s    r   put_on_cooldownr#   ?   sF     $	.> >E+u$*U155566666r   c                       t          j                       fdt          d                                         D             t          d<   t                              t          j        t          d                     d S )Nc                 (    i | ]\  }}|k    ||S  r&   ).0mtnows      r   
<dictcomp>z%cleanup_cooldowns.<locals>.<dictcomp>F   s$    QQQ41aS!Qr   r   r   r   )r   r   itemsr   r    r!   r"   )r*   s   @r   cleanup_cooldownsr-   D   sf    
)++CQQQQ5+=+C+C+E+EQQQE+$*U155566666r   bodyc           
        K   d}t          j        i |d| i                              d          }t          j                            ||ddt           dddd	
          	 t          j                    }|	                    dfd           d{V }|j
        t          j        |                                                    d                    fS # t          j        j        $ ro}	 t          j        |                                                    d                    }n!# t           $ r dt#          |          i}Y nw xY w|j        |fcY d}~S d}~wt           $ r}ddt#          |          ifcY d}~S d}~ww xY w)z>Returns (status_code, json_response). Caller inspects for 429.z-https://openrouter.ai/api/v1/chat/completionsr   utf-8POSTzBearer application/jsonzhttps://hermes-agent.localzHermes Agent OpenRouter Proxy)AuthorizationContent-TypezHTTP-RefererzX-TitledatamethodheadersNc                  F    t           j                             d          S )Nx   timeouturllibrequesturlopenreqs   r   <lambda>z!call_openrouter.<locals>.<lambda>[       FN**3*<< r   errorW  )r!   r"   encoder>   r?   Requestapi_keyasyncioget_event_looprun_in_executorstatusloadsreaddecoderE   	HTTPError	Exceptionstrcode)	r   r.   urlpayloadlooprespeerr_bodyrB   s	           @r   call_openrouterr[   J   s     
9Cj1D1'5112299'BBG
.
 
 '&0w00.86	
 
 !  C&%''))<<<<
 
 
 
 
 
 
 
 {DJtyy{{'9'9''B'BCCCC<!      	)z!&&((//'":":;;HH 	) 	) 	)Q(HHH	)vx & & &Wc!ff%%%%%%%%&sO   $A1C E>+9D%$E%E EEEE>E>!E93E>9E>c                   K   t           d         dz   }t          j        i | dt           d         i                              d          }t          j                            ||dddi	          	 t          j                    }|	                    d
fd           d
{V }|j
        t          j        |                                                    d                    fS # t          j        j        $ ro}	 t          j        |                                                    d                    }n!# t           $ r dt#          |          i}Y nw xY w|j        |fcY d
}~S d
}~wt           $ r}ddt#          |          ifcY d
}~S d
}~ww xY w)z5Forward to local Ollama (OpenAI-compatible endpoint).
ollama_url/chat/completionsr   ollama_modelr0   r1   r4   r2   r5   Nc                  F    t           j                             d          S )NiX  r;   r=   rA   s   r   rC   zcall_ollama.<locals>.<lambda>r   rD   r   rE   rF   )cfgr!   r"   rG   r>   r?   rH   rJ   rK   rL   rM   rN   rO   rP   rE   rQ   rR   rS   rT   )r.   rU   rV   rW   rX   rY   rZ   rB   s          @r   call_ollamarb   h   s     
l
1
1Cj?D?'3~+>??@@GGPPG
.
 
 76+9;M*N ! P PC&%''))<<<<
 
 
 
 
 
 
 
 {DJtyy{{'9'9''B'BCCCC<!      	)z!&&((//'":":;;HH 	) 	) 	)Q(HHH	)vx & & &Wc!ff%%%%%%%%&sO   2A1C$ $F99D32E"3EE"EE"F"F/FFFc                   K   t                       t          D ]}t          |          rt                              d| d           1t                              d|            t          ||            d{V \  }}|dk    r#t                              d|            ||fc S |dk    r.t                              d| d	           t          |           t                              d
| d| d|                    dd          dd                     t                              d           t          |            d{V \  }}|dk    rjd|v rf|d         r^d|d         d                             di                               dd          pdz   |d         d         
                    di           d<   ||fS )z9Main handler: try OpenRouter models, fall back to Ollama.zSkip z (cooldown)zTrying OpenRouter: N   zOK from i  z	429 from z, putting on cooldownzError z from z: rE    d   zHAll OpenRouter models failed/rate-limited. Falling back to local Ollama.choicesz[ollama-fallback] r   messagecontent)r-   all_openrouter_modelsr   loginfor[   warningr#   r   rb   
setdefault)r.   r   rM   rX   s       r   handle_chatro      s       '  5!! 	HH/U///000.u..///,UD99999999S==HH'''(((4<S==KK@E@@@AAAE"""QVQQ5QQDHHWb4I4I$3$4OQQRRR KKZ[[[$T********LFD}}i$Y(:(>(>y"(M(M(Q(QR[]_(`(`(fdfg OA)))R88C4<r   c                        e Zd Zd Zd Zd ZdS )ProxyHandlerc                 n    t                               |                                  d||z              d S )Nz - )rk   rl   address_string)selfr   argss      r   log_messagezProxyHandler.log_message   s8    D''))==ftm==>>>>>r   c                    | j         dvr|                     dd           d S t          | j                            dd                    }	 | j                            |          }t          j        |          }n1# t          $ r$}|                     dd|            Y d }~d S d }~ww xY wt          j                    }t          j        |           	 |                    t          |                    \  }}|                                 n# |                                 w xY w|                     |           |                     dd	           t          j        |                              d
          }|                     dt)          t+          |                               |                                  | j                            |           d S )N)z/v1/chat/completionsr^     z#Only /v1/chat/completions supportedContent-Lengthr   i  z
Bad JSON: r4   r2   r0   )path
send_errorintr8   r   rfilerO   r!   rN   rR   rJ   new_event_loopset_event_looprun_until_completero   closesend_responsesend_headerr"   rG   rS   lenend_headerswfilewrite)	rt   lengthrawr.   rY   rW   rM   rX   rV   s	            r   do_POSTzProxyHandler.do_POST   s   9IIIOOC!FGGGFT\%%&6::;;	*//&))C:c??DD 	 	 	OOC!1a!1!1222FFFFF	
 %''t$$$	22;t3D3DEELFDJJLLLLDJJLLLL6""");<<<*T""))'22)3s7||+<+<===
!!!!!s$   .A: :
B(B##B(%D D#c                    | j         dv r|                     d           |                     dd           t          j        dt          t                    t          d         d                              d          }|                     d	t          t          |                               | 
                                 | j                            |           d S |                     d
           d S )N)/z/healthrd   r4   r2   okr   )rM   models_availabler   r0   ry   rx   )rz   r   r   r!   r"   r   rj   r   rG   rS   r   r   r   r{   )rt   rV   s     r   do_GETzProxyHandler.do_GET   s    9(((s###^-?@@@j$'(=$>$>";/" "   vg	 
 -s3w<</@/@AAAJW%%%Fr   N)__name__
__module____qualname__rv   r   r   r&   r   r   rq   rq      sA        ? ? ?" " "6    r   rq   c                  2   t           d                             d          \  } }t          |          }t          | |ft                    }t
                              d|  d|            t
                              dt                      t
                              dt           d          dt           d          d	           	 |                                 d S # t          $ r2 t
                              d
           |
                                 Y d S w xY w)Nproxy_listen:z%OpenRouter Proxy listening on http://zModels: zOllama fallback: r]   z (r_   )zStopping...)ra   splitr|   r   rq   rk   rl   rj   serve_foreverKeyboardInterruptshutdown)hostportservers      r   mainr      s   ^$**3//JD$t99Dtl33FHHBTBBDBBCCCHH/-//000HHL\!2LLc.6ILLLMMM   s   C 8DD__main__)5__doc__r!   ossysr   rJ   loggingpathlibr   http.serverr   r   urllib.requestr>   urllib.errorhomeCFG_FILEENV_FILEr   existsexitrN   	read_textra   rI   
splitlinesline
startswithr   stripr   rR   r   r   rj   basicConfigINFO	getLoggerrk   rS   boolr   r#   r-   dicttupler|   r[   rb   ro   rq   r   r   r&   r   r   <module>r      s  	 	  				 



          : : : : : : : :         49;;"%<<49;;"%55TY[[9$'DD
 .CH,(,,---dj##%%&&   ++--  D,-- **S!$$Q'--//  9CH7888 
b ""
://1122 " " "b!" 778"== 783?\;]]   ',CE E E Eg*++3 4    
73 7 7 7 7
7 7 7& &D &U395E & & & &<&D &U39%5 & & & &.D U39%5    D, , , , ,) , , ,^   zDFFFFF s   "E7 7	FF