§
    ÛŽ’j!l  ã                  ó   — 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mZm	Z	 ddl
ZddlmZ ddl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 erddlmZmZ  G d„ de¦  «        Z G d„ de¦  «        Z  G d„ d¦  «        Z!dd„Z"dS )uÿ  Host-side client for the in-sandbox MCP gateway, driven through
``backend.exec_shell``.

Three classes:

* :class:`GatewayClient` â€” workspace-side facade over ``/health`` and
  ``/mcps``. Used by the sandboxed workspaces for top-level operations.
* :class:`GatewayMCPClient` â€” an :class:`MCPClient` subclass whose
  protocol behaviour is replaced by gateway-relayed calls. Local
  stdio/HTTP machinery is bypassed; ``model_post_init`` is a no-op;
  ``connect`` / ``close`` / ``list_raw_tools`` / ``get_tool`` all
  round-trip through ``/mcps``.
* :class:`GatewayMCPTool` â€” :class:`ToolBase` whose ``__call__``
  invokes the upstream tool via ``POST /mcps/{name}/tools/{tool}``.

Every request runs inside the sandbox: the host writes an optional
body to a sandbox tempfile, spawns a small Python shim via
:meth:`BackendBase.exec_shell` (see :mod:`._gateway_shim`), and parses
the JSON envelope the shim prints on stdout. No hostâ†’sandbox network
reachability is required â€” the gateway only binds sandbox loopback.
é    )ÚannotationsN)ÚTYPE_CHECKINGÚAny)ÚPrivateAttré   )Úlogger)Ú	MCPClient)ÚToolResultState)ÚPermissionBehaviorÚPermissionDecision)ÚToolBaseÚ	ToolChunké   )ÚBODY_INLINE_LIMITÚSANDBOX_TMP_DIRÚSHIM_SCRIPT)ÚBackendBaseÚ
ExecResultc                  óH   — e Zd ZU dZdZded<   dZded<   dd„Zdd„Zdd„Z	dS )ÚGatewayMCPToolu  An MCP tool whose ``__call__`` is one ``exec_shell``-driven
    request to the gateway.

    Mirrors :class:`agentscope.tool.MCPTool` field-by-field so the
    toolkit treats it identically (same ``name`` format, same
    permission policy) â€” only the call path changes.
    TÚboolÚis_mcpFÚis_state_injectedÚmcp_nameÚstrÚtoolúmcp.types.ToolÚgatewayú'GatewayClient'ÚreturnÚNonec                óÄ  — || _         d|› d|j        › �| _        |j        pd| _        |j        rt	          |j        ¦  «        ni }|                     dd¦  «         |                     di ¦  «         |                     dg ¦  «         || _        d| _        d| _        d| _	        |j
        r(t          |j
        d	¦  «        r|j
        j        pd| _	        || _        || _        d
S )aÜ  Build a gateway-backed MCP tool.

        Mirrors the field surface of :class:`agentscope.tool.MCPTool`
        so the toolkit cannot tell the difference.

        Args:
            mcp_name (`str`):
                Upstream MCP server name; drives the visible
                ``mcp__{mcp}__{tool}`` name and the URL path.
            tool (`mcp.types.Tool`):
                Raw upstream tool descriptor as returned by the gateway.
                ``inputSchema`` is forwarded verbatim;
                ``annotations.readOnlyHint`` drives the permission
                policy.
            gateway (`GatewayClient`):
                Facade dispatching every call through
                :meth:`GatewayClient.exec_request`.
        Úmcp__Ú__Ú ÚtypeÚobjectÚ
propertiesÚrequiredFÚreadOnlyHintN)r   ÚnameÚdescriptionÚinputSchemaÚdictÚ
setdefaultÚinput_schemaÚis_concurrency_safeÚis_external_toolÚis_read_onlyr   Úhasattrr*   Ú_toolÚ_gateway)Úselfr   r   r   Úschemas        úRC:\Projects\DSCons\.venv\Lib\site-packages\agentscope\workspace\_gateway_client.pyÚ__init__zGatewayMCPTool.__init__D   sù   € ð0 !ˆŒØ3˜HÐ3Ð3¨¬	Ð3Ð3ˆŒ	ØÔ+Ð1¨rˆÔà+/Ô+;ÐC•�dÔ&Ñ'Ô'Ð'ÀˆØ×Ò˜& (Ñ+Ô+Ð+Ø×Ò˜,¨Ñ+Ô+Ð+Ø×Ò˜* bÑ)Ô)Ð)Ø"ˆÔà#(ˆÔ Ø %ˆÔà!ˆÔØÔð 	G¥¨Ô(8¸.Ñ IÔ Ið 	GØ $Ô 0Ô =Ð FÀˆDÔàˆŒ
ØˆŒˆˆó    Ú_argsr   Ú_kwargsr   c              �  ó€   K  — | j         rt          t          j        d¬¦  «        S t          t          j        d¬¦  «        S )z{Read-only tools auto-allow; everything else defers via
        ``ASK``. Mirrors :meth:`MCPTool.check_permissions`.
        z1This is a read-only MCP tool. Allowing execution.)ÚbehaviorÚmessagez1MCP tools must be explicitly allowed by the user.)r3   r   r   ÚALLOWÚASK)r7   r<   r=   s      r9   Úcheck_permissionsz GatewayMCPTool.check_permissionsp   sU   è è € ð Ôð 	Ý%Ý+Ô1ØKðñ ô ð õ "Ý'Ô+ØGð
