
    epjE                      d Z ddlm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	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ZddlZddlmZmZmZ ddlmZ ddlmZ ddlmZmZ ddlmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z% ddl&Z&ddl'Z'	 ddl(Z(ddl)Z(d	Z*n# e+$ r d
Z*dZ(Y nw xY wddl,m-Z-m.Z. ddl/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5m6Z6 ddl7m8Z9 ddl:m;Z; ddl<m=Z>m?Z?m@Z@mAZAmBZBmCZCmDZD ddlEmFZFmGZGmHZHmIZImJZJmKZKmLZLmMZMmNZNmOZOmPZPmQZQmRZRmSZSmTZTmUZUmVZVmWZWmXZXmYZYmZZZm[Z[m\Z\ ddl]m^Z^  ej_        e`          Za	 ddlbmcZd n# e+$ r dZdY nw xY wedZeedZf egeM          Zhe&ji        ZjdZkdZldZmdZndZodZpdZqdZrh dZsdZth dZuh dZvdZwdZxd Zyd!Zzd"Z{ ej|        d#          Z} ej|        d$          Z~ eh d%          Z ej|        d&          Zd'Zd(Zd)Zd*Zd(Z G d+ d,          Z G d- d.          Zdd/lmZmZ e G d0 d1                      Z G d2 d3e          Z G d4 d5          Z G d6 d7e          Z G d8 d9e          Z G d: d;e          Z G d< d=e          Z G d> d?e          Z G d@ dAe          Z G dB dC          Z G dD dEe          Z G dF dGe          Z G dH dIe          Z G dJ dKe          Z G dL dMe          Z G dN dOe          Z G dP dQe          Z G dR dSe          Z G dT dUe          Z G dV dWe          Z G dX dYe          Z G dZ d[e          Z G d\ d]e          Z G d^ d_e          Z G d` dae          Z G db dc          Z G dd de          Z G df dge          Z G dh die          Z G dj dke          Z G dl dme          Z G dn doe          Z G dp dqe          Z G dr ds          Z G dt du          Z G dv dw          Z G dx dy          Z G dz d{          Z G d| d}e0          ZddZ	 dddZdS )aQ  
Yuanbao platform adapter.

Connects to the Yuanbao WebSocket gateway, handles authentication (AUTH_BIND),
heartbeat, reconnection, message receive (T05) and send (T06).

Configuration in config.yaml (or via env vars):
    platforms:
      yuanbao:
        extra:
          app_id: "..."              # or YUANBAO_APP_ID
          app_secret: "..."          # or YUANBAO_APP_SECRET
          bot_id: "..."              # or YUANBAO_BOT_ID  (optional, returned by sign-token)
          ws_url: "wss://..."        # or YUANBAO_WS_URL
          api_domain: "https://..."  # or YUANBAO_API_DOMAIN
    )annotationsN)datetimetimezone	timedelta)Enum)Path)ABCabstractmethod)AnyCallableClassVarDictIteratorListOptionalTupleTF)PlatformPlatformConfig)BasePlatformAdapterMessageEventMessageType
SendResultcache_document_from_bytescache_image_from_bytescache_video_from_bytes)helpers)MessageDeduplicator)download_urlget_cos_credentialsupload_to_cosbuild_image_msg_bodybuild_file_msg_bodyguess_mime_typemd5_hex)CMD_TYPE_fields_to_dict_get_string_get_varint_parse_fieldsWS_HEARTBEAT_RUNNINGWS_HEARTBEAT_FINISHHERMES_INSTANCE_IDdecode_conn_msgdecode_inbound_pushdecode_forward_msg_datadecode_query_group_info_rsp decode_get_group_member_list_rspencode_auth_bindencode_pingencode_push_ackencode_send_c2c_messageencode_send_group_messageencode_send_private_heartbeatencode_send_group_heartbeatencode_query_group_infoencode_get_group_member_listnext_seq_no)build_session_key)__version__z0.0.0z0wss://bot-wss.yuanbao.tencent.com/wss/connectionzhttps://bot.yuanbao.tencent.comg      >@      .@      $@d         ?>                  >         >                @     r@g      ^@u?   任务有点复杂，正在努力处理中，请耐心等待...zB\[(image|voice|video|file(?::[^|\]]*)?)\|ybres:([A-Za-z0-9_\-]+)\]z\[(\w+):[^\]]*?(/[^\]]+?)\s*\]>   fileimagevideoz\s*\(\d+/\d+\)$2            c                  <   e Zd ZdZedd            Zedd            Ze	 ddd            Zedd            Zedd            Z	ed d            Z
e	 	 d!d"d            Zed#d            Zed$d            Zed%d            Zed%d            Zed&d            ZdS )'MarkdownProcessoraa  Encapsulates all Markdown-related utilities for the Yuanbao platform.

    Provides static methods for:
    - Fence detection and streaming merge
    - Table row detection and sanitization
    - Paragraph-boundary splitting
    - Atomic-block extraction and chunk splitting
    - Outer markdown fence stripping
    - Markdown hint prompt generation
    textstrreturnboolc                *    t          j        |           S )z7Detect whether the text has unclosed code block fences.)_mdchunktext_has_unclosed_fencerZ   s    ?/home/thesage/.hermes/hermes-agent/gateway/platforms/yuanbao.pyhas_unclosed_fencez$MarkdownProcessor.has_unclosed_fence   s     /555    c                *    t          j        |           S )z.Detect whether the text ends with a table row.)r_   text_ends_with_table_rowra   s    rb   ends_with_table_rowz%MarkdownProcessor.ends_with_table_row   s     0666rd   N	max_charsintlen_fnOptional[Callable[[str], int]]tuple[str, str]c                0    t          j        | ||          S )zJFind the nearest paragraph boundary within max_chars; return (head, tail).rj   )r_   split_at_paragraph_boundary)rZ   rh   rj   s      rb   ro   z-MarkdownProcessor.split_at_paragraph_boundary   s     3D)FSSSSrd   c                *    t          j        |           S )zDDetermine whether an atomic block is a code block (starts with ```).)r_   is_fence_atomra   s    rb   rq   zMarkdownProcessor.is_fence_atom        %d+++rd   c                *    t          j        |           S )zHDetermine whether an atomic block is a table (first line starts with |).)r_   is_table_atomra   s    rb   rt   zMarkdownProcessor.is_table_atom   rr   rd   	list[str]c                *    t          j        |           S )z6Split text into a list of indivisible "atomic blocks".)r_   split_markdown_atomsra   s    rb   split_into_atomsz"MarkdownProcessor.split_into_atoms   s     ,T222rd     c                4    t          j        |||dd          S )a(  
        Split Markdown text into multiple chunks by max_chars.

        Guarantees (provided by the shared core, prefer_paragraphs mode):
        - Each chunk <= max_chars characters (unless a single code block/table itself exceeds the limit)
        - Code blocks (```...```) are not split in the middle
        - Table rows are not split in the middle (tables output as atomic blocks)
        - Split at paragraph boundaries (blank lines, after periods, etc.)
        - Small trailing/leading chunks are merged with neighbours when possible
        TF)prefer_paragraphsbalance_fences)r_   split_text_fence_aware)clsrZ   rh   rj   s       rb   chunk_markdown_textz%MarkdownProcessor.chunk_markdown_text   s-    " ." 
 
 
 	
rd   
prev_chunk
next_chunkc                ,    t          j        ||          S )z>Infer the separator ('\n' or '\n\n') between two split chunks.)r_   infer_block_separator)r~   r   r   s      rb   r   z'MarkdownProcessor.infer_block_separator  s     -j*EEErd   chunksc                *    t          j        |          S )z=Stream-aware fence-conscious chunk merging (see shared core).)r_   merge_streaming_fences)r~   r   s     rb   merge_block_streaming_fencesz.MarkdownProcessor.merge_block_streaming_fences   s     .v666rd   c                X   | s| S |                      d          }t          |          dk     r| S |d                                         }|d                                         }t          j        d|t          j                  s| S |dk    r| S d                    |dd                   }|S )a  
        Strip outer Markdown fence.

        When AI reply is entirely wrapped in ```markdown\n...\n```, remove the outer fence,
        keeping the content. Only strip when the first line is ```markdown (case-insensitive) and the last line is ```.

        Args:
            text: Text to process

        Returns:
            Text with outer fence stripped (returns original if no match)
        
   r   z^```(?:markdown|md)?\s*$z```rW   )splitlenstriprematch
IGNORECASEjoin)rZ   lines
first_line	last_lineinners        rb   strip_outer_markdown_fencez,MarkdownProcessor.strip_outer_markdown_fence'  s      	K

4  u::>>K1X^^%%
"IOO%%	 x3ZOO 	K K 		%"+&&rd   c                f   d| vr| S |                      d          }g }|D ]}|                                }|                    d          r|                    d          rt	          j        d|          rJ|                     d          }d                    d |D                       }|                    |           |dk    s,|                    dd                                          dk    r|                    |           |                    |           d                    |          S )a  
        Table output sanitization.

        Handle common formatting issues in AI-generated Markdown tables:
        1. Remove extra whitespace before/after table rows
        2. Ensure separator rows (|---|---|) are correctly formatted
        3. Remove empty table rows

        Args:
            text: Markdown text containing tables

        Returns:
            Sanitized text
        |r   z^\|[\s\-:]+(\|[\s\-:]+)+\|$c              3  j   K   | ].}|                                 r|                                 n|V  /d S Nr   ).0cells     rb   	<genexpr>z<MarkdownProcessor.sanitize_markdown_table.<locals>.<genexpr>k  sO       * *  )-

>

$* * * * * *rd   z|| )	r   r   
startswithendswithr   r   r   appendreplace)rZ   r   result_lineslinestrippedcells
normalizeds          rb   sanitize_markdown_tablez)MarkdownProcessor.sanitize_markdown_tableM  sS     d??K

4  "$ 	* 	*Dzz||H ""3'' *H,=,=c,B,B *8:HEE 2$NN3//E!$ * *$)* * * " "J !''
3333%%)9)9#r)B)B)H)H)J)Jb)P)P ''1111##D))))yy&&&rd   c                     	 dS )z
        Markdown rendering hint (appended to system prompt).

        Tell AI that Yuanbao platform supports Markdown rendering, including:
        - Code blocks (```lang)
        - Tables (| col | col |)
        - Bold/italic
        a  The current platform supports Markdown rendering. You can use the following formats:
- Code blocks: ```language\ncode\n```
- Tables: | col1 | col2 |\n|---|---|\n| val1 | val2 |
- Bold: **text** / Italic: *text*
Please use Markdown formatting when appropriate to improve readability. r   rd   rb   markdown_hint_system_promptz-MarkdownProcessor.markdown_hint_system_prompt|  s    V	
 	
rd   )rZ   r[   r\   r]   r   )rZ   r[   rh   ri   rj   rk   r\   rl   )rZ   r[   r\   ru   ry   N)rZ   r[   rh   ri   rj   rk   r\   ru   )r   r[   r   r[   r\   r[   )r   ru   r\   ru   rZ   r[   r\   r[   r\   r[   )__name__
__module____qualname____doc__staticmethodrc   rg   ro   rq   rt   rx   classmethodr   r   r   r   r   r   r   rd   rb   rY   rY      s       	 	$ 6 6 6 \6 7 7 7 \7  26T T T T \T , , , \, , , , \, 3 3 3 \3  15	
 
 
 
 [
4 F F F [F 7 7 7 [7 ! ! ! \!J *' *' *' \*'\ 
 
 
 \
 
 
rd   rY   c                  ,   e Zd ZU dZdZdZdZdZdZdZ	i Z
ded	<   i Zd
ed<   ed%d            Zed&d            Zed'd            Zed(d            Zed)d            Zed*d            Ze	 d+d,d!            Ze	 d+d,d"            Ze	 d+d,d#            Zd$S )-SignManagera  Encapsulates all sign-token related logic for the Yuanbao platform.

    Manages token acquisition, caching, signature computation, and
    automatic retry.  All state (cache, locks) is kept as class-level
    attributes so that a single shared client serves the whole process.
    z/api/v5/robotLogic/sign-tokenis'  r   rA   <   r?   zdict[str, dict[str, Any]]_cachezdict[str, asyncio.Lock]_locksapp_keyr[   r\   asyncio.Lockc                d    || j         vrt          j                    | j         |<   | j         |         S )zReturn (creating if needed) the per-app_key refresh lock.

        Must only be called from within a running event loop (async context).
        )r   asyncioLock)r~   r   s     rb   get_refresh_lockzSignManager.get_refresh_lock  s0     #*$$"),..CJwz'""rd   nonce	timestamp
app_secretc                    | |z   |z   |z   }t          j        |                                |                                t          j                                                  S )zCompute HMAC-SHA256 signature (aligned with TypeScript original).

        plain     = nonce + timestamp + app_key + app_secret
        signature = HMAC-SHA256(key=app_secret, msg=plain).hexdigest()
        )hmacnewencodehashlibsha256	hexdigest)r   r   r   r   plains        rb   compute_signaturezSignManager.compute_signature  sN     	!G+j8x
))++U\\^^W^LLVVXXXrd   c                     t          j        t          t          d                              } |                     d          S )zlBuild Beijing-time ISO-8601 timestamp (no milliseconds).

        Format: 2006-01-02T15:04:05+08:00
           )hourstzz%Y-%m-%dT%H:%M:%S+08:00)r   nowr   r   strftime)bjtimes    rb   build_timestampzSignManager.build_timestamp  s<     )!*<*<*<!=!=>>>8999rd   entrydict[str, Any]r]   c                L    |d         t          j                     z
  | j        k    S )zEDetermine whether the cache entry is valid (not expired with margin).	expire_ts)timeCACHE_REFRESH_MARGIN_S)r~   r   s     rb   is_cache_validzSignManager.is_cache_valid  s"     [!DIKK/#2LLLrd   Nonec                8    | j                                          dS )z;Clear all per-app_key refresh locks (called on disconnect).N)r   clearr~   s    rb   clear_lockszSignManager.clear_locks  s     	
rd   ri   c                    t          j                     fd| j                                        D             }|D ]}| j                            |d           t	          |          S )zRemove all expired entries from the token cache.

        Returns the number of entries purged.  Called lazily from
        ``get_token()`` so that stale app_key entries don't accumulate
        indefinitely in long-running processes.
        c                T    g | ]$\  }}|                     d d          z
  dk    "|%S )r   r   get)r   kvr   s      rb   
<listcomp>z-SignManager.purge_expired.<locals>.<listcomp>  sE     
 
 
!QQUU;***Q.. ...rd   N)r   r   itemspopr   )r~   expired_keysr   r   s      @rb   purge_expiredzSignManager.purge_expired  s~     ikk
 
 
 
***,,
 
 
  	$ 	$AJNN1d####<   rd   r   
api_domain	route_envc                  K   |                     d           | j         }t          j        | j                  4 d{V }t          | j        dz             D ]P}t          j        d          }| 	                                }	| 
                    ||	||          }
|||
|	d}dt          t          t          t          d}|r||d	<   t                              d
||dk    rd| d| j         dnd           |                    |||           d{V }|j        dk    r)|j        }t)          d|j         d|dd                    	 |                                }n%# t,          $ r}t/          d|           |d}~ww xY w|                    d          }|dk    r|                    d          }t3          |t4                    st/          d|           t                              d|                    d                     |c cddd          d{V  S || j        k    rW|| j        k     rLt                              d|| j        |dz   | j                   t=          j        | j                   d{V  '|                    dd          }t)          d| d|           	 ddd          d{V  n# 1 d{V swxY w Y   t)          d          )zHSend sign-ticket HTTP request with auto-retry (up to MAX_RETRIES times)./timeoutNrW      )r   r   	signaturer   application/json)Content-TypezX-AppVersionzX-OperationSystemzX-Instance-IdzX-Bot-VersionzX-Route-EnvzSign token request: url=%s%sr   z (retry )r   )jsonheaders   zSign token API returned : z!Sign token response parse error: codedataz*Sign token response missing 'data' field: zSign token success: bot_id=%sbot_idz>Sign token retryable: code=%s, retrying in %ss (attempt=%d/%d)msgzSign token error: code=, msg=z'Sign token failed: max retries exceeded) rstrip
TOKEN_PATHhttpxAsyncClientHTTP_TIMEOUT_SrangeMAX_RETRIESsecrets	token_hexr   r   _APP_VERSION_OPERATION_SYSTEM_YUANBAO_INSTANCE_ID_BOT_VERSIONloggerinfopoststatus_coderZ   RuntimeErrorr   	Exception
ValueErrorr   
isinstancedictRETRYABLE_CODEwarningRETRY_DELAY_Sr   sleep)r~   r   r   r   r   urlclientattemptr   r   r   payloadr   responsebodyresult_dataexcr   r   r   s                       rb   fetchzSignManager.fetch  sK      ""3''999$S-?@@@ <	P <	P <	P <	P <	P <	P <	PF 1!455 ;P ;P)"--//11	11%GZXX	  '"!*!*	  %7$0):%9%1   7-6GM*2?F{{;w;;;;;;PR   "(Sw!P!PPPPPPP'3..#=D&'f(BV'f'fZ^_c`c_cZd'f'fgggY2:--//KK  Y Y Y$%N%N%NOOUXXY #v..199&??622D%dD11 e()cVa)c)cdddKK ?(ASASTTTKK]<	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P` 3---'CO2K2KNNX)!   "-(9:::::::::!ooeR00"#NT#N#N#N#NOOOw;P<	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P| DEEEs?   DJ>	EJ>
F (E;;F  BJ>BJ>>
KKc           	       K   |                                   | j                            |          }|rh|                     |          rSt	          |d         t          j                    z
            }t                              d|           t          |          S | 	                    |          4 d{V  | j                            |          }|r6|                     |          r!t          |          cddd          d{V  S | 
                    ||||           d{V }|                    dd          }|dk    rt          j                    |z   nt          j                    dz   }	|                    dd          |                    d	d          ||                    d
d          |                    dd          |	d| j        |<   ddd          d{V  n# 1 d{V swxY w Y   t          | j        |                   S )zGet WS auth token (with cache).

        Return directly on cache hit without re-requesting; treat as expiring
        60 seconds before actual expiry, triggering refresh.
        r   z"Using cached token (%ds remaining)Ndurationr     tokenr   r   productsourcer(  r   r&  r)  r*  r   )r   r   r   r   ri   r   r  r  r  r   r$  )
r~   r   r   r   r   cachedremainr   r&  r   s
             rb   	get_tokenzSignManager.get_token7  s      	(( 	 c((00 	 ,ty{{:;;FKK<fEEE<<''00 	 	 	 	 	 	 	 	Z^^G,,F $#,,V44 $F||	 	 	 	 	 	 	 	 	 	 	 	 	 	
 7J
INNNNNNNND HHZ33H2:Q,,	h..DIKKRVDVI '2..((8R00$88Ir22((8R00&# #CJw	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	& CJw'(((s   6A G"	CG""
G,/G,c           	       K   t                               d|dd                    |                     |          4 d{V  | j                            |d           |                     ||||           d{V }|                    dd          }|dk    rt          j                    |z   nt          j                    dz   }|                    dd          |                    d	d          ||                    d
d          |                    dd          |d| j        |<   ddd          d{V  n# 1 d{V swxY w Y   t          | j        |                   S )z.Force refresh token (clear cache and re-sign).zC[force-refresh] Clearing cache and re-signing token: app_key=****%sNr&  r   r'  r(  r   r   r)  r*  r+  )	r  r  r   r   r   r$  r   r   r  )r~   r   r   r   r   r   r&  r   s           rb   force_refreshzSignManager.force_refreshd  s      	\^efhfifi^jkkk''00 	 	 	 	 	 	 	 	JNN7D)))7J
INNNNNNNND HHZ33H2:Q,,	h..DIKKRVDVI '2..((8R00$88Ir22((8R00&# #CJw	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	  CJw'(((s   C#D66
E E N)r   r[   r\   r   )
r   r[   r   r[   r   r[   r   r[   r\   r[   r   )r   r   r\   r]   r\   r   r\   ri   r   )
r   r[   r   r[   r   r[   r   r[   r\   r   )r   r   r   r   r  r  r  r  r   r  r   __annotations__r   r   r   r   r   r   r   r   r   r$  r.  r1  r   rd   rb   r   r     s          1JNKM   N
 )+F**** ')F(((( # # # [# Y Y Y \Y : : : \: M M M [M    [ ! ! ! [!$  GF GF GF GF [GFV  () () () () [()X  ) ) ) ) [) ) )rd   r   )	dataclassfieldc                     e Zd ZU dZded<    ee          Zded<   dZded	<   d
Z	ded<   d
Z
ded<   d
Zded<   d
Zded<   d
Zded<    ee          Zded<   d
Zded<   d
Zded<   d
Zded<   d
Zded<   d
Zded<   d
Zded<    ee          Zded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded <    ee          Zded!<    ee          Zded"<    ee          Zded#<    ee          Zded$<   dZ ded%<   dS )&InboundContextzMutable context flowing through the inbound middleware pipeline.

    Each middleware reads/writes fields on this context.  The pipeline
    engine passes it to every middleware in registration order.
    r   adapter)default_factorylist
raw_framesNOptional[dict]pushr   r[   decoded_viafrom_account
group_code
group_namesender_nicknamemsg_bodymsg_idcloud_custom_datachat_id	chat_type	chat_nameraw_text
media_refsforwarded_recordsOptional[str]owner_commandzOptional[Any]r*  msg_typereply_to_message_idreply_to_textquote_media_refs
media_urlsmedia_types	link_urlschannel_prompt)!r   r   r   r   r5  dc_fieldr<  r=  r?  r@  rA  rB  rC  rD  rE  rF  rG  rH  rI  rJ  rK  rL  rM  rO  r*  rP  rQ  rR  rS  rT  rU  rV  rW  r   rd   rb   r9  r9    sX          LLLx555J5555  DK LJJOXd333H3333F GII Hx555J5555 )-,,,, $(M'''' !F     #H"""" *.----#'M''''%Xd;;;;;;;  x555J5555 666K6666 ht444I4444 %)N((((((rd   r9  c                  J    e Zd ZU dZdZded<   edd            ZddZddZ	dS )InboundMiddlewarea  Abstract base class for all inbound pipeline middlewares.

    Subclasses must:
      - Set ``name`` as a class-level attribute (used for pipeline registration
        and dynamic insertion/removal).
      - Implement ``async handle(ctx, next_fn)`` containing the middleware logic.

    Convention:
      - Call ``await next_fn()`` to pass control to the next middleware.
      - Return without calling ``next_fn`` to **stop** the pipeline.
    r   r[   namectxr9  next_fnr   r\   r   c                
   K   dS )zEProcess *ctx* and optionally call *next_fn* to continue the pipeline.Nr   selfr\  r]  s      rb   handlezInboundMiddleware.handle  
        rd   c                >   K   |                      ||           d{V S )zFAllow middleware instances to be called directly (duck-typing compat).N)ra  r_  s      rb   __call__zInboundMiddleware.__call__  s,      [[g.........rd   c                2    d| j         j         d| j        dS )N<z name=>)	__class__r   r[  r`  s    rb   __repr__zInboundMiddleware.__repr__  s"    @4>*@@$)@@@@rd   N)r\  r9  r]  r   r\   r   r   )
r   r   r   r   r[  r5  r
   ra  rd  rj  r   rd   rb   rZ  rZ    s         
 
 DNNNNT T T ^T/ / / /A A A A A Ard   rZ  c                  x    e Zd ZdZddZedd            ZdddZdddZdddZ	ddZ
edd            ZddZdS )InboundPipelinea  Onion-model middleware pipeline engine for inbound message processing.

    Inspired by OpenClaw's MessagePipeline (extensions/yuanbao/src/business/
    pipeline/engine.ts).  Supports named middlewares, conditional guards
    (``when``), and ``use_before`` / ``use_after`` / ``remove`` for dynamic
    composition.

    Accepts both ``InboundMiddleware`` instances (OOP style) and plain
    ``async def(ctx, next_fn)`` callables (functional style) for flexibility.
    r\   r   c                    g | _         d S r   _middlewaresri  s    rb   __init__zInboundPipeline.__init__  s    "$rd   Nc                F    t          | t                    r	| j        | fS | |fS )zHNormalize (name, handler) or (InboundMiddleware,) into (name, callable).)r  rZ  r[  )
name_or_mwhandlers     rb   
_normalizezInboundPipeline._normalize  s/     j"344 	/?J..7""rd   'InboundPipeline'c                r    |                      ||          \  }}| j                            |||f           | S )u   Append a middleware to the end of the pipeline.

        Accepts either:
          - ``pipeline.use(SomeMiddleware())``  — OOP style
          - ``pipeline.use("name", some_fn)``   — functional style
        )rt  ro  r   )r`  rr  rs  whenr[  hs         rb   usezInboundPipeline.use  s=     //*g66a  $4111rd   targetr[   c                   |                      ||          \  }}t          fdt          | j                  D             d          }|||f}|| j                            |           n| j                            ||           | S )zEInsert a middleware before *target* (by name).  Appends if not found.c              3  6   K   | ]\  }\  }}}|k    |V  d S r   r   r   in_rz  s       rb   r   z-InboundPipeline.use_before.<locals>.<genexpr>	  1      VV,!YaA!v++A++++VVrd   Nrt  next	enumeratero  r   insert	r`  rz  rr  rs  rw  r[  rx  idxr   s	    `       rb   
use_beforezInboundPipeline.use_before  s    //*g66aVVVV)D4E*F*FVVVX\]]q$;$$U++++$$S%000rd   c                   |                      ||          \  }}t          fdt          | j                  D             d          }|||f}|| j                            |           n| j                            |dz   |           | S )zDInsert a middleware after *target* (by name).  Appends if not found.c              3  6   K   | ]\  }\  }}}|k    |V  d S r   r   r}  s       rb   r   z,InboundPipeline.use_after.<locals>.<genexpr>  r  rd   NrW   r  r  s	    `       rb   	use_afterzInboundPipeline.use_after  s    //*g66aVVVV)D4E*F*FVVVX\]]q$;$$U++++$$S1We444rd   r[  c                8    fd| j         D             | _         | S )zRemove a middleware by name.c                .    g | ]\  }}}|k    |||fS r   r   )r   r  rx  wr[  s       rb   r   z*InboundPipeline.remove.<locals>.<listcomp>  s+    UUU71a1PT99aAY999rd   rn  )r`  r[  s    `rb   removezInboundPipeline.remove  s'    UUUUd6GUUUrd   r<  c                $    d | j         D             S )zAReturn ordered list of registered middleware names (for testing).c                    g | ]\  }}}|	S r   r   )r   r  r  s      rb   r   z4InboundPipeline.middleware_names.<locals>.<listcomp>$  s    333gaA333rd   rn  ri  s    rb   middleware_namesz InboundPipeline.middleware_names!  s     43!23333rd   r\  r9  c                V   K   | j         ddfd              d{V  dS )zKRun all middlewares in order.  Each middleware receives ``(ctx, next_fn)``.r   r\   r   c                    K   t                    k     ra         \  } }}dz  | |          s2	  |           d {V  n,# t          $ r t                              d| d            w xY wd S d S )NrW   z'[InboundPipeline] middleware [%s] errorTexc_info)r   r  r  error)r[  rs  when_fnchainr\  indexr]  s      rb   r]  z(InboundPipeline.execute.<locals>.next_fn-  s      #e**$$).u&gw
&wws||&!'#w//////////    LL!JD[_L```  %$s   A
 
)A3Nr2  rn  )r`  r\  r  r  r]  s    `@@@rb   executezInboundPipeline.execute(  sa      !	 	 	 	 	 	 	 	 	  giird   r2  r   NN)r\   ru  )rz  r[   r\   ru  )r[  r[   r\   ru  )r\   r<  r\  r9  r\   r   )r   r   r   r   rp  r   rt  ry  r  r  r  propertyr  r  r   rd   rb   rl  rl    s        	 	% % % %
 # # # \#	 	 	 	 		 	 	 	 		 	 	 	 	   
 4 4 4 X4     rd   rl  c                  V    e Zd ZdZdZedd            Zedd
            ZddZddZ	dS )DecodeMiddlewarezDecode raw inbound frames from JSON or Protobuf into ctx.push.

    Encapsulates JSON push parsing (aligned with TS decodeFromContent)
    and Protobuf decoding via ``decode_inbound_push``.
    decoderaw_bodyr<  r\   c                   g }| pg D ]}t          |t                    s|                    d          p|                    dd          }|                    d          p|                    di           }t          |t                    r*	 t	          j        |          }n# t          $ r d|i}Y nw xY w|                    ||pi d           |S )zNormalize raw JSON msg_body array to [{"msg_type": str, "msg_content": dict}].

        Compatible with both PascalCase (MsgType/MsgContent) and
        snake_case (msg_type/msg_content) naming.
        rP  MsgTyper   msg_content
MsgContentrZ   rP  r  )r  r  r   r[   r   loadsr  r   )r  resultitemrP  r  s        rb   convert_json_msg_bodyz&DecodeMiddleware.convert_json_msg_bodyI  s     N 
	T 
	TDdD)) xx
++Ftxx	2/F/FH((=11OTXXlB5O5OK+s++ 88"&*["9"9KK  8 8 8#);"7KKK8MMx@QrRRSSSSs   B  B10B1raw_jsonr  dict | Nonec                   | sdS |                      dd          p|                      dd          }|                      dd          p+|                      dd          p|                      dd          }|                      dg           p|                      d	g           }t                              |          }|s|s|                      d
          sdS |                      d
d          ||                      dd          p|                      dd          |                      dd          p|                      dd          ||                      dd          |                      dd          p|                      dd          |                      dd          p+|                      dd          p|                      dd          ||                      dd          p|                      dd          |                      dd          p|                      dd          |                      d          pdt          |                      d          t                    r+|                      d          pi                      dd          nddS )a  Convert JSON-format push to a dict with the same structure as
        ``decode_inbound_push``.

        Supports standard callback format (callback_command + from_account +
        msg_body) and legacy format fields (GroupId, MsgSeq, MsgKey, MsgBody,
        etc.).
        NrA  r   From_AccountrB  GroupIdgroup_idrE  MsgBodycallback_command
