@astralyn/pi 1.0.5 → 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 (285) hide show
  1. package/CHANGELOG.md +37 -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-HDHRZ7GT.js → chunk-C2ARIVS3.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-IE3Z7ESB.js → chunk-D3XW5X3Z.js} +371 -246
  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 +2 -2
  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-46MSJGKO.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 +1 -1
  39. package/dist/cli/args.d.ts.map +1 -1
  40. package/dist/cli/args.js +13 -1
  41. package/dist/cli/args.js.map +1 -1
  42. package/dist/core/agent-session.d.ts +12 -6
  43. package/dist/core/agent-session.d.ts.map +1 -1
  44. package/dist/core/agent-session.js +146 -174
  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 +11 -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/model-runtime.d.ts.map +1 -1
  60. package/dist/core/model-runtime.js +2 -1
  61. package/dist/core/model-runtime.js.map +1 -1
  62. package/dist/core/nested-tool-calls.d.ts +1 -0
  63. package/dist/core/nested-tool-calls.d.ts.map +1 -1
  64. package/dist/core/nested-tool-calls.js +1 -0
  65. package/dist/core/nested-tool-calls.js.map +1 -1
  66. package/dist/core/sdk.d.ts +4 -0
  67. package/dist/core/sdk.d.ts.map +1 -1
  68. package/dist/core/sdk.js +17 -5
  69. package/dist/core/sdk.js.map +1 -1
  70. package/dist/core/settings-manager.d.ts +13 -0
  71. package/dist/core/settings-manager.d.ts.map +1 -1
  72. package/dist/core/settings-manager.js +37 -13
  73. package/dist/core/settings-manager.js.map +1 -1
  74. package/dist/core/tasks/session.d.ts +9 -0
  75. package/dist/core/tasks/session.d.ts.map +1 -1
  76. package/dist/core/tasks/session.js +21 -0
  77. package/dist/core/tasks/session.js.map +1 -1
  78. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  79. package/dist/core/tools/renderers/bash.js +8 -3
  80. package/dist/core/tools/renderers/bash.js.map +1 -1
  81. package/dist/core/tools/renderers/edit.js +1 -1
  82. package/dist/core/tools/renderers/edit.js.map +1 -1
  83. package/dist/extensions/codemode/execute.d.ts.map +1 -1
  84. package/dist/extensions/codemode/execute.js +63 -7
  85. package/dist/extensions/codemode/execute.js.map +1 -1
  86. package/dist/extensions/codemode/tool.js +2 -2
  87. package/dist/extensions/codemode/tool.js.map +1 -1
  88. package/dist/extensions/index.js +2 -2
  89. package/dist/extensions/index.js.map +1 -1
  90. package/dist/extensions/llama/provider.d.ts.map +1 -1
  91. package/dist/extensions/llama/provider.js +34 -11
  92. package/dist/extensions/llama/provider.js.map +1 -1
  93. package/dist/extensions/mcp/cli.js +17 -23
  94. package/dist/extensions/mcp/cli.js.map +1 -1
  95. package/dist/extensions/mcp/index.d.ts.map +1 -1
  96. package/dist/extensions/mcp/index.js +165 -64
  97. package/dist/extensions/mcp/index.js.map +1 -1
  98. package/dist/extensions/mcp/oauth.d.ts +5 -3
  99. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  100. package/dist/extensions/mcp/oauth.js +36 -10
  101. package/dist/extensions/mcp/oauth.js.map +1 -1
  102. package/dist/extensions/mcp/ui.d.ts +3 -3
  103. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  104. package/dist/extensions/mcp/ui.js +10 -2
  105. package/dist/extensions/mcp/ui.js.map +1 -1
  106. package/dist/extensions/search/code-search.d.ts +29 -0
  107. package/dist/extensions/search/code-search.d.ts.map +1 -0
  108. package/dist/extensions/search/code-search.js +120 -0
  109. package/dist/extensions/search/code-search.js.map +1 -0
  110. package/dist/extensions/search/command.d.ts +5 -0
  111. package/dist/extensions/search/command.d.ts.map +1 -0
  112. package/dist/extensions/search/command.js +69 -0
  113. package/dist/extensions/search/command.js.map +1 -0
  114. package/dist/extensions/search/credential.d.ts +25 -0
  115. package/dist/extensions/search/credential.d.ts.map +1 -0
  116. package/dist/extensions/search/credential.js +117 -0
  117. package/dist/extensions/search/credential.js.map +1 -0
  118. package/dist/extensions/search/devin.d.ts +34 -0
  119. package/dist/extensions/search/devin.d.ts.map +1 -0
  120. package/dist/extensions/search/devin.js +210 -0
  121. package/dist/extensions/search/devin.js.map +1 -0
  122. package/dist/extensions/search/index.d.ts +5 -0
  123. package/dist/extensions/search/index.d.ts.map +1 -0
  124. package/dist/extensions/search/index.js +110 -0
  125. package/dist/extensions/search/index.js.map +1 -0
  126. package/dist/extensions/search/panel.d.ts +53 -0
  127. package/dist/extensions/search/panel.d.ts.map +1 -0
  128. package/dist/extensions/search/panel.js +157 -0
  129. package/dist/extensions/search/panel.js.map +1 -0
  130. package/dist/extensions/search/prompt.d.ts +14 -0
  131. package/dist/extensions/search/prompt.d.ts.map +1 -0
  132. package/dist/extensions/search/prompt.js +221 -0
  133. package/dist/extensions/search/prompt.js.map +1 -0
  134. package/dist/extensions/search/render.d.ts +28 -0
  135. package/dist/extensions/search/render.d.ts.map +1 -0
  136. package/dist/extensions/search/render.js +86 -0
  137. package/dist/extensions/search/render.js.map +1 -0
  138. package/dist/extensions/search/workspace.d.ts +38 -0
  139. package/dist/extensions/search/workspace.d.ts.map +1 -0
  140. package/dist/extensions/search/workspace.js +245 -0
  141. package/dist/extensions/search/workspace.js.map +1 -0
  142. package/dist/modes/interactive/components/bash-execution.d.ts +5 -1
  143. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  144. package/dist/modes/interactive/components/bash-execution.js +14 -14
  145. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  146. package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -1
  147. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  148. package/dist/modes/interactive/components/branch-summary-message.js +5 -2
  149. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  150. package/dist/modes/interactive/components/compaction-summary-message.d.ts +2 -1
  151. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  152. package/dist/modes/interactive/components/compaction-summary-message.js +5 -2
  153. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  154. package/dist/modes/interactive/components/custom-entry.d.ts +3 -1
  155. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/custom-entry.js +7 -2
  157. package/dist/modes/interactive/components/custom-entry.js.map +1 -1
  158. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  159. package/dist/modes/interactive/components/custom-message.js +1 -0
  160. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  161. package/dist/modes/interactive/components/settings-selector.js +1 -1
  162. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  163. package/dist/modes/interactive/components/skill-invocation-message.d.ts +2 -1
  164. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/skill-invocation-message.js +5 -2
  166. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  167. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  168. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  169. package/dist/modes/interactive/interactive-mode.js +52 -59
  170. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  171. package/dist/modes/interactive/program-status-reporter.d.ts +34 -0
  172. package/dist/modes/interactive/program-status-reporter.d.ts.map +1 -0
  173. package/dist/modes/interactive/program-status-reporter.js +107 -0
  174. package/dist/modes/interactive/program-status-reporter.js.map +1 -0
  175. package/dist/modes/interactive/tasks/host-hooks.d.ts +14 -0
  176. package/dist/modes/interactive/tasks/host-hooks.d.ts.map +1 -0
  177. package/dist/modes/interactive/tasks/host-hooks.js +31 -0
  178. package/dist/modes/interactive/tasks/host-hooks.js.map +1 -0
  179. package/dist/modes/interactive/tool-view/tool-execution.d.ts +5 -0
  180. package/dist/modes/interactive/tool-view/tool-execution.d.ts.map +1 -1
  181. package/dist/modes/interactive/tool-view/tool-execution.js +7 -0
  182. package/dist/modes/interactive/tool-view/tool-execution.js.map +1 -1
  183. package/dist/package-manager-cli.d.ts.map +1 -1
  184. package/dist/package-manager-cli.js +24 -0
  185. package/dist/package-manager-cli.js.map +1 -1
  186. package/dist/utils/ansi.d.ts +8 -0
  187. package/dist/utils/ansi.d.ts.map +1 -1
  188. package/dist/utils/ansi.js +30 -11
  189. package/dist/utils/ansi.js.map +1 -1
  190. package/dist/utils/clipboard.d.ts.map +1 -1
  191. package/dist/utils/clipboard.js +12 -11
  192. package/dist/utils/clipboard.js.map +1 -1
  193. package/dist/utils/image-resize-core.d.ts +12 -0
  194. package/dist/utils/image-resize-core.d.ts.map +1 -1
  195. package/dist/utils/image-resize-core.js +5 -0
  196. package/dist/utils/image-resize-core.js.map +1 -1
  197. package/dist/utils/image-resize-worker.js +3 -2
  198. package/dist/utils/image-resize-worker.js.map +1 -1
  199. package/dist/utils/image-resize.d.ts.map +1 -1
  200. package/dist/utils/image-resize.js +9 -10
  201. package/dist/utils/image-resize.js.map +1 -1
  202. package/docs/bundled/README.md +1 -1
  203. package/docs/bundled/extensions/provider.md +1 -1
  204. package/docs/bundled/extensions/question.md +1 -1
  205. package/docs/bundled/extensions/search.md +50 -0
  206. package/docs/bundled/extensions/statusline.md +15 -41
  207. package/docs/codemode.md +23 -3
  208. package/docs/docs.json +0 -4
  209. package/docs/environment-variables.md +1 -0
  210. package/docs/extensions.md +5 -1
  211. package/docs/json.md +2 -2
  212. package/docs/llama-cpp.md +8 -1
  213. package/docs/models.md +4 -1
  214. package/docs/providers.md +17 -0
  215. package/docs/rpc.md +6 -3
  216. package/docs/session-format.md +1 -0
  217. package/docs/settings.md +3 -3
  218. package/docs/terminal-setup.md +14 -0
  219. package/docs/usage.md +5 -3
  220. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  221. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  222. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  223. package/examples/extensions/gondolin/package-lock.json +2 -2
  224. package/examples/extensions/gondolin/package.json +1 -1
  225. package/examples/extensions/sandbox/package-lock.json +2 -2
  226. package/examples/extensions/sandbox/package.json +1 -1
  227. package/examples/extensions/with-deps/package-lock.json +2 -2
  228. package/examples/extensions/with-deps/package.json +1 -1
  229. package/npm-shrinkwrap.json +54 -54
  230. package/package.json +13 -11
  231. package/dist/bundle/chunks/chunk-57XI7YJH.js +0 -2
  232. package/dist/bundle/chunks/chunk-JI4WGISU.js +0 -2
  233. package/dist/bundle/chunks/chunk-TCQ4HHQD.js +0 -2
  234. package/dist/bundle/chunks/chunk-TXJO4AJ7.js +0 -11
  235. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-OWU5SM6J.js +0 -2
  236. package/dist/bundle/chunks/typesafe-system-one-KYKKNU3E.js +0 -2
  237. package/dist/extensions/web-search/auth.d.ts +0 -12
  238. package/dist/extensions/web-search/auth.d.ts.map +0 -1
  239. package/dist/extensions/web-search/auth.js +0 -90
  240. package/dist/extensions/web-search/auth.js.map +0 -1
  241. package/dist/extensions/web-search/constants.d.ts +0 -13
  242. package/dist/extensions/web-search/constants.d.ts.map +0 -1
  243. package/dist/extensions/web-search/constants.js +0 -26
  244. package/dist/extensions/web-search/constants.js.map +0 -1
  245. package/dist/extensions/web-search/execute.d.ts +0 -9
  246. package/dist/extensions/web-search/execute.d.ts.map +0 -1
  247. package/dist/extensions/web-search/execute.js +0 -96
  248. package/dist/extensions/web-search/execute.js.map +0 -1
  249. package/dist/extensions/web-search/format.d.ts +0 -21
  250. package/dist/extensions/web-search/format.d.ts.map +0 -1
  251. package/dist/extensions/web-search/format.js +0 -117
  252. package/dist/extensions/web-search/format.js.map +0 -1
  253. package/dist/extensions/web-search/index.d.ts +0 -6
  254. package/dist/extensions/web-search/index.d.ts.map +0 -1
  255. package/dist/extensions/web-search/index.js +0 -50
  256. package/dist/extensions/web-search/index.js.map +0 -1
  257. package/dist/extensions/web-search/providers/deepseek.d.ts +0 -13
  258. package/dist/extensions/web-search/providers/deepseek.d.ts.map +0 -1
  259. package/dist/extensions/web-search/providers/deepseek.js +0 -110
  260. package/dist/extensions/web-search/providers/deepseek.js.map +0 -1
  261. package/dist/extensions/web-search/providers/http.d.ts +0 -4
  262. package/dist/extensions/web-search/providers/http.d.ts.map +0 -1
  263. package/dist/extensions/web-search/providers/http.js +0 -42
  264. package/dist/extensions/web-search/providers/http.js.map +0 -1
  265. package/dist/extensions/web-search/providers/minimax.d.ts +0 -12
  266. package/dist/extensions/web-search/providers/minimax.d.ts.map +0 -1
  267. package/dist/extensions/web-search/providers/minimax.js +0 -61
  268. package/dist/extensions/web-search/providers/minimax.js.map +0 -1
  269. package/dist/extensions/web-search/render.d.ts +0 -15
  270. package/dist/extensions/web-search/render.d.ts.map +0 -1
  271. package/dist/extensions/web-search/render.js +0 -142
  272. package/dist/extensions/web-search/render.js.map +0 -1
  273. package/dist/extensions/web-search/results.d.ts +0 -35
  274. package/dist/extensions/web-search/results.d.ts.map +0 -1
  275. package/dist/extensions/web-search/results.js +0 -167
  276. package/dist/extensions/web-search/results.js.map +0 -1
  277. package/dist/extensions/web-search/schema.d.ts +0 -10
  278. package/dist/extensions/web-search/schema.d.ts.map +0 -1
  279. package/dist/extensions/web-search/schema.js +0 -36
  280. package/dist/extensions/web-search/schema.js.map +0 -1
  281. package/dist/extensions/web-search/types.d.ts +0 -51
  282. package/dist/extensions/web-search/types.d.ts.map +0 -1
  283. package/dist/extensions/web-search/types.js +0 -5
  284. package/dist/extensions/web-search/types.js.map +0 -1
  285. 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"]}
