@nhtio/adk 0.1.0-master-e6c58205 → 0.1.0-master-89b47bec

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/__vite-browser-external-CU7uua8b.mjs +9 -0
  3. package/__vite-browser-external-CU7uua8b.mjs.map +1 -0
  4. package/__vite-browser-external-DAHxjh-D.js +13 -0
  5. package/__vite-browser-external-DAHxjh-D.js.map +1 -0
  6. package/batteries/embeddings/openai/adapter.cjs +1 -1
  7. package/batteries/embeddings/openai/adapter.mjs +1 -1
  8. package/batteries/embeddings/webllm/adapter.cjs +1 -1
  9. package/batteries/embeddings/webllm/adapter.mjs +1 -1
  10. package/batteries/llm/ollama/adapter.cjs +5 -5
  11. package/batteries/llm/ollama/adapter.mjs +5 -5
  12. package/batteries/llm/ollama/helpers.cjs +2 -2
  13. package/batteries/llm/ollama/helpers.mjs +2 -2
  14. package/batteries/llm/ollama/validation.cjs +1 -1
  15. package/batteries/llm/ollama/validation.mjs +1 -1
  16. package/batteries/llm/openai_chat_completions/adapter.cjs +5 -5
  17. package/batteries/llm/openai_chat_completions/adapter.mjs +5 -5
  18. package/batteries/llm/openai_chat_completions/helpers.cjs +2 -2
  19. package/batteries/llm/openai_chat_completions/helpers.mjs +2 -2
  20. package/batteries/llm/openai_chat_completions/validation.cjs +1 -1
  21. package/batteries/llm/openai_chat_completions/validation.mjs +1 -1
  22. package/batteries/llm/webllm_chat_completions/adapter.cjs +5 -5
  23. package/batteries/llm/webllm_chat_completions/adapter.mjs +5 -5
  24. package/batteries/llm/webllm_chat_completions/validation.cjs +1 -1
  25. package/batteries/llm/webllm_chat_completions/validation.mjs +1 -1
  26. package/batteries/media/contracts.cjs +38 -8
  27. package/batteries/media/contracts.cjs.map +1 -1
  28. package/batteries/media/contracts.d.ts +86 -6
  29. package/batteries/media/contracts.mjs +37 -9
  30. package/batteries/media/contracts.mjs.map +1 -1
  31. package/batteries/media/engines/audio_decode.cjs +46 -6
  32. package/batteries/media/engines/audio_decode.cjs.map +1 -1
  33. package/batteries/media/engines/audio_decode.d.ts +9 -5
  34. package/batteries/media/engines/audio_decode.mjs +47 -7
  35. package/batteries/media/engines/audio_decode.mjs.map +1 -1
  36. package/batteries/media/engines/data.cjs +159 -0
  37. package/batteries/media/engines/data.cjs.map +1 -0
  38. package/batteries/media/engines/data.d.ts +36 -0
  39. package/batteries/media/engines/data.mjs +157 -0
  40. package/batteries/media/engines/data.mjs.map +1 -0
  41. package/batteries/media/engines/exceljs.cjs +290 -0
  42. package/batteries/media/engines/exceljs.cjs.map +1 -0
  43. package/batteries/media/engines/exceljs.d.ts +41 -0
  44. package/batteries/media/engines/exceljs.mjs +288 -0
  45. package/batteries/media/engines/exceljs.mjs.map +1 -0
  46. package/batteries/media/engines/execa_executor.cjs +1 -1
  47. package/batteries/media/engines/execa_executor.mjs +1 -1
  48. package/batteries/media/engines/fs_workspace.cjs +1 -1
  49. package/batteries/media/engines/fs_workspace.mjs +1 -1
  50. package/batteries/media/engines/jimp.cjs +24 -2
  51. package/batteries/media/engines/jimp.cjs.map +1 -1
  52. package/batteries/media/engines/jimp.d.ts +3 -1
  53. package/batteries/media/engines/jimp.mjs +24 -2
  54. package/batteries/media/engines/jimp.mjs.map +1 -1
  55. package/batteries/media/engines/sharp.cjs +37 -4
  56. package/batteries/media/engines/sharp.cjs.map +1 -1
  57. package/batteries/media/engines/sharp.d.ts +5 -3
  58. package/batteries/media/engines/sharp.mjs +37 -4
  59. package/batteries/media/engines/sharp.mjs.map +1 -1
  60. package/batteries/media/engines/sheetjs.cjs +438 -0
  61. package/batteries/media/engines/sheetjs.cjs.map +1 -0
  62. package/batteries/media/engines/sheetjs.d.ts +51 -0
  63. package/batteries/media/engines/sheetjs.mjs +436 -0
  64. package/batteries/media/engines/sheetjs.mjs.map +1 -0
  65. package/batteries/media/engines/soffice.cjs +37 -3
  66. package/batteries/media/engines/soffice.cjs.map +1 -1
  67. package/batteries/media/engines/soffice.mjs +38 -4
  68. package/batteries/media/engines/soffice.mjs.map +1 -1
  69. package/batteries/media/engines/tesseract_js.cjs +1 -1
  70. package/batteries/media/engines/tesseract_js.mjs +1 -1
  71. package/batteries/media/engines/transformers_asr.cjs +1 -1
  72. package/batteries/media/engines/transformers_asr.mjs +1 -1
  73. package/batteries/media/forge.cjs +89 -20
  74. package/batteries/media/forge.cjs.map +1 -1
  75. package/batteries/media/forge.mjs +89 -20
  76. package/batteries/media/forge.mjs.map +1 -1
  77. package/batteries/media/formats.d.ts +9 -0
  78. package/batteries/media/index.d.ts +8 -6
  79. package/batteries/media/lint.cjs +270 -58
  80. package/batteries/media/lint.cjs.map +1 -1
  81. package/batteries/media/lint.d.ts +45 -0
  82. package/batteries/media/lint.mjs +267 -58
  83. package/batteries/media/lint.mjs.map +1 -1
  84. package/batteries/media/registry.d.ts +11 -2
  85. package/batteries/media/steps/data.d.ts +26 -0
  86. package/batteries/media/steps/doc.d.ts +17 -7
  87. package/batteries/media/steps/patch.d.ts +90 -0
  88. package/batteries/media/steps/sheet.d.ts +10 -6
  89. package/batteries/media/steps/text.d.ts +4 -1
  90. package/batteries/media/validate.d.ts +5 -0
  91. package/batteries/media/verbs.d.ts +12 -1
  92. package/batteries/media.cjs +869 -448
  93. package/batteries/media.cjs.map +1 -1
  94. package/batteries/media.mjs +829 -409
  95. package/batteries/media.mjs.map +1 -1
  96. package/batteries/storage/flydrive.cjs +3 -6
  97. package/batteries/storage/flydrive.cjs.map +1 -1
  98. package/batteries/storage/flydrive.mjs +3 -6
  99. package/batteries/storage/flydrive.mjs.map +1 -1
  100. package/batteries/storage/in_memory.cjs +1 -1
  101. package/batteries/storage/in_memory.mjs +1 -1
  102. package/batteries/storage/opfs.cjs +1 -1
  103. package/batteries/storage/opfs.mjs +1 -1
  104. package/batteries/tools/_shared.cjs +2 -2
  105. package/batteries/tools/_shared.mjs +2 -2
  106. package/batteries/tools/color.cjs +2 -2
  107. package/batteries/tools/color.mjs +2 -2
  108. package/batteries/tools/comparison.cjs +3 -3
  109. package/batteries/tools/comparison.mjs +3 -3
  110. package/batteries/tools/data_structure.cjs +3 -3
  111. package/batteries/tools/data_structure.mjs +3 -3
  112. package/batteries/tools/datetime_extended.cjs +3 -3
  113. package/batteries/tools/datetime_extended.mjs +3 -3
  114. package/batteries/tools/datetime_math.cjs +3 -3
  115. package/batteries/tools/datetime_math.mjs +3 -3
  116. package/batteries/tools/encoding.cjs +3 -3
  117. package/batteries/tools/encoding.mjs +3 -3
  118. package/batteries/tools/formatting.cjs +3 -3
  119. package/batteries/tools/formatting.mjs +3 -3
  120. package/batteries/tools/geo_basics.cjs +2 -2
  121. package/batteries/tools/geo_basics.mjs +2 -2
  122. package/batteries/tools/math.cjs +3 -3
  123. package/batteries/tools/math.mjs +3 -3
  124. package/batteries/tools/memory.cjs +6 -6
  125. package/batteries/tools/memory.mjs +6 -6
  126. package/batteries/tools/parsing.cjs +4 -4
  127. package/batteries/tools/parsing.mjs +4 -4
  128. package/batteries/tools/retrievables.cjs +5 -5
  129. package/batteries/tools/retrievables.mjs +5 -5
  130. package/batteries/tools/scrapper.cjs +1 -1
  131. package/batteries/tools/scrapper.mjs +1 -1
  132. package/batteries/tools/searxng.cjs +1 -1
  133. package/batteries/tools/searxng.mjs +1 -1
  134. package/batteries/tools/standing_instructions.cjs +4 -4
  135. package/batteries/tools/standing_instructions.mjs +4 -4
  136. package/batteries/tools/statistics.cjs +4 -4
  137. package/batteries/tools/statistics.mjs +4 -4
  138. package/batteries/tools/string_processing.cjs +3 -3
  139. package/batteries/tools/string_processing.mjs +3 -3
  140. package/batteries/tools/structured_data.cjs +3 -3
  141. package/batteries/tools/structured_data.mjs +3 -3
  142. package/batteries/tools/text_analysis.cjs +3 -3
  143. package/batteries/tools/text_analysis.mjs +3 -3
  144. package/batteries/tools/text_comparison.cjs +2 -2
  145. package/batteries/tools/text_comparison.mjs +2 -2
  146. package/batteries/tools/time.cjs +3 -3
  147. package/batteries/tools/time.mjs +3 -3
  148. package/batteries/tools/unit_conversion.cjs +2 -2
  149. package/batteries/tools/unit_conversion.mjs +2 -2
  150. package/batteries/tools/web_retrieval.cjs +1 -1
  151. package/batteries/tools/web_retrieval.mjs +1 -1
  152. package/batteries/tools.cjs +2 -2
  153. package/batteries/tools.mjs +2 -2
  154. package/batteries/vector/arangodb.cjs +1 -1
  155. package/batteries/vector/arangodb.mjs +1 -1
  156. package/batteries/vector/clickhouse.cjs +1 -1
  157. package/batteries/vector/clickhouse.mjs +1 -1
  158. package/batteries/vector/cloudflare.cjs +1 -1
  159. package/batteries/vector/cloudflare.mjs +1 -1
  160. package/batteries/vector/couchbase.cjs +1 -1
  161. package/batteries/vector/couchbase.mjs +1 -1
  162. package/batteries/vector/duckdb.cjs +1 -1
  163. package/batteries/vector/duckdb.mjs +1 -1
  164. package/batteries/vector/elasticsearch.cjs +1 -1
  165. package/batteries/vector/elasticsearch.mjs +1 -1
  166. package/batteries/vector/filters.cjs +1 -1
  167. package/batteries/vector/filters.mjs +1 -1
  168. package/batteries/vector/hnswlib.cjs +1 -1
  169. package/batteries/vector/hnswlib.mjs +1 -1
  170. package/batteries/vector/lancedb.cjs +1 -1
  171. package/batteries/vector/lancedb.mjs +1 -1
  172. package/batteries/vector/mariadb.cjs +1 -1
  173. package/batteries/vector/mariadb.mjs +1 -1
  174. package/batteries/vector/meilisearch.cjs +1 -1
  175. package/batteries/vector/meilisearch.mjs +1 -1
  176. package/batteries/vector/migrate.cjs +1 -1
  177. package/batteries/vector/migrate.mjs +1 -1
  178. package/batteries/vector/mongodb.cjs +1 -1
  179. package/batteries/vector/mongodb.mjs +1 -1
  180. package/batteries/vector/neo4j.cjs +1 -1
  181. package/batteries/vector/neo4j.mjs +1 -1
  182. package/batteries/vector/opensearch.cjs +1 -1
  183. package/batteries/vector/opensearch.mjs +1 -1
  184. package/batteries/vector/oracle23ai.cjs +1 -1
  185. package/batteries/vector/oracle23ai.mjs +1 -1
  186. package/batteries/vector/pinecone.cjs +1 -1
  187. package/batteries/vector/pinecone.mjs +1 -1
  188. package/batteries/vector/redis.cjs +1 -1
  189. package/batteries/vector/redis.mjs +1 -1
  190. package/batteries/vector/retrievable.cjs +1 -1
  191. package/batteries/vector/retrievable.mjs +1 -1
  192. package/batteries/vector/s3vectors.cjs +1 -1
  193. package/batteries/vector/s3vectors.mjs +1 -1
  194. package/batteries/vector/solr.cjs +1 -1
  195. package/batteries/vector/solr.mjs +1 -1
  196. package/batteries/vector/surrealdb.cjs +1 -1
  197. package/batteries/vector/surrealdb.mjs +1 -1
  198. package/batteries/vector/typesense.cjs +1 -1
  199. package/batteries/vector/typesense.mjs +1 -1
  200. package/batteries/vector/vespa.cjs +1 -1
  201. package/batteries/vector/vespa.mjs +1 -1
  202. package/batteries/vector/weaviate.cjs +1 -1
  203. package/batteries/vector/weaviate.mjs +1 -1
  204. package/batteries.cjs +2 -2
  205. package/batteries.mjs +2 -2
  206. package/bundled_xlsx.d.ts +1075 -0
  207. package/{common-BxoznqbX.mjs → common-BQoFv108.mjs} +7 -7
  208. package/{common-BxoznqbX.mjs.map → common-BQoFv108.mjs.map} +1 -1
  209. package/{common-BBK2e5ro.js → common-DmDn4Gfl.js} +7 -7
  210. package/{common-BBK2e5ro.js.map → common-DmDn4Gfl.js.map} +1 -1
  211. package/common.cjs +7 -7
  212. package/common.mjs +7 -7
  213. package/{dispatch_runner-Hn5idJYx.js → dispatch_runner-BwplbCPr.js} +3 -3
  214. package/{dispatch_runner-Hn5idJYx.js.map → dispatch_runner-BwplbCPr.js.map} +1 -1
  215. package/{dispatch_runner-DUrpj0xR.mjs → dispatch_runner-C1ar_D7W.mjs} +3 -3
  216. package/{dispatch_runner-DUrpj0xR.mjs.map → dispatch_runner-C1ar_D7W.mjs.map} +1 -1
  217. package/dispatch_runner.cjs +1 -1
  218. package/dispatch_runner.mjs +1 -1
  219. package/eslint.cjs +1 -1
  220. package/eslint.mjs +1 -1
  221. package/forge.cjs +4 -4
  222. package/forge.mjs +4 -4
  223. package/formats-CCylUHZw.mjs +161 -0
  224. package/formats-CCylUHZw.mjs.map +1 -0
  225. package/formats-DZVuqZUN.js +196 -0
  226. package/formats-DZVuqZUN.js.map +1 -0
  227. package/guards.cjs +8 -8
  228. package/guards.mjs +8 -8
  229. package/index.cjs +11 -11
  230. package/index.mjs +11 -11
  231. package/mcp/adk-docs-corpus.json +1 -1
  232. package/package.json +198 -182
  233. package/{scrapper-DLj_KkxK.mjs → scrapper-Dn2wXu0Y.mjs} +5 -5
  234. package/{scrapper-DLj_KkxK.mjs.map → scrapper-Dn2wXu0Y.mjs.map} +1 -1
  235. package/{scrapper-Cmf1CSA-.js → scrapper-mBZ0Odq7.js} +5 -5
  236. package/{scrapper-Cmf1CSA-.js.map → scrapper-mBZ0Odq7.js.map} +1 -1
  237. package/{searxng-BKs5s8Su.js → searxng-Dj-iP_Vz.js} +5 -5
  238. package/{searxng-BKs5s8Su.js.map → searxng-Dj-iP_Vz.js.map} +1 -1
  239. package/{searxng-BJ-aZcMz.mjs → searxng-DjFxtFf6.mjs} +5 -5
  240. package/{searxng-BJ-aZcMz.mjs.map → searxng-DjFxtFf6.mjs.map} +1 -1
  241. package/server.json +21 -0
  242. package/skills/adk-assembly/SKILL.md +2 -2
  243. package/{spooled_artifact-BR0IhU9y.js → spooled_artifact-BCmPrxZS.js} +3 -3
  244. package/{spooled_artifact-BR0IhU9y.js.map → spooled_artifact-BCmPrxZS.js.map} +1 -1
  245. package/{spooled_artifact-nTE6ujM8.mjs → spooled_artifact-DL36jnqA.mjs} +3 -3
  246. package/{spooled_artifact-nTE6ujM8.mjs.map → spooled_artifact-DL36jnqA.mjs.map} +1 -1
  247. package/spooled_artifact.cjs +2 -2
  248. package/spooled_artifact.mjs +2 -2
  249. package/{spooled_markdown_artifact-Dj3fNowz.js → spooled_markdown_artifact-CD4PrrpV.js} +3 -3
  250. package/{spooled_markdown_artifact-Dj3fNowz.js.map → spooled_markdown_artifact-CD4PrrpV.js.map} +1 -1
  251. package/{spooled_markdown_artifact-DIYzbQv8.mjs → spooled_markdown_artifact-Da-Xnebh.mjs} +3 -3
  252. package/{spooled_markdown_artifact-DIYzbQv8.mjs.map → spooled_markdown_artifact-Da-Xnebh.mjs.map} +1 -1
  253. package/{thought-Cde2Y_GK.mjs → thought-CtOoZUSE.mjs} +3 -3
  254. package/{thought-Cde2Y_GK.mjs.map → thought-CtOoZUSE.mjs.map} +1 -1
  255. package/{thought-BFtsnUTU.js → thought-eka4iUHa.js} +3 -3
  256. package/{thought-BFtsnUTU.js.map → thought-eka4iUHa.js.map} +1 -1
  257. package/{tool-CcxYxA5X.mjs → tool-CITF20yJ.mjs} +3 -3
  258. package/{tool-CcxYxA5X.mjs.map → tool-CITF20yJ.mjs.map} +1 -1
  259. package/{tool-B2nnkwNU.js → tool-Dhjca7rC.js} +3 -3
  260. package/{tool-B2nnkwNU.js.map → tool-Dhjca7rC.js.map} +1 -1
  261. package/{tool_call-BxL3eU1k.js → tool_call-Cyi0hUUc.js} +4 -4
  262. package/{tool_call-BxL3eU1k.js.map → tool_call-Cyi0hUUc.js.map} +1 -1
  263. package/{tool_call-B3MOog2R.mjs → tool_call-D_k36aI4.mjs} +4 -4
  264. package/{tool_call-B3MOog2R.mjs.map → tool_call-D_k36aI4.mjs.map} +1 -1
  265. package/{tool_registry-BaLUIzWz.mjs → tool_registry-CZ55mIti.mjs} +152 -143
  266. package/tool_registry-CZ55mIti.mjs.map +1 -0
  267. package/{tool_registry-BdKlPuHS.js → tool_registry-DwZ6pzN4.js} +151 -142
  268. package/tool_registry-DwZ6pzN4.js.map +1 -0
  269. package/{turn_runner-M2I8G2ok.js → turn_runner-CCZcsoDU.js} +5 -5
  270. package/{turn_runner-M2I8G2ok.js.map → turn_runner-CCZcsoDU.js.map} +1 -1
  271. package/{turn_runner-BV4BaZK9.mjs → turn_runner-DRPpVvsd.mjs} +5 -5
  272. package/{turn_runner-BV4BaZK9.mjs.map → turn_runner-DRPpVvsd.mjs.map} +1 -1
  273. package/turn_runner.cjs +1 -1
  274. package/turn_runner.mjs +1 -1
  275. package/{validate-JOHfh-BB.mjs → validate-C_hTnDBE.mjs} +95 -8
  276. package/validate-C_hTnDBE.mjs.map +1 -0
  277. package/{validate-DgDZkUKs.js → validate-gaSa9bsk.js} +95 -8
  278. package/validate-gaSa9bsk.js.map +1 -0
  279. package/xlsx-B0sTpEC2.js +30615 -0
  280. package/xlsx-B0sTpEC2.js.map +1 -0
  281. package/xlsx-DiMpn_Xv.mjs +30596 -0
  282. package/xlsx-DiMpn_Xv.mjs.map +1 -0
  283. package/xlsx.zahl-D2_Q-Xs4.mjs +6 -0
  284. package/xlsx.zahl-D2_Q-Xs4.mjs.map +1 -0
  285. package/xlsx.zahl-ZIcUQVv8.js +7 -0
  286. package/xlsx.zahl-ZIcUQVv8.js.map +1 -0
  287. package/tool_registry-BaLUIzWz.mjs.map +0 -1
  288. package/tool_registry-BdKlPuHS.js.map +0 -1
  289. package/validate-DgDZkUKs.js.map +0 -1
  290. package/validate-JOHfh-BB.mjs.map +0 -1