to_account
To_AccountrD  	nick_namerC  msg_seqr   MsgSeqrF  msg_keyMsgKeyrG  CloudCustomDatabot_owner_id
botOwnerIdrecall_msg_seq_listlog_exttrace_id)r  rA  r  rD  rB  rC  r  rF  rE  rG  r  r  r  )r   r  r  r  r  )r  rA  rB  msg_body_rawrE  s        rb   parse_json_pushz DecodeMiddleware.parse_json_push^  s     	4 LL,, 0||NB// 	
 LLr** ,||Ir**,||J++ 	 LLR(( +||Ir** 	 $99,GG  	H 	X\\BT5U5U 	4 !)-? D D(",,|R88ZHLLWY<Z<Z'||,=rBBchllS^`bFcFc$",,|R88||Iq11NX\\(A5N5Nll8R00mHLLB4O4OmS[S_S_`hjlSmSm !).A2!F!F!m(,,WhjlJmJm$LL<<^\[]@^@^#+<<0E#F#F#N$OYZbZfZfgpZqZqswOxOx  Ai006B;;JKKK  A
 
 	
rd   r   bytestuplec                0   	 t          j        |                    d                    }n# t          $ r d}Y nw xY wt	          |t
                    r|                     |          }|r|dfS n)	 t          |          }n# t          $ r d}Y nw xY w|r|dfS dS )zFDecode a single raw frame into (push_dict, decoded_via) or (None, '').utf-8Nr   protobufNr   )r   r  r  r  r  r  r  r.   )r`  r:  r   	conn_jsonr?  s        rb   _decode_singlezDecodeMiddleware._decode_single  s    	
4;;w#7#788II 	 	 	III	 i&& 
	(''	22D $V|#$*400    (Z''xs   '* 99.A> >BBr\  r9  r   c                .  K   |j         }|sd S d }d}|D ]}|                     |j        |          \  }}|sEt                              d|j        j        |r|                                d d         nd           h|9|}|}t                              d|j        j        |t          |                     |                    dg           }	|	rZddd	id
}
|                    dg           |
gz   |	z   |d<   t                              d|j        j        t          |	                     |sd S ||_	        ||_
        t                              d|j        j        |j
        |j	                            dd          |j	                            dd          |j	                            dd          d |j	                            dg           D                        t                              d|j        j        |j	                    |             d {V  d S )Nr   z;[%s] Push decoded but no valid message. raw hex(first64)=%s   z(empty)z#[%s] Frame decoded (via=%s): len=%drE  TIMTextElemrZ   r   r  z;[%s] Merged %d extra msg_body elements from aggregated pushzC[%s] Push decoded (via=%s): from=%s group=%s msg_id=%s msg_types=%srA  rB  rF  c                :    g | ]}|                     d d          S )rP  r   r   r   es     rb   r   z+DecodeMiddleware.handle.<locals>.<listcomp>  s&    IIIqQUU:r""IIIrd   z[%s] Push payload: %s)r=  r  r:  r  r  r[  hexr   r   r?  r@  debug)r`  r\  r]  	data_listmerged_pushr@  r   r?  via
extra_body_seps              rb   ra  zDecodeMiddleware.handle  s/     N	 	F 	 	D++CK>>ID# MK$$&Mdhhjj#&6&6I   ""!5K$c3t99    "XXj"55
 (5vtnUUD.9ooj".M.MQUPV.VYc.cK
+KKU(#j//  
  	F%QKcoHLL,,HLLr**HLL2&&IICHLLR,H,HIII	
 	
 	
 	,ck.>IIIgiird   N)r  r<  r\   r<  )r  r  r\   r  )r   r  r\   r  r  )
r   r   r   r   r[  r   r  r  r  ra  r   rd   rb   r  r  >  s          D    \( /
 /
 /
 \/
f   *4 4 4 4 4 4rd   r  c                      e Zd ZdZdZd	dZdS )
ExtractFieldsMiddlewarez8Extract common fields from ctx.push into ctx attributes.zextract-fieldsr\  r9  r\   r   c                  K   |j         }|                    dd          |_        |                    dd          |_        |                    dd          |_        |                    dd          |_        |                    dg           |_        |                    dd          |_        |                    dd          |_         |             d {V  d S )	NrA  r   rB  rC  rD  rE  rF  rG  )	r?  r   rA  rB  rC  rD  rE  rF  rG  )r`  r\  r]  r?  s       rb   ra  zExtractFieldsMiddleware.handle  s      x88NB77,33,33"hh'8"==xx
B//XXh++
 $)<b A Agiird   Nr  r   r   r   r   r[  ra  r   rd   rb   r  r    s3        BBD	 	 	 	 	 	rd   r  c                      e Zd ZdZdZd	dZdS )
DedupMiddlewarezInbound message deduplication.dedupr\  r9  r\   r   c                   K   |j         rQ|j        j                            |j                   r-t                              d|j        j        |j                    d S  |             d {V  d S )Nz)[%s] Duplicate message ignored: msg_id=%s)rF  r:  _dedupis_duplicater  r  r[  r_  s      rb   ra  zDedupMiddleware.handle  sl      : 	#+,99#*EE 	LLDckFVX[XbcccFgiird   Nr  r  r   rd   rb   r  r    s3        ((D     rd   r  c                      e Zd ZdZdZ eddh          ZdZdd
Ze	dd            Z
ddZe	dd            Zedd            Zed d            Ze	 d!d"d            ZdS )#RecallGuardMiddlewareu5  Intercept Group.CallbackAfterRecallMsg / C2C.CallbackAfterMsgWithDraw.

    Branch A: message in transcript (observed, not yet consumed) → redact content
    Branch B: message not in transcript → append system note
    Branch C: message currently being processed → silent interrupt + delayed redact
    recall_guardGroup.CallbackAfterRecallMsgzC2C.CallbackAfterMsgWithDrawzM[This message was recalled/withdrawn by the sender; original content removed]r\  r9  r\   r   c                   K   |j         pi                     dd          }|| j        vr |             d {V  d S |                     ||           d S )Nr  r   )r?  r   _RECALL_COMMANDS_handle_recall)r`  r\  r]  cmds       rb   ra  zRecallGuardMiddleware.handle  sh      x~2""#5r::d+++'))OOOOOOOFC%%%%%rd   rB  r[   rA  c                \    |                      |rd| nd| |rdnd|pd |rdnd           S )Ngroup:direct:groupdmmain)rH  rI  user_id	thread_id)build_source)r:  rB  rA  s      rb   _build_sourcez#RecallGuardMiddleware._build_source  s^    ##.8V*j***>V>V>V!+5gg (D *4ff	 $ 
 
 	
rd   r  c                
   |j         }|j        pi }|dk    r|                    d          pg }n8|                    d          pd}|                    d          }|s|r||dgng }|s"t                              d|j                   d S |                    d          pd                                }|                    d	          pd                                }	|D ]}
|
                    d          p#t          |
                    d          pd          }|s>|                     ||          }|| 	                    |||||	           p|j
                            |          }|                     ||||	|           d S )
Nr  r  rF  r   r  )rF  r  z2[%s] Recall callback with empty seq_list, skippingrB  rA  )r:  r?  r   r  r  r[  r   r[   _find_processing_session_interrupt_for_recall_msg_content_cache_patch_transcript)r`  r\  r  r:  r?  seq_listmidseqrB  rA  	seq_entryrecalled_id
matched_skrecalled_contents                 rb   r  z$RecallGuardMiddleware._handle_recall  s   +x~2000xx 566<"HH((8$$*C((9%%C=@PCP337788bH 	LLMw|\\\Fhh|,,299;;
006B==??! 
	i 
	iI#--11XSy9Q9Q9WUW5X5XK 66wLLJ%**7JZYeffff#*#=#A#A+#N#N &&wZWghhhh
	i 
	ird   r  rN  c                j    | j                                         D ]\  }}||k    r|| j        v r|c S d S r   )_processing_msg_idsr   _active_sessions)r:  r  skr  s       rb   r  z.RecallGuardMiddleware._find_processing_session;  sK    288:: 	 	GBk!!bG,D&D&D			trd   session_keyc           	        |rd| nd| }d| d| d}t          |t          j        |                     |||          d          }||j        |<   |j                            |          }	|	|	                                 t          	                    d|j
        ||d d	                    |j                            |d
          }
|
r|                     |||
||           d S d S )Nzgroup zdirect chat with u_   [CRITICAL — MESSAGE RECALLED] The user message that triggered your current task (message_id="z") in ud   has been recalled/withdrawn by the sender. IGNORE any prior system note asking you to finish processing tool results — the original request is void. Do NOT continue the task, do NOT call more tools, do NOT reference the recalled content. Reply only with a brief acknowledgment such as "The message has been recalled." in the language the user was using.T)rZ   message_typer*  internalz+[%s] Recall interrupt: msg_id=%s session=%s   r   )r   r   TEXTr  _pending_messagesr  r   setr  r  r[  _processing_msg_texts_schedule_content_redact)r~   r:  r  r  rB  rA  whererecall_textsynth_eventactive_eventrecalled_texts              rb   r  z+RecallGuardMiddleware._interrupt_for_recallB  s=    *4[%%%%9[\9[9[	,/:	, 	,CH	, 	, 	, 	 #$)$$Wj,GG	
 
 
 2=!+./33K@@#A7<Q\^ijmkmjm^nooo  599+rJJ 	h((+}jZfggggg	h 	hrd   r  c                     d fd}t          j         |                      }j                            |           |                    j        j                   d S )Nr\   r   c            	     2  K   t          dd           } | sd S 	 |                                         	                    j        }n# t          $ r Y d S w xY wt          d          D ]}t          j        d           d {V  	 |                     |          }n# t          $ r Y @w xY w|D ]}|	                    d          dk    r|	                    d          
k    rj
        |d<   	 |                     ||           t                              dj        d d                    n8# t          $ r+}t                              dj        |           Y d }~nd }~ww xY w  d S t                              d	j        d d                    d S )
N_session_storer  g      ?roleusercontentz[%s] Recall redact: session %sz[%s] Recall redact failed: %sz?[%s] Recall redact: content not found after polling, session %s)getattrget_or_create_sessionr  
session_idr  r  r   r  load_transcriptr   	_REDACTEDrewrite_transcriptr  r  r[  r  r  )storesidr  
transcriptr   r#  r:  r~   rA  rB  r  r  s         rb   _redactz?RecallGuardMiddleware._schedule_content_redact.<locals>._redactk  s     G%5t<<E 11%%gz<HH       2YY  mC(((((((((!&!6!6s!;!;JJ    H'  Eyy((F22uyy7K7K}7\7\+.=i(_!44S*EEE"KK(H',XcdgegdgXhiiii( _ _ _"NN+JGLZ]^^^^^^^^_ LLZ\c\hjuvywyvyjz{{{{{s;   /A
 

AAB
B+*B+/?D//
E$9!EE$r2  )r   create_task_background_tasksaddadd_done_callbackdiscard)r~   r:  r  r  rB  rA  r"  tasks   ``````  rb   r  z.RecallGuardMiddleware._schedule_content_redacth  s    	| 	| 	| 	| 	| 	| 	| 	| 	| 	| 	|: "7799--!%%d+++w8@AAAAArd   Nr  c                R   t          |dd           }|sd S 	 |                    |                     |||                    j        }n9# t          $ r,}t
                              d|j        |           Y d }~d S d }~ww xY w	 |                    |          }	n9# t          $ r,}t
                              d|j        |           Y d }~d S d }~ww xY wd }
d}|	D ]!}|	                    d          |k    r|}
d} n"|
?|r=|	D ]:}|	                    d          dk    r|	                    d	          |k    r|}
d
} n;|
~| j
        |
d	<   	 |                    ||	           t
                              d|j        ||           n8# t          $ r+}t
                              d|j        |           Y d }~nd }~ww xY wd S |                    |dd| dt          j        t           j                                                  d           t
                              d|j        |           d S )Nr  z*[%s] Recall: failed to resolve session: %sz*[%s] Recall: failed to load transcript: %sr   
message_idzbranch A1: id matchr  r  r  zbranch A2: content matchz$[%s] Recall: redacted msg_id=%s (%s)z*[%s] Recall: rewrite_transcript failed: %ssystemz[recall] message_id="z2" has been recalled; do not quote or reference it.r   )r  r  r   z1[%s] Recall: system note for msg_id=%s (branch B))r  r  r  r  r  r  r  r[  r  r   r  r  r  append_to_transcriptr   r   r   utc	isoformat)r~   r:  r  rB  rA  r  r  r   r#  r!  rz  branch_labelr   s                rb   r  z'RecallGuardMiddleware._patch_transcript  s    !1488 	F	--c.?.?Ua.b.bccnCC 	 	 	NNGWZ[[[FFFFF		..s33JJ 	 	 	NNGWZ[[[FFFFF	  	 	Eyy&&+554 6 >.>#  99V$$..599Y3G3GK[3[3["F#=LE #F9`((j999BGLR]_kllll ` ` `KW\[^________`F 	""3n{nnn!666@@BB)
 )
 	 	 	
 	GWbcccccsG   /A 
A=!A88A=B 
C!!CC8E? ?
F4	!F//F4r  )rB  r[   rA  r[   )r\  r9  r  r[   r\   r   )r  r[   r\   rN  )
r  r[   r  r[   rB  r[   rA  r[   r\   r   )
r  r[   r  r[   rB  r[   rA  r[   r\   r   r   )
r  r[   rB  r[   rA  r[   r  rN  r\   r   )r   r   r   r   r[  	frozensetr  r  ra  r   r  r  r  r   r  r  r  r   rd   rb   r  r    s0         D y&&"   `I& & & & 
 
 
 \
i i i i@    \ #h #h #h [#hJ !B !B !B [!BJ OS:d :d :d :d [:d :d :drd   r  c                  6    e Zd ZdZdZedd	            ZddZdS )SkipSelfMiddlewarezFilter out bot's own messages.z	skip-selfrA  r[   r   rN  r\   r]   c                    | r|sdS | |k    S )z2Detect whether the message is from the bot itself.Fr   )rA  r   s     rb   _is_self_referencez%SkipSelfMiddleware._is_self_reference  s#      	6 	5v%%rd   r\  r9  r   c                   K   |                      |j        |j        j                  r-t                              d|j        j        |j                   d S  |             d {V  d S )Nz'[%s] Ignoring self-sent message from %s)r4  rA  r:  _bot_idr  r  r[  r_  s      rb   ra  zSkipSelfMiddleware.handle  sf      ""3#3S[5HII 	LLBCKDTVYVfgggFgiird   N)rA  r[   r   rN  r\   r]   r  )r   r   r   r   r[  r   r4  ra  r   rd   rb   r2  r2    sQ        ((D& & & \&     rd   r2  c                      e Zd ZdZdZd	dZdS )
ChatRoutingMiddlewarez9Determine chat_id, chat_type, chat_name from push fields.zchat-routingr\  r9  r\   r   c                   K   |j         r*d|j          |_        d|_        |j        p|j         |_        n)d|j         |_        d|_        |j        p|j        |_         |             d {V  d S )Nr  r  r  r  )rB  rH  rI  rC  rJ  rA  rD  r_  s      rb   ra  zChatRoutingMiddleware.handle  s      > 	D33>33CK#CMN<cnCMM6C$466CK CM/C33CCMgiird   Nr  r  r   rd   rb   r8  r8    s3        CCD	 	 	 	 	 	rd   r8  c                  j    e Zd ZdZdd
ZddZddZddZddZe	dd            Z
e	dd            ZdS )AccessPolicyzPlatform-level DM / Group access control policy.

    Encapsulates the allow/deny logic so that both inbound middleware
    and outbound ``send_dm`` can share the same rules without reaching
    into adapter internals.
    	dm_policyr[   dm_allow_fromru   group_policygroup_allow_fromr\   r   c                >    || _         || _        || _        || _        d S r   )
_dm_policy_dm_allow_from_group_policy_group_allow_from)r`  r<  r=  r>  r?  s        rb   rp  zAccessPolicy.__init__  s)     $+)!1rd   r]   c                    t          j        dd                                          dv rdS t          j        dd                                          dv S )NGATEWAY_ALLOW_ALL_USERSr   >   1yestrueTYUANBAO_ALLOW_ALL_USERS)osgetenvlowerri  s    rb   _open_dm_opted_inzAccessPolicy._open_dm_opted_in  sQ    9.3399;;?SSS4y2B77==??CWWWrd   	sender_idc                    | j         dk    rdS | j         dk    r|                                | j        v S | j         dk    r|                                 S dS )u:   Strict DM authorization — pairing does not imply access.disabledF	allowlistopen)rA  r   rB  rN  )r`  rO  s     rb   is_dm_allowedzAccessPolicy.is_dm_allowed	  s]    ?j((5?k))??$$(;;;?f$$))+++urd   c                    t          |pd                                          }|sdS | j        dk    rdS | j        dk    r	|| j        v S | j        dk    rdS | j        dk    r|                                 S dS )z?Whether a DM may reach gateway intake (pairing handshake path).r   FrQ  rR  pairingTrS  )r[   r   rA  rB  rN  )r`  rO  	principals      rb   is_dm_intake_allowedz!AccessPolicy.is_dm_intake_allowed  s    	R((..00	 	5?j((5?k)) 333?i''4?f$$))+++urd   rB  c                    | j         dk    rdS | j         dk    r|                                | j        v S | j         dk    rdS | j         dk    r|                                 S dS )zGPlatform-level group chat inbound filter (open / allowlist / disabled).rQ  FrR  rV  rS  )rC  r   rD  rN  r`  rB  s     rb   is_group_allowedzAccessPolicy.is_group_allowed"  su    ++5,,##%%)???**5''))+++urd   c                    | j         S r   )rA  ri  s    rb   r<  zAccessPolicy.dm_policy.  s
    rd   c                    | j         S r   )rC  ri  s    rb   r>  zAccessPolicy.group_policy2  s    !!rd   N)
r<  r[   r=  ru   r>  r[   r?  ru   r\   r   r\   r]   )rO  r[   r\   r]   )rB  r[   r\   r]   r   )r   r   r   r   rp  rN  rT  rX  r[  r  r<  r>  r   rd   rb   r;  r;    s         
2 
2 
2 
2X X X X
      
 
 
 
    X " " " X" " "rd   r;  c                      e Zd ZdZdZd	dZdS )
AccessGuardMiddlewarez.Platform-level DM/Group access control filter.zaccess-guardr\  r9  r\   r   c                  K   |j         }|j        }|j        dk    rI|                    |j                  s.t
                              d|j        |j        |j                   d S nS|j        dk    rH|	                    |j
                  s.t
                              d|j        |j
        |j                   d S  |             d {V  d S )Nr  z'[%s] DM from %s blocked by dm_policy=%sr  z([%s] Group %s blocked by group_policy=%s)r:  _access_policyrI  rX  rA  r  r  r[  r<  r[  rB  r>  )r`  r\  r]  r:  policys        rb   ra  zAccessGuardMiddleware.handle<  s      +&5=D  ..s/?@@ =L#"2F4D    ]g%%**3>:: >L#.&2E   giird   Nr  r  r   rd   rb   r`  r`  7  s3        88D     rd   r`  c                      e Zd ZdZdZd	dZdS )
AutoSetHomeMiddlewarea  Auto-designate the first inbound conversation as Yuanbao home channel.

    Triggers when no home channel is configured, or when an existing group-chat
    home is superseded by the first DM (direct > group upgrade).
    Silent: writes config.yaml and env, no user-facing message.

    Runs after :class:`BuildSourceMiddleware` and :class:`GroupAtGuardMiddleware`
    so unaddressed group traffic is dropped before home-channel persistence.
    Only senders that pass strict authorization (allowlist / explicit open
    opt-in / pairing-store approval) may claim ``YUANBAO_HOME_CHANNEL``.
    Intake-only pairing forwards must not claim ``YUANBAO_HOME_CHANNEL``.
    zauto-sethomer\  r9  r\   r   c                  K   |j         }|j        s'|                    |          rt          j        dd          }| p|                    d          o
|j        dk    }|j        dk    rd|_        |r	 ddlm} ddl	m
}m}  |            }	|	d	z  }
 ||
          }|j        |d<    ||
|           t          |j                  t          j        d<   t                              d
|j        |j        |j                   n8# t&          $ r+}t                              d|j        |           Y d }~nd }~ww xY w |             d {V  d S )NYUANBAO_HOME_CHANNELr   r  r  Tr   )get_hermes_home)atomic_config_writeread_user_config_rawzconfig.yamlz=[%s] Auto-sethome: designated %s (%s) as Yuanbao home channelz[%s] Auto-sethome failed: %s)r:  _auto_sethome_done_sender_may_designate_homerK  rL  r   rI  hermes_constantsrh  hermes_cli.configri  rj  rH  r[   environr  r  r[  rJ  r  r  )r`  r\  r]  r:  	_cur_home_should_setrh  ri  rj  _homeconfig_pathuser_configr  s                rb   ra  zAutoSetHomeMiddleware.handle`  s     +) 	Tg.P.PQT.U.U 	T	"8"==I N((22Ls}7L  }$$-1* TT@@@@@@[[[[[[[[+O--E"'-"7K )=(<[(I(IK:=+K 67''[AAA9<S[9I9IBJ56KKWck3=   
 ! T T TNN#A7<QRSSSSSSSSTgiis   5BD 
