{"version":3,"file":"string_processing.cjs","names":[],"sources":["../../../src/batteries/tools/string_processing/index.ts"],"sourcesContent":["/**\n * Pre-constructed tools for casing, trimming, normalizing, and transforming strings.\n *\n * @module @nhtio/adk/batteries/tools/string_processing\n *\n * @remarks\n * Pre-constructed bundled tools for the `string_processing` 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'\nimport { camelCase, capitalCase, kebabCase, pascalCase, snakeCase, trainCase } from 'case-anything'\n\ntype StringOp =\n  | { op: 'uppercase' }\n  | { op: 'lowercase' }\n  | { op: 'titlecase' }\n  | { op: 'sentence_case' }\n  | { op: 'capitalize' }\n  | { op: 'camel_case' }\n  | { op: 'pascal_case' }\n  | { op: 'snake_case' }\n  | { op: 'kebab_case' }\n  | { op: 'train_case' }\n  | { op: 'constant_case' }\n  | { op: 'trim' }\n  | { op: 'trim_start' }\n  | { op: 'trim_end' }\n  | { op: 'normalize_whitespace' }\n  | { op: 'reverse' }\n  | { op: 'slug' }\n  | { op: 'strip_html' }\n  | { op: 'count_words' }\n  | { op: 'count_chars' }\n  | { op: 'count_lines' }\n  | { op: 'repeat'; count: number }\n  | { op: 'pad_start'; length: number; char?: string }\n  | { op: 'pad_end'; length: number; char?: string }\n  | { op: 'slice'; start: number; end?: number }\n  | { op: 'truncate'; length: number; suffix?: string }\n  | { op: 'replace'; from: string; to: string }\n  | { op: 'replace_all'; from: string; to: string }\n  | { op: 'regex_replace'; pattern: string; replacement: string; flags?: string }\n  | { op: 'split'; delimiter: string }\n  | { op: 'indent'; size?: number; char?: string }\n  | { op: 'dedent' }\n\nfunction applyStringOp(text: string, op: StringOp): string | number | string[] {\n  switch (op.op) {\n    case 'uppercase':\n      return text.toUpperCase()\n    case 'lowercase':\n      return text.toLowerCase()\n    case 'capitalize':\n      return text.charAt(0).toUpperCase() + text.slice(1)\n    case 'titlecase':\n      return capitalCase(text)\n    case 'sentence_case':\n      return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase()\n    case 'camel_case':\n      return camelCase(text)\n    case 'pascal_case':\n      return pascalCase(text)\n    case 'snake_case':\n      return snakeCase(text)\n    case 'kebab_case':\n      return kebabCase(text)\n    case 'train_case':\n      return trainCase(text)\n    case 'constant_case':\n      return snakeCase(text).toUpperCase()\n    case 'trim':\n      return text.trim()\n    case 'trim_start':\n      return text.trimStart()\n    case 'trim_end':\n      return text.trimEnd()\n    case 'normalize_whitespace':\n      return text.replace(/\\s+/g, ' ').trim()\n    case 'reverse':\n      // Iterate by code point ([...text]), not UTF-16 code unit, so astral characters / emoji\n      // (surrogate pairs) are not split into broken halves. 'A\\ud83d\\udca5B' \\u2192 'B\\ud83d\\udca5A'.\n      return [...text].reverse().join('')\n    case 'slug':\n      return (\n        text\n          .toLowerCase()\n          // Transliterate common Latin-1 letters/ligatures that do NOT decompose under NFD, so they\n          // survive the a-z0-9 filter instead of becoming hyphens (e.g. 'f\\u00f8tex' \\u2192 'fotex', not 'f-tex').\n          .replace(/\\u00df/g, 'ss')\n          .replace(/\\u00e6/g, 'ae')\n          .replace(/\\u0153/g, 'oe')\n          .replace(/\\u00f8/g, 'o')\n          .replace(/\\u0111/g, 'd')\n          .replace(/\\u0142/g, 'l')\n          .replace(/\\u00fe/g, 'th')\n          .normalize('NFD')\n          .replace(/[\\u0300-\\u036f]/g, '')\n          .replace(/[^a-z0-9]+/g, '-')\n          .replace(/^-+|-+$/g, '')\n      )\n    case 'strip_html':\n      return text.replace(/<[^>]*>/g, '')\n    case 'count_words':\n      return text.trim() === '' ? 0 : text.trim().split(/\\s+/).length\n    case 'count_chars':\n      return text.length\n    case 'count_lines':\n      return text === '' ? 0 : text.split('\\n').length\n    case 'repeat':\n      return text.repeat(Math.max(0, Math.min(100, Math.floor(op.count))))\n    case 'pad_start':\n      return text.padStart(op.length, op.char ?? ' ')\n    case 'pad_end':\n      return text.padEnd(op.length, op.char ?? ' ')\n    case 'slice':\n      return text.slice(op.start, op.end)\n    case 'truncate': {\n      const suffix = op.suffix ?? '…'\n      if (text.length <= op.length) return text\n      return text.slice(0, Math.max(0, op.length - suffix.length)) + suffix\n    }\n    case 'replace':\n      return text.replace(op.from, op.to)\n    case 'replace_all':\n      return text.split(op.from).join(op.to)\n    case 'regex_replace': {\n      const flags = (op.flags ?? 'g').replace(/[^gimsuy]/g, '')\n      let re: RegExp\n      try {\n        re = new RegExp(op.pattern, flags)\n      } catch (e) {\n        throw new Error(`Invalid regex: ${isError(e) ? e.message : String(e)}`)\n      }\n      return text.replace(re, op.replacement)\n    }\n    case 'split':\n      return text.split(op.delimiter)\n    case 'indent': {\n      const size = op.size ?? 2\n      const ch = op.char ?? ' '\n      const pad = ch.repeat(size)\n      return text\n        .split('\\n')\n        .map((l) => (l === '' ? l : pad + l))\n        .join('\\n')\n    }\n    case 'dedent': {\n      const lines = text.split('\\n')\n      const nonEmpty = lines.filter((l) => l.trim() !== '')\n      if (nonEmpty.length === 0) return text\n      const minIndent = Math.min(...nonEmpty.map((l) => l.match(/^(\\s*)/)?.[1].length ?? 0))\n      return lines.map((l) => l.slice(minIndent)).join('\\n')\n    }\n    default:\n      throw new Error(`Unknown operation: ${(op as { op: string }).op}`)\n  }\n}\n\n/**\n * Apply an ordered pipeline of string transformations.\n *\n * @remarks\n * Each operation reads the previous step's output. Most operations return strings; `count_*`\n * return numbers and `split` returns an array — once a non-string flows through, subsequent\n * operations report an error rather than coercing. The result is always serialised to a string\n * (arrays become JSON; numbers/booleans are stringified).\n */\nexport const stringTransformTool = new Tool({\n  name: 'string_transform',\n  description:\n    'Apply one or more string transformations in sequence. Supports case conversion (camelCase, snake_case, kebab-case, etc.), trimming, truncation, replacement, splitting, indentation, and more.',\n  inputSchema: validator.object({\n    text: validator.string().required().allow('').description('Input string to transform'),\n    operations: validator\n      .array()\n      .items(validator.object().unknown(true))\n      .required()\n      .description('Ordered list of operations to apply. Each operation has an \"op\" field.'),\n  }),\n  handler: async (args) => {\n    const { text, operations } = args as { text: string; operations: StringOp[] }\n    let current: string | number | string[] = text\n\n    for (const [i, op] of operations.entries()) {\n      if (typeof current !== 'string') {\n        return `Error: Operation ${i + 1} (\"${op.op}\") requires a string input, but the previous operation returned ${Array.isArray(current) ? 'an array' : typeof current}.`\n      }\n      try {\n        current = applyStringOp(current, op)\n      } catch (err) {\n        return `Error in operation ${i + 1} (\"${op.op}\"): ${isError(err) ? err.message : String(err)}`\n      }\n    }\n\n    if (Array.isArray(current)) return JSON.stringify(current)\n    return String(current)\n  },\n})\n\n/**\n * Extract all matches of a regular expression from a string.\n *\n * @remarks\n * The `g` flag is always enabled. `group` selects which capture group to return per match (0 =\n * full match). Output is at most 500 matches; if truncated, the suffix `(truncated at 500)` is\n * appended.\n */\nexport const stringExtractTool = new Tool({\n  name: 'string_extract',\n  description:\n    'Extract all matches of a regex pattern from text. Returns a JSON array of matched strings. Use capture groups to pull out specific parts.',\n  inputSchema: validator.object({\n    text: validator.string().required().allow('').description('Input text to search'),\n    pattern: validator\n      .string()\n      .required()\n      .description('Regular expression pattern (no surrounding slashes)'),\n    flags: validator\n      .string()\n      .default('g')\n      .allow('')\n      .description(\n        'Regex flags (default: \"g\"). \"g\" is always included. Omit or send an empty string for the default.'\n      ),\n    group: validator\n      .number()\n      .default(0)\n      .description('Capture group index to return (0 = full match, 1 = first group). Default: 0.'),\n  }),\n  handler: async (args) => {\n    const {\n      text,\n      pattern,\n      flags: rawFlags,\n      group,\n    } = args as {\n      text: string\n      pattern: string\n      flags: string\n      group: number\n    }\n    const flags = rawFlags.replace(/[^gimsuy]/g, '')\n    const flagsWithG = flags.includes('g') ? flags : flags + 'g'\n\n    let regex: RegExp\n    try {\n      regex = new RegExp(pattern, flagsWithG)\n    } catch (err) {\n      return `Error: Invalid regex — ${isError(err) ? err.message : String(err)}`\n    }\n\n    const matches: string[] = []\n    let match: RegExpExecArray | null\n    const MAX_MATCHES = 500\n\n    while ((match = regex.exec(text)) !== null && matches.length < MAX_MATCHES) {\n      const value = match[group]\n      if (value !== undefined) matches.push(value)\n      if (match.index === regex.lastIndex) regex.lastIndex++\n    }\n\n    if (matches.length === 0) return 'No matches found.'\n    const truncated = matches.length === MAX_MATCHES ? ' (truncated at 500)' : ''\n    return JSON.stringify(matches) + truncated\n  },\n})\n"],"mappings":"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"}