@@ -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). |
@@ -59,7 +59,7 @@ The store is for small state such as IDs, cursors, or summaries. One value may h
59
59
 
60
60
  ## Models
61
61
 
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 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).
63
63
 
64
64
  ```ts
65
65
  type ModelType = "chat" | "image" | "classifier";
@@ -100,6 +100,8 @@ Model IDs differ between providers, for example `typesafe/jev-latest` and `openr
100
100
  interface ClassifierContext {
101
101
  /** The data to classify. */
102
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 }[];
103
105
  /** Questions by ID. One call answers all of them. */
104
106
  questions: Record<string, ClassifierQuestion>;
105
107
  }
@@ -163,6 +165,24 @@ return results.map((result, i) =>
163
165
  );
164
166
  ```
165
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
+
166
186
  ### Generate images
167
187
 
168
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.
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
 
package/docs/models.md CHANGED
@@ -703,7 +703,7 @@ For `bedrock-converse-stream`, `compat.supportsStrictMode` controls Bedrock stri
703
703
 
704
704
  ## Use classifier models
705
705
 
706
- Classifier models do not chat. They answer typed questions about JSON state: pick one of several choices, answer yes or no, or give a score, each with probabilities. Pi includes TypeSafe's Jev model from these providers, and Cloudflare's Clef and Clef Flash models from Workers AI:
706
+ Classifier models do not chat. They answer typed questions about JSON state: pick one of several choices, answer yes or no, or give a score, each with probabilities. Pi includes TypeSafe's Jev model from these providers, Cloudflare's Clef and Clef Flash models from Workers AI, and OpenAI's GPT-6 Luna through the [Decisions API](https://developers.openai.com/api/docs/guides/decisions):
707
707
 
708
708
  | Provider | Model IDs | Authentication |
709
709
  |---|---|---|
@@ -712,9 +712,12 @@ Classifier models do not chat. They answer typed questions about JSON state: pic
712
712
  | `cloudflare-workers-ai` | `typesafe/jev`, `@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash` | `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID` |
713
713
  | `vercel-ai-gateway` | `typesafe-ai/jev` | `AI_GATEWAY_API_KEY` |
714
714
  | `opencode` | `jev-1.13`, `jev-1.13-free` | `OPENCODE_API_KEY` |
715
+ | `openai` | `gpt-6-luna` | `OPENAI_API_KEY` |
715
716
 
716
717
  Chat models on a [llama.cpp router](llama-cpp.md#classification) are also listed as classifier models.
717
718
 
719
+ OpenAI's Decisions API needs an API key. Sign in with ChatGPT credentials do not work with it, so `gpt-6-luna` is not listed as available while `openai` is logged in through `/login`, even when `OPENAI_API_KEY` is set; log out of `openai` to use the key. GPT-6 Luna also judges images passed in `images` (see [Codemode](codemode.md#classify)); other classifier models return an error for them. The API rejects inputs above 922K tokens, but requests that run longer than about five seconds, currently above roughly 600K input tokens, fail with a gateway timeout.
720
+
718
721
  Classifier models do not appear in `/model`. The model reaches them through the [`codemode`](usage.md#enable-codemode) tool, which is off unless an MCP server turned it on. Enable it with `"defaultTools": ["+codemode"]` in [settings](settings.md#tools). Scripts then list classifier models with `models.getAvailableOfType("classifier")` and call `models.classify(model, { state, questions })`:
719
722
 
720
723
  ```js
