
    +V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d?dZ? G d d          Z@ G d  d!          ZA G d" d#          ZBd@d$ZCd@d%ZDd@d&ZEdAd(ZFd)ZG eHd*d+h          ZIddlJZK eKjL        d,          ZMdBd0ZNdddd1dCd9ZOeCZP G d: d;e5          ZQdDd=ZRdDd>ZSdS )Ea  
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      =/root/.hermes/hermes-agent/plugins/platforms/teams/adapter.py_parse_boolr7   k   sk    % % [[]]((**
33344445N    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)selfr;   s     r6   __init__z#_StaticAccessTokenProvider.__init__z   s)     !344::<<r8   F)force_refreshrB   r&   r'   c               B   K   ~| j         st          d          | j         S )Nz=TEAMS_GRAPH_ACCESS_TOKEN is required for graph delivery mode.)r?   
ValueError)r@   rB   s     r6   get_access_tokenz+_StaticAccessTokenProvider.get_access_token}   s-      ! 	^\]]]!!r8   Nonec                    d S N r@   s    r6   clear_cachez&_StaticAccessTokenProvider.clear_cache   s    tr8   N)r;   r2   )rB   r&   r'   r2   r'   rF   )__name__
__module____qualname____doc__rA   rE   rK   rI   r8   r6   r:   r:   w   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 | NonerS   
Any | NonerT   httpx.AsyncBaseTransport | Noner'   rF   c               0    || _         || _        || _        d S rH   )_platform_config_graph_client
_transport)r@   rU   rS   rT   s       r6   rA   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moder>   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_graphrD   )r@   r]   r^   r`   mergedrf   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 )Nr;   rk   TEAMS_DELIVERY_MODEr>   TEAMS_INCOMING_WEBHOOK_URLTEAMS_GRAPH_ACCESS_TOKENTEAMS_TEAM_IDTEAMS_CHANNEL_IDTEAMS_CHAT_ID)re   rg   r;   rj   rk   ri   )rZ   updatero   extratokenhome_channel
setdefaultri   osgetenvitemsrn   )r@   r^   rr   platform_cfgenv_defaultskeyr%   s          r6   rm   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   rg   r>   zATEAMS_INCOMING_WEBHOOK_URL is required for incoming_webhook mode.textg      4@)timeoutrT   )jsonrh   T)re   webhook_urlstatus_code	delivered)httpxr2   rn   r3   rD   _render_summary_markdownAsyncClientr\   postraise_for_statusr   )r@   r]   r^   r   r   bodyclientresponses           r6   rp   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 )Nri   r>   z/chats/)safez	/messagesr   html)contentTypecontent)	json_bodyrl   chatidwebUrl)re   target_typeri   
message_idweb_urlrj   rk   zEGraph delivery mode requires chat_id, or both team_id and channel_id.z/teams/z
/channels/channel)re   r   rj   rk   r   r   )_build_graph_clientr2   rn   r3   r   	post_json_render_summary_htmlrD   )	r@   r]   r^   rS   ri   pathr   rj   rk   s	            r6   rq   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)MicrosoftGraphClientr;   r>   )rT   )r[   tools.microsoft_graph_authr   tools.microsoft_graph_clientr   r2   rn   r3   r:   r\   from_env)r@   r^   r   r   r;   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**r>   z	Summary: summaryNo summary available.zKey decisions:key_decisionszAction items:action_itemszRisks:risks
)_title_textgetattr_bullet_linesjoin)r@   r]   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>r>   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>@  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   )r@   r]   sectionsblocksheadingr   rendereds          r6   r   z'TeamsSummaryWriter._render_summary_html2  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   )r]   r   r   r   s       r6   r   zTeamsSummaryWriter._titleF  sg    $// 	u::g}d;;ALVW[,===RV
3*1	333r8   r%   r$   c                P    t          | pd                                          }|p|S r=   r2   r3   )r%   r$   r   s      r6   r   zTeamsSummaryWriter._textO  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 rI   r   r   s     r6   
<listcomp>z4TeamsSummaryWriter._bullet_lines.<locals>.<listcomp>V  s=    SSStTARARST""SSSr8   c                    g | ]}d | S )z- rI   r   s     r6   r   z4TeamsSummaryWriter._bullet_lines.<locals>.<listcomp>W  s    ...T...r8   z- NonerI   )clsr   r   s      r6   r   z TeamsSummaryWriter._bullet_linesT  s4    SS"SSS.....<8*<r8   rH   )rU   rV   rS   rW   rT   rX   r'   rF   )r]   r   r^   r_   r`   ra   r'   rb   )r^   r_   r'   rb   )r]   r   r^   rb   r'   rb   )r^   rb   r'   r   )r]   r   r'   r2   )r%   r   r$   r2   r'   r2   )r   r   r'   r   )rM   rN   rO   rP   rA   rs   rm   rp   rq   r   r   r   staticmethodr   r   classmethodr   rI   r8   r6   rR   rR      sE         26	$ $(59	$ 	$ 	$ 	$ 	$ 	$ 59	
 
 
 
 
4   0
 
 
 
