Skip to content

Commit 4b1fbcd

Browse files
stephentoubCopilot
andcommitted
Handle mixed schema definitions in codegen
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 63235c4 commit 4b1fbcd

5 files changed

Lines changed: 68 additions & 44 deletions

File tree

scripts/codegen/csharp.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ let sessionDefinitions: Record<string, JSONSchema7Definition> = {};
326326
}
327327

328328
function extractEventVariants(schema: JSONSchema7): EventVariant[] {
329-
const sessionEvent = schema.definitions?.SessionEvent as JSONSchema7;
329+
const sessionEvent = collectDefinitions(schema as Record<string, unknown>).SessionEvent as JSONSchema7;
330330
if (!sessionEvent?.anyOf) throw new Error("Schema must have SessionEvent definition with anyOf");
331331

332332
return sessionEvent.anyOf
@@ -611,14 +611,15 @@ function generateDataClass(variant: EventVariant, knownTypes: Map<string, string
611611

612612
function generateSessionEventsCode(schema: JSONSchema7): string {
613613
generatedEnums.clear();
614-
sessionDefinitions = schema.definitions as Record<string, JSONSchema7Definition> || {};
614+
sessionDefinitions = collectDefinitions(schema as Record<string, unknown>);
615615
const variants = extractEventVariants(schema);
616616
const knownTypes = new Map<string, string>();
617617
const nestedClasses = new Map<string, string>();
618618
const enumOutput: string[] = [];
619619

620620
// Extract descriptions for base class properties from the first variant
621-
const firstVariant = (schema.definitions?.SessionEvent as JSONSchema7)?.anyOf?.[0];
621+
const sessionEventDefinition = sessionDefinitions.SessionEvent;
622+
const firstVariant = typeof sessionEventDefinition === "object" ? (sessionEventDefinition as JSONSchema7).anyOf?.[0] : undefined;
622623
const baseProps = typeof firstVariant === "object" && firstVariant?.properties ? firstVariant.properties : {};
623624
const baseDesc = (name: string) => {
624625
const prop = baseProps[name];

scripts/codegen/go.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
postProcessSchema,
2424
writeGeneratedFile,
2525
collectDefinitions,
26+
withSharedDefinitions,
2627
refTypeName,
2728
resolveRef,
2829
type ApiSchema,
@@ -881,10 +882,12 @@ async function generateRpc(schemaPath?: string): Promise<void> {
881882
// Build a combined schema for quicktype — prefix types to avoid conflicts.
882883
// Include shared definitions from the API schema for $ref resolution.
883884
const sharedDefs = collectDefinitions(schema as Record<string, unknown>);
884-
const combinedSchema: JSONSchema7 = {
885-
$schema: "http://json-schema.org/draft-07/schema#",
886-
definitions: { ...sharedDefs },
887-
};
885+
const combinedSchema = withSharedDefinitions(
886+
{
887+
$schema: "http://json-schema.org/draft-07/schema#",
888+
},
889+
sharedDefs
890+
);
888891

889892
for (const method of allMethods) {
890893
if (isVoidSchema(method.result)) {
@@ -918,13 +921,11 @@ async function generateRpc(schemaPath?: string): Promise<void> {
918921
// Generate types via quicktype
919922
const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
920923
for (const [name, def] of Object.entries(rootDefinitions)) {
921-
await schemaInput.addSource({
922-
name,
923-
schema: JSON.stringify({
924-
...def,
925-
definitions: allDefinitions,
926-
}),
927-
});
924+
const schemaWithDefs = withSharedDefinitions(
925+
typeof def === "object" ? (def as JSONSchema7) : {},
926+
allDefinitions
927+
);
928+
await schemaInput.addSource({ name, schema: JSON.stringify(schemaWithDefs) });
928929
}
929930

930931
const inputData = new InputData();

scripts/codegen/python.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
isNodeFullyExperimental,
2323
postProcessSchema,
2424
writeGeneratedFile,
25+
collectDefinitions,
26+
withSharedDefinitions,
2527
type ApiSchema,
2628
type RpcMethod,
2729
} from "./utils.js";
@@ -1275,10 +1277,12 @@ async function generateRpc(schemaPath?: string): Promise<void> {
12751277

12761278
// Build a combined schema for quicktype, including shared definitions from the API schema
12771279
const sharedDefs = collectDefinitions(schema as Record<string, unknown>);
1278-
const combinedSchema: JSONSchema7 = {
1279-
$schema: "http://json-schema.org/draft-07/schema#",
1280-
definitions: { ...sharedDefs },
1281-
};
1280+
const combinedSchema = withSharedDefinitions(
1281+
{
1282+
$schema: "http://json-schema.org/draft-07/schema#",
1283+
},
1284+
sharedDefs
1285+
);
12821286

12831287
for (const method of allMethods) {
12841288
if (!isVoidSchema(method.result)) {
@@ -1308,13 +1312,11 @@ async function generateRpc(schemaPath?: string): Promise<void> {
13081312
// Generate types via quicktype
13091313
const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
13101314
for (const [name, def] of Object.entries(rootDefinitions)) {
1311-
await schemaInput.addSource({
1312-
name,
1313-
schema: JSON.stringify({
1314-
...def,
1315-
definitions: allDefinitions,
1316-
}),
1317-
});
1315+
const schemaWithDefs = withSharedDefinitions(
1316+
typeof def === "object" ? (def as JSONSchema7) : {},
1317+
allDefinitions
1318+
);
1319+
await schemaInput.addSource({ name, schema: JSON.stringify(schemaWithDefs) });
13181320
}
13191321

13201322
const inputData = new InputData();

scripts/codegen/typescript.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
postProcessSchema,
1717
writeGeneratedFile,
1818
collectDefinitions,
19+
withSharedDefinitions,
1920
isRpcMethod,
2021
isNodeFullyExperimental,
2122
isVoidSchema,
@@ -180,11 +181,13 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js";
180181
// Build a single combined schema with shared definitions and all method types.
181182
// This ensures $ref-referenced types are generated exactly once.
182183
const sharedDefs = collectDefinitions(schema as Record<string, unknown>);
183-
const combinedSchema: JSONSchema7 = {
184-
$schema: "http://json-schema.org/draft-07/schema#",
185-
type: "object",
186-
definitions: { ...sharedDefs },
187-
};
184+
const combinedSchema = withSharedDefinitions(
185+
{
186+
$schema: "http://json-schema.org/draft-07/schema#",
187+
type: "object",
188+
},
189+
sharedDefs
190+
);
188191

189192
// Track which type names come from experimental methods for JSDoc annotations.
190193
const experimentalTypes = new Set<string>();

scripts/codegen/utils.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ const __dirname = path.dirname(__filename);
2121
/** Root of the copilot-sdk repo */
2222
export const REPO_ROOT = path.resolve(__dirname, "../..");
2323

24+
/** Event types to exclude from generation (internal/legacy types) */
25+
export const EXCLUDED_EVENT_TYPES = new Set(["session.import_legacy"]);
26+
27+
export interface JSONSchema7WithDefs extends JSONSchema7 {
28+
$defs?: Record<string, JSONSchema7Definition>;
29+
}
30+
31+
export type SchemaWithSharedDefinitions<T extends JSONSchema7 = JSONSchema7> = T & {
32+
definitions: Record<string, JSONSchema7Definition>;
33+
$defs: Record<string, JSONSchema7Definition>;
34+
};
2435
// ── Schema paths ────────────────────────────────────────────────────────────
2536

2637
export async function getSessionEventsSchemaPath(): Promise<string> {
@@ -51,16 +62,7 @@ export async function getApiSchemaPath(cliArg?: string): Promise<string> {
5162
export function postProcessSchema(schema: JSONSchema7): JSONSchema7 {
5263
if (typeof schema !== "object" || schema === null) return schema;
5364

54-
const processed: JSONSchema7 = { ...schema };
55-
56-
// Normalize $defs → definitions for draft 2019+ compatibility
57-
if ("$defs" in processed && !processed.definitions) {
58-
processed.definitions = (processed as Record<string, unknown>).$defs as Record<
59-
string,
60-
JSONSchema7Definition
61-
>;
62-
delete (processed as Record<string, unknown>).$defs;
63-
}
65+
const processed = { ...schema } as JSONSchema7WithDefs;
6466

6567
if ("const" in processed && typeof processed.const === "boolean") {
6668
processed.enum = [processed.const];
@@ -93,12 +95,14 @@ export function postProcessSchema(schema: JSONSchema7): JSONSchema7 {
9395
}
9496
}
9597

96-
if (processed.definitions) {
98+
const definitions = collectDefinitions(processed as Record<string, unknown>);
99+
if (Object.keys(definitions).length > 0) {
97100
const newDefs: Record<string, JSONSchema7Definition> = {};
98-
for (const [key, value] of Object.entries(processed.definitions)) {
101+
for (const [key, value] of Object.entries(definitions)) {
99102
newDefs[key] = typeof value === "object" ? postProcessSchema(value as JSONSchema7) : value;
100103
}
101104
processed.definitions = newDefs;
105+
processed.$defs = newDefs;
102106
}
103107

104108
if (typeof processed.additionalProperties === "object") {
@@ -339,6 +343,19 @@ export function resolveRef(
339343
export function collectDefinitions(
340344
schema: Record<string, unknown>
341345
): Record<string, JSONSchema7Definition> {
342-
const defs = (schema.definitions ?? schema.$defs ?? {}) as Record<string, JSONSchema7Definition>;
343-
return { ...defs }
346+
const legacyDefinitions = (schema.definitions ?? {}) as Record<string, JSONSchema7Definition>;
347+
const draft2019Definitions = (schema.$defs ?? {}) as Record<string, JSONSchema7Definition>;
348+
return { ...draft2019Definitions, ...legacyDefinitions };
349+
}
350+
351+
export function withSharedDefinitions<T extends JSONSchema7>(
352+
schema: T,
353+
definitions: Record<string, JSONSchema7Definition>
354+
): SchemaWithSharedDefinitions<T> {
355+
const sharedDefinitions = { ...definitions };
356+
return {
357+
...schema,
358+
definitions: sharedDefinitions,
359+
$defs: sharedDefinitions,
360+
};
344361
}

0 commit comments

Comments
 (0)