
    `gjk                    0   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dOdZ	 	 	 dP	 	 	 	 	 	 	 	 	 dQdZdRdSdZ	 	 	 dT	 	 	 	 	 	 	 dUdZ eh d      Z	 	 dV	 	 	 	 	 	 	 	 	 	 	 dWdZddlmZmZ d Zd Zd Zd Zd Zd ZdZ  ejB                  de ddddd d!d"idgd#d$eed%d&'        ejB                  d(e d(d)dd d!d"d g d*d+d,d d-d"d.d/d"d0ddgd#d$eed%d1'        ejB                  d2e d2d3dd d4d"d d5d"d d6d"d d7d"d8d9dd d:d"d.d;d"d<d=gd#d>d?g d#d$eed%d@'        ejB                  dAe dAdBdd dCd"dDdEd"dFg d#d$eed%dG'        ejB                  dHe dHdIdd dJd"d dKd"d dLd"dMg d#d$eed%dN'       y)Xu  
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                 <    	 ddl m}   |        S # t        $ r Y yw xY w)zOLazy import to avoid ImportError when gateway.platforms.yuanbao is unavailable.r   get_active_adapterN)gateway.platforms.yuanbaor
   ImportErrorr	   s    F/root/.hermes/venv/lib/python3.12/site-packages/tools/yuanbao_tools.py_get_active_adapterr      s%    @!## s    	unknownuser
yuanbao_aibot)r            z\To @mention a user, you MUST use the format: space + @ + nickname + space (e.g. " @Alice ").
group_codec           	       K   | sdddS t               }|dddS 	 |j                  |        d{   }|dddS d| |j                  dd	      |j                  d
d      |j                  dd	      |j                  dd	      dddS 7 W# t        $ r-}t        j                  d       dt        |      dcY d}~S d}~ww xY w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	exceptionstr)r   adaptergiexcs       r   get_group_infor0   5   s      +CDD!#G +MNN5++J77:$/OPP$&&r2FF>1566*b1FF#3R8 B

 
	
 8  5?@ 3s8445sO   CB
 B
B
 CAB
 CB
 
	C "B;5C 6C;C  Cactionc                  K   | sdddS t               }|dddS 	 |j                  |        d{   }|dddS |j                  dg       D cg c]l  }|j                  dd	      |j                  d
|j                  dd	            t        j                  |j                  d|j                  dd            d      dn }}|sdddS |rdt        ini }|dk(  r3|D cg c]  }|d   dv s| }	}|	sdddS ddt        |	       d|	d|S |dk(  r|ri|j                         j                         }
|D cg c]  }|
|d
   j                         v s| }}|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 7 xc c}w c c}w c c}w # t        $ r-}t        j                  d        dt        |      dcY d}~S d}~ww xY ww)!u   
    统一的群成员查询工具（对齐 TS query_session_members）。

    action:
      - find      : 按昵称模糊搜索
      - list_bots : 列出 bot 和元宝 AI
      - list_all  : 列出全部成员
    Fr   r   Nr   #get_group_member_list returned Nonemembersr#   r   r$   	nick_name	user_typeroler   r   )r#   r$   r7   zNo members found in this group.mention_hint	list_bots>   r   r   zNo bots found in this group.TzFound z bot(s).)r   msgr4   findz 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(   _USER_TYPE_LABELMENTION_HINTlenstriplowerr)   r*   r+   r,   )r   r1   namementionr-   rawmall_membershintbotsfiltmatchedr/   s                r   query_group_membersrK   R   s      +CDD!#G +MNNB511*==;$/TUU WWY+	
  55B/EE*aeeK.DE(,,EE+quuVQ'78)	
 	
 $/PQQ18-b[ *Q!ai;P.PAQDQ#(3QRRD	{(3 	  Vzz|))+&1STQz]=P=P=R5R1SS#'!'G~5J4&PRS#* 	   %+D61MN* 	   K 01=& 	  C,-[9"
 	
 	
q >	
" R T:  5DE 3s8445s   G3F: F(
F: G3F: A1F+F: 	G3
F: F0,F00F: 8G39F: G3)F: 7F5F5F: 1G32F: =G3>F: G3F: 'G3(F: +F: :	G0"G+%G0&G3+G00G3c                  K   ddl m} 	 t        dt        d|rt	        |      nd            }	  || xs d|      }d| xs dt        |      |D cg c]I  }|j                  dd      |j                  dd      |j                  dd      |j                  dd      dK c}dS # t
        t        f$ r d}Y w xY w# t        $ r-}t        j                  d       d	t        |      d
cY d}~S d}~ww xY wc c}w w)u   
    在内置贴纸表中按关键词模糊搜索，返回 Top-N 候选。

    返回每条候选的 sticker_id / name / description / package_id，
    供 LLM 选择后传给 send_sticker。空 query 时返回前 N 条。
    r   )search_stickersr   2   
   r   )limitz$[yuanbao_tools] search_sticker errorFr   NT
sticker_idrB   description
package_id)rQ   rB   rR   rS   )r   querycountresults)!gateway.platforms.yuanbao_stickerrM   maxminint	TypeError
ValueErrorr)   r*   r+   r,   r?   r(   )rT   rP   rM   
safe_limitmatchesr/   ss          r   search_stickerr`      s      BC%CJR@A
5!%+2Z@ "W 
   eeL"5fb) uu]B7eeL"5	
	  z" 

  5?@ 3s8445