ñ 
ô 
ð 	
r;   Úkwargsr   c              ‹  ó’  K  — | j                              dd| j        › d| j        j        › �d|i¬¦  «        ƒ d{V —†\  }}|dk    r-t          dt          ||¦  «        d	œgt          j        ¬
¦  «        S t          j
        |¦  «        }|                     d¦  «        }|€t          d| j        ›�¦  «        ‚t          j        |¦  «        S )a6  Relay ``POST /mcps/{mcp}/tools/{tool}`` to the gateway.

        4xx / 5xx responses come back as ``ToolChunk(state=ERROR)`` so
        the agent loop can reason about failure. Raises
        :class:`RuntimeError` only if the gateway returns 2xx with no
        ``chunk`` payload (protocol violation).
        ÚPOSTú/mcps/z/tools/Ú	arguments©ÚbodyNé�  Útext)r&   rL   )ÚcontentÚstateÚchunkzgateway returned no chunk for )r6   Úexec_requestr   r5   r+   r   Ú_safe_detailr
   ÚERRORÚjsonÚloadsÚgetÚRuntimeErrorÚmodel_validate)r7   rD   ÚstatusrJ   ÚpayloadÚ
chunk_dicts         r9   Ú__call__zGatewayMCPTool.__call__‚   s  è è € ð "œ]×7Ò7ØØ<�T”]Ð<Ð<¨4¬:¬?Ð<Ð<Ø˜vÐ&ð 8ñ 
ô 
ð 
ð 
ð 
ð 
ð 
ð 
‰ˆ�ð
 �SŠ=ˆ=ÝØ"(µ,¸vÀtÑ2LÔ2LÐMÐMÐNÝ%Ô+ðñ ô ð õ ”*˜TÑ"Ô"ˆØ—[’[ Ñ)Ô)ˆ
ØÐÝØ>°´Ð>Ð>ñô ð õ Ô'¨
Ñ3Ô3Ð3r;   N)r   r   r   r   r   r   r    r!   )r<   r   r=   r   r    r   )rD   r   r    r   )
Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   Ú__annotations__r   r:   rC   r[   © r;   r9   r   r   8   s‚   € € € € € € ðð ð €FÐÐÐÑØ#ÐÐ#Ð#Ð#Ñ#ð* ð * ð * ð * ðX
ð 
ð 
ð 
ð$4ð 4ð 4ð 4ð 4ð 4r;   r   c                  óv   — e Zd ZU dZ ed¬¦  «        Zded<   dd
„Zddœd d„Zd!d„Z	d"d#d„Z
d$d„Zd%d„Zd&d„ZdS )'ÚGatewayMCPClientua  An :class:`MCPClient` whose protocol logic is replaced by
    gateway-relayed calls.

    Constructed from the dict returned by ``GET /mcps`` (or freshly
    from user input via :meth:`GatewayClient.make_client`). The local
    MCP machinery is short-circuited entirely:

    * ``model_post_init`` does nothing (parent's ``_initialize_client``
      is never called â€” no stdio context manager is built).
    * ``connect`` registers the MCP on the gateway via ``POST /mcps``.
    * ``close`` deregisters via ``DELETE /mcps/{name}``.
    * ``list_raw_tools`` / ``get_tool`` fetch and wrap upstream tools.
    N)Údefaultz'GatewayClient | None'r6   Ú_GatewayMCPClient__contextr   r    r!   c                ó   — dS )ux   No-op â€” the parent builds local stdio/HTTP transport, which
        the gateway-relayed client does not need.
        Nra   )r7   re   s     r9   Úmodel_post_initz GatewayMCPClient.model_post_init±   s	   € ð 	ˆr;   F©Ú	connectedr   r   ri   r   c               ó*   — || _         |r	d| _        dS dS )a¯  Wire this client to a gateway facade.

        Instances come out of ``model_validate(spec)`` with the public
        field surface set but no transport wiring. ``attach`` injects
        it so :meth:`connect` / :meth:`close` / :meth:`list_raw_tools`
        can talk to the gateway.

        Args:
            gateway (`GatewayClient`):
                Facade dispatching calls through
                :meth:`GatewayClient.exec_request`.
            connected (`bool`, defaults to `False`):
                When ``True``, mark this client as already connected
                (used by :meth:`GatewayClient.list_mcps` for entries
                the gateway is already serving).
        TN)r6   Ú_is_connected)r7   r   ri   s      r9   ÚattachzGatewayMCPClient.attach¹   s+   € ð,  ˆŒØð 	&Ø!%ˆDÔÐÐð	&ð 	&r;   c           	   ƒ  ó<  K  — | j         rt          d| j        ›d�¦  «        ‚| j        €J ‚|                      d¬¦  «        }| j                             dd|¬¦  «        ƒ d{V —†\  }}|d	k    r(t          d
| j        ›dt          ||¦  «        › �¦  «        ‚d| _         dS )aR  Register this MCP on the gateway via ``POST /mcps``.

        All MCPs (stateless and stateful) must be registered so that
        ``/mcps/{name}/tools/{tool}`` can locate the client.

        Raises:
            `RuntimeError`:
                Already connected, gateway unreachable, or gateway
                returned 4xx/5xx.
        úMCP z8 is already connected. Call close() before reconnecting.NrS   )ÚmoderF   ú/mcpsrI   rK   zgateway failed to add MCP ú: T)rk   rV   r+   r6   Ú