D6!D11D6Nr  r  r   rd   rb   re  re  P  s9          D     rd   re  c                      e Zd ZdZdZdZedd            Zedd
            Zed d            Z	e
d!d            Zed"d            Zed#d            Zed$d            Zed%d&d            Zd'dZdS )(ExtractContentMiddlewarez.Extract raw text and media refs from msg_body.zextract-content  customr  r\   r[   c                   |                      dd          }|                      dd          }|r	d| d| dnd| d}|g}t          j        }dD ]j}|                      |          }|rQt          |t                    r<t          |          |k    r|d|         d	z   n|}|                    d
|             nk|r|                    d           d                    |          S )zAFormat elem_type 1010 (share card) into bracket-placeholder text.titler   linkz[share_card: z | ])card_content
wechat_desNz...(truncated)z	Preview: z[visit link for full content]r   )r   rv  _CARD_CONTENT_MAX_LENGTHr  r[   r   r   r   )	rx  rz  r{  headerr   max_lenr7  valpreviews	            rb   _format_shared_linkz,ExtractContentMiddleware._format_shared_link  s    

7B''zz&"%%6:X22242222@XPU@X@X@X*C3 	 	E**U##C z#s++ >A#hh>P>P#hwh-*:::VY222333 	:LL8999yyrd   rN  c                2   |                      d          }|sdS 	 t          j        |          }t          |t                    r|                     d          nd}n# t          j        t          f$ r d}Y nw xY w|rt          |t                    sdS d| dS )zNFormat elem_type 1007 (link understanding card) into bracket-placeholder text.r  Nr{  z[link: z | visit link for full content])r   r   r  r  r  JSONDecodeError	TypeErrorr[   )rx  r  parsedr{  s       rb   _format_link_understandingz3ExtractContentMiddleware._format_link_understanding  s     **Y'' 	4	Z((F)3FD)A)AK6::f%%%tDD$i0 	 	 	DDD	 	:dC00 	4>>>>>s   A A A76A7r  c                ^   | sdS 	 t           j                            t           j                            |           j                  }|                    d          p|                    d          pg }|r't          |d                                                   ndS # t          $ r Y dS w xY w)zExtract resourceId from Yuanbao resource URL query parameters.

        Args:
            url: Resource URL (e.g., https://...?resourceId=abc123)

        Returns:
            Resource ID string, or empty string if not found
        r   
resourceId
resourceidr   )	urllibparseparse_qsurlparsequeryr   r[   r   r  )r  r  idss      rb   _parse_resource_idz+ExtractContentMiddleware._parse_resource_id  s      	2	L))&,*?*?*D*D*JKKE))L))JUYY|-D-DJC*-53s1v;;$$&&&25 	 	 	22	s   BB 
B,+B,rE  r<  c                `   g }|D ]}|                     dd          }|                     di           }|dk    r.|                     dd          }|r|                    |           c|dk    r|                     d          }t          |t                    sg }d}t	          |          d	k    r$t          |d	         t
                    r	|d	         }n6t	          |          d
k    r#t          |d
         t
                    r|d
         }t          |pi                      d          pd                                          }	|                     |	          }
|                    |
rd|
 dnd           q|dk    r|                     d|                     d|                     dd                              }t          |                     d          pd                                          }|                     |          }
|
r&|                    |r	d| d|
 dnd|
 d           (|                    |rd| dnd           G|dk    rjt          |                     d          pd                                          }|                     |          }
|                    |
rd|
 dnd           |dk    rjt          |                     d          pd                                          }|                     |          }
|                    |
rd|
 dnd           '|dk    r|                     dd          }|rw	 t          j
        |          }t          |t
                    s|                    d            |                     d!          }|d"k    r*|                    |                     dd#                     n|d$k    r)|                    |                     |                     n|d%k    rC|                     |          }|r|                    |           n[|                    d            nE|d&k    r*|                    |                     dd'                     n|                    d            # t          j        t          f$ r |                    |           Y w xY w|                    d            |d(k    r|                     dd          }d}|ra	 t          j
        |          }|                     d)          pd                                }n"# t          j        t          t          f$ r Y nw xY w|                    |rd*| dnd+           u|r|                    d,| d           |rd-                    |          ndS ).a#  Extract plain text content from MsgBody.

        - TIMTextElem      -> text field
        - TIMImageElem     -> "[image]" / "[image|ybres:RID]"
        - TIMFileElem      -> "[file: {filename}]" / "[file:{name}|ybres:RID]"
        - TIMSoundElem     -> "[voice]" / "[voice|ybres:RID]"
        - TIMVideoFileElem -> "[video]" / "[video|ybres:RID]"
        - TIMFaceElem      -> "[emoji: {name}]" or "[emoji]"
        - TIMCustomElem    -> try to extract data field, otherwise "[custom message]"
        - Multiple elems joined with spaces
        rP  r   r  r  rZ   TIMImageElemimage_info_arrayNrW   r   r  [image|ybres:r|  [image]TIMFileElem	file_namefileNamefilenamez[file:|ybres:[file|ybres:[file: [file]TIMSoundElemz[voice|ybres:[voice]TIMVideoFileElem[video|ybres:[video]TIMCustomElemr   z[unsupported message type]	elem_type  z	[mention]      z[chat record]TIMFaceElemr[  z[emoji: z[emoji][ )r   r   r  r<  r   r  r[   r   r  r   r  r  r  r  r  AttributeErrorr   )r~   rE  partselemr  r  rZ   r  
image_info	image_urlridr  file_url	sound_url	video_urldata_valrx  ctyperaw_data	face_name	face_datas                        rb   _extract_textz&ExtractContentMiddleware._extract_text  s     O	/ O	/D!XXj"55I HH]B77GM)){{62.. 'LL&&&n,,#*;;/A#B#B !"2D99 *')$!
'((1,,<LQ<OQU1V1V,!1!!4JJ)**Q..:>Nq>QSW3X3X.!1!!4J!1r 6 6u = = CDDJJLL	,,Y77sI3S3333	JJJJm++";;{GKK
GKKXbdfLgLg4h4hiiw{{5117R88>>@@,,X66 RLLX!h!A(!A!A3!A!A!A!AShbeShShShiiiiLL(!P!68!6!6!6!6QQQQn,,E 2 2 8b99??AA	,,Y77sI3S3333	JJJJ000E 2 2 8b99??AA	,,Y77sI3S3333	JJJJo--";;vr22 ?/!%H!5!5)&$77 %!LL)EFFF$ &

; 7 7 D==!LLFK)H)HIIII"d]]!LL)@)@)H)HIIII"d]]#&#A#A&#I#ID# K %T 2 2 2 2 %-I J J J J"d]]!LLFO)L)LMMMM!LL)EFFF 0)< / / /X...../ LL!=>>>>m++";;vr22	 $(Jx$8$8	%.]]6%:%:%@b$G$G$I$I		 0)^L   	P4	4444yQQQQ /----..."'/sxxR/s+   >RDR+S ?S ;=T99UUrZ   c                r    |                                  } |                     d          rd| dd         z   } | S )zNormalize input text: strip whitespace and convert full-width slash
        (Chinese input method) to ASCII slash so commands are recognized correctly.
           ／r   rW   Nr   r   ra   s    rb   _rewrite_slash_commandz/ExtractContentMiddleware._rewrite_slash_command  s;    
 zz||??8$$ 	"abb>Drd   List[Dict[str, str]]c                   g }| pg D ]S}t          |t                    s|                    dd          }|                    di           pi }t          |t                    s]|dk    r|                    d          }t          |t                    sg }d}t	          |          dk    r$t          |d         t                    r	|d         }n6t	          |          dk    r#t          |d         t                    r|d         }t          |pi                     d	          pd                                          }|r|                    d
|d           R|dk    rt          |                    d	          pd                                          }t          |                    d          pd                                          pkt          |                    d          pd                                          p5t          |                    d          pd                                          }	|r!d|d}
|	r|	|
d<   |                    |
           U|S )zExtract inbound image/file references from TIM msg_body.

        Return example:
          [{"kind": "image", "url": "https://..."}, {"kind": "file", "url": "...", "name": "a.pdf"}]
        rP  r   r  r  r  NrW   r   r  rR   kindr  r  r  r  r  rQ   r[  )r  r  r   r<  r   r[   r   r   )rE  refsr  rP  r  r  r  r  r  r  refs              rb   _extract_inbound_media_refsz4ExtractContentMiddleware._extract_inbound_media_refs'  sl    &(N "	% "	%DdD)) xx
B//Hhh}b117RGgt,, >))#*;;/A#B#B !"2D99 *')$!
'((1,,<LQ<OQU1V1V,!1!!4JJ)**Q..:>Nq>QSW3X3X.!1!!4J!1r 6 6u = = CDDJJLL	 EKK C CDDD=((w{{5117R88>>@@K006B77==?? B7;;z228b99??AAB7;;z228b99??AA 
  %39(*K*KC  0&/FKK$$$rd   c                t   g }| pg D ]}t          |t                    r|                    d          dk    r2|                    d          pi                     dd          }|s`	 t          j        |          }n# t          j        t          f$ r Y w xY wt          |t                    s|                    d          }|dk    rC|                    d          }|r*t          |t                    r|                    |           |d	k    r|                    d
          }|r	 t          j        |          }t          |t                    r|                    d          nd}|r*t          |t                    r|                    |           # t          j        t          f$ r Y w xY w|S )zTExtract link URLs from share-card (1010) and link-understanding (1007) custom elems.rP  r  r  r   r   r  r  r{  r  r  N)	r  r  r   r   r  r  r  r[   r   )	rE  urlsr  data_strrx  r  r{  r  r  s	            rb   _extract_link_urlsz+ExtractContentMiddleware._extract_link_urlsT  s    N 	 	DdD)) TXXj-A-A_-T-T//52::62FFH H--()4   fd++ JJ{++E}}zz&)) &JtS11 &KK%%%$ **Y// !%G!4!45?5M5MWvzz&111SW .JtS$9$9 . KK--- 0)<   s%   (A==BB+A,FF32F3r   r  r>  c                   | pg D ]Q}t          |t                    r|                    d          dk    r2|                    di           pi }t          |t                    s`|                    dd          }|sy	 t          j        |          }n# t          j        t          f$ r Y w xY wt          |t                    r|                    d          dk    s|                    d          pi }t          |t                    r|s d	S d
 }|                                D ].\  }}	|                    d          s ||	          }
|
|
c c S /Sd	S )a  Extract ForwardMsgData from ext_map for elem_type 1009 (WeChat forward).

        The detailed chat-record payload lives in ``msg_content.ext_map``
        (protobuf field 999, ``map<string, string>``):
          - key format: ``wexin_forward_msg_[forward_msg_id]_[userid]``
          - value: a **base64-encoded protobuf** ``ForwardMsgData`` (NOT JSON).
            Decode with base64 then ``decode_forward_msg_data`` to recover the
            ``sub_type`` / ``nick_name`` / ``msg`` structure.

        Matching strategy: take the first ``wexin_forward_msg_`` entry whose
        decoded payload is a valid ``ForwardMsgData`` (``sub_type == 1``).

        Returns the parsed ``ForwardMsgData`` dict or ``None``.
        rP  r  r  r   r   r  r  ext_mapNc                   t          | t                    r| sd S 	 t          j        |           }n# t          j        t          f$ r Y d S w xY wt          |          }t          |t                    r|	                    d          dk    r|S d S )Nsub_typerW   )
r  r[   base64	b64decodebinasciiErrorr  r/   r  r   )valuepbr   s      rb   _parse_valuezIExtractContentMiddleware._extract_forwarded_records.<locals>._parse_value  s    !%--  U  4 )%00BB 
3      44 .r22dD))  dhhz.B.Ba.G.GKts   0 A
	A
wexin_forward_msg_)	r  r  r   r   r  r  r  r   r   )rE  r  r  r  r  rx  r  r  keyr  r  s              rb   _extract_forwarded_recordsz3ExtractContentMiddleware._extract_forwarded_recordsu  s     N '	" '	"DdD)) TXXj-A-A_-T-Thh}b117RGgt,, {{62..H H--()4   vt,, K1H1HD1P1Pkk),,2Ggt,, G tt   &mmoo " "
U~~&:;; %e,,%!MMMMM &	" ts   ?BB-,B-r\  r9  r   c                T  K   |                      |                     |j                            |_        |                     |j                  |_        |                     |j                  |_        |                     |j        |j	                  |_
         |             d {V  d S r   )r  r  rE  rK  r  rL  r  rV  r  rA  rM  r_  s      rb   ra  zExtractContentMiddleware.handle  s      2243E3Ecl3S3STT99#,GG//== $ ? ?cN^ _ _giird   N)rx  r  r\   r[   )rx  r  r\   rN  r  r[   r\   r[   )rE  r<  r\   r[   r   )rE  r<  r\   r  )rE  r<  r\   r<  r4  )rE  r<  r  r[   r\   r>  r  )r   r   r   r   r[  r  r   r  r  r  r   r  r  r  r  r  ra  r   rd   rb   rv  rv    sD       88D#      \ " ? ? ? \?    \$ ^0 ^0 ^0 [^0@    \ * * * \*X    \@ 8 8 8 8 \8t     rd   rv  c                  ^    e Zd ZU dZdZ eh d          Zded<   eddd            Z	ddZ
dS )PlaceholderFilterMiddlewarez>Skip pure placeholder messages (e.g. '[image]' with no media).zplaceholder-filter>      [图片]   [文件]   [视频]   [语音]r  r  r  r  r0  SKIPPABLE_PLACEHOLDERSr   rZ   r[   media_countri   r\   r]   c                L    |dk    rdS |                                 }|| j        v S )zEDetect whether the message is a pure placeholder (should be skipped).r   F)r   r  )r~   rZ   r  r   s       rb   is_skippable_placeholderz4PlaceholderFilterMiddleware.is_skippable_placeholder  s-     ??5::<<3555rd   r\  r9  r   c                   K   |                      |j        t          |j                            r-t                              d|j        j        |j                   d S  |             d {V  d S )Nz%[%s] Skipping placeholder message: %r)r  rK  r   rL  r  r  r:  r[  r_  s      rb   ra  z"PlaceholderFilterMiddleware.handle  sj      ((s3>7J7JKK 	LL@#+BRTWT`aaaFgiird   N)r   )rZ   r[   r  ri   r\   r]   r  )r   r   r   r   r[  r0  r  r5  r   r  ra  r   rd   rb   r  r    s         HHD(1	 3 3 3 ) )    
 6 6 6 6 [6     rd   r  c                  t    e Zd ZU dZdZ eh d          Zded<   edd	            Z	e
dd            ZddZdS )OwnerCommandMiddlewarezDetect bot-owner slash commands in group chat.

    Identifies in-group allowlisted slash commands and determines sender identity.
    Owner commands skip @Bot detection; non-owner attempts are rejected.
    zowner-command>   /q/bg/btw/new/deny/stop/undo/queue/reset/retry/approve/backgroundr0  	ALLOWLISTrZ   r[   r\   c                r    |                                  } |                     d          rd| dd         z   } | S )z?Normalize full-width slash to ASCII slash and strip whitespace.r  r   rW   Nr  ra   s    rb   r  z-OwnerCommandMiddleware._rewrite_slash_command  s;     zz||??8$$ 	"abb>Drd   r?  r  rE  r<  rI  rA  )Tuple[Optional[str], Optional[str], bool]c               6   |dk    s| j         sdS d |pg D             }t          |          dk    rdS |d                             d          pi                     dd          }|                     |          }|                    d	          sdS |                    d
          d                                         }|| j         vrdS t          |pi                     d          pd                                          }	t          |	          o|	|k    }
|||
fS )a2  Identify allowlisted slash commands and determine sender identity.

        Returns (cmd, cmd_line, is_owner):
          - (None, None, False): Not an allowlisted command
          - (cmd, cmd_line, True): Owner match
          - (cmd, cmd_line, False): Allowlisted command but sender is not owner
        r  )NNFc                D    g | ]}|                     d           dk    |S )rP  r  r   r  s     rb   r   z@OwnerCommandMiddleware._detect_owner_command.<locals>.<listcomp>  s9     
 
 
uuZ  M11 111rd   rW   r   r  rZ   r   r   )maxsplitr  )
r  r   r   r  r   r   rM  r[   r   r]   )r~   r?  rE  rI  rA  
text_elemsrZ   cmd_liner  owner_idis_owners              rb   _detect_owner_commandz,OwnerCommandMiddleware._detect_owner_command  s=     s}$$
 
 B
 
 

 z??a$$1!!-006B;;FBGG--d33""3'' 	%$$nnan((+1133cm##$$ 
''77=2>>DDFF>>>h,&>Hh&&rd   r\  r9  r   c           
       K   |j         }|                     |j        |j        |j        |j                  \  }}}|rz|sxt                              d|j        |j	        |j        |           |
                    t          j        |                    |j	        d| d          d|                      d S |r?|r=|r;t                              d|j        |j	        |j        |           ||_        ||_         |             d {V  d S )N)r?  rE  rI  rA  z;[%s] Reject non-owner slash command: chat=%s from=%s cmd=%su   ⚠️ z6 is only available to the creator in private chat modezyuanbao-owner-cmd-denial-r[  z4[%s] Bot owner slash command: chat=%s from=%s cmd=%s)r:  r   r?  rE  rI  rA  r  r  r[  rH  _track_taskr   r#  sendrO  rK  )r`  r\  r]  r:  matched_cmdr  r  s          rb   ra  zOwnerCommandMiddleware.handle  sH     +*.*D*D\m)	 +E +
 +
'Xx  
	x 
	KKMck3+;[    3S[*wK*w*w*wxx>>>! ! !    F 	$8 	$ 	$KKFck3+;[   !,C#CLgiird   Nr   )
r?  r  rE  r<  rI  r[   rA  r[   r\   r  r  )r   r   r   r   r[  r0  r  r5  r   r  r   r   ra  r   rd   rb   r  r    s           D %9 & & &  I        \ (' (' (' [('T     rd   r  c                      e Zd ZdZdZd	dZdS )
BuildSourceMiddlewarez(Build SessionSource from context fields.zbuild-sourcer\  r9  r\   r   c           	        K   |j         }|                    |j        |j        |j        |j        pd |j        p|j        |j        dk    rdnd           |_         |             d {V  d S )Nr  r  )rH  rI  rJ  r  	user_namer  )r:  r  rH  rI  rJ  rA  rD  r*  r`  r\  r]  r:  s       rb   ra  zBuildSourceMiddleware.handle6  s      +))Kmm$,)=S-= # 8 8ffd * 
 

 giird   Nr  r  r   rd   rb   r  r  1  s3        22D
 
 
 
 
 
rd   r  c                      e Zd ZdZdZedd	            Zedd            Zedd            Ze	ddddd            Z
ddZdS )GroupAtGuardMiddlewarezIn group chat, observe non-@bot messages; only reply on @Bot.

    Owner commands skip @Bot detection (owner doesn't need to @Bot).
    zgroup-at-guardrE  r<  r   rN  r\   r]   c                v   |sdS | D ]}|                     d          dk    r|                     d          pi                      dd          }|sJ	 t          j        |          }n# t          j        t          f$ r Y xw xY w|                     d          dk    r|                     d	          |k    r d
S dS )a  Detect whether the message @Bot.

        AT element format: TIMCustomElem, msg_content.data is a JSON string:
            {"elem_type": 1002, "text": "@xxx", "user_id": "<botId>"}
        Considered @Bot when elem_type == 1002 and user_id == bot_id.
        FrP  r  r  r   r   r  r  r  T)r   r   r  r  r  )rE  r   r  r  rx  s        rb   
_is_at_botz!GroupAtGuardMiddleware._is_at_botK  s      	5 	 	Dxx
##66//52::62FFH H--()4   zz+&&$..6::i3H3HF3R3Rttu   A''B ?B r[   c                   |sdS | D ]}|                     d          dk    r|                     d          pi                      dd          }|sJ	 t          j        |          }n# t          j        t          f$ r Y xw xY w|                     d          dk    rU|                     d          |k    r<t          |                     d	          pd                                          }|r|c S dS )
zLExtract the display text used to @-mention this bot (e.g. ``@yuanbao-bot``).r   rP  r  r  r   r  r  r  rZ   )r   r   r  r  r  r[   r   )rE  r   r  r  rx  mention_texts         rb   _extract_bot_mention_textz0GroupAtGuardMiddleware._extract_bot_mention_textc  s     	2 	( 	(Dxx
##66//52::62FFH H--()4   zz+&&$..6::i3H3HF3R3R"6::f#5#5#;<<BBDD (''''rr  c                p    t          |pd          }t                              | |          pd}d| d| dS )zOBuild a per-turn group-chat prompt that highlights which message to respond to.unknownzHYou are handling a Yuanbao group chat message.
- Your identity: user_id=z, @-mention name in this group=z
- Lines in history prefixed with `[nickname|user_id]` are observed group context and are not necessarily addressed to you.
- Treat only the current new message as a request explicitly directed at you, and answer it directly.)r[   r  r  )rE  r   bidbot_mentions       rb   _build_group_channel_promptz2GroupAtGuardMiddleware._build_group_channel_promptx  s[     &%I&&,FFxQWXXe\e&(+& &LW& & &	
rd   N)rF  rM  sender_displayrZ   r\  r9  rF  rM  r>  r   c                  t          |dd          }|sdS 	 |                    |          }	|j        pd}
|}|r*t                              ||d          }|r|r| d| n|}d| d|
 d	| }d
|t          j        t          j                  	                                dd}|r||d<   |
                    |	j        |           dS # t          $ r,}t                              d|j        |           Y d}~dS d}~ww xY w)as  Write a group message into the session transcript without triggering the agent.

        This allows the model to see the full group conversation when it is
        eventually invoked via @bot.  Messages are stored with ``role: "user"``
        in the format ``[nickname|user_id]\n<content>`` so the model
        can distinguish participants and their user ids.
        r  Nr  Fr\  is_dispatchr   r  r   ]
r  r   T)r  r  r   observedr*  z([%s] Failed to observe group message: %s)r  r  r  ForwardedRecordsParseMiddlewarebuild_forward_textr   r   r   r-  r.  r,  r  r  r  r  r[  )r~   r:  r*  r  rZ   r\  rF  rM  r  session_entryr  	body_textsummary
attributedr   r#  s                   rb   _observe_group_messagez-GroupAtGuardMiddleware._observe_group_message  s     !1488 	F	Z!77??Mn1	GI  J9LL%3E M    J8< I4 4 47 4 4 4'IE^EEgEE)EEJ%%\X\:::DDFF 	 E  -&,l#&&(      	Z 	Z 	ZNNEw|UXYYYYYYYYY	Zs   B/C 
C>!C99C>c           	     t  K   |j         }|j        dk    r|j        s|                     |j        |j                  sl|                     ||j        |j        p|j	        |j
        |j        pd |j        |           t                              d|j        |j        |j	                   d S  |             d {V  d S )Nr  )rF  rM  r\  z6[%s] Group message observed (no @bot): chat=%s from=%s)r:  rI  rO  r  rE  r6  r$  r*  rD  rA  rK  rF  rM  r  r  r[  rH  r
  s       rb   ra  zGroupAtGuardMiddleware.handle  s      +=G##C,=#dooVYVbdkdsFtFt#''S%8%LC<Lclz)T"%"7	 (    KKHck3+;   Fgiird   )rE  r<  r   rN  r\   r]   )rE  r<  r   rN  r\   r[   )r  r[   rZ   r[   r\  r9  rF  rN  rM  r>  r\   r   r  )r   r   r   r   r[  r   r  r  r  r   r$  ra  r   rd   rb   r  r  C  s         
 D   \.    \( 
 
 
 \
  !%,0*Z *Z *Z *Z *Z [*ZX     rd   r  c                      e Zd ZdZdZd	dZdS )
GroupAttributionMiddlewarea  Tag group @bot messages with [nickname|user_id] attribution and channel_prompt.

    For group messages that pass the @bot guard (i.e. the bot is mentioned),
    this middleware:
      - Builds a per-turn channel_prompt so the model knows its identity and
        the attribution scheme.
      - Rewrites ctx.raw_text to ``[nickname|user_id]\n<content>`` to match
        the observed-history format.
      - Suppresses the runner's default ``[user_name]`` shared-thread prefix
        by clearing ``source.user_name``.
    zgroup-attributionr\  r9  r\   r   c                Z  K   |j         dk    r|j        s|j        }t                              |j        |j                  |_        |j        pd}|j	        p|j        pd}d| d| d|j
         |_
        |j         t          j        |j        d           |_         |             d {V  d S )Nr  r  r  r   r  )r	  )rI  rO  r:  r  r  rE  r6  rW  rA  rD  rK  r*  dataclassesr   )r`  r\  r]  r:  user_id_labelnickname_labels         rb   ra  z!GroupAttributionMiddleware.handle  s      =G##C,=#kG!7!S!Sgo" "C  ,9	M 0QC4DQ	NP~PPPP#,PPCL z%(0tLLL
giird   Nr  r  r   rd   rb   r'  r'    s9        
 
 D     rd   r'  c                      e Zd ZdZdZdS )YuanbaoMessageTypezYuanbao-local message subtypes; coerced back to :class:`MessageType`
    before leaving the adapter (see :class:`DispatchMiddleware`).chat_recordN)r   r   r   r   CHAT_RECORDr   rd   rb   r-  r-    s!        E E  KKKrd   r-  c                  6    e Zd ZdZdZedd            ZddZdS )ClassifyMessageTypeMiddlewarez>Determine MessageType from text content and msg_body elements.zclassify-msg-typerZ   r[   rE  r<  c                x   |                      d          rt          j        S |D ]
}|                    dd          }|dk    rt          j        c S |dk    rt          j        c S |dk    rt          j        c S |dk    rt          j        c S |dk    r|                    d	          pi                     d
d          }	 t          j	        |          }n# t          j
        t          f$ r d}Y nw xY wt          |t                    r'|                    d          dk    rt          j        c S t          j        S )zClassify message type based on text and msg_body.

        Returns a base :class:`MessageType`, or a yuanbao-local
        :class:`YuanbaoMessageType` for platform-specific subtypes.
        r   rP  r   r  r  r  r  r  r  r   Nr  r  )r   r   COMMANDr   PHOTOVOICEVIDEODOCUMENTr   r  r  r  r  r  r-  r/  r	  )rZ   rE  r  etyper  rx  s         rb   	_classifyz'ClassifyMessageTypeMiddleware._classify  s`    ??3 	'&& 	: 	:DHHZ,,E&&"((((&&"((((***"((((%%"++++'' HH]339r>>vrJJ"!Z11FF,i8 " " "!FFF"fd++ :

;0G0G40O0O-9999s   ?CC/.C/r\  r9  r\   r   c                t   K   |                      |j        |j                  |_         |             d {V  d S r   )r9  rK  rE  rP  r_  s      rb   ra  z$ClassifyMessageTypeMiddleware.handle  s:      ~~clCLAAgiird   N)rZ   r[   rE  r<  r  )r   r   r   r   r[  r   r9  ra  r   rd   rb   r1  r1    sQ        HHD      \ 8     rd   r1  c                  .    e Zd ZdZdZddZddZddZdS )QuoteContextMiddlewarez3Extract quote/reply context from cloud_custom_data.zquote-contextrG  r[   r\   #Tuple[Optional[str], Optional[str]]c                   |sdS 	 t          j        |          }n# t           j        t          f$ r Y dS w xY wt	          |t
                    r|                    d          nd}t	          |t
                    sdS t          |                    d          pd                                          pd}t          |                    d          pd                                          }t          |                    d          p|                    d          pd                                          }|r|r| d	| n|nd}||fS )
