@namzu/sdk 51.1.0 → 52.1.0

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 (395) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts +1 -1
  6. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/agents/examples/SupervisorAgent.d.ts +42 -0
  9. package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
  10. package/dist/agents/examples/SupervisorAgent.js +80 -4
  11. package/dist/agents/examples/SupervisorAgent.js.map +1 -1
  12. package/dist/agents/index.d.ts +1 -1
  13. package/dist/agents/index.d.ts.map +1 -1
  14. package/dist/agents/index.js.map +1 -1
  15. package/dist/authorization/protected-paths.d.ts +36 -0
  16. package/dist/authorization/protected-paths.d.ts.map +1 -0
  17. package/dist/authorization/protected-paths.js +107 -0
  18. package/dist/authorization/protected-paths.js.map +1 -0
  19. package/dist/authorization/readonly-commands.d.ts +39 -0
  20. package/dist/authorization/readonly-commands.d.ts.map +1 -0
  21. package/dist/authorization/readonly-commands.js +797 -0
  22. package/dist/authorization/readonly-commands.js.map +1 -0
  23. package/dist/authorization/readonly-flags.d.ts +19 -0
  24. package/dist/authorization/readonly-flags.d.ts.map +1 -0
  25. package/dist/authorization/readonly-flags.js +178 -0
  26. package/dist/authorization/readonly-flags.js.map +1 -0
  27. package/dist/authorization/removal.d.ts +52 -0
  28. package/dist/authorization/removal.d.ts.map +1 -0
  29. package/dist/authorization/removal.js +283 -0
  30. package/dist/authorization/removal.js.map +1 -0
  31. package/dist/authorization/shell-paths.d.ts +66 -0
  32. package/dist/authorization/shell-paths.d.ts.map +1 -0
  33. package/dist/authorization/shell-paths.js +697 -0
  34. package/dist/authorization/shell-paths.js.map +1 -0
  35. package/dist/bridge/acp/update.d.ts.map +1 -1
  36. package/dist/bridge/acp/update.js +1 -0
  37. package/dist/bridge/acp/update.js.map +1 -1
  38. package/dist/compaction/context-window.d.ts +24 -0
  39. package/dist/compaction/context-window.d.ts.map +1 -1
  40. package/dist/compaction/context-window.js +34 -0
  41. package/dist/compaction/context-window.js.map +1 -1
  42. package/dist/compaction/manual.d.ts.map +1 -1
  43. package/dist/compaction/manual.js +10 -2
  44. package/dist/compaction/manual.js.map +1 -1
  45. package/dist/compaction/plan.d.ts +4 -0
  46. package/dist/compaction/plan.d.ts.map +1 -1
  47. package/dist/compaction/plan.js +6 -2
  48. package/dist/compaction/plan.js.map +1 -1
  49. package/dist/compaction/verifier.d.ts +11 -1
  50. package/dist/compaction/verifier.d.ts.map +1 -1
  51. package/dist/compaction/verifier.js +75 -23
  52. package/dist/compaction/verifier.js.map +1 -1
  53. package/dist/config/runtime.d.ts +55 -7
  54. package/dist/config/runtime.d.ts.map +1 -1
  55. package/dist/config/runtime.js +23 -2
  56. package/dist/config/runtime.js.map +1 -1
  57. package/dist/connector/web/guarded-fetch.d.ts.map +1 -1
  58. package/dist/connector/web/guarded-fetch.js +24 -0
  59. package/dist/connector/web/guarded-fetch.js.map +1 -1
  60. package/dist/connector/web/index.d.ts +1 -1
  61. package/dist/connector/web/index.d.ts.map +1 -1
  62. package/dist/connector/web/page.d.ts +59 -0
  63. package/dist/connector/web/page.d.ts.map +1 -0
  64. package/dist/connector/web/page.js +112 -0
  65. package/dist/connector/web/page.js.map +1 -0
  66. package/dist/connector/web/types.d.ts +47 -0
  67. package/dist/connector/web/types.d.ts.map +1 -1
  68. package/dist/connector/web/types.js.map +1 -1
  69. package/dist/constants/continuation.d.ts +8 -0
  70. package/dist/constants/continuation.d.ts.map +1 -1
  71. package/dist/constants/continuation.js +8 -0
  72. package/dist/constants/continuation.js.map +1 -1
  73. package/dist/constants/plugin/index.d.ts +1 -1
  74. package/dist/constants/plugin/index.d.ts.map +1 -1
  75. package/dist/constants/plugin/index.js +1 -1
  76. package/dist/constants/plugin/index.js.map +1 -1
  77. package/dist/contracts/a2a.d.ts +2 -2
  78. package/dist/manager/agent/lifecycle.d.ts +7 -0
  79. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  80. package/dist/manager/agent/lifecycle.js +11 -2
  81. package/dist/manager/agent/lifecycle.js.map +1 -1
  82. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  83. package/dist/manager/session/turn-recorder.js +4 -2
  84. package/dist/manager/session/turn-recorder.js.map +1 -1
  85. package/dist/plugin/index.d.ts +1 -1
  86. package/dist/plugin/index.d.ts.map +1 -1
  87. package/dist/plugin/index.js +1 -1
  88. package/dist/plugin/index.js.map +1 -1
  89. package/dist/plugin/lifecycle.d.ts +6 -0
  90. package/dist/plugin/lifecycle.d.ts.map +1 -1
  91. package/dist/plugin/lifecycle.js +98 -114
  92. package/dist/plugin/lifecycle.js.map +1 -1
  93. package/dist/plugin/shell-hook.d.ts +90 -22
  94. package/dist/plugin/shell-hook.d.ts.map +1 -1
  95. package/dist/plugin/shell-hook.js +434 -41
  96. package/dist/plugin/shell-hook.js.map +1 -1
  97. package/dist/provider/capabilities.d.ts +9 -0
  98. package/dist/provider/capabilities.d.ts.map +1 -1
  99. package/dist/provider/capabilities.js +15 -0
  100. package/dist/provider/capabilities.js.map +1 -1
  101. package/dist/provider/errors.d.ts.map +1 -1
  102. package/dist/provider/errors.js +13 -0
  103. package/dist/provider/errors.js.map +1 -1
  104. package/dist/provider/fallback.d.ts.map +1 -1
  105. package/dist/provider/fallback.js +10 -0
  106. package/dist/provider/fallback.js.map +1 -1
  107. package/dist/provider/idle-timeout.d.ts.map +1 -1
  108. package/dist/provider/idle-timeout.js +5 -0
  109. package/dist/provider/idle-timeout.js.map +1 -1
  110. package/dist/provider/image-input-refusal.d.ts +9 -0
  111. package/dist/provider/image-input-refusal.d.ts.map +1 -0
  112. package/dist/provider/image-input-refusal.js +54 -0
  113. package/dist/provider/image-input-refusal.js.map +1 -0
  114. package/dist/provider/index.d.ts +2 -1
  115. package/dist/provider/index.d.ts.map +1 -1
  116. package/dist/provider/index.js +2 -1
  117. package/dist/provider/index.js.map +1 -1
  118. package/dist/provider/retry.d.ts +7 -2
  119. package/dist/provider/retry.d.ts.map +1 -1
  120. package/dist/provider/retry.js +38 -4
  121. package/dist/provider/retry.js.map +1 -1
  122. package/dist/public-runtime.d.ts +5 -2
  123. package/dist/public-runtime.d.ts.map +1 -1
  124. package/dist/public-runtime.js +5 -2
  125. package/dist/public-runtime.js.map +1 -1
  126. package/dist/public-types.d.ts +2 -2
  127. package/dist/public-types.d.ts.map +1 -1
  128. package/dist/runtime/jobs/registry.d.ts +21 -0
  129. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  130. package/dist/runtime/jobs/registry.js +15 -2
  131. package/dist/runtime/jobs/registry.js.map +1 -1
  132. package/dist/runtime/query/deferred-sandbox.d.ts +54 -0
  133. package/dist/runtime/query/deferred-sandbox.d.ts.map +1 -0
  134. package/dist/runtime/query/deferred-sandbox.js +75 -0
  135. package/dist/runtime/query/deferred-sandbox.js.map +1 -0
  136. package/dist/runtime/query/executor/tool-call-admission.d.ts +10 -0
  137. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  138. package/dist/runtime/query/executor/tool-call-admission.js +74 -13
  139. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  140. package/dist/runtime/query/executor.d.ts +51 -2
  141. package/dist/runtime/query/executor.d.ts.map +1 -1
  142. package/dist/runtime/query/executor.js +214 -9
  143. package/dist/runtime/query/executor.js.map +1 -1
  144. package/dist/runtime/query/finalize-turn.d.ts +6 -0
  145. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  146. package/dist/runtime/query/finalize-turn.js +1 -1
  147. package/dist/runtime/query/finalize-turn.js.map +1 -1
  148. package/dist/runtime/query/index.d.ts +29 -0
  149. package/dist/runtime/query/index.d.ts.map +1 -1
  150. package/dist/runtime/query/index.js +108 -28
  151. package/dist/runtime/query/index.js.map +1 -1
  152. package/dist/runtime/query/iteration/index.d.ts +57 -0
  153. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  154. package/dist/runtime/query/iteration/index.js +380 -28
  155. package/dist/runtime/query/iteration/index.js.map +1 -1
  156. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +61 -0
  157. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -0
  158. package/dist/runtime/query/iteration/phases/compaction-breaker.js +94 -0
  159. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -0
  160. package/dist/runtime/query/iteration/phases/compaction.d.ts +42 -1
  161. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  162. package/dist/runtime/query/iteration/phases/compaction.js +203 -23
  163. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  164. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  165. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  166. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  167. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  168. package/dist/runtime/query/iteration/phases/tool-review.js +165 -2
  169. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  170. package/dist/runtime/query/iteration/provider-rejected-image.d.ts +21 -1
  171. package/dist/runtime/query/iteration/provider-rejected-image.d.ts.map +1 -1
  172. package/dist/runtime/query/iteration/provider-rejected-image.js +53 -7
  173. package/dist/runtime/query/iteration/provider-rejected-image.js.map +1 -1
  174. package/dist/runtime/query/iteration/request-repair.d.ts +108 -0
  175. package/dist/runtime/query/iteration/request-repair.d.ts.map +1 -0
  176. package/dist/runtime/query/iteration/request-repair.js +593 -0
  177. package/dist/runtime/query/iteration/request-repair.js.map +1 -0
  178. package/dist/runtime/query/iteration/stream-turn.d.ts +6 -0
  179. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  180. package/dist/runtime/query/iteration/stream-turn.js +2 -2
  181. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  182. package/dist/runtime/query/path-escalation.d.ts +36 -0
  183. package/dist/runtime/query/path-escalation.d.ts.map +1 -0
  184. package/dist/runtime/query/path-escalation.js +184 -0
  185. package/dist/runtime/query/path-escalation.js.map +1 -0
  186. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  187. package/dist/runtime/query/plugin-hooks.js +8 -0
  188. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  189. package/dist/runtime/query/reminders.d.ts +35 -0
  190. package/dist/runtime/query/reminders.d.ts.map +1 -0
  191. package/dist/runtime/query/reminders.js +7 -0
  192. package/dist/runtime/query/reminders.js.map +1 -0
  193. package/dist/runtime/query/request-rich-content.d.ts +5 -1
  194. package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
  195. package/dist/runtime/query/request-rich-content.js +67 -25
  196. package/dist/runtime/query/request-rich-content.js.map +1 -1
  197. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  198. package/dist/runtime/query/resume-pending.js +26 -0
  199. package/dist/runtime/query/resume-pending.js.map +1 -1
  200. package/dist/runtime/query/review-policy.d.ts +26 -0
  201. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  202. package/dist/runtime/query/review-policy.js +79 -5
  203. package/dist/runtime/query/review-policy.js.map +1 -1
  204. package/dist/runtime/query/tool-output-budget.d.ts +46 -0
  205. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  206. package/dist/runtime/query/tool-output-budget.js +85 -1
  207. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  208. package/dist/runtime/query/tooling.d.ts +1 -0
  209. package/dist/runtime/query/tooling.d.ts.map +1 -1
  210. package/dist/runtime/query/tooling.js +5 -0
  211. package/dist/runtime/query/tooling.js.map +1 -1
  212. package/dist/session/paths.d.ts +5 -1
  213. package/dist/session/paths.d.ts.map +1 -1
  214. package/dist/session/paths.js +8 -0
  215. package/dist/session/paths.js.map +1 -1
  216. package/dist/store/memory/disk.d.ts.map +1 -1
  217. package/dist/store/memory/disk.js +2 -2
  218. package/dist/store/memory/disk.js.map +1 -1
  219. package/dist/store/memory/markdown.d.ts.map +1 -1
  220. package/dist/store/memory/markdown.js +2 -2
  221. package/dist/store/memory/markdown.js.map +1 -1
  222. package/dist/store/memory/naming.d.ts +9 -0
  223. package/dist/store/memory/naming.d.ts.map +1 -1
  224. package/dist/store/memory/naming.js +12 -0
  225. package/dist/store/memory/naming.js.map +1 -1
  226. package/dist/tools/builtins/bash.d.ts +30 -1
  227. package/dist/tools/builtins/bash.d.ts.map +1 -1
  228. package/dist/tools/builtins/bash.js +170 -26
  229. package/dist/tools/builtins/bash.js.map +1 -1
  230. package/dist/tools/builtins/computer-use.d.ts +1 -1
  231. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  232. package/dist/tools/builtins/computer-use.js +12 -5
  233. package/dist/tools/builtins/computer-use.js.map +1 -1
  234. package/dist/tools/builtins/content-fingerprint.d.ts +2 -0
  235. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  236. package/dist/tools/builtins/content-fingerprint.js +4 -0
  237. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  238. package/dist/tools/builtins/edit.d.ts.map +1 -1
  239. package/dist/tools/builtins/edit.js +60 -18
  240. package/dist/tools/builtins/edit.js.map +1 -1
  241. package/dist/tools/builtins/grep.d.ts +8 -1
  242. package/dist/tools/builtins/grep.d.ts.map +1 -1
  243. package/dist/tools/builtins/grep.js +152 -33
  244. package/dist/tools/builtins/grep.js.map +1 -1
  245. package/dist/tools/builtins/read-file.d.ts +1 -0
  246. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  247. package/dist/tools/builtins/read-file.js +44 -5
  248. package/dist/tools/builtins/read-file.js.map +1 -1
  249. package/dist/tools/builtins/read-render.d.ts +52 -0
  250. package/dist/tools/builtins/read-render.d.ts.map +1 -1
  251. package/dist/tools/builtins/read-render.js +143 -0
  252. package/dist/tools/builtins/read-render.js.map +1 -1
  253. package/dist/tools/builtins/web.d.ts +13 -2
  254. package/dist/tools/builtins/web.d.ts.map +1 -1
  255. package/dist/tools/builtins/web.js +225 -32
  256. package/dist/tools/builtins/web.js.map +1 -1
  257. package/dist/tools/coordinator/ask-user-question.d.ts +2 -0
  258. package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
  259. package/dist/tools/coordinator/ask-user-question.js +256 -92
  260. package/dist/tools/coordinator/ask-user-question.js.map +1 -1
  261. package/dist/tools/coordinator/question-options.d.ts +1 -0
  262. package/dist/tools/coordinator/question-options.d.ts.map +1 -1
  263. package/dist/tools/coordinator/question-options.js +6 -1
  264. package/dist/tools/coordinator/question-options.js.map +1 -1
  265. package/dist/tools/defineTool.d.ts +4 -1
  266. package/dist/tools/defineTool.d.ts.map +1 -1
  267. package/dist/tools/defineTool.js +6 -1
  268. package/dist/tools/defineTool.js.map +1 -1
  269. package/dist/tools/paths.d.ts +6 -0
  270. package/dist/tools/paths.d.ts.map +1 -1
  271. package/dist/tools/paths.js +1 -1
  272. package/dist/tools/paths.js.map +1 -1
  273. package/dist/types/agent/query.d.ts +3 -0
  274. package/dist/types/agent/query.d.ts.map +1 -1
  275. package/dist/types/errors/catalog.js +1 -1
  276. package/dist/types/errors/catalog.js.map +1 -1
  277. package/dist/types/hitl/index.d.ts +104 -0
  278. package/dist/types/hitl/index.d.ts.map +1 -1
  279. package/dist/types/hitl/index.js.map +1 -1
  280. package/dist/types/message/index.d.ts +1 -1
  281. package/dist/types/message/index.d.ts.map +1 -1
  282. package/dist/types/message/index.js +2 -0
  283. package/dist/types/message/index.js.map +1 -1
  284. package/dist/types/plugin/index.d.ts +93 -19
  285. package/dist/types/plugin/index.d.ts.map +1 -1
  286. package/dist/types/plugin/index.js +3 -0
  287. package/dist/types/plugin/index.js.map +1 -1
  288. package/dist/types/provider/errors.d.ts +19 -0
  289. package/dist/types/provider/errors.d.ts.map +1 -1
  290. package/dist/types/provider/errors.js +54 -5
  291. package/dist/types/provider/errors.js.map +1 -1
  292. package/dist/types/provider/interface.d.ts +12 -0
  293. package/dist/types/provider/interface.d.ts.map +1 -1
  294. package/dist/types/provider/stream.d.ts +1 -1
  295. package/dist/types/session/config.d.ts +14 -0
  296. package/dist/types/session/config.d.ts.map +1 -1
  297. package/dist/types/session/events.d.ts +27 -4
  298. package/dist/types/session/events.d.ts.map +1 -1
  299. package/dist/types/session/events.js.map +1 -1
  300. package/dist/types/session/records.d.ts +9 -9
  301. package/dist/types/session/records.d.ts.map +1 -1
  302. package/dist/types/session/records.js +1 -0
  303. package/dist/types/session/records.js.map +1 -1
  304. package/dist/types/session/stop-reason.d.ts +5 -0
  305. package/dist/types/session/stop-reason.d.ts.map +1 -1
  306. package/dist/types/tool/index.d.ts +52 -0
  307. package/dist/types/tool/index.d.ts.map +1 -1
  308. package/dist/types/tool/index.js.map +1 -1
  309. package/package.json +4 -2
  310. package/src/agents/QueryAgent.ts +5 -1
  311. package/src/agents/SupervisorAgent.ts +1 -1
  312. package/src/agents/examples/SupervisorAgent.ts +217 -106
  313. package/src/agents/index.ts +1 -1
  314. package/src/authorization/protected-paths.ts +110 -0
  315. package/src/authorization/readonly-commands.ts +771 -0
  316. package/src/authorization/readonly-flags.ts +196 -0
  317. package/src/authorization/removal.ts +326 -0
  318. package/src/authorization/shell-paths.ts +927 -0
  319. package/src/bridge/acp/update.ts +1 -0
  320. package/src/compaction/context-window.ts +41 -0
  321. package/src/compaction/manual.ts +10 -2
  322. package/src/compaction/plan.ts +11 -2
  323. package/src/compaction/verifier.ts +84 -22
  324. package/src/config/runtime.ts +23 -2
  325. package/src/connector/web/guarded-fetch.ts +26 -0
  326. package/src/connector/web/index.ts +3 -0
  327. package/src/connector/web/page.ts +130 -0
  328. package/src/connector/web/types.ts +47 -0
  329. package/src/constants/continuation.ts +10 -0
  330. package/src/constants/plugin/index.ts +1 -1
  331. package/src/manager/agent/lifecycle.ts +12 -4
  332. package/src/manager/session/turn-recorder.ts +10 -2
  333. package/src/plugin/index.ts +1 -0
  334. package/src/plugin/lifecycle.ts +107 -119
  335. package/src/plugin/shell-hook.ts +477 -43
  336. package/src/provider/capabilities.ts +17 -0
  337. package/src/provider/errors.ts +13 -0
  338. package/src/provider/fallback.ts +10 -0
  339. package/src/provider/idle-timeout.ts +5 -0
  340. package/src/provider/image-input-refusal.ts +54 -0
  341. package/src/provider/index.ts +2 -0
  342. package/src/provider/retry.ts +42 -4
  343. package/src/public-runtime.ts +11 -0
  344. package/src/public-types.ts +4 -0
  345. package/src/runtime/jobs/registry.ts +39 -2
  346. package/src/runtime/query/deferred-sandbox.ts +110 -0
  347. package/src/runtime/query/executor/tool-call-admission.ts +79 -13
  348. package/src/runtime/query/executor.ts +267 -11
  349. package/src/runtime/query/finalize-turn.ts +7 -1
  350. package/src/runtime/query/index.ts +169 -52
  351. package/src/runtime/query/iteration/index.ts +439 -35
  352. package/src/runtime/query/iteration/phases/compaction-breaker.ts +108 -0
  353. package/src/runtime/query/iteration/phases/compaction.ts +241 -38
  354. package/src/runtime/query/iteration/phases/context.ts +11 -0
  355. package/src/runtime/query/iteration/phases/tool-review.ts +177 -2
  356. package/src/runtime/query/iteration/provider-rejected-image.ts +71 -5
  357. package/src/runtime/query/iteration/request-repair.ts +743 -0
  358. package/src/runtime/query/iteration/stream-turn.ts +11 -1
  359. package/src/runtime/query/path-escalation.ts +204 -0
  360. package/src/runtime/query/plugin-hooks.ts +8 -0
  361. package/src/runtime/query/reminders.ts +45 -0
  362. package/src/runtime/query/request-rich-content.ts +77 -27
  363. package/src/runtime/query/resume-pending.ts +27 -0
  364. package/src/runtime/query/review-policy.ts +106 -5
  365. package/src/runtime/query/tool-output-budget.ts +115 -1
  366. package/src/runtime/query/tooling.ts +6 -0
  367. package/src/session/paths.ts +11 -1
  368. package/src/store/memory/disk.ts +2 -1
  369. package/src/store/memory/markdown.ts +2 -1
  370. package/src/store/memory/naming.ts +13 -0
  371. package/src/tools/builtins/bash.ts +203 -34
  372. package/src/tools/builtins/computer-use.ts +13 -4
  373. package/src/tools/builtins/content-fingerprint.ts +5 -0
  374. package/src/tools/builtins/edit.ts +69 -19
  375. package/src/tools/builtins/grep.ts +159 -38
  376. package/src/tools/builtins/read-file.ts +48 -5
  377. package/src/tools/builtins/read-render.ts +193 -0
  378. package/src/tools/builtins/web.ts +258 -33
  379. package/src/tools/coordinator/ask-user-question.ts +309 -103
  380. package/src/tools/coordinator/question-options.ts +8 -1
  381. package/src/tools/defineTool.ts +11 -2
  382. package/src/tools/paths.ts +1 -1
  383. package/src/types/agent/query.ts +3 -0
  384. package/src/types/errors/catalog.ts +1 -1
  385. package/src/types/hitl/index.ts +104 -0
  386. package/src/types/message/index.ts +2 -0
  387. package/src/types/plugin/index.ts +101 -18
  388. package/src/types/provider/errors.ts +66 -5
  389. package/src/types/provider/interface.ts +13 -0
  390. package/src/types/provider/stream.ts +1 -1
  391. package/src/types/session/config.ts +14 -0
  392. package/src/types/session/events.ts +32 -4
  393. package/src/types/session/records.ts +1 -0
  394. package/src/types/session/stop-reason.ts +5 -0
  395. package/src/types/tool/index.ts +53 -2