s]   C8#B# B: C8AC3
C8#B74C86B77C8:	C0"C+%C0&C8+C00C8c                  K   ddl m} ddlm}m}m} |xs dj                         xs	  |dd      }|sdddS t               }|dd
dS | xs dj                         }	d	}
|	s |       }
n"|	j                         r ||	      }
|
 ||	      }
|
	dd|	ddS 	 |j                  ||
j                  dd      |xs d	       d	{   }t        |dd      r6d||
j                  dd      |
j                  dd      dt        |dd	      ddS dt        |dd      dS 7 W# t        $ r-}t        j                  d       dt        |      dcY d	}~S d	}~ww xY ww)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.rB   )chat_idsticker_namereply_toz"[yuanbao_tools] send_sticker errorr   TrQ   )rQ   rB   
message_idz~Sticker delivered to the chat. If you have additional text to say, reply now; otherwise end your turn without generating text.)r   rh   stickerrk   r&   r   zsend_sticker failed)gateway.session_contextrc   rW   rd   re   rf   r@   r   isdigitsend_stickerr(   r)   r*   r+   r,   getattr)rl   rh   rj   rc   rd   re   rf   targetr-   rD   sticker_objresultr/   s                r   ro   ro      s      8  m""$U8PRT(UFO
 	

 "#G +MNN=b


!C"&K(*;;=+C0K-c2K*3' 2P Q
 	
5++$4% , 
 
 vy%()oolB?#3 "&,= U	
 		
 *?@ )

  5=> 3s8445sI   BE+D DD AED 	E&"EE	EEE>   .bmp.gif.jpg.png.jpeg.webpc                  K   |s|sdddS t               }|dddS |r|j                         nd}|j                         }|sK| sdddS |sdddS 	 |j                  |        d{   }|dd	dS |j                  d
g       }	|j                         j	                         }
|	D cg c]<  }|
|j                  d      xs |j                  d      xs dj	                         v r|> }}|sdd| d|  ddS t        |      dkD  rL|D cg c]7  }|j                  dd      |j                  d|j                  dd            d9 }}dd| d|dS |d   j                  dd      }|d   j                  d|d   j                  d|            }|sdddS d| }d}g }	 |rW|j                         rG|j                  |||        d{   }|j                  s|j                  |j                  xs d       |xs g D ]  \  }}t        |      j                  j	                         }|t         v r|j#                  |||        d{   }n|j%                  |||        d{   }|j                  rw|j                  |j                  xs d        |dddS |r"||j                  sddj'                  |      dS d|||j(                  d | d!d"}|r |d#xx   d$dj'                  |       d%z  cc<   |S 7 c c}w c c}w # t        $ r-}t        j                  d       dt        |      dcY d}~S d}~ww xY w7 y7 7 # t        $ r-}t        j                  d&       dt        |      dcY d}~S d}~ww xY w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 providedr3   r4   r$   r5   zNo member matching "z" found in group .r   r#   r"   zMultiple members match "z". Please specify which one.)r   r   
candidatesr   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$   rk   r&   r&   z (partial failure: )z[yuanbao_tools] send_dm error)r   r@   r<   r(   rA   r?   r)   r*   r+   r,   send_dmr   appendr   r   suffix_IMAGE_EXTSsend_image_filesend_documentjoinrk   )r   rB   messager#   media_filesr-   resolved_user_idresolved_nicknamerD   r4   rI   rE   rJ   r|   r/   rh   last_resulterrors
media_path	_is_voiceextrs   s                         r   r   r   "  s    0 ; +OPP!#G +MNN*1w}}r

 $/dee$/^__$	955jAAC{#(3XYYggi,G::<%%'D"AEE*-I{1CIrPPRR G 
 $3D69J:,VWX  7|a %
  $%55B#7$%EE*aeeK6L$M
   %7v=YZ",   'qz~~i< '
z71:>>+W[;\ ]
  +FGG ()*GKF"5w}} '0@'V` aaK&&k//E3EF &1%6B 	H!J	z"))//1Ck!$+$;$;GZ\f$;$gg$+$9$9':Zd$9$ee&&k//F3FG	H $/VWW{*+2E2E$tyy/@AA ')%00"#4"5_E
 6N 3DIIf4E3FaHHNW B  	9JK$s3x88	9 b he(  589 3s8445s  ANL	 'K<(
L	 2N34L	 'AK?(L	 7N8L	 
<LL	 N=L	 N )M	 	M
A=M	 MM	 $M%M	 6'M	 N#M	 N9M	 ;N<L	 ?
L	 		L?"L:4L?5N:L??NM	 M	 M	 		M?"M:4M?5N:M??N)registrytool_resultc                 b    	 ddl m}   | dd      dk(  ry	 t               duS # t        $ r Y w xY w)uN   Toolset availability check — True when running in a yuanbao gateway session.r   rb   HERMES_SESSION_PLATFORMr   yuanbaoTN)rm   rc   r)   r   rb   s    r   _check_yuanbaor     sG    ;4b9YF G  ,,  s   " 	..c                h   K   t        t        | j                  dd             d {         S 7 w)Nr   r   r}   )r   r0   r(   argskws     r   _handle_yb_query_group_infor     s2     ^88L"-    s   %20	2c                   K   t        t        | j                  dd      | j                  dd      | j                  dd      t        | j                  dd                   d {         S 7 w)	Nr   r   r1   list_allrB   rC   Fr   r1   rB   rC   )r   rK   r(   boolr   s     r   _handle_yb_query_group_membersr     sb     088L"-xx*-XXfb!TXXi/0	    s   A!A.#A,$	A.c                d  K   | j                  dd      }|s6	 ddlm}  |dd      }|j                  d      r|j	                  dd      d   }| j                  d	      xs g }g }|D ]  }t        |t              r=|j                  |j                  d
d      t        |j                  dd            f       Pt        |t        t        f      sgt        |      dk\  sv|j                  t        |d         t        |d         f        | j                  dd      }ddlm}	 |	j!                  |      \  }
}|
r|j#                  |
       |	j%                  |      }t'        t)        || j                  dd      || j                  dd      |xs d        d {         S # t
        $ r Y ]w xY w7 w)Nr   r   r   rb   rg   zgroup::r   r   pathis_voiceFr   r   )BasePlatformAdapterrB   r#   r   rB   r   r#   r   )r(   rm   rc   