model_dumprP   rQ   )r7   rJ   rX   Ú	resp_bodys       r9   ÚconnectzGatewayMCPClient.connectÓ   sÿ   è è € ð Ôð 	Ýð4�t”yð 4ð 4ð 4ñô ð ð Œ}Ð(Ð(Ð(Ø�Š FˆÑ+Ô+ˆØ"&¤-×"<Ò"<ØØØð #=ñ #
ô #
ð 
ð 
ð 
ð 
ð 
ð 
Ñˆ�	ð
 �SŠ=ˆ=Ýð5¨T¬Yð 5ð 5Ý ¨	Ñ2Ô2ð5ð 5ñô ð ð "ˆÔÐÐr;   TÚignore_errorsc           	   ƒ  óR  K  — | j         s|rdS t          d| j        ›d�¦  «        ‚| j        €J ‚	 | j                             dd| j        › �¦  «        ƒ d{V —†\  }}|dk    r*|s(t          d| j        ›dt          ||¦  «        › �¦  «        ‚n# t          $ r |s‚ Y nw xY wd	| _         dS )
züDeregister this MCP via ``DELETE /mcps/{name}``.

        Args:
            ignore_errors (`bool`, defaults to `True`):
                Suppress "not connected" precondition and gateway
                4xx/5xx. Mirrors :meth:`MCPClient.close`.
        Nrn   z( is not connected. Call connect() first.ÚDELETErG   rK   zgateway failed to remove MCP rq   F)rk   rV   r+   r6   rP   rQ   Ú	Exception)r7   ru   rX   rs   s       r9   ÚclosezGatewayMCPClient.closeñ   s/  è è € ð Ô!ð 	Øð Ø�ÝØL�t”yÐLÐLÐLñô ð ð Œ}Ð(Ð(Ð(ð	Ø&*¤m×&@Ò&@ØØ$˜œÐ$Ð$ñ'ô 'ð !ð !ð !ð !ð !ð !ÑˆF�Ið ˜Š}ˆ} ]ˆ}Ý"ð9°D´Ið 9ð 9Ý# F¨IÑ6Ô6ð9ð 9ñô ð øøõ ð 	ð 	ð 	Ø ð Øðð ð	øøøð #ˆÔÐÐs   °AB ÂBÂBúlist[mcp.types.Tool]c           	   ƒ  óv  ‡ K  — ‰ j         €J ‚‰ j                              dd‰ j        › d�¦  «        ƒ d{V —†\  }}|dk    r(t          d‰ j        ›dt	          ||¦  «        › �¦  «        ‚t          j        |¦  «        }d„ |D ¦   «         }|‰ _        ‰ j        �ˆ fd	„|D ¦   «         }‰ j	        �ˆ fd
„|D ¦   «         }|S )aÊ  Fetch upstream tools via ``GET /mcps/{name}/tools``.

        Returns raw :class:`mcp.types.Tool` descriptors (upstream names,
        no ``mcp__`` prefix) so the inherited :meth:`get_tool` can
        re-wrap them like a local :class:`MCPClient`. The unfiltered
        list is cached; the returned list has ``enable_tools`` /
        ``disable_tools`` applied.

        Raises:
            `RuntimeError`:
                Gateway returned non-2xx.
        NÚGETrG   z/toolsrK   z%gateway failed to list tools for MCP rq   c                óV   — g | ]&}t           j        j                             |¦  «        ‘Œ'S ra   )ÚmcpÚtypesÚToolrW   )Ú.0Úds     r9   ú
