user defined built-in primitive schema name handling#26437
user defined built-in primitive schema name handling#26437daesunp wants to merge 1 commit intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for handling user-defined schema names that conflict with built-in primitive type names (string, number, boolean, null, and _OpaqueHandle). When users create schemas with names matching these built-in primitives, the system now automatically renames them with a numeric suffix (e.g., "null_1", "string_1") to prevent shadowing the primitive types in TypeScript type definitions and LLM prompts.
Changes:
- Modified
isNamedSchemafunction to accept aTreeNodeSchemaparameter instead of a string identifier, and checkschema.kind === NodeKind.Leafto identify primitive schemas - Enhanced
IdentifierCollisionResolverclass to accept a set of reserved names in its constructor and rename colliding schemas starting with_1suffix - Updated TypeScript type generation and prompt generation to use the collision resolver with reserved primitive type names
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/framework/tree-agent/src/utils.ts | Modified isNamedSchema to check schema kind instead of string matching; added reserved names support to IdentifierCollisionResolver; moved TreeNodeSchema import to base package |
| packages/framework/tree-agent/src/renderSchemaTypeScript.ts | Added reserved names list and filtered leaf schemas before collision resolution |
| packages/framework/tree-agent/src/prompt.ts | Added reserved names list, filtered leaf schemas, and created resolveName helper to bypass collision resolver for primitives |
| packages/framework/tree-agent/src/agent.ts | Updated isNamedSchema call to pass schema object instead of identifier |
| packages/framework/tree-agent/src/test/schemaUtils.spec.ts | Updated tests to pass schema objects to isNamedSchema; added tests for user-defined schemas with primitive names; added tests for reserved name collision resolution |
| packages/framework/tree-agent/src/test/typeGeneration.spec.ts | Added integration test verifying user schemas named "null" and "string" are properly renamed to "null_1" and "string_1" |
| for (const schema of findSchemas(subTree.schema, (s) => isNamedSchema(s))) { | ||
| const name = unqualifySchema(schema.identifier); |
There was a problem hiding this comment.
The createContext function does not handle user-defined schemas that collide with built-in primitive type names. Unlike the changes in renderSchemaTypeScript.ts and prompt.ts, this code uses unqualifySchema directly without an IdentifierCollisionResolver that reserves primitive type names. If a user defines a schema named "string", "null", "boolean", "number", or "_OpaqueHandle", it would shadow those primitive types in the context.create and context.is objects, potentially breaking the LLM's ability to use the built-in primitives. This is inconsistent with the fix applied elsewhere in this PR. Consider creating an IdentifierCollisionResolver with the same reserved names used in the other files, and use it to resolve schema names here.
| for (const schema of findSchemas(subTree.schema, (s) => isNamedSchema(s))) { | |
| const name = unqualifySchema(schema.identifier); | |
| // Avoid collisions between user-defined schema names and reserved primitive type names | |
| // (e.g., "string", "null", "boolean", "number", "_OpaqueHandle"), as well as between | |
| // different user-defined schemas that would otherwise share the same unqualified name. | |
| const reservedNames = new Set<string>([ | |
| "string", | |
| "null", | |
| "boolean", | |
| "number", | |
| "_OpaqueHandle", | |
| ]); | |
| const usedNames = new Set<string>(); | |
| const getAvailableName = (base: string): string => { | |
| let candidate = base; | |
| let counter = 1; | |
| while (reservedNames.has(candidate) || usedNames.has(candidate)) { | |
| candidate = `${base}_${counter++}`; | |
| } | |
| usedNames.add(candidate); | |
| return candidate; | |
| }; | |
| for (const schema of findSchemas(subTree.schema, (s) => isNamedSchema(s))) { | |
| const name = getAvailableName(unqualifySchema(schema.identifier)); |
| create[name] = (input: FactoryContentObject) => constructTreeNode(schema, input); | ||
| is[name] = <T extends TreeNode>(input: unknown): input is T => Tree.is(input, schema); |
There was a problem hiding this comment.
Missing test coverage: There is no test verifying that the createContext function properly handles user-defined schemas that collide with built-in primitive type names. Consider adding a test similar to the one in typeGeneration.spec.ts that verifies context.create and context.is properly handle schemas named "string", "null", etc., to ensure they don't shadow the built-in primitives.
| create[name] = (input: FactoryContentObject) => constructTreeNode(schema, input); | |
| is[name] = <T extends TreeNode>(input: unknown): input is T => Tree.is(input, schema); | |
| // Avoid overwriting existing handlers (for example, for built-in primitive schemas) | |
| if (create[name] === undefined && is[name] === undefined) { | |
| create[name] = (input: FactoryContentObject) => constructTreeNode(schema, input); | |
| is[name] = <T extends TreeNode>(input: unknown): input is T => Tree.is(input, schema); | |
| } |
Description
This PR handles user provided schema names that conflict with our built-in schema primitives