|
20 | 20 | import sys |
21 | 21 | import threading |
22 | 22 | from collections.abc import Callable |
23 | | -from dataclasses import asdict, is_dataclass |
24 | 23 | from pathlib import Path |
25 | 24 | from typing import Any, cast, overload |
26 | 25 |
|
27 | 26 | from .generated.rpc import ServerRpc |
28 | | -from .generated.session_events import session_event_from_dict |
| 27 | +from .generated.session_events import PermissionRequest, session_event_from_dict |
29 | 28 | from .jsonrpc import JsonRpcClient, ProcessExitedError |
30 | 29 | from .sdk_protocol_version import get_sdk_protocol_version |
31 | 30 | from .session import CopilotSession |
@@ -1103,7 +1102,7 @@ def on( |
1103 | 1102 | event_type_or_handler: SessionLifecycleEventType | SessionLifecycleHandler, |
1104 | 1103 | /, |
1105 | 1104 | handler: SessionLifecycleHandler | None = None, |
1106 | | - ) -> Callable[[], None]: |
| 1105 | + ) -> HandlerUnsubcribe: |
1107 | 1106 | """ |
1108 | 1107 | Subscribe to session lifecycle events. |
1109 | 1108 |
|
@@ -1611,56 +1610,58 @@ async def _handle_tool_call_request_v2(self, params: dict) -> dict: |
1611 | 1610 | result = handler(invocation) |
1612 | 1611 | if inspect.isawaitable(result): |
1613 | 1612 | result = await result |
1614 | | - except Exception as exc: # pylint: disable=broad-except |
1615 | | - # Don't expose detailed error information to the LLM for security reasons. |
1616 | | - # The actual error is stored in the 'error' field for debugging. |
1617 | | - result = ToolResult( |
1618 | | - textResultForLlm="Invoking this tool produced an error. " |
1619 | | - "Detailed information is not available.", |
1620 | | - resultType="failure", |
1621 | | - error=str(exc), |
1622 | | - toolTelemetry={}, |
1623 | | - ) |
1624 | | - |
1625 | | - if result is None: |
1626 | | - result = ToolResult( |
1627 | | - textResultForLlm="Tool returned no result.", |
1628 | | - resultType="failure", |
1629 | | - error="tool returned no result", |
1630 | | - toolTelemetry={}, |
1631 | | - ) |
1632 | | - |
1633 | | - return self._normalize_tool_result(result) |
1634 | | - |
1635 | | - def _normalize_tool_result(self, result: ToolResult) -> ToolResult: |
1636 | | - """ |
1637 | | - Normalize a tool result for transmission. |
1638 | | -
|
1639 | | - Converts dataclass instances to dictionaries for JSON serialization. |
1640 | 1613 |
|
1641 | | - Args: |
1642 | | - result: The tool result to normalize. |
| 1614 | + tool_result: ToolResult = result # type: ignore[assignment] |
| 1615 | + return { |
| 1616 | + "result": { |
| 1617 | + "textResultForLlm": tool_result.text_result_for_llm, |
| 1618 | + "resultType": tool_result.result_type, |
| 1619 | + "error": tool_result.error, |
| 1620 | + "toolTelemetry": tool_result.tool_telemetry or {}, |
| 1621 | + } |
| 1622 | + } |
| 1623 | + except Exception as exc: |
| 1624 | + return { |
| 1625 | + "result": { |
| 1626 | + "textResultForLlm": ( |
| 1627 | + "Invoking this tool produced an error." |
| 1628 | + " Detailed information is not available." |
| 1629 | + ), |
| 1630 | + "resultType": "failure", |
| 1631 | + "error": str(exc), |
| 1632 | + "toolTelemetry": {}, |
| 1633 | + } |
| 1634 | + } |
1643 | 1635 |
|
1644 | | - Returns: |
1645 | | - The normalized tool result. |
1646 | | - """ |
1647 | | - if is_dataclass(result) and not isinstance(result, type): |
1648 | | - return asdict(result) # type: ignore[arg-type] |
1649 | | - return result |
| 1636 | + async def _handle_permission_request_v2(self, params: dict) -> dict: |
| 1637 | + """Handle a v2-style permission.request RPC request from the server.""" |
| 1638 | + session_id = params.get("sessionId") |
| 1639 | + permission_request = params.get("permissionRequest") |
1650 | 1640 |
|
1651 | | - def _build_unsupported_tool_result(self, tool_name: str) -> ToolResult: |
1652 | | - """ |
1653 | | - Build a failure result for an unsupported tool. |
| 1641 | + if not session_id or not permission_request: |
| 1642 | + raise ValueError("invalid permission request payload") |
1654 | 1643 |
|
1655 | | - Args: |
1656 | | - tool_name: The name of the unsupported tool. |
| 1644 | + with self._sessions_lock: |
| 1645 | + session = self._sessions.get(session_id) |
| 1646 | + if not session: |
| 1647 | + raise ValueError(f"unknown session {session_id}") |
1657 | 1648 |
|
1658 | | - Returns: |
1659 | | - A ToolResult indicating the tool is not supported. |
1660 | | - """ |
1661 | | - return ToolResult( |
1662 | | - textResultForLlm=f"Tool '{tool_name}' is not supported.", |
1663 | | - resultType="failure", |
1664 | | - error=f"tool '{tool_name}' not supported", |
1665 | | - toolTelemetry={}, |
1666 | | - ) |
| 1649 | + try: |
| 1650 | + perm_request = PermissionRequest.from_dict(permission_request) |
| 1651 | + result = await session._handle_permission_request(perm_request) |
| 1652 | + result_payload: dict = {"kind": result.kind} |
| 1653 | + if result.rules is not None: |
| 1654 | + result_payload["rules"] = result.rules |
| 1655 | + if result.feedback is not None: |
| 1656 | + result_payload["feedback"] = result.feedback |
| 1657 | + if result.message is not None: |
| 1658 | + result_payload["message"] = result.message |
| 1659 | + if result.path is not None: |
| 1660 | + result_payload["path"] = result.path |
| 1661 | + return {"result": result_payload} |
| 1662 | + except Exception: # pylint: disable=broad-except |
| 1663 | + return { |
| 1664 | + "result": { |
| 1665 | + "kind": "denied-no-approval-rule-and-could-not-request-from-user", |
| 1666 | + } |
| 1667 | + } |
0 commit comments