.*
 *
 *
 *
X
 
 
 
$       "   ( 4 4 4 \4    \ = = = [= = =r8   rR   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 rH   )_aiohttp_app)r@   r   s     r6   rA   z_AiohttpBridgeAdapter.__init__b  s    'r8   method'HttpMethod'r   r2   handler'HttpRouteHandler'r'   rF   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   ro   r   r   rn   r   Responsedumps)r   r   r   resultr   	resp_bodyr   s         r6   _aiohttp_handlerz>_AiohttpBridgeAdapter.register_route.<locals>._aiohttp_handlerh  s       ''''''D7?++G+27;DRY3Z3Z3Z+[+[%[%[%[%[%[%[FZZ#..F

6**I$|!I..!3   
 <v....r8   N)r   r   r'   r   )r   router	add_route)r@   r   r   r   r   s      ` r6   register_routez$_AiohttpBridgeAdapter.register_routee  sH    	/ 	/ 	/ 	/ 	/ 	/ 	 **649IJJJJJr8   	directoryc                    d S rH   rI   )r@   r   r   s      r6   serve_staticz"_AiohttpBridgeAdapter.serve_staticx  s    r8   portintc                $   K   t          d          )Nz(aiohttp server is managed by the adapter)NotImplementedError)r@   r   s     r6   startz_AiohttpBridgeAdapter.start{  s      !"LMMMr8   c                
   K   d S rH   rI   rJ   s    r6   stopz_AiohttpBridgeAdapter.stop~  s      r8   N)r   r   )r   r   r   r2   r   r   r'   rF   )r   r2   r   r2   r'   rF   )r   r   r'   rF   rL   )	rM   rN   rO   rP   rA   r   r   r   r   rI   r8   r6   r   r   Z  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_AVAILABLErI   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_idr>   TEAMS_CLIENT_SECRETclient_secretTEAMS_TENANT_ID	tenant_id)r   r   r   rn   r&   )r^   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
  )r^   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  r>   r  r  N)r  r  r	  
