Skip to content

user defined built-in primitive schema name handling#26437

Open
daesunp wants to merge 1 commit intomicrosoft:mainfrom
daesunp:schema-primitive-name-conflict
Open

user defined built-in primitive schema name handling#26437
daesunp wants to merge 1 commit intomicrosoft:mainfrom
daesunp:schema-primitive-name-conflict

Conversation

@daesunp
Copy link
Contributor

@daesunp daesunp commented Feb 12, 2026

Description

This PR handles user provided schema names that conflict with our built-in schema primitives

@daesunp daesunp marked this pull request as ready for review February 12, 2026 23:06
Copilot AI review requested due to automatic review settings February 12, 2026 23:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 isNamedSchema function to accept a TreeNodeSchema parameter instead of a string identifier, and check schema.kind === NodeKind.Leaf to identify primitive schemas
  • Enhanced IdentifierCollisionResolver class to accept a set of reserved names in its constructor and rename colliding schemas starting with _1 suffix
  • 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"

Comment on lines +267 to 268
for (const schema of findSchemas(subTree.schema, (s) => isNamedSchema(s))) {
const name = unqualifySchema(schema.identifier);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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));

Copilot uses AI. Check for mistakes.
Comment on lines 269 to 270
create[name] = (input: FactoryContentObject) => constructTreeNode(schema, input);
is[name] = <T extends TreeNode>(input: unknown): input is T => Tree.is(input, schema);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant