
    PL
j                       d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	m
Z
mZ ddlmZ 	 ddlmZ dZn# e$ r dZdZY nw xY w	 dd	lmZmZ dd
lmZ ddlmZmZ ddlmZ ddlmZ ddlmZm Z  ddl!m"Z"m#Z# ddl$m%Z%m&Z&m'Z'm(Z( ddl)m*Z*m+Z+m,Z, dZ-n1# e$ r) dZ-dZdZdZdZdZdZdZdZdZ dZ#dZ"e.Z%dZ&dZ'dZ(dZ*dZ+dZ,Y nw xY wddl/m0Z0m1Z1 ddl2m3Z3 ddl4m5Z5m6Z6m7Z7m8Z8m9Z9  ej:        e;          Z<dZ=dZ>dddAdZ?e=ddBdZ@ G d  d!          ZA G d" d#          ZB G d$ d%          ZCdCd&ZDdCd'ZEdCd(ZFdDd*ZGd+ZH eId,d-h          ZJddlKZL eLjM        d.          ZNdEd2ZOdddd3dFd;ZPeDZQ G d< d=e5          ZRdGd?ZSdGd@ZTdS )Ha  
Microsoft Teams platform adapter for Hermes Agent.

Uses the microsoft-teams-apps SDK for authentication and activity processing.
Runs an aiohttp webhook server to receive messages from Teams.
Proactive messaging (send, typing) uses the SDK's App.send() method.

Requires:
    pip install microsoft-teams-apps aiohttp
    TEAMS_CLIENT_ID, TEAMS_CLIENT_SECRET, and TEAMS_TENANT_ID env vars

Configuration in config.yaml:
    platforms:
      teams:
        enabled: true
        extra:
          client_id: "your-client-id"      # or TEAMS_CLIENT_ID env var
          client_secret: "your-secret"      # or TEAMS_CLIENT_SECRET env var
          tenant_id: "your-tenant-id"       # or TEAMS_TENANT_ID env var
          port: 3978                        # or TEAMS_PORT env var
    )annotationsN)AnyDictOptional)quote)webTF)AppActivityContext)ClientOptions)MessageActivityConversationReference)TypingActivityInput)AdaptiveCardInvokeActivity)AdaptiveCardActionCardResponse!AdaptiveCardActionMessageResponse)InvokeResponseAdaptiveCardInvokeResponse)
HttpMethodHttpRequestHttpResponseHttpRouteHandler)AdaptiveCardExecuteAction	TextBlock)PlatformPlatformConfig)MessageDeduplicator)BasePlatformAdapterMessageEventMessageType
SendResultcache_image_from_urli  z/api/messagesdefaultvaluer   r$   boolreturnc                   t          | t                    r| S t          | t                    r2|                                                                 }|dv rdS |dv rdS |S )N>   1onyestrueT>   0noofffalseF)
isinstancer&   strstriplower)r%   r$   
normalizeds      C/home/kuhnn/.hermes/hermes-agent/plugins/platforms/teams/adapter.py_parse_boolr7   k   sk    % % [[]]((**
33344445N    intc               T    	 t          |           S # t          t          f$ r |cY S w xY wN)r9   	TypeError
ValueError)r%   r$   s     r6   _coerce_portr>   w   s<    5zzz"   s    ''c                  0    e Zd ZdZddZdddd
ZddZdS )_StaticAccessTokenProviderzSMinimal token-provider shim so outbound Graph delivery can reuse the shared client.access_tokenr2   c                V    t          |pd                                          | _        d S N )r2   r3   _access_token)selfrA   s     r6   __init__z#_StaticAccessTokenProvider.__init__   s)     !344::<<r8   F)force_refreshrH   r&   r'   c               B   K   ~| j         st          d          | j         S )Nz=TEAMS_GRAPH_ACCESS_TOKEN is required for graph delivery mode.)rE   r=   )rF   rH   s     r6   get_access_tokenz+_StaticAccessTokenProvider.get_access_token   s-      ! 	^\]]]!!r8   Nonec                    d S r;    rF   s    r6   clear_cachez&_StaticAccessTokenProvider.clear_cache   s    tr8   N)rA   r2   )rH   r&   r'   r2   r'   rK   )__name__
__module____qualname____doc__rG   rJ   rO   rM   r8   r6   r@   r@   ~   sf        ]]= = = = ?D " " " " " "     r8   r@   c                      e Zd ZdZ	 d#dddd$dZ	 d#d%dZd&dZd'dZd'dZd(dZ	d)dZ
d)dZed)d            Zed*d            Zed+d"            ZdS ),TeamsSummaryWritera  Pipeline-facing Teams outbound delivery surface.

    This stays inside the existing Teams platform plugin so the meeting-pipeline
    PR can reuse one Teams integration surface instead of introducing a second
    adapter elsewhere in the gateway core.
    N)graph_client	transportplatform_configPlatformConfig | NonerW   
Any | NonerX   httpx.AsyncBaseTransport | Noner'   rK   c               0    || _         || _        || _        d S r;   )_platform_config_graph_client
_transport)rF   rY   rW   rX   s       r6   rG   zTeamsSummaryWriter.__init__   s     !0)#r8   payloadr   configdict[str, Any] | Noneexisting_recordOptional[dict[str, Any]]dict[str, Any]c                  K   |                      |          }|r3t          |                    d          d          st          |          S t	          |                    d          p|                    d          pd                                                                          }|sY|                    d          rd}nA|                    d	          s*|                    d
          r|                    d          rd}|dk    r|                     ||           d {V S |dk    r|                     ||           d {V S t          d          )Nforce_resendFr#   delivery_modemoderD   incoming_webhook_urlincoming_webhookchat_idteam_id
channel_idgraphz:Teams delivery_mode must be 'incoming_webhook' or 'graph'.)
_resolve_delivery_configr7   getdictr2   r3   r4   #_write_summary_via_incoming_webhook_write_summary_via_graphr=   )rF   ra   rb   rd   mergedrj   s         r6   write_summaryz TeamsSummaryWriter.write_summary   sz      ..v66 	);vzz./I/ISX#Y#Y#Y 	)(((6::o..J&**V2D2DJKKQQSSYY[[ 	zz011 )I&& 

