Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325import { Compile } from "typebox/compile";import type { TLocalizedValidationError } from "typebox/error";import { Value } from "typebox/value";import type { Tool, ToolCall } from "../types.js";
const validatorCache = new WeakMap<object, ReturnType<typeof Compile>>();const TYPEBOX_KIND = Symbol.for("TypeBox.Kind");
interface JsonSchemaObject { type?: string | string[]; properties?: Record<string, JsonSchemaObject>; items?: JsonSchemaObject | JsonSchemaObject[]; additionalProperties?: boolean | JsonSchemaObject; allOf?: JsonSchemaObject[]; anyOf?: JsonSchemaObject[]; oneOf?: JsonSchemaObject[];}
function isRecord(value: unknown): value is Record<string, unknown> { return typeof value === "object" && value !== null;}
function isJsonSchemaObject(value: unknown): value is JsonSchemaObject { return isRecord(value);}
function hasTypeBoxMetadata(schema: unknown): boolean { return isRecord(schema) && Object.getOwnPropertySymbols(schema).includes(TYPEBOX_KIND);}
function getSchemaTypes(schema: JsonSchemaObject): string[] { if (typeof schema.type === "string") { return [schema.type]; } if (Array.isArray(schema.type)) { return schema.type.filter((type): type is string => typeof type === "string"); } return [];}
function matchesJsonType(value: unknown, type: string): boolean { switch (type) { case "number": return typeof value === "number"; case "integer": return typeof value === "number" && Number.isInteger(value); case "boolean": return typeof value === "boolean"; case "string": return typeof value === "string"; case "null": return value === null; case "array": return Array.isArray(value); case "object": return isRecord(value) && !Array.isArray(value); default: return false; }}
function isValidatorSchema(value: unknown): value is Tool["parameters"] { return isRecord(value);}
function getSubSchemaValidator(schema: JsonSchemaObject): ReturnType<typeof Compile> | undefined { if (!isValidatorSchema(schema)) { return undefined; } try { return getValidator(schema); } catch { return undefined; }}
function coercePrimitiveByType(value: unknown, type: string): unknown { switch (type) { case "number": { if (value === null) { return 0; } if (typeof value === "string" && value.trim() !== "") { const parsed = Number(value); if (Number.isFinite(parsed)) { return parsed; } } if (typeof value === "boolean") { return value ? 1 : 0; } return value; } case "integer": { if (value === null) { return 0; } if (typeof value === "string" && value.trim() !== "") { const parsed = Number(value); if (Number.isInteger(parsed)) { return parsed; } } if (typeof value === "boolean") { return value ? 1 : 0; } return value; } case "boolean": { if (value === null) { return false; } if (typeof value === "string") { if (value === "true") { return true; } if (value === "false") { return false; } } if (typeof value === "number") { if (value === 1) { return true; } if (value === 0) { return false; } } return value; } case "string": { if (value === null) { return ""; } if (typeof value === "number" || typeof value === "boolean") { return String(value); } return value; } case "null": { if (value === "" || value === 0 || value === false) { return null; } return value; } default: return value; }}
function applySchemaObjectCoercion(value: Record<string, unknown>, schema: JsonSchemaObject): void { const properties = schema.properties; const definedKeys = new Set<string>(properties ? Object.keys(properties) : []);
if (properties) { for (const [key, propertySchema] of Object.entries(properties)) { if (!(key in value)) { continue; } value[key] = coerceWithJsonSchema(value[key], propertySchema); } }
if (schema.additionalProperties && isJsonSchemaObject(schema.additionalProperties)) { for (const [key, propertyValue] of Object.entries(value)) { if (definedKeys.has(key)) { continue; } value[key] = coerceWithJsonSchema(propertyValue, schema.additionalProperties); } }}
function applySchemaArrayCoercion(value: unknown[], schema: JsonSchemaObject): void { if (Array.isArray(schema.items)) { for (let index = 0; index < value.length; index++) { const itemSchema = schema.items[index]; if (!itemSchema) { continue; } value[index] = coerceWithJsonSchema(value[index], itemSchema); } return; }
if (isJsonSchemaObject(schema.items)) { for (let index = 0; index < value.length; index++) { value[index] = coerceWithJsonSchema(value[index], schema.items); } }}
function coerceWithUnionSchema(value: unknown, schemas: JsonSchemaObject[]): unknown { for (const schema of schemas) { const candidate = structuredClone(value); const coerced = coerceWithJsonSchema(candidate, schema); const validator = getSubSchemaValidator(schema); if (validator?.Check(coerced)) { return coerced; } } return value;}
function coerceWithJsonSchema(value: unknown, schema: JsonSchemaObject): unknown { let nextValue = value;
if (Array.isArray(schema.allOf)) { for (const nested of schema.allOf) { nextValue = coerceWithJsonSchema(nextValue, nested); } }
if (Array.isArray(schema.anyOf)) { nextValue = coerceWithUnionSchema(nextValue, schema.anyOf); }
if (Array.isArray(schema.oneOf)) { nextValue = coerceWithUnionSchema(nextValue, schema.oneOf); }
const schemaTypes = getSchemaTypes(schema); const matchesUnionMember = schemaTypes.length > 1 && schemaTypes.some((schemaType) => matchesJsonType(nextValue, schemaType)); if (schemaTypes.length > 0 && !matchesUnionMember) { for (const schemaType of schemaTypes) { const candidate = coercePrimitiveByType(nextValue, schemaType); if (candidate !== nextValue) { nextValue = candidate; break; } } }
if (schemaTypes.includes("object") && isRecord(nextValue) && !Array.isArray(nextValue)) { applySchemaObjectCoercion(nextValue, schema); }
if (schemaTypes.includes("array") && Array.isArray(nextValue)) { applySchemaArrayCoercion(nextValue, schema); }
return nextValue;}
function getValidator(schema: Tool["parameters"]): ReturnType<typeof Compile> { const key = schema as object; const cached = validatorCache.get(key); if (cached) { return cached; } const validator = Compile(schema); validatorCache.set(key, validator); return validator;}
function formatValidationPath(error: TLocalizedValidationError): string { if (error.keyword === "required") { const requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties; const requiredProperty = requiredProperties?.[0]; if (requiredProperty) { const basePath = error.instancePath.replace(/^\//, "").replace(/\//g, "."); return basePath ? `${basePath}.${requiredProperty}` : requiredProperty; } } const path = error.instancePath.replace(/^\//, "").replace(/\//g, "."); return path || "root";}
/** * Finds a tool by name and validates the tool call arguments against its TypeBox schema * @param tools Array of tool definitions * @param toolCall The tool call from the LLM * @returns The validated arguments * @throws Error if tool is not found or validation fails */export function validateToolCall(tools: Tool[], toolCall: ToolCall): any { const tool = tools.find((t) => t.name === toolCall.name); if (!tool) { throw new Error(`Tool "${toolCall.name}" not found`); } return validateToolArguments(tool, toolCall);}
/** * Validates tool call arguments against the tool's TypeBox schema * @param tool The tool definition with TypeBox schema * @param toolCall The tool call from the LLM * @returns The validated (and potentially coerced) arguments * @throws Error with formatted message if validation fails */export function validateToolArguments(tool: Tool, toolCall: ToolCall): any { const args = structuredClone(toolCall.arguments); Value.Convert(tool.parameters, args);
const validator = getValidator(tool.parameters); if (!hasTypeBoxMetadata(tool.parameters) && isJsonSchemaObject(tool.parameters)) { const coerced = coerceWithJsonSchema(args, tool.parameters); if (coerced !== args) { if (isRecord(args) && isRecord(coerced)) { for (const key of Object.keys(args)) { delete args[key]; } Object.assign(args, coerced); } else { return validator.Check(coerced) ? coerced : args; } } }
if (validator.Check(args)) { return args; }
const errors = validator .Errors(args) .map((error) => ` - ${formatValidationPath(error)}: ${error.message}`) .join("\n") || "Unknown validation error";
const errorMessage = `Validation failed for tool "${toolCall.name}":\n${errors}\n\nReceived arguments:\n${JSON.stringify(toolCall.arguments, null, 2)}`;
throw new Error(errorMessage);}