Skip to content

Commit 6722743

Browse files
stephentoubCopilot
andauthored
Generate dedicated Python session event types (#1063)
* Generate dedicated Python session event types Align Python session event generation with the newer Go-style dedicated per-event payload model instead of the old merged quicktype Data shape. This updates the runtime/tests for typed payloads while preserving compatibility aliases and legacy Data behavior for existing callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review and CI follow-ups Fix the generated Python docstring escaping that CodeQL flagged, correct dotted-key normalization in the Data compatibility shim, update the stale Go local-cli docs snippet for the newer typed event API, and apply the Python formatter change required by CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clarify typed Python event examples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Align Python generator formats Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix SDK test formatting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use runtime checks in Python README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use isinstance in Python event examples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use isinstance for broadcast events Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use match for Python event data Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Python SDK Ruff failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove Python session event shims Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve Python event helper types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Node.js lint and generated file drift Normalize trailing whitespace in pyDocstringLiteral to prevent cross-platform codegen drift, and reformat test file with Prettier. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Regenerate Python events and fix doc example - Regenerate session_events.py to match latest schema (removes session.import_legacy, adds reasoning_tokens to AssistantUsageData) - Update docs/setup/local-cli.md Python example to use match-based type narrowing with None check, consistent with python/README.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 68f46b3 commit 6722743

File tree

15 files changed

+5305
-3028
lines changed

15 files changed

+5305
-3028
lines changed

docs/setup/local-cli.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ await client.stop();
5454

5555
```python
5656
from copilot import CopilotClient
57+
from copilot.generated.session_events import AssistantMessageData
5758
from copilot.session import PermissionHandler
5859

5960
client = CopilotClient({
@@ -63,7 +64,10 @@ await client.start()
6364

6465
session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="gpt-4.1")
6566
response = await session.send_and_wait("Hello!")
66-
print(response.data.content)
67+
if response:
68+
match response.data:
69+
case AssistantMessageData() as data:
70+
print(data.content)
6771

6872
await client.stop()
6973
```
@@ -99,8 +103,10 @@ func main() {
99103

100104
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"})
101105
response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"})
102-
if d, ok := response.Data.(*copilot.AssistantMessageData); ok {
103-
fmt.Println(d.Content)
106+
if response != nil {
107+
if d, ok := response.Data.(*copilot.AssistantMessageData); ok {
108+
fmt.Println(d.Content)
109+
}
104110
}
105111
}
106112
```
@@ -117,8 +123,10 @@ defer client.Stop()
117123

118124
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"})
119125
response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"})
120-
if d, ok := response.Data.(*copilot.AssistantMessageData); ok {
121-
fmt.Println(d.Content)
126+
if response != nil {
127+
if d, ok := response.Data.(*copilot.AssistantMessageData); ok {
128+
fmt.Println(d.Content)
129+
}
122130
}
123131
```
124132

nodejs/test/python-codegen.test.ts

Lines changed: 329 additions & 0 deletions
Large diffs are not rendered by default.

python/README.md

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ python chat.py
2525

2626
```python
2727
import asyncio
28+
2829
from copilot import CopilotClient
29-
from copilot.session import PermissionHandler
30+
from copilot.generated.session_events import AssistantMessageData, SessionIdleData
3031

3132
async def main():
3233
# Client automatically starts on enter and cleans up on exit
@@ -37,10 +38,11 @@ async def main():
3738
done = asyncio.Event()
3839

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

4547
session.on(on_event)
4648

@@ -57,7 +59,9 @@ If you need more control over the lifecycle, you can call `start()`, `stop()`, a
5759

5860
```python
5961
import asyncio
62+
6063
from copilot import CopilotClient
64+
from copilot.generated.session_events import AssistantMessageData, SessionIdleData
6165
from copilot.session import PermissionHandler
6266

6367
async def main():
@@ -73,10 +77,11 @@ async def main():
7377
done = asyncio.Event()
7478

7579
def on_event(event):
76-
if event.type.value == "assistant.message":
77-
print(event.data.content)
78-
elif event.type.value == "session.idle":
79-
done.set()
80+
match event.data:
81+
case AssistantMessageData() as data:
82+
print(data.content)
83+
case SessionIdleData():
84+
done.set()
8085

8186
session.on(on_event)
8287
await session.send("What is 2+2?")
@@ -333,7 +338,15 @@ Enable streaming to receive assistant response chunks as they're generated:
333338

334339
```python
335340
import asyncio
341+
336342
from copilot import CopilotClient
343+
from copilot.generated.session_events import (
344+
AssistantMessageData,
345+
AssistantMessageDeltaData,
346+
AssistantReasoningData,
347+
AssistantReasoningDeltaData,
348+
SessionIdleData,
349+
)
337350
from copilot.session import PermissionHandler
338351

339352
async def main():
@@ -347,24 +360,24 @@ async def main():
347360
done = asyncio.Event()
348361

349362
def on_event(event):
350-
match event.type.value:
351-
case "assistant.message_delta":
363+
match event.data:
364+
case AssistantMessageDeltaData() as data:
352365
# Streaming message chunk - print incrementally
353-
delta = event.data.delta_content or ""
366+
delta = data.delta_content or ""
354367
print(delta, end="", flush=True)
355-
case "assistant.reasoning_delta":
368+
case AssistantReasoningDeltaData() as data:
356369
# Streaming reasoning chunk (if model supports reasoning)
357-
delta = event.data.delta_content or ""
370+
delta = data.delta_content or ""
358371
print(delta, end="", flush=True)
359-
case "assistant.message":
372+
case AssistantMessageData() as data:
360373
# Final message - complete content
361374
print("\n--- Final message ---")
362-
print(event.data.content)
363-
case "assistant.reasoning":
375+
print(data.content)
376+
case AssistantReasoningData() as data:
364377
# Final reasoning content (if model supports reasoning)
365378
print("--- Reasoning ---")
366-
print(event.data.content)
367-
case "session.idle":
379+
print(data.content)
380+
case SessionIdleData():
368381
# Session finished processing
369382
done.set()
370383

@@ -547,7 +560,9 @@ Provide your own function to inspect each request and apply custom logic (sync o
547560
from copilot.session import PermissionRequestResult
548561
from copilot.generated.session_events import PermissionRequest
549562

550-
def on_permission_request(request: PermissionRequest, invocation: dict) -> PermissionRequestResult:
563+
def on_permission_request(
564+
request: PermissionRequest, invocation: dict
565+
) -> PermissionRequestResult:
551566
# request.kind — what type of operation is being requested:
552567
# "shell" — executing a shell command
553568
# "write" — writing or editing a file
@@ -577,7 +592,9 @@ session = await client.create_session(
577592
Async handlers are also supported:
578593

579594
```python
580-
async def on_permission_request(request: PermissionRequest, invocation: dict) -> PermissionRequestResult:
595+
async def on_permission_request(
596+
request: PermissionRequest, invocation: dict
597+
) -> PermissionRequestResult:
581598
# Simulate an async approval check (e.g., prompting a user over a network)
582599
await asyncio.sleep(0)
583600
return PermissionRequestResult(kind="approved")

python/copilot/client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@
3737
ServerRpc,
3838
register_client_session_api_handlers,
3939
)
40-
from .generated.session_events import PermissionRequest, SessionEvent, session_event_from_dict
40+
from .generated.session_events import (
41+
PermissionRequest,
42+
SessionEvent,
43+
session_event_from_dict,
44+
)
4145
from .session import (
4246
CommandDefinition,
4347
CopilotSession,

0 commit comments

Comments
 (0)