9%%*0**\*B*B %%%AA'6RRRRRRRRR7??66wGGGGGGGGGH
 
 	
r8   c           	        i }| j         }|e|                    t          |j        pi                      |j        rd|vr
|j        |d<   |j        r |                    d|j        j                   |                    t          |pi                      t          j	        dd          t          j	        dd          t          j	        dd          t          j	        dd          t          j	        dd          t          j	        d	d          d
}|
                                D ]!\  }}|r|                    |          s|||<   "|S )NrA   ro   TEAMS_DELIVERY_MODErD   TEAMS_INCOMING_WEBHOOK_URLTEAMS_GRAPH_ACCESS_TOKENTEAMS_TEAM_IDTEAMS_CHANNEL_IDTEAMS_CHAT_ID)ri   rk   rA   rn   ro   rm   )r^   updaters   extratokenhome_channel
setdefaultrm   osgetenvitemsrr   )rF   rb   rv   platform_cfgenv_defaultskeyr%   s          r6   rq   z+TeamsSummaryWriter._resolve_delivery_config   sT   !#,#MM$|17R88999! <nF&B&B)5);~&( S!!,0I0QRRRd6<R(()))  Y'<bAA$&I.JB$O$OI&@"EEy"55)$6;;y"55
 
 ',,.. 	$ 	$JC $VZZ__ $#sr8   c                  K   dd l }t          |                    d          pd                                          }|st	          d          d|                     |          i}|                    d| j                  4 d {V }|                    ||           d {V }|	                                 d d d           d {V  n# 1 d {V swxY w Y   d	||j
        d
dS )Nr   rk   rD   zATEAMS_INCOMING_WEBHOOK_URL is required for incoming_webhook mode.textg      4@)timeoutrX   )jsonrl   T)ri   webhook_urlstatus_code	delivered)httpxr2   rr   r3   r=   _render_summary_markdownAsyncClientr`   postraise_for_statusr   )rF   ra   rb   r   r   bodyclientresponses           r6   rt   z6TeamsSummaryWriter._write_summary_via_incoming_webhook   s      	&**%;<<BCCIIKK 	b`aaa55g>>?$$TT_$MM 	( 	( 	( 	( 	( 	( 	(QW#[[4[@@@@@@@@H%%'''	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 0&#/	
 
 	
s   2C
CCc                  K   |                      |          }t          |                    d          pd                                          }|r|dt	          |d           d}|                    |dd|                     |          di	           d {V }d
d||pi                     d          |pi                     d          dS t          |                    d          pd                                          }t          |                    d          pd                                          }|r|st          d          dt	          |d           dt	          |d           d}|                    |dd|                     |          di	           d {V }d
d|||pi                     d          |pi                     d          dS )Nrm   rD   z/chats/)safez	/messagesr   html)contentTypecontent)	json_bodyrp   chatidwebUrl)ri   target_typerm   
message_idweb_urlrn   ro   zEGraph delivery mode requires chat_id, or both team_id and channel_id.z/teams/z
/channels/channel)ri   r   rn   ro   r   r   )_build_graph_clientr2   rr   r3   r   	post_json_render_summary_htmlr=   )	rF   ra   rb   rW   rm   pathr   rn   ro   s	            r6   ru   z+TeamsSummaryWriter._write_summary_via_graph   sg     
 //77fjj++1r2288:: 	?U7444???D)33!6dF_F_`gFhFh#i#ij 4        H
 ")%"'~222488$N//99   fjj++1r2288::L117R88>>@@
 	j 	W  5eG"--- 5 5Zb)))5 5 5 	 &//v$B[B[\cBdBdeef 0 
 
 
 
 
 
 
 

 %$$#>r..t44 B++H55
 
 	
r8   c                ,   | j         | j         S ddlm} ddlm} t          |                    d          pd                                          }|r |t          |          | j	                  S  ||
                                | j	                  S )Nr   )MicrosoftGraphTokenProvider)MicrosoftGraphClientrA   rD   )rX   )r_   tools.microsoft_graph_authr   tools.microsoft_graph_clientr   r2   rr   r3   r@   r`   from_env)rF   rb   r   r   rA   s        r6   r   z&TeamsSummaryWriter._build_graph_client  s    )%%JJJJJJEEEEEE6::n55;<<BBDD 	''*<88/    $#'0022o
 
 
 	
r8   r2   c           
        d|                      |           ddd|                     t          |dd           d           ddg|                     t          |dd                     dd|                     t          |d	d                     dd
|                     t          |dd                     }d                    |          S )Nz**rD   z	Summary: summaryNo summary available.zKey decisions:key_decisionszAction items:action_itemszRisks:risks
)_title_textgetattr_bullet_linesjoin)rF   ra   liness      r6   r   z+TeamsSummaryWriter._render_summary_markdown(  s    )W%%)))`

77It#D#DF]^^``
 $ G GHH
 
 
  F FGG
 
 
 $ ? ?@@
 yyr8   c                Z   d|                      t          |dd           d          gfdt          t          |dd           pg           fdt          t          |dd           pg           fdt          t          |d	d           pg           fg}d
