Skip to content

Commit bf008d4

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

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
@@ -47,14 +47,17 @@
4747
ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride,
4848
)
4949
from .generated.session_events import (
50+
AssistantMessageData,
5051
CapabilitiesChangedData,
5152
CommandExecuteData,
5253
ElicitationRequestedData,
5354
ExternalToolRequestedData,
5455
PermissionRequest,
5556
PermissionRequestedData,
5657
SessionEvent,
58+
SessionErrorData,
5759
SessionEventType,
60+
SessionIdleData,
5861
session_event_from_dict,
5962
)
6063
from .tools import Tool, ToolHandler, ToolInvocation, ToolResult
@@ -1130,24 +1133,25 @@ async def send_and_wait(
11301133
Example:
11311134
>>> from copilot.generated.session_events import AssistantMessageData
11321135
>>> response = await session.send_and_wait("What is 2+2?")
1133-
>>> if response and isinstance(response.data, AssistantMessageData):
1134-
... print(response.data.content)
1136+
>>> if response:
1137+
... match response.data:
1138+
... case AssistantMessageData() as data:
1139+
... print(data.content)
11351140
"""
11361141
idle_event = asyncio.Event()
11371142
error_event: Exception | None = None
11381143
last_assistant_message: SessionEvent | None = None
11391144

11401145
def handler(event: SessionEventTypeAlias) -> None:
11411146
nonlocal last_assistant_message, error_event
1142-
if event.type == SessionEventType.ASSISTANT_MESSAGE:
1143-
last_assistant_message = event
1144-
elif event.type == SessionEventType.SESSION_IDLE:
1145-
idle_event.set()
1146-
elif event.type == SessionEventType.SESSION_ERROR:
1147-
error_event = Exception(
1148-
f"Session error: {getattr(event.data, 'message', str(event.data))}"
1149-
)
1150-
idle_event.set()
1147+
match event.data:
1148+
case AssistantMessageData():
1149+
last_assistant_message = event
1150+
case SessionIdleData():
1151+
idle_event.set()
1152+
case SessionErrorData() as data:
1153+
error_event = Exception(f"Session error: {data.message or str(data)}")
1154+
idle_event.set()
11511155

11521156
unsubscribe = self.on(handler)
11531157
try:
@@ -1179,10 +1183,11 @@ def on(self, handler: Callable[[SessionEvent], None]) -> Callable[[], None]:
11791183
Example:
11801184
>>> from copilot.generated.session_events import AssistantMessageData, SessionErrorData
11811185
>>> def handle_event(event):
1182-
... if isinstance(event.data, AssistantMessageData):
1183-
... print(f"Assistant: {event.data.content}")
1184-
... elif isinstance(event.data, SessionErrorData):
1185-
... print(f"Error: {event.data.message}")
1186+
... match event.data:
1187+
... case AssistantMessageData() as data:
1188+
... print(f"Assistant: {data.content}")
1189+
... case SessionErrorData() as data:
1190+
... print(f"Error: {data.message}")
11861191
>>> unsubscribe = session.on(handle_event)
11871192
>>> # Later, to stop receiving events:
11881193
>>> unsubscribe()
@@ -1228,90 +1233,89 @@ def _handle_broadcast_event(self, event: SessionEvent) -> None:
12281233
Implements the protocol v3 broadcast model where tool calls and permission requests
12291234
are broadcast as session events to all clients.
12301235
"""
1231-
data = event.data
1232-
1233-
if isinstance(data, ExternalToolRequestedData):
1234-
request_id = data.request_id
1235-
tool_name = data.tool_name
1236-
if not request_id or not tool_name:
1237-
return
1238-
1239-
handler = self._get_tool_handler(tool_name)
1240-
if not handler:
1241-
return # This client doesn't handle this tool; another client will.
1242-
1243-
tool_call_id = data.tool_call_id or ""
1244-
arguments = data.arguments
1245-
tp = getattr(data, "traceparent", None)
1246-
ts = getattr(data, "tracestate", None)
1247-
asyncio.ensure_future(
1248-
self._execute_tool_and_respond(
1249-
request_id, tool_name, tool_call_id, arguments, handler, tp, ts
1236+
match event.data:
1237+
case ExternalToolRequestedData() as data:
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
1254+
)
12501255
)
1251-
)
12521256

