
    ij                       d Z ddlmZ ddlZddlmZ ddlmZmZm	Z	  ej
        e          Zd Zddd	d
dZdZdcdZ	 	 	 dddedZdfdgdZ	 	 	 dhdid"Z eh d#          Z	 	 djdkd(Zdd)lmZmZ d* Zd+ Zd, Zd- Zd. Zd/ Zd0Z  ej!        d1e d1d2d3dd4d5d6idgd7d8eed9d:;            ej!        d<e d<d=d3d4d5d6d4g d>d?d@d4dAd6dBdCd6dDddgd7d8eed9dE;            ej!        dFe dFdGd3d4dHd6d4dId6d4dJd6d4dKd6dLdMd3d4dNd6dBdOd6dPdQgd7dRdSg d7d8eed9dT;            ej!        dUe dUdVd3d4dWd6dXdYd6dZg d7d8eed9d[;            ej!        d\e d\d]d3d4d^d6d4d_d6d4d`d6dag d7d8eed9db;           dS )lu  
yuanbao_tools.py - 元宝平台工具集

提供以下工具函数，供 hermes-agent 的 "hermes-yuanbao" toolset 使用：
  - get_group_info        : 查询群基本信息（群名、群主、成员数）
  - query_group_members   : 查询群成员（按名搜索、列举 bot、列举全部）
  - search_sticker        : 按关键词搜索内置贴纸（返回候选列表，含 sticker_id/name/description）
  - send_sticker          : 向当前会话或指定 chat_id 发送贴纸（TIMFaceElem）
  - send_dm               : 发送私聊消息（按昵称查找用户并发送）

对齐 chatbot-web/yuanbao-openclaw-plugin 的 sticker-search/sticker-send 行为：
LLM 应先用 search_sticker 找到合适的 sticker_id（或直接传中文 name），再用 send_sticker
发送。不要在文本中夹杂裸的 Unicode emoji 当作贴纸。

The active adapter singleton lives in ``gateway.platforms.yuanbao`` and is
accessed via ``get_active_adapter()``.
    )annotationsN)Path)ListOptionalTuplec                 F    	 ddl m}   |             S # t          $ r Y dS w xY w)zOLazy import to avoid ImportError when gateway.platforms.yuanbao is unavailable.r   get_active_adapterN)gateway.platforms.yuanbaor
   ImportErrorr	   s    8/home/ubuntu/.hermes/hermes-agent/tools/yuanbao_tools.py_get_active_adapterr      sJ    @@@@@@!!###   tts    
  unknownuser
yuanbao_aibot)r            z\To @mention a user, you MUST use the format: space + @ + nickname + space (e.g. " @Alice ").
group_codestrreturndictc           	       K   | sdddS t                      }|dddS 	 |                    |            d{V }|dddS d| |                    dd	          |                    d
d          |                    dd	          |                    dd	          dddS # t          $ r6}t                              d           dt          |          dcY d}~S d}~ww xY w)u9   查询群基本信息（群名、群主、成员数）。Fgroup_code is requiredsuccesserrorN Yuanbao adapter is not connectedzquery_group_info returned NoneT
group_name member_countr   owner_idowner_nicknameuser_idnicknameu+   The group is called "派 (Pai)" in the app.)r   r   r    r"   ownernotez$[yuanbao_tools] get_group_info error)r   query_group_infoget	Exceptionlogger	exceptionr   )r   adaptergiexcs       r   get_group_infor2   5   s>      E +CDDD!##G +MNNN5++J77777777:$/OPPP$&&r22FF>15566*b11FF#3R88  B

 

 
	
  5 5 5?@@@ 3s88444444445s$   !B AB 
C(+CCClist_allr!   Factionnamementionboolc                  K   | sdddS t                      }|dddS 	 |                    |            d{V }|dddS d |                    dg           D             }|sdd	dS |r	d
t          ini }|dk    r,d |D             }|sdddS ddt	          |           d|d|S |dk    ry|r^|                                                                fd|D             }	|	rddt	          |	           d| d|	d|S dd| d|d|S ddt	          |           d|d|S ddt	          |           d|d|S # t          $ r6}
t          	                    d           dt          |
          dcY d}