package/docs/providers.md CHANGED
@@ -222,6 +222,23 @@ The `auth.json` key for this provider is `azure`, renamed from `azure-openai-res
222
222
 
223
223
  The `azure` provider serves both Azure OpenAI Responses deployments and Azure AI Foundry Chat Completions deployments (for example `azure/deepseek-v4-pro`).
224
224
 
225
+ Pi sends the model ID as the deployment name. If a deployment has a different name, map it with `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`.
226
+
227
+ To use a Foundry model that Pi does not include, add it under `azure` in [`models.json`](models.md#configure-a-compatible-endpoint) with `api: "openai-completions"`. Custom models require a `baseUrl`; `AZURE_OPENAI_BASE_URL` and `AZURE_OPENAI_RESOURCE_NAME` take priority over it when set:
228
+
229
+ ```json
230
+ {
231
+ "providers": {
232
+ "azure": {
233
+ "baseUrl": "https://your-resource.services.ai.azure.com",
234
+ "models": [
235
+ { "id": "your-deployment", "api": "openai-completions" }
236
+ ]
237
+ }
238
+ }
239
+ }
240
+ ```
241
+
225
242
  ### Amazon Bedrock
226
243
 
227
244
  Use `/login amazon-bedrock` to store a Bedrock API key, or configure one of the ambient AWS credential sources below:
package/docs/rpc.md CHANGED
@@ -941,8 +941,10 @@ Emitted when one low-level agent run completes. Contains all messages generated
941
941
 
942
942
  Emitted after the full session-level run settles. At this point Pi will not continue automatically through retry, compaction retry, or queued follow-up messages.
943
943
 
944
+ `aborted` is `true` when the run ended because it was aborted, for example with Escape.
945
+
944
946
  ```json
