{"version":3,"file":"text_analysis.mjs","names":[],"sources":["../../../src/batteries/tools/text_analysis/index.ts"],"sourcesContent":["/**\n * Pre-constructed tools for extracting counts, token estimates, and character statistics from text.\n *\n * @module @nhtio/adk/batteries/tools/text_analysis\n *\n * @remarks\n * Pre-constructed bundled tools for the `text_analysis` category. Import individually, the whole\n * category, or import every tool via `@nhtio/adk/batteries`.\n */\n\nimport { validator } from '@nhtio/validation'\nimport { Tool, SpooledJsonArtifact } from '@nhtio/adk/common'\n\n/**\n * Analyze text and return statistics as a JSON document.\n *\n * @remarks\n * Reports character counts (with and without spaces), word/sentence/paragraph/line counts,\n * unique word count, average word length, a rough token estimate (4 chars/token), and a set\n * of character-class booleans (`is_all_alpha`, `is_all_ascii`, etc.).\n *\n * Output is JSON, so the artifact constructor is set to {@link @nhtio/adk!SpooledJsonArtifact} — consumer\n * code can read the result back as a parsed record without re-parsing.\n */\nexport const textAnalyzeTool = new Tool({\n  name: 'text_analyze',\n  description:\n    'Analyze text and return statistics: character counts, word/sentence/paragraph/line counts, unique word count, average word length, token estimate, and character-set properties.',\n  inputSchema: validator.object({\n    text: validator.string().required().description('The text to analyze'),\n  }),\n  artifactConstructor: () => SpooledJsonArtifact,\n  handler: async (args) => {\n    const { text } = args as { text: string }\n\n    const charCount = text.length\n    const charCountNoSpaces = text.replace(/\\s/g, '').length\n\n    const words = text.trim() === '' ? [] : text.trim().split(/\\s+/)\n    const wordCount = words.length\n\n    const sentenceMatches = text.match(/[.!?]+(?:\\s|$)/g)\n    const sentenceCount = sentenceMatches ? sentenceMatches.length : text.trim().length > 0 ? 1 : 0\n\n    const paragraphs = text.split(/\\n[ \\t]*\\n/).filter((p) => p.trim().length > 0)\n    const paragraphCount = paragraphs.length || (text.trim().length > 0 ? 1 : 0)\n\n    const lineCount = text === '' ? 0 : text.split('\\n').length\n\n    const tokenEstimate = Math.ceil(text.length / 4)\n\n    const uniqueWords = new Set(\n      words.map((w) => w.toLowerCase().replace(/[^a-z0-9]/g, '')).filter(Boolean)\n    )\n    const uniqueWordCount = uniqueWords.size\n\n    const totalWordLen = words.reduce((sum, w) => sum + w.length, 0)\n    const avgWordLength =\n      wordCount > 0 ? Number.parseFloat((totalWordLen / wordCount).toFixed(2)) : 0\n\n    const isAllAlpha = text.trim().length > 0 && /^[a-zA-Z\\s]*$/.test(text)\n    const isAllNumeric = /^\\d+$/.test(text.trim())\n    const isAllAlphanumeric = text.trim().length > 0 && /^[a-zA-Z0-9\\s]*$/.test(text)\n    const isAllAscii = [...text].every((c) => c.charCodeAt(0) < 128)\n    const isAllLowercase = text === text.toLowerCase() && /[a-z]/.test(text)\n    const isAllUppercase = text === text.toUpperCase() && /[A-Z]/.test(text)\n\n    return JSON.stringify(\n      {\n        char_count: charCount,\n        char_count_no_spaces: charCountNoSpaces,\n        word_count: wordCount,\n        unique_word_count: uniqueWordCount,\n        sentence_count: sentenceCount,\n        paragraph_count: paragraphCount,\n        line_count: lineCount,\n        avg_word_length: avgWordLength,\n        token_estimate: tokenEstimate,\n        is_all_alpha: isAllAlpha,\n        is_all_numeric: isAllNumeric,\n        is_all_alphanumeric: isAllAlphanumeric,\n        is_all_ascii: isAllAscii,\n        is_all_lowercase: isAllLowercase,\n        is_all_uppercase: isAllUppercase,\n        has_unicode: !isAllAscii,\n      },\n      null,\n      2\n    )\n  },\n})\n\n/**\n * Operate on text treated as a list of lines.\n *\n * @remarks\n * Operations: `sort`, `sort_desc`, `reverse`, `deduplicate`, `filter_empty`, `trim_each`,\n * `number` (prefix each line with `1.`, `2.`, …), `count`. Sort and deduplicate respect the\n * optional `case_insensitive` flag.\n */\nexport const textLinesTool = new Tool({\n  name: 'text_lines',\n  description:\n    'Perform operations on text treated as a list of lines: sort, deduplicate, reverse, filter empty lines, trim each line, number each line, or count.',\n  inputSchema: validator.object({\n    text: validator.string().required().description('Multi-line text to process'),\n    operation: validator\n      .string()\n      .valid(\n        'sort',\n        'sort_desc',\n        'reverse',\n        'deduplicate',\n        'filter_empty',\n        'trim_each',\n        'number',\n        'count'\n      )\n      .required()\n      .description('Operation to apply to lines'),\n    case_insensitive: validator\n      .boolean()\n      .default(false)\n      .description('For sort / deduplicate: ignore case (default: false)'),\n  }),\n  handler: async (args) => {\n    const {\n      text,\n      operation,\n      case_insensitive: ci,\n    } = args as {\n      text: string\n      operation: string\n      case_insensitive: boolean\n    }\n    const lines = text.split('\\n')\n\n    switch (operation) {\n      case 'sort':\n        return [...lines]\n          .sort((a, b) => (ci ? a.toLowerCase() : a).localeCompare(ci ? b.toLowerCase() : b))\n          .join('\\n')\n      case 'sort_desc':\n        return [...lines]\n          .sort((a, b) => (ci ? b.toLowerCase() : b).localeCompare(ci ? a.toLowerCase() : a))\n          .join('\\n')\n      case 'reverse':\n        return [...lines].reverse().join('\\n')\n      case 'deduplicate': {\n        const seen = new Set<string>()\n        return lines\n          .filter((line) => {\n            const key = ci ? line.toLowerCase() : line\n            if (seen.has(key)) return false\n            seen.add(key)\n            return true\n          })\n          .join('\\n')\n      }\n      case 'filter_empty':\n        return lines.filter((l) => l.trim() !== '').join('\\n')\n      case 'trim_each':\n        return lines.map((l) => l.trim()).join('\\n')\n      case 'number':\n        return lines.map((l, i) => `${i + 1}. ${l}`).join('\\n')\n      case 'count': {\n        const nonEmpty = lines.filter((l) => l.trim() !== '').length\n        return `${lines.length} lines (${nonEmpty} non-empty, ${lines.length - nonEmpty} empty)`\n      }\n      default:\n        return `Error: Unknown operation \"${operation}\".`\n    }\n  },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAa,kBAAkB,IAAI,KAAK;CACtC,MAAM;CACN,aACE;CACF,aAAa,UAAU,OAAO,EAC5B,MAAM,UAAU,OAAO,EAAE,SAAS,EAAE,YAAY,qBAAqB,EACvE,CAAC;CACD,2BAA2B;CAC3B,SAAS,OAAO,SAAS;EACvB,MAAM,EAAE,SAAS;EAEjB,MAAM,YAAY,KAAK;EACvB,MAAM,oBAAoB,KAAK,QAAQ,OAAO,EAAE,EAAE;EAElD,MAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,MAAM,KAAK;EAC/D,MAAM,YAAY,MAAM;EAExB,MAAM,kBAAkB,KAAK,MAAM,iBAAiB;EACpD,MAAM,gBAAgB,kBAAkB,gBAAgB,SAAS,KAAK,KAAK,EAAE,SAAS,IAAI,IAAI;EAG9F,MAAM,iBADa,KAAK,MAAM,YAAY,EAAE,QAAQ,MAAM,EAAE,KAAK,EAAE,SAAS,CACrD,EAAW,WAAW,KAAK,KAAK,EAAE,SAAS,IAAI,IAAI;EAE1E,MAAM,YAAY,SAAS,KAAK,IAAI,KAAK,MAAM,IAAI,EAAE;EAErD,MAAM,gBAAgB,KAAK,KAAK,KAAK,SAAS,CAAC;EAK/C,MAAM,kBAAkB,IAHA,IACtB,MAAM,KAAK,MAAM,EAAE,YAAY,EAAE,QAAQ,cAAc,EAAE,CAAC,EAAE,OAAO,OAAO,CAEpD,EAAY;EAEpC,MAAM,eAAe,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;EAC/D,MAAM,gBACJ,YAAY,IAAI,OAAO,YAAY,eAAe,WAAW,QAAQ,CAAC,CAAC,IAAI;EAE7E,MAAM,aAAa,KAAK,KAAK,EAAE,SAAS,KAAK,gBAAgB,KAAK,IAAI;EACtE,MAAM,eAAe,QAAQ,KAAK,KAAK,KAAK,CAAC;EAC7C,MAAM,oBAAoB,KAAK,KAAK,EAAE,SAAS,KAAK,mBAAmB,KAAK,IAAI;EAChF,MAAM,aAAa,CAAC,GAAG,IAAI,EAAE,OAAO,MAAM,EAAE,WAAW,CAAC,IAAI,GAAG;EAC/D,MAAM,iBAAiB,SAAS,KAAK,YAAY,KAAK,QAAQ,KAAK,IAAI;EACvE,MAAM,iBAAiB,SAAS,KAAK,YAAY,KAAK,QAAQ,KAAK,IAAI;EAEvE,OAAO,KAAK,UACV;GACE,YAAY;GACZ,sBAAsB;GACtB,YAAY;GACZ,mBAAmB;GACnB,gBAAgB;GAChB,iBAAiB;GACjB,YAAY;GACZ,iBAAiB;GACjB,gBAAgB;GAChB,cAAc;GACd,gBAAgB;GAChB,qBAAqB;GACrB,cAAc;GACd,kBAAkB;GAClB,kBAAkB;GAClB,aAAa,CAAC;EAChB,GACA,MACA,CACF;CACF;AACF,CAAC;;;;;;;;;AAUD,IAAa,gBAAgB,IAAI,KAAK;CACpC,MAAM;CACN,aACE;CACF,aAAa,UAAU,OAAO;EAC5B,MAAM,UAAU,OAAO,EAAE,SAAS,EAAE,YAAY,4BAA4B;EAC5E,WAAW,UACR,OAAO,EACP,MACC,QACA,aACA,WACA,eACA,gBACA,aACA,UACA,OACF,EACC,SAAS,EACT,YAAY,6BAA6B;EAC5C,kBAAkB,UACf,QAAQ,EACR,QAAQ,KAAK,EACb,YAAY,sDAAsD;CACvE,CAAC;CACD,SAAS,OAAO,SAAS;EACvB,MAAM,EACJ,MACA,WACA,kBAAkB,OAChB;EAKJ,MAAM,QAAQ,KAAK,MAAM,IAAI;EAE7B,QAAQ,WAAR;GACE,KAAK,QACH,OAAO,CAAC,GAAG,KAAK,EACb,MAAM,GAAG,OAAO,KAAK,EAAE,YAAY,IAAI,GAAG,cAAc,KAAK,EAAE,YAAY,IAAI,CAAC,CAAC,EACjF,KAAK,IAAI;GACd,KAAK,aACH,OAAO,CAAC,GAAG,KAAK,EACb,MAAM,GAAG,OAAO,KAAK,EAAE,YAAY,IAAI,GAAG,cAAc,KAAK,EAAE,YAAY,IAAI,CAAC,CAAC,EACjF,KAAK,IAAI;GACd,KAAK,WACH,OAAO,CAAC,GAAG,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI;GACvC,KAAK,eAAe;IAClB,MAAM,uBAAO,IAAI,IAAY;IAC7B,OAAO,MACJ,QAAQ,SAAS;KAChB,MAAM,MAAM,KAAK,KAAK,YAAY,IAAI;KACtC,IAAI,KAAK,IAAI,GAAG,GAAG,OAAO;KAC1B,KAAK,IAAI,GAAG;KACZ,OAAO;IACT,CAAC,EACA,KAAK,IAAI;GACd;GACA,KAAK,gBACH,OAAO,MAAM,QAAQ,MAAM,EAAE,KAAK,MAAM,EAAE,EAAE,KAAK,IAAI;GACvD,KAAK,aACH,OAAO,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,IAAI;GAC7C,KAAK,UACH,OAAO,MAAM,KAAK,GAAG,MAAM,GAAG,IAAI,EAAE,IAAI,GAAG,EAAE,KAAK,IAAI;GACxD,KAAK,SAAS;IACZ,MAAM,WAAW,MAAM,QAAQ,MAAM,EAAE,KAAK,MAAM,EAAE,EAAE;IACtD,OAAO,GAAG,MAAM,OAAO,UAAU,SAAS,cAAc,MAAM,SAAS,SAAS;GAClF;GACA,SACE,OAAO,6BAA6B,UAAU;EAClD;CACF;AACF,CAAC"}