t          j        |                     |                     dg}|D ]\  }}|                    dt          j        |           d           t          |          dk    rE|dk    r?|                    dt          j        t          |d                              d           |r=d	                    d |D                       }|                    |rd| dpd           |                    d           d	                    |          S )NSummaryr   r   zKey decisionsr   zAction itemsr   Risksr   z<h2>z</h2>z<h3>z</h3>   z<p>r   z</p>rD   c              3     K   | ]J}t          |                                          #d t          j        t          |                     dV  KdS )z<li>z</li>N)r2   r3   r   escape.0items     r6   	<genexpr>z:TeamsSummaryWriter._render_summary_html.<locals>.<genexpr>G  sY      "o"oD]`ae]f]f]l]l]n]n"o#G$+c$ii*@*@#G#G#G"o"o"o"o"o"or8   z<ul>z</ul>z<p>None</p>)
r   r   listr   r   r   appendlenr2   r   )rF   ra   sectionsblocksheadingr   rendereds          r6   r   z'TeamsSummaryWriter._render_summary_html9  s   GGY$E$EG^__`ad77OT#J#J#PbQQRT''>4"H"H"NBOOPd77GT::@bAAB	
 BT[[%9%9::AAAB& 		- 		-NGUMM<W!5!5<<<===5zzQ7i#7#7DDKE!H$>$>DDDEEE -77"o"oTY"o"o"ooohA+A(+A+A+AR]SSSSm,,,,wwvr8   c                    t          | dd           }|rt          |          S t          | dd           }|rt          |dd           nd }d|pd S )Ntitlemeeting_ref
meeting_idzMeeting r   )r   r2   )ra   r   r   r   s       r6   r   zTeamsSummaryWriter._titleM  sg    $// 	u::g}d;;ALVW[,===RV
3*1	333r8   r%   r$   c                P    t          | pd                                          }|p|S rC   r2   r3   )r%   r$   r   s      r6   r   zTeamsSummaryWriter._textV  s)    5;B%%''wr8   values	list[str]c                <    d |pg D             }d |D             pdgS )Nc                    g | ]D}t          |                                          #t          |                                          ES rM   r   r   s     r6   
<listcomp>z4TeamsSummaryWriter._bullet_lines.<locals>.<listcomp>]  s=    SSStTARARST""SSSr8   c                    g | ]}d | S )z- rM   r   s     r6   r   z4TeamsSummaryWriter._bullet_lines.<locals>.<listcomp>^  s    ...T...r8   z- NonerM   )clsr   r   s      r6   r   z TeamsSummaryWriter._bullet_lines[  s4    SS"SSS.....<8*<r8   r;   )rY   rZ   rW   r[   rX   r\   r'   rK   )ra   r   rb   rc   rd   re   r'   rf   )rb   rc   r'   rf   )ra   r   rb   rf   r'   rf   )rb   rf   r'   r   )ra   r   r'   r2   )r%   r   r$   r2   r'   r2   )r   r   r'   r   )rQ   rR   rS   rT   rG   rw   rq   rt   ru   r   r   r   staticmethodr   r   classmethodr   rM   r8   r6   rV   rV      sE         26	$ $(59	$ 	$ 	$ 	$ 	$ 	$ 59	
 
 
 
 
4   0
 
 
 
.*
 *
 *
 *
X
 
 
 
$       "   ( 4 4 4 \4    \ = = = [= = =r8   rV   c                  :    e Zd ZdZddZddZddZddZddZdS )_AiohttpBridgeAdaptera3  HttpServerAdapter that bridges the Teams SDK into an aiohttp server.

    Without a custom adapter, ``App()`` unconditionally imports fastapi/uvicorn
    and allocates a ``FastAPI()`` instance.  This bridge captures the SDK's
    route registrations and wires them into our own aiohttp ``Application``.
    aiohttp_app'web.Application'c                    || _         d S r;   )_aiohttp_app)rF   r   s     r6   rG   z_AiohttpBridgeAdapter.__init__i  s    'r8   method'HttpMethod'r   r2   handler'HttpRouteHandler'r'   rK   c                V    dfd}| j         j                            |||           dS )z2Register an SDK route handler as an aiohttp route.request'web.Request'r'   'web.Response'c                z  K   |                                   d {V }t          | j                  } t          ||                     d {V }|                    dd          }|                    d          }|)t          j        |t          j        |          d          S t          j        |          S )N)r   headersstatus   r   application/json)r   r   content_type)r   )r   rs   r   r   rr   r   Responsedumps)r   r   r   resultr   	resp_bodyr   s         r6   _aiohttp_handlerz>_AiohttpBridgeAdapter.register_route.<locals>._aiohttp_handlero  s       ''''''D7?++G+27;DRY3Z3Z3Z+[+[%[%[%[%[%[%[FZZ#..F

6**I$|!I..!3   
 <v....r8   N)r   r   r'   r   )r   router	add_route)rF   r   r   r   r   s      ` r6   register_routez$_AiohttpBridgeAdapter.register_routel  sH    	/ 	/ 	/ 	/ 	/ 	/ 	 **649IJJJJJr8   	directoryc                    d S r;   rM   )rF   r   r   s      r6   serve_staticz"_AiohttpBridgeAdapter.serve_static  s    r8   portr9   c                $   K   t          d          )Nz(aiohttp server is managed by the adapter)NotImplementedError)rF   r   s     r6   startz_AiohttpBridgeAdapter.start  s      !"LMMMr8   c                
   K   d S r;   rM   rN   s    r6   stopz_AiohttpBridgeAdapter.stop  s      r8   N)r   r   )r   r   r   r2   r   r   r'   rK   )r   r2   r   r2   r'   rK   )r   r9   r'   rK   rP   )	rQ   rR   rS   rT   rG   r   r   r   r  rM   r8   r6   r   r   a  s         ( ( ( (K K K K&   N N N N     r8   r   c                     t           ot          S )zDReturn True when all Teams dependencies and credentials are present.)TEAMS_SDK_AVAILABLEAIOHTTP_AVAILABLErM   r8   r6   check_requirementsr    s    4#44r8   c                J   t          | di           pi }t          j        d          p|                    dd          }t          j        d          p|                    dd          }t          j        d          p|                    dd          }t	          |o|o|          S )	zAReturn True when the config has the minimum required credentials.r   TEAMS_CLIENT_ID	client_idrD   TEAMS_CLIENT_SECRETclient_secretTEAMS_TENANT_ID	tenant_id)r   r   r   rr   r&   )rb   r   r  r
  r  s        r6   validate_configr    s    FGR((.BE	+,,J		+r0J0JII344V		/SU8V8VM	+,,J		+r0J0JI	9m9	:::r8   c                     t          |           S )z7Check whether Teams is configured (env or config.yaml).)r  )rb   s    r6   is_connectedr    s    6"""r8   dict | Nonec                    t          j        dd                                          } t          j        dd                                          }t          j        dd                                          }| r|r|sdS | ||d}t          j        dd                                          }|r$	 t          |          |d<   n# t          $ r Y nw xY wt          j        d	d                                          }|r||d
<   t          j        dd                                          }|r|t          j        dd          d|d<   |S )a  Seed ``PlatformConfig.extra`` from env vars during gateway config load.

    Called by the platform registry's env-enablement hook BEFORE adapter
    construction, so ``gateway status`` and ``get_connected_platforms()``
    reflect env-only configuration without instantiating the Teams SDK.
    Returns ``None`` when Teams isn't minimally configured.

    The special ``home_channel`` key in the returned dict becomes a proper
    ``HomeChannel`` dataclass on the ``PlatformConfig`` via the core hook.
    r  rD   r	  r  N)r  r
  r  
TEAMS_PORTr   TEAMS_SERVICE_URLservice_urlTEAMS_HOME_CHANNELTEAMS_HOME_CHANNEL_NAMEHome)rm   namer   )r   r   r3   r9   r=   )r  r
  r  seedr   r  homes          r6   _env_enablementr    sl    	+R006688II3R88>>@@M	+R006688I - I t& D
 9\2&&,,..D 	t99DLL 	 	 	D	)/44::<<K *)]9)2..4466D 
I7@@ 
  
^ Ks   .C 
CCz&https://smba.trafficmanager.net/teams/zsmba.trafficmanager.netz!smba.infra.gov.teams.microsoft.usz^[A-Za-z0-9:@\-_.]+$rawr2   Optional[str]c                    | sdS 	 ddl m}  ||           }n# t          $ r Y dS w xY w|j        dk    rdS |j        t
          vrdS |                     d          r| n| dz   }|S )a  Return a normalized service URL or ``None`` if it is not allowed.

    Requires ``https://`` and a host in ``_ALLOWED_TEAMS_SERVICE_HOSTS``.
    The trailing slash is added if absent so callers can append
    ``v3/conversations/...`` without double slashes.
    Nr   )urlparsehttps/)urllib.parser  	Exceptionschemehostname_ALLOWED_TEAMS_SERVICE_HOSTSendswith)r  r  parsedr5   s       r6   _validate_teams_service_urlr)    s      t))))))#   tt}t:::tS))8sSyJs    
&&)	thread_idmedia_filesforce_documentrm   messager*  r+  Optional[list]r,  Dict[str, Any]c          	       K   t          | di           pi }t          j        d          p|                    dd          }t          j        d          p|                    dd          }t          j        d          p|                    dd          }	|r|r|	sd	d
iS t          j        d          p|                    dd          pt          }
t          |
          }|d	dt          t                     iS |sd	diS t          	                    |          sd	diS t          	                    |	          sd	diS d|	 d}| d| d}t          sd	diS 	 ddl}|                    d          }|                    d          4 d{V }|                    |d||dddd i|!          4 d{V 	 }|j        d"k    rU|                                 d{V }d	d#|j         d$|dd%          icddd          d{V  cddd          d{V  S |                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   |                    d&          }|sd	d'icddd          d{V  S d(|d)d*}|                    ||d+| d,d-|.          4 d{V 	 }|j        d"k    rU|                                 d{V }d	d/|j         d$|dd%          icddd          d{V  cddd          d{V  S |                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   ddd          d{V  n# 1 d{V swxY w Y   d|                    d0          d1S # t$          j        $ r  t(          $ r-}t*                              d2d3           d	d4| icY d}~S d}~ww xY w)5a  Acquire a Bot Framework bearer token and POST a single message activity.

    Used by ``tools/send_message_tool._send_via_adapter`` when the gateway
    runner is not in this process (e.g. ``hermes cron`` running as a
    separate process from ``hermes gateway``).  Without this hook,
    ``deliver=teams`` cron jobs fail with ``No live adapter for platform``.

    Configuration: requires ``TEAMS_CLIENT_ID``, ``TEAMS_CLIENT_SECRET``,
    ``TEAMS_TENANT_ID``, ``TEAMS_HOME_CHANNEL`` (the conversation ID), and
    optionally ``TEAMS_SERVICE_URL`` (Bot Framework service host; must be
    a known Bot Framework endpoint, see ``_ALLOWED_TEAMS_SERVICE_HOSTS``).

    Security: ``service_url`` is validated against an allowlist of known
    Bot Framework hosts to block SSRF / token-exfiltration via a tampered
    env var.  ``chat_id`` is validated to match the documented Bot
    Framework ID character set so it cannot escape the URL path.

    ``media_files`` and ``force_document`` are accepted for signature
    parity but not implemented for the standalone path; messages with
    attachments will send as text-only.  The live adapter handles
    attachments via the SDK.
    r   r  r  rD   r	  r
  r  r  errorzaTeams standalone send: TEAMS_CLIENT_ID, TEAMS_CLIENT_SECRET, and TEAMS_TENANT_ID are all requiredr  r  NzeTeams standalone send: TEAMS_SERVICE_URL host is not on the Bot Framework allowlist; expected one of z<Teams standalone send: chat_id (conversation ID) is requiredz`Teams standalone send: chat_id contains characters outside the Bot Framework conversation ID setzSTeams standalone send: TEAMS_TENANT_ID contains characters outside the expected setz"https://login.microsoftonline.com/z/oauth2/v2.0/tokenzv3/conversations/z/activitiesz,Teams standalone send: aiohttp not installedr   g      .@)totalT)	trust_envclient_credentialsz%https://api.botframework.com/.default)
grant_typer  r
  scopeContent-Typez!application/x-www-form-urlencoded)datar   r   i  z-Teams standalone send: token request failed (z): i,  rA   z:Teams standalone send: token response missing access_tokenr-  markdown)typer   
textFormatzBearer r   )Authorizationr7  )r   r   r   z-Teams standalone send: activity post failed (r   successr   zTeams standalone send raisedexc_infozTeams standalone send failed: )r   r   r   rr   _DEFAULT_TEAMS_SERVICE_URLr)  sortedr&  _TEAMS_CONV_ID_REmatchr  aiohttpClientTimeoutClientSessionr   r   r   r   asyncioCancelledErrorr#  loggerdebug)pconfigrm   r-  r*  r+  r,  r   r  r
  r  raw_service_urlr  	token_urlactivities_url_aiohttpper_request_timeoutsession
token_respr   token_payloadrA   activity	send_respsend_payloades                            r6   _standalone_sendrY    s     > GWb))/RE	+,,J		+r0J0JII344V		/SU8V8VM	+,,J		+r0J0JI ~- ~I ~|}} 		%&& 	&99]B''	&% 
 .o>>K62336 6 	  YWXX""7++ }{||""9-- pnooRYRRRI#JJgJJJN IGHH4?"""" '4444@@))D)99 %	6 %	6 %	6 %	6 %	6 %	6 %	6W||"6!*%2D	  ()LM+ $ 
 
 8 8 8 8 8 8 8 8 $++!+!2!2222222D#%wU_Uf%w%wkoptqtptku%w%wx8 8 8 8 8 8 8 8 8 8 8 8 8%	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 '1oo&7&7 7 7 7 7 7 78 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 ),,^<<L _!]^%%	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6* "( H
 ||%=|%=%=$6  , $   6 6 6 6 6 6 6 6 #s**!*!1!1111111D#%vU^Ue%v%vjnospsosjt%v%vw6 6 6 6 6 6 6 6 6 6 6 6 63%	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6J &/^^%5%55555556 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 63%	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6 %	6N &**400
 
 	
 !    ? ? ?3dCCC=!==>>>>>>>?s   6N 'M3<I /MN I .M 
I
	
MI
	M+N >+M*<L7&M8N L7%M7
M	MM	MN 
M$$N 'M$(N O	"O>O	O	c                       e Zd ZdZdZd, fdZd-dZd.d
Zd/dZd0dZ	d1dZ
	 	 d2d3dZ	 	 d4d5d#Zd6d7d$Z	 	 	 d8d9d'Z	 	 d4d:d)Zd;d+Z xZS )<TeamsAdapterz;Microsoft Teams adapter using the microsoft-teams-apps SDK.`m  rb   r   c                   t                                          |t          d                     |j        pi }|                    d          pt          j        dd          | _        |                    d          pt          j        dd          | _        |                    d          pt          j        dd          | _	        t          |                    d	          p&t          j        d
t          t                                        | _        d | _        d | _        t!          d          | _        i | _        d S )Nteamsr  r  rD   r
  r	  r  r  r   r  i  )max_size)superrG   r   r   rr   r   r   
_client_id_client_secret
_tenant_idr>   r2   _DEFAULT_PORT_port_app_runnerr   _dedup
_conv_refs)rF   rb   r   	__class__s      r6   rG   zTeamsAdapter.__init__s  s   '!2!2333"))K00TBI>OQS4T4T#ii88`BIF[]_<`<`))K00TBI>OQS4T4T!IIfL<]9K9K!L!L
 

 &*	26)4888 +-r8   r'   r&   c           
     x   K   t           s                     ddd           dS t          s                     ddd           dS  j        r j        r j        s                     ddd           dS 	 t          j                    }|j        	                    dd	            t           j         j         j        t          |          t          d
di                     _         j        j        d fd            } j        j        d fd            } j                                         d {V  t          j        |           _         j                                         d {V  t          j         j        d j                  }|                                 d {V  d _                                          t2                              d j        t6                     dS # t8          $ rA}                     dd| d           t2                              d|           Y d }~dS d }~ww xY w)NMISSING_SDKzImicrosoft-teams-apps not installed. Run: pip install microsoft-teams-appsF)	retryablez/aiohttp not installed. Run: pip install aiohttpMISSING_CREDENTIALSzJTEAMS_CLIENT_ID, TEAMS_CLIENT_SECRET, and TEAMS_TENANT_ID are all requiredz/healthc                ,    t          j        d          S )Nok)r   )r   r   )_s    r6   <lambda>z&TeamsAdapter.connect.<locals>.<lambda>  s    CLd<S<S<S r8   z
User-AgentHermes)r   )r  r
  r  http_server_adapterr   ctx ActivityContext[MessageActivity]c                B   K                        |            d {V  d S r;   )_on_messageru  rF   s    r6   _handle_messagez-TeamsAdapter.connect.<locals>._handle_message  s3      &&s+++++++++++r8   +ActivityContext[AdaptiveCardInvokeActivity]r'   1InvokeResponse[AdaptiveCardActionMessageResponse]c                >   K                        |            d {V S r;   )_on_card_actionry  s    r6   _handle_card_actionz1TeamsAdapter.connect.<locals>._handle_card_action  s/       "11#666666666r8   z0.0.0.0Tz0[teams] Webhook server listening on 0.0.0.0:%d%sCONNECT_FAILEDzTeams connection failed: z[teams] Failed to connect: %s)ru  rv  )ru  r{  r'   r|  )r  _set_fatal_errorr  ra  rb  rc  r   Applicationr   add_getr	   r   r   rf  
on_messageon_card_action
initialize	AppRunnerrg  setupTCPSitere  r   _running_mark_connectedrJ  info_WEBHOOK_PATHr#  r1  )rF   r   rz  r  siterX  s   `     r6   connectzTeamsAdapter.connect  s     " 	!![ "   
 5  	!!A "   
 5 	d&9 	 	!!%\ "   
 51	/++K&&y2S2STTT/"1/$9+$F$F$lH-EFFF  DI Y!, , , , , "!, Y%7 7 7 7 7 &%7 )&&(((((((((=55DL,$$&&&&&&&&&;t|Y
CCD**,, DM  """KKB
  
 4 	 	 	!! /A// "   
 LL8!<<<55555	s   6E6G. .
H986H44H9rK   c                   K   d| _         | j        r&| j                                         d {V  d | _        d | _        |                                  t
                              d           d S )NFz[teams] Disconnected)r  rg  cleanuprf  _mark_disconnectedrJ  r  rN   s    r6   
disconnectzTeamsAdapter.disconnect  sv      < 	 ,&&(((((((((DL	!!!*+++++r8   ru  rv  c                  K   |j         }| j        r| j        j        nd}|rt          |j        dd          |k    rdS t          |dd          }|r| j                            |          rdS t          |j        dd          }|r|j        | j	        |<   d}t          |d          r|j        r|j        }d|v r-ddl}|                    dd|                                          }|j        }t          |dd          pd}	|	d	k    rd
}
n|	dk    rd}
n|	dk    rd}
nd
}
|j        }t          |dd          pt          |dd          }t          |dd          pd}|                     |j        t          |dd          pd|
t!          |          |t          |dd          p| j                  }g }g }t          |dd          pg D ]}t          |dd          }t          |dd          pd}|r|                    d          ru	 t'          |           d{V }|r*|                    |           |                    |           # t*          $ r%}t,                              d|           Y d}~d}~ww xY w|rt0          j        nt0          j        }t7          ||||||          }|                     |           d{V  dS )z>Process an incoming Teams message and dispatch to the gateway.Nr   rD   r   z<at>r   z<at>[^<]*</at>\s*conversation_typepersonaldm	groupChatgroupr   aad_object_idr  r  )rm   	chat_name	chat_typeuser_id	user_nameguild_idattachmentscontent_urlr   zimage/z,[teams] Failed to cache image attachment: %s)r   sourcemessage_type
media_urlsmedia_typesr   )rU  rf  r   r   from_rh  is_duplicateconversationconversation_refri  hasattrr   resubr3   build_sourcer2   rc  
startswithr"   r   r#  rJ  warningr    PHOTOTEXTr   handle_message)rF   ru  rU  bot_idmsg_idconv_idr   r  conv	conv_typer  from_accountr  r  r  r  r  attr  r   cachedrX  msg_typeevents                           r6   rx  zTeamsAdapter._on_message  s~     < "&4 	ghndD99VCCF 4.. 	dk..v66 	F (/t<< 	<'*';DOG$ 8V$$ 	! 	!=DT>>III66.D99??AAD $D"5t<<B	
""II+%%II)##!III  ~,>>a',X\^`BaBaL&$77=2	""GdFD117RLLT;55H # 
 
 
8]D99?R 
	V 
	VC!#}d;;K"3==CL V|66x@@ VV#7#D#DDDDDDDF 9"))&111#**<888  V V VNN#QSTUUUUUUUUV )3H;$$8H!!#
 
 
 !!%(((((((((((s    AI
I1I,,I1rm   r2   card'AdaptiveCard''Any'c                2  K   ddl m} | j                            |          }|rJ| j        rC |                                |          }| j        j                            ||           d{V S | j        r!| j                            ||           d{V S dS )zJSend an AdaptiveCard, using a stored ConversationReference when available.r   )MessageActivityInputN)microsoft_teams.apir  ri  rr   rf  add_cardactivity_sendersend)rF   rm   r  r  conv_refrU  s         r6   
_send_cardzTeamsAdapter._send_card)  s      <<<<<<?&&w// 	7	 	7++--66t<<H277(KKKKKKKKKY 	7666666666tr8   -'ActivityContext[AdaptiveCardInvokeActivity]'3'InvokeResponse[AdaptiveCardActionMessageResponse]'c                  K   ddl m}m} |j        j        j        }|j        pi }|                    dd          }|                    dd          }|r|st          dt          d          	          S t          j        d
d                                          }t          j        dd                                                                          dv }	|	s|s9t                              d           t          dt          d          	          S |j        j        }
t#          |
dd          pt#          |
dd          }d |                    d          D             }d|vr>||vr:t                              d|           t          dt          d          	          S ddddd}|                    |          }|st          dt          d          	          S  ||          sat          dt'          t)                                          d                              t/          dd          g                    	          S  |||           d d!d"d#d$}|                    d%d          }|                    d&d          }g }|rM|                    t/          d'dd()                     |                    t/          d*| d+d                     |r(|                    t/          d,| dd-                     |                    t/          ||         dd()                     t          dt'          t)                                          d                              |                    	          S ).z4Handle an Adaptive Card Action.Execute button click.r   )resolve_gateway_approvalhas_blocking_approvalhermes_actionrD   session_keyr   zUnknown action.)r%   )r   r   TEAMS_ALLOWED_USERSTEAMS_ALLOW_ALL_USERS>   r)   r+   r,   us   [teams] card action rejected: TEAMS_ALLOWED_USERS not configured and TEAMS_ALLOW_ALL_USERS not set — default denyuB   ⛔ Approval buttons require TEAMS_ALLOWED_USERS to be configured.r  Nr   c                ^    h | ]*}|                                 |                                 +S rM   )r3   )r   uids     r6   	<setcomp>z/TeamsAdapter._on_card_action.<locals>.<setcomp>\  s-    XXX3CIIKKX399;;XXXr8   ,*u3   [teams] Unauthorized card action by %s — ignoringu   ⛔ Not authorized.oncerR  alwaysdeny)approve_onceapprove_sessionapprove_alwaysr  1.4u,   ⚠️ Approval already resolved or expired.Tr   wrapu   ✅ Allowed (once)u   ✅ Allowed (session)u   ✅ Always allowedu
   ❌ Denied)r  rR  r  r  cmddesc    ⚠️ Command Approval RequiredBolderr   r  weight```

```Reason: r   r  isSubtle)tools.approvalr  r  rU  r%   actionr8  rr   r   r   r   r   r3   r4   rJ  r  r  r   splitr   r   with_version	with_bodyr   r   )rF   ru  r  r  r  r8  r  r  allowed_csv	allow_allr  
clicker_idallowed_ids
choice_mapchoice	label_mapr  r  r   s                      r6   r~  zTeamsAdapter._on_card_action5  s      	SRRRRRRR#*{ b"55hh}b11 	K 	!6=NOOO    i 5r::@@BBI5r::@@BBHHJJNbb	 	 
I   &:b      <-L EEhQ]_cegIhIhJXX+2C2CC2H2HXXXK+%%*K*G*GTV`aaa%:AVWWW    #(&	
 

 .. 	!6=NOOO   
 %$[11 	!3&..!\%((Y	/]dh i i ijkk      	! f555 ).* 	
 
	 hhub!!xx## 	GKK	'IPT]efffgggKK	'9s'9'9'9EEEFFF 	UKK	'8$'8'8tdSSSTTTI9V#44QQQRRR/"nn11%88BB4HH  
 
 
 	
r8   dangerous commandNcommandr  descriptionmetadataOptional[Dict[str, Any]]r!   c                  K   | j         st          dd          S t          |          dk    r|dd         dz   n|}|t          |          dk    r|dd         dz   n||d}t                                          d	                              t          d
dd          t          d| dd          t          d| dd          g                              t          ddi |ddid          t          ddi |ddi          t          ddi |ddi          t          ddi |ddid           g          }	 | 	                    ||           d{V }	|	rt          |	d!d          nd}
t          d|
"          S # t          $ rF}t                              d#|d$           t          dt          |          d%          cY d}~S d}~ww xY w)&z>Send an Adaptive Card approval prompt with Allow/Deny buttons.FTeams app not initializedr>  r1  i  Nz...r   )r  r  r  r  r  Tr  r  r  r  r  r  r  z
Allow Oncehermes_approver  r  positive)r   verbr8  stylezAllow Sessionr  )r   r   r8  zAlways Allowr  Denyr  destructiver   r=  z%[teams] send_exec_approval failed: %sr?  r>  r1  rm  )rf  r!   r   r   r  r  r   with_actionsr   r  r   r#  rJ  r1  r2   )rF   rm   r  r  r  r  cmd_previewbtn_data_baser  r   r   rX  s               r6   send_exec_approvalzTeamsAdapter.send_exec_approval  s      y 	Pe3NOOOO03Gt0C0Cgetenu,, ',/LL3,>,>74C4=5((G
 
 NN\%  YAU]^^^9{999EEE7+77dTRRR  
 \&)KMK?NKK$	   ))NMN?<MNN  
 ()MMM?<LMM  
  )CMC?FCC'	  #   	D	K??7D99999999F8>Ht444DJdzBBBB 	K 	K 	KLL@!dLSSSe3q66TJJJJJJJJJ	Ks   AF 
G;G	GGr   reply_tor  c                  K   | j         st          dd          S |                     |          }|                     |          }d }|D ] }	 |r|                                r}|dk    rw	 | j                             |||           d {V }	nt# t          $ rF}
t                              d|
           | j         	                    ||           d {V }	Y d }
~
n)d }
~
ww xY w| j         	                    ||           d {V }	t          |	dd           }# t          $ r+}t          dt          |          d          cY d }~c S d }~ww xY wt          d|	          S )
NFr  r  r-   z3Teams reply() failed, falling back to flat send: %sr   Tr  r=  )rf  r!   format_messagetruncate_messageisdigitreplyr#  rJ  rK  r  r   r2   )rF   rm   r   r	  r  	formattedchunkslast_message_idchunkr   	reply_errrX  s               r6   r  zTeamsAdapter.send  s      y 	Pe3NOOOO''00	&&y11 	O 	OEO B 0 0 2 2 Bx3
F'+yw%'P'P!P!P!P!P!P!P$ F F F Q%   (,y~~gu'E'E!E!E!E!E!E!EF $(9>>'5#A#AAAAAAAF")&$"="= O O O!%s1vvNNNNNNNNNNNO $?CCCCsH   D*"BD
C<CDC5D
EE;EEc                   K   | j         sd S 	 | j                             |t                                 d {V  d S # t          $ r Y d S w xY wr;   )rf  r  r   r#  )rF   rm   r  s      r6   send_typingzTeamsAdapter.send_typing  sq      y 	F	)..*=*?*?@@@@@@@@@@@ 	 	 	DD	s   -< 
A
	A
	image_urlcaptionc                  K   | j         st          dd          S 	 dd l}dd l}ddlm}m}	 |                    d          s|                    d          r|}
d}n|                    d	          }|	                    |          d         pd}t          |d
          5 }d| d|                    |                                                                           }
d d d            n# 1 swxY w Y    |||
          } |	                                |          }|r|                    |          }| j                            |          }|r'| j         j                            ||           d {V }n!| j                             ||           d {V }t          dt)          |dd                     S # t*          $ rF}t,                              d|d           t          dt1          |          d          cY d }~S d }~ww xY w)NFr  r  r   )
Attachmentr  zhttp://zhttps://z	image/pngzfile://rbzdata:z;base64,)r   r  Tr   r=  z[teams] send_image failed: %sr?  r  )rf  r!   base64	mimetypesr  r  r  r  removeprefix
guess_typeopen	b64encodereaddecodeadd_attachmentsadd_textri  rr   r  r  r   r#  rJ  r1  r2   )rF   rm   r  r  r	  r  r  r  r  r  r  	mime_typer   f
attachmentrU  r  r   rX  s                      r6   
send_imagezTeamsAdapter.send_image  s      y 	Pe3NOOOO	KMMMLLLLLLLL##I.. c)2F2Fz2R2R c''		 !--i88%0066q9H[	$%% c"b)"b"bV=M=Maffhh=W=W=^=^=`=`"b"bKc c c c c c c c c c c c c c c $TTTJ++--==jIIH 6#,,W55**733H A#y8==hQQQQQQQQ#y~~gx@@@@@@@@dwvtT7R7RSSSS 	K 	K 	KLL8!dLKKKe3q66TJJJJJJJJJ	KsD   BF9 A C)F9 )C--F9 0C-1CF9 9
H	;H>H	H	
image_pathc                D   K   |                      ||||           d {V S )N)rm   r  r  r	  )r(  )rF   rm   r)  r  r	  kwargss         r6   send_image_filezTeamsAdapter.send_image_file(  sM       __ 	 % 
 
 
 
 
 
 
 
 	
r8   rs   c                   K   |d|dS )Nunknown)r  r:  rm   rM   )rF   rm   s     r6   get_chat_infozTeamsAdapter.get_chat_info7  s      wGGGr8   )rb   r   r'   r&   rP   )ru  rv  r'   rK   )rm   r2   r  r  r'   r  )ru  r  r'   r  )r  N)rm   r2   r  r2   r  r2   r  r2   r  r  r'   r!   )NN)
rm   r2   r   r2   r	  r  r  r  r'   r!   r;   )rm   r2   r  r  r'   rK   )NNN)rm   r2   r  r2   r  r  r	  r  r  r  r'   r!   )
rm   r2   r)  r2   r  r  r	  r  r'   r!   )rm   r2   r'   rs   )rQ   rR   rS   rT   MAX_MESSAGE_LENGTHrG   r  r  rx  r  r~  r  r  r  r(  r,  r/  __classcell__)rj  s   @r6   r[  r[  n  s       EE- - - - - - J J J JX, , , ,O) O) O) O)b
 
 
 
]
 ]
 ]
 ]
H /-1<K <K <K <K <KD #'-1"D "D "D "D "DH     "&"&-1(K (K (K (K (K\ "&"&
 
 
 
 
H H H H H H H Hr8   r[  rK   c                 ~   ddl m} m} ddlm}m}m}m}m}  | d          }|r |d| d            |dd          sd	S  |d
            |d            |d           t                        |d            |d            |d           t                        |d           t                        |d|pd          }|s |d           d	S  |d|
                                            |d | d          pdd          }	|	s |d           d	S  |d|	
                                            |d | d          pd          }
|
s |d           d	S  |d|

                                           t                        |d            |dd          rS |d | d           pd          }|r, |d |                    d!d                      |d"           n$ |d d           n |d#d$            |d%           t                        |d&            |d'            |d(           d	S ))z7Guide the user through Teams setup using the Teams CLI.r   )get_env_valuesave_env_value)promptprompt_yes_no
print_infoprint_successprint_warningr  z#Teams: already configured (app ID: )zReconfigure Teams?FNz2You'll need the Teams CLI. If you haven't already:z-  npm install -g @microsoft/teams.cli@previewz  teams loginzAThen expose port 3978 publicly (devtunnel / ngrok / cloudflared),zand create your bot:zM  teams app create --name "Hermes" --endpoint "https://<tunnel>/api/messages"zMThe CLI will print CLIENT_ID, CLIENT_SECRET, and TENANT_ID. Paste them below.z	Client IDrD   r#   u.   Client ID is required — skipping Teams setupzClient secretr	  T)r$   passwordu2   Client secret is required — skipping Teams setupz	Tenant IDr  u.   Tenant ID is required — skipping Teams setupzDTo find your AAD object ID for the allowlist: teams status --verbosez0Restrict access to specific users? (recommended)z(Allowed AAD object IDs (comma-separated)r   zAllowlist configuredr  r,   uF   ⚠️  Open access — anyone who can message the bot can command it.z+Teams configuration saved to ~/.hermes/.envz>Install the app in Teams:  teams app install --id <teamsAppId>z1Restart the gateway:       hermes gateway restart)hermes_cli.configr4  r5  hermes_cli.cli_outputr6  r7  r8  r9  r:  printr3   replace)r4  r5  r6  r7  r8  r9  r:  existing_idr  r
  r  alloweds               r6   interactive_setuprD  =  s                         - 122K 
GGGGHHH}1599 	FJCDDDJ>???J	GGGJRSSSJ%&&&Jbccc	GGGJ^___	GGG{K,=2>>>I FGGGN$ioo&7&7888F?MMBW4X4X4^\^imnnnM JKKKN(-*=*=*?*?@@@{MM:K,L,L,RPRSSSI FGGGN$ioo&7&7888	GGGJUVVV}GNN `&6!M"788>B
 
 
  	6N0'//#r2J2JKKKM01111N0"5555.777^___	GGGM?@@@JOPPPJBCCCCCr8   c                    |                      ddd t          t          t          g ddt          t
          dt          ddd	d
dd           dS )u:   Plugin entry point — called by the Hermes plugin system.r^  zMicrosoft Teamsc                     t          |           S r;   )r[  )cfgs    r6   rr  zregister.<locals>.<lambda>  s    L$5$5 r8   )r  r	  r  z(pip install microsoft-teams-apps aiohttpr  r  r  r\  u   💼Tu   You are chatting via Microsoft Teams. Teams renders a subset of markdown — bold (**text**), italic (*text*), and inline code (`code`) work, but complex tables or raw HTML do not. Keep responses clear and professional.)r  labeladapter_factorycheck_fnr  r  required_envinstall_hintsetup_fnenv_enablement_fncron_deliver_env_varstandalone_sender_fnallowed_users_envallow_all_envmax_message_lengthemojiallow_update_commandplatform_hintN)register_platformr  r  r  rD  r  rY  )ru  s    r6   registerrX    sq    55#'!RRR?" * 2 ./- !0A  % % % % %r8   )r%   r   r$   r&   r'   r&   )r%   r   r$   r9   r'   r9   r0  )r'   r  )r  r2   r'   r  )rm   r2   r-  r2   r*  r  r+  r.  r,  r&   r'   r/  rP   )UrT   
__future__r   rH  r   r   loggingr   typingr   r   r   r"  r   rE  r   r  ImportErrormicrosoft_teams.appsr	   r
   "microsoft_teams.common.http.clientr   r  r   r   %microsoft_teams.api.activities.typingr   3microsoft_teams.api.activities.invoke.adaptive_cardr   (microsoft_teams.api.models.adaptive_cardr   r   *microsoft_teams.api.models.invoke_responser   r   !microsoft_teams.apps.http.adapterr   r   r   r   microsoft_teams.cardsr   r   r   r  r2   gateway.configr   r   gateway.platforms.helpersr   gateway.platforms.baser   r   r    r!   r"   	getLoggerrQ   rJ  rd  r  r7   r>   r@   rV   r   r  r  r  r  rA  	frozensetr&  r  	_re_teamscompilerC  r)  rY  check_teams_requirementsr[  rD  rX  rM   r8   r6   <module>rm     s   , # " " " " "     				 & & & & & & & & & &         
CCC'99999999@@@@@@JJJJJJJJIIIIII^^^^^^        feeeeeee            MLLLLLLLLL   M
COO !%%)"(,%!%NJKLLMIII'* 4 3 3 3 3 3 3 3 9 9 9 9 9 9              
	8	$	$ 05 	 	 	 	 	 	 0=              P= P= P= P= P= P= P= P=f% % % % % % % %P5 5 5 5
; ; ; ;# # # #
$ $ $ $V F 
  )y'*         %I%&=>>    8  $"& w? w? w? w? w? w?v . JH JH JH JH JH& JH JH JH^DD DD DD DDR' ' ' ' ' 's"   7 	AAA
B +C ?C 