
    `gj                        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y)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y)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enabledc                p    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     J/root/.hermes/venv/lib/python3.12/site-packages/gateway/profile_routing.pyspecificityzProfileRoute.specificity>   s>     ==FA<<FA>>FA    c                   | j                   sy| j                  |k7  ry| j                  r| j                  |k7  ry| j                  r| j                  |k7  r| j                  |k7  ry| j                  r| j                  |k7  ryy)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   parent_chat_ids         r   matcheszProfileRoute.matchesJ   sj    $ ||==H$>>dnn	9<<DLLG38V==T]]h6r   )returnint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
IML"Hm"!G]!#I}#GT	 	 #'!%#'(,   	
 ! & 
r   r   c                   | sg S g }| D ]  }t        |t              s|j                  dd      }|j                  dd      }|j                  dd      }|r|st        j	                  d|       e	 ddlm}m}  ||      } ||       |j                  t        ||||j                  d	      |j                  d
      |j                  d      |j                  dd                    |j                  d d       t        j                  dt        |             |S # t        t        f$ r t        j	                  d||       Y <w xY w)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
    amm r   )keyreversez.Loaded %d profile routes (most-specific-first))
isinstancedictgetloggerwarninghermes_cli.profilesr.   r/   
ValueErrorImportErrorappendr   sortdebuglen)rawroutesentryr   r   r   r.   r/   s           r   parse_profile_routesrD   i   s@   
 	!#F "
%&yy$99Z,))Ir*wNNH 		 -W5G!'* 	!:.		),))K0		)T2
	
1"
H KK+TK:
LLA3v;OM# K( 	NNOQUW^_	s   /D&EEc                F    | D ]  }|j                  |||||      s|c S  y)z5Return the best-matching route, or None for no match.)r   r   r   r    N)r!   )rB   r   r   r   r   r    routes          r   match_profile_routerG      s5      ==HgQZky=zL r   )rA   zOptional[List[Dict[str, Any]]]r"   List[ProfileRoute]r$   )rB   rH   r   r   r   r   r   r   r   r   r    r   r"   zOptional[ProfileRoute])r(   
__future__r   dataclassesr   typingr   r   r   r   logging	getLoggerr%   r8   r   rD   rG   r+   r   r   <module>rN      s   %N # ! , , 			8	$ $3 3 3l.h #!#$(  	
  " r   