Skip to content

Commit 581abad

Browse files
Phase L: emit discriminated unions in Python codegen + drop hand-written PermissionRequestResult
Brings Python in line with TS/Rust/.NET/Go which all emit per-variant types for �nyOf-of-\ discriminated unions in the schemas (PermissionRequest, PermissionDecision, AuthInfo, SendAttachment, ToolExecutionCompleteContent, TaskInfo, SystemNotification, etc.). Previously the Python codegen merged each one into a single flat dataclass with every variant's fields collapsed to Optional — see `scripts/codegen/python.ts:897-901` for the old remap table that fronted the merged blob as `PermissionRequest`. Codegen changes (`scripts/codegen/python.ts`) * `tryEmitPyRefBasedDiscriminatedUnion` in the hand-written session-events pipeline emits each variant as its own `@dataclass`, plus a `Name = VariantA | VariantB | ...` union alias and a `_load_Name(obj)` dispatcher that switches on the discriminator (matches `findPyDiscriminator`). * `postProcessRefBasedDiscriminatedUnionsForPython` does the equivalent on the quicktype-emitted RPC types: detects each `\`-based discriminated union, deletes the merged flat class quicktype produced, emits the union alias and dispatcher, and rewrites every `Name.from_dict(x)` / `to_class(Name, x)` call (including in RPC method wrappers generated later) to route through the dispatcher. * Acronym resolution table (`Api → API`, `Mcp → MCP`, `Cli → CLI`, etc.) to map schema names to the actual class names quicktype emits. * `postProcessDiscriminatorDefaultsForPython` replaces the dataclass-level `kind` field on each variant with a class-level `ClassVar[str]` constant. Users construct variants with no discriminator arg required: `PermissionDecisionApproveOnce()` instead of `PermissionDecisionApproveOnce(kind=PermissionDecisionApproveOnceKind.APPROVE_ONCE)`. `from_dict` / `to_dict` are rewritten in lock-step. Hand-written SDK changes * `copilot.session.PermissionRequestResult` becomes a type alias for `PermissionDecision | PermissionNoResult` (mirrors TS `nodejs/src/types.ts:883`). The hand-written `PermissionRequestResult` dataclass and the `_decision_from_result` mapper are gone — handlers now return the generated variant directly. `PermissionNoResult` is a tiny hand-written sentinel for the v1-protocol case. * `PermissionHandler.approve_all` returns `PermissionDecisionApproveOnce()`. * `CopilotSession._execute_permission_and_respond` passes the returned decision straight through to the RPC; v2 servers reject `PermissionNoResult` with a clear `ValueError`. * `CopilotClient._handle_permission_request_v2` uses `_load_PermissionRequest` and serialises the variant result with `.to_dict()`. Tests and scenarios updated in lock-step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f4d22d7 commit 581abad

18 files changed

Lines changed: 3141 additions & 2701 deletions

File tree

python/copilot/client.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,16 @@
3939
from .generated.rpc import (
4040
ClientSessionApiHandlers,
4141
ConnectRequest,
42+
PermissionDecisionUserNotAvailable,
4243
RemoteSessionMode,
4344
ServerRpc,
4445
_InternalServerRpc,
4546
from_datetime,
4647
register_client_session_api_handlers,
4748
)
4849
from .generated.session_events import (
49-
PermissionRequest,
5050
SessionEvent,
51+
_load_PermissionRequest,
5152
session_event_from_dict,
5253
)
5354
from .session import (
@@ -61,6 +62,7 @@
6162
ExitPlanModeHandler,
6263
InfiniteSessionConfig,
6364
MCPServerConfig,
65+
PermissionNoResult,
6466
ProviderConfig,
6567
ReasoningEffort,
6668
SectionTransformFn,
@@ -3193,22 +3195,14 @@ async def _handle_permission_request_v2(self, params: dict) -> dict:
31933195
raise ValueError(f"unknown session {session_id}")
31943196

31953197
try:
3196-
perm_request = PermissionRequest.from_dict(permission_request)
3198+
perm_request = _load_PermissionRequest(permission_request)
31973199
result = await session._handle_permission_request(perm_request)
3198-
if result.kind == "no-result":
3200+
if isinstance(result, PermissionNoResult):
31993201
raise ValueError(NO_RESULT_PERMISSION_V2_ERROR)
3200-
return {"result": {"kind": result.kind}}
3202+
return {"result": result.to_dict()}
32013203
except ValueError as exc:
32023204
if str(exc) == NO_RESULT_PERMISSION_V2_ERROR:
32033205
raise
3204-
return {
3205-
"result": {
3206-
"kind": "user-not-available",
3207-
}
3208-
}
3206+
return {"result": PermissionDecisionUserNotAvailable().to_dict()}
32093207
except Exception: # pylint: disable=broad-except
3210-
return {
3211-
"result": {
3212-
"kind": "user-not-available",
3213-
}
3214-
}
3208+
return {"result": PermissionDecisionUserNotAvailable().to_dict()}

0 commit comments

Comments
 (0)