Skip to content

Commit 698dc0d

Browse files
stephentoubCopilot
andcommitted
Preserve Python event helper types
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a98118d commit 698dc0d

9 files changed

Lines changed: 575 additions & 266 deletions

File tree

nodejs/test/python-codegen.test.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,239 @@ describe("python session event codegen", () => {
8282
expect(code).toContain("encoded: str");
8383
expect(code).toContain("count: int");
8484
});
85+
86+
it("preserves key shortened nested type names", () => {
87+
const schema: JSONSchema7 = {
88+
definitions: {
89+
SessionEvent: {
90+
anyOf: [
91+
{
92+
type: "object",
93+
required: ["type", "data"],
94+
properties: {
95+
type: { const: "permission.requested" },
96+
data: {
97+
type: "object",
98+
required: ["requestId", "permissionRequest"],
99+
properties: {
100+
requestId: { type: "string" },
101+
permissionRequest: {
102+
anyOf: [
103+
{
104+
type: "object",
105+
required: [
106+
"kind",
107+
"fullCommandText",
108+
"intention",
109+
"commands",
110+
"possiblePaths",
111+
"possibleUrls",
112+
"hasWriteFileRedirection",
113+
"canOfferSessionApproval",
114+
],
115+
properties: {
116+
kind: { const: "shell", type: "string" },
117+
fullCommandText: { type: "string" },
118+
intention: { type: "string" },
119+
commands: {
120+
type: "array",
121+
items: {
122+
type: "object",
123+
required: ["identifier", "readOnly"],
124+
properties: {
125+
identifier: { type: "string" },
126+
readOnly: { type: "boolean" },
127+
},
128+
},
129+
},
130+
possiblePaths: {
131+
type: "array",
132+
items: { type: "string" },
133+
},
134+
possibleUrls: {
135+
type: "array",
136+
items: {
137+
type: "object",
138+
required: ["url"],
139+
properties: { url: { type: "string" } },
140+
},
141+
},
142+
hasWriteFileRedirection: { type: "boolean" },
143+
canOfferSessionApproval: { type: "boolean" },
144+
},
145+
},
146+
{
147+
type: "object",
148+
required: ["kind", "fact"],
149+
properties: {
150+
kind: { const: "memory", type: "string" },
151+
fact: { type: "string" },
152+
action: {
153+
type: "string",
154+
enum: ["store", "vote"],
155+
default: "store",
156+
},
157+
direction: {
158+
type: "string",
159+
enum: ["upvote", "downvote"],
160+
},
161+
},
162+
},
163+
],
164+
},
165+
},
166+
},
167+
},
168+
},
169+
{
170+
type: "object",
171+
required: ["type", "data"],
172+
properties: {
173+
type: { const: "elicitation.requested" },
174+
data: {
175+
type: "object",
176+
properties: {
177+
requestedSchema: {
178+
type: "object",
179+
required: ["type", "properties"],
180+
properties: {
181+
type: { const: "object", type: "string" },
182+
properties: {
183+
type: "object",
184+
additionalProperties: {},
185+
},
186+
},
187+
},
188+
mode: {
189+
type: "string",
190+
enum: ["form", "url"],
191+
},
192+
},
193+
},
194+
},
195+
},
196+
{
197+
type: "object",
198+
required: ["type", "data"],
199+
properties: {
200+
type: { const: "capabilities.changed" },
201+
data: {
202+
type: "object",
203+
properties: {
204+
ui: {
205+
type: "object",
206+
properties: {
207+
elicitation: { type: "boolean" },
208+
},
209+
},
210+
},
211+
},
212+
},
213+
},
214+
],
215+
},
216+
},
217+
};
218+
219+
const code = generatePythonSessionEventsCode(schema);
220+
221+
expect(code).toContain("class PermissionRequest:");
222+
expect(code).toContain("class PermissionRequestShellCommand:");
223+
expect(code).toContain("class PermissionRequestShellPossibleURL:");
224+
expect(code).toContain("class PermissionRequestMemoryAction(Enum):");
225+
expect(code).toContain("class PermissionRequestMemoryDirection(Enum):");
226+
expect(code).toContain("class ElicitationRequestedSchema:");
227+
expect(code).toContain("class ElicitationRequestedMode(Enum):");
228+
expect(code).toContain("class CapabilitiesChangedUI:");
229+
expect(code).not.toContain("class PermissionRequestedDataPermissionRequest:");
230+
expect(code).not.toContain("class ElicitationRequestedDataRequestedSchema:");
231+
expect(code).not.toContain("class CapabilitiesChangedDataUi:");
232+
});
233+
234+
it("keeps distinct enum types even when they share the same values", () => {
235+
const schema: JSONSchema7 = {
236+
definitions: {
237+
SessionEvent: {
238+
anyOf: [
239+
{
240+
type: "object",
241+
required: ["type", "data"],
242+
properties: {
243+
type: { const: "assistant.message" },
244+
data: {
245+
type: "object",
246+
properties: {
247+
toolRequests: {
248+
type: "array",
249+
items: {
250+
type: "object",
251+
required: ["toolCallId", "name", "type"],
252+
properties: {
253+
toolCallId: { type: "string" },
254+
name: { type: "string" },
255+
type: {
256+
type: "string",
257+
enum: ["function", "custom"],
258+
},
259+
},
260+
},
261+
},
262+
},
263+
},
264+
},
265+
},
266+
{
267+
type: "object",
268+
required: ["type", "data"],
269+
properties: {
270+
type: { const: "session.import_legacy" },
271+
data: {
272+
type: "object",
273+
properties: {
274+
legacySession: {
275+
type: "object",
276+
properties: {
277+
chatMessages: {
278+
type: "array",
279+
items: {
280+
type: "object",
281+
properties: {
282+
toolCalls: {
283+
type: "array",
284+
items: {
285+
type: "object",
286+
properties: {
287+
type: {
288+
type: "string",
289+
enum: [
290+
"function",
291+
"custom",
292+
],
293+
},
294+
},
295+
},
296+
},
297+
},
298+
},
299+
},
300+
},
301+
},
302+
},
303+
},
304+
},
305+
},
306+
],
307+
},
308+
},
309+
};
310+
311+
const code = generatePythonSessionEventsCode(schema);
312+
313+
expect(code).toContain("class AssistantMessageToolRequestType(Enum):");
314+
expect(code).toContain("type: AssistantMessageToolRequestType");
315+
expect(code).toContain("parse_enum(AssistantMessageToolRequestType,");
316+
expect(code).toContain(
317+
"class SessionImportLegacyDataLegacySessionChatMessagesItemToolCallsItemType(Enum):"
318+
);
319+
});
85320
});