zHExtract quote text context, mapping to MessageEvent.reply_to_*.
        r  quoteNidr   descrD  rO  r   )	r   r  r  r  r  r  r   r[   r   )r`  rG  r  r?  quote_idrA  sender
quote_texts           rb   _extract_quote_contextz-QuoteContextMiddleware._extract_quote_context  sT    ! 	:	Z 122FF$i0 	 	 	::	 (2&$'?'?I

7###T%&& 	:uyy,"--3355=599V$$*++1133UYY011QUYY{5K5KQrRRXXZZBFPV=))4)))D
##s    55r\  r9  List[Tuple[str, str, str]]c           	     r  K   |j         g S |j        }g }	 t          |dd          }|r|j        g S |                    |j                  }|                    |j                  }t          |pg           D ]}|                    dd          }|r||j         k    r&|                    dd          }	t          |	t                    rd|	v rt                              |	          D ]}
|
                    d          }|
                    d          }|                    d	          \  }}}|                                }|t           v r*|                    |||                                f            nB# t$          $ r5}t&                              d
t          |dd          |           Y d}~nd}~ww xY w|S )ay  Look up the quoted message in the transcript history and return any
        ``[kind|ybres:RID]`` anchors found in its content as
        ``(rid, kind, filename)`` tuples.

        Returns ``[]`` when ``ctx.reply_to_message_id`` is unset, when the
        transcript store / source is unavailable, or when the quoted message
        carries no resolvable media anchors.
        Nr  r*  r   r  r  rW   rH   :z'[%s] quote transcript lookup failed: %sr[  yuanbao)rQ  r:  r  r*  r  r  r  reversedr   r  r[   _YB_RES_REF_REfinditerr  	partitionr   _RESOLVABLE_MEDIA_KINDSr   r  r  r  )r`  r\  r:  rL  r  r   historyr   r  _contentmheadr  r  r  r  r#  s                    rb   #_extract_media_refs_from_transcriptz:QuoteContextMiddleware._extract_media_refs_from_transcript,  s      "*I+13
	G%5t<<E CJ.	!77
CCM++M,DEEG2..  gglB// cS%<<<779b11h,, Mh1F1F+44X>> M M wwqzzggajj,0NN3,?,?)a#zz||#:::&--sD(..:J:J.KLLL 	 	 	NN933S       	
 s   E5 EE5 5
F4?+F//F4r   c                   K   |                      |j                  \  |_        |_        |                     |           d {V |_         |             d {V  d S r   )rE  rG  rQ  rR  rS  rS  r_  s      rb   ra  zQuoteContextMiddleware.handleV  sk      595P5PQTQf5g5g2!2%)%M%Mc%R%RRRRRRRgiird   N)rG  r[   r\   r=  )r\  r9  r\   rF  r  )r   r   r   r   r[  rE  rS  ra  r   rd   rb   r<  r<    s\        ==D$ $ $ $*( ( ( (T     rd   r<  c                      e Zd ZdZdZddZedd            Ze	 ddd            Z	dZ
edd            Zedd            ZdS )r  u  Deep-parse WeChat forwarded chat records (elem_type 1009) for dispatch.

    Activates when a full ``ForwardMsgData`` dict is available on the current
    turn, carried by the current message (``ctx.forwarded_records``).
    Resolves media to ``[kind|ybres:RID]``
    placeholders, appends downloadable refs to ``ctx.media_refs`` (for
    :class:`MediaResolveMiddleware`), and rewrites ``ctx.raw_text``.

    Group @bot turns *without* a forward on the current message rely on the
    eagerly-rendered summaries that :class:`GroupAtGuardMiddleware` writes to
    the transcript at observe time — there is no run-time summary fallback
    here.

    On any failure the middleware leaves ``ctx.raw_text`` untouched
    (graceful degradation, design §2.8).
    zforwarded-records-parser\  r9  r\   r   c           	     D  K   	 |j         r=|                     |           d {V  |                     |j         |d          |_        nG# t          $ r:}t
                              dt          |j        dd          |           Y d }~nd }~ww xY w |             d {V  d S )NTr  z,[%s] forwarded-records deep parse failed: %sr[  rI  )	rM  _send_loading_heartbeatr  rK  r  r  r  r  r:  )r`  r\  r]  r#  s       rb   ra  z&ForwardedRecordsParseMiddleware.handlep  s      		$ i223777777777#66s7LRUcg6hh 	 	 	NN>VY77       	 giis   AA	 	
B0BBc                   K   	 | j         j        j                            | j        t
                     d{V  dS # t          $ r Y dS w xY w)z@Best-effort RUNNING heartbeat so the user sees a loading bubble.N)r:  	_outbound	heartbeatsend_heartbeat_oncerH  r*   r  )r\  s    rb   rW  z7ForwardedRecordsParseMiddleware._send_loading_heartbeat  s      	+'1EE1            	 	 	DD	s   5; 
A	A	r   mediar  
plain_textr[   $Tuple[str, Optional[Dict[str, str]]]c                   |                     dd          p|                     dd                                                                          }t          |                     d          pd                                          }t          |                     d          pd                                          }t          |                     d          pd                                          }|pt                              |          }|dk    r>|r!|rd| d	|                                 d|d
fS d|p|                                 dfS |dv rE|r*|r(d|d
}|r||d<   d| d	|                                 |fS d|                                 dfS |dk    rB|p#t          |                     d          pd          }	d|	 d|                                 dfS |dk    r>|r!|rd| d	|                                 d|d
fS d|p|                                 dfS d|pd d	|p|                                 dfS )a:  Render one ``msgContent.multimedia`` entry as a textual marker.

        Returns ``(marker, ref)``. Downloadable media emits a
        ``[kind|ybres:RID]`` marker and a ``ctx.media_refs`` ref dict when a
        usable RID/URL is present; otherwise a plain ``[kind] name`` marker
        and ``ref=None``.
        typer   doc_typer  media_idr  rR   r  z] r  z[image] N)rQ   documentr   rQ   r[  r  z[file] rz  z[link] r  rS   r  z[video] r  r\  )r   r   rM  r[   rv  r  r  )
r~   r\  r]  
media_typer  rb  r  r  r  
link_titles
             rb   _media_markerz-ForwardedRecordsParseMiddleware._media_marker  s    ii++HuyyR/H/HOOQQWWYY
%))E""(b))//11uyy,,23399;;		+..4"55;;==	 J2EEcJJ   bs b9s99i99@@BBW]`DaDaaa7i5:77>>@@$FF555 Gs G/5c&B&B ,"+CK8c88Y88??AA3FF(Y((//11477"Cc%))G*<*<*B&C&CJ/Z//#//6688$>>   bs b9s99i99@@BBW]`DaDaaa0i.30077994??>:(>>C,<9>>EEGGMMrd   rw  forward_data/Iterator[Tuple[str, str, List[Dict[str, str]]]]c              #     K   t          |t                    r|                    d          ndpg D ]}t          |t                    s|                    dd          }|                    dd          }|                    dg           pg }g }|s|}n5g }|D ]}	t          |	t                    s|	                    dd          }
|
d	k    r*|                    |	                    d
d                     _|
dk    rv|	                    dg           pg D ]\}t          |t                    rE|                     ||          \  }}|                    |           ||                    |           ]|
dk    r|                    d           |r|                    |           d                    d |D                       p|}t          |          | j        k    r|d| j                 dz   }|||fV  dS )u4  Walk ``ForwardMsgData['msg']`` and yield ``(sender, body, refs)``.

        Per-record dispatch over ``msgContent`` (text / multimedia / nested
        forward / fallback); ``body`` is capped at
        :attr:`FORWARD_MSG_TEXT_MAX_CHARS`. Media goes through
        :meth:`_media_marker`, always building full ``[kind|ybres:RID]``
        markers; ``refs`` holds that record's downloadable ``ctx.media_refs``
        entries in textual order — the order PatchAnchorsMiddleware relies on
        (design §2.10.6). Headers / footers are the caller's job.
        r   NrC  r   	plainText
msgContentr`  r   rW   rZ   rH   
multimediar   u   [嵌套聊天记录]z  c              3     K   | ]}||V  	d S r   r   )r   ps     rb   r   zEForwardedRecordsParseMiddleware._walk_forward_msgs.<locals>.<genexpr>  s'      $;$;1$;Q$;$;$;$;$;$;rd   u   …(已截断))r  r  r   r   rf  r   r   FORWARD_MSG_TEXT_MAX_CHARS)r~   rg  r   rC  r]  msg_contentsr  renderedr  mcmc_typer\  markerr  s                 rb   _walk_forward_msgsz2ForwardedRecordsParseMiddleware._walk_forward_msgs  sK      0:,/M/MWL$$U+++SW^\^ $	) $	)Cc4(( WWXr**Fb11J77<44:L)+D J%#%& 5 5B%b$// !  ffVQ//G!||RVVFB%7%78888 A%'VVL"%=%=%C 5 5E)%66 5.1.?.?$):/" /" !&V 4 4 4#&?$(KK$4$4$45 !A%;<<<<% 5!LL44499$;$;$;$;$;;;Iz8}}s===#$Dc&D$DEHXX(D(((((I$	) $	)rd   r  r]   c               h   |j         pd}d| dg}|                     |          D ]<\  }}}|                    | d|            |r|j                            |           =d                    |          }	|r8|j                                        r|	d|j                                         z  }	|	S )u  Render ``ForwardMsgData`` into forward text.

        Body lines are ``发送人：正文`` with full ``[kind|ybres:RID]`` media
        markers preserved. When ``is_dispatch`` is true, refs are appended to
        ``ctx.media_refs`` for downstream resolution and a ``用户附言：
        {ctx.raw_text}`` footer is added; observed callers skip both since
        no later middleware runs.
        u   用户u   当前用户的昵称为u   以下为用户的聊天记录u   ：r   u   

用户附言：)rD  ru  r   rL  extendr   rK  r   )
r~   rg  r\  r  nicknamer   rC  r!  r  rZ   s
             rb   r  z2ForwardedRecordsParseMiddleware.build_forward_text  s     &2(6H668XY"%"8"8"F"F 	, 	,FD$LLF--t--... ,%%d+++yy 	A3<--// 	A@#,*<*<*>*>@@@Drd   Nr  r4  )r\  r  r]  r[   r\   r^  )rg  r  r\   rh  )rg  r  r\  r9  r  r]   r\   r[   )r   r   r   r   r[  ra  r   rW  r   rf  ro  ru  r  r   rd   rb   r  r  \  s         " %D        \ ,.)N )N )N )N [)NX "&2) 2) 2) [2)l    [  rd   r  c                  t   e Zd ZU dZdZi Zded<   dZded<   dZded	<   e	d4d            Z
e	d5d            Ze	d6d            Zed7d            Zed8d            Zed7d            Ze	ddddd9d$            Ze	d:d(            Ze	d;d,            Ze	d<d-            Ze	d=d/            Zed>d2            Zd?d3ZdS )@MediaResolveMiddlewarez6Resolve inbound media references to downloadable URLs.zmedia-resolvez+ClassVar[Dict[str, Tuple[str, str, float]]]_resource_cacheiQ ClassVar[int]_RESOURCE_CACHE_TTL_S   _RESOURCE_CACHE_MAX_SIZEresource_idr[   r\   Optional[Tuple[str, str]]c                J   |sdS | j                             |          }|dS |\  }}}t          j                    |z
  | j        k    r| j                             |d           dS t
          j                            |          s| j                             |d           dS ||fS )zOReturn cached ``(local_path, mime)`` if still valid and file exists, else None.N)r{  r   r   r}  r   rK  pathisfile)r~   r  r   
local_pathmimetss         rb   _get_cached_resourcez+MediaResolveMiddleware._get_cached_resource	  s      	4#''44=4$
D"9;;c777##K6664w~~j)) 	##K66644rd   r  r  r   c                    |sdS t           j                   j        k    rIt           j         fd          }|d j        dz           D ]} j                            |d           ||t          j                    f j        |<   dS )zIStore download result in cache. Evicts oldest entries when over capacity.Nc                *    j         |          d         S )NrH   )r{  )r   r~   s    rb   <lambda>z=MediaResolveMiddleware._put_cached_resource.<locals>.<lambda>.	  s    CDWXYDZ[\D] rd   )r     )r   r{  r  sortedr   r   )r~   r  r  r  sorted_keysr   s   `     rb   _put_cached_resourcez+MediaResolveMiddleware._put_cached_resource'	  s      	Fs"##s'CCC !4:]:]:]:]^^^K !D3#?1#D!DE 1 1#''40000,6dikk+JK(((rd   media_paths	List[str]mimesr]   c                    |                      |          }|dS |\  }}t                              d|j        ||           |                    |           |                    |           dS )z8Append a cached resource to output lists when available.NF'[%s] resource cache hit: rid=%s path=%sT)r  r  r  r[  r   )r~   r:  r  r  r  hitr  r  s           rb   _append_cached_resourcez.MediaResolveMiddleware._append_cached_resource3	  sz     &&{33;5
D5L+z	
 	
 	
 	:&&&Ttrd   r  c                    t           j                            |           j        }t          j                            |          d                                         }|dv r|S dS )z$Guess image extension from URL path.rW   >   .heic.tiff.bmp.gif.jpg.png.jpeg.webpr  )r  r  r  r  rK  splitextrM  )r  r  exts      rb   _guess_image_ext_from_urlz0MediaResolveMiddleware._guess_image_ext_from_urlH	  sX     |$$S)).gt$$Q'--//VVVJvrd   c                  K   |                                 }|st          d          |                                  d{V }t          |                    d          pd                                           }t          |                    d          pd                                           pd}t          |                    d          p| j        p| j                                                   }|r|st          d          | j         d	}d
|||d}t          j	        dd          4 d{V }t          d          D ]M}	|                    |d|i|           d{V }
|
j        dk    r|	dk    rt                              | j        | j        | j                   d{V }t          |                    d          pd                                           }t          |                    d          p|pd                                           pd}t          |                    d          p| j        p| j                                                   }|r|s n0||d<   ||d<   ||d<   0|
                                 |
                                }|                    d          }|dvr)t          d| d|                    dd                     t#          |                    d          t$                    r|                    d          n|}t          |pi                     d          p|pi                     d          pd                                           }|r|c cddd          d{V  S t          d          ddd          d{V  n# 1 d{V swxY w Y   t          d           )!zLow-level helper: exchange a ``resourceId`` for a direct download URL.

        Handles token retrieval, the ``/api/resource/v1/download`` API call,
        and a single 401-retry with token force-refresh.  Raises on failure.
        zmissing resource_idNr(  r   r*  webr   z-missing token or bot_id for resource downloadz/api/resource/v1/downloadr   )r   X-IDX-TokenX-Sourcer>   T)r   follow_redirectsrH   r  )paramsr   i  r   r  r  r  r   >   Nr   z"resource/v1/download failed: code=r  r   r   r  realUrlz(resource/v1/download missing url/realUrlz)resource/v1/download did not return a URL)r   r  _get_cached_tokenr[   r   r6  _app_key_api_domainr  r  r  r  r   r1  _app_secretraise_for_statusr   r  r  )r:  r  
token_datar(  r*  r   api_urlr   r  r  respr  r   r   real_urls                  rb   _fetch_resource_urlz*MediaResolveMiddleware._fetch_resource_urlQ	  s      "'')) 	64555"4466666666
JNN7++1r2288::Z^^H--677==??H5Z^^H--TTGDTUU[[]] 	PF 	PNOOO(CCC.	
 
 $TDIII 	O 	O 	O 	O 	O 	O 	OV 88 O O#ZZ{8S]dZeeeeeeee#s**w!||'2'@'@('*=w?R( ( " " " " " "J  
w 7 7 =2>>DDFFE !9!9!LV!LuMMSSUU^Y^F !9!9!`W_!`PWP`aaggiiF   &,GFO).GI&*0GJ'%%'''))++{{6**y((&aTaaUZ\^I_I_aa   /9V9L9Ld.S.S`w{{6***Y`
//66[4:2:J:J9:U:U[Y[\\bbdd $#OO9	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O: ##MNNN;	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O> FGGGs   2H=N#N##
N-0N-c                  K   	 t           j                            |          }n# t          $ r |cY S w xY wt           j                            |j                  }|                    d          p|                    d          pg }|r't          |d                                                   nd}|s|S 	 t          
                    | |           d{V S # t          $ r |cY S w xY w)a"  Resolve Yuanbao resource placeholder to a directly fetchable real URL.

        Common URL patterns:
          https://hunyuan.tencent.com/api/resource/download?resourceId=...
        Direct GET returns 401; need business API:
          GET /api/resource/v1/download?resourceId=...
        r  r  r   r   N)r  r  r  r  r  r  r   r[   r   rz  r  )r:  r  r  r  resource_idsr  s         rb   _resolve_download_urlz,MediaResolveMiddleware._resolve_download_url	  s     	\**3//FF 	 	 	JJJ	 %%fl33yy..O%))L2I2IOR6BJc,q/**00222 	J	/CCG[YYYYYYYYY 	 	 	JJJ	s   $ 336 C C&%C&Nr   )r  log_tagr  	fetch_urlr  r  rN  r  c                 K   |rA|                      |          }|*t                              d|j        ||d                    |S 	 t	          ||j                   d{V \  }}	n;# t          $ r.}
t                              d|j        |||
           Y d}
~
dS d}
~
ww xY w|dk    r|                     |          }	 t          ||          }n:# t          $ r-}
t                              d|j        ||
           Y d}
~
dS d}
~
ww xY wt          d|           }|                    d	          s|	                    d	          r|	nd
}|                     |||           ||fS |dk    rRt          |          }t          |          p|	                    d          r|	nd}|                     |||           ||fS |sEt          j                            |          }t$          j                            |j                  pd}	 t+          ||          }n:# t          $ r-}
t                              d|j        ||
           Y d}
~
dS d}
~
ww xY wt          |          p|	pd}|                     |||           ||fS )a  Download a Yuanbao resource and cache locally. Returns ``(local_path, mime)`` or ``None``.

        When *resource_id* is provided, an in-memory cache keyed by resourceId
        is consulted first to skip redundant downloads of the same resource
        within the TTL window.
        Nr  r   max_size_mbz5[%s] inbound media download failed: kind=%s %s err=%srR   )r  z,[%s] inbound image cache rejected: %s err=%simage/
image/jpegrS   zvideo/z	video/mp4rQ   z)[%s] inbound file cache failed: %s err=%sapplication/octet-stream)r  r  r  r[  media_download_urlMEDIA_MAX_SIZE_MBr  r  r  r   r  r#   r   r  r   r  r  r  rK  r  basenamer   )r~   r:  r  r  r  r  r  r  
file_bytescontent_typer#  r  r  r  r  s                  rb   _download_and_cachez*MediaResolveMiddleware._download_and_cache	  s(       	**;77C=L+s1v   
		-?w'@. . . ( ( ( ( ( ($J  	 	 	NNGdGS   44444	 7??//	::C3JCHHH

   BL'3   ttttt #=3==11D??8,, ['3'>'>x'H'HZ||l$$[*dCCCt##7??/
;;J":..  , 7 7 A AR{  $$[*dCCCt##  	@\**955F((55?I	2:yIIJJ 	 	 	NN;gs   44444	 y))W\W=W  j$???4sH   A' '
B1#BB>C 
D"DDH 
I "IIrL  r  Tuple[List[str], List[str]]c           	        K   g }g }g }|D ]}t          |                    d          pd                                                                          }t          |                    d          pd                                          }t          |                    d          pd                                          }	|t          vs|st
                              |          }
|
r                     |
||          r|                    |||	|
pdf           |s||fS t          j
        j                  d fd
t          j                    }t          j        fd|D             ddi d{V }t          t          j                    |z
  dz            }d}t!          ||          D ]\  \  }}}}}t#          |t$                    r1t&                              dj        ||dd         |           |dz  }P||dz  }X|\  }}|                    |           |                    |           t&                              dj        j        t/          |          t/          |          ||           ||fS )a  Resolve inbound media refs: download to local cache, return (local_paths, mime_types).

        Yuanbao COS hostnames resolve to private IPs, tripping the SSRF guard
        in vision_tools. We download ourselves and return local cache paths.

        Resolution runs with bounded concurrency
        (``adapter.media_resolve_concurrency``); see :meth:`_resolve_ybres_refs`
        for the same order-preserving / exception-isolated contract.
        r  r   r  r[  r[   r  r  r\   r  c                  K   4 d {V  	                      |           d {V }nL# t          $ r?}t                              dj        | ||           Y d }~d d d           d {V  d S d }~ww xY w                    || |pd d|d d          |           d {V cd d d           d {V  S # 1 d {V swxY w Y   d S )Nz8[%s] inbound media resolve failed: kind=%s url=%s err=%szplaceholder_url=P   r  r  r  r  r  )r  r  r  r  r[  r  )	r  r  r  r  r  r#  r:  r~   	semaphores	         rb   _resolve_onez@MediaResolveMiddleware._resolve_media_urls.<locals>._resolve_one
  s      !         &)&?&?&M&M M M M M M MII       NNRdC    444               !44'&.$9s3B3x99 # 5                                     s6   B7+B7
A4#A/B7/A440B77
CCc              3  >   K   | ]\  }}}} ||||          V  d S r   r   )r   r  r  r  r  r  s        rb   r   z=MediaResolveMiddleware._resolve_media_urls.<locals>.<genexpr>&
  sO       6 6*$Xs l4h44 6 6 6 6 6 6rd   return_exceptionsTNrw  r   z9[%s] inbound media resolve crashed: kind=%s url=%s err=%sr  rW   z[[%s] media resolve batch: scope=media concurrency=%d total=%d ok=%d failed=%d elapsed_ms=%d)
r  r[   r  r[   r  r[   r  r[   r\   r  )r[   r   r   rM  rN  rv  r  r  r   r   	Semaphoremedia_resolve_concurrencyr   	monotonicgatherri   zipr  BaseExceptionr  r  r[  r  r   )r~   r:  rL  rT  rU  activer  r  r  r  r  _t0results_elapsed_ms_failed	_filename_ridr  r  r  r  r  s   ``                  @@rb   _resolve_media_urlsz*MediaResolveMiddleware._resolve_media_urls	  s      !#
!#24 		< 		<Cswwv,"--3355;;==Dcggenn*++1133C3776??0b117799H222#2*==cBBC s227C[YY MM4h	r:;;;; 	+{**%g&GHH		 	 	 	 	 	 	 	* n6 6 6 6.46 6 6
 #
 
 
 
 
 
 
 

 4>++c1T9::474H4H 	% 	%0(T3	4&&-00 OL$CRC&   1~1%Jj)))t$$$$ 	iL-KK
OO	
 	
 	
 ;&&rd   r  rF  
log_prefixc          	     `   K   g }g }g }|D ]A\  }}}	|t           vr                     |||          r)|                    |||	f           B|s||fS t          j        j                  d fdt          j                    }
t          j        fd|D             d	d
i d{V }t          t          j                    |
z
  dz            }d}t          ||          D ]\  \  }}}	}t          |t                    r*t                              dj        |||           |dz  }H||dz  }P|\  }}|                    |           |                    |           t                              dj        j        t#          |          t#          |          ||           ||fS )u9  Resolve ``(rid, kind, filename)`` ybres tuples to local paths.

        Runs with bounded concurrency (``adapter.media_resolve_concurrency``)
        so cold-start turns with many anchors don't pay ``N × (RPC + download)``
        sequentially. Output order matches input; per-rid failures are isolated.
        r  r[   r  r  r\   r  c           
       K   4 d {V  	                      |            d {V }nM# t          $ r@}t                              dj        | ||           Y d }~d d d           d {V  d S d }~ww xY w                    |||pd  d|  |            d {V cd d d           d {V  S # 1 d {V swxY w Y   d S )Nz-[%s] %s resolve failed: rid=%s kind=%s err=%sz rid=r  )r  r  r  r  r[  r  )	r  r  r  	fresh_urlr#  r:  r~   r  r  s	        rb   r  z@MediaResolveMiddleware._resolve_ybres_refs.<locals>._resolve_oneg
  s               &)&=&=gs&K&K K K K K K KII       NNGj#tS    444               !44'&.$)5555 # 5                                     s6   B2+B2
A5$A0B20A55*B22
B<?B<c              3  :   K   | ]\  }}} |||          V  d S r   r   )r   r  r  r  r  s       rb   r   z=MediaResolveMiddleware._resolve_ybres_refs.<locals>.<genexpr>}
  s8      RR4GCxll3h//RRRRRRrd   r  TNrw  r   z.[%s] %s resolve crashed: rid=%s kind=%s err=%srW   z[[%s] media resolve batch: scope=ybres concurrency=%d total=%d ok=%d failed=%d elapsed_ms=%d)r  r[   r  r[   r  r[   r\   r  )rN  r  r   r   r  r  r   r  r  ri   r  r  r  r  r  r[  r  r   )r~   r:  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  s   `` `             @@rb   _resolve_ybres_refsz*MediaResolveMiddleware._resolve_ybres_refsI
  sM       "$-/#' 	1 	1Cx222**7CeLL MM3h/0000 	&%%%g&GHH		 	 	 	 	 	 	 	 	( nRRRR6RRR
