§
    øžyj  ã                  óº   — d Z ddlmZ ddlmZ ddlmZmZmZm	Z	 ddl
Z
 e
j        e¦  «        Z ed¬¦  «         G d„ d	¦  «        ¦   «         Zdd„Z	 	 	 	 ddd„ZdS )uU  Profile-based routing for the gateway with hierarchical matching.

Allows a single Hermes instance to route specific Discord guilds/channels/threads
to different profiles â€” each with their own model, tools, memory, and persona.

Matching priority (most specific first):
  1. platform + chat_id + thread_id (exact thread)  â€” specificity 14
  2. platform + chat_id (channel route)             â€” specificity 6
  3. platform + guild_id (guild/server route)       â€” specificity 2
  4. No match                                       â†’ default profile

Parent-chain matching:
For Discord threads and forum posts, ``parent_chat_id`` carries the
direct parent (the channel for a thread, the forum channel for a post).
Routes keyed on a channel match both direct messages and messages in
any thread/post whose parent is that channel.

Configuration (config.yaml):

    gateway:
      profile_routes:
        - name: server-default
          platform: discord
          guild_id: "YOUR_GUILD_ID"
          profile: server-profile

        - name: special-channel
          platform: discord
          guild_id: "YOUR_GUILD_ID"
          chat_id: "YOUR_CHANNEL_ID"
          profile: channel-profile

        - name: thread-route
          platform: discord
          chat_id: "YOUR_CHANNEL_ID"
          thread_id: "YOUR_THREAD_ID"
          profile: thread-profile
é    )Úannotations)Ú	dataclass)ÚAnyÚDictÚListÚOptionalNT)Úfrozenc                  ó”   — e Zd ZU dZded<   ded<   ded<   dZded<   dZded	<   dZded
<   dZded<   e	dd„¦   «         Z
	 	 	 	 ddd„ZdS )ÚProfileRoutez>A single routing rule that maps a platform scope to a profile.ÚstrÚnameÚplatformÚprofileNúOptional[str]Úguild_idÚchat_idÚ	thread_idTÚboolÚenabledÚreturnÚintc                óR   — d}| j         r|dz  }| j        r|dz  }| j        r|dz  }|S )z#Higher value = more specific match.r   é   é   é   )r   r   r   )ÚselfÚss     ú=/home/ragecks/.hermes/hermes-agent/gateway/profile_routing.pyÚspecificityzProfileRoute.specificity>   sG   € ð ˆØŒ=ð 	Ø�‰FˆAØŒ<ð 	Ø�‰FˆAØŒ>ð 	Ø�‰FˆAØˆó    Úparent_chat_idc                óÀ   — | j         sdS | j        |k    rdS | j        r| j        |k    rdS | j        r| j        |k    r| j        |k    rdS | j        r| j        |k    rdS dS )a$  Return True if this route matches the given source fields.

        All configured discriminators are matched conjunctively (AND): every
        discriminator that the route declares must hold. ``chat_id`` supports
        hierarchical matching for Discord forums/threads:
        - Direct channel match: chat_id == route.chat_id
        - Thread in channel: parent_chat_id == route.chat_id
        A route declaring both ``guild_id`` and ``chat_id`` requires both to
        match (a chat match alone does not satisfy a guild constraint).
        FT)r   r   r   r   r   )r   r   r   r   r   r!   s         r   ÚmatcheszProfileRoute.matchesJ   s‡   € ð$ Œ|ð 	Ø�5ØŒ=˜HÒ$Ð$Ø�5ØŒ>ð 	˜dœn°	Ò9Ð9Ø�5ØŒ<ð 	˜DœL¨GÒ3Ð3¸¼ÈÒ8VÐ8VØ�5ØŒ=ð 	˜Tœ]¨hÒ6Ð6Ø�5Øˆtr    )r   r   ©NNNN)r   r   r   r   r   r   r   r   r!   r   r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú__annotations__r   r   r   r   Úpropertyr   r#   © r    r   r   r   2   sÀ   € € € € € € àHÐHà€I€I�IØ€M€M�MØ€L€L�LØ"€HÐ"Ð"Ð"Ñ"Ø!€GÐ!Ð!Ð!Ñ!Ø#€IÐ#Ð#Ð#Ñ#Ø€GÐÐÐÑàð	ð 	ð 	ñ „Xð	ð #'Ø!%Ø#'Ø(,ðð ð ð ð ð ð r    r   ÚrawúOptional[List[Dict[str, Any]]]r   úList[ProfileRoute]c                ó"  — | sg S g }| D �]D}t          |t          ¦  «        sŒ|                     dd¦  «        }|                     dd¦  «        }|                     dd¦  «        }|r|st                               d|¦  «         Œ{	 ddlm}m}  ||¦  «        } ||¦  «         n3# t          t          f$ r t                               d||¦  «         Y ŒÊw xY w| 
                    t          ||||                     d	¦  «        |                     d
