-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathinstallation_confirmation.py
More file actions
210 lines (177 loc) · 7.45 KB
/
Copy pathinstallation_confirmation.py
File metadata and controls
210 lines (177 loc) · 7.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
"""Installation confirmation receiver support."""
from __future__ import annotations
import asyncio
import inspect
import logging
from collections.abc import Awaitable
from dataclasses import dataclass
from typing import Literal, Protocol
from ._jsonrpc import JsonRpcClient
from .generated.rpc import (
InstallationConfirmationRequest,
InstallationConfirmationResponse,
InstallationDecision,
)
logger = logging.getLogger(__name__)
InstallationConfirmationDecision = InstallationDecision | Literal["confirm", "decline", "cancel"]
"""Decision returned by an installation confirmation handler."""
@dataclass(frozen=True)
class InstallationConfirmationContext:
"""Cancellation signal for one installation review on its original connection."""
cancelled: asyncio.Event
"""Set when the runtime retires this request or the original connection closes."""
class InstallationConfirmationHandler(Protocol):
"""Collect a fresh human decision for one complete installation review.
Match the request's operation and optional policy session metadata to the
exact action previously registered on this connection before presenting it.
The SDK echoes the original challenge and fingerprint, so the handler
returns only an explicit decision.
"""
def __call__(
self,
request: InstallationConfirmationRequest,
context: InstallationConfirmationContext,
) -> InstallationConfirmationDecision | Awaitable[InstallationConfirmationDecision]:
"""Return ``"confirm"``, ``"decline"`` or ``"cancel"``."""
class _InstallationConfirmationAdapter:
def __init__(
self,
client: JsonRpcClient,
handler: InstallationConfirmationHandler | None,
) -> None:
self._client = client
self._handler = handler
self._loop = asyncio.get_running_loop()
self._connection_closed = asyncio.Event()
def register(self) -> None:
self._client.set_raw_request_handler("installations.confirm", self.handle_request)
def close_connection(self) -> None:
if self._connection_closed.is_set():
return
if self._loop.is_closed():
return
self._loop.call_soon_threadsafe(self._connection_closed.set)
async def handle_request(self, message: dict) -> None:
request_id = message["id"]
request_cancelled = self._client.incoming_request_cancelled_event(request_id)
cancelled = asyncio.Event()
if request_cancelled.is_set() or self._connection_closed.is_set():
cancelled.set()
context = InstallationConfirmationContext(cancelled=cancelled)
try:
request = InstallationConfirmationRequest.from_dict(message.get("params"))
except Exception:
await self._client._send_error_response( # noqa: SLF001
request_id,
-32602,
"Invalid installation confirmation review",
None,
)
return
if self._handler is None:
await self._client._send_error_response( # noqa: SLF001
request_id,
-32603,
"No installations client-global handler registered",
None,
)
return
confirmation_id = request.confirmation_id
review_fingerprint = request.review_fingerprint
handler_task = asyncio.create_task(self._call_handler(request, context))
request_cancelled_task = asyncio.create_task(request_cancelled.wait())
connection_closed_task = asyncio.create_task(self._connection_closed.wait())
wait_tasks = {handler_task, request_cancelled_task, connection_closed_task}
try:
done, pending = await asyncio.wait(wait_tasks, return_when=asyncio.FIRST_COMPLETED)
for task in pending - {handler_task}:
task.cancel()
if connection_closed_task in done or self._connection_closed.is_set():
cancelled.set()
_drop_late_handler_result(handler_task)
return
if request_cancelled_task in done or request_cancelled.is_set():
cancelled.set()
_drop_late_handler_result(handler_task)
await self._client._send_error_response( # noqa: SLF001
request_id,
-32800,
"Installation confirmation request cancelled",
None,
)
return
decision = await handler_task
if self._connection_closed.is_set():
cancelled.set()
return
if request_cancelled.is_set():
cancelled.set()
await self._client._send_error_response( # noqa: SLF001
request_id,
-32800,
"Installation confirmation request cancelled",
None,
)
return
try:
normalised = _normalise_decision(decision)
except ValueError:
await self._client._send_error_response( # noqa: SLF001
request_id,
-32603,
"Invalid installation confirmation decision",
None,
)
return
response = InstallationConfirmationResponse(
confirmation_id=confirmation_id,
decision=normalised,
review_fingerprint=review_fingerprint,
)
await self._client._send_response(request_id, response.to_dict()) # noqa: SLF001
except Exception as exc: # pylint: disable=broad-except
if self._connection_closed.is_set():
cancelled.set()
return
await self._client._send_error_response( # noqa: SLF001
request_id,
-32603,
str(exc) or "Installation confirmation handler failed",
None,
)
finally:
for task in (request_cancelled_task, connection_closed_task):
if not task.done():
task.cancel()
async def _call_handler(
self,
request: InstallationConfirmationRequest,
context: InstallationConfirmationContext,
) -> InstallationConfirmationDecision:
assert self._handler is not None
result = self._handler(request, context)
if inspect.isawaitable(result):
result = await result
return result
def _normalise_decision(decision: InstallationConfirmationDecision) -> InstallationDecision:
if isinstance(decision, InstallationDecision):
return decision
if isinstance(decision, str):
try:
return InstallationDecision(decision)
except ValueError as exc:
raise ValueError("Invalid installation confirmation decision") from exc
raise ValueError("Invalid installation confirmation decision")
def _drop_late_handler_result(task: asyncio.Task) -> None:
if task.done():
try:
task.result()
except Exception:
logger.debug("Installation confirmation handler failed after retirement", exc_info=True)
return
def consume(completed: asyncio.Task) -> None:
try:
completed.result()
except Exception:
logger.debug("Installation confirmation handler failed after retirement", exc_info=True)
task.add_done_callback(consume)