~
S d}
~
ww xY w)u   
    统一的群成员查询工具（对齐 TS query_session_members）。

    action:
      - find      : 按昵称模糊搜索
      - list_bots : 列出 bot 和元宝 AI
      - list_all  : 列出全部成员
    Fr   r   Nr   #get_group_member_list returned Nonec                   g | ]}|                     d d          |                     d|                     dd                    t                               |                     d|                     dd                    d          dS )	r&   r!   r'   	nick_name	user_typeroler   r   )r&   r'   r=   )r+   _USER_TYPE_LABEL.0ms     r   
<listcomp>z'query_group_members.<locals>.<listcomp>l   s     	
 	
 	
  55B//EE*aeeK.D.DEE(,,EE+quuVQ'7'788)  	
 	
 	
    memberszNo members found in this group.mention_hint	list_botsc                &    g | ]}|d          dv |S )r=   )r   r    r?   s     r   rB   z'query_group_members.<locals>.<listcomp>}   s'    QQQ!ai;P.P.PA.P.P.PrC   zNo bots found in this group.TzFound z bot(s).)r   msgrD   findc                L    g | ] }|d                                           v |!S )r'   )lowerr@   rA   filts     r   rB   z'query_group_members.<locals>.<listcomp>   s3    SSSTQz]=P=P=R=R5R5R15R5R5RrC   z member(s) matching "z".zNo match for "z". All members listed below.z member(s).z)[yuanbao_tools] query_group_members error)r   get_group_member_listr+   MENTION_HINTlenstriprL   r,   r-   r.   r   )r   r4   r5   r6   r/   rawall_membershintbotsmatchedr1   rN   s              @r   query_group_membersrX   R   s       E +CDDD!##G +MNNNB511*========;$/TUUU	
 	
 WWY++	
 	
 	
  	R$/PQQQ18@--b[  QQ{QQQD S#(3QRRR3D		333  	  V zz||))++SSSSkSSS #'SGSS4SSS#*  	   %NDNNN*  	   =K 0 0===&  	  9C,,999"
 
 	
 	
  5 5 5DEEE 3s88444444445sH   !E &E *%E E )AE E E (E 
F+E<6F<F
   querylimitintc           	       K   ddl m} 	 t          dt          d|rt	          |          nd                    }n# t
          t          f$ r d}Y nw xY w	  || pd|          }nC# t          $ r6}t          	                    d           d	t          |          d
cY d}~S d}~ww xY wd| pdt          |          d |D             dS )u   
    在内置贴纸表中按关键词模糊搜索，返回 Top-N 候选。

    返回每条候选的 sticker_id / name / description / package_id，
    供 LLM 选择后传给 send_sticker。空 query 时返回前 N 条。
    r   )search_stickersr   2   rY   r!   )r[   z$[yuanbao_tools] search_sticker errorFr   NTc           	         g | ]Y}|                     d d          |                     dd          |                     dd          |                     dd          dZS )
sticker_idr!   r5   description
package_id)ra   r5   rb   rc   r+   )r@   ss     r   rB   z"search_sticker.<locals>.<listcomp>   su     
 
 
   eeL"55fb)) uu]B77eeL"55	 
 
 
rC   )r   rZ   countresults)!gateway.platforms.yuanbao_stickerr^   maxminr\   	TypeError
ValueErrorr,   r-   r.   r   rQ   )rZ   r[   r^   
safe_limitmatchesr1   s         r   search_stickerro      s+      BAAAAAC%$?CJJJR@@AA

z"   


5!/%+2Z@@@ 5 5 5?@@@ 3s88444444445
 "W
 
 
 
 
	  s-   /: AAA$ $
B$.+BB$B$stickerchat_idreply_toc                  K   ddl m} ddlm}m}m} |pd                                p |dd          }|sdddS t                      }|dd
dS | pd                                }	d	}
|	s |            }
n,|	                                r ||	          }
|
 ||	          }
|
	dd|	ddS 	 |	                    ||

                    dd          |pd	           d	{V }nC# t          $ r6}t                              d           dt          |          dcY d	}~S d	}~ww xY wt          |dd          rBd||

                    dd          |

                    dd          dt          |dd	          ddS dt          |dd          dS )u  
    向 chat_id（缺省取当前会话）发送一张内置贴纸（TIMFaceElem）。

    Args:
        sticker:   贴纸名称（如 "六六六"）或 sticker_id（如 "278"）。为空时随机发送一张。
        chat_id:   目标会话；缺省时使用当前会话上下文（HERMES_SESSION_CHAT_ID）。
                   格式：``direct:{account_id}`` / ``group:{group_code}`` / 或裸 account_id。
        reply_to:  群聊场景的引用消息 ID（可选）。

    Returns: ``{"success": bool, ...}``
    r   get_session_env)get_sticker_by_idget_sticker_by_nameget_random_stickerr!   HERMES_SESSION_CHAT_IDFz8chat_id is required (no active yuanbao session detected)r   Nr   zSticker not found: z:. Use search_sticker first to discover available stickers.r5   )rq   sticker_namerr   z"[yuanbao_tools] send_sticker errorr   Tra   )ra   r5   
