{"version":3,"file":"comparison.mjs","names":[],"sources":["../../../src/batteries/tools/comparison/index.ts"],"sourcesContent":["/**\n * Pre-constructed tools for comparing primitive values, arrays, and ranges.\n *\n * @module @nhtio/adk/batteries/tools/comparison\n *\n * @remarks\n * Pre-constructed bundled tools for the `comparison` category. Import individually, the whole\n * category, or import every tool via `@nhtio/adk/batteries`.\n */\n\nimport { Tool } from '@nhtio/adk/common'\nimport { isError } from '@nhtio/adk/guards'\nimport { validator } from '@nhtio/validation'\n\ntype Primitive = string | number | boolean | null | undefined\n\nfunction coerce(value: Primitive): number | string | boolean | null {\n  if (value === null || value === undefined) return null\n  if (typeof value === 'number' || typeof value === 'boolean') return value\n  const n = Number(value)\n  if (!Number.isNaN(n) && value.trim() !== '') return n\n  return value\n}\n\nfunction compareTwo(a: Primitive, b: Primitive): number {\n  const ca = coerce(a)\n  const cb = coerce(b)\n  if (ca === null && cb === null) return 0\n  if (ca === null) return -1\n  if (cb === null) return 1\n  if (typeof ca === 'number' && typeof cb === 'number') return ca - cb\n  return String(ca).localeCompare(String(cb))\n}\n\n/**\n * Compare two values and return their relationship (`equal`, `greater_than`, `less_than`).\n *\n * @remarks\n * Handles numbers, strings (lexicographic), booleans, ISO date strings, and null/undefined.\n * Use `type_hint` to force a specific interpretation; the default `auto` infers from the\n * values themselves.\n */\nexport const compareValuesTool = new Tool({\n  name: 'compare_values',\n  description:\n    'Compare two values and return their relationship: equal, greater_than, less_than, or not_comparable. Handles numbers, strings (lexicographic), booleans, dates (ISO strings), null/undefined.',\n  inputSchema: validator.object({\n    a: validator.any().required().description('First value to compare'),\n    b: validator.any().required().description('Second value to compare'),\n    type_hint: validator\n      .string()\n      .valid('auto', 'string', 'number', 'date')\n      .default('auto')\n      .description(\n        'Force interpretation: \"auto\" detects, \"date\" parses ISO date strings, \"string\" compares lexicographically, \"number\" coerces to numbers (default: auto)'\n      ),\n    case_insensitive: validator\n      .boolean()\n      .default(false)\n      .description('For string comparisons: ignore case (default: false)'),\n  }),\n  handler: async (args) => {\n    const {\n      a: rawA,\n      b: rawB,\n      type_hint: typeHint,\n      case_insensitive: ci,\n    } = args as {\n      a: unknown\n      b: unknown\n      type_hint: string\n      case_insensitive: boolean\n    }\n\n    let av: unknown = rawA\n    let bv: unknown = rawB\n\n    try {\n      if (typeHint === 'date') {\n        const da = new Date(av as string)\n        const db = new Date(bv as string)\n        if (Number.isNaN(da.getTime())) return `Error: Cannot parse \"${av}\" as a date.`\n        if (Number.isNaN(db.getTime())) return `Error: Cannot parse \"${bv}\" as a date.`\n        const diff = da.getTime() - db.getTime()\n        const rel = diff === 0 ? 'equal' : diff > 0 ? 'greater_than' : 'less_than'\n        return `${rel}\\na = ${da.toISOString()}\\nb = ${db.toISOString()}`\n      }\n\n      if (typeHint === 'number') {\n        const na = Number(av)\n        const nb = Number(bv)\n        if (Number.isNaN(na)) return `Error: Cannot convert \"${av}\" to a number.`\n        if (Number.isNaN(nb)) return `Error: Cannot convert \"${bv}\" to a number.`\n        const diff = na - nb\n        return diff === 0 ? 'equal' : diff > 0 ? 'greater_than' : 'less_than'\n      }\n\n      if (typeHint === 'string') {\n        const sa = ci ? String(av).toLowerCase() : String(av)\n        const sb = ci ? String(bv).toLowerCase() : String(bv)\n        const cmp = sa.localeCompare(sb)\n        return cmp === 0 ? 'equal' : cmp > 0 ? 'greater_than' : 'less_than'\n      }\n\n      // auto\n      if (ci && typeof av === 'string' && typeof bv === 'string') {\n        av = av.toLowerCase()\n        bv = bv.toLowerCase()\n      }\n      const cmp = compareTwo(av as Primitive, bv as Primitive)\n      return cmp === 0 ? 'equal' : cmp > 0 ? 'greater_than' : 'less_than'\n    } catch (err) {\n      return `Error: ${isError(err) ? err.message : String(err)}`\n    }\n  },\n})\n\n/**\n * Compare two JSON objects and report keys only in A, keys only in B, keys with matching\n * values, and keys with differing values.\n *\n * @remarks\n * Accepts JSON strings (not pre-parsed objects) so the schema can validate the input\n * shape uniformly across providers. When `deep` is true (default), nested objects and arrays\n * are compared structurally; when false, comparison reduces to JSON serialisation equality.\n */\nexport const compareRecordsTool = new Tool({\n  name: 'compare_records',\n  description:\n    'Compare two JSON objects and report: keys only in A, keys only in B, keys present in both with the same value, and keys present in both with different values.',\n  inputSchema: validator.object({\n    record_a: validator.string().required().description('First JSON object (as a string)'),\n    record_b: validator.string().required().description('Second JSON object (as a string)'),\n    deep: validator\n      .boolean()\n      .default(true)\n      .description(\n        'Use deep equality for nested values (default: true). When false, compares by JSON serialization.'\n      ),\n  }),\n  handler: async (args) => {\n    const {\n      record_a: recordA,\n      record_b: recordB,\n      deep,\n    } = args as { record_a: string; record_b: string; deep: boolean }\n\n    let a: Record<string, unknown>\n    let b: Record<string, unknown>\n\n    try {\n      a = JSON.parse(recordA) as Record<string, unknown>\n    } catch {\n      return 'Error: record_a is not valid JSON.'\n    }\n    try {\n      b = JSON.parse(recordB) as Record<string, unknown>\n    } catch {\n      return 'Error: record_b is not valid JSON.'\n    }\n\n    if (typeof a !== 'object' || a === null || Array.isArray(a))\n      return 'Error: record_a must be a JSON object (not array or primitive).'\n    if (typeof b !== 'object' || b === null || Array.isArray(b))\n      return 'Error: record_b must be a JSON object (not array or primitive).'\n\n    function isEqual(x: unknown, y: unknown): boolean {\n      if (!deep) return JSON.stringify(x) === JSON.stringify(y)\n      if (x === y) return true\n      if (typeof x !== typeof y) return false\n      if (x === null || y === null) return x === y\n      // An array and a plain object are never equal, even if the object has matching integer-like\n      // keys ([1,2] vs {\"0\":1,\"1\":2}). Without this guard, a one-sided array falls through to the\n      // Object.keys() comparison and is wrongly reported as identical.\n      if (Array.isArray(x) !== Array.isArray(y)) return false\n      if (Array.isArray(x) && Array.isArray(y)) {\n        if (x.length !== y.length) return false\n        for (const [i, element] of x.entries()) {\n          if (!isEqual(element, y[i])) return false\n        }\n        return true\n      }\n      if (typeof x === 'object' && typeof y === 'object') {\n        const xk = Object.keys(x as object).sort()\n        const yk = Object.keys(y as object).sort()\n        if (JSON.stringify(xk) !== JSON.stringify(yk)) return false\n        for (const k of xk) {\n          if (!isEqual((x as Record<string, unknown>)[k], (y as Record<string, unknown>)[k]))\n            return false\n        }\n        return true\n      }\n      return false\n    }\n\n    const keysA = new Set(Object.keys(a))\n    const keysB = new Set(Object.keys(b))\n\n    const onlyInA: string[] = []\n    const onlyInB: string[] = []\n    const same: string[] = []\n    const different: Array<{ key: string; a: unknown; b: unknown }> = []\n\n    for (const k of keysA) {\n      if (!keysB.has(k)) {\n        onlyInA.push(k)\n      } else if (isEqual(a[k], b[k])) {\n        same.push(k)\n      } else {\n        different.push({ key: k, a: a[k], b: b[k] })\n      }\n    }\n\n    for (const k of keysB) {\n      if (!keysA.has(k)) onlyInB.push(k)\n    }\n\n    const lines: string[] = []\n\n    if (onlyInA.length > 0) lines.push(`Only in A (${onlyInA.length}): ${onlyInA.join(', ')}`)\n    if (onlyInB.length > 0) lines.push(`Only in B (${onlyInB.length}): ${onlyInB.join(', ')}`)\n    if (same.length > 0) lines.push(`Same value (${same.length}): ${same.join(', ')}`)\n\n    if (different.length > 0) {\n      lines.push(`Different value (${different.length}):`)\n      for (const { key, a: av, b: bv } of different) {\n        lines.push(`  ${key}: A=${JSON.stringify(av)}, B=${JSON.stringify(bv)}`)\n      }\n    }\n\n    if (lines.length === 0) return 'Records are identical.'\n    return lines.join('\\n')\n  },\n})\n"],"mappings":"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"}