export const OMIT = Symbol("omit") export const OMIT_LABEL = "" export type Schema = Record & { enum?: unknown anyOf?: unknown oneOf?: unknown const?: unknown items?: unknown type?: unknown description?: unknown default?: unknown properties?: unknown required?: unknown } export function isRecord(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value) } export function numberValue(value: unknown): number { return typeof value === "number" && Number.isFinite(value) ? value : 0 } export function asSchema(value: unknown): Schema | undefined { return isRecord(value) ? value : undefined } export function parseJsonValue(value: string): unknown | undefined { try { return JSON.parse(value) } catch { return undefined } } export function schemaStringArray(value: unknown): string[] | undefined { return Array.isArray(value) && value.every(item => typeof item === "string") ? value : undefined } function schemaArray(value: unknown): Schema[] | undefined { return Array.isArray(value) && value.every(isRecord) ? (value as Schema[]) : undefined } export function schemaEnum(schema: Schema): unknown[] | undefined { const ownEnum = Array.isArray(schema.enum) ? schema.enum : undefined if (ownEnum) return ownEnum const variants = schemaArray(schema.anyOf) ?? schemaArray(schema.oneOf) const values = variants?.map(variant => variant.const) return values?.every(value => value !== undefined) ? values : undefined } export function formatSchemaType(schema: Schema | undefined): string { if (!schema) return "any" if ("const" in schema) return JSON.stringify(schema.const) const enumValues = schemaEnum(schema) if (enumValues) return enumValues.map(value => JSON.stringify(value)).join(" | ") const variants = schemaArray(schema.anyOf) ?? schemaArray(schema.oneOf) if (variants) return variants.map(formatSchemaType).join(" | ") const items = asSchema(schema.items) if (items) return `${formatSchemaType(items)}[]` const type = schema.type if (Array.isArray(type)) return type.join(" | ") return typeof type === "string" ? type : "any" } export function getSchemaDescription(schema: Schema | undefined) { return typeof schema?.description === "string" ? schema.description : undefined } function cloneSchemaValue(value: unknown): unknown { return structuredClone(value) } export function hasObjectSchemaProperties(schema: Schema | undefined) { return !!asSchema(schema?.properties) } export function defaultObjectArgs(schema: Schema | undefined): Record { const object: Record = {} const properties = asSchema(schema?.properties) const required = new Set(schemaStringArray(schema?.required) ?? []) if (!properties) return object for (const [name, rawSchema] of Object.entries(properties)) { if (required.has(name)) object[name] = defaultArgValue(asSchema(rawSchema), true) } return object } export function defaultArgValue(schema: Schema | undefined, seedArray: boolean): unknown { if (!schema) return null if ("default" in schema) return cloneSchemaValue(schema.default) if ("const" in schema) return cloneSchemaValue(schema.const) const enumValues = schemaEnum(schema) if (enumValues?.length) return cloneSchemaValue(enumValues[0]) const type = schema.type if (type === "string") return "" if (type === "number" || type === "integer") return 0 if (type === "boolean") return false if (type === "object" || hasObjectSchemaProperties(schema)) return defaultObjectArgs(schema) if (type === "array") { const items = asSchema(schema.items) return seedArray && items ? [defaultArgValue(items, false)] : [] } return null } export function formatArgValue(value: unknown) { if (value === OMIT) return OMIT_LABEL const json = JSON.stringify(value) return json === undefined ? "undefined" : json } export function formatInputValue(value: unknown) { if (value === OMIT) return "" return formatArgValue(value) } export function coerceArgValue(text: string, schema: Schema | undefined): unknown | undefined { const type = schema?.type if (type === "string") { const parsed = parseJsonValue(text.trim()) return typeof parsed === "string" ? parsed : undefined } if (type === "number" || type === "integer") { if (text.trim() === "") return undefined const value = Number(text.trim()) if (!Number.isFinite(value) || (type === "integer" && !Number.isInteger(value))) return undefined return value } const parsed = parseJsonValue(text.trim() || "null") if (parsed === undefined) return undefined if (type === "array" && !Array.isArray(parsed)) return undefined if (type === "object" && !isRecord(parsed)) return undefined return parsed } export function formatToolArgs(args: Record) { return Object.entries(args) .map(([k, v]) => `${k}=${JSON.stringify(v)}`) .join(", ") }