message_idz~Sticker delivered to the chat. If you have additional text to say, reply now; otherwise end your turn without generating text.)r   rq   rp   r{   r)   r   zsend_sticker failed)gateway.session_contextru   rh   rv   rw   rx   rR   r   isdigitsend_stickerr+   r,   r-   r.   r   getattr)rp   rq   rr   ru   rv   rw   rx   targetr/   rS   sticker_objresultr1   s                r   r~   r~      s       877777          m""$$U8PRT(U(UF 
O
 
 	

 "##G +MNNN=b


!
!C"&K 3((**;;== 	1++C00K--c22KQ3 Q Q Q
 
 	
5++$44% , 
 
 
 
 
 
 
 

  5 5 5=>>> 3s88444444445 vy%(( 

)oolB??#33  "&,== U	
 	
 		
 *?@@  s   .4C# #
D#-+DD#D#>   .bmp.gif.jpg.png.jpeg.webpmessager&   media_files Optional[List[Tuple[str, bool]]]c                  K   |s|sdddS t                      }|dddS |r|                                nd}|                                }|sH| sdddS |sdddS 	 |                    |            d{V }|dd	dS |                    d
g           }	|                                                                fd|	D             }
|
sdd| d|  ddS t          |
          dk    rd |
D             }dd| d|dS |
d                             dd          }|
d                             d|
d                             d|                    }nC# t          $ r6}t                              d           dt          |          dcY d}~S d}~ww xY w|sdddS d| }d}g }	 |rU|                                rA|
                    |||            d{V }|j        s|                    |j        pd           |pg D ]\  }}t          |          j                                        }|t           v r|                    |||            d{V }n|                    |||            d{V }|j        s|                    |j        pd           |dddS |r!||j        sdd                    |          dS d |||j        d!| d"d#}|r'|d$xx         d%d                    |           d&z  cc<   |S # t          $ r6}t                              d'           dt          |          dcY d}~S d}~ww xY w)(a  
    Send a DM (private chat message) to a group member, with optional media.

    Workflow:
      1. If user_id is provided, send directly.
      2. Otherwise, search the group member list by name to resolve user_id.
      3. Send text via adapter.send_dm(), then iterate media_files by extension.

    Args:
        group_code: The group where the target user belongs.
        name: Target user's nickname (partial match, case-insensitive).
        message: The message text to send.
        user_id: (Optional) If already known, skip the member lookup.
        media_files: (Optional) List of (file_path, is_voice) tuples to send
                     after the text message.  Images are sent via
                     send_image_file; everything else via send_document.
    Fz"message or media_files is requiredr   Nr   r!   z3group_code is required when user_id is not providedz-name is required when user_id is not providedr9   rD   c                    g | ]D}|                     d           p|                     d          pd                                v B|ES )r'   r;   r!   )r+   rL   rM   s     r   rB   zsend_dm.<locals>.<listcomp>R  s\       AEE*--I{1C1CIrPPRRRR RRRrC   zNo member matching "z" found in group .r   c           
         g | ]C}|                     d d          |                     d|                     dd                    dDS )r&   r!   r'   r;   r%   rd   r?   s     r   rB   zsend_dm.<locals>.<listcomp>^  sa       
  $%55B#7#7$%EE*aeeK6L6L$M$M   rC   zMultiple members match "z". Please specify which one.)r   r   
