@@ -12,13 +12,15 @@ import path from "path";
1212import { promisify } from "util" ;
1313import type { JSONSchema7 } from "json-schema" ;
1414import {
15- getSessionEventsSchemaPath ,
15+ applyTitleSuggestions ,
1616 getApiSchemaPath ,
17- writeGeneratedFile ,
18- isRpcMethod ,
17+ getRpcSchemaTypeName ,
18+ getSessionEventsSchemaPath ,
1919 isNodeFullyExperimental ,
20+ isRpcMethod ,
2021 EXCLUDED_EVENT_TYPES ,
2122 REPO_ROOT ,
23+ writeGeneratedFile ,
2224 type ApiSchema ,
2325 type RpcMethod ,
2426} from "./utils.js" ;
@@ -199,12 +201,12 @@ interface EventVariant {
199201
200202let generatedEnums = new Map < string , { enumName : string ; values : string [ ] } > ( ) ;
201203
202- function getOrCreateEnum ( parentClassName : string , propName : string , values : string [ ] , enumOutput : string [ ] , description ?: string ) : string {
204+ function getOrCreateEnum ( parentClassName : string , propName : string , values : string [ ] , enumOutput : string [ ] , description ?: string , explicitName ?: string ) : string {
203205 const valuesKey = [ ...values ] . sort ( ) . join ( "|" ) ;
204206 for ( const [ , existing ] of generatedEnums ) {
205207 if ( [ ...existing . values ] . sort ( ) . join ( "|" ) === valuesKey ) return existing . enumName ;
206208 }
207- const enumName = `${ parentClassName } ${ propName } ` ;
209+ const enumName = explicitName ?? `${ parentClassName } ${ propName } ` ;
208210 generatedEnums . set ( enumName , { enumName, values } ) ;
209211
210212 const lines : string [ ] = [ ] ;
@@ -413,7 +415,7 @@ function resolveSessionPropertyType(
413415 const variants = nonNull as JSONSchema7 [ ] ;
414416 const discriminatorInfo = findDiscriminator ( variants ) ;
415417 if ( discriminatorInfo ) {
416- const baseClassName = `${ parentClassName } ${ propName } ` ;
418+ const baseClassName = ( propSchema . title as string ) ?? `${ parentClassName } ${ propName } ` ;
417419 const renamedBase = applyTypeRename ( baseClassName ) ;
418420 const polymorphicCode = generatePolymorphicClasses ( baseClassName , discriminatorInfo . property , variants , knownTypes , nestedClasses , enumOutput , propSchema . description ) ;
419421 nestedClasses . set ( renamedBase , polymorphicCode ) ;
@@ -423,11 +425,11 @@ function resolveSessionPropertyType(
423425 return hasNull || ! isRequired ? "object?" : "object" ;
424426 }
425427 if ( propSchema . enum && Array . isArray ( propSchema . enum ) ) {
426- const enumName = getOrCreateEnum ( parentClassName , propName , propSchema . enum as string [ ] , enumOutput , propSchema . description ) ;
428+ const enumName = getOrCreateEnum ( parentClassName , propName , propSchema . enum as string [ ] , enumOutput , propSchema . description , propSchema . title as string | undefined ) ;
427429 return isRequired ? enumName : `${ enumName } ?` ;
428430 }
429431 if ( propSchema . type === "object" && propSchema . properties ) {
430- const nestedClassName = `${ parentClassName } ${ propName } ` ;
432+ const nestedClassName = ( propSchema . title as string ) ?? `${ parentClassName } ${ propName } ` ;
431433 nestedClasses . set ( nestedClassName , generateNestedClass ( nestedClassName , propSchema , knownTypes , nestedClasses , enumOutput ) ) ;
432434 return isRequired ? nestedClassName : `${ nestedClassName } ?` ;
433435 }
@@ -438,20 +440,20 @@ function resolveSessionPropertyType(
438440 const variants = items . anyOf . filter ( ( v ) : v is JSONSchema7 => typeof v === "object" ) ;
439441 const discriminatorInfo = findDiscriminator ( variants ) ;
440442 if ( discriminatorInfo ) {
441- const baseClassName = `${ parentClassName } ${ propName } Item` ;
443+ const baseClassName = ( items . title as string ) ?? `${ parentClassName } ${ propName } Item` ;
442444 const renamedBase = applyTypeRename ( baseClassName ) ;
443445 const polymorphicCode = generatePolymorphicClasses ( baseClassName , discriminatorInfo . property , variants , knownTypes , nestedClasses , enumOutput , items . description ) ;
444446 nestedClasses . set ( renamedBase , polymorphicCode ) ;
445447 return isRequired ? `${ renamedBase } []` : `${ renamedBase } []?` ;
446448 }
447449 }
448450 if ( items . type === "object" && items . properties ) {
449- const itemClassName = `${ parentClassName } ${ propName } Item` ;
451+ const itemClassName = ( items . title as string ) ?? `${ parentClassName } ${ propName } Item` ;
450452 nestedClasses . set ( itemClassName , generateNestedClass ( itemClassName , items , knownTypes , nestedClasses , enumOutput ) ) ;
451453 return isRequired ? `${ itemClassName } []` : `${ itemClassName } []?` ;
452454 }
453455 if ( items . enum && Array . isArray ( items . enum ) ) {
454- const enumName = getOrCreateEnum ( parentClassName , `${ propName } Item` , items . enum as string [ ] , enumOutput , items . description ) ;
456+ const enumName = getOrCreateEnum ( parentClassName , `${ propName } Item` , items . enum as string [ ] , enumOutput , items . description , items . title as string | undefined ) ;
455457 return isRequired ? `${ enumName } []` : `${ enumName } []?` ;
456458 }
457459 const itemType = schemaTypeToCSharp ( items , true , knownTypes ) ;
@@ -584,7 +586,7 @@ namespace GitHub.Copilot.SDK;
584586export async function generateSessionEvents ( schemaPath ?: string ) : Promise < void > {
585587 console . log ( "C#: generating session-events..." ) ;
586588 const resolvedPath = schemaPath ?? ( await getSessionEventsSchemaPath ( ) ) ;
587- const schema = JSON . parse ( await fs . readFile ( resolvedPath , "utf-8" ) ) as JSONSchema7 ;
589+ const schema = applyTitleSuggestions ( JSON . parse ( await fs . readFile ( resolvedPath , "utf-8" ) ) as JSONSchema7 ) ;
588590 const code = generateSessionEventsCode ( schema ) ;
589591 const outPath = await writeGeneratedFile ( "dotnet/src/Generated/SessionEvents.cs" , code ) ;
590592 console . log ( ` ✓ ${ outPath } ` ) ;
@@ -608,12 +610,12 @@ function singularPascal(s: string): string {
608610 return p ;
609611}
610612
611- function resultTypeName ( rpcMethod : string ) : string {
612- return `${ typeToClassName ( rpcMethod ) } Result` ;
613+ function resultTypeName ( method : RpcMethod ) : string {
614+ return getRpcSchemaTypeName ( method . result , `${ typeToClassName ( method . rpcMethod ) } Result` ) ;
613615}
614616
615- function paramsTypeName ( rpcMethod : string ) : string {
616- return `${ typeToClassName ( rpcMethod ) } Params` ;
617+ function paramsTypeName ( method : RpcMethod ) : string {
618+ return getRpcSchemaTypeName ( method . params , `${ typeToClassName ( method . rpcMethod ) } Request` ) ;
617619}
618620
619621function resolveRpcType ( schema : JSONSchema7 , isRequired : boolean , parentClassName : string , propName : string , classes : string [ ] ) : string {
@@ -627,7 +629,14 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam
627629 }
628630 // Handle enums (string unions like "interactive" | "plan" | "autopilot")
629631 if ( schema . enum && Array . isArray ( schema . enum ) ) {
630- const enumName = getOrCreateEnum ( parentClassName , propName , schema . enum as string [ ] , rpcEnumOutput , schema . description ) ;
632+ const enumName = getOrCreateEnum (
633+ parentClassName ,
634+ propName ,
635+ schema . enum as string [ ] ,
636+ rpcEnumOutput ,
637+ schema . description ,
638+ schema . title as string | undefined ,
639+ ) ;
631640 return isRequired ? enumName : `${ enumName } ?` ;
632641 }
633642 if ( schema . type === "object" && schema . properties ) {
@@ -638,17 +647,28 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam
638647 if ( schema . type === "array" && schema . items ) {
639648 const items = schema . items as JSONSchema7 ;
640649 if ( items . type === "object" && items . properties ) {
641- const itemClass = singularPascal ( propName ) ;
650+ const itemClass = ( items . title as string ) ?? singularPascal ( propName ) ;
642651 if ( ! emittedRpcClasses . has ( itemClass ) ) classes . push ( emitRpcClass ( itemClass , items , "public" , classes ) ) ;
643652 return isRequired ? `List<${ itemClass } >` : `List<${ itemClass } >?` ;
644653 }
654+ if ( items . enum && Array . isArray ( items . enum ) ) {
655+ const itemEnum = getOrCreateEnum (
656+ parentClassName ,
657+ `${ propName } Item` ,
658+ items . enum as string [ ] ,
659+ rpcEnumOutput ,
660+ items . description ,
661+ items . title as string | undefined ,
662+ ) ;
663+ return isRequired ? `List<${ itemEnum } >` : `List<${ itemEnum } >?` ;
664+ }
645665 const itemType = schemaTypeToCSharp ( items , true , rpcKnownTypes ) ;
646666 return isRequired ? `List<${ itemType } >` : `List<${ itemType } >?` ;
647667 }
648668 if ( schema . type === "object" && schema . additionalProperties && typeof schema . additionalProperties === "object" ) {
649669 const vs = schema . additionalProperties as JSONSchema7 ;
650670 if ( vs . type === "object" && vs . properties ) {
651- const valClass = `${ parentClassName } ${ propName } Value` ;
671+ const valClass = ( vs . title as string ) ?? `${ parentClassName } ${ propName } Value` ;
652672 classes . push ( emitRpcClass ( valClass , vs , "public" , classes ) ) ;
653673 return isRequired ? `Dictionary<string, ${ valClass } >` : `Dictionary<string, ${ valClass } >?` ;
654674 }
@@ -785,7 +805,7 @@ function emitServerInstanceMethod(
785805 groupExperimental : boolean
786806) : void {
787807 const methodName = toPascalCase ( name ) ;
788- const resultClassName = ` ${ typeToClassName ( method . rpcMethod ) } Result` ;
808+ const resultClassName = resultTypeName ( method ) ;
789809 if ( method . stability === "experimental" ) {
790810 experimentalRpcTypes . add ( resultClassName ) ;
791811 }
@@ -797,7 +817,7 @@ function emitServerInstanceMethod(
797817
798818 let requestClassName : string | null = null ;
799819 if ( paramEntries . length > 0 ) {
800- requestClassName = ` ${ typeToClassName ( method . rpcMethod ) } Request` ;
820+ requestClassName = paramsTypeName ( method ) ;
801821 if ( method . stability === "experimental" ) {
802822 experimentalRpcTypes . add ( requestClassName ) ;
803823 }
@@ -872,7 +892,7 @@ function emitSessionRpcClasses(node: Record<string, unknown>, classes: string[])
872892
873893function emitSessionMethod ( key : string , method : RpcMethod , lines : string [ ] , classes : string [ ] , indent : string , groupExperimental : boolean ) : void {
874894 const methodName = toPascalCase ( key ) ;
875- const resultClassName = ` ${ typeToClassName ( method . rpcMethod ) } Result` ;
895+ const resultClassName = resultTypeName ( method ) ;
876896 if ( method . stability === "experimental" ) {
877897 experimentalRpcTypes . add ( resultClassName ) ;
878898 }
@@ -889,7 +909,7 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
889909 return aReq - bReq ;
890910 } ) ;
891911
892- const requestClassName = ` ${ typeToClassName ( method . rpcMethod ) } Request` ;
912+ const requestClassName = paramsTypeName ( method ) ;
893913 if ( method . stability === "experimental" ) {
894914 experimentalRpcTypes . add ( requestClassName ) ;
895915 }
@@ -964,12 +984,12 @@ function emitClientSessionApiRegistration(clientSchema: Record<string, unknown>,
964984 for ( const { methods } of groups ) {
965985 for ( const method of methods ) {
966986 if ( method . result ) {
967- const resultClass = emitRpcClass ( resultTypeName ( method . rpcMethod ) , method . result , "public" , classes ) ;
987+ const resultClass = emitRpcClass ( resultTypeName ( method ) , method . result , "public" , classes ) ;
968988 if ( resultClass ) classes . push ( resultClass ) ;
969989 }
970990
971991 if ( method . params ?. properties && Object . keys ( method . params . properties ) . length > 0 ) {
972- const paramsClass = emitRpcClass ( paramsTypeName ( method . rpcMethod ) , method . params , "public" , classes ) ;
992+ const paramsClass = emitRpcClass ( paramsTypeName ( method ) , method . params , "public" , classes ) ;
973993 if ( paramsClass ) classes . push ( paramsClass ) ;
974994 }
975995 }
@@ -986,13 +1006,13 @@ function emitClientSessionApiRegistration(clientSchema: Record<string, unknown>,
9861006 lines . push ( `{` ) ;
9871007 for ( const method of methods ) {
9881008 const hasParams = method . params ?. properties && Object . keys ( method . params . properties ) . length > 0 ;
989- const taskType = method . result ? `Task<${ resultTypeName ( method . rpcMethod ) } >` : "Task" ;
1009+ const taskType = method . result ? `Task<${ resultTypeName ( method ) } >` : "Task" ;
9901010 lines . push ( ` /// <summary>Handles "${ method . rpcMethod } ".</summary>` ) ;
9911011 if ( method . stability === "experimental" && ! groupExperimental ) {
9921012 lines . push ( ` [Experimental(Diagnostics.Experimental)]` ) ;
9931013 }
9941014 if ( hasParams ) {
995- lines . push ( ` ${ taskType } ${ clientHandlerMethodName ( method . rpcMethod ) } (${ paramsTypeName ( method . rpcMethod ) } request, CancellationToken cancellationToken = default);` ) ;
1015+ lines . push ( ` ${ taskType } ${ clientHandlerMethodName ( method . rpcMethod ) } (${ paramsTypeName ( method ) } request, CancellationToken cancellationToken = default);` ) ;
9961016 } else {
9971017 lines . push ( ` ${ taskType } ${ clientHandlerMethodName ( method . rpcMethod ) } (CancellationToken cancellationToken = default);` ) ;
9981018 }
@@ -1028,8 +1048,8 @@ function emitClientSessionApiRegistration(clientSchema: Record<string, unknown>,
10281048 const handlerProperty = toPascalCase ( groupName ) ;
10291049 const handlerMethod = clientHandlerMethodName ( method . rpcMethod ) ;
10301050 const hasParams = method . params ?. properties && Object . keys ( method . params . properties ) . length > 0 ;
1031- const paramsClass = paramsTypeName ( method . rpcMethod ) ;
1032- const taskType = method . result ? `Task<${ resultTypeName ( method . rpcMethod ) } >` : "Task" ;
1051+ const paramsClass = paramsTypeName ( method ) ;
1052+ const taskType = method . result ? `Task<${ resultTypeName ( method ) } >` : "Task" ;
10331053 const registrationVar = `register${ typeToClassName ( method . rpcMethod ) } Method` ;
10341054
10351055 if ( hasParams ) {
@@ -1120,7 +1140,7 @@ internal static class Diagnostics
11201140export async function generateRpc ( schemaPath ?: string ) : Promise < void > {
11211141 console . log ( "C#: generating RPC types..." ) ;
11221142 const resolvedPath = schemaPath ?? ( await getApiSchemaPath ( ) ) ;
1123- const schema = JSON . parse ( await fs . readFile ( resolvedPath , "utf-8" ) ) as ApiSchema ;
1143+ const schema = applyTitleSuggestions ( JSON . parse ( await fs . readFile ( resolvedPath , "utf-8" ) ) as ApiSchema ) ;
11241144 const code = generateRpcCode ( schema ) ;
11251145 const outPath = await writeGeneratedFile ( "dotnet/src/Generated/Rpc.cs" , code ) ;
11261146 console . log ( ` ✓ ${ outPath } ` ) ;
0 commit comments