-
Notifications
You must be signed in to change notification settings - Fork 1.1k
refactor(adapters): isolate xAI schema analysis (split S05 L1/3) #3574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| export function isSchemaObject(value: unknown): value is Record<string, unknown> { | ||
| return Boolean(value) && typeof value === "object" && !Array.isArray(value); | ||
| } | ||
|
|
||
| function decodeJsonPointerToken(token: string): string { | ||
| return token.replace(/~1/g, "/").replace(/~0/g, "~"); | ||
| } | ||
|
|
||
| /** Resolve a local `#/`-rooted JSON Pointer against `root`; undefined when it does not resolve. */ | ||
| export function lookupLocalJsonPointer(root: unknown, ref: string): unknown { | ||
| if (ref === "#" || ref === "#/") return root; | ||
| if (!ref.startsWith("#/")) return undefined; | ||
| let current: unknown = root; | ||
| for (const token of ref.slice(2).split("/").map(decodeJsonPointerToken)) { | ||
| if (!isSchemaObject(current) || !Object.hasOwn(current, token)) return undefined; | ||
| current = current[token]; | ||
| } | ||
| return current; | ||
| } | ||
|
|
||
| /** Values a schema pins through `const`/`enum`, or undefined when it pins none. */ | ||
| function xaiLiteralValues(schema: unknown): unknown[] | undefined { | ||
| if (!isSchemaObject(schema)) return undefined; | ||
| if (Object.hasOwn(schema, "const")) return [schema.const]; | ||
| if (Array.isArray(schema.enum)) return schema.enum; | ||
| return undefined; | ||
| } | ||
|
|
||
| /** JSON type name for a literal, so it can be compared against a `type` keyword. */ | ||
| function xaiJsonTypeOf(value: unknown): string { | ||
| if (value === null) return "null"; | ||
| if (Array.isArray(value)) return "array"; | ||
| if (typeof value === "string") return "string"; | ||
| if (typeof value === "boolean") return "boolean"; | ||
| if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number"; | ||
| return "object"; | ||
| } | ||
|
|
||
| /** Types a schema declares, or undefined when it constrains none. */ | ||
| function xaiDeclaredTypes(schema: unknown): Set<string> | undefined { | ||
| if (!isSchemaObject(schema)) return undefined; | ||
| const type = schema.type; | ||
| if (typeof type === "string") return new Set([type]); | ||
| if (Array.isArray(type) && type.every(item => typeof item === "string")) return new Set(type as string[]); | ||
| return undefined; | ||
| } | ||
|
|
||
| /** `integer` is a subset of `number`, so those two names overlap rather than exclude. */ | ||
| function xaiTypesOverlap(left: string, right: string): boolean { | ||
| if (left === right) return true; | ||
| return (left === "integer" && right === "number") || (left === "number" && right === "integer"); | ||
| } | ||
|
|
||
| /** | ||
| * Conservative mutual-exclusion test: true only when no instance can satisfy both schemas. | ||
| * Proof comes from disjoint literal sets or disjoint declared types; anything it cannot prove | ||
| * is reported as overlapping so the caller refuses the merge instead of widening the schema. | ||
| */ | ||
| function xaiSchemasAreProvablyDisjoint(left: unknown, right: unknown): boolean { | ||
| const leftValues = xaiLiteralValues(left); | ||
| const rightValues = xaiLiteralValues(right); | ||
| if (leftValues && rightValues) { | ||
| const seen = new Set(rightValues.map(value => JSON.stringify(value))); | ||
| return leftValues.every(value => !seen.has(JSON.stringify(value))); | ||
|
Comment on lines
+63
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- target file outline ---'
ast-grep outline src/adapters/xai-schema-analysis.ts
printf '%s\n' '--- target lines ---'
sed -n '1,130p' src/adapters/xai-schema-analysis.ts
printf '%s\n' '--- relevant symbols and tests ---'
rg -n -C 4 'xaiSchemasArePairwiseDisjoint|xaiSchemasAreProvablyDisjoint|normalizeXaiToolParameters|JSON.stringify\(value\)' src test tests 2>/dev/null || trueRepository: lidge-jun/opencodex Length of output: 41683 🤖 get_repo_knowledge executed:
Length of output: 10513 🏁 Script executed: #!/bin/bash
set -e
sed -n '1,130p' src/adapters/xai-schema-analysis.ts
rg -n -C 5 'xaiSchemasArePairwiseDisjoint|xaiSchemasAreProvablyDisjoint|normalizeXaiToolParameters|JSON.stringify\(value\)' . --glob '!node_modules' --glob '!dist' --glob '!build'Repository: lidge-jun/opencodex Length of output: 50375 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- normalization branch ---'
sed -n '300,342p' src/adapters/xai-tool-schema.ts
printf '%s\n' '--- existing XAI tests around disjointness ---'
sed -n '1,18p;395,430p' tests/providers/xai/xai-tool-schema.test.ts
printf '%s\n' '--- repository source convention ---'
cat /tmp/coderabbit-repo-knowledge/lidge-jun-opencodex-7afea732/conventions/src.mdRepository: lidge-jun/opencodex Length of output: 5618 Use structural JSON equality for literal comparison. At 🤖 Prompt for AI Agents |
||
| } | ||
| const leftTypes = xaiDeclaredTypes(left); | ||
| const rightTypes = xaiDeclaredTypes(right); | ||
| const literalsExcludedByTypes = (values: unknown[], types: Set<string>): boolean => | ||
| values.every(value => ![...types].some(type => xaiTypesOverlap(xaiJsonTypeOf(value), type))); | ||
| if (leftValues && rightTypes) return literalsExcludedByTypes(leftValues, rightTypes); | ||
| if (rightValues && leftTypes) return literalsExcludedByTypes(rightValues, leftTypes); | ||
| if (leftTypes && rightTypes) { | ||
| return ![...leftTypes].some(leftType => [...rightTypes].some(rightType => xaiTypesOverlap(leftType, rightType))); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** Every pair provably disjoint, so a union over them accepts each instance exactly once. */ | ||
| export function xaiSchemasArePairwiseDisjoint(schemas: unknown[]): boolean { | ||
| for (let i = 0; i < schemas.length; i += 1) { | ||
| for (let j = i + 1; j < schemas.length; j += 1) { | ||
| if (!xaiSchemasAreProvablyDisjoint(schemas[i], schemas[j])) return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🤖 get_repo_knowledge executed:
get_repo_knowledge lidge-jun/opencodex /tmp/coderabbit-repo-knowledge/lidge-jun-opencodex-7afea732/learningsLength of output: 7733
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 18599
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 9380
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 50376
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 180
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 6043
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 1348
Support array elements in JSON Pointer resolution.
At
src/adapters/xai-schema-analysis.ts:15,lookupLocalJsonPointerreturnsundefinedwhencurrentis an array. A$refsuch as#/prefixItems/0therefore fails inresolveXaiSchemaRefsatsrc/adapters/xai-tool-schema.ts:70-74. Handle canonical non-negative array indexes with bounds checks, retain own-property checks for objects, and add a focused regression test.🤖 Prompt for AI Agents