<listcomp>z3GatewayMCPClient.list_raw_tools.<locals>.<listcomp>*  s*   € ÐDÐDÐD¸!•S”Y”^×2Ò2°1Ñ5Ô5ÐDÐDÐDr;   c                ó0   •— g | ]}|j         ‰j        v ¯|‘ŒS ra   )r+   Úenable_tools©r�   Útr7   s     €r9   rƒ   z3GatewayMCPClient.list_raw_tools.<locals>.<listcomp>0  s(   ø€ ÐMÐMÐM˜q°´¸4Ô;LÐ1LÐ1L˜Ð1LÐ1LÐ1Lr;   c                ó0   •— g | ]}|j         ‰j        v¯|‘ŒS ra   )r+   Údisable_toolsr†   s     €r9   rƒ   z3GatewayMCPClient.list_raw_tools.<locals>.<listcomp>2  s/   ø€ ð ð ð Ø¨¬°dÔ6HÐ(HÐ(H�Ð(HÐ(HÐ(Hr;   )
r6   rP   r+   rV   rQ   rS   rT   Ú_cached_toolsr…   r‰   )r7   rX   rJ   ÚdataÚ	raw_toolss   `    r9   Úlist_raw_toolszGatewayMCPClient.list_raw_tools  s3  øè è € ð Œ}Ð(Ð(Ð(Ø!œ]×7Ò7ØØ&�T”YÐ&Ð&Ð&ñ
ô 
ð 
ð 
ð 
ð 
ð 
ð 
‰ˆ�ð �SŠ=ˆ=Ýð0¸¼	ð 0ð 0Ý ¨Ñ-Ô-ð0ð 0ñô ð õ Œz˜$ÑÔˆàDÐD¸tÐDÑDÔDˆ	Ø&ˆÔð ÔÐ(ØMÐMÐMÐM IÐMÑMÔMˆIØÔÐ)ðð ð ð Ø$ðñ ô ˆIð Ðr;   r+   r   r   c              ƒ  óÚ   K  — | j         €|                      ¦   «         ƒ d{V —† | j         pg D ]$}|j        |k    r|                      |¦  «        c S Œ%t	          d|›d| j        ›d�¦  «        ‚)u©  Look up a single tool by upstream name and wrap it.

        Falls back to :meth:`list_raw_tools` on cache miss, then
        searches the unfiltered cache so tools that ``enable_tools`` /
        ``disable_tools`` would have hidden are still resolvable â€”
        matching :meth:`MCPClient.get_tool`.

        Raises:
            `ValueError`:
                No tool with that upstream name exists on the gateway.
        NzTool z not found in MCP ú.)rŠ   r�   r+   Ú
_wrap_toolÚ
ValueError)r7   r+   Úraws      r9   Úget_toolzGatewayMCPClient.get_tool7  s    è è € ð ÔÐ%Ø×%Ò%Ñ'Ô'Ð'Ð'Ð'Ð'Ð'Ð'Ð'ØÔ%Ð+¨ð 	,ð 	,ˆCØŒx˜4ÒÐØ—’ sÑ+Ô+Ð+Ð+Ð+ð  åØ<�DÐ<Ð<¨d¬iÐ<Ð<Ð<ñ
ô 
ð 	
r;   r   r   c                óL   — | j         €J ‚t          | j        || j         ¬¦  «        S )zWBuild a :class:`GatewayMCPTool` bound to this client's
        gateway facade.
        N)r   r   r   )r6   r   r+   )r7   r   s     r9   r�   zGatewayMCPClient._wrap_toolQ  s6   € ð Œ}Ð(Ð(Ð(ÝØ”YØØ”Mð
ñ 
ô 
ð 	
r;   )re   r   r    r!   )r   r   ri   r   r    r!   ©r    r!   )T)ru   r   r    r!   )r    rz   )r+   r   r    r   )r   r   r    r   )r\   r]   r^   r_   r   r6   r`   rg   rl   rt   ry   r�   r“   r�   ra   r;   r9   rc   rc       sâ   € € € € € € ðð ð (3 {¸4Ð'@Ñ'@Ô'@€HÐ@Ð@Ð@Ñ@ðð ð ð ð  ð	&ð &ð &ð &ð &ð &ð4"ð "ð "ð "ð<#ð #ð #ð #ð #ð@$ð $ð $ð $ðL
ð 
ð 
ð 
ð4	
ð 	
ð 	
ð 	
ð 	
ð 	
r;   rc   c                  óh   — e Zd ZdZdeeddddœd,d„Zd-d„Zd.d„Zddœd/d„Z	d0d„Z
dd d!œd1d(„Zd2d+„ZdS )3ÚGatewayClienta=  Workspace-side facade over the in-sandbox MCP gateway.

    Every method dispatches through :meth:`exec_request`, which in
    turn drives :meth:`BackendBase.exec_shell` on the workspace's
    backend so the network call always happens **inside** the sandbox.
    No host port mapping or HTTPS proxy is required.
    N)ÚtimeoutÚinline_limitÚtmp_dirÚgateway_log_pathÚ
auth_tokenÚinstance_nonceÚbackendú'BackendBase'Úgateway_portÚintr˜   úfloat | Noner™   rš   r   r›   ú
str | Nonerœ   r�   r    r!   c               ó’   — || _         || _        || _        || _        || _        || _        || _        || _        d| _        d| _	        dS )u  Build a workspace-side gateway facade.

        Args:
            backend (`BackendBase`):
                Workspace backend; every request runs as
                ``backend.exec_shell([...])`` inside the sandbox.
            gateway_port (`int`):
                TCP port the gateway listens on inside the sandbox.
                The shim dials ``http://127.0.0.1:<gateway_port>``.
            timeout (`float | None`, defaults to `None`):
                Per-request timeout forwarded to
                :meth:`BackendBase.exec_shell`; ``None`` waits
                indefinitely.
            inline_limit (`int`, defaults to `BODY_INLINE_LIMIT`):
                Response bodies above this size spill through a sandbox
                tempfile rather than base64-inline through stdout.
            tmp_dir (`str`, defaults to `SANDBOX_TMP_DIR`):
                Sandbox directory for request/response tempfiles. Must
                be writable by the gateway process.
            gateway_log_path (`str | None`, defaults to `None`):
                Sandbox-side path of the gateway's stdout/stderr log
                file. When set, :meth:`exec_request` failures trigger
                a ``/health`` probe and â€” if the gateway is
                unreachable â€” a tail of this log is emitted at
                ``ERROR`` level to help diagnose crashes.
            auth_token (`str | None`, defaults to `None`):
                Optional bearer token forwarded to the gateway by the
                in-sandbox shim.
            instance_nonce (`str | None`, defaults to `None`):
                Optional nonce expected from ``/health``. Used by shared
                network backends to make sure the probed port belongs to the
                gateway process that was just launched before sending auth.
        g      @i   N)
rž   r    r˜   r™   rš   r›   rœ   r�   Ú_health_probe_timeoutÚ_log_tail_bytes)	r7   rž   r    r˜   r™   rš   r›   rœ   r�   s	            r9   r:   zGatewayClient.__init__i  s[   € ðZ ˆŒØ(ˆÔØˆŒØ(ˆÔØˆŒØ 0ˆÔØ$ˆŒØ,ˆÔð -0ˆÔ"ð %)ˆÔÐÐr;   r   c              ƒ  ó  K  — 	 |                       ddd¬¦  «        ƒ d{V —†\  }}n# t          $ r Y dS w xY w|dk    rdS | j        €dS 	 t          j        |¦  «        }n## t          j        t          t          f$ r Y dS w xY wt          |t          ¦  «        sdS | 
                    d¦  «        }t          |t          ¦  «        oF|                     ¦   «         o2| j                             ¦   «         ot          j        || j        ¦  «        S )	uk  Probe ``/health``.
        any other outcome (shim transport failure, non-200) â†’ ``False``.
        With no expected ``instance_nonce``, HTTP 200 means healthy. When a
        nonce is configured, the response must be a JSON object containing the
        matching ``instance_nonce``. Transport failures, non-200 responses,
        malformed JSON, non-object JSON, and nonce mismatches return
        ``False``.

        The ``/health`` path is treated specially by
        :meth:`exec_request` â€” it never triggers the failure
        diagnostic, so a dead gateway does not recursively probe
        itself.
        r|   ú/healthF)Úinclude_authNéÈ   Tr�   )rP   rx   r�   rS   rT   ÚJSONDecodeErrorÚUnicodeDecodeErrorÚ	TypeErrorÚ
