Skip to content

Commit 9a0a8f2

Browse files
stephentoubCopilot
andcommitted
Use match for Python event data
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent adc2d9b commit 9a0a8f2

8 files changed

Lines changed: 225 additions & 187 deletions

File tree

python/README.md

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ python chat.py
2727
import asyncio
2828

2929
from copilot import CopilotClient
30-
from copilot.generated.session_events import AssistantMessageData
30+
from copilot.generated.session_events import AssistantMessageData, SessionIdleData
3131

3232
async def main():
3333
# Client automatically starts on enter and cleans up on exit
@@ -38,10 +38,11 @@ async def main():
3838
done = asyncio.Event()
3939

4040
def on_event(event):
41-
if event.type.value == "assistant.message" and isinstance(event.data, AssistantMessageData):
42-
print(event.data.content)
43-
elif event.type.value == "session.idle":
44-
done.set()
41+
match event.data:
42+
case AssistantMessageData() as data:
43+
print(data.content)
44+
case SessionIdleData():
45+
done.set()
4546

4647
session.on(on_event)
4748

@@ -60,7 +61,7 @@ If you need more control over the lifecycle, you can call `start()`, `stop()`, a
6061
import asyncio
6162

6263
from copilot import CopilotClient
63-
from copilot.generated.session_events import AssistantMessageData
64+
from copilot.generated.session_events import AssistantMessageData, SessionIdleData
6465
from copilot.session import PermissionHandler
6566

6667
async def main():
@@ -76,10 +77,11 @@ async def main():
7677
done = asyncio.Event()
7778

7879
def on_event(event):
79-
if event.type.value == "assistant.message" and isinstance(event.data, AssistantMessageData):
80-
print(event.data.content)
81-
elif event.type.value == "session.idle":
82-
done.set()
80+
match event.data:
81+
case AssistantMessageData() as data:
82+
print(data.content)
83+
case SessionIdleData():
84+
done.set()
8385

8486
session.on(on_event)
8587
await session.send("What is 2+2?")
@@ -343,6 +345,7 @@ from copilot.generated.session_events import (
343345
AssistantMessageDeltaData,
344346
AssistantReasoningData,
345347
AssistantReasoningDeltaData,
348+
SessionIdleData,
346349
)
347350
from copilot.session import PermissionHandler
348351

@@ -357,28 +360,24 @@ async def main():
357360
done = asyncio.Event()
358361

359362
def on_event(event):
360-
match event.type.value:
361-
case "assistant.message_delta":
362-
if isinstance(event.data, AssistantMessageDeltaData):
363-
# Streaming message chunk - print incrementally
364-
delta = event.data.delta_content or ""
365-
print(delta, end="", flush=True)
366-
case "assistant.reasoning_delta":
367-
if isinstance(event.data, AssistantReasoningDeltaData):
368-
# Streaming reasoning chunk (if model supports reasoning)
369-
delta = event.data.delta_content or ""
370-
print(delta, end="", flush=True)
371-
case "assistant.message":
372-
if isinstance(event.data, AssistantMessageData):
373-
# Final message - complete content
374-
print("\n--- Final message ---")
375-
print(event.data.content)
376-
case "assistant.reasoning":
377-
if isinstance(event.data, AssistantReasoningData):
378-
# Final reasoning content (if model supports reasoning)
379-
print("--- Reasoning ---")
380-
print(event.data.content)
381-
case "session.idle":
363+
match event.data:
364+
case AssistantMessageDeltaData() as data:
365+
# Streaming message chunk - print incrementally
366+
delta = data.delta_content or ""
367+
print(delta, end="", flush=True)
368+
case AssistantReasoningDeltaData() as data:
369+
# Streaming reasoning chunk (if model supports reasoning)
370+
delta = data.delta_content or ""
371+
print(delta, end="", flush=True)
372+
case AssistantMessageData() as data:
373+
# Final message - complete content
374+
print("\n--- Final message ---")
375+
print(data.content)
376+
case AssistantReasoningData() as data:
377+
# Final reasoning content (if model supports reasoning)
378+
print("--- Reasoning ---")
379+
print(data.content)
380+
case SessionIdleData():
382381
# Session finished processing
383382
done.set()
384383

python/copilot/session.py