"
 
 
 
 
 
 
 
 4>++c1T9::-0-A-A 	 	)!S$6&-00 DL*c4   1~1JD$t$$$LL 	iL-KK	
 	
 	
 E!!rd   c                t  K   t          |dd          }|sg g fS 	 |                    |          }|                    |j                  }n<# t          $ r/}t
                              d|j        |           g g fcY d}~S d}~ww xY w|sg g fS |t           d         }g }t                      }	t          |          D ]D}
|
                    d          }t          |t                    rd|vr2t          t                              |                    }t          |          D ]}|                    d          }|                    d          }|                    d          \  }}}|                                }|t(          vrc||	v rh|	                    |           |                    |||                                f           t/          |          t0          k    r nt/          |          t0          k    r nF|                                 |sg g fS |                     ||d	
           d{V S )zYResolve recent observed image/file anchors from transcript into ``(local_paths, mimes)``.r  Nz.[%s] Observed-media hydration setup failed: %sr  r  rW   rH   rH  zobserved-mediar  )r  r  r  r  r  r  r  r[   OBSERVED_MEDIA_BACKFILL_LOOKBACKr  rJ  r   r  r[   r<  rK  rL  r  rM  r   rN  r%  r   r   ,OBSERVED_MEDIA_BACKFILL_MAX_RESOLVE_PER_TURNreverser  )r~   r:  r*  r  r   rO  r#  windoworderseenr   r  matchesrQ  rR  r  r  r  r  s                      rb   _collect_observed_mediaz.MediaResolveMiddleware._collect_observed_media
  s     
 !1488 	r6M	!77??M++M,DEEGG 	 	 	NN@c   r6MMMMMM	  	r6M ::;;<,.EEF## 	 	Cggi((Ggs++ y/G/G>227;;<<Gg&&  wwqzzggajj$(NN3$7$7!azz||666$;;c4)9)9:;;;u::!MMME N5zzIII J 	 	r6M,,U'7 - 
 
 
 
 
 
 
 
 	
s   /A 
B$A?9B?BrS  c                B   K   |                      ||d           d{V S )zResolve media anchors carried by the quoted message.

        ``quote_media_refs`` is a list of ``(rid, kind, filename)`` tuples
        produced by :class:`QuoteContextMiddleware` from the transcript.
        r?  r  N)r  )r~   r:  rS  s      rb   _resolve_quote_mediaz+MediaResolveMiddleware._resolve_quote_media
  sK       ,,%' - 
 
 
 
 
 
 
 
 	
rd   r\  r9  c                   g }g }| j         }|s||fS t          | j        dd          }|s||fS |                    |          }t	          |t
                    r|s||fS t                      }t                              |          D ]}|	                    d          pd
                                                                }|	                    d          pd
                                }	|	r|	|v rnt          j                            |	          s|                    |	           t!          t          j                            |	                    p	|dk    rdnd}
|                    |	           |                    |
           ||fS )	u^  Private-chat fallback for recovering already-local quoted media.

        Only already-local media is handled here: by the time a turn is cached,
        ``PatchAnchorsMiddleware`` has rewritten resolved ``|ybres:`` anchors to
        ``[image: /path]`` / ``[file: name → /path]``. Unresolved anchors are an
        original-turn resolution failure and belong to that turn's handling, not
        this quote fallback — so no re-download happens here.

        Returns ``(local_paths, mimes)`` for media already downloaded to the
        local cache on its original turn, ready to inject as-is.
        r  NrW   r   rH   rR   r  r  )rQ  r  r:  r   r  r[   r  _YB_LOCAL_MEDIA_RErL  r  r   rM  rK  r  existsr%  r#   r  r   )r\  pathsr  rid_keycacherZ   r  rQ  r  r  r  s              rb   _collect_quote_local_mediaz1MediaResolveMiddleware._collect_quote_local_media
  s    ) 	 %<%94@@ 	 %<yy!!$$$ 	 D 	 %< EE#,,T22 	 	AGGAJJ$"++--3355DGGAJJ$"++--D 44<<7>>$'' HHTNNN"27#3#3D#9#9::  $5O  LLLLe|rd   c                B  	
K   |j         }g 
g 	t                      d	
fd}|                     ||j                   d {V }t	          d |d         D                       } ||           |j        Q|j        r+ ||                     ||j                   d {V            n ||                     |                     no|j	        dk    rd	  || 
                    ||j                   d {V            n8# t          $ r+}t                              d	|j        |           Y d }~nd }~ww xY w
|_        	|_        t$                              |j        |          r(t                              d
|j        |j                   d S  |             d {V  d S )N
pair_listsr  r\   r   c                    | \  }}t          ||          D ]K\  }}|r|v r                    |                               |                               |           Ld S r   )r  r%  r   )r  u_listm_listurQ  r  typesr  s        rb   _add_unique_pairsz8MediaResolveMiddleware.handle.<locals>._add_unique_pairs  su    'NFFFF++    1 AIIAQ   rd   c              3     K   | ]}|d V  	dS )rW   Nr   )r   r  s     rb   r   z0MediaResolveMiddleware.handle.<locals>.<genexpr>)  s'      55a15555555rd   r   r  z;[%s] observed-image hydration raised, continuing anyway: %sz.[%s] Skip placeholder after media download: %r)r  r  r\   r   )r:  r  r  rL  sumrQ  rS  r  r  rI  r  r*  r  r  r  r[  rT  rU  r  r  rK  r  )r`  r\  r]  r:  r  	own_pairs	own_countr#  r  r  r  s           @@@rb   ra  zMediaResolveMiddleware.handle  s(     
 +EE	  	  	  	  	  	  	  	  227CNKKKKKKKK	559Q<55555	)$$$
 ".# H!!(A(A'3K_(`(`"`"`"`"`"`"`aaaa "!$"A"A#"F"FGGGG]g%%!!(D(DWcj(Y(Y"Y"Y"Y"Y"Y"YZZZZ   QL#         '??iXX 	LLI7<Y\YefffFgiis   *D 
D6!D11D6)r  r[   r\   r  )r  r[   r  r[   r  r[   r\   r   )r  r[   r  r  r  r  r\   r]   r  )r  r[   r\   r[   )r  r[   r  r[   r  rN  r  r[   r  r[   r\   r  )rL  r  r\   r  )r  rF  r  r[   r\   r  )r\   r  )rS  rF  r\   r  )r\  r9  r\   r  r  )r   r   r   r   r[  r{  r5  r}  r  r   r  r  r  r   r  r  r  r  r  r  r  r  r  ra  r   rd   rb   rz  rz  
	  s        @@D DFOEEEE+77777.11111      [ " 	K 	K 	K [	K    [(    \ 8H 8H 8H \8Ht    \0  $(F  F  F  F  F  [F P X' X' X' [X't R" R" R" [R"h :
 :
 :
 [:
x 

 

 

 [

 ( ( ( \(T; ; ; ; ; ;rd   rz  c                  6    e Zd ZdZdZedd	            ZddZdS )PatchAnchorsMiddlewarea,  Replace ``[kind|ybres:RID]`` anchors in ``ctx.raw_text`` with local paths.

    Runs after :class:`MediaResolveMiddleware` so that ``ctx.media_urls`` /
    ``ctx.media_types`` are already populated with downloaded resources
    (own media + quote media or group-observed media).  The transcript
    written downstream then records usable local paths for the model
    instead of opaque ``ybres:`` references.

    Only resolved media (paths starting with ``/``) are substituted; any
    anchor without a corresponding local resource is left untouched.
    zpatch-anchorsrZ   r[   r  r  r  r\   c                   | r|s| S | }t          ||          D ]+\  }}|                    d          st                              |          }|s n|                    d          }|                    d          \  }}	}
|                                }|dk    r|                    d          rd| d}nQ|dk    r=|
                                pt          j        	                    |          }d	| d
| d}n|dk    rd| d}n|d |
                                         |z   ||                                d          z   }-|S )Nr   rW   rH  rR   r  z[image: r|  rQ   r  u    → rS   z[video: )r  r   rK  searchr  rM  r   rK  r  r  startend)rZ   r  r  patchedr  rQ  anchor_matchrR  r  r  r  replacementlabels                rb   _patchzPatchAnchorsMiddleware._patch`  s    	4 	Ke$$ 	 	DAq<<$$ )0099L %%a((D $s 3 3D!X::<<Dw1<<#9#9-ooo ((?BG,<,<Q,?,?888A888-ooo.,,,.../,**,,--./ G
 rd   r\  r9  r   c                   K   |                      |j        |j        |j                  |_         |             d {V  d S r   )r  rK  rT  rU  r_  s      rb   ra  zPatchAnchorsMiddleware.handle~  s>      {{3<QQgiird   N)rZ   r[   r  r  r  r  r\   r[   r  )r   r   r   r   r[  r   r  ra  r   rd   rb   r  r  Q  sW        
 
 D   \:     rd   r  c                  6    e Zd ZdZdZddZedd            ZdS )DispatchMiddlewarez.Build MessageEvent and dispatch to AI handler.dispatchr\  r9  r\   r   c                  	K   j         	t          j        	j        j                            dd          	j        j                            dd                    d	fd}j        d	k    r	j        v}	j                            t          j
                              }|                    |           t                              d
	j        |                                pdd d                    |rpt          j        |                     	          dpdd d                    }	j                            |           |                    	j        j                   nat          j         |            dj        pd           }	j                            |           |                    	j        j                    |             d {V  d S )Ngroup_sessions_per_userTthread_sessions_per_userF)r  r  r\   r   c                   K   t          j        t          d j        D                       rt          j        n,t          j        t                    rj        nt          j        j	        j
        pd j        t          j                  t          j                  j        j        j        
  
        } r'j
        r j
        j        <   j        pdj        <   j
        r]j        rVj        }j        |j
        <   t)          |          dk    r-t          |          d t)          |          dz
           D ]}||=                     |            d {V  d S )Nc              3  @   K   | ]}|                     d           V  dS ))zapplication/ztext/Nr   )r   mts     rb   r   zMDispatchMiddleware.handle.<locals>._dispatch_inbound_event.<locals>.<genexpr>  s/      ^^2==)BCC^^^^^^rd   )
rZ   r  r*  r*  raw_messagerT  rU  rQ  rR  rW  r   r   )r   rK  anyrU  r   r7  r  rP  r	  r*  rF  r?  r<  rT  rQ  rR  rW  r  r  r  r   handle_message)eventr  r   _skr:  r\  s      rb   _dispatch_inbound_eventz:DispatchMiddleware.handle.<locals>._dispatch_inbound_event  s      \ ^^co^^^^^*K((
 *4CL+)N)N *$)z:-H// 11$'$;!/"1%  E(  Hsz H36:+C058\5GR-c2z %cl %2$'Lcj!u::##!%[[):#e**s*:):; % %!!HH((///////////rd   r  z-[%s] Group message enqueued (qsize=%d) for %sr   rT   zyuanbao-group-consumer-r  r  zyuanbao-inbound-r  r2  )r:  r<   r*  configextrar   rI  _group_queues
setdefaultr   Queue
put_nowaitr  r  r[  qsizer#  _consume_group_queue_inbound_tasksr%  r&  r'  rF  )
r`  r\  r]  r  is_newqueueconsumerr(  r  r:  s
    `      @@rb   ra  zDispatchMiddleware.handle  s     +J$+N$8$<$<=VX\$]$]%,^%9%=%=>XZ_%`%`
 
 
	0 	0 	0 	0 	0 	0 	0 	0@ =G## 55F)44S'-//JJE4555KK?ekkmmciR"-=    K".--gs;;ECI2ss3CEE   &**8444**7+A+IJJJ&''))A
(?iAA  D "&&t,,,""7#9#ABBBgiird   r:  'YuanbaoAdapter'r  r[   c                  K   d}| j                             |          }|sdS 	 	 	 t          j        |                                |           d{V }n# t          j        $ r Y nw xY wt
                              d| j        |pddd         |                                           	  |             d{V  || j	        v r#t          j
        d           d{V  || j	        v #n0# t          $ r# t
                              d	| j                   Y nw xY w	 | j                             |d           dS # | j                             |d           w xY w)
zIDrain the group queue one dispatch at a time, waiting for each to finish.rO   NTr   z3[%s] Group queue: dispatching for %s (remaining=%d)r   rT   g?z[%s] Group queue consumer error)r   r   r   wait_forTimeoutErrorr  r  r[  r$  r  r  r  	exceptionr   )r:  r  _IDLE_TIMEOUTr(  dispatch_fns        rb   r%  z'DispatchMiddleware._consume_group_queue  s      %))+66 	F	9V(/(8m(\(\(\"\"\"\"\"\"\KK+   EIL;#4"crc":EKKMM  V%+--'''''''%)AAA%mC000000000 &)AAA  V V V$$%FUUUUUVV  !%%k488888G!%%k48888sS   D5 .A D5 A'$D5 &A''AD5 )<C& %D5 &*DD5 DD5 5ENr  )r:  r*  r  r[   r\   r   )r   r   r   r   r[  ra  r   r%  r   rd   rb   r  r    sX        88D@ @ @ @D 9 9 9 \9 9 9rd   r  c                  b    e Zd ZU dZeeeeee	e
eeeeeeeeeeeeegZded<   edd            ZdS )	InboundPipelineBuilderzFactory for building InboundPipeline instances.

    Separates pipeline assembly (business knowledge) from the pipeline engine
    (InboundPipeline) so the engine stays generic and reusable.
    z
list[type]_DEFAULT_MIDDLEWARESr\   rl  c                p    t                      }| j        D ]}|                     |                        |S )z6Build the default inbound message processing pipeline.)rl  r3  ry  )r~   pipelinemw_clss      rb   buildzInboundPipelineBuilder.build  sA     #$$. 	# 	#FLL""""rd   N)r\   rl  )r   r   r   r   r  r  r  r  r2  r8  r`  rv  r  r  r  r  re  r'  r1  r<  r  rz  r  r  r3  r5  r   r7  r   rd   rb   r2  r2    s           	 #"%')(    .    [  rd   r2  c                     e Zd ZU dZd,dZed             Zed-d	            Zed.d            Zed/d            Z	d/dZ
d0dZd1dZd2dZd0dZd0dZd3dZdZded<   d4dZd5d Zd6d"Zefd7d&Zd0d'Zd/d(Zd/d)Zd0d*Zd+S )8ConnectionManagera  Manages the WebSocket connection lifecycle for YuanbaoAdapter.

    Responsibilities:
      - Opening and closing the WebSocket
      - AUTH_BIND handshake
      - Heartbeat (ping/pong) loop
      - Receive loop (frame dispatch)
      - Reconnect with exponential backoff
    r:  r*  r\   r   c                    || _         d | _        d | _        d | _        d | _        i | _        d | _        d| _        d| _        d| _	        i | _
        i | _        d S )Nr   F)_adapter_ws_connect_id_heartbeat_task
_recv_task_pending_acks_pending_pong_consecutive_hb_timeouts_reconnect_attempts_reconnecting_inbound_buffer_inbound_timersr`  r:  s     rb   rp  zConnectionManager.__init__  se    *.7;268:7;-.%() #(02?Ard   c                    | j         S r   )r<  ri  s    rb   wszConnectionManager.ws(  s	    xrd   rN  c                    | j         S r   )r=  ri  s    rb   
connect_idzConnectionManager.connect_id,  s    rd   ri   c                    | j         S r   )rC  ri  s    rb   reconnect_attemptsz$ConnectionManager.reconnect_attempts0  s    ''rd   r]   c                    | j         dS t          | j         dd           }|du rdS t          |          r)	 t           |                      S # t          $ r Y dS w xY wdS )NFrS  T)r<  r  callabler]   r  )r`  	open_attrs     rb   is_connectedzConnectionManager.is_connected4  s    85DHfd33	4I 	IIKK(((   uuus   A 
AAc                   K   | j         }t          s=d}|                    d|d           t                              d|j        |           dS |j        r|j        s=d}|                    d|d           t                              d	|j        |           dS | j	        g	 t          | j	        dd
          }|du st          |          r, |            r"t                              d|j                   dS n# t          $ r Y nw xY w|                    d|j        d          sdS 	 t                              d|j        |j                   t"                              |j        |j        |j        |j                   d
{V }|                    d          rt+          |d                   |_        t                              d|j        |j                   t1          j        t5          j        |j        d
d
d          t8                     d
{V | _	        |                     |           d
{V }|s|                                  d
{V  dS d| _        |                                  t1          j!                    |_"        t1          j#        | $                                d| j%                   | _&        t1          j#        | '                                d| j%                   | _(        t                              d|j        | j%        |j                   tR          *                    |           dS # t0          j+        $ rR t                              d|j                   |                                  d
{V  |,                                 Y dS t          $ r\}t                              d|j        |d           |                                  d
{V  |,                                 Y d
}~dS d
}~ww xY w)u   Open WebSocket connection: sign-token → WS connect → AUTH_BIND → start loops.

        Returns True on success, False on failure.
        z:Yuanbao startup failed: 'websockets' package not installedyuanbao_missing_dependencyT)	retryablez$[%s] %s. Run: pip install websocketsFzJYuanbao startup failed: YUANBAO_APP_ID and YUANBAO_APP_SECRET are requiredyuanbao_missing_credentialsz[%s] %sNrS  z*[%s] Already connected, skipping connect()zyuanbao-app-keyzYuanbao app keyz [%s] Fetching sign token from %sr   r   z[%s] Connecting to %s   ping_intervalping_timeoutclose_timeoutr   r   yuanbao-heartbeat-r  yuanbao-recv-z%[%s] Connected. connectId=%s botId=%sz[%s] Connection timed outz[%s] connect() failed: %sr  )-r;  WEBSOCKETS_AVAILABLE_set_fatal_errorr  r  r[  r  r  r  r<  r  rO  r  r  _acquire_platform_lockr  r  r   r.  
_route_envr   r[   r6  _ws_urlr   r,  
websocketsconnectCONNECT_TIMEOUT_SECONDS_authenticate_cleanup_wsrC  _mark_connectedget_running_loop_loopr#  _heartbeat_loopr=  r>  _receive_loopr?  YuanbaoAdapter
set_activer-  _release_platform_lock)r`  r:  r   rP  r  authedr#  s          rb   rS  zConnectionManager.openD  s     
 -# 	NC$$%A3RV$WWWNNA7<QTUUU5 	w': 	E  $$%BCSX$YYYLLGL#6665 8#DHfd;;	$$))<)<$$LL!Mw|\\\4    --w/1B
 
 	 5:	KK:GL'J]^^^*44 '"5w7J!,  5          J ~~h'' <"%j&:";"; KK/wOOO$-"O"&!%"#	   0        DH  --j99999999F &&(((((((((u ()D$##%%%#466GM#*#6$$&&-T$BR-T-T$ $ $D  &1""$$+M4;K+M+M  DO KK7d.  
 %%g...4# 	 	 	LL4glCCC""$$$$$$$$$**,,,55 	 	 	LL4glCRVLWWW""$$$$$$$$$**,,,55555		s;   !AC7 7
DD&D+L8 C#L8 8AO=	O=!AO88O=c                v  K   | j         rD| j                                          	 | j          d{V  n# t          j        $ r Y nw xY wd| _         | j        rD| j                                         	 | j         d{V  n# t          j        $ r Y nw xY wd| _        t          d          }| j                                        D ]+}|                                s|	                    |           ,| j        
                                 t                                           |                                  d{V  dS )zGCancel background tasks, fail pending futures, and close the WebSocket.NzYuanbaoAdapter disconnected)r>  cancelr   CancelledErrorr?  r  r@  valuesdoneset_exceptionr   r   r   rg  )r`  disc_excfuts      rb   closezConnectionManager.close  s       	( '')))*********)   #'D ? 	#O""$$$o%%%%%%%%)   "DO   =>>%,,.. 	, 	,C88:: ,!!(+++  """ 	!!!           s!   2 AA/A= =BBr  r  c                :  K   | j         }| j        dS |                    dd          }|j        p|                    dd          }|                    d          pd}|j        p|                    dd          pd}t          t          j                              }t          d	||||t          t          t          |
	  	        }| j                            |           d{V  t                              d|j        ||           	 t!          j                    }	|	                                t&          z   }
	 |
|	                                z
  }|dk    r"t                              d|j                   dS t!          j        | j                                        |           d{V }t/          |t0          t2          f          s	 t5          t1          |                    }n# t6          $ r Y w xY w|                    di           }|                    dd          }|                    dd          }|t8          d         k    ri|dk    rc|                     |          }|r*|| _        t                              d|j        |           dS t                              d|j                   dS {# t           j         $ r$ t                              d|j                   Y dS t6          $ r.}t                              d|j        |d           Y d}~dS d}~ww xY w)zSend AUTH_BIND and read frames until BIND_ACK is received.

        Returns True on success, False on failure/timeout.
        NFr(  r   r   r*  botr   ybBot)	biz_iduidr*  r(  rF  app_versionoperation_systembot_versionr   z&[%s] AUTH_BIND sent (msg_id=%s uid=%s)Tr   z+[%s] AUTH_BIND timeout waiting for BIND_ACKr   rR  cmd_typer   r  Responsez	auth-bindz$[%s] BIND_ACK received: connectId=%sz[%s] BIND_ACK missing connectIdz[%s] AUTH_BIND timeoutz[%s] AUTH_BIND error: %sr  )!r;  r<  r   r6  ra  r[   uuiduuid4r2   r  r  r  r  r  r  r[  r   ri  r   AUTH_TIMEOUT_SECONDSr  r,  recvr  r  	bytearrayr-   r  r%   _extract_connect_idr=  r  r-  )r`  r  r:  r(  r~  r*  r   rF  
auth_bytesrj  deadline	remainingrawr   rR  r  r  rK  r#  s                      rb   rf  zConnectionManager._authenticate  s%     
 -85w++o="!=!=))2U&O*..b*I*IOR	TZ\\""%$.$

 

 


 hmmJ'''''''''=w|VUXYYY%	,..Ezz||&::H%$uzz||3	>>LL!NPWP\]]] 5#,TX]]__iPPPPPPPPP!#y'9:: )%**55CC    H wwvr**88J33hhub))x
333{8J8J!%!9!9#!>!>J! %+5($JGLZdeee#t%FUUU$u7%: # 	 	 	LL17<@@@55 	 	 	LL3W\3QULVVV55555	sW   A-J1 5AJ1 G# "J1 #
G0-J1 /G00BJ1  J1 /J1 1/L#	L,#LLdecoded_msgc                   |                     dd          }|sdS 	 t          t          |                    }t          |d          }|dk    r9t	          |d          }t
                              d| j        j        ||           dS t	          |d          }|r|ndS # t          $ r1}t
          
                    d	| j        j        |           Y d}~dS d}~ww xY w)
z0Extract connectId from decoded BIND_ACK message.r   rd   NrW   r   rH   z*[%s] AuthBindRsp error: code=%d message=%rr   z$[%s] Failed to extract connectId: %s)r   r&   r)   r(   r'   r  r  r;  r[  r  r  )r`  r  r   fdictr   messagerK  r#  s           rb   r  z%ConnectionManager._extract_connect_id  s    !oofc22 	4	#M$$7$788Eua((Dqyy%eQ//@M&g   t$UA..J!+5::5 	 	 	NNA4=CUWZ[[[44444	s   A)B B 
C'&CCc                  K   | j         }	 |j        rWt          j        t                     d{V  | j        /	 t          t          j                              }t          |          }t          j
                    }|                                }|| _        || j        |<   | j                            |           d{V  t                              d|j        |           	 t          j        |d           d{V  d| _        n# t          j        $ r | j                            |d           | xj        dz  c_        t                              d|j        | j        t.                     | j        t.          k    rYt                              d|j                   |                                  Y | j                            |d           d| _        dS Y nw xY w| j                            |d           d| _        n'# | j                            |d           d| _        w xY wn8# t2          $ r+}t                              d	|j        |           Y d}~nd}~ww xY w|j        UdS dS # t          j        $ r Y dS w xY w)
zJSend HEARTBEAT (ping) every 30s; trigger reconnect after threshold misses.Nz[%s] PING sent (msg_id=%s)r?   r   r   rW   z[%s] PONG timeout (%d/%d)z7[%s] Heartbeat threshold exceeded, triggering reconnectz[%s] Heartbeat send failed: %s)r;  _runningr   r  HEARTBEAT_INTERVAL_SECONDSr<  r[   r  r  r3   ri  create_futurerA  r@  r  r  r  r[  r,  rB  r-  r   r  HEARTBEAT_TIMEOUT_THRESHOLDschedule_reconnectr  rs  )r`  r:  rF  
ping_byteslooppong_futurer#  s          rb   rk  z!ConnectionManager._heartbeat_loop#  s     -"	" Vm$>?????????8#V ..F!,V!4!4J"355D262D2D2F2FK)4D&1<D&v.(--
333333333LL!=w|VTTT2%.{DIIIIIIIIII8955"/ 
# 
# 
#*..vt<<<55:557#L$*GId    8<WWW"NN+dfmfrsss 33555"*..vt<<<-1*** XW
# *..vt<<<-1** *..vt<<<-1*1111*  V V VLL!A7<QTUUUUUUUUV= " V V V V V@ % 	 	 	DD	s~   /I, B(H( $#D H  B*G2H  3"H( H  GH  #H(  $H$$H( 'I, (
I2!II, II, ,I?>I?c                  K   | j         }	 | j        2 3 d{V }t          |t          t          f          s$|                     t          |                     d{V  M6 dS # t          j        $ r Y dS t          j	        j
        $ r}t          |dd          }t                              d|j        |t          |dd                     |r?|t          v r6t                              d|j        |           |                                 n|                                  Y d}~dS Y d}~dS d}~wt&          $ r@}t                              d|j        |           |                                  Y d}~dS d}~ww xY w)z(Read WS frames and dispatch by cmd_type.Nr   z3[%s] WebSocket connection closed: code=%s reason=%sreasonr   z7[%s] Close code %d is non-recoverable, NOT reconnectingz[%s] receive_loop exited: %s)r;  r<  r  r  r  _handle_framer   rs  rc  
exceptionsConnectionClosedr  r  r  r[  NO_RECONNECT_CLOSE_CODESr  _mark_disconnectedr  r  )r`  r:  r  	close_exc
close_coder#  s         rb   rl  zConnectionManager._receive_loopL  s     -	&!X 5 5 5 5 5 5 5c!#y'9:: ((s4444444444 &XX % 	 	 	DD$5 	* 	* 	* FD99JNNEj')Xr*J*J    *j,DDDML*   **,,,,''))))))))) -,,,,,  	& 	& 	&NN97<MMM##%%%%%%%%%	&s5   A" AAA" "E74E7BD**E775E22E7r  r  c           	     .  K   | j         }	 t          |          }n9# t          $ r,}t                              d|j        |           Y d}~dS d}~ww xY w|                    di           }|                    dd          }|                    dd          }|                    dd          }|                    d	d
          }	|                    dd          }