@@ -1,10 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../chunk-Cek0wNdY.js");
3
- const require_tool_registry = require("../tool_registry-BdKlPuHS.js");
3
+ const require_tool_registry = require("../tool_registry-DwZ6pzN4.js");
4
4
  require("../guards.cjs");
5
5
  const require_batteries_media_contracts = require("./media/contracts.cjs");
6
6
  const require_exceptions = require("../exceptions-D79HIKeX.js");
7
- const require_validate = require("../validate-DgDZkUKs.js");
7
+ const require_validate = require("../validate-gaSa9bsk.js");
8
+ const require_formats = require("../formats-DZVuqZUN.js");
8
9
  let _nhtio_validation = require("@nhtio/validation");
9
10
  let _nhtio_middleware = require("@nhtio/middleware");
10
11
  let moo = require("moo");
@@ -48,7 +49,7 @@ var lexer = moo.default.compile({
48
49
  pipe: "|",
49
50
  eq: "=",
50
51
  comma: ",",
51
- ref: /@[A-Za-z0-9_-]+/,
52
+ ref: /@[A-Za-z0-9:_-]+/,
52
53
  regex: /\/(?:\\.|\[(?:\\.|[^\]\\\n])*\]|[^/\\[\n])+\/[a-z]*/,
53
54
  dstring: /"(?:\\.|[^"\\\n])*"/,
54
55
  sstring: /'(?:\\.|[^'\\\n])*'/,
@@ -87,7 +88,7 @@ var spanOf = (tok, length) => ({
87
88
  length: length ?? tok.text.length
88
89
  });