1253-
elif isinstance(data, PermissionRequestedData):
1254-
request_id = data.request_id
1255-
permission_request = data.permission_request
1256-
if not request_id or not permission_request:
1257-
return
1257+
case PermissionRequestedData() as data:
1258+
request_id = data.request_id
1259+
permission_request = data.permission_request
1260+
if not request_id or not permission_request:
1261+
return
12581262

1259-
resolved_by_hook = getattr(data, "resolved_by_hook", None)
1260-
if resolved_by_hook:
1261-
return # Already resolved by a permissionRequest hook; no client action needed.
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.
12621266

1263-
with self._permission_handler_lock:
1264-
perm_handler = self._permission_handler
1265-
if not perm_handler:
1266-
return # This client doesn't handle permissions; another client will.
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.
12671271

1268-
asyncio.ensure_future(
1269-
self._execute_permission_and_respond(request_id, permission_request, perm_handler)
1270-
)
1272+
asyncio.ensure_future(
1273+
self._execute_permission_and_respond(request_id, permission_request, perm_handler)
1274+
)
12711275

1272-
elif isinstance(data, CommandExecuteData):
1273-
request_id = data.request_id
1274-
command_name = data.command_name
1275-
command = data.command
1276-
args = data.args
1277-
if not request_id or not command_name:
1278-
return
1279-
asyncio.ensure_future(
1280-
self._execute_command_and_respond(
1281-
request_id, command_name, command or "", args or ""
1276+
case CommandExecuteData() as data:
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 ""
1286+
)
12821287
)
1283-
)
12841288

1285-
elif isinstance(data, ElicitationRequestedData):
1286-
with self._elicitation_handler_lock:
1287-
handler = self._elicitation_handler
1288-
if not handler:
1289-
return
1290-
request_id = data.request_id
1291-
if not request_id:
1292-
return
1293-
context: ElicitationContext = {
1294-
"session_id": self.session_id,
1295-
"message": data.message or "",
1296-
}
1297-
if data.requested_schema is not None:
1298-
context["requestedSchema"] = data.requested_schema.to_dict()
1299-
if data.mode is not None:
1300-
context["mode"] = data.mode.value
1301-
if data.elicitation_source is not None:
1302-
context["elicitationSource"] = data.elicitation_source
1303-
if data.url is not None:
1304-
context["url"] = data.url
1305-
asyncio.ensure_future(self._handle_elicitation_request(context, request_id))
1306-
1307-
elif isinstance(data, CapabilitiesChangedData):
1308-
cap: SessionCapabilities = {}
1309-
if data.ui is not None:
1310-
ui_cap: SessionUiCapabilities = {}
1311-
if data.ui.elicitation is not None:
1312-
ui_cap["elicitation"] = data.ui.elicitation
1313-
cap["ui"] = ui_cap
1314-
self._capabilities = {**self._capabilities, **cap}
1289+
case ElicitationRequestedData() as data:
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+
case CapabilitiesChangedData() as data:
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}
13151319

13161320
async def _execute_tool_and_respond(
13171321
self,
@@ -1803,8 +1807,9 @@ async def get_messages(self) -> list[SessionEvent]:
18031807
>>> from copilot.generated.session_events import AssistantMessageData
18041808
>>> events = await session.get_messages()
18051809
>>> for event in events:
1806-
... if isinstance(event.data, AssistantMessageData):
1807-
... print(f"Assistant: {event.data.content}")
1810+
... match event.data:
1811+
... case AssistantMessageData() as data:
1812+
... print(f"Assistant: {data.content}")
18081813
"""
18091814
response = await self._client.request("session.getMessages", {"sessionId": self.session_id})
18101815
# Convert dict events to SessionEvent objects

0 commit comments

Comments
 (0)