isinstancer.   rU   r   ÚisasciiÚsecretsÚcompare_digest)r7   rX   rJ   rY   Únonces        r9   ÚhealthzGatewayClient.health¦  sT  è è € ð	Ø!%×!2Ò!2ØØØ"ð "3ñ "ô "ð ð ð ð ð ð ‰LˆF�D�Døõ
 ð 	ð 	ð 	Ø�5�5ð	øøøà�SŠ=ˆ=Ø�5ØÔÐ&Ø�4ð	Ý”j Ñ&Ô&ˆGˆGøÝÔ$Õ&8½)ÐDð 	ð 	ð 	Ø�5�5ð	øøøå˜'¥4Ñ(Ô(ð 	Ø�5Ø—’Ð,Ñ-Ô-ˆå�u�cÑ"Ô"ð CØ—’‘”ðCàÔ#×+Ò+Ñ-Ô-ðCõ Ô& u¨dÔ.AÑBÔBð		
s   „!& ¦
4³4Á	A ÁA>Á=A>úlist[GatewayMCPClient]c              ƒ  óÖ   ‡ K  — ‰                       dd¦  «        ƒ d{V —†\  }}|dk    r t          dt          ||¦  «        › �¦  «        ‚t          j        |¦  «        }ˆ fd„|D ¦   «         S )u>  Fetch every MCP the gateway is currently serving.

        Returned clients are marked already-connected (via
        :meth:`GatewayMCPClient.attach`) â€” the gateway is already
        maintaining their upstream sessions.

        Raises:
            `RuntimeError`:
                Gateway returned non-2xx.
        r|   rp   NrK   zgateway failed to list MCPs: c                ó>   •— g | ]}‰                      |d ¬¦  «        ‘ŒS )Trh   )Úmake_client)r�   Úspecr7   s     €r9   rƒ   z+GatewayClient.list_mcps.<locals>.<listcomp>ß  s,   ø€ ÐIÐIÐI¸4�× Ò  °Ð Ñ6Ô6ÐIÐIÐIr;   )rP   rV   rQ   rS   rT   )r7   rX   rJ   Úspecss   `   r9   Ú	list_mcpszGatewayClient.list_mcpsÎ  s�   øè è € ð "×.Ò.¨u°gÑ>Ô>Ð>Ð>Ð>Ð>Ð>Ð>‰ˆ�Ø�SŠ=ˆ=ÝØLµ¸VÀTÑ0JÔ0JÐLÐLñô ð õ ”
˜4Ñ Ô ˆØIÐIÐIÐIÀ5ÐIÑIÔIÐIr;   Frh   r¸   údict[str, Any]ri   rc   c               óh   — t                                |¦  «        }|                     | |¬¦  «         |S )uò  Build a :class:`GatewayMCPClient` wired to this gateway.

        Args:
            spec (`dict[str, Any]`):
                ``MCPClient.model_dump(mode="json")`` payload â€” either
                from ``GET /mcps`` or from user input via ``add_mcp``.
            connected (`bool`, defaults to `False`):
                Mark the client as already-connected. Set by
                :meth:`list_mcps`; leave ``False`` when the caller will
                ``await client.connect()`` itself.
        rh   )rc   rW   rl   )r7   r¸   ri   Úclients       r9   r·   zGatewayClient.make_clientá  s2   € õ" "×0Ò0°Ñ6Ô6ˆØ�Š�d iˆÑ0Ô0Ð0Øˆr;   c              ƒ  ó
   K  — dS )u�   No-op kept for API parity â€” the transport holds no host-side
        resources, but callers keep their shutdown idiom.
        Nra   )r7   s    r9   ÚaclosezGatewayClient.acloseö  s   è è € ð 	ˆr;   T)rJ   r©   ÚmethodÚpathrJ   r   r©   útuple[int, bytes]c             ƒ  ó¶  K  — d}d}|�m| j         › dt          j        ¦   «         j        › d�}|}| j                             |t          j        |d¬¦  «                             d¦  «        ¦  «        ƒ d{V —† 	 	 | j         	                    dd	t          |d
| j        › |› �|t          | j        ¦  «        | j         |r	| j        pdndg	| j        ¬¦  «        ƒ d{V —†}|�2	 | j                             |¦  «        ƒ d{V —† nJ# t"          $ r Y n>w xY wn9# |�2	 | j                             |¦  «        ƒ d{V —† w # t"          $ r Y w w xY ww xY w|j        dk    r;t'          d|j        › d|j                             d¬¦  «        dd…         › �¦  «        ‚	 t          j        |j        ¦  «        }n2# t"          $ r%}	t'          d|j        dd…         ›�¦  «        |	‚d}	~	ww xY wt1          |d         ¦  «        }
|
dk    r&t'          d|                     dd¦  «        › �¦  «        ‚d|v rZ|d         }| j                             |¦  «        ƒ d{V —†}	 | j                             |¦  «        ƒ d{V —† n8# t"          $ r Y n,w xY wt7          j        |                     dd¦  «        ¦  «        }|
|fS # t"          $ r)}|dk    r|                      |||¦  «        ƒ d{V —† ‚ d}~ww xY w)u  Relay one HTTP request through the sandbox.

        Writes ``body`` (if any) to a sandbox tempfile, runs
        ``python3 -c <SHIM_SCRIPT> ...`` inside the sandbox via
        :meth:`BackendBase.exec_shell`, and parses the JSON envelope
        the shim prints on stdout. Inline bodies are base64-decoded;
        oversized bodies are pulled back through ``body_file``. Request
        and response tempfiles are best-effort cleaned up.

        On any failure â€” shim non-zero exit, non-JSON stdout, or
        ``status == -1`` transport error â€” a self-diagnostic step
        probes ``/health`` and, if the gateway is unreachable, tails
        :attr:`gateway_log_path` at ``ERROR`` level so the real crash
        cause reaches the host log stream. The original exception is
        always re-raised so the caller's error contract is unchanged.
        The ``/health`` path itself skips diagnosis to avoid recursing
        on a dead gateway.

        Args:
            method (`str`):
                HTTP verb (``GET`` / ``POST`` / ``DELETE``).
            path (`str`):
                Path-only URL, e.g. ``/mcps/<name>/tools/<tool>``.
            body (`Any`, optional):
                JSON-serializable request body; ``None`` for no body.
            include_auth (`bool`, defaults to `True`):
                Whether to send the configured bearer token to the shim.
                Health probes set this to ``False`` so a port-race cannot
                leak the token to a process that is not the gateway.

        Returns:
            `tuple[int, bytes]`:
                Status code + raw response bytes (callers decode).

        Raises:
            `RuntimeError`:
                Shim crash (non-zero exit / non-JSON stdout) or
                transport failure (``status == -1``).
        r%   Nú/z.jsonF)Úensure_asciizutf-8Úpython3z-czhttp://127.0.0.1:)r˜   r   zgateway shim exited with rq   Úreplace©Úerrorsiô  z'gateway shim produced non-JSON stdout: rª   rX   éÿÿÿÿzgateway request failed: Úerrorzunknown errorÚ	body_filerJ   r¨   )rš   ÚuuidÚuuid4Úhexrž   Ú
write_filerS   ÚdumpsÚencodeÚ
exec_shellr   r    r   r™   rœ   r˜   Údelete_pathrx   Ú	exit_coderV   ÚstderrÚdecoderT   Ústdoutr¡   rU   Ú	read_fileÚbase64Ú	b64decodeÚ_diagnose_failure)r7   rÀ   rÁ   rJ   r©   rÌ   Úwrote_body_fileÚresultÚenvÚerX   ÚspilledÚ
body_bytesÚexcs                 r9   rP   zGatewayClient.exec_requestþ  s  è è € ð^ ˆ	Ø&*ˆØÐØœ<ÐAÐA­$¬*©,¬,Ô*:ÐAÐAÐAˆIØ'ˆOØ”,×)Ò)ØÝ”
˜4¨eÐ4Ñ4Ô4×;Ò;¸GÑDÔDñô ð ð ð ð ð ð ð ð
<	ðØ-1¬\×-DÒ-Dà!ØÝ#ØØE¨DÔ,=ÐE¸tÐEÐEØ!Ý˜DÔ-Ñ.Ô.ØœØ3?ÐG˜œÐ.¨BøÀRð
ð !œLð .Eñ .ô .ð (ð (ð (ð (ð (ð (�ð #Ð.ðØ"œl×6Ò6°ÑGÔGÐGÐGÐGÐGÐGÐGÐGÐGøÝ$ð ð ð Ø˜ðøøøð /ø�?Ð.ðØ"œl×6Ò6°ÑGÔGÐGÐGÐGÐGÐGÐGÐGÐGøÝ$ð ð ð Ø˜ðøøøð /øøøð Ô 1Ò$Ð$Ý"ðF°Ô0@ð Fð FØ”}×+Ò+°9Ð+Ñ=Ô=¸d¸s¸dÔCðFð Fñô ð ð
Ý”j ¤Ñ/Ô/��øÝð ð ð Ý"ð/Ø”} T c TÔ*ð/ð /ñô ð ðøøøøðøøøõ ˜˜XœÑ'Ô'ˆFØ˜Š|ˆ|Ý"ð;Ø—w’w˜w¨Ñ8Ô8ð;ð ;ñô ð ð
 ˜cÐ!Ð!Ø˜kÔ*�Ø#'¤<×#9Ò#9¸'Ñ#BÔ#BÐBÐBÐBÐBÐBÐB�
ðØœ,×2Ò2°7Ñ;Ô;Ð;Ð;Ð;Ð;Ð;Ð;Ð;Ð;øÝ ð ð ð Ø�Dðøøøõ $Ô-¨c¯gªg°f¸bÑ.AÔ.AÑBÔB�
à˜:Ð%Ð%øÝð 	ð 	ð 	ð �yÒ Ð Ø×,Ò,¨V°T¸3Ñ?Ô?Ð?Ð?Ð?Ð?Ð?Ð?Ð?Øøøøøð	øøøs½   Á8A!D ÃJ% Ã C= Ã<J% Ã=
D
ÄJ% Ä	D
Ä
J% ÄEÄ D3Ä2EÄ3
E Ä=EÄ?E Å EÅA	J% ÆF( Æ'J% Æ(
GÆ2 GÇGÇA0J% É I) É(J% É)
I6É3J% É5I6É6.J% Ê%
KÊ/$KËKrã   ÚBaseExceptionc              ƒ  ó  K  — 	 |                       ¦   «         ƒ d{V —†}n# t          $ r d}Y nw xY w|rdS t          j        d|||| j        ¦  «         | j        €dS 	 | j                             | j        ¦  «        ƒ d{V —†}n3# t          $ r&}t          j        d| j        |¦  «         Y d}~dS d}~ww xY w|| j         d…                              d¬¦  «        }t          j        dt          |¦  «        | j        |¦  «         dS )uX  Best-effort post-failure diagnostic invoked by
        :meth:`exec_request` on any request failure.

        Probes ``/health`` (via :meth:`health`, which short-circuits
        the diagnostic path); if the gateway does not answer, emits
        the tail of :attr:`gateway_log_path` at ``ERROR`` level so the
        real crash cause reaches the host log stream. Every step is
        guarded â€” diagnosis must never raise, so the caller's original
        exception is always the one that surfaces.

        Args:
            method (`str`):
                HTTP verb of the failed request (for log context).
            path (`str`):
                Path of the failed request (for log context).
            exc (`BaseException`):
                Original exception raised by the shim call (for log
                context; not re-raised here).
        NFzfGateway unreachable during %s %s: %s. Probing /health failed. Attempting to tail gateway log at %r ...z$Failed to read gateway log at %r: %srÇ   rÈ   z*Gateway log tail (last %d bytes of %r):
%s)
r³   rx   r   rË   r›   rž   rÙ   r¦   r×   Úlen)r7   rÀ   rÁ   rã   ÚhealthyÚ	log_bytesÚread_excÚtails           r9   rÜ   zGatewayClient._diagnose_failureu  st  è è € ð2	Ø ŸKšK™MœMÐ)Ð)Ð)Ð)Ð)Ð)ˆGˆGøÝð 	ð 	ð 	ØˆGˆGˆGð	øøøð ð 	ð ˆFåŒð7àØØØÔ!ñ	
ô 	
ð 	
ð Ô Ð(ØˆFð	Ø"œl×4Ò4°TÔ5JÑKÔKÐKÐKÐKÐKÐKÐKˆIˆIøÝð 	ð 	ð 	ÝŒLØ6ØÔ%Øñô ð ð
 ˆFˆFˆFˆFˆFøøøøð	øøøð ˜$Ô.Ð.Ð0Ð0Ô1×8Ò8À	Ð8ÑJÔJˆÝŒØ9Ý�‰IŒIØÔ!Øñ		
ô 	
ð 	
ð 	
ð 	
s$   „ Ÿ.­.Á%B Â
B2ÂB-Â-B2)rž   rŸ   r    r¡   r˜   r¢   r™   r¡   rš   r   r›   r£   rœ   r£   r�   r£   r    r!   )r    r   )r    r´   )r¸   r»   ri   r   r    rc   r•   )
rÀ   r   rÁ   r   rJ   r   r©   r   r    rÂ   )rÀ   r   rÁ   r   rã   rä   r    r!   )r\   r]   r^   r_   r   r   r:   r³   rº   r·   r¿   rP   rÜ   ra   r;   r9   r—   r—   `  sü   € € € € € ðð ð !%Ø-Ø&Ø'+Ø!%Ø%)ð;)ð ;)ð ;)ð ;)ð ;)ð ;)ðz&
ð &
ð &
ð &
ðPJð Jð Jð Jð.  ð	ð ð ð ð ð ð*ð ð ð ð Ø!ðuð uð uð uð uð uðn@
ð @
ð @
ð @
ð @
ð @
r;   r—   rX   r¡   rJ   Úbytesr    r   c                ó  — 	 t          j        |¦  «        }n4# t          $ r' d| › d|dd…                              d¬¦  «        › �cY S w xY wt	          |t
          ¦  «        rd|v rd| › d|d         › �S d| › dt          |¦  «        dd…         › �S )u†   Best-effort ``HTTP <status>: <detail>`` string from a gateway
    error response â€” tolerates non-JSON, missing ``detail``, etc.
    zHTTP rq   Nrª   rÇ   rÈ   Údetail)rS   rT   rx   r×   r®   r.   r   )rX   rJ   r‹   s      r9   rQ   rQ   »  sÇ   € ðGÝŒz˜$ÑÔˆˆøÝð Gð Gð GØF�vÐFÐF  d s d¤×!2Ò!2¸)Ð!2Ñ!DÔ!DÐFÐFÐFÐFÐFðGøøøå�$�ÑÔð 2 (¨dÐ"2Ð"2Ø1�vÐ1Ð1  h¤Ð1Ð1Ð1Ø.�6Ð.Ð.�S ™YœY t¨ tœ_Ð.Ð.Ð.s   ‚ —.AÁA)rX   r¡   rJ   rë   r    r   )#r_   Ú