python/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -558,10 +558,10 @@ Provide your own function to inspect each request and apply custom logic (sync o
558558

559559
```python
560560
from copilot.session import PermissionRequestResult
561-
from copilot.generated.session_events import PermissionRequestedDataPermissionRequest
561+
from copilot.generated.session_events import PermissionRequest
562562

563563
def on_permission_request(
564-
request: PermissionRequestedDataPermissionRequest, invocation: dict
564+
request: PermissionRequest, invocation: dict
565565
) -> PermissionRequestResult:
566566
# request.kind — what type of operation is being requested:
567567
# "shell" — executing a shell command
@@ -593,7 +593,7 @@ Async handlers are also supported:
593593

594594
```python
595595
async def on_permission_request(
596-
request: PermissionRequestedDataPermissionRequest, invocation: dict
596+
request: PermissionRequest, invocation: dict
597597
) -> PermissionRequestResult:
598598
# Simulate an async approval check (e.g., prompting a user over a network)
599599
await asyncio.sleep(0)

python/copilot/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
register_client_session_api_handlers,
3939
)
4040
from .generated.session_events import (
41-
PermissionRequestedDataPermissionRequest,
41+
PermissionRequest,
4242
SessionEvent,
4343
session_event_from_dict,
4444
)
@@ -2633,7 +2633,7 @@ async def _handle_permission_request_v2(self, params: dict) -> dict:
26332633
raise ValueError(f"unknown session {session_id}")
26342634

26352635
try:
2636-
perm_request = PermissionRequestedDataPermissionRequest.from_dict(permission_request)
2636+
perm_request = PermissionRequest.from_dict(permission_request)
26372637
result = await session._handle_permission_request(perm_request)
26382638
if result.kind == "no-result":
26392639
raise ValueError(NO_RESULT_PERMISSION_V2_ERROR)

0 commit comments

Comments
 (0)