mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 03:45:39 +00:00
Share ordered schema positions between Gemini literal checks, DeepSeek regex checks, and catalog array-shape checks. Reuse the canonical keyword and local reference owners; keep unreferenced instance literals opaque and unresolved external or dynamic targets unknown. Gemini and catalog checks now catch constraints in schemas reached through references into examples. Preserve source paths, resource scopes, cycles and regex occurrence counts. Align fixture first-error selection with the native Gemini check and use consistent bracketed array locations in diagnostics. Validation: 55 focused tests passed, including full/compact MCP catalogs; typecheck, lint, discovery, and Node source-loader checks passed. The old Gemini scanner reproduced the local-reference false negative. Installed Gemini and DeepSeek clients were not run.
72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
import { geminiUnsupportedLiteral } from "../../scripts/lib/gemini-schema-literals.mjs";
|
|
import { clientSchemaPositions } from "../../scripts/lib/client-schema-positions.mjs";
|
|
|
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
|
|
/** VS Code Copilot rejects falsy `items` for any type that admits arrays. */
|
|
export const arrayItemSchemaError = (
|
|
schema: unknown,
|
|
path: string,
|
|
): string | undefined => {
|
|
for (const position of clientSchemaPositions(schema)) {
|
|
if (position.kind !== "schema") continue;
|
|
const value = position.schema;
|
|
const admitsArrays =
|
|
value.type === "array" ||
|
|
(Array.isArray(value.type) && value.type.includes("array"));
|
|
if (admitsArrays && !value.items)
|
|
return `${path}${position.path.slice(1)}: tool parameters array type must have items`;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
/** Reject Gemini-incompatible boolean and empty-string input literals. */
|
|
export const unsupportedInputLiteralSchemaError = (
|
|
schema: unknown,
|
|
path: string,
|
|
): string | undefined => {
|
|
const finding = geminiUnsupportedLiteral(schema);
|
|
if (finding === undefined) return undefined;
|
|
const reason =
|
|
finding.reason === "boolean"
|
|
? finding.keyword === "const"
|
|
? "is boolean"
|
|
: "contains a boolean"
|
|
: finding.keyword === "const"
|
|
? "is an empty string"
|
|
: "contains an empty string";
|
|
return `${path}${finding.path.slice(1)} ${reason}`;
|
|
};
|
|
|
|
/**
|
|
* Gemini function declarations accept `items` only on a schema whose type is
|
|
* the single `array` type (`items: field predicate failed: $type ==
|
|
* Type.ARRAY`), and strict gateways drop untyped item unions. Return every
|
|
* advertised position with either shape so a catalog test names them all.
|
|
*/
|
|
export const strictGatewayItemSchemaErrors = (
|
|
schema: unknown,
|
|
path = "$",
|
|
): readonly string[] => {
|
|
const errors: string[] = [];
|
|
for (const position of clientSchemaPositions(schema)) {
|
|
if (position.kind !== "schema") continue;
|
|
const value = position.schema;
|
|
const selectedPath = `${path}${position.path.slice(1)}`;
|
|
if (value.items !== undefined && value.type !== "array")
|
|
errors.push(
|
|
`${selectedPath}.items is beside type ${JSON.stringify(value.type)}`,
|
|
);
|
|
const items = value.items;
|
|
if (
|
|
value.type === "array" &&
|
|
isRecord(items) &&
|
|
items.type === undefined &&
|
|
(Array.isArray(items.oneOf) || Array.isArray(items.anyOf))
|
|
)
|
|
errors.push(`${selectedPath}.items is an untyped union`);
|
|
}
|
|
return errors;
|
|
};
|