¦  «        |                     d¦  «        |                     dd¦  «        ¬¦  «        ¦  «         �ŒF|                     d„ d¬¦  «         t                               dt          |¦  «        ¦  «         |S )z†Parse profile_routes from config.yaml into ProfileRoute objects.

    Returns routes sorted by specificity (most specific first).
    r   Ú r   r   z6Skipping profile route %s: missing platform or profiler   )Únormalize_profile_nameÚvalidate_profile_namez2Skipping profile route %s: invalid profile name %rr   r   r   r   T)r   r   r   r   r   r   r   c                ó   — | j         S )N)r   )Úrs    r   ú<lambda>z&parse_profile_routes.<locals>.<lambda>•   s   € ˜aœm€ r    )ÚkeyÚreversez.Loaded %d profile routes (most-specific-first))Ú
isinstanceÚdictÚgetÚloggerÚwarningÚhermes_cli.profilesr1   r2   Ú
ValueErrorÚImportErrorÚappendr   ÚsortÚdebugÚlen)r,   ÚroutesÚentryr   r   r   r1   r2   s           r   Úparse_profile_routesrF   i   sæ  € ð
 ð Øˆ	Ø!#€FØð "
ñ "
ˆÝ˜%¥Ñ&Ô&ð 	ØØ�yŠy˜ Ñ$Ô$ˆØ—9’9˜Z¨Ñ,Ô,ˆØ—)’)˜I rÑ*Ô*ˆØð 	˜wð 	Ý�NŠNØHØñô ð ð ð		ðð ð ð ð ð ð ð ð -Ð,¨WÑ5Ô5ˆGØ!Ð! 'Ñ*Ô*Ð*Ð*øÝ�KÐ(ð 	ð 	ð 	Ý�NŠNÐOÐQUÐW^Ñ_Ô_Ð_ØˆHð	øøøð 	�ŠÝØØ!ØØŸš :Ñ.Ô.ØŸ	š	 )Ñ,Ô,ØŸ)š) KÑ0Ô0ØŸ	š	 )¨TÑ2Ô2ðñ ô ñ
	
ô 
	
ð 
	
ñ 
	
ð ‡K‚KÐ+Ð+°T€KÑ:Ô:Ð:Ý
‡L‚LÐAÅ3ÀvÁ;Ä;ÑOÔOÐOØ€Ms   ÂB$Â$-CÃCrD   r   r   r   r   r   r   r!   úOptional[ProfileRoute]c                óL   — | D ] }|                      |||||¬¦  «        r|c S Œ!dS )z5Return the best-matching route, or None for no match.)r   r   r   r!   N)r#   )rD   r   r   r   r   r!   Úroutes          r   Úmatch_profile_routerJ   š   sF   € ð ð ð ˆØ�=Š=˜¨H¸gÐQZÐkyˆ=ÑzÔzð 	ØˆLˆLˆLð	àˆ4r    )r,   r-   r   r.   r$   )rD   r.   r   r   r   r   r   r   r   r   r!   r   r   rG   )r(   Ú
__future__r   Údataclassesr   Útypingr   r   r   r   ÚloggingÚ	getLoggerr%   r;   r   rF   rJ   r+   r    r   ú<module>rP      sþ   ðð%ð %ðN #Ð "Ð "Ð "Ð "Ð "à !Ð !Ð !Ð !Ð !Ð !Ø ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,à €€€à	ˆÔ	˜8Ñ	$Ô	$€ð €�$ÐÑÔð3ð 3ð 3ð 3ð 3ñ 3ô 3ñ Ôð3ðl.ð .ð .ð .ðh #Ø!Ø#Ø$(ðð ð ð ð ð ð r    