89
90
  var unquote = (raw) => raw.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\(["'\\/])/g, "$1");
90
- var fail$5 = (tok, message, exemplar) => {
91
+ var fail$6 = (tok, message, exemplar) => {
91
92
  throw new require_exceptions.E_MEDIA_PIPE_SYNTAX([`${message}${tok ? ` at line ${tok.line}, col ${tok.col}` : " at end of input"}. Write it like: ${exemplar}`]);
92
93
  };
93
94
  var Parser = class {
@@ -104,19 +105,19 @@ var Parser = class {
104
105
  }
105
106
  parsePipeline() {
106
107
  const segments = [];
107
- if (this.#toks.length === 0) fail$5(void 0, "empty pipe expression", "extract text | chunk by=sentence");
108
+ if (this.#toks.length === 0) fail$6(void 0, "empty pipe expression", "extract text | chunk by=sentence");
108
109
  segments.push(this.#parseSegment());
109
110
  while (this.#peek()?.type === "pipe") {
110
111
  this.#next();
111
112
  segments.push(this.#parseSegment());
112
113
  }
113
114
  const trailing = this.#peek();
114
- if (trailing) fail$5(trailing, `unexpected "${trailing.text}" after a complete segment`, "segment | segment (segments are separated by |)");
115
+ if (trailing) fail$6(trailing, `unexpected "${trailing.text}" after a complete segment`, "segment | segment (segments are separated by |)");
115
116
  return segments;
116
117
  }
117
118
  #parseSegment() {
118
119
  const first = this.#peek();
119
- if (!first || first.type !== "ident") fail$5(first, "expected a verb to start the segment", "convert to=pdf");
120
+ if (!first || first.type !== "ident") fail$6(first, "expected a verb to start the segment", "convert to=pdf");
120
121
  const head = this.#next();
121
122
  const collected = [head.value];
122
123
  const posAfterWord = [this.#pos];
@@ -139,12 +140,12 @@ var Parser = class {
139
140
  for (;;) {
140
141
  const tok = this.#peek();
141
142
  if (!tok || tok.type === "pipe") break;
142
- if (tok.type === "dash") fail$5(tok, "unexpected \"-\" — values containing dashes must be quoted", `${verbText} name="value-with-dashes"`);
143
- if (tok.type !== "ident" || this.#peek(1)?.type !== "eq") fail$5(tok, `unexpected "${tok.text}" — args must be name=value`, `${verbText} name=value`);
143
+ if (tok.type === "dash") fail$6(tok, "unexpected \"-\" — values containing dashes must be quoted", `${verbText} name="value-with-dashes"`);
144
+ if (tok.type !== "ident" || this.#peek(1)?.type !== "eq") fail$6(tok, `unexpected "${tok.text}" — args must be name=value`, `${verbText} name=value`);
144
145
  const name = this.#next().value;
145
146
  this.#next();
146
147
  const value = this.#parseValue(verbText, name);
147
- if (args.has(name)) fail$5(tok, `duplicate arg "${name}"`, `${verbText} ${name}=value (give each arg once)`);
148
+ if (args.has(name)) fail$6(tok, `duplicate arg "${name}"`, `${verbText} ${name}=value (give each arg once)`);
148
149
  args.set(name, value);
149
150
  }
150
151
  return {
@@ -173,7 +174,7 @@ var Parser = class {
173
174
  }
174
175
  #parseScalar(verbText, argName) {
175
176
  const tok = this.#peek();
176
- if (!tok) fail$5(tok, `missing value for "${argName}"`, `${verbText} ${argName}=value`);
177
+ if (!tok) fail$6(tok, `missing value for "${argName}"`, `${verbText} ${argName}=value`);
177
178
  const t = tok;
178
179
  switch (t.type) {
179
180
  case "int":
@@ -193,7 +194,7 @@ var Parser = class {
193
194
  case "range": {
194
195
  this.#next();
195
196
  const [lo, hi] = t.value.split("-").map(Number);
196
- if (lo > hi) fail$5(t, `range ${t.value} is descending (start must be ≤ end)`, `${argName}=${hi}-${lo}`);
197
+ if (lo > hi) fail$6(t, `range ${t.value} is descending (start must be ≤ end)`, `${argName}=${hi}-${lo}`);
197
198
  const nums = [];
198
199
  for (let n = lo; n <= hi; n++) nums.push(n);
199
200
  return {
@@ -236,7 +237,7 @@ var Parser = class {
236
237
  new RegExp(source, flags);
237
238
  } catch (err) {
238
239
  const detail = require_tool_registry.isError(err) ? err.message : String(err);
239
- fail$5(t, `invalid regex ${t.text}: ${detail}`, `${argName}=/pattern/flags`);
240
+ fail$6(t, `invalid regex ${t.text}: ${detail}`, `${argName}=/pattern/flags`);
240
241
  }
241
242
  return {
242
243
  value: {
@@ -258,9 +259,9 @@ var Parser = class {
258
259
  span: spanOf(t)
259
260
  };
260
261
  case "dash":
261
- fail$5(t, "unexpected \"-\" in a bare value", `${argName}="value-with-dashes" (quote values containing dashes)`);
262
+ fail$6(t, "unexpected \"-\" in a bare value", `${argName}="value-with-dashes" (quote values containing dashes)`);
262
263
  break;
263
- default: fail$5(t, `unexpected "${t.text}"`, `${verbText} ${argName}=value`);
264
+ default: fail$6(t, `unexpected "${t.text}"`, `${verbText} ${argName}=value`);
264
265
  }
265
266
  throw new require_exceptions.E_MEDIA_PIPE_SYNTAX(["internal parser error"]);
266
267
  }
@@ -798,137 +799,255 @@ var materializedName = (filename) => {
798
799
  /** Convenience: read a step arg with a typed cast (validation already ran). */
799
800
  var argOf = (step, name) => step.args[name];
800
801
  //#endregion
801
- //#region src/batteries/media/formats.ts
802
- /** Well-known MIME constants used across the battery. */
803
- var MIME = {
804
- DOCX: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
805
- XLSX: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
806
- PPTX: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
807
- ODT: "application/vnd.oasis.opendocument.text",
808
- ODS: "application/vnd.oasis.opendocument.spreadsheet",
809
- ODP: "application/vnd.oasis.opendocument.presentation",
810
- PDF: "application/pdf",
811
- DOC: "application/msword",
812
- XLS: "application/vnd.ms-excel",
813
- PPT: "application/vnd.ms-powerpoint",
814
- PAGES: "application/x-iwork-pages-sffpages",
815
- NUMBERS: "application/x-iwork-numbers-sffnumbers",
816
- KEYNOTE: "application/x-iwork-keynote-sffkey",
817
- TXT: "text/plain",
818
- MD: "text/markdown",
819
- CSV: "text/csv",
820
- HTML: "text/html",
821
- JSON: "application/json",
822
- PNG: "image/png",
823
- JPEG: "image/jpeg",
824
- WEBP: "image/webp",
825
- TIFF: "image/tiff",
826
- AVIF: "image/avif",
827
- WAV: "audio/wav",
828
- MP3: "audio/mpeg"
829
- };
830
- /** Legacy Office MIME types (extraction only — mutation is rejected). */
831
- var LEGACY_OFFICE_MIMES = new Set([
832
- MIME.DOC,
833
- MIME.XLS,
834
- MIME.PPT
835
- ]);
836
- /** Apple iWork MIME types (extraction only — mutation is rejected). */
837
- var IWORK_MIMES = new Set([
838
- MIME.PAGES,
839
- MIME.NUMBERS,
840
- MIME.KEYNOTE
841
- ]);
842
- var SPREADSHEET_MIMES = new Set([
843
- MIME.XLSX,
844
- MIME.ODS,
845
- MIME.XLS,
846
- MIME.CSV
847
- ]);
848
- var PRESENTATION_MIMES = new Set([
849
- MIME.PPTX,
850
- MIME.ODP,
851
- MIME.PPT,
852
- MIME.KEYNOTE
853
- ]);
854
- var DOCUMENT_MIMES = new Set([
855
- MIME.PDF,
856
- MIME.DOCX,
857
- MIME.ODT,
858
- MIME.DOC,
859
- MIME.PAGES,
860
- MIME.TXT,
861
- MIME.MD,
862
- MIME.HTML,
863
- MIME.JSON,
864
- "application/rtf",
865
- "text/rtf"
866
- ]);
802
+ //#region src/batteries/media/steps/patch.ts
803
+ var PATCH_PREFIX = "*** Begin Patch";
804
+ var PATCH_SUFFIX = "*** End Patch";
805
+ var ADD_FILE_PREFIX = "*** Add File:";
806
+ var DELETE_FILE_PREFIX = "*** Delete File:";
807
+ var UPDATE_FILE_PREFIX = "*** Update File:";
808
+ var MOVE_TO_PREFIX = "*** Move to:";
809
+ /** `true` when `patch` is the structured envelope rather than a unified diff. */
810
+ var isStructuredPatch = (patch) => patch.trimStart().startsWith(PATCH_PREFIX);
811
+ /** Normalize a workspace path: relative, no `.`/`..`/empty segments, forward slashes. */
812
+ var normalizeWorkspacePath = (path) => {
813
+ const normalized = path.replace(/\\/g, "/").trim();
814
+ if (!normalized) throw new Error("apply_patch path cannot be empty");
815
+ if (normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) throw new Error(`apply_patch path "${path}" must be relative to the workspace root`);
816
+ const segments = normalized.split("/");
817
+ const sanitizedSegments = [];
818
+ for (const segment of segments) {
819
+ if (!segment || segment === "." || segment === "..") throw new Error(`apply_patch path "${path}" contains invalid segment "${segment || "(empty)"}"`);
820
+ sanitizedSegments.push(segment);
821
+ }
822
+ return sanitizedSegments.join("/");
823
+ };
867
824
  /**
868
- * Classify a MIME type into the verb table's broad format family.
825
+ * Parse a structured `*** Begin Patch` envelope.
869
826
  *
870
- * @param mimeType - The media MIME type.
871
- * @returns The family used by verb-applicability checks.
827
+ * @param patch - The raw patch text.
828
+ * @returns The parsed operations.
872
829
  */
873
- var familyOf = (mimeType) => {
874
- const mime = mimeType.toLowerCase().split(";")[0].trim();
875
- if (SPREADSHEET_MIMES.has(mime)) return "spreadsheet";
876
- if (PRESENTATION_MIMES.has(mime)) return "presentation";
877
- if (DOCUMENT_MIMES.has(mime)) return "document";
878
- if (mime.startsWith("image/")) return "image";
879
- if (mime.startsWith("audio/")) return "audio";
880
- if (mime.startsWith("text/")) return "document";
881
- return "document";
830
+ var parseStructuredPatch = (patch) => {
831
+ const lines = patch.replace(/\r\n/g, "\n").split("\n");
832
+ if (lines[0]?.trim() !== PATCH_PREFIX) throw new Error("a structured patch must start with \"*** Begin Patch\"");
833
+ const lastNonEmptyIndex = [...lines].reverse().findIndex((line) => line.trim() !== "");
834
+ const endIndex = lastNonEmptyIndex === -1 ? -1 : lines.length - 1 - lastNonEmptyIndex;
835
+ if (endIndex < 0 || lines[endIndex]?.trim() !== PATCH_SUFFIX) throw new Error("a structured patch must end with \"*** End Patch\"");
836
+ const body = lines.slice(1, endIndex);
837
+ const operations = [];
838
+ let totalAdded = 0;
839
+ let totalRemoved = 0;
840
+ let i = 0;
841
+ while (i < body.length) {
842
+ const line = body[i] ?? "";
843
+ if (line.trim() === "" || line.startsWith("*** End of File")) {
844
+ i += 1;
845
+ continue;
846
+ }
847
+ if (line.startsWith(ADD_FILE_PREFIX)) {
848
+ const path = normalizeWorkspacePath(line.slice(13).trim());
849
+ i += 1;
850
+ const contentLines = [];
851
+ while (i < body.length) {
852
+ const next = body[i] ?? "";
853
+ if (next.startsWith("*** ")) break;
854
+ if (!next.startsWith("+")) throw new Error(`apply_patch add-file line must start with "+": "${next}"`);
855
+ contentLines.push(next.slice(1));
856
+ i += 1;
857
+ }
858
+ operations.push({
859
+ type: "add",
860
+ path,
861
+ content: contentLines.join("\n"),
862
+ added: contentLines.length
863
+ });
864
+ totalAdded += contentLines.length;
865
+ continue;
866
+ }
867
+ if (line.startsWith(DELETE_FILE_PREFIX)) {
868
+ const path = normalizeWorkspacePath(line.slice(16).trim());
869
+ operations.push({
870
+ type: "delete",
871
+ path
872
+ });
873
+ i += 1;
874
+ continue;
875
+ }
876
+ if (!line.startsWith(UPDATE_FILE_PREFIX)) throw new Error(`apply_patch invalid structured patch header: "${line}"`);
877
+ const path = normalizeWorkspacePath(line.slice(16).trim());
878
+ i += 1;
879
+ let movePath;
880
+ if ((body[i] ?? "").startsWith(MOVE_TO_PREFIX)) {
881
+ movePath = normalizeWorkspacePath((body[i] ?? "").slice(12).trim());
882
+ i += 1;
883
+ }
884
+ const hunks = [];
885
+ let opAdded = 0;
886
+ let opRemoved = 0;
887
+ while (i < body.length && (body[i] ?? "").startsWith("@@")) {
888
+ i += 1;
889
+ const hunkLines = [];
890
+ while (i < body.length) {
891
+ const next = body[i] ?? "";
892
+ if (next.startsWith("@@") || next.startsWith("*** ")) break;
893
+ hunkLines.push(next);
894
+ i += 1;
895
+ }
896
+ if (hunkLines.length === 0) throw new Error("apply_patch contains an empty hunk");
897
+ const oldLines = [];
898
+ const newLines = [];
899
+ let added = 0;
900
+ let removed = 0;
901
+ for (const hunkLine of hunkLines) if (hunkLine.startsWith("+")) {
902
+ newLines.push(hunkLine.slice(1));
903
+ added += 1;
904
+ } else if (hunkLine.startsWith("-")) {
905
+ oldLines.push(hunkLine.slice(1));
906
+ removed += 1;
907
+ } else if (hunkLine.startsWith(" ")) {
908
+ const contextLine = hunkLine.slice(1);
909
+ oldLines.push(contextLine);
910
+ newLines.push(contextLine);
911
+ } else throw new Error(`apply_patch invalid hunk line (must start with +, -, or space): "${hunkLine}"`);
912
+ if (added === 0 && removed === 0) throw new Error("apply_patch hunk must contain at least one added or removed line");
913
+ opAdded += added;
914
+ opRemoved += removed;
915
+ hunks.push({
916
+ oldLines,
917
+ newLines,
918
+ added,
919
+ removed
920
+ });
921
+ }
922
+ if (hunks.length === 0) throw new Error(`apply_patch update operation for "${path}" must contain at least one hunk`);
923
+ operations.push({
924
+ type: "update",
925
+ path,
926
+ movePath,
927
+ hunks,
928
+ added: opAdded,
929
+ removed: opRemoved
930
+ });
931
+ totalAdded += opAdded;
932
+ totalRemoved += opRemoved;
933
+ }
934
+ if (operations.length === 0) throw new Error("apply_patch contains no operations");
935
+ return {
936
+ operations,
937
+ added: totalAdded,
938
+ removed: totalRemoved
939
+ };
940
+ };
941
+ var countSequenceMatches = (haystack, needle, start) => {
942
+ if (needle.length === 0) return 0;
943
+ let matches = 0;
944
+ let i = start;
945
+ while (i <= haystack.length - needle.length) {
946
+ let matched = true;
947
+ let j = 0;
948
+ while (j < needle.length) {
949
+ if (haystack[i + j] !== needle[j]) {
950
+ matched = false;
951
+ break;
952
+ }
953
+ j += 1;
954
+ }
955
+ if (matched) matches += 1;
956
+ i += 1;
957
+ }
958
+ return matches;
959
+ };
960
+ var findExactSequence = (haystack, needle, start) => {
961
+ if (needle.length === 0) return start;
962
+ let i = start;
963
+ while (i <= haystack.length - needle.length) {
964
+ let matched = true;
965
+ let j = 0;
966
+ while (j < needle.length) {
967
+ if (haystack[i + j] !== needle[j]) {
968
+ matched = false;
969
+ break;
970
+ }
971
+ j += 1;
972
+ }
973
+ if (matched) return i;
974
+ i += 1;
975
+ }
976
+ return -1;
882
977
  };
883
978
  /**
884
- * Reason a mutation verb cannot run against this MIME type, or `undefined` when mutation is
885
- * allowed. Returns a reason string (not a throw) so callers compose model-actionable failures.
979
+ * Apply an update operation's hunks to `inputText`. Context matching is exact and rejects
980
+ * ambiguity: a hunk whose old-lines match more than one location past the cursor fails
981
+ * rather than guessing.
886
982
  *
887
- * @param mimeType - The media MIME type.
888
- * @returns A human-readable reason, or `undefined` when mutation is supported.
983
+ * @param inputText - The file's current text.
984
+ * @param hunks - The parsed hunks, in order.
985
+ * @returns The patched text.
889
986
  */
890
- var unsupportedForMutationReason = (mimeType) => {
891
- const mime = mimeType.toLowerCase().split(";")[0].trim();
892
- if (LEGACY_OFFICE_MIMES.has(mime)) return `legacy Office format (${mime}) supports extraction only — convert it first (e.g. convert to=docx)`;
893
- if (IWORK_MIMES.has(mime)) return `Apple iWork format (${mime}) supports extraction only`;
894
- };
895
- /** A small extension → MIME lookup for filenames produced by transforms. */
896
- var EXT_TO_MIME = {
897
- pdf: MIME.PDF,
898
- docx: MIME.DOCX,
899
- xlsx: MIME.XLSX,
900
- pptx: MIME.PPTX,
901
- odt: MIME.ODT,
902
- ods: MIME.ODS,
903
- odp: MIME.ODP,
904
- doc: MIME.DOC,
905
- xls: MIME.XLS,
906
- ppt: MIME.PPT,
907
- txt: MIME.TXT,
908
- md: MIME.MD,
909
- csv: MIME.CSV,
910
- html: MIME.HTML,
911
- json: MIME.JSON,
912
- rtf: "application/rtf",
913
- png: MIME.PNG,
914
- jpg: MIME.JPEG,
915
- jpeg: MIME.JPEG,
916
- webp: MIME.WEBP,
917
- tiff: MIME.TIFF,
918
- avif: MIME.AVIF,
919
- wav: MIME.WAV,
920
- mp3: MIME.MP3
987
+ var applyUpdateHunks = (inputText, hunks) => {
988
+ const lines = inputText.replace(/\r\n/g, "\n").split("\n");
989
+ let cursor = 0;
990
+ for (const hunk of hunks) {
991
+ if (countSequenceMatches(lines, hunk.oldLines, cursor) > 1) throw new Error("patch context is ambiguous and matches multiple locations");
992
+ const start = findExactSequence(lines, hunk.oldLines, cursor);
993
+ if (start === -1) throw new Error("the patch could not be applied cleanly to the source text");
994
+ lines.splice(start, hunk.oldLines.length, ...hunk.newLines);
995
+ cursor = start + hunk.newLines.length;
996
+ }
997
+ return lines.join("\n");
921
998
  };
922
999
  /**
923
- * Replace (or add) a filename's extension.
1000
+ * Apply a parsed structured patch to a virtual workspace of files keyed by normalized path.
924
1001
  *
925
- * @param filename - The original filename.
926
- * @param ext - The new extension, without the dot.
927
- * @returns The filename with the new extension.
1002
+ * @param files - The workspace (mutated in place).
1003
+ * @param patch - The parsed envelope.
1004
+ * @returns The workspace and the number of files touched.
928
1005
  */
929
- var replaceExtension = (filename, ext) => {
930
- const dot = filename.lastIndexOf(".");
931
- return `${dot > 0 ? filename.slice(0, dot) : filename}.${ext}`;
1006
+ var applyOperations = (files, patch) => {
1007
+ let modifiedFiles = 0;
1008
+ for (const operation of patch.operations) {
1009
+ if (operation.type === "add") {
1010
+ if (files.has(operation.path)) throw new Error(`cannot add file "${operation.path}": the path already exists`);
1011
+ files.set(operation.path, {
1012
+ text: operation.content,
1013
+ mimeType: inferTextMimeFromPath(operation.path)
1014
+ });
1015
+ modifiedFiles += 1;
1016
+ continue;
1017
+ }
1018
+ if (operation.type === "delete") {
1019
+ if (!files.has(operation.path)) throw new Error(`cannot delete file "${operation.path}": the path does not exist`);
1020
+ files.delete(operation.path);
1021
+ modifiedFiles += 1;
1022
+ continue;
1023
+ }
1024
+ const current = files.get(operation.path);
1025
+ if (!current) throw new Error(`cannot update file "${operation.path}": the path does not exist`);
1026
+ const updatedText = applyUpdateHunks(current.text, operation.hunks);
1027
+ const targetPath = operation.movePath ?? operation.path;
1028
+ if (operation.movePath && files.has(targetPath)) throw new Error(`cannot move file "${operation.path}" to "${targetPath}": the target already exists`);
1029
+ files.delete(operation.path);
1030
+ files.set(targetPath, {
1031
+ text: updatedText,
1032
+ mimeType: current.mimeType
1033
+ });
1034
+ modifiedFiles += 1;
1035
+ }
1036
+ return {
1037
+ files,
1038
+ modifiedFiles
1039
+ };
1040
+ };
1041
+ /** Infer a text MIME from a workspace path's extension (Add File outputs). */
1042
+ var inferTextMimeFromPath = (path) => {
1043
+ const dot = path.lastIndexOf(".");
1044
+ const ext = dot > 0 ? path.slice(dot + 1).toLowerCase() : "";
1045
+ if (ext === "json") return "application/json";
1046
+ if (ext === "md" || ext === "markdown") return "text/markdown";
1047
+ if (ext === "csv") return "text/csv";
1048
+ if (ext === "yaml" || ext === "yml") return "application/yaml";
1049
+ if (ext === "html" || ext === "htm") return "text/html";
1050
+ return "text/plain";
932
1051
  };
933
1052
  //#endregion
934
1053
  //#region src/batteries/media/steps/text.ts
@@ -999,20 +1118,20 @@ var splitFixed = (text, chunkSize, overlap) => {
999
1118
  }
1000
1119
  return chunks;
1001
1120
  };
1002
- var decoder = new TextDecoder("utf-8", { fatal: false });
1003
- var encoder = new TextEncoder();
1121
+ var decoder$1 = new TextDecoder("utf-8", { fatal: false });
1122
+ var encoder$1 = new TextEncoder();
1004
1123
  /** Decode payload bytes as UTF-8 text (UTF-16 BOM aware). */
1005
1124
  var decodeText = (bytes) => {
1006
1125
  if (bytes.length >= 2) {
1007
1126
  if (bytes[0] === 255 && bytes[1] === 254) return new TextDecoder("utf-16le").decode(bytes.subarray(2));
1008
1127
  if (bytes[0] === 254 && bytes[1] === 255) return new TextDecoder("utf-16be").decode(bytes.subarray(2));
1009
1128
  }
1010
- return decoder.decode(bytes).replace(/\r\n?/g, "\n");
1129
+ return decoder$1.decode(bytes).replace(/\r\n?/g, "\n");
1011
1130
  };
1012
1131
  var textPayload = (source, text, suffix) => ({
1013
- bytes: encoder.encode(text),
1014
- mimeType: MIME.TXT,
1015
- filename: replaceExtension(source.filename, suffix)
1132
+ bytes: encoder$1.encode(text),
1133
+ mimeType: require_formats.MIME.TXT,
1134
+ filename: require_formats.replaceExtension(source.filename, suffix)
1016
1135
  });
1017
1136
  /** `chunk` — split text content into retrieval chunks (data result). */
1018
1137
  var chunkStep = async (ctx) => {
@@ -1033,9 +1152,9 @@ var chunkStep = async (ctx) => {
1033
1152
  * clear reason.
1034
1153
  */
1035
1154
  var extractTextStep = async (ctx) => {
1036
- const family = familyOf(ctx.payload.mimeType);
1155
+ const family = require_formats.familyOf(ctx.payload.mimeType);
1037
1156
  const mime = ctx.payload.mimeType.toLowerCase().split(";")[0].trim();
1038
- if (!(mime.startsWith("text/") || mime === MIME.JSON || mime === MIME.MD || mime === MIME.CSV)) throw new require_exceptions.E_MEDIA_STEP_FAILED(["extract.text", `extraction for ${family} media (${mime}) is not yet implemented in this build`]);
1157
+ if (!(mime.startsWith("text/") || mime === require_formats.MIME.JSON || mime === require_formats.MIME.MD || mime === require_formats.MIME.CSV)) throw new require_exceptions.E_MEDIA_STEP_FAILED(["extract.text", `extraction for ${family} media (${mime}) is not yet implemented in this build`]);
1039
1158
  const text = decodeText(ctx.payload.bytes);
1040
1159
  return {
1041
1160
  kind: "data",
@@ -1049,7 +1168,7 @@ var extractMetadataStep = async (ctx) => {
1049
1168
  filename: ctx.payload.filename,
1050
1169
  mime_type: ctx.payload.mimeType,
1051
1170
  size_bytes: ctx.payload.bytes.byteLength,
1052
- family: familyOf(ctx.payload.mimeType)
1171
+ family: require_formats.familyOf(ctx.payload.mimeType)
1053
1172
  };
1054
1173
  return {
1055
1174
  kind: "data",
@@ -1094,7 +1213,7 @@ var updateTextStep = async (ctx) => {
1094
1213
  kind: "media",
1095
1214
  payload: {
1096
1215
  ...ctx.payload,
1097
- bytes: encoder.encode(updated)
1216
+ bytes: encoder$1.encode(updated)
1098
1217
  }
1099
1218
  };
1100
1219
  };
@@ -1124,9 +1243,13 @@ var diffStep = async (ctx) => {
1124
1243
  asText: patch
1125
1244
  };
1126
1245
  };
1127
- /** `apply_patch` — apply a unified diff to the media text. */
1246
+ /**
1247
+ * `apply_patch` — apply a unified diff, or a structured `*** Begin Patch` envelope
1248
+ * (multi-file Add/Delete/Update/Move — the GitHub Copilot apply_patch dialect).
1249
+ */
1128
1250
  var applyPatchStep = async (ctx) => {
1129
1251
  const patch = argOf(ctx.step, "patch");
1252
+ if (isStructuredPatch(patch)) return applyStructuredPatch(ctx, patch);
1130
1253
  const text = decodeText(ctx.payload.bytes);
1131
1254
  const result = (await import("diff")).applyPatch(text, patch);
1132
1255
  if (result === false) throw new require_exceptions.E_MEDIA_STEP_FAILED(["apply_patch", "the patch does not apply to this media content (context mismatch)"]);
@@ -1134,30 +1257,86 @@ var applyPatchStep = async (ctx) => {
1134
1257
  kind: "media",
1135
1258
  payload: {
1136
1259
  ...ctx.payload,
1137
- bytes: encoder.encode(result)
1260
+ bytes: encoder$1.encode(result)
1138
1261
  }
1139
1262
  };
1140
1263
  };
1264
+ /** The structured-envelope path: primary media + `with=@refs` form a virtual workspace. */
1265
+ var applyStructuredPatch = async (ctx, patch) => {
1266
+ const verb = "apply_patch";
1267
+ const fail = (message) => {
1268
+ throw new require_exceptions.E_MEDIA_STEP_FAILED([verb, message]);
1269
+ };
1270
+ const files = /* @__PURE__ */ new Map();
1271
+ const addToWorkspace = (payload) => {
1272
+ let path;
1273
+ try {
1274
+ path = normalizeWorkspacePath(payload.filename);
1275
+ } catch (err) {
1276
+ fail(require_tool_registry.isError(err) ? err.message : String(err));
1277
+ throw err;
1278
+ }
1279
+ if (files.has(path)) fail(`duplicate workspace path in inputs: "${path}"`);
1280
+ files.set(path, {
1281
+ text: decodeText(payload.bytes),
1282
+ mimeType: payload.mimeType
1283
+ });
1284
+ };
1285
+ addToWorkspace(ctx.payload);
1286
+ const raw = ctx.step.args.with;
1287
+ if (raw !== void 0) {
1288
+ const refs = Array.isArray(raw) ? raw : [raw];
1289
+ for (const ref of refs) {
1290
+ if (ref.kind !== "id") fail("builder refs are not yet supported here");
1291
+ addToWorkspace(await ctx.resolveRef(ref.id));
1292
+ }
1293
+ }
1294
+ let parsed;
1295
+ let applied;
1296
+ try {
1297
+ parsed = parseStructuredPatch(patch);
1298
+ applied = applyOperations(files, parsed);
1299
+ } catch (err) {
1300
+ fail(require_tool_registry.isError(err) ? err.message : String(err));
1301
+ throw err;
1302
+ }
1303
+ const payloads = [...applied.files.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([path, file]) => ({
1304
+ bytes: encoder$1.encode(file.text),
1305
+ mimeType: file.mimeType,
1306
+ filename: path
1307
+ }));
1308
+ if (payloads.length === 0) fail("the patch deleted every file in the workspace");
1309
+ if (payloads.length === 1) return {
1310
+ kind: "media",
1311
+ payload: payloads[0]
1312
+ };
1313
+ return {
1314
+ kind: "media-list",
1315
+ payloads
1316
+ };
1317
+ };
1141
1318
  //#endregion
1142
1319
  //#region src/batteries/media/steps/doc.ts
1143
1320
  /**
1144
- * Doc-domain step extensions: format-preserving text mutation for DOCX/PPTX, asset
1145
- * extraction from OOXML/ODF archives and PDFs, and format conversion via the convert engine.
1321
+ * Doc-domain step extensions: format-preserving text mutation for DOCX/PPTX/ODF, PDF visual
1322
+ * redaction, asset extraction from OOXML/ODF archives and PDFs, and format conversion via the
1323
+ * convert engine.
1146
1324
  *
1147
1325
  * @remarks
1148
1326
  * Internal sibling of the `@nhtio/adk/batteries/media` entry. Ported from the source server's
1149
1327
  * doc adapters; the format-dispatch contract follows the server's green e2e suite: DOCX and
1150
1328
  * PPTX redact/sanitize/normalize/update_text mutate text in place inside the container
1151
- * (preserving the source format); text-like inputs mutate as text; PDF redact falls back to
1152
- * text extraction (the server's PDF "visual annotation" path is content-stream-preserving and
1153
- * out of scope here the failure message says exactly that).
1329
+ * (preserving the source format); ODT/ODS/ODP redact/update_text mutate `content.xml` the
1330
+ * same way; text-like inputs mutate as text. PDF redact is VISUAL (pdf-lib draw-over +
1331
+ * metadata strip content streams keep the text); the result carries that warning verbatim
1332
+ * because it is a trust boundary, not a footnote.
1154
1333
  */
1155
1334
  var zipPromise$2;
1156
1335
  var jszip$2 = () => {
1157
1336
  zipPromise$2 ??= import("jszip").then((m) => "default" in m ? m.default : m);
1158
1337
  return zipPromise$2;
1159
1338
  };
1160
- var fail$4 = (verb, message) => {
1339
+ var fail$5 = (verb, message) => {
1161
1340
  throw new require_exceptions.E_MEDIA_STEP_FAILED([verb, message]);
1162
1341
  };
1163
1342
  var escapeXml$1 = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
@@ -1203,17 +1382,17 @@ var replaceInOoxml = (xml, paragraphTag, textTag, pattern, replacement) => {
1203
1382
  };
1204
1383
  /** Map text-node mutation over every relevant part of a DOCX or PPTX archive. */
1205
1384
  var mutateOoxmlText = async (ctx, verb, mutateXml) => {
1206
- const kind = ctx.payload.mimeType.toLowerCase().split(";")[0].trim() === MIME.DOCX ? "docx" : "pptx";
1385
+ const kind = ctx.payload.mimeType.toLowerCase().split(";")[0].trim() === require_formats.MIME.DOCX ? "docx" : "pptx";
1207
1386
  const JSZip = await jszip$2();
1208
1387
  let zip;
1209
1388
  try {
1210
1389
  zip = await JSZip.loadAsync(ctx.payload.bytes);
1211
1390
  } catch (err) {
1212
- fail$4(verb, `could not open the document container: ${require_tool_registry.isError(err) ? err.message : String(err)}`);
1391
+ fail$5(verb, `could not open the document container: ${require_tool_registry.isError(err) ? err.message : String(err)}`);
1213
1392
  throw err;
1214
1393
  }
1215
1394
  const parts = kind === "docx" ? Object.keys(zip.files).filter((n) => /^word\/(document|header\d*|footer\d*)\.xml$/.test(n)) : Object.keys(zip.files).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n));
1216
- if (parts.length === 0) fail$4(verb, "the container has no text parts to mutate (corrupt file?)");
1395
+ if (parts.length === 0) fail$5(verb, "the container has no text parts to mutate (corrupt file?)");
1217
1396
  for (const part of parts) {
1218
1397
  const xml = await zip.file(part).async("text");
1219
1398
  const next = mutateXml(xml, kind);
@@ -1239,21 +1418,170 @@ var toGlobalRegex = (value) => {
1239
1418
  const ref = value;
1240
1419
  return new RegExp(ref.source, ref.flags.includes("g") ? ref.flags : `${ref.flags}g`);
1241
1420
  };
1242
- /** `redact` format dispatch: text media → text path; DOCX/PPTX → in-place container edit. */
1421
+ var ODF_MIMES = new Set([
1422
+ require_formats.MIME.ODT,
1423
+ require_formats.MIME.ODS,
1424
+ require_formats.MIME.ODP
1425
+ ]);
1426
+ /**
1427
+ * Replace matches inside the text nodes of ODF `content.xml`, aggregating per paragraph so
1428
+ * patterns spanning `<text:span>` formatting splits still match.
1429
+ */
1430
+ var replaceTextInContentXml = (xml, pattern, replacement) => {
1431
+ let totalMatches = 0;
1432
+ return {
1433
+ xml: xml.replace(/<text:(?:p|h)\b[^>]*>[\s\S]*?<\/text:(?:p|h)>/g, (paragraphXml) => {
1434
+ const textNodes = [];
1435
+ const textRegex = />([^<]+)</g;
1436
+ let match;
1437
+ while (match = textRegex.exec(paragraphXml)) {
1438
+ const content = unescapeXml(match[1]);
1439
+ if (content.trim() === "") continue;
1440
+ textNodes.push({
1441
+ content,
1442
+ start: match.index,
1443
+ end: match.index + match[0].length
1444
+ });
1445
+ }
1446
+ if (textNodes.length === 0) return paragraphXml;
1447
+ const aggregated = textNodes.map((n) => n.content).join("");
1448
+ const p = new RegExp(pattern.source, pattern.flags);
1449
+ const replaced = aggregated.replace(p, replacement);
1450
+ if (replaced === aggregated) return paragraphXml;
1451
+ const counter = new RegExp(pattern.source, pattern.flags);
1452
+ while (counter.exec(aggregated)) {
1453
+ totalMatches += 1;
1454
+ if (!counter.global) break;
1455
+ }
1456
+ let out = paragraphXml;
1457
+ for (let i = textNodes.length - 1; i >= 0; i--) {
1458
+ const node = textNodes[i];
1459
+ const newContent = i === 0 ? escapeXml$1(replaced) : "";
1460
+ out = out.slice(0, node.start) + `>${newContent}<` + out.slice(node.end);
1461
+ }
1462
+ return out;
1463
+ }),
1464
+ matchCount: totalMatches
1465
+ };
1466
+ };
1467
+ /** Map a `content.xml` mutation over an ODF container (ODT/ODS/ODP). */
1468
+ var mutateOdfText = async (ctx, verb, mutateXml) => {
1469
+ const JSZip = await jszip$2();
1470
+ let zip;
1471
+ try {
1472
+ zip = await JSZip.loadAsync(ctx.payload.bytes);
1473
+ } catch (err) {
1474
+ fail$5(verb, `could not open the document container: ${require_tool_registry.isError(err) ? err.message : String(err)}`);
1475
+ throw err;
1476
+ }
1477
+ const part = zip.file("content.xml");
1478
+ if (!part) fail$5(verb, "the ODF container has no content.xml (corrupt file?)");
1479
+ const xml = await part.async("text");
1480
+ const next = mutateXml(xml);
1481
+ if (next !== xml) zip.file("content.xml", next);
1482
+ const bytes = await zip.generateAsync({
1483
+ type: "uint8array",
1484
+ compression: "DEFLATE"
1485
+ });
1486
+ return {
1487
+ kind: "media",
1488
+ payload: {
1489
+ ...ctx.payload,
1490
+ bytes
1491
+ }
1492
+ };
1493
+ };
1494
+ /** Visual page-level redaction: draw-over banner on matching pages + metadata strip. */
1495
+ var redactPdf = async (ctx, verb, patterns) => {
1496
+ const { PDFDocument, PDFName, PDFString, rgb, StandardFonts } = await import("pdf-lib");
1497
+ let doc;
1498
+ try {
1499
+ doc = await PDFDocument.load(ctx.payload.bytes, { ignoreEncryption: true });
1500
+ } catch (err) {
1501
+ fail$5(verb, `could not parse the PDF: ${require_tool_registry.isError(err) ? err.message : String(err)}`);
1502
+ throw err;
1503
+ }
1504
+ let pageTexts = [];
1505
+ try {
1506
+ const { PDFParse } = await import("pdf-parse");
1507
+ pageTexts = (await new PDFParse({ data: ctx.payload.bytes }).getText()).pages.map((p) => p.text ?? "");
1508
+ } catch {
1509
+ pageTexts = [];
1510
+ }
1511
+ const pages = doc.getPages();
1512
+ const font = await doc.embedFont(StandardFonts.Helvetica);
1513
+ let pagesRedacted = 0;
1514
+ for (const [i, page] of pages.entries()) {
1515
+ const pageText = pageTexts[i] ?? "";
1516
+ if (!patterns.some((pattern) => new RegExp(pattern.source, pattern.flags).test(pageText))) continue;
1517
+ const { width } = page.getSize();
1518
+ page.drawRectangle({
1519
+ x: 0,
1520
+ y: 0,
1521
+ width,
1522
+ height: 14,
1523
+ color: rgb(1, 1, .9)
1524
+ });
1525
+ page.drawText(`[Page ${i + 1}: content redacted]`, {
1526
+ x: 4,
1527
+ y: 2,
1528
+ size: 8,
1529
+ font,
1530
+ color: rgb(.5, 0, 0)
1531
+ });
1532
+ pagesRedacted += 1;
1533
+ }
1534
+ for (const field of [
1535
+ "Title",
1536
+ "Author",
1537
+ "Subject",
1538
+ "Keywords",
1539
+ "Creator",
1540
+ "Producer"
1541
+ ]) try {
1542
+ const infoDict = doc.context.lookup(doc.context.trailerInfo.Info);
1543
+ if (infoDict && typeof infoDict.get === "function" && typeof infoDict.set === "function") {
1544
+ if (infoDict.get(PDFName.of(field))) infoDict.set(PDFName.of(field), PDFString.of(""));
1545
+ }
1546
+ } catch {}
1547
+ try {
1548
+ const catalog = doc.context.lookup(doc.context.trailerInfo.Root);
1549
+ if (catalog && typeof catalog.get === "function" && typeof catalog.delete === "function") {
1550
+ if (catalog.get(PDFName.of("Metadata"))) catalog.delete(PDFName.of("Metadata"));
1551
+ }
1552
+ } catch {}
1553
+ if (pagesRedacted === 0) fail$5(verb, "no page text matched the redaction pattern(s) — nothing was redacted");
1554
+ const bytes = new Uint8Array(await doc.save());
1555
+ return {
1556
+ kind: "media",
1557
+ payload: {
1558
+ ...ctx.payload,
1559
+ bytes
1560
+ }
1561
+ };
1562
+ };
1563
+ /**
1564
+ * `redact` — format dispatch: text media → text path; DOCX/PPTX/ODF → in-place container
1565
+ * edit; PDF → visual redaction (draw-over + metadata strip — see {@link PDF_REDACTION_WARNING}).
1566
+ */
1243
1567
  var docRedactStep = async (ctx) => {
1244
1568
  const mime = ctx.payload.mimeType.toLowerCase().split(";")[0].trim();
1245
1569
  if (mime.startsWith("text/")) return redactStep(ctx);
1246
- if (mime === MIME.DOCX || mime === MIME.PPTX) {
1247
- const raw = ctx.step.args.match;
1248
- const patterns = Array.isArray(raw) ? raw : [raw];
1249
- const replacement = argOf(ctx.step, "replace") ?? "█";
1250
- return mutateOoxmlText(ctx, "redact", (xml, kind) => {
1251
- let out = xml;
1252
- for (const p of patterns) out = replaceInOoxml(out, kind === "docx" ? "w:p" : "a:p", kind === "docx" ? "w:t" : "a:t", toGlobalRegex(p), replacement).xml;
1253
- return out;
1254
- });
1255
- }
1256
- fail$4("redact", `redaction for ${mime} is not supported in this build (text, DOCX, and PPTX are). For PDF, extract the text first: extract text | redact …`);
1570
+ const raw = ctx.step.args.match;
1571
+ const patterns = Array.isArray(raw) ? raw : [raw];
1572
+ const replacement = argOf(ctx.step, "replace") ?? "█";
1573
+ if (mime === require_formats.MIME.DOCX || mime === require_formats.MIME.PPTX) return mutateOoxmlText(ctx, "redact", (xml, kind) => {
1574
+ let out = xml;
1575
+ for (const p of patterns) out = replaceInOoxml(out, kind === "docx" ? "w:p" : "a:p", kind === "docx" ? "w:t" : "a:t", toGlobalRegex(p), replacement).xml;
1576
+ return out;
1577
+ });
1578
+ if (ODF_MIMES.has(mime)) return mutateOdfText(ctx, "redact", (xml) => {
1579
+ let out = xml;
1580
+ for (const p of patterns) out = replaceTextInContentXml(out, toGlobalRegex(p), replacement).xml;
1581
+ return out;
1582
+ });
1583
+ if (mime === require_formats.MIME.PDF) return redactPdf(ctx, "redact", patterns.map(toGlobalRegex));
1584
+ fail$5("redact", `redaction for ${mime} is not supported in this build (text, DOCX, PPTX, ODT/ODS/ODP, and PDF are).`);
1257
1585
  throw new Error("unreachable");
1258
1586
  };
1259
1587
  var BLOCKED_CONTROL = (code) => code >= 0 && code <= 8 || code === 11 || code === 12 || code >= 14 && code <= 31 || code === 127;
@@ -1283,8 +1611,8 @@ var docSanitizeStep = async (ctx) => {
1283
1611
  }
1284
1612
  };
1285
1613
  }
1286
- if (mime === MIME.DOCX || mime === MIME.PPTX) return mutateOoxmlText(ctx, "sanitize", (xml, kind) => sanitizeTextNodes(xml, kind === "docx" ? "w:t" : "a:t"));
1287
- fail$4("sanitize", `sanitize for ${mime} is not supported in this build (text, DOCX, PPTX are)`);
1614
+ if (mime === require_formats.MIME.DOCX || mime === require_formats.MIME.PPTX) return mutateOoxmlText(ctx, "sanitize", (xml, kind) => sanitizeTextNodes(xml, kind === "docx" ? "w:t" : "a:t"));
1615
+ fail$5("sanitize", `sanitize for ${mime} is not supported in this build (text, DOCX, PPTX are)`);
1288
1616
  throw new Error("unreachable");
1289
1617
  };
1290
1618
  var normalizeText = (text) => text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[\t ]+$/gm, "");
@@ -1301,7 +1629,7 @@ var docNormalizeStep = async (ctx) => {
1301
1629
  }
1302
1630
  };
1303
1631
  }
1304
- if (mime === MIME.DOCX || mime === MIME.PPTX) return mutateOoxmlText(ctx, "normalize", (xml, kind) => {
1632
+ if (mime === require_formats.MIME.DOCX || mime === require_formats.MIME.PPTX) return mutateOoxmlText(ctx, "normalize", (xml, kind) => {
1305
1633
  const tag = kind === "docx" ? "w:t" : "a:t";
1306
1634
  return xml.replace(new RegExp(`<${tag}(?:\\s[^>]*)?>([^<]*)</${tag}>`, "g"), (full, content) => {
1307
1635
  const normalized = normalizeText(unescapeXml(content));
@@ -1309,28 +1637,38 @@ var docNormalizeStep = async (ctx) => {
1309
1637
  return full.replace(content, escapeXml$1(normalized));
1310
1638
  });
1311
1639
  });
1312
- fail$4("normalize", `normalize for ${mime} is not supported in this build (text, DOCX, PPTX are)`);
1640
+ fail$5("normalize", `normalize for ${mime} is not supported in this build (text, DOCX, PPTX are)`);
1313
1641
  throw new Error("unreachable");
1314
1642
  };
1315
- /** `update_text` — anchor replacement; DOCX/PPTX in-place, TARGET_NOT_FOUND when absent. */
1643
+ /** `update_text` — anchor replacement; DOCX/PPTX/ODF in-place, TARGET_NOT_FOUND when absent. */
1316
1644
  var docUpdateTextStep = async (ctx) => {
1317
1645
  const mime = ctx.payload.mimeType.toLowerCase().split(";")[0].trim();
1318
1646
  if (mime.startsWith("text/")) return updateTextStep(ctx);
1319
- if (mime === MIME.DOCX || mime === MIME.PPTX) {
1320
- const anchor = argOf(ctx.step, "anchor");
1321
- const replace = argOf(ctx.step, "replace") ?? "";
1647
+ const anchor = argOf(ctx.step, "anchor");
1648
+ const replace = argOf(ctx.step, "replace") ?? "";
1649
+ const escapedAnchor = anchor.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1650
+ if (mime === require_formats.MIME.DOCX || mime === require_formats.MIME.PPTX) {
1322
1651
  let found = 0;
1323
1652
  const result = await mutateOoxmlText(ctx, "update_text", (xml, kind) => {
1324
1653
  if (found > 0) return xml;
1325
- const escaped = anchor.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1326
- const { xml: next, matchCount } = replaceInOoxml(xml, kind === "docx" ? "w:p" : "a:p", kind === "docx" ? "w:t" : "a:t", new RegExp(escaped), replace);
1654
+ const { xml: next, matchCount } = replaceInOoxml(xml, kind === "docx" ? "w:p" : "a:p", kind === "docx" ? "w:t" : "a:t", new RegExp(escapedAnchor), replace);
1327
1655
  found += matchCount;
1328
1656
  return next;
1329
1657
  });
1330
- if (found === 0) fail$4("update_text", `anchor text not found: "${anchor.slice(0, 80)}"`);
1658
+ if (found === 0) fail$5("update_text", `anchor text not found: "${anchor.slice(0, 80)}"`);
1331
1659
  return result;
1332
1660
  }
1333
- fail$4("update_text", `text update for ${mime} is not supported (text, DOCX, PPTX are; PDF text is not reliably editable)`);
1661
+ if (ODF_MIMES.has(mime)) {
1662
+ let found = 0;
1663
+ const result = await mutateOdfText(ctx, "update_text", (xml) => {
1664
+ const { xml: next, matchCount } = replaceTextInContentXml(xml, new RegExp(escapedAnchor), replace);
1665
+ found += matchCount;
1666
+ return next;
1667
+ });
1668
+ if (found === 0) fail$5("update_text", `anchor text not found: "${anchor.slice(0, 80)}"`);
1669
+ return result;
1670
+ }
1671
+ fail$5("update_text", `text update for ${mime} is not supported (text, DOCX, PPTX, ODT/ODS/ODP are; PDF text is not reliably editable)`);
1334
1672
  throw new Error("unreachable");
1335
1673
  };
1336
1674
  var ASSET_PATTERNS = {
@@ -1365,9 +1703,9 @@ var extractAssetsStep = async (ctx) => {
1365
1703
  const requested = ctx.step.args.types ?? ["all"];
1366
1704
  const wantAll = requested.includes("all");
1367
1705
  const want = (kind) => wantAll || requested.includes(kind);
1368
- if (mime === MIME.PDF) {
1369
- if (!want("image")) fail$4(verb, "PDF asset extraction currently supports images (types=image or all)");
1370
- if (!ctx.engines.hasConvert(mime, "images")) fail$4(verb, "PDF asset extraction requires an engine that converts application/pdf to \"images\", and none is configured. Do not retry this verb on PDFs in this deployment.");
1706
+ if (mime === require_formats.MIME.PDF) {
1707
+ if (!want("image")) fail$5(verb, "PDF asset extraction currently supports images (types=image or all)");
1708
+ if (!ctx.engines.hasConvert(mime, "images")) fail$5(verb, "PDF asset extraction requires an engine that converts application/pdf to \"images\", and none is configured. Do not retry this verb on PDFs in this deployment.");
1371
1709
  const format = argOf(ctx.step, "format");
1372
1710
  const result = await ctx.engines.convert({
1373
1711
  bytes: ctx.payload.bytes,
@@ -1377,7 +1715,7 @@ var extractAssetsStep = async (ctx) => {
1377
1715
  options: format ? { format } : void 0,
1378
1716
  signal: ctx.signal
1379
1717
  });
1380
- if (result.outputs.length === 0) fail$4(verb, "no embedded images were found in the PDF");
1718
+ if (result.outputs.length === 0) fail$5(verb, "no embedded images were found in the PDF");
1381
1719
  const wantedMime = format ? EXT_MIME[format] ?? `image/${format}` : void 0;
1382
1720
  const payloads = [];
1383
1721
  for (const [i, img] of result.outputs.entries()) {
@@ -1405,13 +1743,13 @@ var extractAssetsStep = async (ctx) => {
1405
1743
  };
1406
1744
  }
1407
1745
  if (![
1408
- MIME.DOCX,
1409
- MIME.XLSX,
1410
- MIME.PPTX,
1411
- MIME.ODT,
1412
- MIME.ODS,
1413
- MIME.ODP
1414
- ].includes(mime)) fail$4(verb, `asset extraction for ${mime} is not supported (OOXML/ODF archives and PDF are)`);
1746
+ require_formats.MIME.DOCX,
1747
+ require_formats.MIME.XLSX,
1748
+ require_formats.MIME.PPTX,
1749
+ require_formats.MIME.ODT,
1750
+ require_formats.MIME.ODS,
1751
+ require_formats.MIME.ODP
1752
+ ].includes(mime)) fail$5(verb, `asset extraction for ${mime} is not supported (OOXML/ODF archives and PDF are)`);
1415
1753
  const zip = await (await jszip$2()).loadAsync(ctx.payload.bytes);
1416
1754
  const payloads = [];
1417
1755
  for (const [name, entry] of Object.entries(zip.files)) {
@@ -1427,7 +1765,7 @@ var extractAssetsStep = async (ctx) => {
1427
1765
  filename: basename
1428
1766
  });
1429
1767
  }
1430
- if (payloads.length === 0) fail$4(verb, "no embedded assets matched the requested types");
1768
+ if (payloads.length === 0) fail$5(verb, "no embedded assets matched the requested types");
1431
1769
  return {
1432
1770
  kind: "media-list",
1433
1771
  payloads
@@ -1438,7 +1776,7 @@ var convertStep = async (ctx) => {
1438
1776
  const verb = "convert";
1439
1777
  const to = argOf(ctx.step, "to");
1440
1778
  const supported = ctx.engines.convertTargets(ctx.payload.mimeType);
1441
- if (!supported.includes(to)) fail$4(verb, `no configured engine (or computed path) can produce "${to}" from ${ctx.payload.mimeType}; reachable targets: ${supported.join(", ") || "(none)"}`);
1779
+ if (!supported.includes(to)) fail$5(verb, `no configured engine (or computed path) can produce "${to}" from ${ctx.payload.mimeType}; reachable targets: ${supported.join(", ") || "(none)"}`);
1442
1780
  const output = (await ctx.engines.convert({
1443
1781
  bytes: ctx.payload.bytes,
1444
1782
  mimeType: ctx.payload.mimeType,
@@ -1446,7 +1784,7 @@ var convertStep = async (ctx) => {
1446
1784
  to,
1447
1785
  signal: ctx.signal
1448
1786
  })).outputs[0];
1449
- if (!output) fail$4(verb, "the conversion produced no output");
1787
+ if (!output) fail$5(verb, "the conversion produced no output");
1450
1788
  const dot = ctx.payload.filename.lastIndexOf(".");
1451
1789
  const base = dot > 0 ? ctx.payload.filename.slice(0, dot) : ctx.payload.filename;
1452
1790
  return {
@@ -1468,32 +1806,222 @@ var DOC_STEPS = [
1468
1806
  ["convert", convertStep]
1469
1807
  ];
1470
1808
  //#endregion
1471
- //#region src/batteries/media/steps/sheet.ts
1809
+ //#region src/batteries/media/steps/data.ts
1472
1810
  /**
1473
- * `sheet.*` step implementations: ExcelJS workbook mutations on in-memory bytes.
1811
+ * `append` and `data.*` step implementations: deterministic text/data mutations on in-memory
1812
+ * bytes — append a line, set/merge/delete at a JSON or YAML path.
1474
1813
  *
1475
1814
  * @remarks
1476
- * Internal sibling of the `@nhtio/adk/batteries/media` entry. Ported from the source server's
1477
- * sheet adapters (its green e2e suite informs the spec coverage). All ten mutations operate on
1478
- * xlsx natively via ExcelJS (optional peer, lazily imported); ODS and legacy-xls inputs are
1479
- * normalized to xlsx first through the configured `sheetNormalize` engine, degrading to a
1480
- * readable failure when none is configured. Output is always xlsx.
1815
+ * Internal sibling of the `@nhtio/adk/batteries/media` entry. Pure and cross-env: text ops
1816
+ * are string concatenation; structured ops parse (JSON natively, YAML via the `js-yaml`
1817
+ * dependency), mutate the value tree, and re-serialize **preserving the source format**
1818
+ * `data.set` on a YAML file yields YAML, on a JSON file yields JSON. These are the verbs
1819
+ * that make the lossy text family first-class media: `empty:json | data set path=… value=…`
1820
+ * is a create-then-populate chain with no engine requirement at all.
1481
1821
  */
1482
- var excelPromise;
1483
- var excel = () => {
1484
- excelPromise ??= import("exceljs").then((m) => "default" in m ? m.default : m);
1485
- return excelPromise;
1822
+ var fail$4 = (verb, message) => {
1823
+ throw new require_exceptions.E_MEDIA_STEP_FAILED([verb, message]);
1824
+ };
1825
+ var encoder = new TextEncoder();
1826
+ var decoder = new TextDecoder();
1827
+ /** MIME types the text-shaped verbs operate on. */
1828
+ var TEXTUAL = (mime) => mime.startsWith("text/") || mime === require_formats.MIME.JSON || mime === require_formats.MIME.YAML;
1829
+ /** `append` — append text to text-family media. */
1830
+ var appendStep = async (ctx) => {
1831
+ const verb = "append";
1832
+ const mime = ctx.payload.mimeType.toLowerCase().split(";")[0].trim();
1833
+ if (!TEXTUAL(mime)) fail$4(verb, `append expects text-family media (txt/md/csv/yaml/json-lines); the media is ${mime}`);
1834
+ const text = argOf(ctx.step, "text");
1835
+ const newline = argOf(ctx.step, "newline") ?? true;
1836
+ const current = decoder.decode(ctx.payload.bytes);
1837
+ const next = current + (newline && current.length > 0 && !current.endsWith("\n") ? "\n" : "") + text + (newline ? "\n" : "");
1838
+ return {
1839
+ kind: "media",
1840
+ payload: {
1841
+ ...ctx.payload,
1842
+ bytes: encoder.encode(next)
1843
+ }
1844
+ };
1845
+ };
1846
+ var parseDoc = async (verb, payload) => {
1847
+ const mime = payload.mimeType.toLowerCase().split(";")[0].trim();
1848
+ const text = decoder.decode(payload.bytes);
1849
+ if (mime === require_formats.MIME.JSON) try {
1850
+ return {
1851
+ value: text.trim() === "" ? null : JSON.parse(text),
1852
+ format: "json"
1853
+ };
1854
+ } catch (err) {
1855
+ fail$4(verb, `the media is not valid JSON: ${require_tool_registry.isError(err) ? err.message : String(err)}`);
1856
+ }
1857
+ if (mime === require_formats.MIME.YAML) try {
1858
+ const { load } = await import("js-yaml");
1859
+ return {
1860
+ value: load(text) ?? null,
1861
+ format: "yaml"
1862
+ };
1863
+ } catch (err) {
1864
+ fail$4(verb, `the media is not valid YAML: ${require_tool_registry.isError(err) ? err.message : String(err)}`);
1865
+ }
1866
+ fail$4(verb, `data operations expect JSON or YAML media; the media is ${mime}`);
1867
+ throw new Error("unreachable");
1868
+ };
1869
+ var serializeDoc = async (doc, payload) => {
1870
+ if (doc.format === "yaml") {
1871
+ const { dump } = await import("js-yaml");
1872
+ return {
1873
+ ...payload,
1874
+ bytes: encoder.encode(dump(doc.value))
1875
+ };
1876
+ }
1877
+ return {
1878
+ ...payload,
1879
+ bytes: encoder.encode(JSON.stringify(doc.value, null, 2))
1880
+ };
1881
+ };
1882
+ /** Parse a dot/bracket path (`a.b[2].c`) into segments. */
1883
+ var parsePath = (verb, raw) => {
1884
+ if (typeof raw !== "string" || raw.trim() === "") fail$4(verb, "path must be a non-empty string");
1885
+ const segments = [];
1886
+ const re = /([^.[\]]+)|\[(\d+)\]/g;
1887
+ let match;
1888
+ while (match = re.exec(raw)) if (match[2] !== void 0) segments.push(Number(match[2]));
1889
+ else segments.push(match[1]);
1890
+ if (segments.length === 0) fail$4(verb, `could not parse path "${raw}"`);
1891
+ return segments;
1892
+ };
1893
+ /** Walk to the parent of the path target, creating containers when `create` is set. */
1894
+ var walkToParent = (verb, root, segments, create) => {
1895
+ let node = root;
1896
+ for (let i = 0; i < segments.length - 1; i++) {
1897
+ const seg = segments[i];
1898
+ const isIndex = typeof seg === "number";
1899
+ if (node === null || typeof node !== "object") fail$4(verb, `path segment "${String(seg)}" walks through a non-container value`);
1900
+ const container = node;
1901
+ let next = isIndex ? container[seg] : container[seg];
1902
+ if (next === void 0 || next === null) {
1903
+ if (!create) fail$4(verb, `path segment "${String(seg)}" does not exist`);
1904
+ next = typeof segments[i + 1] === "number" ? [] : {};
1905
+ if (isIndex) container[seg] = next;
1906
+ else container[seg] = next;
1907
+ }
1908
+ node = next;
1909
+ }
1910
+ if (node === null || typeof node !== "object") fail$4(verb, "the path target parent is not an object or array");
1911
+ return {
1912
+ parent: node,
1913
+ key: segments[segments.length - 1]
1914
+ };
1915
+ };
1916
+ /** `data.set` — set a value at a JSON/YAML path, creating intermediate containers. */
1917
+ var dataSetStep = async (ctx) => {
1918
+ const verb = "data set";
1919
+ const path = argOf(ctx.step, "path");
1920
+ const rawValue = argOf(ctx.step, "value");
1921
+ if (rawValue === void 0) fail$4(verb, "value is required (JSON-encoded scalar or structure)");
1922
+ let value;
1923
+ try {
1924
+ value = JSON.parse(String(rawValue));
1925
+ } catch {
1926
+ value = String(rawValue);
1927
+ }
1928
+ const doc = await parseDoc(verb, ctx.payload);
1929
+ const segments = parsePath(verb, path);
1930
+ if (doc.value === null || typeof doc.value !== "object") doc.value = typeof segments[0] === "number" ? [] : {};
1931
+ const { parent, key } = walkToParent(verb, doc.value, segments, true);
1932
+ if (Array.isArray(parent)) parent[Number(key)] = value;
1933
+ else parent[String(key)] = value;
1934
+ return {
1935
+ kind: "media",
1936
+ payload: await serializeDoc(doc, ctx.payload)
1937
+ };
1486
1938
  };
1939
+ /** Deep merge `fragment` into `target` (objects merge recursively; everything else replaces). */
1940
+ var deepMerge = (target, fragment) => {
1941
+ if (target === null || fragment === null || typeof target !== "object" || typeof fragment !== "object" || Array.isArray(target) !== Array.isArray(fragment) || Array.isArray(fragment)) return fragment;
1942
+ const out = { ...target };
1943
+ for (const [k, v] of Object.entries(fragment)) out[k] = k in out ? deepMerge(out[k], v) : v;
1944
+ return out;
1945
+ };
1946
+ /** `data.merge` — merge a JSON fragment into the document (deep by default). */
1947
+ var dataMergeStep = async (ctx) => {
1948
+ const verb = "data merge";
1949
+ const rawFragment = argOf(ctx.step, "fragment");
1950
+ const strategy = argOf(ctx.step, "strategy") ?? "deep";
1951
+ let fragment;
1952
+ try {
1953
+ fragment = JSON.parse(String(rawFragment));
1954
+ } catch (err) {
1955
+ fail$4(verb, `fragment must be valid JSON: ${require_tool_registry.isError(err) ? err.message : String(err)}`);
1956
+ }
1957
+ if (fragment === null || typeof fragment !== "object" || Array.isArray(fragment)) fail$4(verb, "fragment must be a JSON object");
1958
+ const doc = await parseDoc(verb, ctx.payload);
1959
+ if (doc.value === null || typeof doc.value !== "object" || Array.isArray(doc.value)) fail$4(verb, "merge requires the document root to be an object");
1960
+ doc.value = strategy === "shallow" ? {
1961
+ ...doc.value,
1962
+ ...fragment
1963
+ } : deepMerge(doc.value, fragment);
1964
+ return {
1965
+ kind: "media",
1966
+ payload: await serializeDoc(doc, ctx.payload)
1967
+ };
1968
+ };
1969
+ /** `data.delete` — remove a key/index at a JSON/YAML path. */
1970
+ var dataDeleteStep = async (ctx) => {
1971
+ const verb = "data delete";
1972
+ const path = argOf(ctx.step, "path");
1973
+ const doc = await parseDoc(verb, ctx.payload);
1974
+ const segments = parsePath(verb, path);
1975
+ if (doc.value === null || typeof doc.value !== "object") fail$4(verb, "the document has no structure to delete from");
1976
+ const { parent, key } = walkToParent(verb, doc.value, segments, false);
1977
+ if (Array.isArray(parent)) {
1978
+ const idx = Number(key);
1979
+ if (idx < 0 || idx >= parent.length) fail$4(verb, `index ${idx} is out of range`);
1980
+ parent.splice(idx, 1);
1981
+ } else {
1982
+ if (!(String(key) in parent)) fail$4(verb, `key "${String(key)}" does not exist at that path`);
1983
+ delete parent[String(key)];
1984
+ }
1985
+ return {
1986
+ kind: "media",
1987
+ payload: await serializeDoc(doc, ctx.payload)
1988
+ };
1989
+ };
1990
+ /** The data step registry fragment, keyed by canonical verb id. */
1991
+ var DATA_STEPS = [
1992
+ ["append", appendStep],
1993
+ ["data.set", dataSetStep],
1994
+ ["data.merge", dataMergeStep],
1995
+ ["data.delete", dataDeleteStep]
1996
+ ];
1997
+ //#endregion
1998
+ //#region src/batteries/media/steps/sheet.ts
1999
+ /**
2000
+ * `sheet.*` step implementations: thin dispatchers into the engine registry's edit
2001
+ * capability.
2002
+ *
2003
+ * @remarks
2004
+ * Internal sibling of the `@nhtio/adk/batteries/media` entry. The actual workbook surgery
2005
+ * lives in edit-capable engines (`engines/exceljs` preserves styling; `engines/sheetjs`
2006
+ * covers the wider read matrix but strips styling — SheetJS CE limitation). Each step here:
2007
+ * normalizes non-xlsx spreadsheet inputs to xlsx when the selected edit coverage requires it
2008
+ * (any spreadsheet-family MIME with a declared `hasConvert(mime, 'xlsx')` edge qualifies —
2009
+ * ODS/XLS via soffice or sheetjs, XLSB/FODS/SYLK/DIF/DBF/NUMBERS via sheetjs), dispatches the
2010
+ * op through `ctx.engines.edit`, and degrades to a model-actionable failure when no edit
2011
+ * engine is configured.
2012
+ */
1487
2013
  var fail$3 = (verb, message) => {
1488
2014
  throw new require_exceptions.E_MEDIA_STEP_FAILED([verb, message]);
1489
2015
  };
1490
- /** Acquire xlsx bytes for the step, converting ODS/xls to xlsx via the engine registry. */
1491
- var acquireXlsx = async (ctx, verb) => {
2016
+ /** Acquire bytes the edit dispatch can work on, normalizing to xlsx when necessary. */
2017
+ var acquireEditable = async (ctx, verb) => {
1492
2018
  const mime = ctx.payload.mimeType.toLowerCase().split(";")[0].trim();
1493
- if (mime === MIME.XLSX) return ctx.payload.bytes;
1494
- const mutationBlock = unsupportedForMutationReason(mime);
1495
- if (mime === MIME.ODS || mime === MIME.XLS) {
1496
- if (!ctx.engines.hasConvert(mime, "xlsx")) fail$3(verb, `this input is ${mime} — an engine that converts it to xlsx is required first, and none is configured. Do not retry this verb on this media in this deployment.`);
2019
+ if (ctx.engines.hasEdit?.(mime)) return {
2020
+ bytes: ctx.payload.bytes,
2021
+ mimeType: mime
2022
+ };
2023
+ if (require_formats.SPREADSHEET_MIMES.has(mime) && mime !== require_formats.MIME.XLSX) {
2024
+ if (!ctx.engines.hasConvert(mime, "xlsx")) fail$3(verb, require_formats.unsupportedForMutationReason(mime) ?? `this input is ${mime} — an engine that converts it to xlsx is required first, and none is configured. Do not retry this verb on this media in this deployment.`);
1497
2025
  const output = (await ctx.engines.convert({
1498
2026
  bytes: ctx.payload.bytes,
1499
2027
  mimeType: mime,
@@ -1502,247 +2030,93 @@ var acquireXlsx = async (ctx, verb) => {
1502
2030
  signal: ctx.signal
1503
2031
  })).outputs[0];
1504
2032
  if (!output) fail$3(verb, "normalizing the workbook to xlsx produced no output");
1505
- return output.bytes;
2033
+ return {
2034
+ bytes: output.bytes,
2035
+ mimeType: require_formats.MIME.XLSX
2036
+ };
1506
2037
  }
1507
- if (mutationBlock) fail$3(verb, mutationBlock);
1508
- fail$3(verb, `sheet operations expect a spreadsheet; the media is ${mime}`);
1509
- throw new Error("unreachable");
1510
- };
1511
- /** The shared open → mutate → write-back lifecycle every sheet verb uses. */
1512
- var withWorkbook = async (ctx, verb, mutate) => {
1513
- const xlsxBytes = await acquireXlsx(ctx, verb);
1514
- const wb = new (await (excel())).Workbook();
1515
- try {
1516
- await wb.xlsx.load(xlsxBytes.buffer.slice(xlsxBytes.byteOffset, xlsxBytes.byteOffset + xlsxBytes.byteLength));
1517
- } catch (err) {
1518
- fail$3(verb, `could not open the spreadsheet: ${require_tool_registry.isError(err) ? err.message : String(err)}`);
2038
+ if (mime !== require_formats.MIME.XLSX) {
2039
+ const mutationBlock = require_formats.unsupportedForMutationReason(mime);
2040
+ if (mutationBlock) fail$3(verb, mutationBlock);
2041
+ fail$3(verb, `sheet operations expect a spreadsheet; the media is ${mime}`);
1519
2042
  }
1520
- await mutate(wb);
1521
2043
  return {
1522
- kind: "media",
1523
- payload: {
1524
- bytes: new Uint8Array(await wb.xlsx.writeBuffer()),
1525
- mimeType: MIME.XLSX,
1526
- filename: replaceExtension(ctx.payload.filename, "xlsx")
1527
- }
2044
+ bytes: ctx.payload.bytes,
2045
+ mimeType: require_formats.MIME.XLSX
1528
2046
  };
1529
2047
  };
1530
- /** Resolve the target worksheet from the frozen `sheet=` rule (bare number=index, string=name). */
1531
- var resolveSheet = (verb, wb, sheetArg) => {
1532
- if (sheetArg === void 0) {
1533
- const first = wb.worksheets[0];
1534
- if (!first) fail$3(verb, "the workbook has no worksheets");
1535
- return first;
2048
+ /** Dispatch one `sheet.*` op through the registry's edit capability. */
2049
+ var dispatchEdit = async (ctx, verb, op) => {
2050
+ const { bytes, mimeType } = await acquireEditable(ctx, verb);
2051
+ if (!ctx.engines.hasEdit?.(mimeType, op)) fail$3(verb, `no engine that edits spreadsheets is configured — add engines/exceljs (or engines/sheetjs) to the pipeline's engines array. Do not retry this verb in this deployment.`);
2052
+ try {
2053
+ const result = await ctx.engines.edit({
2054
+ bytes,
2055
+ mimeType,
2056
+ op,
2057
+ args: ctx.step.args,
2058
+ signal: ctx.signal
2059
+ });
2060
+ return {
2061
+ kind: "media",
2062
+ payload: {
2063
+ bytes: result.bytes,
2064
+ mimeType: result.mimeType,
2065
+ filename: require_formats.replaceExtension(ctx.payload.filename, "xlsx")
2066
+ }
2067
+ };
2068
+ } catch (err) {
2069
+ if (require_tool_registry.isError(err) && err.name.startsWith("E_MEDIA_")) throw err;
2070
+ fail$3(verb, require_tool_registry.isError(err) ? err.message : String(err));
2071
+ throw err;
1536
2072
  }
1537
- if (typeof sheetArg === "number") {
1538
- const byIndex = wb.worksheets[sheetArg - 1];
1539
- if (byIndex) return byIndex;
1540
- if (wb.getWorksheet(String(sheetArg))) fail$3(verb, `no sheet at index ${sheetArg}, but a sheet NAMED "${sheetArg}" exists — quote it: sheet="${sheetArg}"`);
1541
- fail$3(verb, `sheet index ${sheetArg} is out of range (1-based; the workbook has ${wb.worksheets.length})`);
1542
- }
1543
- const ws = wb.getWorksheet(sheetArg);
1544
- if (!ws) fail$3(verb, `no sheet named "${sheetArg}". Sheets: ${wb.worksheets.map((w) => `"${w.name}"`).join(", ")}`);
1545
- return ws;
1546
- };
1547
- var columnLetterToNumber = (col) => {
1548
- let n = 0;
1549
- for (const ch of col.toUpperCase()) n = n * 26 + (ch.charCodeAt(0) - 64);
1550
- return n;
1551
- };
1552
- var asCellValue = (verb, value) => {
1553
- if (value === null) return null;
1554
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
1555
- fail$3(verb, `cell values must be string, number, boolean, or null; got ${typeof value}`);
1556
- throw new Error("unreachable");
2073
+ };
2074
+ /** Pre-dispatch arg validation: keep the historical readable failures for malformed args. */
2075
+ var requireJsonArray = (verb, value, message) => {
2076
+ if (!Array.isArray(value) || value.length === 0) fail$3(verb, message);
1557
2077
  };
1558
2078
  /** `sheet.add_rows` — insert rows before/after an index, or append. */
1559
2079
  var sheetAddRowsStep = async (ctx) => {
1560
2080
  const verb = "sheet add_rows";
1561
2081
  const rows = ctx.step.args.rows;
1562
2082
  if (!Array.isArray(rows) || rows.length === 0 || !rows.every(Array.isArray)) fail$3(verb, `rows must be a non-empty JSON array of arrays, e.g. rows='[["a",1]]'`);
1563
- const before = argOf(ctx.step, "before");
1564
- const after = argOf(ctx.step, "after");
1565
- return withWorkbook(ctx, verb, (wb) => {
1566
- const ws = resolveSheet(verb, wb, argOf(ctx.step, "sheet"));
1567
- const insertAt = before !== void 0 ? before : after !== void 0 ? after + 1 : ws.rowCount + 1;
1568
- const cellRows = rows.map((r) => r.map((v) => asCellValue(verb, v)));
1569
- ws.spliceRows(insertAt, 0, ...cellRows);
1570
- return { added: cellRows.length };
1571
- });
2083
+ return dispatchEdit(ctx, verb, "sheet.add_rows");
1572
2084
  };
1573
2085
  /** `sheet.add_columns` — insert columns with headers or full descriptors. */
1574
2086
  var sheetAddColumnsStep = async (ctx) => {
1575
2087
  const verb = "sheet add_columns";
1576
2088
  const headers = ctx.step.args.headers;
1577
- const columns = ctx.step.args.columns ?? headers?.map((header) => ({ header }));
1578
- if (!Array.isArray(columns) || columns.length === 0) fail$3(verb, `provide headers=a,b or columns='[{"header":"X","values":[1]}]'`);
1579
- const before = argOf(ctx.step, "before");
1580
- const after = argOf(ctx.step, "after");
1581
- return withWorkbook(ctx, verb, (wb) => {
1582
- const ws = resolveSheet(verb, wb, argOf(ctx.step, "sheet"));
1583
- const insertAt = before !== void 0 ? before : after !== void 0 ? after + 1 : ws.columnCount + 1;
1584
- for (const [i, col] of columns.entries()) {
1585
- if (typeof col?.header !== "string") fail$3(verb, "every column needs a string header");
1586
- ws.spliceColumns(insertAt + i, 0, [col.header, ...(col.values ?? []).map((v) => asCellValue(verb, v))]);
1587
- }
1588
- return { added: columns.length };
1589
- });
2089
+ requireJsonArray(verb, ctx.step.args.columns ?? headers, `provide headers=a,b or columns='[{"header":"X","values":[1]}]'`);
2090
+ return dispatchEdit(ctx, verb, "sheet.add_columns");
1590
2091
  };
1591
2092
  /** `sheet.update_cells` — set cells by A1 address or row/col. Leading `=` writes a formula. */
1592
2093
  var sheetUpdateCellsStep = async (ctx) => {
1593
2094
  const verb = "sheet update_cells";
1594
- const updates = ctx.step.args.updates;
1595
- if (!Array.isArray(updates) || updates.length === 0) fail$3(verb, `updates must be a non-empty JSON array, e.g. updates='[{"address":"B2","value":3}]'`);
1596
- return withWorkbook(ctx, verb, (wb) => {
1597
- const ws = resolveSheet(verb, wb, argOf(ctx.step, "sheet"));
1598
- let modified = 0;
1599
- for (const raw of updates) {
1600
- const address = raw.address;
1601
- const row = raw.row;
1602
- const col = raw.col;
1603
- let cell;
1604
- if (address) cell = ws.getCell(address);
1605
- else if (row !== void 0 && col !== void 0) cell = typeof col === "string" ? ws.getCell(row, columnLetterToNumber(col)) : ws.getCell(row, col);
1606
- else {
1607
- fail$3(verb, "each update needs address, or both row and col");
1608
- continue;
1609
- }
1610
- const value = raw.value;
1611
- if (typeof value === "string" && value.startsWith("=")) cell.value = { formula: value.slice(1) };
1612
- else cell.value = asCellValue(verb, value);
1613
- modified += 1;
1614
- }
1615
- return { modified };
1616
- });
2095
+ requireJsonArray(verb, ctx.step.args.updates, `updates must be a non-empty JSON array, e.g. updates='[{"address":"B2","value":3}]'`);
2096
+ return dispatchEdit(ctx, verb, "sheet.update_cells");
1617
2097
  };
1618
2098
  /** `sheet.delete_rows` — delete 1-based rows (bottom-up so indices stay valid). */
1619
- var sheetDeleteRowsStep = async (ctx) => {
1620
- const verb = "sheet delete_rows";
1621
- const rows = ctx.step.args.rows;
1622
- return withWorkbook(ctx, verb, (wb) => {
1623
- const ws = resolveSheet(verb, wb, argOf(ctx.step, "sheet"));
1624
- for (const r of [...rows].sort((a, b) => b - a)) ws.spliceRows(r, 1);
1625
- return { removed: rows.length };
1626
- });
1627
- };
2099
+ var sheetDeleteRowsStep = async (ctx) => dispatchEdit(ctx, "sheet delete_rows", "sheet.delete_rows");
1628
2100
  /** `sheet.delete_columns` — delete 1-based columns. */
1629
- var sheetDeleteColumnsStep = async (ctx) => {
1630
- const verb = "sheet delete_columns";
1631
- const columns = ctx.step.args.columns;
1632
- return withWorkbook(ctx, verb, (wb) => {
1633
- const ws = resolveSheet(verb, wb, argOf(ctx.step, "sheet"));
1634
- for (const c of [...columns].sort((a, b) => b - a)) ws.spliceColumns(c, 1);
1635
- return { removed: columns.length };
1636
- });
1637
- };
2101
+ var sheetDeleteColumnsStep = async (ctx) => dispatchEdit(ctx, "sheet delete_columns", "sheet.delete_columns");
1638
2102
  /** `sheet.rename_sheet` — rename by NAME (the server contract; index targeting is rejected). */
1639
- var sheetRenameSheetStep = async (ctx) => {
1640
- const verb = "sheet rename_sheet";
1641
- const sheet = argOf(ctx.step, "sheet");
1642
- const to = argOf(ctx.step, "to");
1643
- return withWorkbook(ctx, verb, (wb) => {
1644
- const ws = wb.getWorksheet(sheet);
1645
- if (!ws) fail$3(verb, `rename targets a sheet NAME; no sheet named "${sheet}". Sheets: ${wb.worksheets.map((w) => `"${w.name}"`).join(", ")}`);
1646
- ws.name = to;
1647
- return { modified: 1 };
1648
- });
1649
- };
2103
+ var sheetRenameSheetStep = async (ctx) => dispatchEdit(ctx, "sheet rename_sheet", "sheet.rename_sheet");
1650
2104
  /** `sheet.add_sheet` — add a worksheet, optionally at a 1-based position. */
1651
- var sheetAddSheetStep = async (ctx) => {
1652
- const verb = "sheet add_sheet";
1653
- const name = argOf(ctx.step, "name");
1654
- const at = argOf(ctx.step, "at");
1655
- return withWorkbook(ctx, verb, (wb) => {
1656
- if (wb.getWorksheet(name)) fail$3(verb, `a sheet named "${name}" already exists`);
1657
- const ws = wb.addWorksheet(name);
1658
- if (at !== void 0) {
1659
- const all = wb.worksheets;
1660
- const wsIdx = all.indexOf(ws);
1661
- if (wsIdx !== at - 1) all.forEach((w, i) => {
1662
- w.orderNo = w === ws ? at : i + 1 >= at && i < wsIdx ? i + 2 : i + 1;
1663
- });
1664
- }
1665
- return { added: 1 };
1666
- });
1667
- };
2105
+ var sheetAddSheetStep = async (ctx) => dispatchEdit(ctx, "sheet add_sheet", "sheet.add_sheet");
1668
2106
  /** `sheet.remove_sheet` — remove by NAME (the server contract). */
1669
- var sheetRemoveSheetStep = async (ctx) => {
1670
- const verb = "sheet remove_sheet";
1671
- const sheet = argOf(ctx.step, "sheet");
1672
- return withWorkbook(ctx, verb, (wb) => {
1673
- const ws = wb.getWorksheet(sheet);
1674
- if (!ws) fail$3(verb, `remove targets a sheet NAME; no sheet named "${sheet}". Sheets: ${wb.worksheets.map((w) => `"${w.name}"`).join(", ")}`);
1675
- wb.removeWorksheet(ws.id);
1676
- return { removed: 1 };
1677
- });
1678
- };
2107
+ var sheetRemoveSheetStep = async (ctx) => dispatchEdit(ctx, "sheet remove_sheet", "sheet.remove_sheet");
1679
2108
  /** `sheet.reorder_sheets` — every sheet exactly once, by name or 1-based index. */
1680
2109
  var sheetReorderSheetsStep = async (ctx) => {
1681
2110
  const verb = "sheet reorder_sheets";
1682
- const order = ctx.step.args.order;
1683
- if (!Array.isArray(order) || order.length === 0) fail$3(verb, `order must be a JSON array of names/indices, e.g. order='["Summary",2]'`);
1684
- return withWorkbook(ctx, verb, (wb) => {
1685
- const refs = order;
1686
- if (refs.length !== wb.worksheets.length) fail$3(verb, `order must include every worksheet exactly once (the workbook has ${wb.worksheets.length})`);
1687
- const reordered = [];
1688
- const seen = /* @__PURE__ */ new Set();
1689
- for (const ref of refs) {
1690
- const ws = typeof ref === "number" ? wb.worksheets[ref - 1] : wb.getWorksheet(ref);
1691
- if (!ws) fail$3(verb, `sheet not found: ${String(ref)}`);
1692
- if (seen.has(ws.id)) fail$3(verb, `duplicate sheet reference: ${String(ref)}`);
1693
- seen.add(ws.id);
1694
- reordered.push(ws);
1695
- }
1696
- reordered.forEach((ws, i) => {
1697
- ws.orderNo = i + 1;
1698
- });
1699
- return { modified: reordered.length };
1700
- });
2111
+ requireJsonArray(verb, ctx.step.args.order, `order must be a JSON array of names/indices, e.g. order='["Summary",2]'`);
2112
+ return dispatchEdit(ctx, verb, "sheet.reorder_sheets");
1701
2113
  };
1702
2114
  /** `sheet.transform_table` — rename/select/drop columns by header name. */
1703
2115
  var sheetTransformTableStep = async (ctx) => {
1704
2116
  const verb = "sheet transform_table";
1705
- const headerRow = argOf(ctx.step, "header_row") ?? 1;
1706
- const rename = ctx.step.args.rename;
1707
- const select = ctx.step.args.select;
1708
- const drop = ctx.step.args.drop;
1709
- return withWorkbook(ctx, verb, (wb) => {
1710
- const ws = resolveSheet(verb, wb, argOf(ctx.step, "sheet"));
1711
- let modified = 0;
1712
- const headerMap = /* @__PURE__ */ new Map();
1713
- ws.getRow(headerRow).eachCell({ includeEmpty: false }, (cell, colNumber) => {
1714
- if (typeof cell.value === "string") headerMap.set(cell.value, colNumber);
1715
- });
1716
- if (rename) for (const { from, to } of rename) {
1717
- const colNum = headerMap.get(from);
1718
- if (colNum !== void 0) {
1719
- ws.getRow(headerRow).getCell(colNum).value = to;
1720
- headerMap.delete(from);
1721
- headerMap.set(to, colNum);
1722
- modified += 1;
1723
- }
1724
- }
1725
- if (drop) {
1726
- const cols = drop.map((name) => headerMap.get(name)).filter((n) => n !== void 0).sort((a, b) => b - a);
1727
- for (const col of cols) {
1728
- ws.spliceColumns(col, 1);
1729
- modified += 1;
1730
- }
1731
- }
1732
- if (select) {
1733
- const keep = new Set(select);
1734
- const cols = [];
1735
- ws.getRow(headerRow).eachCell({ includeEmpty: false }, (cell, colNumber) => {
1736
- if (typeof cell.value === "string" && !keep.has(cell.value)) cols.push(colNumber);
1737
- });
1738
- cols.sort((a, b) => b - a);
1739
- for (const col of cols) {
1740
- ws.spliceColumns(col, 1);
1741
- modified += 1;
1742
- }
1743
- }
1744
- return { modified };
1745
- });
2117
+ const headerRow = argOf(ctx.step, "header_row");
2118
+ if (headerRow !== void 0 && (!Number.isFinite(headerRow) || headerRow < 1)) fail$3(verb, "header_row must be a 1-based row number");
2119
+ return dispatchEdit(ctx, verb, "sheet.transform_table");
1746
2120
  };
1747
2121
  /** The sheet step registry fragment, keyed by canonical verb id. */
1748
2122
  var SHEET_STEPS = [
@@ -1876,8 +2250,8 @@ var setSlideTitle = (slideXml, title) => {
1876
2250
  /** Acquire pptx bytes, converting ODP to PPTX via the engine registry. */
1877
2251
  var acquirePptx = async (ctx, verb) => {
1878
2252
  const mime = ctx.payload.mimeType.toLowerCase().split(";")[0].trim();
1879
- if (mime === MIME.PPTX) return ctx.payload.bytes;
1880
- if (mime === MIME.ODP) {
2253
+ if (mime === require_formats.MIME.PPTX) return ctx.payload.bytes;
2254
+ if (mime === require_formats.MIME.ODP) {
1881
2255
  if (!ctx.engines.hasConvert(mime, "pptx")) fail$2(verb, "this input is ODP — an engine that converts it to PPTX is required first, and none is configured. Do not retry this verb on this media in this deployment.");
1882
2256
  const output = (await ctx.engines.convert({
1883
2257
  bytes: ctx.payload.bytes,
@@ -1889,7 +2263,7 @@ var acquirePptx = async (ctx, verb) => {
1889
2263
  if (!output) fail$2(verb, "normalizing the presentation to PPTX produced no output");
1890
2264
  return output.bytes;
1891
2265
  }
1892
- const reason = unsupportedForMutationReason(mime);
2266
+ const reason = require_formats.unsupportedForMutationReason(mime);
1893
2267
  if (reason) fail$2(verb, reason);
1894
2268
  fail$2(verb, `slides operations expect a presentation; the media is ${mime}`);
1895
2269
  throw new Error("unreachable");
@@ -1913,8 +2287,8 @@ var withPresentation = async (ctx, verb, mutate) => {
1913
2287
  type: "uint8array",
1914
2288
  compression: "DEFLATE"
1915
2289
  }),
1916
- mimeType: MIME.PPTX,
1917
- filename: replaceExtension(ctx.payload.filename, "pptx")
2290
+ mimeType: require_formats.MIME.PPTX,
2291
+ filename: require_formats.replaceExtension(ctx.payload.filename, "pptx")
1918
2292
  }
1919
2293
  };
1920
2294
  };
@@ -2258,7 +2632,7 @@ var extractTextDeepStep = async (ctx) => {
2258
2632
  });
2259
2633
  if (mime.startsWith("image/")) return asData(await runOcr(ctx));
2260
2634
  if (ocrMode === "force") return asData(await runOcr(ctx));
2261
- if (mime === MIME.PDF) {
2635
+ if (mime === require_formats.MIME.PDF) {
2262
2636
  const text = await extractPdfText(ctx);
2263
2637
  if (text.trim().length === 0 && ocrMode === "auto") {
2264
2638
  if (!ctx.engines.hasConvert(mime, "txt")) fail$1(VERB, "this PDF has no text layer (scanned?). An engine that converts application/pdf to text (OCR) is required to read it, and none is configured. Do not retry this verb on this media in this deployment.");
@@ -2266,11 +2640,11 @@ var extractTextDeepStep = async (ctx) => {
2266
2640
  }
2267
2641
  return asData(text);
2268
2642
  }
2269
- if (mime === MIME.DOCX || mime === MIME.DOC) return asData(await extractDocxText(ctx));
2270
- if (mime === MIME.XLSX) return asData(await extractXlsxText(ctx));
2271
- if (mime === MIME.ODT || mime === MIME.ODS || mime === MIME.ODP) return asData(await extractOdfText(ctx));
2272
- if (mime === MIME.PPTX) return asData(await extractPptxText(ctx));
2273
- if (mime.startsWith("text/") || mime === MIME.JSON) return asData(decodeText(ctx.payload.bytes));
2643
+ if (mime === require_formats.MIME.DOCX || mime === require_formats.MIME.DOC) return asData(await extractDocxText(ctx));
2644
+ if (mime === require_formats.MIME.XLSX) return asData(await extractXlsxText(ctx));
2645
+ if (mime === require_formats.MIME.ODT || mime === require_formats.MIME.ODS || mime === require_formats.MIME.ODP) return asData(await extractOdfText(ctx));
2646
+ if (mime === require_formats.MIME.PPTX) return asData(await extractPptxText(ctx));
2647
+ if (mime.startsWith("text/") || mime === require_formats.MIME.JSON) return asData(decodeText(ctx.payload.bytes));
2274
2648
  fail$1(VERB, `text extraction for ${mime} is not supported in this build. For PPT/legacy formats: convert to=pptx | extract text (requires a convert engine).`);
2275
2649
  throw new Error("unreachable");
2276
2650
  };
@@ -2281,16 +2655,16 @@ var extractMetadataDeepStep = async (ctx) => {
2281
2655
  filename: ctx.payload.filename,
2282
2656
  mime_type: ctx.payload.mimeType,
2283
2657
  size_bytes: ctx.payload.bytes.byteLength,
2284
- family: familyOf(ctx.payload.mimeType)
2658
+ family: require_formats.familyOf(ctx.payload.mimeType)
2285
2659
  };
2286
2660
  try {
2287
- if (mime === MIME.PDF) {
2661
+ if (mime === require_formats.MIME.PDF) {
2288
2662
  const { PDFDocument } = await import("pdf-lib");
2289
2663
  const doc = await PDFDocument.load(ctx.payload.bytes, { ignoreEncryption: true });
2290
2664
  meta.page_count = doc.getPageCount();
2291
2665
  meta.title = doc.getTitle() ?? void 0;
2292
2666
  meta.author = doc.getAuthor() ?? void 0;
2293
- } else if (mime === MIME.XLSX) {
2667
+ } else if (mime === require_formats.MIME.XLSX) {
2294
2668
  const mod = await import("exceljs");
2295
2669
  const wb = new ("default" in mod ? mod.default : mod).Workbook();
2296
2670
  await wb.xlsx.load(ctx.payload.bytes.buffer.slice(ctx.payload.bytes.byteOffset, ctx.payload.bytes.byteOffset + ctx.payload.bytes.byteLength));
@@ -2298,7 +2672,7 @@ var extractMetadataDeepStep = async (ctx) => {
2298
2672
  name: ws.name,
2299
2673
  rows: ws.rowCount
2300
2674
  }));
2301
- } else if (mime === MIME.PPTX) {
2675
+ } else if (mime === require_formats.MIME.PPTX) {
2302
2676
  const zip = await (await jszip()).loadAsync(ctx.payload.bytes);
2303
2677
  meta.slide_count = Object.keys(zip.files).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n)).length;
2304
2678
  }
@@ -2332,10 +2706,16 @@ var INGEST_STEPS = [["extract.text", extractTextDeepStep], ["extract.metadata",
2332
2706
  /** The maximum number of conversion hops a computed path may take. */
2333
2707
  var MAX_HOPS = 3;
2334
2708
  /**
2335
- * Format tokens that are valid conversion endpoints but never intermediate path nodes.
2336
- * Without this rule the pathfinder would happily route `docx txt → …` (lossy garbage in,
2337
- * garbage out) or auto-chain `audiopcmtxt` (skipping the resample the transcription
2338
- * step performs between those legs).
2709
+ * The no-lossy-intermediates ROUTING rule and nothing else. These format tokens are valid
2710
+ * conversion endpoints but never intermediate path nodes: without this rule the pathfinder
2711
+ * would happily route `docxtxt…` (lossy garbage in, garbage out) or auto-chain
2712
+ * `audio → pcm → txt` (skipping the resample the transcription step performs between those
2713
+ * legs).
2714
+ *
2715
+ * This set does NOT gate mutation, creation, or advertising. A lossy format as the *desired
2716
+ * output* is still valid media: `txt` can be created (`empty:txt`), appended to, patched,
2717
+ * diffed, and targeted by `convert to=txt` — terminality only forbids silently routing
2718
+ * *through* it on the way to somewhere else.
2339
2719
  */
2340
2720
  var TERMINAL_TOKENS = new Set([
2341
2721
  "txt",
@@ -2403,6 +2783,20 @@ var buildEngineRegistry = (engines, selection = []) => {
2403
2783
  }
2404
2784
  return out;
2405
2785
  };
2786
+ const editCandidates = (mimeType, op) => {
2787
+ const mime = normalizeMime(mimeType);
2788
+ const out = [];
2789
+ for (const engine of engines) for (const capability of engine.edits ?? []) {
2790
+ if (!matchesAny(capability.over, mime)) continue;
2791
+ if (!capability.ops.includes(op)) continue;
2792
+ out.push({
2793
+ engine,
2794
+ capability
2795
+ });
2796
+ break;
2797
+ }
2798
+ return out;
2799
+ };
2406
2800
  const arbitrate = async (kind, request, candidates) => {
2407
2801
  if (candidates.length === 0) return void 0;
2408
2802
  if (selection.length === 0 || candidates.length === 1) return candidates[0];
@@ -2441,7 +2835,7 @@ var buildEngineRegistry = (engines, selection = []) => {
2441
2835
  if (!matchesAny(capability.from, mime)) continue;
2442
2836
  for (const to of capability.to) {
2443
2837
  if (TERMINAL_TOKENS.has(to)) continue;
2444
- const nextMime = EXT_TO_MIME[to];
2838
+ const nextMime = require_formats.EXT_TO_MIME[to];
2445
2839
  if (!nextMime || visited.has(nextMime)) continue;
2446
2840
  visited.add(nextMime);
2447
2841
  queue.push({
@@ -2478,7 +2872,7 @@ var buildEngineRegistry = (engines, selection = []) => {
2478
2872
  reachable.push(to);
2479
2873
  }
2480
2874
  if (TERMINAL_TOKENS.has(to)) continue;
2481
- const nextMime = EXT_TO_MIME[to];
2875
+ const nextMime = require_formats.EXT_TO_MIME[to];
2482
2876
  if (nextMime && !visitedMimes.has(nextMime)) {
2483
2877
  visitedMimes.add(nextMime);
2484
2878
  next.push(nextMime);
@@ -2546,13 +2940,38 @@ var buildEngineRegistry = (engines, selection = []) => {
2546
2940
  if (!chosen) throw new Error(`all engines capable of this mutate (${candidates.map((c) => c.engine.id).join(", ")}) were excluded by selection middleware`);
2547
2941
  return chosen.capability.mutate(request);
2548
2942
  };
2943
+ const edit = async (request) => {
2944
+ const candidates = editCandidates(request.mimeType, request.op);
2945
+ if (candidates.length === 0) {
2946
+ const allOps = /* @__PURE__ */ new Set();
2947
+ for (const engine of engines) for (const capability of engine.edits ?? []) for (const op of capability.ops) allOps.add(op);
2948
+ throw new Error(`no configured engine can apply "${request.op}" to ${request.mimeType}; declared edit ops: ${[...allOps].join(", ") || "(none)"} (engines: ${ids()})`);
2949
+ }
2950
+ const chosen = await arbitrate("edit", {
2951
+ mimeType: request.mimeType,
2952
+ filename: "",
2953
+ bytes: request.bytes,
2954
+ ops: [request.op]
2955
+ }, candidates);
2956
+ if (!chosen) throw new Error(`all engines capable of "${request.op}" (${candidates.map((c) => c.engine.id).join(", ")}) were excluded by selection middleware`);
2957
+ return chosen.capability.edit(request);
2958
+ };
2549
2959
  return {
2550
2960
  engines,
2551
2961
  hasConvert,
2552
2962
  hasMutate: () => engines.some((e) => (e.mutates ?? []).length > 0),
2963
+ hasEdit: (mime, op) => {
2964
+ for (const engine of engines) for (const capability of engine.edits ?? []) {
2965
+ if (mime !== void 0 && !matchesAny(capability.over, normalizeMime(mime))) continue;
2966
+ if (op !== void 0 && !capability.ops.includes(op)) continue;
2967
+ return true;
2968
+ }
2969
+ return false;
2970
+ },
2553
2971
  convertTargets,
2554
2972
  convert,
2555
- mutate
2973
+ mutate,
2974
+ edit
2556
2975
  };
2557
2976
  };
2558
2977
  //#endregion
@@ -2752,7 +3171,7 @@ var pdfLib = () => {
2752
3171
  return pdfLibPromise;
2753
3172
  };
2754
3173
  var requirePdf = (verb, payload) => {
2755
- if (payload.mimeType.toLowerCase().split(";")[0].trim() !== MIME.PDF) throw new require_exceptions.E_MEDIA_STEP_FAILED([verb, `page operations for ${payload.mimeType} are not yet implemented in this build (PDF is supported)`]);
3174
+ if (payload.mimeType.toLowerCase().split(";")[0].trim() !== require_formats.MIME.PDF) throw new require_exceptions.E_MEDIA_STEP_FAILED([verb, `page operations for ${payload.mimeType} are not yet implemented in this build (PDF is supported)`]);
2756
3175
  };
2757
3176
  var loadPdf = async (verb, bytes) => {
2758
3177
  const { PDFDocument } = await pdfLib();
@@ -2810,8 +3229,8 @@ var splitStep = async (ctx) => {
2810
3229
  const bytes = new Uint8Array(await out.save());
2811
3230
  payloads.push({
2812
3231
  bytes,
2813
- mimeType: MIME.PDF,
2814
- filename: replaceExtension(ctx.payload.filename, "").replace(/\.$/, "") + `.part${part}.pdf`
3232
+ mimeType: require_formats.MIME.PDF,
3233
+ filename: require_formats.replaceExtension(ctx.payload.filename, "").replace(/\.$/, "") + `.part${part}.pdf`
2815
3234
  });
2816
3235
  part += 1;
2817
3236
  }
@@ -2930,6 +3349,7 @@ var PURE_STEPS = [
2930
3349
  ["extract.text", extractTextStep],
2931
3350
  ["extract.metadata", extractMetadataStep],
2932
3351
  ["chunk", chunkStep],
3352
+ ...DATA_STEPS,
2933
3353
  ...SHEET_STEPS,
2934
3354
  ...SLIDES_STEPS,
2935
3355
  ...DOC_STEPS,
@@ -3007,7 +3427,8 @@ var createMediaPipeline = async (config = {}) => {
3007
3427
  return callable;
3008
3428
  };
3009
3429
  //#endregion
3010
- exports.EXT_TO_MIME = EXT_TO_MIME;
3430
+ exports.EMPTY_MIME = require_batteries_media_contracts.EMPTY_MIME;
3431
+ exports.EXT_TO_MIME = require_formats.EXT_TO_MIME;
3011
3432
  exports.E_INVALID_MEDIA_PIPELINE_CONFIG = require_exceptions.E_INVALID_MEDIA_PIPELINE_CONFIG;
3012
3433
  exports.E_MEDIA_BAD_ARG = require_exceptions.E_MEDIA_BAD_ARG;
3013
3434
  exports.E_MEDIA_ENGINE_REQUIRED = require_exceptions.E_MEDIA_ENGINE_REQUIRED;
@@ -3020,7 +3441,7 @@ exports.E_MEDIA_UNKNOWN_ARG = require_exceptions.E_MEDIA_UNKNOWN_ARG;
3020
3441
  exports.E_MEDIA_UNKNOWN_VERB = require_exceptions.E_MEDIA_UNKNOWN_VERB;
3021
3442
  exports.E_MEDIA_UNSUPPORTED_OP = require_exceptions.E_MEDIA_UNSUPPORTED_OP;
3022
3443
  exports.FOLDED_VERBS = require_validate.FOLDED_VERBS;
3023
- exports.MIME = MIME;
3444
+ exports.MIME = require_formats.MIME;
3024
3445
  exports.MediaChain = MediaChain;
3025
3446
  exports.PCM_MIME = require_batteries_media_contracts.PCM_MIME;
3026
3447
  exports.VERBS = require_validate.VERBS;
@@ -3029,7 +3450,7 @@ exports.availableVerbs = require_validate.availableVerbs;
3029
3450
  exports.buildEngineRegistry = buildEngineRegistry;
3030
3451
  exports.bytesToPcm = require_batteries_media_contracts.bytesToPcm;
3031
3452
  exports.createMediaPipeline = createMediaPipeline;
3032
- exports.familyOf = familyOf;
3453
+ exports.familyOf = require_formats.familyOf;
3033
3454
  exports.foldVerb = require_validate.foldVerb;
3034
3455
  exports.fromOps = require_validate.fromOps;
3035
3456
  exports.implementsMediaEngine = require_batteries_media_contracts.implementsMediaEngine;
@@ -3038,11 +3459,11 @@ exports.isRegExpRef = require_validate.isRegExpRef;
3038
3459
  exports.lowerSegments = lowerSegments;
3039
3460
  exports.parsePipeRaw = parsePipeRaw;
3040
3461
  exports.pcmToBytes = require_batteries_media_contracts.pcmToBytes;
3041
- exports.replaceExtension = replaceExtension;
3462
+ exports.replaceExtension = require_formats.replaceExtension;
3042
3463
  exports.suggestVerbs = require_validate.suggestVerbs;
3043
3464
  exports.toOps = require_validate.toOps;
3044
3465
  exports.toPipe = require_validate.toPipe;
3045
- exports.unsupportedForMutationReason = unsupportedForMutationReason;
3466
+ exports.unsupportedForMutationReason = require_formats.unsupportedForMutationReason;
3046
3467
  exports.validateOps = require_validate.validateOps;
3047
3468
  exports.validateSegments = require_validate.validateSegments;
3048
3469