Skip to content

Commit 52beae2

Browse files
fix: avoid .NET RPC schema collisions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e11be79 commit 52beae2

File tree

2 files changed

+83
-7
lines changed

2 files changed

+83
-7
lines changed

dotnet/src/Generated/Rpc.cs

Lines changed: 52 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/codegen/csharp.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,7 @@ export async function generateSessionEvents(schemaPath?: string): Promise<void>
596596
// ══════════════════════════════════════════════════════════════════════════════
597597

598598
let emittedRpcClasses = new Set<string>();
599+
let emittedRpcClassSchemas = new Map<string, string>();
599600
let experimentalRpcTypes = new Set<string>();
600601
let rpcKnownTypes = new Map<string, string>();
601602
let rpcEnumOutput: string[] = [];
@@ -616,6 +617,31 @@ function paramsTypeName(rpcMethod: string): string {
616617
return `${typeToClassName(rpcMethod)}Params`;
617618
}
618619

620+
function stableStringify(value: unknown): string {
621+
if (Array.isArray(value)) {
622+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
623+
}
624+
if (value && typeof value === "object") {
625+
const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b));
626+
return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`).join(",")}}`;
627+
}
628+
return JSON.stringify(value);
629+
}
630+
631+
function chooseRpcClassName(preferredName: string, fallbackName: string, schema: JSONSchema7): string {
632+
const schemaKey = stableStringify(schema);
633+
const existingPreferred = emittedRpcClassSchemas.get(preferredName);
634+
if (!existingPreferred || existingPreferred === schemaKey) return preferredName;
635+
636+
let candidate = fallbackName;
637+
let suffix = 2;
638+
while (true) {
639+
const existing = emittedRpcClassSchemas.get(candidate);
640+
if (!existing || existing === schemaKey) return candidate;
641+
candidate = `${fallbackName}${suffix++}`;
642+
}
643+
}
644+
619645
function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassName: string, propName: string, classes: string[]): string {
620646
// Handle anyOf: [T, null] → T? (nullable typed property)
621647
if (schema.anyOf) {
@@ -638,7 +664,9 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam
638664
if (schema.type === "array" && schema.items) {
639665
const items = schema.items as JSONSchema7;
640666
if (items.type === "object" && items.properties) {
641-
const itemClass = singularPascal(propName);
667+
const defaultName = (items.title as string) ?? singularPascal(propName);
668+
const contextualName = `${parentClassName}${defaultName}`;
669+
const itemClass = chooseRpcClassName(defaultName, contextualName, items);
642670
if (!emittedRpcClasses.has(itemClass)) classes.push(emitRpcClass(itemClass, items, "public", classes));
643671
return isRequired ? `List<${itemClass}>` : `List<${itemClass}>?`;
644672
}
@@ -661,6 +689,7 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam
661689
function emitRpcClass(className: string, schema: JSONSchema7, visibility: "public" | "internal", extraClasses: string[]): string {
662690
if (emittedRpcClasses.has(className)) return "";
663691
emittedRpcClasses.add(className);
692+
emittedRpcClassSchemas.set(className, stableStringify(schema));
664693

665694
const requiredSet = new Set(schema.required || []);
666695
const lines: string[] = [];
@@ -1061,6 +1090,7 @@ function emitClientSessionApiRegistration(clientSchema: Record<string, unknown>,
10611090

10621091
function generateRpcCode(schema: ApiSchema): string {
10631092
emittedRpcClasses.clear();
1093+
emittedRpcClassSchemas.clear();
10641094
experimentalRpcTypes.clear();
10651095
rpcKnownTypes.clear();
10661096
rpcEnumOutput = [];

0 commit comments

Comments
 (0)