__future__r   rÚ   rS   r°   rÍ   Útypingr   r   Ú	mcp.typesr~   Úpydanticr   Ú_loggingr   r	   r@   r
   Ú
permissionr   r   r   r   r   Ú_gateway_shimr   r   r   r   r   r   rc   r—   rQ   ra   r;   r9   ú<module>rõ      sþ  ððð ð, #Ð "Ð "Ð "Ð "Ð "à €€€Ø €€€Ø €€€Ø €€€Ø %Ð %Ð %Ð %Ð %Ð %Ð %Ð %à Ð Ð Ð Ø  Ð  Ð  Ð  Ð  Ð  à Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð Ø %Ð %Ð %Ð %Ð %Ð %ðð ð ð ð ð ð ð ð 'Ð &Ð &Ð &Ð &Ð &Ð &Ð &ðð ð ð ð ð ð ð ð ð ð ð /Ø.Ð.Ð.Ð.Ð.Ð.Ð.Ð.ðb4ð b4ð b4ð b4ð b4�Xñ b4ô b4ð b4ðPz
ð z
ð z
ð z
ð z
�yñ z
ô z
ð z
ð@U
ð U
ð U
ð U
ð U
ñ U
ô U
ð U
ðv

/ð 
/ð 
/ð 
/ð 
/ð 
/r;   