candidatesr   r&   r'   r;   z+[yuanbao_tools] send_dm member lookup errorzCould not resolve user_idzdirect:r   ztext send failedzmedia send failedz%No deliverable text or media remainedz; TzDM sent to "z" successfully.)r   r&   r'   r{   r)   r)   z (partial failure: )z[yuanbao_tools] send_dm error)r   rR   rO   r+   rL   rQ   r,   r-   r.   r   send_dmr   appendr   r   suffix_IMAGE_EXTSsend_image_filesend_documentjoinr{   )r   r5   r   r&   r   r/   resolved_user_idresolved_nicknamerS   rD   rW   r   r1   rq   last_resulterrors
media_path	_is_voiceextr   rN   s                       @r   r   r   "  s     0  Q; Q +OPPP!##G +MNNN*19w}}r

  *9 	f$/deee 	`$/^___$	955jAAAAAAAAC{#(3XYYYggi,,G::<<%%''D   "  G
  $XDXX:XXX   7||a 
 %  
  %ZZZZ",    'qz~~i<< '
z71:>>+W[;\;\ ] ] 	9 	9 	9JKKK$s3xx88888888	9  H +FGGG +(**GKF"5 	Gw}} 	G '0@'V` a aaaaaaaK& Gk/E3EFFF &1%6B 	H 	H!J	z"")//11Ck!!$+$;$;GZ\f$;$g$ggggggg$+$9$9':Zd$9$e$eeeeeee& Hk/F3FGGG$/VWWW 	B{*+2E*$tyy/@/@AAA ')%0E#4EEE
 
  	I6NNNHDIIf4E4EHHHHNNN 5 5 58999 3s88444444445s]    !E AE (E AE 
F +FFF*C6L  !"L  ;L   
M 
+L;5M ;M )registrytool_resultc                 v    	 ddl m}   | dd          dk    rdS n# t          $ r Y nw xY wt                      duS )uN   Toolset availability check — True when running in a yuanbao gateway session.r   rt   HERMES_SESSION_PLATFORMr!   yuanbaoTN)r|   ru   r,   r   rt   s    r   _check_yuanbaor     sm    ;;;;;;?4b99YFF4 G     ,,s    
((c                t   K   t          t          |                     dd                     d {V           S )Nr   r!   r   )r   r2   r+   argskws     r   _handle_yb_query_group_infor     sX      ^88L"--           rC   c                  K   t          t          |                     dd          |                     dd          |                     dd          t          |                     dd                               d {V           S )	Nr   r!   r4   r3   r5   r6   Fr   r4   r5   r6   )r   rX   r+   r7   r   s     r   _handle_yb_query_group_membersr     s      088L"--xx*--XXfb!!TXXi//00	           rC   c                  K   |                      dd          }|sU	 ddlm}  |dd          }|                    d          r|                    dd          d         }n# t
          $ r Y nw xY w|                      d	          pg }g }|D ]}t          |t                    rM|                    |                     d
d          t          |                     dd                    f           dt          |t          t          f          rPt          |          dk    r=|                    t          |d                   t          |d                   f           |                      dd          }ddlm}	 |	                    |          \  }
}|
r|                    |
           t%          t'          ||                      dd          ||                      dd          |pd            d {V           S )Nr   r!   r   rt   ry   zgroup::r   r   pathis_voiceFr   r   )BasePlatformAdapterr5   r&   r   r5   r   r&   r   )r+   r|   ru   
startswithsplitr,   
isinstancer   r   r7   listtuplerQ   r   gateway.platforms.baser   extract_mediaextendr   r   )r   r   r   ru   rq   	raw_mediar   itemr   r   embedded_medias              r   _handle_yb_send_dmr     s5     ,++J 	??????%o&>CCG!!(++ 6$]]32215
 	 	 	D	 ''-2IK > >dD!! 	> 4 4d488JPU;V;V6W6WXYYYYtUm,, 	>TaDGd47mm<=== hhy"%%G::::::1??HHNG +>***W488FB+?+?B'''4	           s   AA   
A-,A-c           	        K   t          t          |                     dd          |                     dd                     d {V           S )NrZ   r!   r[   rY   rZ   r[   )r   ro   r+   r   s     r   _handle_yb_search_stickerr     si      ^hhw##hhw##           rC   c           
        K   t          t          |                     dd          |                     dd          |                     dd                     d {V           S )Nrp   r!   rq   rr   rp   rq   rr   )r   r~   r+   r   s     r   _handle_yb_send_stickerr     sz      \B''B''*b))           rC   zhermes-yuanbaoyb_query_group_infoul   Query basic info about a group (called '派/Pai' in the app), including group name, owner, and member count.objectstringz)The unique group identifier (group_code).)typerb   )r   