Lines changed: 98 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,17 @@
4545
)
4646
from .generated.rpc import ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride
4747
from .generated.session_events import (
48+
AssistantMessageData,
4849
CapabilitiesChangedData,
4950
CommandExecuteData,
5051
ElicitationRequestedData,
5152
ExternalToolRequestedData,
5253
PermissionRequest,
5354
PermissionRequestedData,
5455
SessionEvent,
56+
SessionErrorData,
5557
SessionEventType,
58+
SessionIdleData,
5659
session_event_from_dict,
5760
)
5861
from .tools import Tool, ToolHandler, ToolInvocation, ToolResult
@@ -1134,24 +1137,25 @@ async def send_and_wait(
11341137
Example:
11351138
>>> from copilot.generated.session_events import AssistantMessageData
11361139
>>> response = await session.send_and_wait("What is 2+2?")
1137-
>>> if response and isinstance(response.data, AssistantMessageData):
1138-
... print(response.data.content)
1140+
>>> if response:
1141+
... match response.data:
1142+
... case AssistantMessageData() as data:
1143+
... print(data.content)
11391144
"""
11401145
idle_event = asyncio.Event()
11411146
error_event: Exception | None = None
11421147
last_assistant_message: SessionEvent | None = None
11431148

11441149
def handler(event: SessionEventTypeAlias) -> None:
11451150
nonlocal last_assistant_message, error_event
1146-
if event.type == SessionEventType.ASSISTANT_MESSAGE:
1147-
last_assistant_message = event
1148-
elif event.type == SessionEventType.SESSION_IDLE:
1149-
idle_event.set()
1150-
elif event.type == SessionEventType.SESSION_ERROR:
1151-
error_event = Exception(
1152-
f"Session error: {getattr(event.data, 'message', str(event.data))}"
1153-
)
1154-
idle_event.set()
1151+
match event.data:
1152+
case AssistantMessageData():
1153+
last_assistant_message = event
1154+
case SessionIdleData():
1155+
idle_event.set()
1156+
case SessionErrorData() as data:
1157+
error_event = Exception(f"Session error: {data.message or str(data)}")
1158+
idle_event.set()
11551159

11561160
unsubscribe = self.on(handler)
11571161
try:
@@ -1183,10 +1187,11 @@ def on(self, handler: Callable[[SessionEvent], None]) -> Callable[[], None]:
11831187
Example:
11841188
>>> from copilot.generated.session_events import AssistantMessageData, SessionErrorData
11851189
>>> def handle_event(event):
1186-
... if isinstance(event.data, AssistantMessageData):
1187-
... print(f"Assistant: {event.data.content}")
1188-
... elif isinstance(event.data, SessionErrorData):
1189-
... print(f"Error: {event.data.message}")
1190+
... match event.data:
1191+
... case AssistantMessageData() as data:
1192+
... print(f"Assistant: {data.content}")
1193+
... case SessionErrorData() as data:
1194+
... print(f"Error: {data.message}")
11901195
>>> unsubscribe = session.on(handle_event)
11911196
>>> # Later, to stop receiving events:
11921197
>>> unsubscribe()
@@ -1232,90 +1237,89 @@ def _handle_broadcast_event(self, event: SessionEvent) -> None:
12321237
Implements the protocol v3 broadcast model where tool calls and permission requests
12331238
are broadcast as session events to all clients.
12341239
"""
1235-
data = event.data
1236-
1237-
if isinstance(data, ExternalToolRequestedData):
1238-
request_id = data.request_id
1239-
tool_name = data.tool_name
1240-
if not request_id or not tool_name:
1241-
return
1242-
1243-
handler = self._get_tool_handler(tool_name)
1244-
if not handler:
1245-
return # This client doesn't handle this tool; another client will.
1246-
1247-
tool_call_id = data.tool_call_id or ""
1248-
arguments = data.arguments
1249-
tp = getattr(data, "traceparent", None)
1250-
ts = getattr(data, "tracestate", None)
1251-
asyncio.ensure_future(
1252-
self._execute_tool_and_respond(
1253-
request_id, tool_name, tool_call_id, arguments, handler, tp, ts
1240+
match event.data:
1241+
case ExternalToolRequestedData() as data:
1242+
request_id = data.request_id
1243+
tool_name = data.tool_name
1244+
if not request_id or not tool_name:
1245+
return
1246+
1247+
handler = self._get_tool_handler(tool_name)
1248+
if not handler:
1249+
return # This client doesn't handle this tool; another client will.
1250+
1251+
tool_call_id = data.tool_call_id or ""
1252+
arguments = data.arguments
1253+
tp = getattr(data, "traceparent", None)
1254+
ts = getattr(data, "tracestate", None)
1255+
asyncio.ensure_future(
1256+
self._execute_tool_and_respond(
1257+
request_id, tool_name, tool_call_id, arguments, handler, tp, ts
1258+
)
12541259
)
1255-
)
12561260

1257-
elif isinstance(data, PermissionRequestedData):
1258-
request_id = data.request_id
1259-
permission_request = data.permission_request
1260-
if not request_id or not permission_request:
1261-
return
1261+
case PermissionRequestedData() as data:
1262+
request_id = data.request_id
1263+
permission_request = data.permission_request
1264+
if not request_id or not permission_request:
1265+
return
12621266

1263-
resolved_by_hook = getattr(data, "resolved_by_hook", None)
1264-
if resolved_by_hook:
1265-
return # Already resolved by a permissionRequest hook; no client action needed.
1267+
resolved_by_hook = getattr(data, "resolved_by_hook", None)
1268+
if resolved_by_hook:
1269+
return # Already resolved by a permissionRequest hook; no client action needed.
12661270

1267-
with self._permission_handler_lock:
1268-
perm_handler = self._permission_handler
1269-
if not perm_handler:
1270-
return # This client doesn't handle permissions; another client will.
1271+
with self._permission_handler_lock:
1272+
perm_handler = self._permission_handler
1273+
if not perm_handler:
1274+
return # This client doesn't handle permissions; another client will.
12711275

1272-
asyncio.ensure_future(
1273-
self._execute_permission_and_respond(request_id, permission_request, perm_handler)
1274-
)
1276+
asyncio.ensure_future(
1277+
self._execute_permission_and_respond(request_id, permission_request, perm_handler)
1278+
)
12751279

1276-
elif isinstance(data, CommandExecuteData):
1277-
request_id = data.request_id
1278-
command_name = data.command_name
1279-
command = data.command
1280-
args = data.args
1281-
if not request_id or not command_name:
1282-
return
1283-
asyncio.ensure_future(
1284-
self._execute_command_and_respond(
1285-
request_id, command_name, command or "", args or ""
1280+
case CommandExecuteData() as data:
1281+
request_id = data.request_id
1282+
command_name = data.command_name
1283+
command = data.command
1284+
args = data.args
1285+
if not request_id or not command_name:
1286+
return
1287+
asyncio.ensure_future(
1288+
self._execute_command_and_respond(
1289+
request_id, command_name, command or "", args or ""
1290+
)
12861291
)
1287-
)
12881292

1289-
elif isinstance(data, ElicitationRequestedData):
1290-
with self._elicitation_handler_lock:
1291-
handler = self._elicitation_handler
1292-
if not handler:
1293-
return
1294-
request_id = data.request_id
1295-
if not request_id:
1296-
return
1297-
context: ElicitationContext = {
1298-
"session_id": self.session_id,
1299-
"message": data.message or "",
1300-
}
1301-
if data.requested_schema is not None:
1302-
context["requestedSchema"] = data.requested_schema.to_dict()
1303-
if data.mode is not None:
1304-
context["mode"] = data.mode.value
1305-
if data.elicitation_source is not None:
1306-
context["elicitationSource"] = data.elicitation_source
1307-
if data.url is not None:
1308-
context["url"] = data.url
1309-
asyncio.ensure_future(self._handle_elicitation_request(context, request_id))
1310-
1311-
elif isinstance(data, CapabilitiesChangedData):
1312-
cap: SessionCapabilities = {}
1313-
if data.ui is not None:
1314-
ui_cap: SessionUiCapabilities = {}
1315-
if data.ui.elicitation is not None:
1316-
ui_cap["elicitation"] = data.ui.elicitation
1317-
cap["ui"] = ui_cap
1318-
self._capabilities = {**self._capabilities, **cap}
1293+
case ElicitationRequestedData() as data:
1294+
with self._elicitation_handler_lock:
1295+
handler = self._elicitation_handler
1296+
if not handler:
1297+
return
1298+
request_id = data.request_id
1299+
if not request_id:
1300+
return
1301+
context: ElicitationContext = {
1302+
"session_id": self.session_id,
1303+
"message": data.message or "",
1304+
}
1305+
if data.requested_schema is not None:
1306+
context["requestedSchema"] = data.requested_schema.to_dict()
1307+
if data.mode is not None:
1308+
context["mode"] = data.mode.value
1309+
if data.elicitation_source is not None:
1310+
context["elicitationSource"] = data.elicitation_source
1311+
if data.url is not None:
1312+
context["url"] = data.url
1313+
asyncio.ensure_future(self._handle_elicitation_request(context, request_id))
1314+
1315+
case CapabilitiesChangedData() as data:
1316+
cap: SessionCapabilities = {}
1317+
if data.ui is not None:
1318+
ui_cap: SessionUiCapabilities = {}
1319+
if data.ui.elicitation is not None:
1320+
ui_cap["elicitation"] = data.ui.elicitation
1321+
cap["ui"] = ui_cap
1322+
self._capabilities = {**self._capabilities, **cap}
13191323

13201324
async def _execute_tool_and_respond(
13211325
self,
@@ -1807,8 +1811,9 @@ async def get_messages(self) -> list[SessionEvent]:
18071811
>>> from copilot.generated.session_events import AssistantMessageData
18081812
>>> events = await session.get_messages()
18091813
>>> for event in events:
1810-
... if isinstance(event.data, AssistantMessageData):
1811-
... print(f"Assistant: {event.data.content}")
1814+
... match event.data:
1815+
... case AssistantMessageData() as data:
1816+
... print(f"Assistant: {data.content}")
18121817
"""
18131818
response = await self._client.request("session.getMessages", {"sessionId": self.session_id})
18141819
# Convert dict events to SessionEvent objects

0 commit comments

Comments
 (0)