refactor(schema): share gemini union cleanup

This commit is contained in:
Peter Steinberger
2026-02-14 21:55:41 +00:00
parent 9e7aab9baf
commit 153601f98b

View File

@@ -29,6 +29,16 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS = new Set([
"maxProperties", "maxProperties",
]); ]);
const SCHEMA_META_KEYS = ["description", "title", "default"] as const;
function copySchemaMeta(from: Record<string, unknown>, to: Record<string, unknown>): void {
for (const key of SCHEMA_META_KEYS) {
if (key in from && from[key] !== undefined) {
to[key] = from[key];
}
}
}
// Check if an anyOf/oneOf array contains only literal values that can be flattened. // Check if an anyOf/oneOf array contains only literal values that can be flattened.
// TypeBox Type.Literal generates { const: "value", type: "string" }. // TypeBox Type.Literal generates { const: "value", type: "string" }.
// Some schemas may use { enum: ["value"], type: "string" }. // Some schemas may use { enum: ["value"], type: "string" }.
@@ -164,6 +174,39 @@ function tryResolveLocalRef(ref: string, defs: SchemaDefs | undefined): unknown
return defs.get(name); return defs.get(name);
} }
function simplifyUnionVariants(params: { obj: Record<string, unknown>; variants: unknown[] }): {
variants: unknown[];
simplified?: unknown;
} {
const { obj, variants } = params;
const { variants: nonNullVariants, stripped } = stripNullVariants(variants);
const flattened = tryFlattenLiteralAnyOf(nonNullVariants);
if (flattened) {
const result: Record<string, unknown> = {
type: flattened.type,
enum: flattened.enum,
};
copySchemaMeta(obj, result);
return { variants: nonNullVariants, simplified: result };
}
if (stripped && nonNullVariants.length === 1) {
const lone = nonNullVariants[0];
if (lone && typeof lone === "object" && !Array.isArray(lone)) {
const result: Record<string, unknown> = {
...(lone as Record<string, unknown>),
};
copySchemaMeta(obj, result);
return { variants: nonNullVariants, simplified: result };
}
return { variants: nonNullVariants, simplified: lone };
}
return { variants: stripped ? nonNullVariants : variants };
}
function cleanSchemaForGeminiWithDefs( function cleanSchemaForGeminiWithDefs(
schema: unknown, schema: unknown,
defs: SchemaDefs | undefined, defs: SchemaDefs | undefined,
@@ -198,20 +241,12 @@ function cleanSchemaForGeminiWithDefs(
const result: Record<string, unknown> = { const result: Record<string, unknown> = {
...(cleaned as Record<string, unknown>), ...(cleaned as Record<string, unknown>),
}; };
for (const key of ["description", "title", "default"]) { copySchemaMeta(obj, result);
if (key in obj && obj[key] !== undefined) {
result[key] = obj[key];
}
}
return result; return result;
} }
const result: Record<string, unknown> = {}; const result: Record<string, unknown> = {};
for (const key of ["description", "title", "default"]) { copySchemaMeta(obj, result);
if (key in obj && obj[key] !== undefined) {
result[key] = obj[key];
}
}
return result; return result;
} }
@@ -229,74 +264,18 @@ function cleanSchemaForGeminiWithDefs(
: undefined; : undefined;
if (hasAnyOf) { if (hasAnyOf) {
const { variants: nonNullVariants, stripped } = stripNullVariants(cleanedAnyOf ?? []); const simplified = simplifyUnionVariants({ obj, variants: cleanedAnyOf ?? [] });
if (stripped) { cleanedAnyOf = simplified.variants;
cleanedAnyOf = nonNullVariants; if ("simplified" in simplified) {
} return simplified.simplified;
const flattened = tryFlattenLiteralAnyOf(nonNullVariants);
if (flattened) {
const result: Record<string, unknown> = {
type: flattened.type,
enum: flattened.enum,
};
for (const key of ["description", "title", "default"]) {
if (key in obj && obj[key] !== undefined) {
result[key] = obj[key];
}
}
return result;
}
if (stripped && nonNullVariants.length === 1) {
const lone = nonNullVariants[0];
if (lone && typeof lone === "object" && !Array.isArray(lone)) {
const result: Record<string, unknown> = {
...(lone as Record<string, unknown>),
};
for (const key of ["description", "title", "default"]) {
if (key in obj && obj[key] !== undefined) {
result[key] = obj[key];
}
}
return result;
}
return lone;
} }
} }
if (hasOneOf) { if (hasOneOf) {
const { variants: nonNullVariants, stripped } = stripNullVariants(cleanedOneOf ?? []); const simplified = simplifyUnionVariants({ obj, variants: cleanedOneOf ?? [] });
if (stripped) { cleanedOneOf = simplified.variants;
cleanedOneOf = nonNullVariants; if ("simplified" in simplified) {
} return simplified.simplified;
const flattened = tryFlattenLiteralAnyOf(nonNullVariants);
if (flattened) {
const result: Record<string, unknown> = {
type: flattened.type,
enum: flattened.enum,
};
for (const key of ["description", "title", "default"]) {
if (key in obj && obj[key] !== undefined) {
result[key] = obj[key];
}
}
return result;
}
if (stripped && nonNullVariants.length === 1) {
const lone = nonNullVariants[0];
if (lone && typeof lone === "object" && !Array.isArray(lone)) {
const result: Record<string, unknown> = {
...(lone as Record<string, unknown>),
};
for (const key of ["description", "title", "default"]) {
if (key in obj && obj[key] !== undefined) {
result[key] = obj[key];
}
}
return result;
}
return lone;
} }
} }