propertiesrequired)r5   rb   
parametersTu   👥)r5   toolsetschemahandlercheck_fnis_asyncemojiyb_query_group_membersu:  Query members of a group (called '派/Pai' in the app). Use this tool when you need to @mention someone, find a user by name, list bots (including Yuanbao AI), or list all members. IMPORTANT: You MUST call this tool before @mentioning any user, because you need the exact nickname to construct the @mention format.)rJ   rF   r3   u   find — search a user by name (use when you need to @mention or look up someone); list_bots — list bots and Yuanbao AI assistants; list_all — list all members.)r   enumrb   zUser name to search (partial match, case-insensitive). Required for 'find'. Use the name the user mentioned in the conversation.booleanz{Set to true when you need to @mention/at someone in your reply. The response will include the exact @mention format to use.r   u   📋
yb_send_dmu`  Send a private/direct message (DM) to a user in a group, with optional media files. This tool automatically looks up the user by name in the group member list and sends the message. Use this when someone asks to privately message / 私信 / DM a user. Supports text, images, and file attachments. You can also provide user_id directly if already known.u   The group where the target user belongs. Extract from chat_id: 'group:328306697' → '328306697'. Required when user_id is not provided.zdTarget user's display name (partial match, case-insensitive). Required when user_id is not provided.zEThe message text to send as a DM. Can be empty if only sending media.z}Target user's account ID. If provided, skips the member lookup. Usually obtained from a previous yb_query_group_members call.arrayzOptional list of media files to send along with the DM. Images (.jpg/.png/.gif/.webp/.bmp) are sent as image messages; other files are sent as document attachments.z.Absolute local file path of the media to send.z5Whether this file is a voice message (default false).)r   r   r   )r   rb   itemsr   u   ✉️yb_search_stickerui  Search the built-in Yuanbao sticker (TIM face / 表情包) catalogue by keyword. Returns the top matching candidates with sticker_id, name, and description. Use this BEFORE yb_send_sticker to discover the right sticker_id. Sticker = 贴纸 = TIM face — NOT a message reaction. Prefer sending a sticker over bare Unicode emoji when reacting/expressing emotion.uw   Search keyword (Chinese or English, e.g. '666', '比心', 'cool', '吃瓜'). Empty string returns the first N stickers.integerz8Max number of candidates to return (default 10, max 50).r   u   🔍yb_send_stickeru  Send a built-in sticker (TIMFaceElem / 贴纸表情) to the current Yuanbao chat. Call yb_search_sticker first if you don't know the sticker_id/name. Sticker = 贴纸 = TIM face — NOT a message reaction. CRITICAL: Whenever the user asks you to send a sticker / 贴纸 / 表情包, you MUST use this tool. DO NOT draw a PNG via execute_code / Pillow / matplotlib and then call send_image_file — that produces a fake 'sticker' image instead of a real TIM face and is the WRONG path. If no suitable sticker_id is known, call yb_search_sticker first. When the recent thread shows users sending stickers, prefer matching that tone by replying with a sticker instead of (or in addition to) text.u   Sticker name (e.g. '六六六', '比心', 'ok') or numeric sticker_id (e.g. '278'). Empty string sends a random built-in sticker.zvTarget chat. Defaults to the current session. Format: 'direct:{account_id}', 'group:{group_code}', or bare account_id.z5Optional ref_msg_id to quote-reply (group chat only).r   u   🎨)r   r   r   r   )r3   r!   F)
r   r   r4   r   r5   r   r6   r7   r   r   )r!   rY   )rZ   r   r[   r\   r   r   )r!   r!   r!   )rp   r   rq   r   rr   r   r   r   )r!   N)r   r   r5   r   r   r   r&   r   r   r   r   r   )"__doc__
__future__r   loggingpathlibr   typingr   r   r   	getLogger__name__r-   r   r>   rP   r2   rX   ro   r~   	frozensetr   r   tools.registryr   r   r   r   r   r   r   r   _TOOLSETregisterrH   rC   r   <module>r      sX   $ # " " " " "        ( ( ( ( ( ( ( ( ( (		8	$	$   !VGG 6 5 5 5 5> 	W5 W5 W5 W5 W5t! ! ! ! !J K K K K K^ iJJJKK 48x5 x5 x5 x5 x5~ 1 0 0 0 0 0 0 0- - -    # # #L       	%= $#N  &	
 	
 " (
/   4  	!(T  %#N 
 %===9	  %d  &V + : &x0? 
  
* *V +
c2 2 2 2h  	F  %A  %A  %#j 
 %X  $H
 !) )1/_% %
 )2/f) )	' 	' &,H    71 1d i5
 5
? ?@ 
MG G G GT  	#a  %E  &#]   
 
 6 &
C" " " "J  	!	!  %V  %c  %#Z  ( -
 
& &N $
[. . . . . .rC   