@@ -1,4 +1,4 @@
1
- import { readFile } from 'node:fs/promises'
1
+ import { readFile, stat } from 'node:fs/promises'
2
2
  import { isAbsolute, join, relative, sep } from 'node:path'
3
3
  import { z } from 'zod'
4
4
  import { walkFilesLocally } from '../../sandbox/file-walk.js'
@@ -51,6 +51,12 @@ function relativeInclude(include: string, root: string, sandboxed: boolean): str
51
51
  return pattern.includes('/') ? pattern : `**/${pattern}`
52
52
  }
53
53
 
54
+ /** Entries returned when `head_limit` is not given. */
55
+ export const DEFAULT_HEAD_LIMIT = 250
56
+
57
+ /** A matching or context line longer than this is replaced by a notice, not printed. */
58
+ export const MAX_LINE_COLUMNS = 500
59
+
54
60
  const inputSchema = z.object({
55
61
  pattern: z.string().describe('Regular expression pattern to search for in file contents'),
56
62
  path: z
@@ -68,12 +74,32 @@ const inputSchema = z.object({
68
74
  .min(0)
69
75
  .default(0)
70
76
  .describe('Number of lines to show before and after each match'),
77
+ output_mode: z
78
+ .enum(['files_with_matches', 'content', 'count'])
79
+ .default('files_with_matches')
80
+ .describe(
81
+ 'What to return. "files_with_matches" (default): the paths of files that match, newest first. "content": matching lines with file and line number (supports context_lines). "count": the number of matching lines per file.',
82
+ ),
83
+ head_limit: z.coerce
84
+ .number()
85
+ .int()
86
+ .min(0)
87
+ .optional()
88
+ .describe(
89
+ `Return at most this many entries (file paths, output lines, or count rows depending on output_mode). Defaults to ${DEFAULT_HEAD_LIMIT}. Pass 0 for no limit; use that sparingly, a large result wastes context.`,
90
+ ),
91
+ offset: z.coerce
92
+ .number()
93
+ .int()
94
+ .min(0)
95
+ .default(0)
96
+ .describe('Skip this many entries before applying head_limit, to page through results.'),
71
97
  max_results: z.coerce
72
98
  .number()
73
99
  .int()
74
100
  .min(1)
75
- .default(100)
76
- .describe('Maximum number of matching lines to return'),
101
+ .optional()
102
+ .describe('Deprecated: use head_limit. Used as head_limit when head_limit is not given.'),
77
103
  })
78
104
 
79
105
  const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5 MB — skip binaries/large files
@@ -91,7 +117,7 @@ function isBinaryContent(buffer: Buffer): boolean {
91
117
  export const GrepTool = defineTool({
92
118
  name: 'grep',
93
119
  description:
94
- 'Searches file contents using a regular expression. Returns matching lines with file paths, line numbers, and optional context lines. Searches incrementally with a bounded traversal; identifies incomplete searches. Skips binary files, files over 5 MB, and symlinks. Choose the narrowest relevant directory.',
120
+ 'Searches file contents using a regular expression. By default returns the paths of matching files, newest first; output_mode "content" returns matching lines with file paths, line numbers and optional context lines, and "count" returns the number of matching lines per file. Results are paged: head_limit (default 250, 0 for none) and offset. A line over 500 characters is replaced by a notice. Searches with a bounded traversal and identifies incomplete searches. Skips binary files, files over 5 MB, and symlinks. Choose the narrowest relevant directory.',
95
121
  inputSchema,
96
122
  category: 'analysis',
97
123
  // Declared so a path outside the turn's roots can be reviewed before the
@@ -158,12 +184,21 @@ export const GrepTool = defineTool({
158
184
  }
159
185
  const entries = walkSandbox ? walkSandbox(root, options) : walkFilesLocally(root, options)
160
186
 
187
+ // `execute` is reachable without the schema's defaults.
188
+ const mode = input.output_mode ?? 'files_with_matches'
189
+ // `max_results` predates `head_limit`; it keeps meaning "at most this many".
190
+ const headLimit = input.head_limit ?? input.max_results ?? DEFAULT_HEAD_LIMIT
191
+ const offset = input.offset ?? 0
192
+ // Content lines in output order. Only content mode uses them as the
193
+ // result; the other modes build from `matchedFiles`.
161
194
  const results: string[] = []
195
+ const matchedFiles: { relPath: string; host?: string; count: number }[] = []
162
196
  let totalMatches = 0
163
197
  let filesSearched = 0
164
198
  let filesMatched = 0
165
199
 
166
200
  let truncated = false
201
+ let pageHasMore = false
167
202
  let failure: string | undefined
168
203
  let unreadableFiles = 0
169
204
  try {
@@ -181,14 +216,16 @@ export const GrepTool = defineTool({
181
216
  if (entry.size > MAX_FILE_SIZE) continue
182
217
 
183
218
  let content: string
219
+ let contained: string | undefined
184
220
  try {
185
221
  const buffer = await readWithSignal(
186
222
  () =>
187
223
  sandbox
188
224
  ? sandbox.readFile(filePath)
189
- : resolveWithinReal(root, filePath).then((contained) =>
190
- readFile(contained, { signal: context.abortSignal }),
191
- ),
225
+ : resolveWithinReal(root, filePath).then((path) => {
226
+ contained = path
227
+ return readFile(path, { signal: context.abortSignal })
228
+ }),
192
229
  context.abortSignal,
193
230
  )
194
231
  context.abortSignal?.throwIfAborted()
@@ -201,7 +238,12 @@ export const GrepTool = defineTool({
201
238
  }
202
239
 
203
240
  const lines = content.split('\n')
204
- let fileHasMatch = false
241
+ let fileMatches = 0
242
+ const relPath = sandbox
243
+ ? `./${relativePosix(sandbox.rootDir, filePath)}`
244
+ : `./${relative(context.workingDirectory, join(lexicalRoot, relative(root, filePath)))
245
+ .split(sep)
246
+ .join('/')}`
205
247
 
206
248
  for (let i = 0; i < lines.length; i++) {
207
249
  context.abortSignal?.throwIfAborted()
@@ -209,16 +251,12 @@ export const GrepTool = defineTool({
209
251
  regex.lastIndex = 0
210
252
  if (!regex.test(line)) continue
211
253
 
212
- if (!fileHasMatch) {
213
- fileHasMatch = true
214
- filesMatched++
215
- }
216
-
217
- const relPath = sandbox
218
- ? `./${relativePosix(sandbox.rootDir, filePath)}`
219
- : `./${relative(context.workingDirectory, join(lexicalRoot, relative(root, filePath)))
220
- .split(sep)
221
- .join('/')}`
254
+ if (fileMatches === 0) filesMatched++
255
+ fileMatches++
256
+ totalMatches++
257
+ // A file list only needs to know that the file matches.
258
+ if (mode === 'files_with_matches') break
259
+ if (mode === 'count') continue
222
260
 
223
261
  if (input.context_lines > 0) {
224
262
  const start = Math.max(0, i - input.context_lines)
@@ -230,22 +268,32 @@ export const GrepTool = defineTool({
230
268
 
231
269
  for (let j = start; j <= end; j++) {
232
270
  const prefix = j === i ? ':' : '-'
233
- results.push(`${relPath}${prefix}${j + 1}${prefix}${lines[j]}`)
271
+ const text = lines[j] ?? ''
272
+ results.push(
273
+ `${relPath}${prefix}${j + 1}${prefix}${clipLine(text, j === i ? 'matching' : 'context')}`,
274
+ )
234
275
  }
235
276
  } else {
236
- results.push(`${relPath}:${i + 1}:${line}`)
277
+ results.push(`${relPath}:${i + 1}:${clipLine(line, 'matching')}`)
237
278
  }
238
279
 
239
- totalMatches++
240
- if (totalMatches >= input.max_results) break
280
+ // A full page ends the search: another file is not read just to
281
+ // prove there is more, so a page that is exactly full is reported
282
+ // as possibly continuing.
283
+ if (headLimit > 0 && results.length >= offset + headLimit) {
284
+ pageHasMore = true
285
+ break
286
+ }
241
287
  }
242
288
 
243
- if (totalMatches >= input.max_results) {
244
- // Do not read another file just to prove there are more hits. Reaching
245
- // the requested cap means completeness was not established.
246
- truncated = true
247
- break
289
+ if (fileMatches > 0 && mode !== 'content') {
290
+ matchedFiles.push({
291
+ relPath,
292
+ ...(contained !== undefined ? { host: contained } : {}),
293
+ count: fileMatches,
294
+ })
248
295
  }
296
+ if (pageHasMore) break
249
297
  }
250
298
  } catch (error) {
251
299
  truncated = true
@@ -255,21 +303,66 @@ export const GrepTool = defineTool({
255
303
  truncated = true
256
304
  failure ??= `${unreadableFiles} file(s) could not be read.`
257
305
  }
258
- const summary =
259
- totalMatches > 0
260
- ? `Found ${totalMatches} match(es) in ${filesMatched} file(s) (${filesSearched} files searched)`
261
- : truncated
262
- ? `No matches found in the files searched for pattern "${input.pattern}"`
263
- : `No matches found for pattern "${input.pattern}"`
306
+ if (pageHasMore) truncated = true
307
+
308
+ // Entries as the model will page through them.
309
+ let entriesOut: string[]
310
+ if (mode === 'content') {
311
+ entriesOut = results
312
+ } else if (mode === 'count') {
313
+ entriesOut = matchedFiles.map((f) => `${f.relPath}:${f.count}`)
314
+ } else {
315
+ entriesOut = (await sortByNewest(matchedFiles)).map((f) => f.relPath)
316
+ }
317
+ const total = entriesOut.length
318
+ const page =
319
+ headLimit === 0 ? entriesOut.slice(offset) : entriesOut.slice(offset, offset + headLimit)
320
+ // Reported only when it did something, like a `| head`: a limit that cut
321
+ // nothing off is not news.
322
+ const moreRemain = headLimit > 0 && (pageHasMore || total - offset > headLimit)
323
+ const paging = [
324
+ ...(moreRemain ? [`limit: ${headLimit}`] : []),
325
+ ...(offset > 0 ? [`offset: ${offset}`] : []),
326
+ ].join(', ')
327
+
328
+ let body: string
329
+ let summary: string | undefined
330
+ if (page.length === 0) {
331
+ body = ''
332
+ summary =
333
+ offset > 0 && total > 0
334
+ ? 'No entries at this offset.'
335
+ : truncated
336
+ ? `No matches found in the files searched for pattern "${input.pattern}"`
337
+ : `No matches found for pattern "${input.pattern}"`
338
+ } else if (mode === 'files_with_matches') {
339
+ body = `Found ${page.length} ${page.length === 1 ? 'file' : 'files'}${paging ? ` (${paging})` : ''}\n${page.join('\n')}`
340
+ } else if (mode === 'count') {
341
+ body = page.join('\n')
342
+ summary = `Found ${totalMatches} total ${totalMatches === 1 ? 'match' : 'matches'} across ${filesMatched} ${filesMatched === 1 ? 'file' : 'files'}${paging ? ` (${paging})` : ''}. ${filesSearched} files searched.`
343
+ } else {
344
+ body = page.join('\n')
345
+ summary = `Found ${totalMatches} match(es) in ${filesMatched} file(s) (${filesSearched} files searched)${offset > 0 ? ` from offset ${offset}` : ''}`
346
+ }
264
347
  const notice = failure
265
348
  ? `[Search incomplete: ${failure}]`
266
- : truncated
267
- ? `[Search incomplete: reached max_results (${input.max_results}). Narrow the directory or include pattern for a complete search.]`
268
- : undefined
349
+ : pageHasMore
350
+ ? `[Search incomplete: the page of ${headLimit} is full, so more may follow. Pass offset to continue, or narrow the directory or include pattern.]`
351
+ : truncated
352
+ ? '[Search incomplete: the search did not reach every file. Narrow the directory or include pattern for a complete search.]'
353
+ : undefined
269
354
  return {
270
355
  success: failure === undefined,
271
- output: [results.join('\n'), summary, notice].filter(Boolean).join('\n\n'),
272
- data: { totalMatches, filesSearched, filesMatched, truncated },
356
+ output: [body, summary, notice].filter(Boolean).join('\n\n'),
357
+ data: {
358
+ totalMatches,
359
+ filesSearched,
360
+ filesMatched,
361
+ truncated,
362
+ mode,
363
+ ...(moreRemain ? { appliedLimit: headLimit } : {}),
364
+ ...(offset > 0 ? { appliedOffset: offset } : {}),
365
+ },
273
366
  facts: {
274
367
  kind: 'search',
275
368
  pattern: input.pattern,
@@ -283,3 +376,31 @@ export const GrepTool = defineTool({
283
376
  }
284
377
  },
285
378
  })
379
+
380
+ /** A line past the column cap is not printed: one minified bundle would be the whole result. */
381
+ function clipLine(line: string, kind: 'matching' | 'context'): string {
382
+ return line.length > MAX_LINE_COLUMNS ? `[Omitted long ${kind} line]` : line
383
+ }
384
+
385
+ /**
386
+ * Newest modification first; ties by path. A file that cannot be stat'ed, and
387
+ * every file in a sandbox (which reports no modification time), sorts as
388
+ * oldest, so a sandbox listing is in path order.
389
+ */
390
+ async function sortByNewest<T extends { relPath: string; host?: string }>(
391
+ files: readonly T[],
392
+ ): Promise<T[]> {
393
+ const stamped = await Promise.all(
394
+ files.map(async (file) => {
395
+ if (file.host === undefined) return { file, mtime: 0 }
396
+ try {
397
+ return { file, mtime: (await stat(file.host)).mtimeMs }
398
+ } catch {
399
+ return { file, mtime: 0 }
400
+ }
401
+ }),
402
+ )
403
+ return stamped
404
+ .sort((a, b) => b.mtime - a.mtime || a.file.relPath.localeCompare(b.file.relPath))
405
+ .map((entry) => entry.file)
406
+ }
@@ -1,4 +1,4 @@
1
- import { readFile } from 'node:fs/promises'
1
+ import { readFile, stat } from 'node:fs/promises'
2
2
  import { extname } from 'node:path'
3
3
  import { z } from 'zod'
4
4
  import type { ToolContext } from '../../types/tool/index.js'
@@ -7,7 +7,7 @@ import { defineTool } from '../defineTool.js'
7
7
  import { resolveWithinAnyReal, toolRoots } from '../paths.js'
8
8
  import { looksBinary } from '../text-window.js'
9
9
  import { fingerprintContent } from './content-fingerprint.js'
10
- import { type RenderedRead, renderNumberedRead } from './read-render.js'
10
+ import { type RenderedRead, renderCappedRead } from './read-render.js'
11
11
 
12
12
  const inputSchema = z.object({
13
13
  path: z.string().describe('Path to the file to read (absolute or relative)'),
@@ -34,12 +34,18 @@ const inputSchema = z.object({
34
34
  .optional()
35
35
  .describe('Starting line number (0-indexed). Defaults to 0 (beginning of file).'),
36
36
  limit: z.coerce.number().optional().describe('Maximum number of lines to read'),
37
+ allow_large: z
38
+ .boolean()
39
+ .optional()
40
+ .describe(
41
+ 'Raise the size and token caps for this one read. Set it only after a read was refused as too large and you genuinely need all of the file.',
42
+ ),
37
43
  })
38
44
 
39
45
  export const ReadFileTool = defineTool({
40
46
  name: 'read',
41
47
  description:
42
- 'Reads a file and returns its contents with line numbers. Supports readRange ([start,end], 1-indexed inclusive) or offset/limit for large files. Without a window it returns the first 2000 lines and says so — pass offset/limit to continue.',
48
+ 'Reads a file and returns its contents with line numbers. Supports readRange ([start,end], 1-indexed inclusive) or offset/limit for large files. Without a window it returns the first 2000 lines and says so — pass offset/limit to continue. A file over 256 KiB is refused unless a window is named, and a result over about 25,000 tokens is cut to a first page that names the next call; pass allow_large only when you need all of it.',
43
49
  inputSchema,
44
50
  category: 'filesystem',
45
51
  // Declared so a path outside the turn's roots can be reviewed before the
@@ -49,6 +55,9 @@ export const ReadFileTool = defineTool({
49
55
  readOnly: true,
50
56
  destructive: false,
51
57
  concurrencySafe: true,
58
+ // Bounded by the size and token caps in `renderCappedRead`, so the turn's
59
+ // default per-result cap and the per-message budget leave it alone.
60
+ selfLimitingOutput: true,
52
61
 
53
62
  presentCall(input) {
54
63
  if (typeof input.path !== 'string') return undefined
@@ -84,7 +93,9 @@ export const ReadFileTool = defineTool({
84
93
  }
85
94
  }
86
95
  const content = buffer.toString('utf-8')
87
- const rendered = renderNumberedRead(content, input)
96
+ const capped = renderCappedRead(content, buffer.length, input, capOptions(input, context))
97
+ if (!capped.ok) return refusedRead(input.path, buffer.length, capped.message, true)
98
+ const rendered = capped.rendered
88
99
  recordObservedRead(context, input.path, content, rendered)
89
100
 
90
101
  return {
@@ -102,6 +113,13 @@ export const ReadFileTool = defineTool({
102
113
  }
103
114
 
104
115
  const filePath = await resolveWithinAnyReal(toolRoots(context), input.path)
116
+ // Refused on the size alone, before the file is in memory, when no window
117
+ // was named: the cap exists so a huge file is never loaded to be refused.
118
+ if (input.readRange === undefined && input.limit === undefined) {
119
+ const { size } = await stat(filePath)
120
+ const early = renderCappedRead('', size, input, capOptions(input, context))
121
+ if (!early.ok) return refusedRead(filePath, size, early.message, false)
122
+ }
105
123
  const buffer = await readFile(filePath)
106
124
  const binaryGuidance = describeStructuredBinaryRead(filePath, buffer)
107
125
  if (binaryGuidance) {
@@ -121,7 +139,9 @@ export const ReadFileTool = defineTool({
121
139
  }
122
140
  }
123
141
  const content = buffer.toString('utf-8')
124
- const rendered = renderNumberedRead(content, input)
142
+ const capped = renderCappedRead(content, buffer.length, input, capOptions(input, context))
143
+ if (!capped.ok) return refusedRead(filePath, buffer.length, capped.message, false)
144
+ const rendered = capped.rendered
125
145
  recordObservedRead(context, filePath, content, rendered)
126
146
 
127
147
  return {
@@ -138,6 +158,29 @@ export const ReadFileTool = defineTool({
138
158
  },
139
159
  })
140
160
 
161
+ function capOptions(
162
+ input: { path: string; allow_large?: boolean | undefined },
163
+ context: ToolContext,
164
+ ): Parameters<typeof renderCappedRead>[3] {
165
+ const tokens = Number(context.env?.NAMZU_FILE_READ_MAX_OUTPUT_TOKENS)
166
+ return {
167
+ path: input.path,
168
+ ext: extname(input.path).toLowerCase(),
169
+ ...(input.allow_large === true ? { allowLarge: true } : {}),
170
+ ...(Number.isFinite(tokens) && tokens > 0 ? { caps: { maxTokens: tokens } } : {}),
171
+ }
172
+ }
173
+
174
+ function refusedRead(path: string, bytes: number, message: string, sandboxed: boolean) {
175
+ return {
176
+ success: false as const,
177
+ output: '',
178
+ error: message,
179
+ data: { path, tooLarge: true, ...(sandboxed ? { sandboxed: true } : {}) },
180
+ facts: { kind: 'read' as const, path, bytes },
181
+ }
182
+ }
183
+
141
184
  /**
142
185
  * What a read returned, as fields. A file that is not text is named as such,
143
186
  * so a host says "Binary file, 4 KB" instead of drawing the bytes the model's
@@ -102,3 +102,196 @@ export function resolveReadWindow(
102
102
  const end = input.limit ? start + input.limit : Math.min(totalLines, start + DEFAULT_READ_LINES)
103
103
  return { start, end }
104
104
  }
105
+
106
+ /**
107
+ * Largest file `read` returns whole when the caller named no window.
108
+ * A file over it is refused with the way to read part of it.
109
+ */
110
+ export const MAX_READ_BYTES = 262_144
111
+
112
+ /** Largest read result, in estimated tokens. */
113
+ export const MAX_READ_TOKENS = 25_000
114
+
115
+ /**
116
+ * Bytes of text one token is allowed to stand for when bounding a SELECTED
117
+ * window by its bytes: a window may hold at most `tokens * 128` bytes before
118
+ * it is refused outright, without estimating anything.
119
+ */
120
+ const SELECTED_BYTES_PER_TOKEN = 128
121
+
122
+ /**
123
+ * What `allow_large` multiplies both caps by.
124
+ *
125
+ * The caps it lifts are "what still fits in the context left this turn",
126
+ * which this kernel does not track, so it lifts them by a fixed factor.
127
+ */
128
+ export const ALLOW_LARGE_FACTOR = 4
129
+
130
+ /** Share of the page a trimmed first page is aimed at, so the estimate lands under the cap. */
131
+ const TRIM_TARGET = 0.85
132
+
133
+ /** The ways a read can be refused before it returns anything. */
134
+ export type ReadRefusal = string
135
+
136
+ export interface ReadCaps {
137
+ readonly maxBytes: number
138
+ readonly maxTokens: number
139
+ }
140
+
141
+ export interface ReadCapOptions {
142
+ readonly path: string
143
+ /** File extension with the dot, lower case; JSON text is denser per token. */
144
+ readonly ext: string
145
+ readonly allowLarge?: boolean
146
+ /** Overrides for the defaults, from the host's environment. */
147
+ readonly caps?: Partial<ReadCaps>
148
+ }
149
+
150
+ /** Estimated tokens: characters over four, over two for JSON text. */
151
+ export function estimateReadTokens(text: string, ext: string): number {
152
+ const perToken = ext === '.json' || ext === '.jsonl' || ext === '.jsonc' ? 2 : 4
153
+ return Math.round(text.length / perToken)
154
+ }
155
+
156
+ function humanBytes(n: number): string {
157
+ if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MiB`
158
+ if (n >= 1024) return `${Math.round(n / 1024)} KiB`
159
+ return `${n} B`
160
+ }
161
+
162
+ function allowLargeHint(allowedAlready: boolean): string {
163
+ return allowedAlready
164
+ ? ' Even with allow_large this is more than a read may return.'
165
+ : ' To read it anyway, call read again with allow_large: true, but only if you need all of it or were asked for the whole file.'
166
+ }
167
+
168
+ /**
169
+ * Render a read, or refuse it, under the size and token caps.
170
+ *
171
+ * Three rules, in the order they are checked:
172
+ *
173
+ * 1. No window named and the file over {@link MAX_READ_BYTES}: refuse. A caller
174
+ * that asked for a window (`readRange`, `limit`) has chosen its size.
175
+ * 2. A selected window over `maxTokens * 128` bytes: refuse.
176
+ * 3. The selected text over `maxTokens` by estimate: for the first page of an
177
+ * unwindowed read, return the lines that fit with a notice naming the next
178
+ * call; for any other read, refuse and report the count.
179
+ *
180
+ * Pure, like {@link renderNumberedRead}; a read that none of this touches
181
+ * renders byte for byte as `renderNumberedRead` does.
182
+ */
183
+ export function renderCappedRead(
184
+ content: string,
185
+ byteLength: number,
186
+ input: ReadWindowRequest,
187
+ options: ReadCapOptions,
188
+ ): { ok: true; rendered: RenderedRead } | { ok: false; message: ReadRefusal } {
189
+ const factor = options.allowLarge ? ALLOW_LARGE_FACTOR : 1
190
+ const maxBytes = (options.caps?.maxBytes ?? MAX_READ_BYTES) * factor
191
+ const maxTokens = (options.caps?.maxTokens ?? MAX_READ_TOKENS) * factor
192
+ const hint = allowLargeHint(options.allowLarge === true)
193
+
194
+ const windowed = input.readRange !== undefined || input.limit !== undefined
195
+ if (!windowed && byteLength > maxBytes) {
196
+ return {
197
+ ok: false,
198
+ message: `File content (${humanBytes(byteLength)}) exceeds the maximum allowed size (${humanBytes(maxBytes)}). Use offset and limit to read part of it, or search for what you need instead of reading all of it.${hint}`,
199
+ }
200
+ }
201
+
202
+ const lines = content.split('\n')
203
+ const { start, end } = resolveReadWindow(input, lines.length)
204
+ const selected = lines.slice(start, end)
205
+ const selectedText = selected.join('\n')
206
+
207
+ const selectedBytes = Buffer.byteLength(selectedText, 'utf8')
208
+ if (selectedBytes > maxTokens * SELECTED_BYTES_PER_TOKEN) {
209
+ return {
210
+ ok: false,
211
+ message: `The requested lines hold over ${humanBytes(maxTokens * SELECTED_BYTES_PER_TOKEN)} of text, more than a read can return. Use a smaller limit; if a single line is this large, no limit will fit it, so search for what you need instead.${hint}`,
212
+ }
213
+ }
214
+
215
+ const tokens = estimateReadTokens(selectedText, options.ext)
216
+ if (tokens <= maxTokens) {
217
+ return { ok: true, rendered: renderNumberedRead(content, input) }
218
+ }
219
+
220
+ const firstPage = input.readRange === undefined && input.limit === undefined && start === 0
221
+ if (!firstPage) {
222
+ return {
223
+ ok: false,
224
+ message: `File content (${tokens} tokens) exceeds the maximum allowed tokens (${maxTokens}). Use offset and limit to read part of it, or search for what you need instead of reading all of it.${hint}`,
225
+ }
226
+ }
227
+ return {
228
+ ok: true,
229
+ rendered: trimFirstPage(selected, lines.length, selectedText, tokens, maxTokens, options.path),
230
+ }
231
+ }
232
+
233
+ /**
234
+ * The first page of a file whose first window is over the token cap: the
235
+ * leading lines that fit, numbered, and a notice that says it is a page and
236
+ * names the next call.
237
+ *
238
+ * Sized from the measured ratio of characters to tokens, aimed at
239
+ * {@link TRIM_TARGET} of the cap, and shrunk by 30% up to six times if the
240
+ * estimate still lands over. A page whose first lines are themselves too long
241
+ * falls back to a character excerpt, which cannot be paged by line.
242
+ */
243
+ function trimFirstPage(
244
+ selected: readonly string[],
245
+ totalLines: number,
246
+ selectedText: string,
247
+ tokens: number,
248
+ maxTokens: number,
249
+ path: string,
250
+ ): RenderedRead {
251
+ const charsPerToken = Math.max(0.5, selectedText.length / Math.max(1, tokens))
252
+ const fits = (text: string) => text.length / charsPerToken <= maxTokens
253
+
254
+ let keep = Math.max(
255
+ 1,
256
+ Math.min(
257
+ selected.length,
258
+ Math.floor(((selected.length * maxTokens) / Math.max(1, tokens)) * TRIM_TARGET),
259
+ ),
260
+ )
261
+ let page = selected.slice(0, keep).join('\n')
262
+ for (let i = 0; i < 6; i++) {
263
+ if (fits(page) || keep <= 1) break
264
+ keep = Math.max(1, Math.floor(keep * 0.7))
265
+ page = selected.slice(0, keep).join('\n')
266
+ }
267
+
268
+ const tail = 'Do NOT answer from this page alone if the answer may be further in the file.]'
269
+ if (!fits(page) || page.trim() === '') {
270
+ let chars = Math.max(1, Math.floor(maxTokens * charsPerToken * TRIM_TARGET))
271
+ let excerpt = selectedText.slice(0, chars)
272
+ for (let i = 0; i < 6 && !fits(excerpt); i++) {
273
+ chars = Math.max(1, Math.floor(chars * 0.7))
274
+ excerpt = selectedText.slice(0, chars)
275
+ }
276
+ const last = excerpt.charCodeAt(excerpt.length - 1)
277
+ if (last >= 0xd800 && last <= 0xdbff) excerpt = excerpt.slice(0, -1)
278
+ const shown = excerpt.split('\n').length
279
+ return {
280
+ output: `${numbered(excerpt.split('\n'))}\n\n[Truncated: PARTIAL view — ${path}: showing the first ${excerpt.length} of ${selectedText.length} characters (${tokens} tokens, cap ${maxTokens}); this file has very long lines and cannot be paged by line. Use grep to find a specific section, or read with offset and limit. ${tail}`,
281
+ totalLines,
282
+ returnedLines: shown,
283
+ partial: true,
284
+ }
285
+ }
286
+
287
+ return {
288
+ output: `${numbered(selected.slice(0, keep))}\n\n[Truncated: PARTIAL view — ${path}: showing lines 1-${keep} of ${totalLines} total (${tokens} tokens, cap ${maxTokens}). Call read with offset=${keep} limit=${keep} for the next page, or grep to find a specific section. ${tail}`,
289
+ totalLines,
290
+ returnedLines: keep,
291
+ partial: true,
292
+ }
293
+ }
294
+
295
+ function numbered(lines: readonly string[]): string {
296
+ return lines.map((line, i) => `${i + 1}\t${line}`).join('\n')
297
+ }