startswithsplitr)   
isinstancedictr   r   listtupler?   r,   gateway.platforms.baser   extract_mediaextendfilter_media_delivery_pathsr   r   )r   r   r   rc   rh   	raw_mediar   itemr   r   embedded_medias              r   _handle_yb_send_dmr     s    ,+J	?%&>CG!!(+$]]3215

 '-2IK >dD! 4d488JPU;V6WXYtUm,TaDGd47m<=	> hhy"%G:1??HNG>*%AA+NKW488FB+?B''4	   +  		*sB   F05F A?F0F0B9F0F.F0	F+'F0*F++F0c           	        K   t        t        | j                  dd      | j                  dd             d {         S 7 w)NrT   r   rP   rO   rT   rP   )r   r`   r(   r   s     r   _handle_yb_search_stickerr     s@     ^hhw#hhw#    s   6AA	Ac           
        K   t        t        | j                  dd      | j                  dd      | j                  dd             d {         S 7 w)Nrl   r   rh   rj   rl   rh   rj   )r   ro   r(   r   s     r   _handle_yb_send_stickerr     sN     \B'B'*b)    s   AA	A
	A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).)typerR   )r   
propertiesrequired)rB   rR   
parametersTu   👥)rB   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.)r;   r9   r   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   enumrR   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   rR   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,   returnr   )r   r   F)
r   r,   r1   r,   rB   r,   rC   r   r   r   )r   rO   )rT   r,   rP   rZ   r   r   )r   r   r   )rl   r,   rh   r,   rj   r,   r   r   )r   N)r   r,   rB   r,   r   r,   r#   r,   r   z Optional[List[Tuple[str, bool]]]r   r   )"__doc__
__future__r   loggingpathlibr   typingr   r   r   	getLogger__name__r*   r   r=   r>   r0   rK   r`   ro   	frozensetr   r   tools.registryr   r   r   r   r   r   r   r   _TOOLSETregister     r   <module>r      s  $ #   ( (			8	$ !VG 6 5> 	W5W5W5 W5 	W5
 
W5t!J KKK K 
	K^ JK 48x5x5
x5 x5 	x5
 2x5 
x5~ 1-$N    	%= $#N &	
" (
/4   	!(T  %#N
 %=9	 %d &V+: &x0? 
*V +
c2h   	F  %A %A %#j
 %X $H
 !) )1/_%
 )2/f)	' &,H 71d i5
?@ 
MGT   	#a  %E &#] 
6 &
C"J   	!	!  %V %c %#Z( -
&N $
[.r   