
    [j                        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     9/home/ice/.hermes/hermes-agent/gateway/profile_routing.pyspecificityzProfileRoute.specificity>   sG     = 	FA< 	FA> 	FA    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> 	dn	995< 	DLG338V8V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IIIMMMLLL"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 )zParse 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    am 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 "
 "
%&& 	yy$$99Z,,))Ir** 	w 	NNH   		        -,W55G!!'****K( 	 	 	NNOQUW^___H	 	!:..		),,))K00		)T22  
	
 
	
 
	
 
	
 KK++TK:::
LLA3v;;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       ==HgQZky=zz 	LLL	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    