|t          d         k    r|dk    rt                              d|j        |           | j        4| j        	                                s| j        
                    d           nN|rL|| j        v rC| j                            |          }|	                                s|
                    d           dS |t          d         k    r(|dv r$t                              d|j        ||           dS |t          d         k    r~|rX|| j        v rO| j                            |          }|	                                s d|i}|
r|
|d<   |
                    |           n"t                              d|j        ||           dS |t          d         k    r}t                              d|j        ||t          |
                     |	rp| j        i	 t!          |          }| j                            |           d{V  n8# t          $ r+}t                              d|j        |           Y d}~nd}~ww xY w|r|| j        v r| j                            |          }|	                                sX	 |
rt%          |
          nd|i}|
                    |           n,# t          $ r}|                    |           Y d}~nd}~ww xY wdS |
rDt                              d|j        |t          |
                     |                     |
           dS t                              d|j        |||           dS )z Handle a single WebSocket frame.z[%s] Failed to decode frame: %sNrR  r  r   r  r   rF  need_ackFr   rd   r  pingz'[%s] HEARTBEAT_ACK received (msg_id=%s)T>   send_group_heartbeatsend_private_heartbeatz-[%s] Heartbeat ACK received: cmd=%s msg_id=%sz)[%s] Unmatched Response: cmd=%s msg_id=%sPushz0[%s] Push received: cmd=%s msg_id=%s data_len=%dz[%s] Failed to send PushAck: %szL[%s] WS received inbound push, decoding and dispatching: cmd=%s, data_len=%dz1[%s] Ignoring frame: cmd_type=%d cmd=%s msg_id=%s)r;  r-   r  r  r  r[  r   r%   rA  ru  
set_resultr@  r   r  r   r<  r4   r  r.   rv  _push_to_inbound)r`  r  r:  r   r#  rR  r  r  rF  r  r   rx  r  	ack_bytesack_excdecodeds                   rb   r  zConnectionManager._handle_frameh  s     -	!#&&CC 	 	 	LL:GL#NNNFFFFF	 wwvr""88J++hhub!!(B''88J..ggfc** x
+++vLLBGLRXYYY!-d6H6M6M6O6O-"--d3333 )Fd&888(,,V44xxzz )NN4(((F x
+++ 8
 1
 1
 LLH',X[]cdddF x
+++ &D$666(,,V44xxzz +$d^F .)-vNN6***?L#v   F x'''KKJGLZ]_egjkogpgpqqq [DH0[ / 5 5I(--	2222222222  [ [ [LL!BGLRYZZZZZZZZ[  &D$666(,,V44xxzz //?C"W"5d";";";&RVw////$ / / /))#......../  ,bL#s4yy   %%d+++F?L(C	
 	
 	
 	
 	
sD    
A!AA//K 
L)!LL*M< <
N%N  N%g      ?float_DEBOUNCE_WINDOWr  r[   c                T   	 t          j        |                    d                    }t          |t                    rw|                    dd          p|                    dd          }|                    dd          p+|                    dd          p|                    dd          }|r| d| S n# t          $ r Y nw xY w	 t          |          }|r/|                    dd           d|                    dd           S n# t          $ r Y nw xY wd	t          |           S )
zLightweight decode to extract sender key for debounce grouping.

        Returns 'from_account:group_code' or a fallback unique key.
        r  rA  r   r  rB  r  r  rH  
__unknown_)	r   r  r  r  r  r   r  r.   r@  )r`  r  r  rA  rB  r?  s         rb   _extract_sender_keyz%ConnectionManager._extract_sender_key  sk   
	Z 8 899F&$'' :JJ~r22 6zz."55 
 JJ|R00 2zz)R002zz*b11 
   :*99Z999 	 	 	D		&x00D V((>266UU,PR9S9SUUUV 	 	 	D	 +BxLL***s$   B2B6 6
CC?D 
DDc           	        |                      |          }| j                            |d          }|r|                                 || j        vr
g | j        |<   | j        |                             |           t                              d| j        j	        |t          | j        |                              t          j                    }|                    | j        | j        |          }|| j        |<   dS )a=  Debounced inbound dispatch.

        Buffers raw frames from the same sender within a short time window,
        then dispatches all buffered data as a single aggregated pipeline
        execution.  This merges multi-part messages (e.g. image + text sent
        as separate WS pushes) into one pipeline run.
        Nz2[%s] Debounce: buffered frame for key=%s, count=%d)r  rF  r   rr  rE  r   r  r  r;  r[  r   r   ri  
call_laterr  _flush_inbound_buffer)r`  r  r  existing_timerr  timers         rb   r  z"ConnectionManager._push_to_inbound  s     &&x00 -11#t<< 	$!!### d***(*D %S!((222@MS)=c)B%C%C	
 	
 	
 '))!&
 

 %*S!!!rd   r  c                   | j                             |d           | j                            |g           }|sdS | j        }t                              d|j        |t          |                     t          ||          }|	                    t          j        |j                            |          d|                      dS )uC   Flush the debounce buffer for a given key — execute the pipeline.Nz1[%s] Debounce flush: key=%s, aggregated %d frames)r:  r=  zyuanbao-pipeline-r  )rF  r   rE  r;  r  r  r[  r   r9  r  r   r#  _inbound_pipeliner  )r`  r  r  r:  r\  s        rb   r  z'ConnectionManager._flush_inbound_buffer  s      d+++(,,S"55	 	F-?L#s9~~	
 	
 	

 WCCCG/%--c22*S**
 
 
 	 	 	 	 	rd   encoded_conn_msgreq_idr   c                  K   | j         t          d          t          j                    }|                                }|| j        |<   	 | j                             |           d{V  t          j        t          j        |          |           d{V }|| j        	                    |d           S # t          j
        $ r  t          $ r  w xY w# | j        	                    |d           w xY w)a	  Send a business-layer request and wait for the response.

        1. Register a Future in pending_acks[req_id]
        2. Send encoded_conn_msg (bytes) to WS
        3. asyncio.wait_for(future, timeout)
        4. Clean up pending_acks on timeout/exception
        NNot connectedr   )r<  r  r   ri  r  r@  r  r,  shieldr   r-  r  )r`  r  r  r   r  futurer  s          rb   send_biz_requestz"ConnectionManager.send_biz_request  s"      8///'))!%!3!3!5!5%+6"		1(-- 0111111111"+GN6,B,BGTTTTTTTTTF ""640000 # 	 	 	 	 	 		 ""640000s   AB6 6CC C1c                    | j         j        r/| j        s*t          j        |                                            dS dS dS )zBSchedule a reconnect only if running and not already reconnecting.N)r;  r  rD  r   r#  _reconnect_with_backoffri  s    rb   r  z$ConnectionManager.schedule_reconnect9  sV    =! 	@$*< 	@ < < > >?????	@ 	@ 	@ 	@rd   c                   K   | j         r't                              d| j        j                   dS d| _         	 |                                  d{V 	 d| _         S # d| _         w xY w)u?   Reconnect with exponential backoff (1s, 2s, 4s, … up to 60s).z,[%s] Reconnect already in progress, skippingFTN)rD  r  r  r;  r[  _do_reconnectri  s    rb   r  z)ConnectionManager._reconnect_with_backoff>  s       	LLGI[\\\5!	'++---------!&DD&&&&s   A 	A$c           	     ,  K   | j         }t          t                    D ]:}|dz   | _        t	          d|z  d          }t
                              d|j        |dz   t          |           t          j	        |           d{V  | 
                                 d{V  	 t                              |j        |j        |j        |j                   d{V }|                    d          rt%          |d                   |_        t          j        t+          j        |j        ddd	          t0          
           d{V | _        |                     |           d{V }|s@t
                              d|j        |dz              | 
                                 d{V  d| _        d| _        |                                 | j        r2| j                                        s| j                                          t          j!        | "                                d| j#                   | _        | j$        r2| j$                                        s| j$                                          t          j!        | %                                d| j#                   | _$        t
                              d|j        |dz   | j#                   tL          '                    |            dS # t          j(        $ r( t
                              d|j        |dz              Y  tR          $ r0}t
                              d|j        |dz   |           Y d}~4d}~ww xY wt
          *                    d|j        t                     |+                                 dS )z>Internal reconnect loop, called under the _reconnecting guard.rW   rH   r   z#[%s] Reconnect attempt %d/%d in %dsNrV  r   rW  rX  r   z![%s] Re-auth failed on attempt %dr   r\  r  r]  z,[%s] Reconnected on attempt %d. connectId=%sTz#[%s] Reconnect attempt %d timed outz$[%s] Reconnect attempt %d failed: %sz*[%s] Giving up after %d reconnect attemptsF),r;  r  MAX_RECONNECT_ATTEMPTSrC  minr  r  r[  r   r  rg  r   r1  r  r  r  ra  r   r[   r6  r,  rc  rd  rb  re  r<  rf  r  rB  rh  r>  ru  rr  r#  rk  r=  r?  rl  rm  rn  r-  r  r  r  )r`  r:  r  waitr  rp  r#  s          rb   r  zConnectionManager._do_reconnectI  s$     -344 A	 A	G'.{D$qG|R((DKK5gk+A4   -%%%%%%%%%""$$$$$$$$$6#.#<#<$g&97;N%0 $= $ $      
 >>(++ @&)*X*>&?&?GO!(!1&&*%)&'	   4" " "        $11*======== NN#FV]`aVabbb**,,,,,,,,,+,(01-'')))' 20D0I0I0K0K 2(//111'.':((**@d.>@@( ( ($
 ? -4?+?+?+A+A -O**,,,")"5&&((;)9;;# # #
 BL'A+t/?   ))'222tt' a a aDglT[^_T_`````   :GL'TU+WZ       
 	8',H^	
 	
 	
 	""$$$us&   DK(#EK((3M	M'%MMc                0  K   | j         }d| _         |	 t          j        |                                t                     d{V  dS # t          j        $ r/ t                              d| j        j	        t                     Y dS t          $ r Y dS w xY wdS )zClose and clear the WebSocket connection, bounded by
        ``WS_CLOSE_TIMEOUT_S`` so an unresponsive server can't stall teardown
        (see the constant's definition for the full rationale).Nr   u>   [%s] WS close handshake exceeded %.1fs — dropping connection)r<  r   r,  ry  WS_CLOSE_TIMEOUT_Sr-  r  r  r;  r[  r  )r`  rI  s     rb   rg  zConnectionManager._cleanup_ws  s       X>&rxxzz;MNNNNNNNNNNNN'    TM&(:          >s   3A	 	:B	BBNr:  r*  r\   r   )r\   rN  r3  r^  r2  )r  r  r\   r]   )r  r  r\   rN  )r  r  r\   r   )r  r  r\   r[   )r  r  r\   r   )r  r[   r\   r   )r  r  r  r[   r   r  r\   r  )r   r   r   r   rp  r  rI  rK  rM  rQ  rS  ry  rf  r  rk  rl  r  r  r5  r  r  r  DEFAULT_SEND_TIMEOUTr  r  r  r  rg  r   rd   rb   r9  r9    s         B B B B"   X       X  ( ( ( X(    X` ` ` `D! ! ! !BC C C CJ   .% % % %R& & & &8T
 T
 T
 T
p "!!!!+ + + +> *  *  *  *D   4 .	1 1 1 1 1@@ @ @ @
	' 	' 	' 	'J J J JX     rd   r9  c                  X    e Zd ZdZedd            Zedd            ZddZ	 	 dddZdS )MediaSendHandleru{  Abstract base class for media send strategies.

    Subclasses implement:
      - acquire_file(): how to obtain file bytes (download URL / read local)
      - build_msg_body(): how to build TIMxxxElem from upload result

    The shared flow (check ws → cancel notifier → validate → COS upload
    → lock → dispatch) is handled by the base handle() template method.
    r:  r*  kwargsr   r\   Tuple[bytes, str, str]c                
   K   dS )zReturn (file_bytes, filename, content_type).

        Raises:
            ValueError: when file cannot be acquired (not found, empty, etc.)
        Nr   r`  r:  r  s      rb   acquire_filezMediaSendHandler.acquire_file  rb  rd   upload_resultr  r<  c                    dS )z<Build platform-specific MsgBody list from COS upload result.Nr   r`  r  r  s      rb   build_msg_bodyzMediaSendHandler.build_msg_body  s      rd   r]   c                    dS )z:Override to return False for non-COS media (e.g. sticker).Tr   ri  s    rb   needs_cos_uploadz!MediaSendHandler.needs_cos_upload  s    trd   NrH  r[   reply_torN  caption'SendResult'c           	     4  K   |j         }|j        j        }|j        t	          ddd          S |j                            |           	  | j        |fi | d{V \  }}	}
|                                 r4t          	                    ||	|j
                  }|rt	          d|          S |                                 rt          |          }|                                 d{V }|                    dd          }|                    d	d          p|j        pd}t          |j        |j        ||	||j        
           d{V }t'          ||	|
||d         |d                    d{V }d |                                D             } | j        |f||	|
d|}n | j        i fi |}|r|                    dd|id           |                    dd          }|                    ||||           d{V S # t0          $ r(}t	          dt3          |                    cY d}~S d}~wt4          $ r`}t7          |           j        }t:                              d|j        ||d           t	          dt3          |                    cY d}~S d}~ww xY w)z(Template method: shared media send flow.NFr  Tsuccessr  rT  r  r  r(  r   r   )r   r   r(  r  r   r   
bucketNameregion)r  r  r  credentialsbucketr  c                "    i | ]\  }}|d v	||S )>   r  	file_uuidr  r   )r   r   r   s      rb   
<dictcomp>z+MediaSendHandler.handle.<locals>.<dictcomp>  s4       !Q III qIIIrd   )r  r  r  r  rZ   r  rB  rB  z[%s] %s.handle() failed: %sr  ) _connectionrY  rC  rI  r   cancel_slow_notifierr  r  MessageSendervalidate_mediar  r$   r  r   r6  r   r  r  ra  r    r   r  r   dispatch_msg_bodyr  r[   r  r`  r   r  r  r[  )r`  r:  rH  r  r  r  connrC  r  r  r  validation_errr  r  r(  r   r  r  
fwd_kwargsrE  gcver#  handler_names                           rb   ra  zMediaSendHandler.handle  s      "")7?e?dSSSS..w777P	=7Ht7H8 8!8 8 2 2 2 2 2 2.J, $$&& K!.!=!='*C" " " K%e>JJJJ$$&& ,=#J//	 $+#<#<#>#>>>>>>>
'^^GR88NN8R00IGOIr  %8#,&2%!%0% % %       '4)%!- +&|4&x0' ' ' ! ! ! ! ! ! %+\\^^  
 /4.!'%!-	 
 !  /4.r<<V<<  !.?PQQ  
 L"--B11'8XZ\1]]]]]]]]] 	< 	< 	<e3r77;;;;;;;;; 	= 	= 	=::.LLL-lC$     e3s88<<<<<<<<<	=s9   
AG= *EG= =
JH*$J*J7AJJJ)r:  r*  r  r   r\   r  )r  r  r  r   r\   r<  r^  r  )r:  r*  rH  r[   r  rN  r  rN  r  r   r\   r  )	r   r   r   r   r
   r  r  r  ra  r   rd   rb   r  r    s             ^ K K K ^K    #'!%a= a= a= a= a= a= a=rd   r  c                      e Zd ZdZd Zd ZdS )ImageUrlHandleruD   Strategy: send image from a URL (download → COS → TIMImageElem).c                |  K   |d         }t                               d|j        |           t          ||j                   d {V \  }}|r|dk    r,|                    d          d         }t          |          pd}t          j        	                    |                    d          d                   pd}|||fS )	Nr  z$[%s] ImageUrlHandler: downloading %sr  r  ?r   r  	image.jpg)
r  r  r[  r  r  r   r#   rK  r  r  )r`  r:  r  r  r  r  	path_partr  s           rb   r  zImageUrlHandler.acquire_file-  s      ,	:GL)TTT);7#<*
 *
 *
 $
 $
 $
 $
 $
 $
 
L  	F|/III!,,Q/I*955EL7##IOOC$8$8$;<<K8\11rd   c                    t          |d         |d         |d         |d         |                    dd          |                    dd          |d         	          S 
Nr  r  r  sizewidthr   heightr  )r  r  r  r  r  r  	mime_typer!   r   r  s      rb   r  zImageUrlHandler.build_msg_body9  g    #e$$J'v&##GQ// $$Xq11^,
 
 
 	
rd   Nr   r   r   r   r  r  r   rd   rb   r  r  *  s8        NN
2 
2 
2	
 	
 	
 	
 	
rd   r  c                      e Zd ZdZd Zd ZdS )ImageFileHandleruL   Strategy: send image from a local file path (read → COS → TIMImageElem).c                  K   |d         }t           j                            |          st          d|           t                              d|j        |           t          |d          5 }|                                }d d d            n# 1 swxY w Y   t           j        	                    |          pd}t          |          pd}|||fS )N
image_pathFile not found: z![%s] ImageFileHandler: reading %srbr  r  )rK  r  r  r  r  r  r[  rS  readr  r#   )r`  r:  r  r  fr  r  r  s           rb   r  zImageFileHandler.acquire_fileH  s       .
w~~j)) 	><
<<===7zRRR*d## 	"qJ	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"7##J//>;&x00@L8\11   -BBBc                    t          |d         |d         |d         |d         |                    dd          |                    dd          |d         	          S r  r  r  s      rb   r  zImageFileHandler.build_msg_bodyS  r  rd   Nr  r   rd   rb   r  r  E  s8        VV	2 	2 	2	
 	
 	
 	
 	
rd   r  c                      e Zd ZdZd Zd ZdS )FileUrlHandleruB   Strategy: send file from a URL (download → COS → TIMFileElem).c                x  K   |d         }t                               d|j        |           t          ||j                   d {V \  }}|                    d          }|s<|                    d          d         }t          j        	                    |          pd}|r|dk    rt          |          pd}|||fS )	Nr  z#[%s] FileUrlHandler: downloading %sr  r  r  r   rQ   r  )r  r  r[  r  r  r   r   rK  r  r  r#   )r`  r:  r  r  r  r  r  r  s           rb   r  zFileUrlHandler.acquire_fileb  s      z*97<RRR);'";*
 *
 *
 $
 $
 $
 $
 $
 $
 
L ::j)) 	= s++A.Iw''	22<fH 	S|/III*844R8RL8\11rd   c                X    t          |d         |d         |d         |d                   S Nr  r  r  r  )r  r  r  r  r"   r  s      rb   r  zFileUrlHandler.build_msg_bodyp  9    "e$J'$v&	
 
 
 	
rd   Nr  r   rd   rb   r  r  _  s8        LL2 2 2
 
 
 
 
rd   r  c                      e Zd ZdZd Zd ZdS )DocumentHandleruB   Strategy: send local file/document (read → COS → TIMFileElem).c                  K   |d         }t           j                            |          st          d|           t                              d|j        |           t          |d          5 }|                                }d d d            n# 1 swxY w Y   |	                    d          p t           j        
                    |          pd}t          |          pd}|||fS )N	file_pathr  z [%s] DocumentHandler: reading %sr	  r  rc  r  )rK  r  r  r  r  r  r[  rS  r
  r   r  r#   )r`  r:  r  r  r  r  r  r  s           rb   r  zDocumentHandler.acquire_file|  s     ,	w~~i(( 	=;	;;<<<6iPPP)T"" 	"aJ	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"::j))VRW-=-=i-H-HVJ&x00N4N8\11r  c                X    t          |d         |d         |d         |d                   S r  r  r  s      rb   r  zDocumentHandler.build_msg_body  r  rd   Nr  r   rd   rb   r  r  y  s8        LL	2 	2 	2
 
 
 
 
rd   r  c                  &    e Zd ZdZddZd Zd ZdS )	StickerHandlerzAStrategy: send sticker/emoji (TIMFaceElem, no COS upload needed).r\   r]   c                    dS )NFr   ri  s    rb   r  zStickerHandler.needs_cos_upload  s    urd   c                
   K   dS )N)rd   stickerr  r   r  s      rb   r  zStickerHandler.acquire_file  s      99rd   c                   ddl m}m}m}m} |                    d          }|                    d          }|* ||          }	|	t          d|           ||	          S | ||          S  |            }	 ||	          S )Nr   )get_sticker_by_nameget_random_stickerbuild_face_msg_bodybuild_sticker_msg_bodysticker_name
face_indexzSticker not found: )r%  )!gateway.platforms.yuanbao_stickerr   r!  r"  r#  r   r  )
r`  r  r  r   r!  r"  r#  r$  r%  r  s
             rb   r  zStickerHandler.build_msg_body  s    	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 zz.11ZZ--
#)),77G !G|!G!GHHH))'222#&&*====((**G))'222rd   Nr^  )r   r   r   r   r  r  r  r   rd   rb   r  r    sL        KK   : : :3 3 3 3 3rd   r  c                  D    e Zd ZdZddZdd
Z	 dddZddZ	 	 dd dZdS )!GroupQueryServiceaN  Encapsulates all group query operations (both low-level WS calls and
    higher-level AI-tool-facing wrappers).

    Responsibilities:
      - Low-level WS encode/decode for group info and member list queries
      - Chat-id parsing, error wrapping and result filtering for AI tools
      - Member cache population on the adapter
    r:  r*  r\   r   c                    || _         d S r   )r;  rG  s     rb   rp  zGroupQueryService.__init__  s    rd   rB  r[   r>  c                  K   | j         }|j        j        dS t          |          }ddlm}  ||          }|d         d         }	 |j                            ||           d{V }|                    di           }|                    dd          }	|	dk    r#t          	                    d|j
        |	           dS |                    d	d
          p|                    dd
          }
|
r$t          |
t                    rt          |
          S d|iS # t          j        $ r% t          	                    d|j
        |           Y dS t           $ r,}t          	                    d|j
        |           Y d}~dS d}~ww xY w)zQuery group info via WS (group name, owner, member count, etc.).

        Returns:
            Decoded dict or None on failure.
        Nr   r-   rR  rF  r  statusz'[%s] query_group_info failed: status=%dr   rd   r!  rB  z'[%s] query_group_info timeout: group=%sz [%s] query_group_info failed: %s)r;  r  rI  r9   gateway.platforms.yuanbao_protor-   r  r   r  r  r[  r  r  r0   r   r-  r  )r`  rB  r:  encoded_decoder  r  r   rR  r-  biz_datar#  s               rb   query_group_info_rawz&GroupQueryService.query_group_info_raw  s      -!)4)*55NNNNNN''""*	$0AA'RXAYYYYYYYYH<<++DXXh**F{{H',X^___t||FC00MHLL4M4MH =Jx77 =28<<< *--# 	 	 	NNDglT^___44 	 	 	NN=w|SQQQ44444	s,   A5D >AD D 0E<	E<!E77E<r   r   offsetri   limitc                  K   | j         }|j        j        dS t          |||          }ddlm}  ||          }|d         d         }	 |j                            ||           d{V }	|	                    di           }
|
                    dd          }|dk    r#t          	                    d	|j
        |           dS |	                    d
