@astralyn/pi 1.0.4 → 1.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 (332) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/bundle/chunks/{anthropic-messages-YIOJIMVL.js → anthropic-messages-3K3DWVY6.js} +1 -1
  3. package/dist/bundle/chunks/anthropic.js +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-LDW4FGAQ.js → azure-openai-responses-CH74HGP4.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
  6. package/dist/bundle/chunks/{chunk-UCXU7KLO.js → chunk-3MMB5I7J.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-GIG5IHES.js → chunk-5KJH3GGH.js} +1 -1
  8. package/dist/bundle/chunks/chunk-6VYRLG5F.js +2 -0
  9. package/dist/bundle/chunks/{chunk-73WG7F7N.js → chunk-C2ARIVS3.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-23OTKHCH.js → chunk-D3XW5X3Z.js} +490 -260
  11. package/dist/bundle/chunks/{chunk-IFELM7MW.js → chunk-EJEOEBSJ.js} +1 -1
  12. package/dist/bundle/chunks/chunk-H35KWBT5.js +11 -0
  13. package/dist/bundle/chunks/chunk-KAC2YPTW.js +2 -0
  14. package/dist/bundle/chunks/chunk-MAC4N3BW.js +2 -0
  15. package/dist/bundle/chunks/chunk-QLU3LESZ.js +2 -0
  16. package/dist/bundle/chunks/{chunk-A6ALEEY5.js → chunk-TEAAYBTS.js} +1 -1
  17. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-XO45ZT3V.js +2 -0
  18. package/dist/bundle/chunks/codemode-worker.js +101 -5
  19. package/dist/bundle/chunks/github-copilot.js +1 -1
  20. package/dist/bundle/chunks/{google-generative-ai-S5A5IBU4.js → google-generative-ai-VVO64ZRQ.js} +1 -1
  21. package/dist/bundle/chunks/{google-vertex-UBRQFDB4.js → google-vertex-LKS5NUHA.js} +1 -1
  22. package/dist/bundle/chunks/image-resize-worker.js +1 -1
  23. package/dist/bundle/chunks/{llama-cpp-classify-R7DQLTQN.js → llama-cpp-classify-H3IZDR5O.js} +2 -2
  24. package/dist/bundle/chunks/{mistral-conversations-A2N5FSOV.js → mistral-conversations-NJUOG4ET.js} +2 -2
  25. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  26. package/dist/bundle/chunks/{openai-codex-responses-Q6JHCWLE.js → openai-codex-responses-TZWBTTYM.js} +2 -2
  27. package/dist/bundle/chunks/openai-codex.js +1 -1
  28. package/dist/bundle/chunks/{openai-completions-H4VMO7AU.js → openai-completions-7HTTU6FH.js} +1 -1
  29. package/dist/bundle/chunks/openai-decisions-776H4VDZ.js +5 -0
  30. package/dist/bundle/chunks/{openai-responses-G4PGNM2H.js → openai-responses-7A6BAQ5X.js} +1 -1
  31. package/dist/bundle/chunks/{openrouter-images-YP3YVB54.js → openrouter-images-TDLJV5N4.js} +1 -1
  32. package/dist/bundle/chunks/{pi-messages-HZZ7GEEW.js → pi-messages-ZXNERWJG.js} +1 -1
  33. package/dist/bundle/chunks/typesafe-system-one-2DQLG4R2.js +2 -0
  34. package/dist/bundle/chunks/{virtual-modules-IJTAY26N.js → virtual-modules-2NHGQRS6.js} +1 -1
  35. package/dist/bundle/cli.js +1 -1
  36. package/dist/bundle/index.js +1 -1
  37. package/dist/bundle/rpc-entry.js +1 -1
  38. package/dist/cli/args.d.ts +2 -1
  39. package/dist/cli/args.d.ts.map +1 -1
  40. package/dist/cli/args.js +24 -5
  41. package/dist/cli/args.js.map +1 -1
  42. package/dist/core/agent-session.d.ts +38 -10
  43. package/dist/core/agent-session.d.ts.map +1 -1
  44. package/dist/core/agent-session.js +190 -186
  45. package/dist/core/agent-session.js.map +1 -1
  46. package/dist/core/bash-executor.d.ts.map +1 -1
  47. package/dist/core/bash-executor.js +40 -45
  48. package/dist/core/bash-executor.js.map +1 -1
  49. package/dist/core/context-snapshot.d.ts +4 -1
  50. package/dist/core/context-snapshot.d.ts.map +1 -1
  51. package/dist/core/context-snapshot.js +45 -0
  52. package/dist/core/context-snapshot.js.map +1 -1
  53. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  54. package/dist/core/export-html/tool-renderer.js +2 -0
  55. package/dist/core/export-html/tool-renderer.js.map +1 -1
  56. package/dist/core/extensions/types.d.ts +13 -0
  57. package/dist/core/extensions/types.d.ts.map +1 -1
  58. package/dist/core/extensions/types.js.map +1 -1
  59. package/dist/core/mcp-servers.d.ts +8 -0
  60. package/dist/core/mcp-servers.d.ts.map +1 -1
  61. package/dist/core/mcp-servers.js +30 -7
  62. package/dist/core/mcp-servers.js.map +1 -1
  63. package/dist/core/model-runtime.d.ts.map +1 -1
  64. package/dist/core/model-runtime.js +2 -1
  65. package/dist/core/model-runtime.js.map +1 -1
  66. package/dist/core/nested-tool-calls.d.ts +1 -0
  67. package/dist/core/nested-tool-calls.d.ts.map +1 -1
  68. package/dist/core/nested-tool-calls.js +1 -0
  69. package/dist/core/nested-tool-calls.js.map +1 -1
  70. package/dist/core/resource-loader.d.ts +3 -0
  71. package/dist/core/resource-loader.d.ts.map +1 -1
  72. package/dist/core/resource-loader.js +3 -3
  73. package/dist/core/resource-loader.js.map +1 -1
  74. package/dist/core/sdk.d.ts +13 -4
  75. package/dist/core/sdk.d.ts.map +1 -1
  76. package/dist/core/sdk.js +19 -6
  77. package/dist/core/sdk.js.map +1 -1
  78. package/dist/core/settings-manager.d.ts +13 -0
  79. package/dist/core/settings-manager.d.ts.map +1 -1
  80. package/dist/core/settings-manager.js +37 -13
  81. package/dist/core/settings-manager.js.map +1 -1
  82. package/dist/core/skills.d.ts +4 -1
  83. package/dist/core/skills.d.ts.map +1 -1
  84. package/dist/core/skills.js +6 -1
  85. package/dist/core/skills.js.map +1 -1
  86. package/dist/core/system-prompt.d.ts +6 -0
  87. package/dist/core/system-prompt.d.ts.map +1 -1
  88. package/dist/core/system-prompt.js +9 -4
  89. package/dist/core/system-prompt.js.map +1 -1
  90. package/dist/core/tasks/session.d.ts +9 -0
  91. package/dist/core/tasks/session.d.ts.map +1 -1
  92. package/dist/core/tasks/session.js +21 -0
  93. package/dist/core/tasks/session.js.map +1 -1
  94. package/dist/core/tools/read.d.ts +13 -0
  95. package/dist/core/tools/read.d.ts.map +1 -1
  96. package/dist/core/tools/read.js +18 -1
  97. package/dist/core/tools/read.js.map +1 -1
  98. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  99. package/dist/core/tools/renderers/bash.js +8 -3
  100. package/dist/core/tools/renderers/bash.js.map +1 -1
  101. package/dist/core/tools/renderers/edit.js +1 -1
  102. package/dist/core/tools/renderers/edit.js.map +1 -1
  103. package/dist/extensions/codemode/execute.d.ts.map +1 -1
  104. package/dist/extensions/codemode/execute.js +65 -8
  105. package/dist/extensions/codemode/execute.js.map +1 -1
  106. package/dist/extensions/codemode/index.d.ts.map +1 -1
  107. package/dist/extensions/codemode/index.js +1 -0
  108. package/dist/extensions/codemode/index.js.map +1 -1
  109. package/dist/extensions/codemode/tool.d.ts +9 -2
  110. package/dist/extensions/codemode/tool.d.ts.map +1 -1
  111. package/dist/extensions/codemode/tool.js +14 -6
  112. package/dist/extensions/codemode/tool.js.map +1 -1
  113. package/dist/extensions/index.js +2 -2
  114. package/dist/extensions/index.js.map +1 -1
  115. package/dist/extensions/llama/provider.d.ts.map +1 -1
  116. package/dist/extensions/llama/provider.js +34 -11
  117. package/dist/extensions/llama/provider.js.map +1 -1
  118. package/dist/extensions/mcp/cli.js +17 -23
  119. package/dist/extensions/mcp/cli.js.map +1 -1
  120. package/dist/extensions/mcp/index.d.ts.map +1 -1
  121. package/dist/extensions/mcp/index.js +165 -64
  122. package/dist/extensions/mcp/index.js.map +1 -1
  123. package/dist/extensions/mcp/oauth.d.ts +5 -3
  124. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  125. package/dist/extensions/mcp/oauth.js +36 -10
  126. package/dist/extensions/mcp/oauth.js.map +1 -1
  127. package/dist/extensions/mcp/resources.d.ts +3 -4
  128. package/dist/extensions/mcp/resources.d.ts.map +1 -1
  129. package/dist/extensions/mcp/resources.js +3 -4
  130. package/dist/extensions/mcp/resources.js.map +1 -1
  131. package/dist/extensions/mcp/runtime.d.ts +4 -1
  132. package/dist/extensions/mcp/runtime.d.ts.map +1 -1
  133. package/dist/extensions/mcp/runtime.js +22 -5
  134. package/dist/extensions/mcp/runtime.js.map +1 -1
  135. package/dist/extensions/mcp/tools.d.ts +2 -1
  136. package/dist/extensions/mcp/tools.d.ts.map +1 -1
  137. package/dist/extensions/mcp/tools.js +2 -1
  138. package/dist/extensions/mcp/tools.js.map +1 -1
  139. package/dist/extensions/mcp/ui.d.ts +3 -3
  140. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  141. package/dist/extensions/mcp/ui.js +10 -2
  142. package/dist/extensions/mcp/ui.js.map +1 -1
  143. package/dist/extensions/search/code-search.d.ts +29 -0
  144. package/dist/extensions/search/code-search.d.ts.map +1 -0
  145. package/dist/extensions/search/code-search.js +120 -0
  146. package/dist/extensions/search/code-search.js.map +1 -0
  147. package/dist/extensions/search/command.d.ts +5 -0
  148. package/dist/extensions/search/command.d.ts.map +1 -0
  149. package/dist/extensions/search/command.js +69 -0
  150. package/dist/extensions/search/command.js.map +1 -0
  151. package/dist/extensions/search/credential.d.ts +25 -0
  152. package/dist/extensions/search/credential.d.ts.map +1 -0
  153. package/dist/extensions/search/credential.js +117 -0
  154. package/dist/extensions/search/credential.js.map +1 -0
  155. package/dist/extensions/search/devin.d.ts +34 -0
  156. package/dist/extensions/search/devin.d.ts.map +1 -0
  157. package/dist/extensions/search/devin.js +210 -0
  158. package/dist/extensions/search/devin.js.map +1 -0
  159. package/dist/extensions/search/index.d.ts +5 -0
  160. package/dist/extensions/search/index.d.ts.map +1 -0
  161. package/dist/extensions/search/index.js +110 -0
  162. package/dist/extensions/search/index.js.map +1 -0
  163. package/dist/extensions/search/panel.d.ts +53 -0
  164. package/dist/extensions/search/panel.d.ts.map +1 -0
  165. package/dist/extensions/search/panel.js +157 -0
  166. package/dist/extensions/search/panel.js.map +1 -0
  167. package/dist/extensions/search/prompt.d.ts +14 -0
  168. package/dist/extensions/search/prompt.d.ts.map +1 -0
  169. package/dist/extensions/search/prompt.js +221 -0
  170. package/dist/extensions/search/prompt.js.map +1 -0
  171. package/dist/extensions/search/render.d.ts +28 -0
  172. package/dist/extensions/search/render.d.ts.map +1 -0
  173. package/dist/extensions/search/render.js +86 -0
  174. package/dist/extensions/search/render.js.map +1 -0
  175. package/dist/extensions/search/workspace.d.ts +38 -0
  176. package/dist/extensions/search/workspace.d.ts.map +1 -0
  177. package/dist/extensions/search/workspace.js +245 -0
  178. package/dist/extensions/search/workspace.js.map +1 -0
  179. package/dist/main.d.ts.map +1 -1
  180. package/dist/main.js +1 -0
  181. package/dist/main.js.map +1 -1
  182. package/dist/modes/interactive/components/bash-execution.d.ts +5 -1
  183. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  184. package/dist/modes/interactive/components/bash-execution.js +14 -14
  185. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  186. package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -1
  187. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  188. package/dist/modes/interactive/components/branch-summary-message.js +5 -2
  189. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  190. package/dist/modes/interactive/components/compaction-summary-message.d.ts +2 -1
  191. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  192. package/dist/modes/interactive/components/compaction-summary-message.js +5 -2
  193. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  194. package/dist/modes/interactive/components/custom-entry.d.ts +3 -1
  195. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
  196. package/dist/modes/interactive/components/custom-entry.js +7 -2
  197. package/dist/modes/interactive/components/custom-entry.js.map +1 -1
  198. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/custom-message.js +1 -0
  200. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  201. package/dist/modes/interactive/components/settings-selector.js +1 -1
  202. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  203. package/dist/modes/interactive/components/skill-invocation-message.d.ts +2 -1
  204. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/skill-invocation-message.js +5 -2
  206. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  207. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  208. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  209. package/dist/modes/interactive/interactive-mode.js +52 -59
  210. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  211. package/dist/modes/interactive/program-status-reporter.d.ts +34 -0
  212. package/dist/modes/interactive/program-status-reporter.d.ts.map +1 -0
  213. package/dist/modes/interactive/program-status-reporter.js +107 -0
  214. package/dist/modes/interactive/program-status-reporter.js.map +1 -0
  215. package/dist/modes/interactive/tasks/host-hooks.d.ts +14 -0
  216. package/dist/modes/interactive/tasks/host-hooks.d.ts.map +1 -0
  217. package/dist/modes/interactive/tasks/host-hooks.js +31 -0
  218. package/dist/modes/interactive/tasks/host-hooks.js.map +1 -0
  219. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  220. package/dist/modes/interactive/theme/theme.js +1 -0
  221. package/dist/modes/interactive/theme/theme.js.map +1 -1
  222. package/dist/modes/interactive/tool-view/tool-execution.d.ts +5 -0
  223. package/dist/modes/interactive/tool-view/tool-execution.d.ts.map +1 -1
  224. package/dist/modes/interactive/tool-view/tool-execution.js +7 -0
  225. package/dist/modes/interactive/tool-view/tool-execution.js.map +1 -1
  226. package/dist/package-manager-cli.d.ts.map +1 -1
  227. package/dist/package-manager-cli.js +24 -0
  228. package/dist/package-manager-cli.js.map +1 -1
  229. package/dist/utils/ansi.d.ts +8 -0
  230. package/dist/utils/ansi.d.ts.map +1 -1
  231. package/dist/utils/ansi.js +30 -11
  232. package/dist/utils/ansi.js.map +1 -1
  233. package/dist/utils/clipboard.d.ts.map +1 -1
  234. package/dist/utils/clipboard.js +12 -11
  235. package/dist/utils/clipboard.js.map +1 -1
  236. package/dist/utils/image-resize-core.d.ts +12 -0
  237. package/dist/utils/image-resize-core.d.ts.map +1 -1
  238. package/dist/utils/image-resize-core.js +5 -0
  239. package/dist/utils/image-resize-core.js.map +1 -1
  240. package/dist/utils/image-resize-worker.js +3 -2
  241. package/dist/utils/image-resize-worker.js.map +1 -1
  242. package/dist/utils/image-resize.d.ts.map +1 -1
  243. package/dist/utils/image-resize.js +9 -10
  244. package/dist/utils/image-resize.js.map +1 -1
  245. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  246. package/dist/utils/syntax-highlight.js +6 -1
  247. package/dist/utils/syntax-highlight.js.map +1 -1
  248. package/docs/bundled/README.md +1 -1
  249. package/docs/bundled/extensions/provider.md +1 -1
  250. package/docs/bundled/extensions/question.md +1 -1
  251. package/docs/bundled/extensions/search.md +50 -0
  252. package/docs/bundled/extensions/statusline.md +15 -41
  253. package/docs/codemode.md +26 -5
  254. package/docs/docs.json +0 -4
  255. package/docs/environment-variables.md +1 -0
  256. package/docs/extensions.md +6 -2
  257. package/docs/json.md +2 -2
  258. package/docs/llama-cpp.md +8 -1
  259. package/docs/mcp.md +3 -1
  260. package/docs/models.md +4 -1
  261. package/docs/providers.md +17 -0
  262. package/docs/rpc.md +6 -3
  263. package/docs/session-format.md +1 -0
  264. package/docs/settings.md +4 -4
  265. package/docs/terminal-setup.md +14 -0
  266. package/docs/usage.md +19 -5
  267. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  268. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  269. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  270. package/examples/extensions/gondolin/package-lock.json +2 -2
  271. package/examples/extensions/gondolin/package.json +1 -1
  272. package/examples/extensions/sandbox/package-lock.json +2 -2
  273. package/examples/extensions/sandbox/package.json +1 -1
  274. package/examples/extensions/with-deps/package-lock.json +2 -2
  275. package/examples/extensions/with-deps/package.json +1 -1
  276. package/npm-shrinkwrap.json +54 -54
  277. package/package.json +13 -11
  278. package/dist/bundle/chunks/chunk-57XI7YJH.js +0 -2
  279. package/dist/bundle/chunks/chunk-JI4WGISU.js +0 -2
  280. package/dist/bundle/chunks/chunk-TCQ4HHQD.js +0 -2
  281. package/dist/bundle/chunks/chunk-TXJO4AJ7.js +0 -11
  282. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-OWU5SM6J.js +0 -2
  283. package/dist/bundle/chunks/typesafe-system-one-KYKKNU3E.js +0 -2
  284. package/dist/extensions/web-search/auth.d.ts +0 -12
  285. package/dist/extensions/web-search/auth.d.ts.map +0 -1
  286. package/dist/extensions/web-search/auth.js +0 -90
  287. package/dist/extensions/web-search/auth.js.map +0 -1
  288. package/dist/extensions/web-search/constants.d.ts +0 -13
  289. package/dist/extensions/web-search/constants.d.ts.map +0 -1
  290. package/dist/extensions/web-search/constants.js +0 -26
  291. package/dist/extensions/web-search/constants.js.map +0 -1
  292. package/dist/extensions/web-search/execute.d.ts +0 -9
  293. package/dist/extensions/web-search/execute.d.ts.map +0 -1
  294. package/dist/extensions/web-search/execute.js +0 -96
  295. package/dist/extensions/web-search/execute.js.map +0 -1
  296. package/dist/extensions/web-search/format.d.ts +0 -21
  297. package/dist/extensions/web-search/format.d.ts.map +0 -1
  298. package/dist/extensions/web-search/format.js +0 -117
  299. package/dist/extensions/web-search/format.js.map +0 -1
  300. package/dist/extensions/web-search/index.d.ts +0 -6
  301. package/dist/extensions/web-search/index.d.ts.map +0 -1
  302. package/dist/extensions/web-search/index.js +0 -50
  303. package/dist/extensions/web-search/index.js.map +0 -1
  304. package/dist/extensions/web-search/providers/deepseek.d.ts +0 -13
  305. package/dist/extensions/web-search/providers/deepseek.d.ts.map +0 -1
  306. package/dist/extensions/web-search/providers/deepseek.js +0 -110
  307. package/dist/extensions/web-search/providers/deepseek.js.map +0 -1
  308. package/dist/extensions/web-search/providers/http.d.ts +0 -4
  309. package/dist/extensions/web-search/providers/http.d.ts.map +0 -1
  310. package/dist/extensions/web-search/providers/http.js +0 -42
  311. package/dist/extensions/web-search/providers/http.js.map +0 -1
  312. package/dist/extensions/web-search/providers/minimax.d.ts +0 -12
  313. package/dist/extensions/web-search/providers/minimax.d.ts.map +0 -1
  314. package/dist/extensions/web-search/providers/minimax.js +0 -61
  315. package/dist/extensions/web-search/providers/minimax.js.map +0 -1
  316. package/dist/extensions/web-search/render.d.ts +0 -15
  317. package/dist/extensions/web-search/render.d.ts.map +0 -1
  318. package/dist/extensions/web-search/render.js +0 -142
  319. package/dist/extensions/web-search/render.js.map +0 -1
  320. package/dist/extensions/web-search/results.d.ts +0 -35
  321. package/dist/extensions/web-search/results.d.ts.map +0 -1
  322. package/dist/extensions/web-search/results.js +0 -167
  323. package/dist/extensions/web-search/results.js.map +0 -1
  324. package/dist/extensions/web-search/schema.d.ts +0 -10
  325. package/dist/extensions/web-search/schema.d.ts.map +0 -1
  326. package/dist/extensions/web-search/schema.js +0 -36
  327. package/dist/extensions/web-search/schema.js.map +0 -1
  328. package/dist/extensions/web-search/types.d.ts +0 -51
  329. package/dist/extensions/web-search/types.d.ts.map +0 -1
  330. package/dist/extensions/web-search/types.js +0 -5
  331. package/dist/extensions/web-search/types.js.map +0 -1
  332. package/docs/bundled/extensions/web-search.md +0 -46