945
- {"type": "agent_settled"}
947
+ {"type": "agent_settled", "aborted": false}
946
948
  ```
947
949
 
948
950
  ### turn_start / turn_end
@@ -1083,11 +1085,12 @@ When complete:
1083
1085
  "content": [{"type": "text", "text": "total 48\n..."}],
1084
1086
  "details": {...}
1085
1087
  },
1086
- "isError": false
1088
+ "isError": false,
1089
+ "durationMs": 42
1087
1090
  }
1088
1091
  ```
1089
1092
 
1090
- Use `toolCallId` to correlate events. The `partialResult` shape is tool-defined; built-in bash snapshots can be rendered by replacement, while other tools may use a different shape.
1093
+ `durationMs` is how long the tool's `execute()` took, measured with a monotonic clock; it is absent when the tool did not run. Use `toolCallId` to correlate events. The `partialResult` shape is tool-defined; built-in bash snapshots can be rendered by replacement, while other tools may use a different shape.
1091
1094
 
1092
1095
  ### queue_update
1093
1096
 
@@ -125,6 +125,7 @@ interface AssistantMessage {
125
125
  responseModel?: string;
126
126
  responseId?: string;
127
127
  providerThinkingLevel?: string;
128
+ thinkingLevel?: ModelThinkingLevel;
128
129
  diagnostics?: AssistantMessageDiagnostic[];
129
130
  usage: Usage;
130
131
  stopReason: "pending" | "stop" | "length" | "toolUse" | "error" | "aborted" | "deferred";