d          p|	                    dd          }|r%t          |t                    rt          |          }ng ddd}|r8|                    d          r#t          j                    |d         f|j        |<   |S # t           j        $ r% t          	                    d|j
        |           Y dS t$          $ r,}t          	                    d|j
        |           Y d}~dS d}~ww xY w)zQuery group member list via WS.

        Returns:
            Decoded dict or None on failure.  Also populates adapter._member_cache.
        Nr3  r4  r   r+  rR  rF  r,  r-  z,[%s] get_group_member_list failed: status=%dr   rd   r!  T)membersnext_offsetis_completer7  z,[%s] get_group_member_list timeout: group=%sz%[%s] get_group_member_list failed: %s)r;  r  rI  r:   r.  r-   r  r   r  r  r[  r  r  r1   r   _member_cacher   r-  r  )r`  rB  r3  r4  r:  r/  r0  r  r  r   rR  r-  r1  r  r#  s                  rb   get_group_member_list_rawz+GroupQueryService.get_group_member_list_raw  s      -!)4.z&PUVVVNNNNNN''""*	$0AA'RXAYYYYYYYYH<<++DXXh**F{{Mw|]cdddt||FC00MHLL4M4MH PJx77 P9(CC%'$OO U&**Y// U59Y[[&BS4T%j1M# 	 	 	NNI7<Ycddd44 	 	 	NNBGLRUVVV44444	s&   
A5E BE 0F>		F>!F99F>rH  r  c                   K   |                     d          sddiS |t          d          d         }|                     |           d{V }|ddiS |S )zAI tool: Query current group info.

        No parameters needed (group_code extracted from session context).
        Returns group name, owner, member count, etc.
        r  r  -This command is only available in group chatsNzFailed to query group info)r   r   r2  )r`  rH  rB  r  s       rb   query_group_infoz"GroupQueryService.query_group_info  sz       !!(++ 	NLMMS]]^^,
00<<<<<<<<>9::rd   list_allNactionr[  rN  c                  	K   |                     d          sddiS |t          d          d         }|                     |           d{V }|ddiS |                    dg           }|dk    r%|r#|                                		fd|D             }n|d	k    rd
 |D             }d}|r7t          |          dk    r$d |D             }dd                    |          z   }|dd         t          |          |dS )a\  AI tool: Query group member list.

        Args:
            chat_id: Chat ID (extracted from session context)
            action: 'find' (search by name) | 'list_bots' (list bots) | 'list_all' (list all)
            name: Search keyword when action='find'

        Returns:
            {"members": [...], "total": int, "mentionHint": str}
        r  r  r=  NzFailed to query group membersr7  findc                   g | ]}|                     d d          pd                                v sX|                     dd          pd                                v s,|                     dd          pd                                v |S )rx  r   	name_cardr  r   rM  )r   rQ  r  s     rb   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>1  s       QUU:r228b??AAAAQUU;339r@@BBBBQUU9b117R>>@@@@  A@@rd   	list_botsc                j    g | ]0}d |                     dd          pd                                v .|1S )r{  rx  r   rE  r   rQ  s     rb   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>8  sB    \\\QUquuZ7L7L7RPR6Y6Y6[6[-[-[q-[-[-[rd   r   
   c                    g | ]B}|                     d           p*|                     d          p|                     dd          CS )rD  rx  r  r   r   rH  s     rb   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>=  sJ    fffYZQUU;''T155+<+<TiQS@T@Tfffrd   zMention with @name: z, rT   )r7  totalmentionHint)r   r   r;  r   rM  r   )
r`  rH  r@  r[  rB  r  r7  mention_hintnamesr  s
            @rb   query_session_membersz'GroupQueryService.query_session_members  sb       !!(++ 	NLMMS]]^^,
55jAAAAAAAA><==**Y++VJJLLE   "  GG {""\\'\\\G  	Es7||r))ff^efffE1DIIe4D4DDL ss|\\'
 
 	
rd   r  rB  r[   r\   r>  r   r   rB  r[   r3  ri   r4  ri   r\   r>  )rH  r[   r\   r  )r?  N)rH  r[   r@  r[   r[  rN  r\   r  )	r   r   r   r   rp  r2  r;  r>  rO  r   rd   rb   r(  r(    s                   @ >A# # # # #R   " !"	.
 .
 .
 .
 .
 .
 .
rd   r(  c                  D    e Zd ZdZddZddZddZddZdddZddZ	dS )HeartbeatManagerzManages reply heartbeat (RUNNING / FINISH) lifecycle.

    Responsibilities:
      - Periodic RUNNING heartbeat sender (every 2s)
      - Auto-FINISH after 30s inactivity
      - Explicit stop with optional FINISH signal
    r:  r*  r\   r   c                0    || _         i | _        i | _        d S r   )r;  _reply_heartbeat_tasks_reply_hb_last_activerG  s     rb   rp  zHeartbeatManager.__init__P  s    ?A#79"""rd   rH  r[   heartbeat_valri   c                <  K   | j         }|j        }|j        |j        sdS 	 |                    d          r/|t          d          d         }t          |j        ||          }n,|                    d          }t          |j        ||          }|j        	                    |           d{V  |t          k    rdnd}t                              d|j        ||           dS # t          $ r,}	t                              d	|j        |	           Y d}	~	dS d}	~	ww xY w)
z9Send a single heartbeat (RUNNING or FINISH), best effort.Nr  )rA  rB  rZ  r  )rA  r  rZ  RUNNINGFINISHz%[%s] Reply heartbeat %s sent: chat=%sz#[%s] send_heartbeat_once failed: %s)r;  r  rI  r6  r   r   r8   removeprefixr7   r  r*   r  r  r[  r  )
r`  rH  rX  r:  r  rB  r/  r  status_namer#  s
             rb   r[  z$HeartbeatManager.send_heartbeat_onceU  se     -"7?'/?F	S!!(++ $S]]^^4
5!()+   %11)<<
7!()+  
 ',,w''''''''''48L'L'L))RZKLL7k7      	S 	S 	SLL>cRRRRRRRRR	Ss   CC% %
D/!DDc                  K   | j         }|j        }|j        |j        sdS | j                            |          }|r1|                                st          j                    | j        |<   dS t          j                    | j        |<   t          j
        |                     |          d|           }|| j        |<   dS )zGStart or renew the Reply Heartbeat periodic sender (RUNNING, every 2s).Nzyuanbao-reply-hb-r  )r;  r  rI  r6  rV  r   ru  r   rW  r   r#  _worker)r`  rH  r:  r  existingr(  s         rb   r  zHeartbeatManager.starts  s      -"7?'/?F.227;; 	HMMOO 	26)++D&w/F.2ikk"7+"LL!!.W..
 
 
 04#G,,,rd   c                t  K   	 |                      |t                     d{V  	 t          j        t                     d{V  | j                            |d          }t          j                    |z
  t          k    rn6| j	        j
        }|j        n"|                      |t                     d{V  d}n$# t          j        $ r d}Y nt          $ r d}Y nw xY w|s3	 |                      |t                     d{V  n# t          $ r Y nw xY w| j                            |d           | j                            |d           dS # |s3	 |                      |t                     d{V  n# t          $ r Y nw xY w| j                            |d           | j                            |d           w xY w)ztBackground coroutine: send RUNNING heartbeat every 2s.
        30s without renewal -> send FINISH and exit.
        NTr   F)r[  r*   r   r  REPLY_HEARTBEAT_INTERVAL_SrW  r   r   REPLY_HEARTBEAT_TIMEOUT_Sr;  r  rI  rs  r  r+   rV  r   )r`  rH  last_activer  	cancelleds        rb   r_  zHeartbeatManager._worker  sF     	:**74HIIIIIIIIINm$>?????????"8<<WaHH9;;,/HHH}07?..w8LMMMMMMMMMN$ II % 	 	 	III 	 	 	III	
  227<OPPPPPPPPPP    D'++GT:::&**7D99999  227<OPPPPPPPPPP    D'++GT:::&**7D9999sl   B2B9 6E
 9C
E
 CE
 CE
  !D 
DD
F7!E0/F70
E=:F7<E==:F7Tsend_finishr]   c                @  K   | j                             |d          }|rG|                                s3|                                 	 | d{V  n# t          j        $ r Y nw xY w|r5	 |                     |t                     d{V  dS # t          $ r Y dS w xY wdS )z0Stop Reply Heartbeat and optionally send FINISH.N)	rV  r   ru  rr  r   rs  r[  r+   r  )r`  rH  rf  r(  s       rb   stopzHeartbeatManager.stop  s      *..w== 			 	KKMMM







)    	..w8KLLLLLLLLLLL   	 	s$   	A A$#A$*!B 
BBc                  K   t          | j                                                  D ]*}|                                s|                                 +| j                                         | j                                         dS )z!Cancel all reply heartbeat tasks.N)r<  rV  rt  ru  rr  r   rW  r`  r(  s     rb   ry  zHeartbeatManager.close  sz      4;;==>> 	 	D99;; #))+++"((*****rd   Nr  )rH  r[   rX  ri   r\   r   rH  r[   r\   r   )TrH  r[   rf  r]   r\   r   r2  )
r   r   r   r   rp  r[  r  r_  rh  ry  r   rd   rb   rT  rT  G  s         : : : :
S S S S<4 4 4 4(!: !: !: !:F    + + + + + +rd   rT  c                  :    e Zd ZdZddZddZddZddZddZdS )SlowResponseNotifierzManages delayed 'please wait' notifications for slow agent responses.

    Starts a timer per chat_id; if the agent hasn't replied within
    SLOW_RESPONSE_TIMEOUT_S seconds, sends a courtesy message.
    r:  r*  rC  'MessageSender'r\   r   c                0    || _         || _        i | _        d S r   )r;  _sender_tasks)r`  r:  rC  s      rb   rp  zSlowResponseNotifier.__init__  s    /1rd   rH  r[   c                   K   |                      |           t          j        |                     |          d|           }|| j        |<   dS )zCStart a delayed task that notifies the user when the agent is slow.zyuanbao-slow-resp-r  N)rr  r   r#  	_notifierrr  r`  rH  r(  s      rb   r  zSlowResponseNotifier.start  s]      G"NN7##/g//
 
 
  $Grd   c                  K   	 t          j        t                     d{V  t                              d| j        j        t          t                    |           | j        	                    |t                     d{V  dS # t           j        $ r Y dS t          $ r1}t                              d| j        j        |           Y d}~dS d}~ww xY w)z@Wait SLOW_RESPONSE_TIMEOUT_S, then push a 'please wait' message.Nz<[%s] Agent response exceeded %ds for %s, sending wait noticez&[%s] Slow-response notifier failed: %s)r   r  SLOW_RESPONSE_TIMEOUT_Sr  r  r;  r[  ri   rq  send_text_chunkSLOW_RESPONSE_MESSAGErs  r  r  )r`  rH  r#  s      rb   rt  zSlowResponseNotifier._notifier  s      
	\- 7888888888KKN"C(?$@$@'   ,..w8MNNNNNNNNNNN% 	 	 	DD 	\ 	\ 	\LLA4=CUWZ[[[[[[[[[	\s   A>B C	C&CCc                    | j                             |d          }|r*|                                s|                                 dS dS dS )z@Cancel the pending slow-response notifier for *chat_id*, if any.N)rr  r   ru  rr  ru  s      rb   rr  zSlowResponseNotifier.cancel  sS    {w-- 			 	KKMMMMM	 	 	 	rd   c                   K   t          | j                                                  D ]*}|                                s|                                 +| j                                         dS )zCancel all slow-response tasks.N)r<  rr  rt  ru  rr  r   rj  s     rb   ry  zSlowResponseNotifier.close  sc      ++--.. 	 	D99;; rd   N)r:  r*  rC  ro  r\   r   rk  r2  )	r   r   r   r   rp  r  rt  rr  ry  r   rd   rb   rn  rn    s         2 2 2 2
$ $ $ $\ \ \ \        rd   rn  c                  v   e Zd ZU dZ eh d          Zded<   dZded<   dIdZdJdZ	dKdZ
	 	 dLdMdZ	 	 dNdOd!Z	 dPdQd&Z	 	 dLdRd)Z	 	 	 dSdTd.ZdUdVd1Z	 dPdWd2Z ej        d3ej                  ZdXd4ZdUdYd5Z	 dPdZd6Zed[d:            Ze	 d\d]d@            Ze	 	 d^d_dF            Zed`dG            ZdadHZdS )br  ac  Core message sending dispatcher for YuanbaoAdapter.

    Responsibilities:
      - Per-chat-id lock management (serial send ordering)
      - Text chunk sending with retry
      - C2C / Group message encoding and dispatch
      - Media send helpers (image, file, sticker, document)
      - Direct send helper (text + media, used by send_message tool)
    >   r  r  r  r  r  r  zClassVar[frozenset]
IMAGE_EXTSrw  r|  CHAT_DICT_MAX_SIZEr:  r*  r\   r   c                    || _         t          j                    | _        d | _        d | _        t                      t                      t                      t                      t                      d| _        d S )N)r  
image_filer  rc  r  )r;  collectionsOrderedDict_chat_locks_on_send_start_on_send_finishr  r  r  r  r  _media_handlersrG  s     rb   rp  zMessageSender.__init__  sq    GRG^G`G` ?C?C )***,,&(('))%''=
 =
rd   r[  r[   rs  r  c                    || j         |<   dS )z1Register (or replace) a named media send handler.N)r  )r`  r[  rs  s      rb   register_handlerzMessageSender.register_handler  s    %,T"""rd   rH  r   c                   || j         v r'| j                             |           | j         |         S t          | j                   | j        k    rd}t	          | j                   D ]?}| j         |                                         s| j                             |           d} n@|s9| j                             t          t          | j                                        t          j
                    | j         |<   | j         |         S )z=Return (or create) a per-chat-id lock with safe LRU eviction.FT)r  move_to_endr   r~  r<  lockedr   r  iterr   r   )r`  rH  evictedr  s       rb   get_chat_lockzMessageSender.get_chat_lock  s   d&&&((111#G,,t  D$;;;GD,--  ',3355 $((---"GE  C $$T$t/?*@*@%A%ABBB$+LNN!((rd   Nr   r  r  rN  rB  r  c           
     H  K   | j         }|j        }|j        t          ddd          S | j        r|                     |           |                     |          }|4 d{V  |                     |          }|                     ||j                  }	t          
                    d|j        t          |          |j        t          |	          d |	D                        t          |	          D ]K\  }
}|
dk    r|nd}|                     ||||	           d{V }|j        s|c cddd          d{V  S L	 ddd          d{V  n# 1 d{V swxY w Y   | j        r-	 |                     |           d{V  n# t"          $ r Y nw xY wt          d
          S )zHSend text message with auto-chunking and per-chat-id ordering guarantee.NFr  Tr  zJ[%s] truncate_message: input=%d chars, max=%d, output=%d chunk(s) sizes=%sc                ,    g | ]}t          |          S r   r   r   cs     rb   r   z+MessageSender.send_text.<locals>.<listcomp>D  s    555c!ff555rd   r   r  )r  )r;  r  rI  r   r  r  strip_cron_wrappertruncate_messageMAX_TEXT_CHUNKr  r  r[  r   r  rx  r  r  r  )r`  rH  r  r  rB  r:  r  lockcontent_to_sendr   r~  chunkr_tor  s                 rb   	send_textzMessageSender.send_text-  s      -"7?e?dSSSS 	)(((!!'** 	" 	" 	" 	" 	" 	" 	" 	""55g>>O**?G<RSSFKK\c/22G4JF55f555  
 &f-- " "5#$66xxt#33GUDU_3````````~ "!MM	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"""	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"  	**73333333333   $''''s+   #CE;E
EE(F 
FFr  r  r  r   c                   K   | j                             |          }|t          dd|          S  |j        | j        |f||d| d{V S )2Dispatch media send to the named handler strategy.NFzUnknown media handler: r  )r  r  )r  r   r   ra  r;  )r`  rH  r  r  r  r  rs  s          rb   
send_mediazMessageSender.send_mediaT  s       &**<88?@@@    $W^M7
w
 
28
 
 
 
 
 
 
 
 	
rd   r  media_files Optional[List[Tuple[str, bool]]]Dict[str, Any]c                  K   | j         }d}|                                r/|                    ||           d{V }|j        sdd|j         iS |pg D ]\  }}t          |          j                                        }|| j        v r|	                    ||           d{V }n|
                    ||           d{V }|j        sdd|j         ic S |ddiS dd||r|j        nddS )	a@  Send text + media via Yuanbao (used by the ``send_message`` tool).

        Unlike Weixin which creates a fresh adapter per call, Yuanbao reuses
        the running gateway adapter (persistent WebSocket).  Logic mirrors
        send_weixin_direct: send text first, then iterate media_files by
        extension.
        Nr  zYuanbao send failed: zYuanbao media send failed: z6No deliverable text or media remained after processingTrI  )r  platformrH  r*  )r;  r   r  r  r  r   suffixrM  r}  send_image_filesend_documentr*  )	r`  rH  r  r  r:  last_result
media_path	_is_voicer  s	            rb   send_directzMessageSender.send_directj  sr      -.2 ==?? 	N 'Wg > >>>>>>>K& N!L9J!L!LMM &1%6B 	T 	T!J	z"")//11Cdo%%$+$;$;GZ$P$PPPPPPP$+$9$9':$N$NNNNNNN& T!R{?P!R!RSSSST UVV !4?I+00T	
 
 	
rd   rE  r<  c                @  K   |                      |          }|4 d{V  |                    d          r5|t          d          d         }|                     |||           d{V }n3|                    d          }|                     |||           d{V }	 ddd          d{V  n# 1 d{V swxY w Y   |                    d          r$t          d|                    d                    S t          d	|                    d
d                    S )z5Lock + dispatch an arbitrary MsgBody to C2C or group.Nr  r  r  r  Tr  r  r*  Fr  Unknown errorr  )r  r   r   send_group_msg_bodyr\  send_c2c_msg_bodyr   r   )	r`  rH  rE  r  rB  r  grpr  r  s	            rb   r  zMessageSender.dispatch_msg_body  s      !!'** 	c 	c 	c 	c 	c 	c 	c 	c!!(++ cc(mmnn-#77XxPPPPPPPP$11)<<
#55j(Wa5bbbbbbbbb	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c ::i   	Ndvzz)7L7LMMMM%vzz'?/S/STTTTs   A>B11
B;>B;r   rZ   retryri   c           	     |  K   | j         }d}t          |          D ]k}	 |                    d          r5|t          d          d         }	|                     |	||           d{V }
n3|                    d          }|                     |||           d{V }
|
                    d          r&t          d|
                    d          	          c S |
                    d
d          }t          
                    d|j        |dz   ||           nL# t          $ r?}t          |          }t          
                    d|j        |dz   ||           Y d}~nd}~ww xY w||dz
  k     rt          j        d|z             d{V  mt                              d|j        ||           t          dd|           S )zFSend a single text chunk with retry (exponential backoff: 1s, 2s, 4s).r  r  Nr  r  r  Tr  r  r  z-[%s] send_text_chunk attempt %d/%d failed: %srW   z0[%s] send_text_chunk attempt %d/%d exception: %srH   z>[%s] send_text_chunk max retries (%d) exceeded. Last error: %sFzMax retries exceeded: r  )r;  r  r   r   send_group_messager\  send_c2c_messager   r   r  r  r[  r  r[   r   r  r  )r`  rH  rZ   r  r  rB  r:  
last_errorr  r  r  r  r#  s                rb   rx  zMessageSender.send_text_chunk  s#      -)
U|| 	2 	2G%%h// _!#h--..1C $ 7 7T8 L LLLLLLLCC!(!5!5i!@!@J $ 5 5j$S] 5 ^ ^^^^^^^C779%% S%dswwy?Q?QRRRRRR WWWo>>
CL'A+uj        XX
FL'A+uj        ""mAL111111111LL%	
 	
 	
 %/T
/T/TUUUUs   B5D<D
E5EEr  r  c                R   K   dd|idg}|                      |||           d{V S )z<Send C2C text message, return {success: bool, msg_key: str}.r  rZ   r  r  N)r  )r`  r  rZ   rB  rE  s        rb   r  zMessageSender.send_c2c_message  sG      !.~NNO++JZ+XXXXXXXXXrd   c                l   K   |                      ||          }|                     |||           d{V S )zDSend group text message, auto-converting @nickname to TIMCustomElem.N)_build_msg_body_with_mentionsr  )r`  rB  rZ   r  rE  s        rb   r  z MessageSender.send_group_message  sG       55dJGG--j(HMMMMMMMMMrd   z!(?:(?<=\s)|(?<=^))@(\S+?)(?=\s|$)c                   | j         j                            |          }|r<|\  }}t          j                    |z
  | j         j        k     r|}n| j         j        |= g }ng }|sdd|idgS i }|D ]b}|                    d          p|                    d          pd}	|                    d          pd}
|	r|
r|	|
f||	                                <   cg }d}| j                            |          D ]}|                                }||k    r8|||         	                                }|r|
                    dd|id           |                    d	          }|                    |                                          }|r9|\  }}
|
                    d
dt          j        dd| |
d          id           n|
                    ddd| id           |                                }|t          |          k     r8||d         	                                }|r|
                    dd|id           |s|
                    dd|id           |S )zNParse @nickname patterns and build mixed TIMTextElem + TIMCustomElem msg_body.r  rZ   r  rx  r  r   r  r   rW   r  r   r  @)r  rZ   r  N)r;  r:  r   r   MEMBER_CACHE_TTL_SrM  _AT_USER_RErL  r  r   r   r  r   dumpsr  r   )r`  rZ   rB  r,  r  member_listr7  nickname_to_uidrQ  nickr~  rE  last_idxr   r  segrx  r   	real_nicktails                       rb   r  z+MessageSender._build_msg_body_with_mentions  s   ,00<< 	$OBy{{R$-"BBB%M/
;G 	P!.~NNOO 	< 	<A55$$@k(:(:@bD%%	""(bC < <15s

-%..t44 	# 	#EKKMMEx8E>*0022 _OOPVX[}$]$]^^^{{1~~H#''(8(899E 	f!&	3 /
9cf+g+g h h$! !     ]FTbX`TbTbKc d deeeyy{{HHc$ii		?((**D \]FTX> Z Z[[[ 	XOOPT~VVWWWrd   c                   K   | j         }dt                       }t          |||j        pd||          }|                     |||           d{V S )z(Send C2C message with arbitrary MsgBody.c2c_r   )r  rE  rA  rF  rB  N)r;  r;   r5   r6  _dispatch_encoded)r`  r  rE  rB  r:  r  r/  s          rb   r  zMessageSender.send_c2c_msg_body"  st      -''')! .B!
 
 
 ++GWfEEEEEEEEErd   c                   K   | j         }dt                       }t          |||j        pd||pd          }|                     |||           d{V S )z*Send group message with arbitrary MsgBody.grp_r   )rB  rE  rA  rF  
ref_msg_idN)r;  r;   r6   r6  r  )r`  rB  rE  r  r:  r  r/  s          rb   r  z!MessageSender.send_group_msg_body/  sz       -'''+! .B~2
 
 
 ++GWfEEEEEEEEErd   r/  r  r  c                  K   	 | j                             ||           d{V }d|                    dd          dS # t          j        $ r ddt
           d	d
cY S t          $ r}dt          |          d
cY d}~S d}~ww xY w)zBSend pre-encoded bytes via WS and return a normalised result dict.r,  NTrF  r   )r  r  FzRequest timeout after sr  )r  r  r   r   r-  r  r  r[   )r:  r/  r  r   r#  s        rb   r  zMessageSender._dispatch_encodedC  s      
	9$0AA'RXAYYYYYYYYH#Xr0J0JKKK# 	a 	a 	a$/_H\/_/_/_````` 	9 	9 	9$s3xx88888888	9s!   :? B	B'A>8B>B   r  Optional[bytes]r  r  c                    | t          |           dk    rd| S |dz  dz  }t          |           |k    r"t          |           dz  dz  }d| d|dd| d	S dS )
zMedia pre-validation: check file validity before sending/uploading.

        Returns:
            Error description (str) if validation fails, otherwise None.
        Nr   zEmpty file: i   zFile too large: z (z.1fzMB > zMB)r  )r  r  r  	max_bytessize_mbs        rb   r  zMessageSender.validate_mediaR  s     ZA!5!5,(,,,$&-	z??Y&&*oo,t3GThTT'TTTKTTTTtrd   ry   
max_lengthrj   rk   r  c                    |pt           } ||           |k    r| gS t                              | ||          }d |D             }|r|n| gS )aj  
        Split a long message into chunks with table-awareness.

        Delegates core splitting to ``MarkdownProcessor.chunk_markdown_text``
        and strips page indicators like ``(1/3)`` from the output.

        Falls back to ``BasePlatformAdapter.truncate_message`` for non-table
        content and for overall text that fits in a single chunk.
        rn   c                D    g | ]}t                               d |          S r4  )_INDICATOR_REsubr  s     rb   r   z2MessageSender.truncate_message.<locals>.<listcomp>~  s(    ;;;q-##B**;;;rd   )r   rY   r   )r  r  rj   _lenr   s        rb   r  zMessageSender.truncate_messagee  sq     }4==J&&9 #66Z 7 
 

 <;F;;;.vvgY.rd   c                8   |                      d          s| S d}d}|                     |          }|                     |          }|dk     s|dk     s||k    r| S | d|         }d|vr| S |t          |          z   }| ||                                         }|p| S )zFStrip scheduler cron header/footer wrapper for cleaner Yuanbao output.zCronjob Response: z
-------------

zI

To stop or manage this job, send me a new message (e.g. "stop reminder r   Nz

(job_id: )r   rB  rfindr   r   )r  dividerfooter_prefixdivider_pos
footer_posr  
body_startr!  s           rb   r  z MessageSender.strip_cron_wrapper  s     !!"677 	N'ell7++]]=11
??j1nn
k0I0IN+&&&N 3w<</
z*,-3355wrd   c                <   K   | j                                          dS )zCRelease chat locks (no-op for now; placeholder for future cleanup).N)r  r   ri  s    rb   ry  zMessageSender.close  s!           rd   r  )r[  r[   rs  r  r\   r   rH  r[   r\   r   r  
rH  r[   r  r[   r  rN  rB  r[   r\   r  r  )rH  r[   r  r[   r  rN  r  rN  r  r   r\   r  r   rH  r[   r  r[   r  r  r\   r  )
rH  r[   rE  r<  r  rN  rB  r[   r\   r  )Nr   r   )rH  r[   rZ   r[   r  rN  r  ri   rB  r[   r\   r  r4  )r  r[   rZ   r[   rB  r[   r\   r  )rB  r[   rZ   r[   r  rN  r\   r  )rZ   r[   rB  r[   r\   r<  )r  r[   rE  r<  rB  r[   r\   r  )rB  r[   rE  r<  r  rN  r\   r  )r:  r*  r/  r  r  r[   r\   r  r  r  r  r  r[   r  ri   r\   rN  r   )r  r[   r  ri   rj   rk   r\   r  )r  r[   r\   r[   r2  )r   r   r   r   r0  r}  r5  r~  rp  r  r  r  r  r  r  rx  r  r  r   compile	MULTILINEr  r  r  r  r   r  r  r  r  ry  r   rd   rb   r  r    s          '0i0b0b0b&c&cJcccc(,,,,,
 
 
 
&- - - -) ) ) ). #'%( %( %( %( %(V #'!%
 
 
 
 
4 9=	)
 )
 )
 )
 )
^ #'U U U U U2 #'*V *V *V *V *V\Y Y Y Y Y #'	N N N N N "*A2<PPK6 6 6 6pF F F F F" #'	F F F F F( 
9 
9 
9 \
9 GI    \$  15/ / / / \/<    \,! ! ! ! ! !rd   r  c                      e Zd ZU dZej        Zded<   d1dZd2dZd2dZ		 	 d3d4dZ
d5dZ	 d6d7dZd2dZd8d9d"Zd2d#Zd2d$Zd:d&Zed;d(            Ze	 d<d=d/            Zd>d0ZdS )?OutboundManageru  Outbound coordinator that orchestrates sending, heartbeat and slow-response.

    Composes:
      - MessageSender   — core text/media sending
      - HeartbeatManager — reply heartbeat (RUNNING / FINISH) lifecycle
      - SlowResponseNotifier — delayed 'please wait' notifications

    YuanbaoAdapter holds a single ``_outbound: OutboundManager`` and delegates
    all outbound operations through it.
    r|  r~  r:  r*  r\   r   c                    || _         t          |          | _        t          |          | _        t          || j                  | _        | j        | j        _        | j	        | j        _
        d S r   )r;  r  rC  rT  rZ  rn  slow_notifier_handle_send_startr  _handle_send_finishr  rG  s     rb   rp  zOutboundManager.__init__  s_    %27%;%;+;G+D+D3GQUQ\3]3] &*%<"&*&>###rd   rH  r[   c                :    | j                             |           dS )zFCalled by MessageSender before sending: cancel slow-response notifier.Nr  rr  r`  rH  s     rb   r  z"OutboundManager._handle_send_start      !!'*****rd   c                V   K   | j                             |t                     d{V  dS )z=Called by MessageSender after sending: send FINISH heartbeat.N)rZ  r[  r+   r  s     rb   r  z#OutboundManager._handle_send_finish  s7      n00:MNNNNNNNNNNNrd   Nr   r  r  rN  rB  r  c                N   K   | j                             ||||           d{V S )z%Send text message with auto-chunking.r  N)rC  r  )r`  rH  r  r  rB  s        rb   r  zOutboundManager.send_text  s:      
 [**7GXR\*]]]]]]]]]rd   r  r  r   c                :   K    | j         j        ||fi | d{V S )r  N)rC  r  )r`  rH  r  r  s       rb   r  zOutboundManager.send_media  s9       ,T[+G\LLVLLLLLLLLLrd   r  r  r  r  c                J   K   | j                             |||           d{V S )z.Send text + media (used by send_message tool).N)rC  r  )r`  rH  r  r  s       rb   r  zOutboundManager.send_direct  s4      
 [,,Wg{KKKKKKKKKrd   c                J   K   | j                             |           d{V  dS )z Start reply heartbeat (RUNNING).N)rZ  r  r  s     rb   start_typingzOutboundManager.start_typing  s4      n""7+++++++++++rd   Frf  r]   c                N   K   | j                             ||           d{V  dS )zStop reply heartbeat.rf  N)rZ  rh  )r`  rH  rf  s      rb   stop_typingzOutboundManager.stop_typing  s9      n!!'{!CCCCCCCCCCCrd   c                J   K   | j                             |           d{V  dS )zStart slow-response notifier.N)r  r  r  s     rb   start_slow_notifierz#OutboundManager.start_slow_notifier  s5       &&w///////////rd   c                :    | j                             |           dS )zCancel slow-response notifier.Nr  r  s     rb   r  z$OutboundManager.cancel_slow_notifier  r  rd   r   c                6    | j                             |          S )z@Proxy to MessageSender.get_chat_lock for backward compatibility.)rC  r  r  s     rb   r  zOutboundManager.get_chat_lock  s    {((111rd   collections.OrderedDictc                    | j         j        S )z>Proxy to MessageSender._chat_locks for backward compatibility.)rC  r  ri  s    rb   r  zOutboundManager._chat_locks  s     {&&rd   r  r  r  r  r  ri   c                :    t                               | ||          S )z&Proxy to MessageSender.validate_media.)r  r  )r  r  r  s      rb   r  zOutboundManager.validate_media  s    
 ++J+NNNrd   c                   K   | j                                          d{V  | j                                         d{V  | j                                         d{V  dS )zShut down all sub-managers.N)rC  ry  rZ  r  ri  s    rb   ry  zOutboundManager.close  s      k!!!!!!!!!n""$$$$$$$$$ &&(((((((((((rd   r  rk  r  r  )rH  r[   r  r[   r  r   r\   r  r   r  )Frl  r  )r\   r  r  r  r2  )r   r   r   r   r  r~  r5  rp  r  r  r  r  r  r  r  r  r  r  r  r  r   r  ry  r   rd   rb   r  r    s        	 	 )6(HHHHH? ? ? ?+ + + +O O O O EI^ ^ ^ ^ ^M M M M 9=L L L L L, , , ,D D D D D0 0 0 0+ + + +2 2 2 2 ' ' ' X' GIO O O O \O) ) ) ) ) )rd   r  c                      e Zd ZU dZej        ZdZded<   dZ	dZ
ded<   dZd	ed
<   dZded<   edSd            ZedTd            ZdU fdZdVdZedWd            ZdXd Zd!d"dYd$ZdZd%Z	 	 	 d[d\d0Zd]d2Zd^d_d4Zd`d5Zda fd7Zdbd8Z	 dcddd=Zd>ZdedfdAZ	 	 	 dgdhdDZ 	 	 	 dgdidFZ!	 	 	 dgdjdIZ"	 	 	 dgdkdMZ#	 	 	 	 dldmdOZ$dndQZ%dndRZ& xZ'S )orm  zCYuanbao AI Bot adapter backed by a persistent WebSocket connection.ry   ri   r  TrT   r  i  r|  REPLY_REF_MAX_ENTRIESNz$ClassVar[Optional['YuanbaoAdapter']]_active_instancer\   Optional['YuanbaoAdapter']c                    | j         S )z7Return the currently connected YuanbaoAdapter, or None.r  r   s    rb   
get_activezYuanbaoAdapter.get_active  s     ##rd   r:  r   c                    || _         dS )z0Register (or clear) the active adapter instance.Nr  )r~   r:  s     rb   rn  zYuanbaoAdapter.set_active  s      'rd   r  r   r  r   c                &   t                                          |t          j                   |j        pi }|                    d          pd                                | _        |                    d          pd                                | _        |                    d          pd | _	        |                    d          pt                                          | _        |                    d          pt                              d          | _        |                    d          pd                                | _        	 t!          |                    d	t"                              }n# t$          t&          f$ r
 t"          }Y nw xY wt)          t*          t-          t.          |                    | _        t3          |           | _        t7          |           | _        t;                      | _        t;                      | _        i | _         d
| _!        tE          d          | _#        i | _$        i | _%        i | _&        i | _'        |                    d          ptQ          j)        dd                                          *                                }|                    d          ptQ          j)        dd          }d |+                    d          D             }|                    d          ptQ          j)        dd                                          *                                }|                    d          ptQ          j)        dd          }	d |	+                    d          D             }
tY          ||||
          | _-        t]          |           | _/        t`          1                                | _2        tQ          j)        d          p|j3        r|j3        j4        nd}tk          |          o|6                    d           | _7        d S )Napp_idr   r   r   ws_urlr   r   r   r  rP   i,  )ttl_secondsr<  YUANBAO_DM_POLICYrV  r=  YUANBAO_DM_ALLOW_FROMc                ^    g | ]*}|                                 |                                 +S r   r   r   xs     rb   r   z+YuanbaoAdapter.__init__.<locals>.<listcomp>\  s2    #b#b#b!XYX_X_XaXa#bAGGII#b#b#brd   ,r>  YUANBAO_GROUP_POLICYr?  YUANBAO_GROUP_ALLOW_FROMc                ^    g | ]*}|                                 |                                 +S r   r   r  s     rb   r   z+YuanbaoAdapter.__init__.<locals>.<listcomp>g  s2    &h&h&hQ^_^e^e^g^g&hqwwyy&h&h&hrd   )r<  r=  r>  r?  rg  r  )8superrp  r   YUANBAOr  r   r   r  r  r6  DEFAULT_WS_GATEWAY_URLrb  DEFAULT_API_DOMAINr  r  ra  ri   _DEFAULT_RESOLVE_CONCURRENCYr  r  max_MIN_RESOLVE_CONCURRENCYr  _MAX_RESOLVE_CONCURRENCYr  r9  r  r  rY  r  r&  r$  r:  r  r   r  r   r  r  r  rK  rL  rM  r   r;  rb  r(  _group_queryr2  r7  r  home_channelrH  r]   r   rk  )r`  r  r  _extra_raw_concurrencyr<  _dm_allow_from_rawr=  r>  _group_allow_from_rawr?  _existing_homerh  s               rb   rp  zYuanbaoAdapter.__init__  s   !1222 #$jj228b??AA!'L!9!9!?R F F H H&,jj&:&:&Bd#ZZ11K5KRRTT!'L!9!9!O=O W WX[ \ \ &

; 7 7 =2DDFF	<"6::.IKg#h#hii:& 	< 	< 	<;	<.1$(*:;;/
 /
& /@.E.E*9$*?*? 25 58EE
 =?). *c::: 8: 46 57" 35 JJ{## 9y,i88
%''%%'' 	 JJ'' 6y0"55 	 $c#b7I7O7OPS7T7T#b#b#b JJ~&& <y/;;
%''%%'' 	 JJ)** 9y3R88 	 'i&h:O:U:UVY:Z:Z&h&h&h*'%-	
 
 
 .d33 3I2N2N2P2P #9:: 
+1+>FF''B 	 )-^(<(<(h^E^E^_gEhEhAhs   (E. .F	F	r(  asyncio.Taskc                x    | j                             |           |                    | j         j                   |S )z@Register a fire-and-forget task so it won't be GC'd prematurely.)r$  r%  r&  r'  rj  s     rb   r  zYuanbaoAdapter._track_task  s8    ""4(((t5=>>>rd   r]   c                    dS )zCYuanbao gates DM/group access at intake via dm_policy/group_policy.Tr   ri  s    rb   enforces_own_access_policyz)YuanbaoAdapter.enforces_own_access_policy  s	     trd   r\  r9  c                    | j         }t          |j        pd                                          }|sdS |j        dk    rW|                    |          rdS |j        dk    r3ddlm}  |            	                    t          j        j        |          S dS |j        dk    rmt          |j        pd                                          }|sdS |j        d	k    r|                    |          S |j        d
k    r|                                S dS dS )zTrue when the sender may persist ``YUANBAO_HOME_CHANNEL``.

        Intake-only pairing forwards are excluded until the sender is on the
        strict allowlist, has explicit open-world opt-in, or is approved in the
        pairing store.
        r   Fr  TrV  r   )PairingStorer  rR  rS  )rb  r[   rA  r   rI  rT  r<  gateway.pairingr+  is_approvedr   r  r  rB  r>  r[  rN  )r`  r\  rc  rC  r+  rB  s         rb   rl  z)YuanbaoAdapter._sender_may_designate_home  s/     $2S%+,,2244 	5=D  ##F++ t9,,888888#|~~11(2B2H&QQQ5=G##S^1r2288::J u"k11..z:::"f,,//1115urd   F)is_reconnectr.  c               D   K   | j                                          d{V S )zhConnect to Yuanbao WS gateway and authenticate.

        Delegates to ConnectionManager.open().
        N)r  rS  )r`  r.  s     rb   rd  zYuanbaoAdapter.connect  s/      
 %**,,,,,,,,,rd   c                V  K   t           j        | u rt                               d           d| _        |                                  |                                  | j                                         d{V  | j                                         d{V  t          | j
                  D ]*}|                                s|                                 +| j
                                         | j                                         t                              d| j                   dS )z;Cancel background tasks and close the WebSocket connection.NFz[%s] Disconnected)rm  r  rn  r  r  ro  r  ry  rY  r<  r&  ru  rr  r   r   r  r  r[  rj  s     rb   
disconnectzYuanbaoAdapter.disconnect  s#     *d22%%d+++!!!##%%% $$&&&&&&&&&n""$$$$$$$$$ ,-- 	 	D99;; !!###  """'33333rd   r   rH  r[   r  r  rN  metadataOptional[Dict[str, Any]]rB  r   c                N   K   | j                             ||||           d{V S )zCSend text message with auto-chunking. Delegates to OutboundManager.r  N)rY  r  )r`  rH  r  r  r2  rB  s         rb   r  zYuanbaoAdapter.send  s:       ^--gwU_-`````````rd   r  c                D   K   |                     d          r|ddS |ddS )u  Return basic chat metadata derived from the chat_id prefix.

        chat_id conventions:
          "group:<group_code>"  → group chat
          "direct:<account>"   → C2C / direct message (default)

        TODO (T06): fetch real chat name/member-count from Yuanbao API.
        r  r  )r[  r`  r  r  r  s     rb   get_chat_infozYuanbaoAdapter.get_chat_info  s:       h'' 	6#W555...rd   r>  c                n   K   	 | j                             |           d{V  dS # t          $ r Y dS w xY w)zGSend "typing" status heartbeat (RUNNING). Delegates to OutboundManager.N)rY  r  r  )r`  rH  r2  s      rb   send_typingzYuanbaoAdapter.send_typing  s[      	.--g66666666666 	 	 	DD	s    & 
44c                r   K   	 | j                             |d           d{V  dS # t          $ r Y dS w xY w)zStop the RUNNING heartbeat loop without sending FINISH immediately.

        FINISH is sent by send() after actual message delivery to ensure correct ordering:
        RUNNING... -> message arrives -> FINISH.
        Fr  N)rY  r  r  r  s     rb   r  zYuanbaoAdapter.stop_typing  s`      	.,,W%,HHHHHHHHHHH 	 	 	DD	s   "( 
66r  c                  K   |j         j        }| j                            |           d{V  	 t	                                          ||           d{V  | j                            |           |j        }|rV| j        	                    |          |k    r:| j        
                    |d           | j        
                    |d           dS dS dS # | j                            |           |j        }|rU| j        	                    |          |k    r8| j        
                    |d           | j        
                    |d           w w w xY w)z9Wrap base class processing with a slow-response notifier.N)r*  rH  rY  r  r  _process_message_backgroundr  r*  r  r   r   r  )r`  r  r  rH  rF  rh  s        rb   r;  z*YuanbaoAdapter._process_message_background  s     ,&n00999999999	B''55e[IIIIIIIIIN//888 %F B$266{CCvMM(,,[$???*..{DAAAAAB BMM N//888 %F B$266{CCvMM(,,[$???*..{DAAAABMs   (C A;Ec                F   K   | j                             |           d{V S )z2Query group info (delegates to GroupQueryService).N)r  r2  rZ  s     rb   r>  zYuanbaoAdapter.query_group_info  s/      &;;JGGGGGGGGGrd   r   r   r3  r4  c                L   K   | j                             |||           d{V S )z9Query group member list (delegates to GroupQueryService).r6  N)r  r;  )r`  rB  r3  r4  s       rb   get_group_member_listz$YuanbaoAdapter.get_group_member_list  s:       &@@TZbg@hhhhhhhhhrd   i'  r  rZ   c                   K   | j                             |          st          dd          S t          |          | j        k    r|d| j                 dz   }d| }|                     |||           d{V S )a  
        Actively send C2C private chat message.

        Args:
            user_id: Target user ID
            text: Message text (limit 10000 characters)
            group_code: Source group code (for group-originated DM context)

        Returns:
            SendResult
        FzDM access denied for this userr  Nz
...(truncated)r  r  )rb  rT  r   r   DM_MAX_CHARSr  )r`  r  rZ   rB  rH  s        rb   send_dmzYuanbaoAdapter.send_dm%  s       "0099 	Ue3STTTTt99t(((***+.@@D%G%%YYwYDDDDDDDDDrd   r  r  c                B   K    | j         j        |df|||d| d{V S )zKSend image message (URL). Delegates to OutboundManager via ImageUrlHandler.r  )r  r  r  NrY  r  )r`  rH  r  r  r  r2  r  s          rb   
send_imagezYuanbaoAdapter.send_image<  s_       /T^.[
w)
 
 
 
 
 
 
 
 
 
 	
rd   r  c                B   K    | j         j        |df|||d| d{V S )zISend local image file. Delegates to OutboundManager via ImageFileHandler.r  )r  r  r  NrC  )r`  rH  r  r  r  r2  r  s          rb   r  zYuanbaoAdapter.send_image_fileL  s_       /T^.\
w:
 
 
 
 
 
 
 
 
 
 	
rd   r  r  c                B   K    | j         j        |df|||d| d{V S )zISend file message (URL). Delegates to OutboundManager via FileUrlHandler.r  )r  r  r  NrC  )r`  rH  r  r  r  r2  r  s          rb   	send_filezYuanbaoAdapter.send_file\  s_       /T^.Z
8
 
 
 
 
 
 
 
 
 
 	
rd   r$  r%  Optional[int]c                B   K    | j         j        |df|||d| d{V S )zDSend sticker/emoji. Delegates to OutboundManager via StickerHandler.r  )r  r$  r%  NrC  )r`  rH  r$  r%  r  r  s         rb   send_stickerzYuanbaoAdapter.send_stickerl  s`       /T^.Y
%*
 
 	
 
 
 
 
 
 
 
 	
rd   r  c                D   K    | j         j        |df||||d| d{V S )zMSend local file (document). Delegates to OutboundManager via DocumentHandler.rc  )r  r  r  r  NrC  )r`  rH  r  r  r  r  r2  r  s           rb   r  zYuanbaoAdapter.send_document|  sb       /T^.Z
w(
 
 	
 
 
 
 
 
 
 
 	
rd   r  c                v   K   t                               | j        | j        | j        | j                   d{V S )z<Get the current valid sign token (using module-level cache).rV  N)r   r.  r  r  r  ra  ri  s    rb   r  z YuanbaoAdapter._get_cached_token  sU       **M4+T-=o + 
 
 
 
 
 
 
 
 	
rd   c                R    | j         }|j        | j        |j        |j        | j        dS )z3Return a snapshot of the current connection status.)	connectedr   rK  rM  r  )r  rQ  r6  rK  rM  rb  )r`  r  s     rb   
get_statuszYuanbaoAdapter.get_status  s4    *l/"&"9l
 
 	
rd   r\   r  )r:  r  r\   r   )r  r   r  r   r\   r   )r(  r&  r\   r&  r^  )r\  r9  r\   r]   )r.  r]   r\   r]   r2  )NNr   )rH  r[   r  r[   r  rN  r2  r3  rB  r[   r\   r   )rH  r[   r\   r  r   )rH  r[   r2  r>  r\   r   rk  )r  r[   r\   r   rP  rQ  rR  r4  )r  r[   rZ   r[   rB  r[   r\   r   )NNN)rH  r[   r  r[   r  rN  r  rN  r2  r>  r  r   r\   r   )rH  r[   r  r[   r  rN  r  rN  r2  r>  r  r   r\   r   )rH  r[   r  r[   r  rN  r  rN  r2  r>  r  r   r\   r   )rH  r[   r$  rN  r%  rH  r  rN  r  r   r\   r   )NNNN)rH  r[   r  r[   r  rN  r  rN  r  rN  r2  r>  r  r   r\   r   )r\   r  )(r   r   r   r   r   r  PLATFORMr  r5  splits_long_messagesr  r  r  r   r  rn  rp  r  r  r)  rl  rd  r1  r  r6  r8  r  r;  r>  r>  r@  rA  rD  r  rG  rJ  r  r  rO  __classcell__)rh  s   @rb   rm  rm     s5        MMHN+..... >BAAAA$ $ $ [$ ' ' ' ['ii ii ii ii ii ii^       X   < 5: - - - - - -4 4 4 46 #'-1	a 	a 	a 	a 	a/ / / /    	 	 	 	B B B B B B4H H H H
 >Ai i i i i LE E E E E6 "&"&#'
 
 
 
 
( "&"&#'
 
 
 
 
( #'"&#'
 
 
 
 
& '+$("&
 
 
 
 
( #'!%"&#'
 
 
 
 
$
 
 
 
	
 	
 	
 	
 	
 	
 	
 	
rd   rm  r\   r  c                 4    t                                           S )z,Delegate to ``YuanbaoAdapter.get_active()``.)rm  r  r   rd   rb   get_active_adapterrU    s    $$&&&rd   r:  r*  rH  r[   r  r  r  r  c                J   K   | j                             |||           d{V S )z,Delegate to ``OutboundManager.send_direct``.N)rY  r  )r:  rH  r  r  s       rb   send_yuanbao_directrW    s5       "..wMMMMMMMMMrd   rP  r   )
r:  r*  rH  r[   r  r[   r  r  r\   r  )r   
__future__r   r   r  r  r  r)  r   r   r   loggingrK  r   r	  r   urllib.parser  r  r   r   r   enumr   pathlibr   abcr	   r
   typingr   r   r   r   r   r   r   r   sysr  rc  websockets.exceptionsr^  ImportErrorgateway.configr   r   gateway.platforms.baser   r   r   r   r   r   r   gateway.platformsr   r_   gateway.platforms.helpersr   gateway.platforms.yuanbao_mediar   r  r   r    r!   r"   r#   r$   r.  r%   r&   r'   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r2   r3   r4   r5   r6   r7   r8   r9   r:   r;   gateway.sessionr<   	getLoggerr   r  
hermes_clir=   _HERMES_VERSIONr  r  r[   r  r  r  r  r  r  re  r  r  r  r  r  r  AUTH_FAILED_CODESAUTH_RETRYABLE_CODESrb  rc  REPLY_REF_TTL_Srw  ry  r  rK  r  r0  rN  r  r  r  r  r  r  rY   r   r6  r7  rX  r9  rZ  rl  r  r  r  r  r2  r8  r;  r`  re  rv  r  r  r  r  r'  r-  r1  r<  r  rz  r  r  r2  r9  r  r  r  r  r  r  r(  rT  rn  r  r  rm  rU  rW  r   rd   rb   <module>rn     s   " # " " " " "                				 				        2 2 2 2 2 2 2 2 2 2             # # # # # # # # Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q Q 



         JJJ 4 3 3 3 3 3 3 3                  2 1 1 1 1 1 9 9 9 9 9 9                                                                  2 . - - - - -		8	$	$
9999999   OOO s-.. L  L 6 !        @??     '&& )))  !       Y  I   RZ ABB  $)$>$>$>??  
-.. $&  /1 ,  !   K
 K
 K
 K
 K
 K
 K
 K
Zp) p) p) p) p) p) p) p)f 5 4 4 4 4 4 4 4
>) >) >) >) >) >) >) >)BA A A A A A A A6] ] ] ] ] ] ] ]|] ] ] ] ]( ] ] ]@    /   "	 	 	 	 	' 	 	 	Nd Nd Nd Nd Nd- Nd Nd Ndb    *   &    -   "D" D" D" D" D" D" D" D"N    -   2. . . . .- . . .bt t t t t0 t t tl	    "3   2^ ^ ^ ^ ^. ^ ^ ^B    -   $~ ~ ~ ~ ~. ~ ~ ~B    !2   >               $ $ $ $ $$5 $ $ $NG G G G G. G G GTk k k k k&7 k k k\D	 D	 D	 D	 D	. D	 D	 D	N/ / / / /. / / /d_9 _9 _9 _9 _9* _9 _9 _9D% % % % % % % %N[
 [
 [
 [
 [
 [
 [
 [
z~= ~= ~= ~= ~=s ~= ~= ~=B
 
 
 
 
& 
 
 
6
 
 
 
 
' 
 
 
4
 
 
 
 
% 
 
 
4
 
 
 
 
& 
 
 
.3 3 3 3 3% 3 3 3>U
 U
 U
 U
 U
 U
 U
 U
px+ x+ x+ x+ x+ x+ x+ x+v. . . . . . . .bj! j! j! j! j! j! j! j!Z]) ]) ]) ]) ]) ]) ]) ])@^
 ^
 ^
 ^
 ^
( ^
 ^
 ^
L' ' ' ' 59	N N N N N N Ns$    
B 	BBD" "D,+D,