TEAMS_PORTr   TEAMS_SERVICE_URLservice_urlTEAMS_HOME_CHANNELTEAMS_HOME_CHANNEL_NAMEHome)ri   namer~   )r   r   r3   r   rD   )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_documentri   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          }|                                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.|                    d/          d0S # t$          j        $ r  t(          $ r-}t*                              d1d.2           d	d3| icY d}~S d}~ww xY w)4a  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  r>   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      .@)total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,  r;   z:Teams standalone send: token response missing access_tokenr*  markdown)typer   
textFormatzBearer r   )Authorizationr3  )r   r   r   z-Teams standalone send: activity post failed (Tr   successr   zTeams standalone send raisedexc_infozTeams standalone send failed: )r   r   r   rn   _DEFAULT_TEAMS_SERVICE_URLr&  sortedr#  _TEAMS_CONV_ID_REmatchr  aiohttpClientTimeoutClientSessionr   r   r   r   asyncioCancelledErrorr   loggerdebug)pconfigri   r*  r'  r(  r)  r|   r  r  r	  raw_service_urlr  	token_urlactivities_url_aiohttpper_request_timeoutsession
token_respr   token_payloadr;   activity	send_respsend_payloades                            r6   _standalone_sendrU    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@@))++ %	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   4N 	'M1<H>-M?N H>,M>
I	MI	M)N <+M(<L5$M6N 	L5#M5
L?	?ML?	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  r^   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  r>   r  r  r	  r  r   r  i  )max_size)superrA   r   r|   rn   r   r   
_client_id_client_secret
_tenant_idr   r2   _DEFAULT_PORT_port_app_runnerr   _dedup
_conv_refs)r@   r^   r|   	__class__s      r6   rA   zTeamsAdapter.__init__l  s   '!2!2333"))K00TBI>OQS4T4T#ii88`BIF[]_<`<`))K00TBI>OQS4T4T6**Ybic-FXFX.Y.YZZ
%)	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 rH   )_on_messagerq  r@   s    r6   _handle_messagez-TeamsAdapter.connect.<locals>._handle_message  s3      &&s+++++++++++r8   +ActivityContext[AdaptiveCardInvokeActivity]r'   1InvokeResponse[AdaptiveCardActionMessageResponse]c                >   K                        |            d {V S rH   )_on_card_actionru  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)rq  rr  )rq  rw  r'   rx  )r   _set_fatal_errorr  r]  r^  r_  r   Applicationr   add_getr	   r   r   rb  
on_messageon_card_action
initialize	AppRunnerrc  setupTCPSitera  r   _running_mark_connectedrF  info_WEBHOOK_PATHr   r.  )r@   r   rv  r{  siterT  s   `     r6   connectzTeamsAdapter.connectz  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rF   c                   K   d| _         | j        r&| j                                         d {V  d | _        d | _        |                                  t
                              d           d S )NFz[teams] Disconnected)r  rc  cleanuprb  _mark_disconnectedrF  r  rJ   s    r6   
disconnectzTeamsAdapter.disconnect  sv      < 	 ,&&(((((((((DL	!!!*+++++r8   rq  rr  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   r>   r   z<at>r   z<at>[^<]*</at>\s*conversation_typepersonaldm	groupChatgroupr   aad_object_idr  r	  )ri   	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   )rQ  rb  r   r   from_rd  is_duplicateconversationconversation_refre  hasattrr   resubr3   build_sourcer2   r_  
startswithr"   r   r   rF  warningr    PHOTOTEXTr   handle_message)r@   rq  rQ  bot_idmsg_idconv_idr   r  conv	conv_typer  from_accountr  r  r  r  r  attr  r   cachedrT  msg_typeevents                           r6   rt  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ri   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  re  rn   rb  add_cardactivity_sendersend)r@   ri   r  r  conv_refrQ  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_actionr>   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 rI   )r3   )r   uids     r6   	<setcomp>z/TeamsAdapter._on_card_action.<locals>.<setcomp>S  s-    XXX3CIIKKX399;;XXXr8   ,*u3   [teams] Unauthorized card action by %s — ignoringu   ⛔ Not authorized.oncerN  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  rN  r  r  cmddesc    ⚠️ Command Approval RequiredBolderr   r  weight```

```Reason: r   r  isSubtle)tools.approvalr  r  rQ  r%   actionr4  rn   r   r   r   r   r3   r4   rF  r  r  r   splitr   r   with_version	with_bodyr   r   )r@   rq  r  r  r  r4  r  r  allowed_csv	allow_allr  
clicker_idallowed_ids
choice_mapchoice	label_mapr  r  r   s                      r6   rz  zTeamsAdapter._on_card_action,  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:  r.  i  Nz...r   )r  r  r  r  r  Tr  r  r  r  r  r  r  z
Allow Oncehermes_approver  r  positive)r   verbr4  stylezAllow Sessionr  )r   r  r4  zAlways Allowr  Denyr  destructiver   r9  z%[teams] send_exec_approval failed: %sr;  r:  r.  ri  )rb  r!   r   r   r  r  r   with_actionsr   r  r   r   rF  r.  r2   )r@   ri   r  r  r  r  cmd_previewbtn_data_baser  r   r   rT  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   r9  )rb  r!   format_messagetruncate_messageisdigitreplyr   rF  rG  r  r   r2   )r@   ri   r   r  r  	formattedchunkslast_message_idchunkr   	reply_errrT  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rH   )rb  r  r   r   )r@   ri   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   r9  z[teams] send_image failed: %sr;  r   )rb  r!   base64	mimetypesr  r  r  r  removeprefix
guess_typeopen	b64encodereaddecodeadd_attachmentsadd_textre  rn   r  r  r   r   rF  r.  r2   )r@   ri   r  r  r  r  r  r  r  r  r  	mime_typer   f
attachmentrQ  r  r   rT  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)ri   r  r  r  )r$  )r@   ri   r%  r  r  kwargss         r6   send_image_filezTeamsAdapter.send_image_file  sM       __ 	 % 
 
 
 
 
 
 
 
 	
r8   ro   c                   K   |d|dS )Nunknown)r  r6  ri   rI   )r@   ri   s     r6   get_chat_infozTeamsAdapter.get_chat_info.  s      wGGGr8   )r^   r   r'   r&   rL   )rq  rr  r'   rF   )ri   r2   r  r  r'   r  )rq  r  r'   r  )r  N)ri   r2   r  r2   r  r2   r  r2   r  r  r'   r!   )NN)
ri   r2   r   r2   r  r  r  r  r'   r!   rH   )ri   r2   r  r  r'   rF   )NNN)ri   r2   r  r2   r  r  r  r  r  r  r'   r!   )
ri   r2   r%  r2   r  r  r  r  r'   r!   )ri   r2   r'   ro   )rM   rN   rO   rP   MAX_MESSAGE_LENGTHrA   r  r  rt  r  rz  r  r  r  r$  r(  r+  __classcell__)rf  s   @r6   rW  rW  g  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   rW  rF   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 IDr>   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.configr0  r1  hermes_cli.cli_outputr2  r3  r4  r5  r6  printr3   replace)r0  r1  r2  r3  r4  r5  r6  existing_idr  r  r	  alloweds               r6   interactive_setupr@  4  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.rZ  zMicrosoft Teamsc                     t          |           S rH   )rW  )cfgs    r6   rn  zregister.<locals>.<lambda>  s    L$5$5 r8   )r  r  r  z(pip install microsoft-teams-apps aiohttpr  r  r  rX  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  r@  r  rU  )rq  s    r6   registerrT  }  sq    55#'!RRR?" * 2 ./- !0A  % % % % %r8   )r%   r   r$   r&   r'   r&   r,  )r'   r  )r  r2   r'   r  )ri   r2   r*  r2   r'  r  r(  r+  r)  r&   r'   r,  rL   )TrP   
__future__r   rD  r   r   loggingr   typingr   r   r   r  r   rA  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"   	getLoggerrM   rF  r`  r  r7   r:   rR   r   r  r
  r  r  r=  	frozensetr#  r  	_re_teamscompiler?  r&  rU  check_teams_requirementsrW  r@  rT  rI   r8   r6   <module>ri     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 	 	 	 	 	 	        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 . HH HH HH HH HH& HH HH HHZDD DD DD DDR' ' ' ' ' 's"   7 	AAA
B +C ?C 