@@ -1 +1 @@
1
- {"version":3,"file":"image-resize-core.js","sourceRoot":"","sources":["../../src/utils/image-resize-core.ts"],"names":[],"mappings":"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","sourcesContent":["import { applyExifOrientation } from \"./exif-orientation.ts\";\nimport { loadPhoton } from \"./photon.ts\";\n\nexport interface ImageResizeOptions {\n\tmaxWidth?: number; // Default: 2000\n\tmaxHeight?: number; // Default: 2000\n\tmaxBytes?: number; // Default: 4.5MB of base64 payload (below Anthropic's 5MB limit)\n\tjpegQuality?: number; // Default: 80\n}\n\nexport interface ResizedImage {\n\tdata: string; // base64\n\tmimeType: string;\n\toriginalWidth: number;\n\toriginalHeight: number;\n\twidth: number;\n\theight: number;\n\twasResized: boolean;\n}\n\n// 4.5MB of base64 payload. Provides headroom below Anthropic's 5MB limit.\nconst DEFAULT_MAX_BYTES = 4.5 * 1024 * 1024;\n\nconst DEFAULT_OPTIONS: Required<ImageResizeOptions> = {\n\tmaxWidth: 2000,\n\tmaxHeight: 2000,\n\tmaxBytes: DEFAULT_MAX_BYTES,\n\tjpegQuality: 80,\n};\n\ninterface EncodedCandidate {\n\tdata: string;\n\tencodedSize: number;\n\tmimeType: string;\n}\n\nfunction encodeCandidate(buffer: Uint8Array, mimeType: string): EncodedCandidate {\n\tconst data = Buffer.from(buffer).toString(\"base64\");\n\treturn {\n\t\tdata,\n\t\tencodedSize: Buffer.byteLength(data, \"utf-8\"),\n\t\tmimeType,\n\t};\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and encoded file size.\n * Returns null if the image cannot be resized below maxBytes.\n *\n * Uses Photon (Rust/WASM) for image processing. If Photon is not available,\n * returns null.\n *\n * Strategy for staying under maxBytes:\n * 1. First resize to maxWidth/maxHeight\n * 2. Try both PNG and JPEG formats, pick the smaller one\n * 3. If still too large, try JPEG with decreasing quality\n * 4. If still too large, progressively reduce dimensions until 1x1\n */\nexport async function resizeImageInProcess(\n\tinputBytes: Uint8Array,\n\tmimeType: string,\n\toptions?: ImageResizeOptions,\n): Promise<ResizedImage | null> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst inputBase64Size = Math.ceil(inputBytes.byteLength / 3) * 4;\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\tlet image: ReturnType<typeof photon.PhotonImage.new_from_byteslice> | undefined;\n\ttry {\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(inputBytes);\n\t\timage = applyExifOrientation(photon, rawImage, inputBytes);\n\t\tif (image !== rawImage) rawImage.free();\n\n\t\tconst originalWidth = image.get_width();\n\t\tconst originalHeight = image.get_height();\n\t\tconst format = mimeType.split(\"/\")[1] ?? \"png\";\n\n\t\t// Check if already within all limits (dimensions AND encoded size)\n\t\tif (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && inputBase64Size < opts.maxBytes) {\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(inputBytes).toString(\"base64\"),\n\t\t\t\tmimeType: mimeType || `image/${format}`,\n\t\t\t\toriginalWidth,\n\t\t\t\toriginalHeight,\n\t\t\t\twidth: originalWidth,\n\t\t\t\theight: originalHeight,\n\t\t\t\twasResized: false,\n\t\t\t};\n\t\t}\n\n\t\t// Calculate initial dimensions respecting max limits\n\t\tlet targetWidth = originalWidth;\n\t\tlet targetHeight = originalHeight;\n\n\t\tif (targetWidth > opts.maxWidth) {\n\t\t\ttargetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);\n\t\t\ttargetWidth = opts.maxWidth;\n\t\t}\n\t\tif (targetHeight > opts.maxHeight) {\n\t\t\ttargetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);\n\t\t\ttargetHeight = opts.maxHeight;\n\t\t}\n\n\t\tfunction tryEncodings(width: number, height: number, jpegQualities: number[]): EncodedCandidate[] {\n\t\t\tconst resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3);\n\n\t\t\ttry {\n\t\t\t\tconst candidates: EncodedCandidate[] = [encodeCandidate(resized.get_bytes(), \"image/png\")];\n\t\t\t\tfor (const quality of jpegQualities) {\n\t\t\t\t\tcandidates.push(encodeCandidate(resized.get_bytes_jpeg(quality), \"image/jpeg\"));\n\t\t\t\t}\n\t\t\t\treturn candidates;\n\t\t\t} finally {\n\t\t\t\tresized.free();\n\t\t\t}\n\t\t}\n\n\t\tconst qualitySteps = Array.from(new Set([opts.jpegQuality, 85, 70, 55, 40]));\n\t\tlet currentWidth = targetWidth;\n\t\tlet currentHeight = targetHeight;\n\n\t\twhile (true) {\n\t\t\tconst candidates = tryEncodings(currentWidth, currentHeight, qualitySteps);\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tif (candidate.encodedSize < opts.maxBytes) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdata: candidate.data,\n\t\t\t\t\t\tmimeType: candidate.mimeType,\n\t\t\t\t\t\toriginalWidth,\n\t\t\t\t\t\toriginalHeight,\n\t\t\t\t\t\twidth: currentWidth,\n\t\t\t\t\t\theight: currentHeight,\n\t\t\t\t\t\twasResized: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (currentWidth === 1 && currentHeight === 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst nextWidth = currentWidth === 1 ? 1 : Math.max(1, Math.floor(currentWidth * 0.75));\n\t\t\tconst nextHeight = currentHeight === 1 ? 1 : Math.max(1, Math.floor(currentHeight * 0.75));\n\t\t\tif (nextWidth === currentWidth && nextHeight === currentHeight) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcurrentWidth = nextWidth;\n\t\t\tcurrentHeight = nextHeight;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\tif (image) {\n\t\t\timage.free();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"image-resize-core.js","sourceRoot":"","sources":["../../src/utils/image-resize-core.ts"],"names":[],"mappings":"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","sourcesContent":["import { applyExifOrientation } from \"./exif-orientation.ts\";\nimport { loadPhoton } from \"./photon.ts\";\n\nexport interface ImageResizeOptions {\n\tmaxWidth?: number; // Default: 2000\n\tmaxHeight?: number; // Default: 2000\n\tmaxBytes?: number; // Default: 4.5MB of base64 payload (below Anthropic's 5MB limit)\n\tjpegQuality?: number; // Default: 80\n}\n\nexport interface ResizedImage {\n\tdata: string; // base64\n\tmimeType: string;\n\toriginalWidth: number;\n\toriginalHeight: number;\n\twidth: number;\n\theight: number;\n\twasResized: boolean;\n}\n\n/**\n * Tag on image resize worker replies. Node can post its own messages on the worker channel\n * (for example `{ \"watch:require\": [...] }` under `node --watch`), so replies must be identifiable.\n */\nexport const IMAGE_RESIZE_WORKER_RESPONSE_TYPE = \"pi:image-resize-response\";\n\nexport type ResizeImageWorkerResponse =\n\t| { type: typeof IMAGE_RESIZE_WORKER_RESPONSE_TYPE; result: ResizedImage | null }\n\t| { type: typeof IMAGE_RESIZE_WORKER_RESPONSE_TYPE; error: string };\n\n// 4.5MB of base64 payload. Provides headroom below Anthropic's 5MB limit.\nconst DEFAULT_MAX_BYTES = 4.5 * 1024 * 1024;\n\nconst DEFAULT_OPTIONS: Required<ImageResizeOptions> = {\n\tmaxWidth: 2000,\n\tmaxHeight: 2000,\n\tmaxBytes: DEFAULT_MAX_BYTES,\n\tjpegQuality: 80,\n};\n\ninterface EncodedCandidate {\n\tdata: string;\n\tencodedSize: number;\n\tmimeType: string;\n}\n\nfunction encodeCandidate(buffer: Uint8Array, mimeType: string): EncodedCandidate {\n\tconst data = Buffer.from(buffer).toString(\"base64\");\n\treturn {\n\t\tdata,\n\t\tencodedSize: Buffer.byteLength(data, \"utf-8\"),\n\t\tmimeType,\n\t};\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and encoded file size.\n * Returns null if the image cannot be resized below maxBytes.\n *\n * Uses Photon (Rust/WASM) for image processing. If Photon is not available,\n * returns null.\n *\n * Strategy for staying under maxBytes:\n * 1. First resize to maxWidth/maxHeight\n * 2. Try both PNG and JPEG formats, pick the smaller one\n * 3. If still too large, try JPEG with decreasing quality\n * 4. If still too large, progressively reduce dimensions until 1x1\n */\nexport async function resizeImageInProcess(\n\tinputBytes: Uint8Array,\n\tmimeType: string,\n\toptions?: ImageResizeOptions,\n): Promise<ResizedImage | null> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst inputBase64Size = Math.ceil(inputBytes.byteLength / 3) * 4;\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\tlet image: ReturnType<typeof photon.PhotonImage.new_from_byteslice> | undefined;\n\ttry {\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(inputBytes);\n\t\timage = applyExifOrientation(photon, rawImage, inputBytes);\n\t\tif (image !== rawImage) rawImage.free();\n\n\t\tconst originalWidth = image.get_width();\n\t\tconst originalHeight = image.get_height();\n\t\tconst format = mimeType.split(\"/\")[1] ?? \"png\";\n\n\t\t// Check if already within all limits (dimensions AND encoded size)\n\t\tif (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && inputBase64Size < opts.maxBytes) {\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(inputBytes).toString(\"base64\"),\n\t\t\t\tmimeType: mimeType || `image/${format}`,\n\t\t\t\toriginalWidth,\n\t\t\t\toriginalHeight,\n\t\t\t\twidth: originalWidth,\n\t\t\t\theight: originalHeight,\n\t\t\t\twasResized: false,\n\t\t\t};\n\t\t}\n\n\t\t// Calculate initial dimensions respecting max limits\n\t\tlet targetWidth = originalWidth;\n\t\tlet targetHeight = originalHeight;\n\n\t\tif (targetWidth > opts.maxWidth) {\n\t\t\ttargetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);\n\t\t\ttargetWidth = opts.maxWidth;\n\t\t}\n\t\tif (targetHeight > opts.maxHeight) {\n\t\t\ttargetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);\n\t\t\ttargetHeight = opts.maxHeight;\n\t\t}\n\n\t\tfunction tryEncodings(width: number, height: number, jpegQualities: number[]): EncodedCandidate[] {\n\t\t\tconst resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3);\n\n\t\t\ttry {\n\t\t\t\tconst candidates: EncodedCandidate[] = [encodeCandidate(resized.get_bytes(), \"image/png\")];\n\t\t\t\tfor (const quality of jpegQualities) {\n\t\t\t\t\tcandidates.push(encodeCandidate(resized.get_bytes_jpeg(quality), \"image/jpeg\"));\n\t\t\t\t}\n\t\t\t\treturn candidates;\n\t\t\t} finally {\n\t\t\t\tresized.free();\n\t\t\t}\n\t\t}\n\n\t\tconst qualitySteps = Array.from(new Set([opts.jpegQuality, 85, 70, 55, 40]));\n\t\tlet currentWidth = targetWidth;\n\t\tlet currentHeight = targetHeight;\n\n\t\twhile (true) {\n\t\t\tconst candidates = tryEncodings(currentWidth, currentHeight, qualitySteps);\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tif (candidate.encodedSize < opts.maxBytes) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdata: candidate.data,\n\t\t\t\t\t\tmimeType: candidate.mimeType,\n\t\t\t\t\t\toriginalWidth,\n\t\t\t\t\t\toriginalHeight,\n\t\t\t\t\t\twidth: currentWidth,\n\t\t\t\t\t\theight: currentHeight,\n\t\t\t\t\t\twasResized: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (currentWidth === 1 && currentHeight === 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst nextWidth = currentWidth === 1 ? 1 : Math.max(1, Math.floor(currentWidth * 0.75));\n\t\t\tconst nextHeight = currentHeight === 1 ? 1 : Math.max(1, Math.floor(currentHeight * 0.75));\n\t\t\tif (nextWidth === currentWidth && nextHeight === currentHeight) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcurrentWidth = nextWidth;\n\t\t\tcurrentHeight = nextHeight;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\tif (image) {\n\t\t\timage.free();\n\t\t}\n\t}\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { parentPort } from "node:worker_threads";
2
- import { resizeImageInProcess } from "./image-resize-core.js";
2
+ import { IMAGE_RESIZE_WORKER_RESPONSE_TYPE, resizeImageInProcess, } from "./image-resize-core.js";
3
3
  function isResizeImageWorkerRequest(value) {
4
4
  if (!value || typeof value !== "object")
5
5
  return false;
@@ -17,11 +17,12 @@ port.once("message", (message) => {
17
17
  throw new Error("Invalid image resize worker request");
18
18
  }
19
19
  const result = await resizeImageInProcess(message.inputBytes, message.mimeType, message.options);
20
- const response = { result };
20
+ const response = { type: IMAGE_RESIZE_WORKER_RESPONSE_TYPE, result };
21
21
  port.postMessage(response);
22
22
  }
23
23
  catch (error) {
24
24
  const response = {
25
+ type: IMAGE_RESIZE_WORKER_RESPONSE_TYPE,
25
26
  error: error instanceof Error ? error.message : String(error),
26
27
  };
27
28
  port.postMessage(response);
@@ -1 +1 @@
1
- {"version":3,"file":"image-resize-worker.js","sourceRoot":"","sources":["../../src/utils/image-resize-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAA8C,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAa1G,SAAS,0BAA0B,CAAC,KAAc;IACjD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,OAAO,MAAM,CAAC,UAAU,YAAY,UAAU,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC;AACvF,CAAC;AAED,MAAM,IAAI,GAAG,UAAU,CAAC;AACxB,IAAI,CAAC,IAAI,EAAE,CAAC;IACX,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC5D,CAAC;AAED,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,OAAgB,EAAE,EAAE;IACzC,KAAK,CAAC,KAAK,IAAI,EAAE;QAChB,IAAI,CAAC;YACJ,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACxD,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACjG,MAAM,QAAQ,GAA8B,EAAE,MAAM,EAAE,CAAC;YACvD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,QAAQ,GAA8B;gBAC3C,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC7D,CAAC;YACF,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;IACF,CAAC,CAAC,EAAE,CAAC;AACN,CAAC,CAAC,CAAC","sourcesContent":["import { parentPort } from \"node:worker_threads\";\nimport { type ImageResizeOptions, type ResizedImage, resizeImageInProcess } from \"./image-resize-core.ts\";\n\ninterface ResizeImageWorkerRequest {\n\tinputBytes: Uint8Array;\n\tmimeType: string;\n\toptions?: ImageResizeOptions;\n}\n\ninterface ResizeImageWorkerResponse {\n\tresult?: ResizedImage | null;\n\terror?: string;\n}\n\nfunction isResizeImageWorkerRequest(value: unknown): value is ResizeImageWorkerRequest {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst record = value as Record<string, unknown>;\n\treturn record.inputBytes instanceof Uint8Array && typeof record.mimeType === \"string\";\n}\n\nconst port = parentPort;\nif (!port) {\n\tthrow new Error(\"image resize worker requires parentPort\");\n}\n\nport.once(\"message\", (message: unknown) => {\n\tvoid (async () => {\n\t\ttry {\n\t\t\tif (!isResizeImageWorkerRequest(message)) {\n\t\t\t\tthrow new Error(\"Invalid image resize worker request\");\n\t\t\t}\n\t\t\tconst result = await resizeImageInProcess(message.inputBytes, message.mimeType, message.options);\n\t\t\tconst response: ResizeImageWorkerResponse = { result };\n\t\t\tport.postMessage(response);\n\t\t} catch (error) {\n\t\t\tconst response: ResizeImageWorkerResponse = {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t\tport.postMessage(response);\n\t\t}\n\t})();\n});\n"]}
1
+ {"version":3,"file":"image-resize-worker.js","sourceRoot":"","sources":["../../src/utils/image-resize-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACN,iCAAiC,EAGjC,oBAAoB,GACpB,MAAM,wBAAwB,CAAC;AAQhC,SAAS,0BAA0B,CAAC,KAAc;IACjD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,OAAO,MAAM,CAAC,UAAU,YAAY,UAAU,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC;AACvF,CAAC;AAED,MAAM,IAAI,GAAG,UAAU,CAAC;AACxB,IAAI,CAAC,IAAI,EAAE,CAAC;IACX,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC5D,CAAC;AAED,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,OAAgB,EAAE,EAAE;IACzC,KAAK,CAAC,KAAK,IAAI,EAAE;QAChB,IAAI,CAAC;YACJ,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACxD,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACjG,MAAM,QAAQ,GAA8B,EAAE,IAAI,EAAE,iCAAiC,EAAE,MAAM,EAAE,CAAC;YAChG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,QAAQ,GAA8B;gBAC3C,IAAI,EAAE,iCAAiC;gBACvC,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC7D,CAAC;YACF,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;IACF,CAAC,CAAC,EAAE,CAAC;AACN,CAAC,CAAC,CAAC","sourcesContent":["import { parentPort } from \"node:worker_threads\";\nimport {\n\tIMAGE_RESIZE_WORKER_RESPONSE_TYPE,\n\ttype ImageResizeOptions,\n\ttype ResizeImageWorkerResponse,\n\tresizeImageInProcess,\n} from \"./image-resize-core.ts\";\n\ninterface ResizeImageWorkerRequest {\n\tinputBytes: Uint8Array;\n\tmimeType: string;\n\toptions?: ImageResizeOptions;\n}\n\nfunction isResizeImageWorkerRequest(value: unknown): value is ResizeImageWorkerRequest {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst record = value as Record<string, unknown>;\n\treturn record.inputBytes instanceof Uint8Array && typeof record.mimeType === \"string\";\n}\n\nconst port = parentPort;\nif (!port) {\n\tthrow new Error(\"image resize worker requires parentPort\");\n}\n\nport.once(\"message\", (message: unknown) => {\n\tvoid (async () => {\n\t\ttry {\n\t\t\tif (!isResizeImageWorkerRequest(message)) {\n\t\t\t\tthrow new Error(\"Invalid image resize worker request\");\n\t\t\t}\n\t\t\tconst result = await resizeImageInProcess(message.inputBytes, message.mimeType, message.options);\n\t\t\tconst response: ResizeImageWorkerResponse = { type: IMAGE_RESIZE_WORKER_RESPONSE_TYPE, result };\n\t\t\tport.postMessage(response);\n\t\t} catch (error) {\n\t\t\tconst response: ResizeImageWorkerResponse = {\n\t\t\t\ttype: IMAGE_RESIZE_WORKER_RESPONSE_TYPE,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t\tport.postMessage(response);\n\t\t}\n\t})();\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"image-resize.d.ts","sourceRoot":"","sources":["../../src/utils/image-resize.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAwB,MAAM,wBAAwB,CAAC;AAE1G,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AA0E/E;;;;;;GAMG;AACH,wBAAsB,WAAW,CAChC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqB9B;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAO5E"}
1
+ {"version":3,"file":"image-resize.d.ts","sourceRoot":"","sources":["../../src/utils/image-resize.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,kBAAkB,EACvB,KAAK,YAAY,EAGjB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAmE/E;;;;;;GAMG;AACH,wBAAsB,WAAW,CAChC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqB9B;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAO5E"}
@@ -1,12 +1,12 @@
1
1
  import { Worker } from "node:worker_threads";
2
- import { resizeImageInProcess } from "./image-resize-core.js";
2
+ import { IMAGE_RESIZE_WORKER_RESPONSE_TYPE, resizeImageInProcess, } from "./image-resize-core.js";
3
3
  function toTransferableBytes(input) {
4
4
  // Transfer detaches the buffer, so transfer a worker-owned copy and leave the
5
5
  // caller's bytes intact.
6
6
  return new Uint8Array(input);
7
7
  }
8
8
  function isResizeImageWorkerResponse(value) {
9
- return value !== null && typeof value === "object";
9
+ return (value !== null && typeof value === "object" && "type" in value && value.type === IMAGE_RESIZE_WORKER_RESPONSE_TYPE);
10
10
  }
11
11
  function createResizeWorker(workerSpecifier) {
12
12
  return new Worker(workerSpecifier);
@@ -29,16 +29,15 @@ async function resizeImageInWorker(workerSpecifier, inputBytes, mimeType, option
29
29
  settled = true;
30
30
  reject(error);
31
31
  };
32
- worker.once("message", (message) => {
33
- if (!isResizeImageWorkerResponse(message)) {
34
- fail(new Error("Invalid image resize worker response"));
32
+ worker.on("message", (message) => {
33
+ // Ignore messages Node posts on the worker channel itself, such as
34
+ // `{ "watch:require": [...] }` under `node --watch` (nodejs/node#65044).
35
+ if (!isResizeImageWorkerResponse(message))
35
36
  return;
36
- }
37
- if (message.error) {
37
+ if ("error" in message)
38
38
  fail(new Error(message.error));
39
- return;
40
- }
41
- settle(message.result ?? null);
39
+ else
40
+ settle(message.result);
42
41
  });
43
42
  worker.once("error", fail);
44
43
  worker.once("exit", (code) => {
@@ -1 +1 @@
1
- {"version":3,"file":"image-resize.js","sourceRoot":"","sources":["../../src/utils/image-resize.ts"],"names":[],"mappings":"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","sourcesContent":["import { Worker } from \"node:worker_threads\";\nimport { type ImageResizeOptions, type ResizedImage, resizeImageInProcess } from \"./image-resize-core.ts\";\n\nexport type { ImageResizeOptions, ResizedImage } from \"./image-resize-core.ts\";\n\ninterface ResizeImageWorkerResponse {\n\tresult?: ResizedImage | null;\n\terror?: string;\n}\n\nfunction toTransferableBytes(input: Uint8Array): Uint8Array<ArrayBuffer> {\n\t// Transfer detaches the buffer, so transfer a worker-owned copy and leave the\n\t// caller's bytes intact.\n\treturn new Uint8Array(input);\n}\n\nfunction isResizeImageWorkerResponse(value: unknown): value is ResizeImageWorkerResponse {\n\treturn value !== null && typeof value === \"object\";\n}\n\nfunction createResizeWorker(workerSpecifier: string | URL): Worker {\n\treturn new Worker(workerSpecifier);\n}\n\nasync function resizeImageInWorker(\n\tworkerSpecifier: string | URL,\n\tinputBytes: Uint8Array,\n\tmimeType: string,\n\toptions?: ImageResizeOptions,\n): Promise<ResizedImage | null> {\n\tconst worker = createResizeWorker(workerSpecifier);\n\ttry {\n\t\tconst inputBytesForWorker = toTransferableBytes(inputBytes);\n\t\treturn await new Promise<ResizedImage | null>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst settle = (result: ResizedImage | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolve(result);\n\t\t\t};\n\t\t\tconst fail = (error: Error): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\treject(error);\n\t\t\t};\n\n\t\t\tworker.once(\"message\", (message: unknown) => {\n\t\t\t\tif (!isResizeImageWorkerResponse(message)) {\n\t\t\t\t\tfail(new Error(\"Invalid image resize worker response\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (message.error) {\n\t\t\t\t\tfail(new Error(message.error));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettle(message.result ?? null);\n\t\t\t});\n\t\t\tworker.once(\"error\", fail);\n\t\t\tworker.once(\"exit\", (code) => {\n\t\t\t\tif (!settled) {\n\t\t\t\t\tfail(new Error(`Image resize worker exited with code ${code}`));\n\t\t\t\t}\n\t\t\t});\n\t\t\tworker.postMessage(\n\t\t\t\t{\n\t\t\t\t\tinputBytes: inputBytesForWorker,\n\t\t\t\t\tmimeType,\n\t\t\t\t\toptions,\n\t\t\t\t},\n\t\t\t\t[inputBytesForWorker.buffer],\n\t\t\t);\n\t\t});\n\t} finally {\n\t\tvoid worker.terminate().catch(() => undefined);\n\t}\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and encoded file size.\n * Runs Photon in a worker thread so WASM decoding, resizing, and encoding do not\n * block the TUI event loop. If the worker cannot be loaded (for example in some\n * Bun compiled executable layouts), fall back to in-process resizing so image\n * reads still work.\n */\nexport async function resizeImage(\n\tinputBytes: Uint8Array,\n\tmimeType: string,\n\toptions?: ImageResizeOptions,\n): Promise<ResizedImage | null> {\n\tconst isTypeScriptRuntime = import.meta.url.endsWith(\".ts\");\n\tconst workerUrl = new URL(\n\t\tisTypeScriptRuntime ? \"./image-resize-worker.ts\" : \"./image-resize-worker.js\",\n\t\timport.meta.url,\n\t);\n\n\t// Bun compiled executables resolve worker entrypoints by string path, not via\n\t// new URL(..., import.meta.url). Try the string path first under Bun so the\n\t// release binary uses the embedded worker instead of falling back in-process.\n\tif (typeof process.versions.bun === \"string\") {\n\t\ttry {\n\t\t\treturn await resizeImageInWorker(\"./src/utils/image-resize-worker.ts\", inputBytes, mimeType, options);\n\t\t} catch {}\n\t}\n\n\ttry {\n\t\treturn await resizeImageInWorker(workerUrl, inputBytes, mimeType, options);\n\t} catch {\n\t\treturn resizeImageInProcess(inputBytes, mimeType, options);\n\t}\n}\n\n/**\n * Format a dimension note for resized images.\n * This helps the model understand the coordinate mapping.\n */\nexport function formatDimensionNote(result: ResizedImage): string | undefined {\n\tif (!result.wasResized) {\n\t\treturn undefined;\n\t}\n\n\tconst scale = result.originalWidth / result.width;\n\treturn `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;\n}\n"]}
1
+ {"version":3,"file":"image-resize.js","sourceRoot":"","sources":["../../src/utils/image-resize.ts"],"names":[],"mappings":"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","sourcesContent":["import { Worker } from \"node:worker_threads\";\nimport {\n\tIMAGE_RESIZE_WORKER_RESPONSE_TYPE,\n\ttype ImageResizeOptions,\n\ttype ResizedImage,\n\ttype ResizeImageWorkerResponse,\n\tresizeImageInProcess,\n} from \"./image-resize-core.ts\";\n\nexport type { ImageResizeOptions, ResizedImage } from \"./image-resize-core.ts\";\n\nfunction toTransferableBytes(input: Uint8Array): Uint8Array<ArrayBuffer> {\n\t// Transfer detaches the buffer, so transfer a worker-owned copy and leave the\n\t// caller's bytes intact.\n\treturn new Uint8Array(input);\n}\n\nfunction isResizeImageWorkerResponse(value: unknown): value is ResizeImageWorkerResponse {\n\treturn (\n\t\tvalue !== null && typeof value === \"object\" && \"type\" in value && value.type === IMAGE_RESIZE_WORKER_RESPONSE_TYPE\n\t);\n}\n\nfunction createResizeWorker(workerSpecifier: string | URL): Worker {\n\treturn new Worker(workerSpecifier);\n}\n\nasync function resizeImageInWorker(\n\tworkerSpecifier: string | URL,\n\tinputBytes: Uint8Array,\n\tmimeType: string,\n\toptions?: ImageResizeOptions,\n): Promise<ResizedImage | null> {\n\tconst worker = createResizeWorker(workerSpecifier);\n\ttry {\n\t\tconst inputBytesForWorker = toTransferableBytes(inputBytes);\n\t\treturn await new Promise<ResizedImage | null>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst settle = (result: ResizedImage | null): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolve(result);\n\t\t\t};\n\t\t\tconst fail = (error: Error): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\treject(error);\n\t\t\t};\n\n\t\t\tworker.on(\"message\", (message: unknown) => {\n\t\t\t\t// Ignore messages Node posts on the worker channel itself, such as\n\t\t\t\t// `{ \"watch:require\": [...] }` under `node --watch` (nodejs/node#65044).\n\t\t\t\tif (!isResizeImageWorkerResponse(message)) return;\n\t\t\t\tif (\"error\" in message) fail(new Error(message.error));\n\t\t\t\telse settle(message.result);\n\t\t\t});\n\t\t\tworker.once(\"error\", fail);\n\t\t\tworker.once(\"exit\", (code) => {\n\t\t\t\tif (!settled) {\n\t\t\t\t\tfail(new Error(`Image resize worker exited with code ${code}`));\n\t\t\t\t}\n\t\t\t});\n\t\t\tworker.postMessage(\n\t\t\t\t{\n\t\t\t\t\tinputBytes: inputBytesForWorker,\n\t\t\t\t\tmimeType,\n\t\t\t\t\toptions,\n\t\t\t\t},\n\t\t\t\t[inputBytesForWorker.buffer],\n\t\t\t);\n\t\t});\n\t} finally {\n\t\tvoid worker.terminate().catch(() => undefined);\n\t}\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and encoded file size.\n * Runs Photon in a worker thread so WASM decoding, resizing, and encoding do not\n * block the TUI event loop. If the worker cannot be loaded (for example in some\n * Bun compiled executable layouts), fall back to in-process resizing so image\n * reads still work.\n */\nexport async function resizeImage(\n\tinputBytes: Uint8Array,\n\tmimeType: string,\n\toptions?: ImageResizeOptions,\n): Promise<ResizedImage | null> {\n\tconst isTypeScriptRuntime = import.meta.url.endsWith(\".ts\");\n\tconst workerUrl = new URL(\n\t\tisTypeScriptRuntime ? \"./image-resize-worker.ts\" : \"./image-resize-worker.js\",\n\t\timport.meta.url,\n\t);\n\n\t// Bun compiled executables resolve worker entrypoints by string path, not via\n\t// new URL(..., import.meta.url). Try the string path first under Bun so the\n\t// release binary uses the embedded worker instead of falling back in-process.\n\tif (typeof process.versions.bun === \"string\") {\n\t\ttry {\n\t\t\treturn await resizeImageInWorker(\"./src/utils/image-resize-worker.ts\", inputBytes, mimeType, options);\n\t\t} catch {}\n\t}\n\n\ttry {\n\t\treturn await resizeImageInWorker(workerUrl, inputBytes, mimeType, options);\n\t} catch {\n\t\treturn resizeImageInProcess(inputBytes, mimeType, options);\n\t}\n}\n\n/**\n * Format a dimension note for resized images.\n * This helps the model understand the coordinate mapping.\n */\nexport function formatDimensionNote(result: ResizedImage): string | undefined {\n\tif (!result.wasResized) {\n\t\treturn undefined;\n\t}\n\n\tconst scale = result.originalWidth / result.width;\n\treturn `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"syntax-highlight.d.ts","sourceRoot":"","sources":["../../src/utils/syntax-highlight.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAC1D,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB;AAoED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,cAAmB,GAAG,MAAM,CAoDtF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAQ9E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,gGAAgG;AAChG,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG"}
1
+ {"version":3,"file":"syntax-highlight.d.ts","sourceRoot":"","sources":["../../src/utils/syntax-highlight.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAC1D,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB;AAoED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,cAAmB,GAAG,MAAM,CAyDtF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAQ9E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,gGAAgG;AAChG,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG"}
@@ -65,7 +65,12 @@ export function renderHighlightedHtml(html, theme = {}) {
65
65
  return;
66
66
  }
67
67
  const formatter = getActiveFormatter(scopes, theme);
68
- output += formatter ? formatter(textBuffer) : textBuffer;
68
+ output += formatter
69
+ ? textBuffer
70
+ .split("\n")
71
+ .map((line) => (line ? formatter(line) : line))
72
+ .join("\n")
73
+ : textBuffer;
69
74
  textBuffer = "";
70
75
  };
71
76
  let index = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"syntax-highlight.js","sourceRoot":"","sources":["../../src/utils/syntax-highlight.ts"],"names":[],"mappings":"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","sourcesContent":["import hljs from \"highlight.js/lib/index.js\";\nimport { decodeHtmlEntityAt } from \"./html.ts\";\n\nexport type HighlightFormatter = (text: string) => string;\nexport type HighlightTheme = Partial<Record<string, HighlightFormatter>>;\n\nexport interface HighlightOptions {\n\tlanguage?: string;\n\tignoreIllegals?: boolean;\n\tlanguageSubset?: string[];\n\ttheme?: HighlightTheme;\n}\n\nconst SPAN_CLOSE = \"</span>\";\nconst HIGHLIGHT_CLASS_PREFIX = \"hljs-\";\n\nfunction getScopeFromSpanTag(tag: string): string | undefined {\n\tconst match = /\\sclass\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/.exec(tag);\n\tconst classValue = match?.[1] ?? match?.[2];\n\tif (!classValue) {\n\t\treturn undefined;\n\t}\n\n\tfor (const className of classValue.split(/\\s+/)) {\n\t\tif (className.startsWith(HIGHLIGHT_CLASS_PREFIX)) {\n\t\t\treturn className.slice(HIGHLIGHT_CLASS_PREFIX.length);\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getScopeFormatter(scope: string, theme: HighlightTheme): HighlightFormatter | undefined {\n\tconst exact = theme[scope];\n\tif (exact) {\n\t\treturn exact;\n\t}\n\n\tconst dotIndex = scope.indexOf(\".\");\n\tif (dotIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dotIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\tconst dashIndex = scope.indexOf(\"-\");\n\tif (dashIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dashIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getActiveFormatter(scopes: Array<string | undefined>, theme: HighlightTheme): HighlightFormatter | undefined {\n\tfor (let i = scopes.length - 1; i >= 0; i--) {\n\t\tconst scope = scopes[i];\n\t\tif (!scope) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst formatter = getScopeFormatter(scope, theme);\n\t\tif (formatter) {\n\t\t\treturn formatter;\n\t\t}\n\t}\n\treturn theme.default;\n}\n\nfunction isSpanOpenTagStart(html: string, index: number): boolean {\n\tif (!html.startsWith(\"<span\", index)) {\n\t\treturn false;\n\t}\n\tconst nextChar = html[index + \"<span\".length];\n\treturn nextChar === \">\" || nextChar === \" \" || nextChar === \"\\t\" || nextChar === \"\\n\" || nextChar === \"\\r\";\n}\n\nexport function renderHighlightedHtml(html: string, theme: HighlightTheme = {}): string {\n\tlet output = \"\";\n\tlet textBuffer = \"\";\n\tconst scopes: Array<string | undefined> = [];\n\n\tconst flushText = () => {\n\t\tif (!textBuffer) {\n\t\t\treturn;\n\t\t}\n\t\tconst formatter = getActiveFormatter(scopes, theme);\n\t\toutput += formatter ? formatter(textBuffer) : textBuffer;\n\t\ttextBuffer = \"\";\n\t};\n\n\tlet index = 0;\n\twhile (index < html.length) {\n\t\tif (isSpanOpenTagStart(html, index)) {\n\t\t\tconst tagEndIndex = html.indexOf(\">\", index + 5);\n\t\t\tif (tagEndIndex !== -1) {\n\t\t\t\tflushText();\n\t\t\t\tconst tag = html.slice(index, tagEndIndex + 1);\n\t\t\t\tconst scope = getScopeFromSpanTag(tag);\n\t\t\t\tscopes.push(scope);\n\t\t\t\tindex = tagEndIndex + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (html.startsWith(SPAN_CLOSE, index)) {\n\t\t\tflushText();\n\t\t\tif (scopes.length > 0) {\n\t\t\t\tscopes.pop();\n\t\t\t}\n\t\t\tindex += SPAN_CLOSE.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (html[index] === \"&\") {\n\t\t\tconst decoded = decodeHtmlEntityAt(html, index);\n\t\t\tif (decoded) {\n\t\t\t\ttextBuffer += decoded.text;\n\t\t\t\tindex += decoded.length;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\ttextBuffer += html[index];\n\t\tindex++;\n\t}\n\n\tflushText();\n\treturn output;\n}\n\nexport function highlight(code: string, options: HighlightOptions = {}): string {\n\tconst html = options.language\n\t\t? hljs.highlight(code, {\n\t\t\t\tlanguage: options.language,\n\t\t\t\tignoreIllegals: options.ignoreIllegals,\n\t\t\t}).value\n\t\t: hljs.highlightAuto(code, options.languageSubset).value;\n\treturn renderHighlightedHtml(html, options.theme);\n}\n\nexport function supportsLanguage(name: string): boolean {\n\treturn hljs.getLanguage(name) !== undefined;\n}\n\n/** Every grammar loads statically with this module; kept so upstream callers stay unchanged. */\nexport async function loadAllHighlightLanguages(): Promise<void> {}\n"]}
1
+ {"version":3,"file":"syntax-highlight.js","sourceRoot":"","sources":["../../src/utils/syntax-highlight.ts"],"names":[],"mappings":"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","sourcesContent":["import hljs from \"highlight.js/lib/index.js\";\nimport { decodeHtmlEntityAt } from \"./html.ts\";\n\nexport type HighlightFormatter = (text: string) => string;\nexport type HighlightTheme = Partial<Record<string, HighlightFormatter>>;\n\nexport interface HighlightOptions {\n\tlanguage?: string;\n\tignoreIllegals?: boolean;\n\tlanguageSubset?: string[];\n\ttheme?: HighlightTheme;\n}\n\nconst SPAN_CLOSE = \"</span>\";\nconst HIGHLIGHT_CLASS_PREFIX = \"hljs-\";\n\nfunction getScopeFromSpanTag(tag: string): string | undefined {\n\tconst match = /\\sclass\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/.exec(tag);\n\tconst classValue = match?.[1] ?? match?.[2];\n\tif (!classValue) {\n\t\treturn undefined;\n\t}\n\n\tfor (const className of classValue.split(/\\s+/)) {\n\t\tif (className.startsWith(HIGHLIGHT_CLASS_PREFIX)) {\n\t\t\treturn className.slice(HIGHLIGHT_CLASS_PREFIX.length);\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getScopeFormatter(scope: string, theme: HighlightTheme): HighlightFormatter | undefined {\n\tconst exact = theme[scope];\n\tif (exact) {\n\t\treturn exact;\n\t}\n\n\tconst dotIndex = scope.indexOf(\".\");\n\tif (dotIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dotIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\tconst dashIndex = scope.indexOf(\"-\");\n\tif (dashIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dashIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getActiveFormatter(scopes: Array<string | undefined>, theme: HighlightTheme): HighlightFormatter | undefined {\n\tfor (let i = scopes.length - 1; i >= 0; i--) {\n\t\tconst scope = scopes[i];\n\t\tif (!scope) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst formatter = getScopeFormatter(scope, theme);\n\t\tif (formatter) {\n\t\t\treturn formatter;\n\t\t}\n\t}\n\treturn theme.default;\n}\n\nfunction isSpanOpenTagStart(html: string, index: number): boolean {\n\tif (!html.startsWith(\"<span\", index)) {\n\t\treturn false;\n\t}\n\tconst nextChar = html[index + \"<span\".length];\n\treturn nextChar === \">\" || nextChar === \" \" || nextChar === \"\\t\" || nextChar === \"\\n\" || nextChar === \"\\r\";\n}\n\nexport function renderHighlightedHtml(html: string, theme: HighlightTheme = {}): string {\n\tlet output = \"\";\n\tlet textBuffer = \"\";\n\tconst scopes: Array<string | undefined> = [];\n\n\tconst flushText = () => {\n\t\tif (!textBuffer) {\n\t\t\treturn;\n\t\t}\n\t\tconst formatter = getActiveFormatter(scopes, theme);\n\t\toutput += formatter\n\t\t\t? textBuffer\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.map((line) => (line ? formatter(line) : line))\n\t\t\t\t\t.join(\"\\n\")\n\t\t\t: textBuffer;\n\t\ttextBuffer = \"\";\n\t};\n\n\tlet index = 0;\n\twhile (index < html.length) {\n\t\tif (isSpanOpenTagStart(html, index)) {\n\t\t\tconst tagEndIndex = html.indexOf(\">\", index + 5);\n\t\t\tif (tagEndIndex !== -1) {\n\t\t\t\tflushText();\n\t\t\t\tconst tag = html.slice(index, tagEndIndex + 1);\n\t\t\t\tconst scope = getScopeFromSpanTag(tag);\n\t\t\t\tscopes.push(scope);\n\t\t\t\tindex = tagEndIndex + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (html.startsWith(SPAN_CLOSE, index)) {\n\t\t\tflushText();\n\t\t\tif (scopes.length > 0) {\n\t\t\t\tscopes.pop();\n\t\t\t}\n\t\t\tindex += SPAN_CLOSE.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (html[index] === \"&\") {\n\t\t\tconst decoded = decodeHtmlEntityAt(html, index);\n\t\t\tif (decoded) {\n\t\t\t\ttextBuffer += decoded.text;\n\t\t\t\tindex += decoded.length;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\ttextBuffer += html[index];\n\t\tindex++;\n\t}\n\n\tflushText();\n\treturn output;\n}\n\nexport function highlight(code: string, options: HighlightOptions = {}): string {\n\tconst html = options.language\n\t\t? hljs.highlight(code, {\n\t\t\t\tlanguage: options.language,\n\t\t\t\tignoreIllegals: options.ignoreIllegals,\n\t\t\t}).value\n\t\t: hljs.highlightAuto(code, options.languageSubset).value;\n\treturn renderHighlightedHtml(html, options.theme);\n}\n\nexport function supportsLanguage(name: string): boolean {\n\treturn hljs.getLanguage(name) !== undefined;\n}\n\n/** Every grammar loads statically with this module; kept so upstream callers stay unchanged. */\nexport async function loadAllHighlightLanguages(): Promise<void> {}\n"]}
@@ -24,7 +24,7 @@ The following extensions are additions maintained by `@astralyn/pi`:
24
24
  | [Question](extensions/question.md) | `question` | Ask structured questions through native interactive UI |
25
25
  | [Provider](extensions/provider.md) | `/provider` | Edit models.json providers: connection, API type, and models |
26
26
  | [Statusline](extensions/statusline.md) | Footer status | Show concise extension-managed activity state |
27
- | [Web Search](extensions/web-search.md) | `web_search` | Search the live web through MiniMax and DeepSeek with fused results |
27
+ | [Search](extensions/search.md) | `code_search`, `web_search` | Devin-driven semantic code search and web search with one shared key |
28
28
 
29
29
  All bundled extensions use the public Extension API. Enable or disable them in `pi config` or through `builtin:<name>` entries in [settings](../settings.md#codemode-and-tool-selection). `--no-extensions` disables them; `-e builtin:<name>` loads one explicitly.
30
30
 
@@ -1,4 +1,4 @@
1
- # provider — visual models.json editor
1
+ # Provider
2
2
 
3
3
  Minimalist visual editor for the `providers` record of `models.json` (`~/.pi/agent/models.json`; respects `PI_CODING_AGENT_DIR`). The extension never registers runtime providers and stores no parallel configuration; Pi's native [models.json](../../models.md) mechanism remains the source of truth. See [Providers](../../providers.md) for credential setup and runtime resolution.
4
4
 
@@ -1,4 +1,4 @@
1
- # question — structured user questions
1
+ # Question
2
2
 
3
3
  Adds a `question` tool for asking one to four multiple-choice questions when the agent needs a user decision. A Pi-native take on AskUserQuestion: a lightweight custom dialog with concise transcript summaries and a bounded model-facing result.
4
4
 
@@ -0,0 +1,50 @@
1
+ # Search
2
+
3
+ Devin Search adds two tools that share one Devin sign-in:
4
+
5
+ | Tool | What it does |
6
+ |---|---|
7
+ | `code_search` | Finds where behavior lives in the local repository from a plain-language description |
8
+ | `web_search` | Searches the live web and returns page excerpts |
9
+
10
+ In the transcript, each call is one line: the query, then live progress while it runs and the number of files or results once it finishes. Expand it (`Ctrl+O` or a click) to see the files with their line ranges, or the result titles with their sites; titles open the page. A failed call always shows its error.
11
+
12
+ ## Signing in
13
+
14
+ Run `/search` to open the Devin Search panel. It shows whether you are signed in and offers:
15
+
16
+ - **Sign in with browser** opens `app.devin.ai` (the panel also shows the link, and the copy key copies it). Approve access, then paste the code Devin shows into the panel. Pasting a session token there works too.
17
+ - **Paste a token** saves an existing Devin session token.
18
+ - **Sign out** removes the saved token after a confirmation.
19
+
20
+ The panel checks every new token with Devin before saving it, so a rejected token never replaces a working one. The token is saved, exactly as Devin issued it, to `~/.pi/agent/search-auth.json`.
21
+
22
+ For headless and CI runs, set `SEARCH_KEY` to a Devin session token. A saved token takes precedence over it.
23
+
24
+ Without a credential, a session starts with both tools turned off. Signing in turns them on; turning one off with `/tools` is respected.
25
+
26
+ ## code_search
27
+
28
+ `code_search` sends your query and a directory tree of the working directory (up to three levels, without dependency, build, and `.gitignore`d directories) to Devin's SWE-grep model. The model plans up to three rounds of up to eight read-only commands, `rg`, `readfile`, and `tree`, which run on your machine, and then answers with files and line ranges. The result is a reading list to verify, not evidence.
29
+
30
+ What leaves your machine: the query, the directory tree, and the output of the commands the model runs, including the file contents it reads.
31
+
32
+ Enforced locally, not by Devin:
33
+
34
+ - **Confinement.** Every path the model names must resolve inside the searched directory, both as written and after following symlinks.
35
+ - **Read-only.** The three commands are the only operations; there is no shell and no write.
36
+ - **Bounded output.** Each command result is capped at 40 lines of 200 characters, and each request times out after 30 seconds.
37
+
38
+ | Parameter | Type | Default | Description |
39
+ |---|---|---|---|
40
+ | `query` | string | required | The behavior, flow, error, or concept to locate, in concise English. Keep identifiers and error text verbatim |
41
+ | `path` | string | working directory | Subdirectory to search; must be inside the working directory |
42
+
43
+ ## web_search
44
+
45
+ `web_search` returns up to 10 results, each with a title, URL, and the parts of the page that match the query (shortened to 1,500 characters).
46
+
47
+ | Parameter | Type | Default | Description |
48
+ |---|---|---|---|
49
+ | `query` | string | required | Search query. Be specific: product names, versions, exact error text |
50
+ | `max_results` | int 1–10 | 5 | Results to return |
@@ -1,9 +1,6 @@
1
- # statusline — fixed two-line footer
1
+ # Statusline
2
2
 
3
- Replaces Pi's built-in footer with a fixed, color-coded two-line status display
4
- using a balanced left/right layout. It registers no tool or command and has no
5
- configuration file. The footer auto-enables on `session_start` in TUI mode and
6
- clears itself on `session_shutdown`.
3
+ Replaces Pi's built-in footer with a fixed, color-coded two-line status display using a balanced left/right layout. It registers no tool or command and has no configuration file. The footer auto-enables on `session_start` in TUI mode and clears itself on `session_shutdown`.
7
4
 
8
5
  ## What it shows
9
6
 
@@ -17,10 +14,8 @@ CTX 2.1%/1.0M ↑13k ↓13k R440k CH99.4% $0.074
17
14
  **Left**
18
15
 
19
16
  - **Model:** human-readable model name, falling back to the model id.
20
- - **Provider:** the model source in parentheses, such as `(deepseek)` or
21
- `(opencode-go)`.
22
- - **Effort:** the current runtime thinking level when the model supports reasoning and
23
- the level is not `off`.
17
+ - **Provider:** the model source in parentheses, such as `(deepseek)` or `(opencode-go)`.
18
+ - **Effort:** the current runtime thinking level when the model supports reasoning and the level is not `off`.
24
19
 
25
20
  **Right**
26
21
 
@@ -31,8 +26,7 @@ CTX 2.1%/1.0M ↑13k ↓13k R440k CH99.4% $0.074
31
26
 
32
27
  **Left**
33
28
 
34
- - **Context:** used percentage plus context-window size. The percentage uses
35
- fixed semantic tiers: accent normally, warning above 40%, error above 80%.
29
+ - **Context:** used percentage plus context-window size. The percentage uses fixed semantic tiers: accent normally, warning above 40%, error above 80%.
36
30
 
37
31
  **Right**
38
32
 
@@ -41,20 +35,12 @@ CTX 2.1%/1.0M ↑13k ↓13k R440k CH99.4% $0.074
41
35
  - `R` cumulative cache-read tokens.
42
36
  - `W` cumulative cache-write tokens, omitted when zero.
43
37
  - Cumulative totals include assistant requests, usage-bearing tool results, compaction summaries, branch summaries, and independently settled Background usage records on the active branch.
44
- - `CH` cache-hit percentage for the latest assistant request, calculated as
45
- `cacheRead / (input + cacheRead + cacheWrite)` and shown only when that
46
- request used cache.
47
- - `$` cumulative cost; `(sub)` is appended when the current model uses an OAuth
48
- subscription.
38
+ - `CH` cache-hit percentage for the latest assistant request, calculated as `cacheRead / (input + cacheRead + cacheWrite)` and shown only when that request used cache.
39
+ - `$` cumulative cost; `(sub)` is appended when the current model uses an OAuth subscription.
49
40
 
50
41
  **Middle (optional)**
51
42
 
52
- - Extension status text from `ctx.ui.setStatus()` is sorted by key and pinned
53
- to the center of the line — anchored to the absolute line center so it does
54
- not drift as the usage cluster grows, falling back to balancing the free
55
- space when the side zones collide with the center anchor. When space runs
56
- out it is dropped so CTX and usage stay readable. Plain text is muted;
57
- strings that already carry ANSI color are left unchanged.
43
+ - Extension status text from `ctx.ui.setStatus()` is sorted by key and pinned to the center of the line — anchored to the absolute line center so it does not drift as the usage cluster grows, falling back to balancing the free space when the side zones collide with the center anchor. When space runs out it is dropped so CTX and usage stay readable. Plain text is muted; strings that already carry ANSI color are left unchanged.
58
44
 
59
45
  Background billing uses the first valid `task-usage` record per execution ID; managed foreground results omit duplicate `usage`. Opening `/tasks`, detaching, or repeating `tasks read`/`wait` does not add billing. Accrued worker retry, cancellation, failure, and provider-supplied compaction usage is included when settled; missing usage is not fabricated. Quarantined late settlements are excluded from active totals. This does not change the footer's active-branch scope (session-wide statistics can include other branches), parent CTX estimate, or latest-parent-request `CH`. See the [persisted record format](../../session-format.md#background-records).
60
46
 
@@ -74,14 +60,11 @@ When the terminal is too narrow for the full layout:
74
60
  4. Model/effort with the short path and git branch, then the short path alone.
75
61
  5. Model/effort alone.
76
62
 
77
- The short path is `~/basename` (or bare basename). Shortening the path or dropping
78
- the provider can make room for the branch again. If none of these pairs fits,
79
- the model/effort and short path are truncated.
63
+ The short path is `~/basename` (or bare basename). Shortening the path or dropping the provider can make room for the branch again. If none of these pairs fits, the model/effort and short path are truncated.
80
64
 
81
65
  **Line 2** (first drop → last):
82
66
 
83
- 1. Keep the fullest usage cluster that fits beside CTX, trying full usage,
84
- then without `W`, then without `W` and `R`.
67
+ 1. Keep the fullest usage cluster that fits beside CTX, trying full usage, then without `W`, then without `W` and `R`.
85
68
  2. Include centered extension status only if the selected pair leaves enough room.
86
69
  3. If no usage variant fits, omit status and truncate CTX with the full usage cluster.
87
70
 
@@ -94,17 +77,13 @@ Status therefore yields to usage; it never causes an extra usage field to be dro
94
77
  - Context uses `accent`, `warning`, or `error` according to the fixed thresholds.
95
78
  - Working directory uses `success`; Git branch uses `accent`.
96
79
  - Usage statistics use `dim`; extension status text uses `muted`.
97
- - Each line left-aligns its primary cluster and right-aligns the secondary
98
- cluster, filling the gap with spaces so wide terminals stay balanced.
80
+ - Each line left-aligns its primary cluster and right-aligns the secondary cluster, filling the gap with spaces so wide terminals stay balanced.
99
81
 
100
- Pi does not expose auto-compaction state to extension footer factories, so the
101
- native `(auto)` marker is intentionally not reproduced.
82
+ Pi does not expose auto-compaction state to extension footer factories, so the native `(auto)` marker is intentionally not reproduced.
102
83
 
103
84
  ## Migration
104
85
 
105
- The previous `/statusline` command and `~/.pi/agent/pi-config/statusline.json`
106
- configuration are no longer used. An existing config file is left untouched and
107
- may be removed manually.
86
+ The previous `/statusline` command and `~/.pi/agent/pi-config/statusline.json` configuration are no longer used. An existing config file is left untouched and may be removed manually.
108
87
 
109
88
  ## Limits
110
89
 
@@ -113,11 +92,6 @@ may be removed manually.
113
92
 
114
93
  ## Implementation notes
115
94
 
116
- Footer paint runs on every TUI render. Each footer instance keeps one cache for
117
- active-branch usage totals and the latest assistant cache-hit percentage, keyed
118
- by session manager, session id, and leaf id. It clears on footer `invalidate()`.
119
- Session entries are append-only: finalized messages and usage records advance
120
- the leaf, so accounting does not need to inspect streaming message mutations.
95
+ Footer paint runs on every TUI render. Each footer instance keeps one cache for active-branch usage totals and the latest assistant cache-hit percentage, keyed by session manager, session id, and leaf id. It clears on footer `invalidate()`. Session entries are append-only: finalized messages and usage records advance the leaf, so accounting does not need to inspect streaming message mutations.
121
96
 
122
- Thinking level and context usage come from the live Extension API. Rendering
123
- receives plain display data and owns no session state or subscriptions.
97
+ Thinking level and context usage come from the live Extension API. Rendering receives plain display data and owns no session state or subscriptions.
package/docs/codemode.md CHANGED
@@ -15,7 +15,7 @@ A script may start with an options line:
15
15
  - `max_output_tokens` (default 10000) limits the output. Longer output keeps its start and end, and the full text is written to a temp file whose path is included in the result. A script fails when its output passes 16777216 characters of text and base64 image data or 100000 `text()`, `image()`, and `console` calls; write large data to a file with a tool instead.
16
16
  - `timeout_ms` is a hard deadline for the whole script. It is unset by default. Image generation can take minutes, so do not set a short deadline for scripts that generate images.
17
17
 
18
- The result starts with `Script completed` or `Script failed`, the wall time, and the output. A failed script keeps its partial output, followed by `Script error:` and the error. Tool calls are real: calls made before a failure are not undone. Calls still running when the script ends are cancelled, and unawaited promises are discarded.
18
+ The result starts with `Script completed` or `Script failed`, the wall time, and the output. Text and image items appear in order, each on its own line. When the output has more than one text item (from `text()` or `return`), each starts with a `==> text N/M <==` line. `console` calls follow in one `<console_output>` block with one line per call. A failed script keeps its partial output, followed by `Script error:` and the error. Tool calls are real: calls made before a failure are not undone. Calls still running when the script ends are cancelled, and unawaited promises are discarded.
19
19
 
20
20
  ## Globals
21
21
 
@@ -24,7 +24,7 @@ The result starts with `Script completed` or `Script failed`, the wall time, and
24
24
  | `tools.<name>(args)` | Call a tool. See [Call tools](#call-tools). |
25
25
  | `text(value)` | Add a text item to the output. Strings are added as is, other values as JSON. |
26
26
  | `image(value)` | Add an image to the output: a base64 `data:` URL, an `{ image_url }` object, or an image block `{ type: "image", data, mimeType }` such as those returned by MCP tools and `models.generateImages()`. Remote URLs are not supported. PNG, JPEG, GIF, and WebP are accepted. Each image is also saved to a temp file, and the result names the path before the image. |
27
- | `console.log(...)` | Like `text()`; `info`, `warn`, `error`, and `debug` do the same. |
27
+ | `console.log(...)` | Add a line to the `<console_output>` block after the other output. Arguments are joined with spaces; `info`, `warn`, `error`, and `debug` do the same. |
28
28
  | `return value` | A top-level `return` adds the value like `text()`. |
29
29
  | `exit()` | End the script successfully. |
30
30
  | `store(key, value)` / `load(key)` | Keep small JSON values across `codemode` calls. See [Store values](#store-values). |
@@ -42,13 +42,14 @@ What a call resolves to depends on the tool:
42
42
 
43
43
  - Tools with an output schema resolve to a structured value. `bash` resolves to `{ output, truncated, full_output_path?, exit_code, wall_time_seconds }`, also for non-zero exit codes. Its `output` is not limited to the 2000 lines or 50KB the model sees: it holds up to 1 MiB, and longer output keeps its first and last 512 KiB around an omission marker, with `truncated` set and the full output in `full_output_path`. A command moved to the background resolves to `{ task_id, status }` instead; see [Tasks](bundled/tasks.md).
44
44
  - MCP tools resolve to their `CallToolResult`, including `isError` and `structuredContent`.
45
- - Other tools, such as `read`, `edit`, and `write`, resolve to their text output.
45
+ - `read` resolves to the file's text, or for an image to an image block `{ type: "image", data, mimeType, note }` that `image()` shows. `data` is the base64 image the model would see and `note` the text that goes with it, such as resize hints.
46
+ - Other tools, such as `edit` and `write`, resolve to their text output.
46
47
 
47
48
  A call that fails, is blocked, or gets invalid arguments rejects with an `Error` that carries the tool's error text. Use `Promise.allSettled()` to keep the results of the calls that succeed.
48
49
 
49
50
  The `codemode` description lists tools with their TypeScript declarations, grouped by namespace (for example one MCP server). Tools with `deferred` exposure, which includes MCP tools with the default `codemode` exposure, are not listed, so the description stays the same while MCP servers connect. Listed declarations share a budget of 3000 estimated tokens (`codemode.inlineBudget` in [settings](settings.md#tools)). Scripts find the other tools with `searchTools()`, `describeTool()`, `describeNamespace()`, or by filtering `ALL_TOOLS`.
50
51
 
51
- While `codemode` is active, `codemode.mode` in [settings](settings.md#tools) decides how the other tools are presented. With `on` (default) declared tools stay declared, and their descriptions say how to call them from scripts. With `only` they are hidden from the model and listed in the `codemode` description instead, so the model calls them through scripts.
52
+ While `codemode` is active, `codemode.mode` in [settings](settings.md#tools) decides how the other tools are presented. With `on` (default) declared tools stay declared, and their descriptions say how to call them from scripts. With `only` they are hidden from the model and listed in the `codemode` description instead, so the model calls them through scripts. Tool declarations in the `codemode` description, `describeTool()`, and `ALL_TOOLS` carry the tools' prompt guidelines, since the system prompt rules only cover declared tools.
52
53
 
53
54
  ## Store values
54
55
 
@@ -58,7 +59,7 @@ The store is for small state such as IDs, cursors, or summaries. One value may h
58
59
 
59
60
  ## Models
60
61
 
61
- `models` reaches the model catalog and runs non-LLM models with the session's credentials: classifiers, which answer typed questions about JSON state, and image models, which generate images. Chat models are listed but cannot be run from scripts. Which classifier and image models exist is described in [Use classifier models](models.md#use-classifier-models) and [Use image models](models.md#use-image-models).
62
+ `models` reaches the model catalog and runs non-LLM models with the session's credentials: classifiers, which answer typed questions about JSON state and, for some models, images, and image models, which generate images. Chat models are listed but cannot be run from scripts. Which classifier and image models exist is described in [Use classifier models](models.md#use-classifier-models) and [Use image models](models.md#use-image-models).
62
63
 
63
64
  ```ts
64
65
  type ModelType = "chat" | "image" | "classifier";
@@ -99,6 +100,8 @@ Model IDs differ between providers, for example `typesafe/jev-latest` and `openr
99
100
  interface ClassifierContext {
100
101
  /** The data to classify. */
101
102
  state: Record<string, unknown>;
103
+ /** Images judged together with `state`. Only models whose `input` includes "image" accept them. */
104
+ images?: { type: "image"; data: string; mimeType: string }[];
102
105
  /** Questions by ID. One call answers all of them. */
103
106
  questions: Record<string, ClassifierQuestion>;
104
107
  }
@@ -162,6 +165,24 @@ return results.map((result, i) =>
162
165
  );
163
166
  ```
164
167
 
168
+ Classifiers whose `input` includes `"image"` also judge images. `tools.read()` returns an image file as an image block that `images` accepts. Other classifiers return an error result when `images` is not empty.
169
+
170
+ ```js
171
+ const luna = await models.getModelOfType("classifier", "openai", "gpt-6-luna");
172
+ const photo = await tools.read({ path: "screenshot.png" });
173
+ const result = await models.classify(luna, {
174
+ state: { task: "Settings page redesign" },
175
+ images: [photo],
176
+ questions: {
177
+ broken: {
178
+ type: "bool",
179
+ instructions: "Does the screenshot show a broken layout?",
180
+ criteria: { true: "Overlapping, cut-off, or misaligned elements", false: "Clean layout" },
181
+ },
182
+ },
183
+ });
184
+ ```
185
+
165
186
  ### Generate images
166
187
 
167
188
  ```ts
package/docs/docs.json CHANGED
@@ -80,10 +80,6 @@
80
80
  "title": "Statusline",
81
81
  "path": "bundled/extensions/statusline.md"
82
82
  },
83
- {
84
- "title": "Web Search",
85
- "path": "bundled/extensions/web-search.md"
86
- },
87
83
  {
88
84
  "title": "Bundled themes",
89
85
  "path": "bundled/themes.md"
@@ -89,6 +89,7 @@ These variables are read by Pi itself:
89
89
  | `PI_RADIUS_GATEWAY` | Override the Radius gateway origin used by `/bug` uploads and Radius relay connections |
90
90
  | `PI_HARDWARE_CURSOR` | Set to `1` to show the hardware cursor; see [Terminal setup](terminal-setup.md) |
91
91
  | `PI_HYPERLINKS` | Override OSC 8 hyperlink detection with `1`, `0`, or `auto` |
92
+ | `PI_PROGRAM_STATUS` | Override OSC 7501 program status detection: `1` always reports, `0` never reports; otherwise Pi reports only after the terminal confirms support. See [Terminal setup](terminal-setup.md#program-status) |
92
93
  | `PI_IMAGE_PROTOCOL` | Override inline image detection with `kitty`, `iterm2`, `none`, or `auto` |
93
94
  | `PI_TRUE_COLOR` | Override truecolor detection with `1`, `0`, or `auto` |
94
95
  | `PI_TUI_ESC_TIMEOUT` | How long to wait after a lone ESC before treating it as Escape, in milliseconds; defaults to `100` over SSH and `10` otherwise. Increase it if Alt-key input is misread as Escape |
@@ -610,7 +610,8 @@ pi.on("agent_before_settle", async (event, ctx) => {
610
610
  };
611
611
  });
612
612
 
613
- pi.on("agent_settled", async (_event, ctx) => {
613
+ pi.on("agent_settled", async (event, ctx) => {
614
+ // event.aborted is true when the run ended because it was aborted, for example with Escape.
614
615
  // ctx.isIdle() is true; runs requested here start after all settled handlers finish.
615
616
  });
616
617
  ```
@@ -731,6 +732,7 @@ pi.on("tool_execution_update", async (event, ctx) => {
731
732
 
732
733
  pi.on("tool_execution_end", async (event, ctx) => {
733
734
  // event.toolCallId, event.toolName, event.result, event.isError
735
+ // event.durationMs - execute() time in ms (monotonic); absent when the tool did not run
734
736
  });
735
737
  ```
736
738
 
@@ -2473,6 +2475,8 @@ pi.registerTool({
2473
2475
  - `lastComponent` - the previously returned component for that slot, if any
2474
2476
  - `invalidate()` - request a rerender of this tool row
2475
2477
  - `toolCallId`, `cwd`, `executionStarted`, `argsComplete`, `isPartial`, `expanded`, `showImages`, `isError`
2478
+ - `durationMs` - how long the tool's execution took, from the final result; `undefined` while it runs, when it did not run, or for results stored before durations were recorded
2479
+ - `outputPad` - horizontal padding from the `outputPad` setting; renderers with `renderShell: "self"` apply it themselves
2476
2480
  - `result` - the current partial or final result, if one has arrived
2477
2481
 
2478
2482
  Use `context.state` for cross-slot shared state. Keep slot-local caches on the returned component instance when you want to reuse and mutate the same component across renders.
@@ -3234,7 +3238,7 @@ pi.on("tool_call", async (event, ctx) => {
3234
3238
  });
3235
3239
  ```
3236
3240
 
3237
- A tool that orchestrates other tools can adjust what the model sees while it is active with `prepareLoadout(loadout)`. It runs whenever the active tools change and receives the declared tools, the callable tools, and every registered tool with its exposure and namespace. It returns replacement `descriptions` for declared tools (including its own) and `hiddenDeclarations`: active tools whose declarations requests leave out while they stay active and callable. `codemode` uses only this hook, `exposure`, and `ctx.executeTool()`, so another tool can implement the same behavior under a different name.
3241
+ A tool that orchestrates other tools can adjust what the model sees while it is active with `prepareLoadout(loadout)`. It runs whenever the active tools change and receives the declared tools, the callable tools, and every registered tool with its exposure, namespace, and prompt guidelines. It returns replacement `descriptions` for declared tools (including its own) and `hiddenDeclarations`: active tools whose declarations requests leave out while they stay active and callable. The default system prompt leaves hidden tools out of its tool list and rules, and names no tool in the skills hint when the file reader is hidden, so the orchestrating tool should present their guidelines itself. `codemode` uses only this hook, `exposure`, and `ctx.executeTool()`, so another tool can implement the same behavior under a different name.
3238
3242
 
3239
3243
  ### Tool renderer resolution
3240
3244
 
package/docs/json.md CHANGED
@@ -32,7 +32,7 @@ Session events are defined by `AgentSessionEvent`:
32
32
  type AgentSessionEvent =
33
33
  | Exclude<AgentEvent, { type: "agent_end" }>
34
34
  | { type: "agent_end"; messages: AgentMessage[]; willRetry: boolean }
35
- | { type: "agent_settled" }
35
+ | { type: "agent_settled"; aborted: boolean }
36
36
  | { type: "queue_update"; steering: readonly string[]; followUp: readonly string[] }
37
37
  | { type: "compaction_start"; reason: "manual" | "threshold" | "overflow" }
38
38
  | { type: "entry_appended"; entry: SessionEntry }
@@ -68,7 +68,7 @@ type AgentEvent =
68
68
  // Tool execution
69
69
  | { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any }
70
70
  | { type: "tool_execution_update"; toolCallId: string; toolName: string; args: any; partialResult: any }
71
- | { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError: boolean };
71
+ | { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError: boolean; durationMs?: number };
72
72
  ```
73
73
 
74
74
  ## Message Types
package/docs/llama-cpp.md CHANGED
@@ -102,7 +102,14 @@ curl http://127.0.0.1:8080/models
102
102
 
103
103
  ## Classification
104
104
 
105
- Every model listed for chat is also listed as a classifier model with the same ID and the `llama-cpp-classify` API. Classifier models answer typed `choice`, `bool`, and `score` questions about JSON state, like TypeSafe's Jev models. The model reaches them from [`codemode`](usage.md#enable-codemode) scripts, and extensions through `ctx.modelRegistry.classify()`; see [Classifier models](models.md#use-classifier-models).
105
+ Classifier models answer typed `choice`, `bool`, and `score` questions about JSON state, like TypeSafe's Jev models. The model reaches them from [`codemode`](usage.md#enable-codemode) scripts, and extensions through `ctx.modelRegistry.classify()`; see [Classifier models](models.md#use-classifier-models). Pi lists llama.cpp models as classifiers in two ways:
106
+
107
+ - **Decision models** such as [Julia-1, Laya, Kev, lev, and OpenJev](https://huggingface.co/collections/ggml-org/decision-models-6abf80cca3c83f127060a769) answer natively through llama.cpp's `/v1/systemone` endpoint. They appear only as classifiers, with the `typesafe-system-one` API, and not in `/model`.
108
+ - **Chat models** are also listed as classifiers with the same ID and the `llama-cpp-classify` API, which reads answers from next-token probabilities as described below.
109
+
110
+ llama.cpp 0.6.0 and later report decision models in the router's model list: their `architecture.output_modalities` contains `decisions`. The router reads this from the GGUF metadata without loading the model, so Pi recognizes unloaded and sleeping decision models too. Older llama.cpp builds do not report it, and Pi lists their decision models as chat models.
111
+
112
+ ### Chat models as classifiers
106
113
 
107
114
  The model does not generate an answer. Each question becomes one chat prompt: the state, every question of the request, the state again, and then the question with its answers under single-token labels. Labels are letters for a choice (up to 62 options), `Yes`/`No` for a bool, and digits for a score (up to 10 levels). The second copy of the state is read with the questions in view, which improved accuracy on JevBench with small models. Pi reads the probabilities of the labels as the next token and normalizes them. A choice returns every option's probability and a confidence of `(n * peak - 1) / (n - 1)`; a score returns the expected level.
108
115