Files
gemini-nexus/src/lib/antigravity/JsonSchemaUtils.ts
T
Sujit BiswasandDraculabo 1265d044f6 feat: Implement collapsible sidebar and refined status bar UI (#45)
* chore: mark peer dependencies in package-lock.json

* docs: add comprehensive developer guide for antigravity-manager

* build: add @radix-ui/react-tooltip dependency

The tooltip component is needed for future UI enhancements to provide better user guidance and information display.

* feat(StatusBar): add collapsed view with toggle button

- Introduce `isCollapsed` prop to support both expanded and compact layouts
- Collapsed view shows a single icon button with status tooltip
- Enhanced visual styling with shadows, borders, and improved spacing
- Use Tooltip component to display status in collapsed mode

* feat(ui): add Radix UI tooltip component wrapper

Create a reusable tooltip component using Radix UI primitives.
The wrapper provides styled content with animations and integrates
with the project's class name utility for consistent theming.

* feat(layout): add collapsible sidebar with persistent state

- Add toggle button to collapse/expand sidebar with smooth animations
- Persist sidebar state in localStorage to maintain user preference
- Show tooltips for navigation items when sidebar is collapsed
- Adjust StatusBar component to support collapsed state

* chore: remove "peer": true flags from package-lock.json

* build: mark peer dependencies in package-lock.json

* build: add yaml v2.8.2 dependency and update package-lock

* docs(project_overview): update skill documentation formatting

* docs: remove extra blank line in AGENTS.md

* docs: update changelog formatting to use hyphens

* docs: improve readability of OpenSpec workflow files

* chore: standardize YAML string quotes in GitHub configs

* style(StatusBar): reformat JSX for consistency and readability

- Simplify className expres

* style(ui): standardize quotes and class order in tooltip component

- Use single quotes consistently across imports and strings
- Reorder Tailwind CSS classes to follow recommended conventions

* style(ipc): remove unnecessary parentheses in debug log statement

* style(MainLayout): improve code formatting and consistency

- Reformat multi-line import for better readability
- Normalize Tailwind CSS class ordering
- Remove unnecessary whitespace and ensure consistent quotes
- Maintain all functionality while improving code style

* style(i18n): format long translation strings for better readability

* style: remove unnecessary line break in logger.info call

The logger.info call had an unnecessary line break that made the code less readable. The line has been consolidated to improve code style consistency.

* chore: disable several ESLint rules for development flexibility

Disable TypeScript ESLint rules and other linting rules that are too restrictive during active development. This allows faster iteration without constant linting errors for common patterns like `any` types, unused variables, and inline comments.

* refactor: use const for immutable variables in request mapper

- Change `let` to `const` for variables that are not reassigned
- Improves code clarity and prevents accidental reassignment

* refactor: replace direct hasOwnProperty calls with safe variant

Use Object.prototype.hasOwnProperty.call to avoid issues when objects have overridden hasOwnProperty method. This ensures consistent behavior across all object types.

* fix: disable eslint for intentional assignment in useEffect

The eslint warning was triggered by directly using setProxyConfig with config.proxy inside useEffect without including it in the dependency array. This assignment is intentional and only needs to run when config changes, not when setProxyConfig changes. Adding the eslint-disable comment prevents the warning while maintaining correct behavior.

* chore: disable react-hooks/exhaustive-deps rule

This rule was already disabled; the change only adds a trailing comma to the ESLint configuration for consistency.

* fix(eslint): re-enable lint rules for hooks and case-declarations

---------

Co-authored-by: Draculabo <[email protected]>
2026-01-28 18:55:40 +08:00

183 lines
5.5 KiB
TypeScript

/**
* Recursively cleans JSON Schema to meet Gemini interface requirements
*
* 1. [New] Flatten $ref and $defs: Replace references with actual definitions to solve Gemini's lack of $ref support
* 2. Remove unsupported fields: $schema, additionalProperties, format, default, uniqueItems, validation fields
* 3. Handle Union types: ["string", "null"] -> "string"
* 4. Convert type field values to lowercase (Gemini v1internal requirement)
* 5. Remove numeric validation fields: multipleOf, exclusiveMinimum, exclusiveMaximum, etc.
*/
export function cleanJsonSchema(value: any) {
// 0. Preprocessing: Expand $ref (Schema Flattening)
if (value && typeof value === 'object' && !Array.isArray(value)) {
const defs: Record<string, any> = {};
// Extract $defs or definitions
if (value['$defs']) {
Object.assign(defs, value['$defs']);
delete value['$defs'];
}
if (value['definitions']) {
Object.assign(defs, value['definitions']);
delete value['definitions'];
}
if (Object.keys(defs).length > 0) {
// Recursively replace references
flattenRefs(value, defs);
}
}
// Recursive cleaning
cleanJsonSchemaRecursive(value);
}
/**
* Recursively expand $ref
*/
function flattenRefs(map: any, defs: Record<string, any>) {
if (!map || typeof map !== 'object') return;
// 检查并替换 $ref
if (typeof map['$ref'] === 'string') {
const refPath = map['$ref'];
// Parse reference name (e.g. #/$defs/MyType -> MyType)
const parts = refPath.split('/');
const refName = parts[parts.length - 1] || refPath;
if (defs[refName]) {
const defSchema = defs[refName];
// $ref nodes should not have other properties, remove $ref directly
delete map['$ref'];
if (defSchema && typeof defSchema === 'object') {
for (const [k, v] of Object.entries(defSchema)) {
// Only insert if the key does not exist in current map (avoid overwrite)
if (map[k] === undefined) {
// Clone deep to avoid reference issues
map[k] = JSON.parse(JSON.stringify(v));
}
}
// Recursively process $refs in the newly merged content
flattenRefs(map, defs);
}
}
}
// Recursively process all children
for (const k in map) {
if (Object.prototype.hasOwnProperty.call(map, k)) {
const v = map[k];
if (typeof v === 'object' && v !== null) {
flattenRefs(v, defs);
}
}
}
}
function cleanJsonSchemaRecursive(value: any) {
if (!value || typeof value !== 'object') {
return;
}
if (Array.isArray(value)) {
// Array: Recursively process each element
for (const v of value) {
cleanJsonSchemaRecursive(v);
}
} else {
const map = value;
// 1. Recursively process all children first to ensure nested structures are cleaned
for (const k in map) {
if (Object.prototype.hasOwnProperty.call(map, k)) {
cleanJsonSchemaRecursive(map[k]);
}
}
// 2. Collect and process validation fields (Migration logic: Downgrade constraints to Hints in description)
const constraints: string[] = [];
// Validation fields blacklist for migration
const validationFields = [
['pattern', 'pattern'],
['minLength', 'minLen'],
['maxLength', 'maxLen'],
['minimum', 'min'],
['maximum', 'max'],
['minItems', 'minItems'],
['maxItems', 'maxItems'],
['exclusiveMinimum', 'exclMin'],
['exclusiveMaximum', 'exclMax'],
['multipleOf', 'multipleOf'],
['format', 'format'],
];
for (const [field, label] of validationFields) {
if (map[field] !== undefined) {
const val = map[field];
// Only migrate if value is primitive type
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
constraints.push(`${label}: ${val}`);
delete map[field];
} else {
// If not expected type, leave as is (JS pass by reference, just don't delete)
}
}
}
// 3. Append constraint info to description
if (constraints.length > 0) {
const suffix = ` [Constraint: ${constraints.join(', ')}]`;
map['description'] = (map['description'] || '') + suffix;
}
// 4. Physically remove "hard" blacklist items that interfere with generation
const hardRemoveFields = [
'$schema',
'additionalProperties',
'enumCaseInsensitive',
'enumNormalizeWhitespace',
'uniqueItems',
'default',
'const',
'examples',
// Advanced logic fields common in MCP tools but unsupported by Gemini
'propertyNames',
'anyOf',
'oneOf',
'allOf',
'not',
'if',
'then',
'else',
'dependencies',
'dependentSchemas',
'dependentRequired',
'cache_control', // Fixes 400 error triggered by cache_control mentioned by user
];
for (const field of hardRemoveFields) {
delete map[field];
}
// 5. Handle type field (Gemini requires single lowercase string)
if (map['type']) {
const typeVal = map['type'];
if (typeof typeVal === 'string') {
map['type'] = typeVal.toLowerCase();
} else if (Array.isArray(typeVal)) {
// Union type downgrade: take the first non-null type
let selectedType = 'string';
for (const item of typeVal) {
if (typeof item === 'string' && item !== 'null') {
selectedType = item.toLowerCase();
break;
}
}
map['type'] = selectedType;
}
}
}
}