pi-ui-extend 0.1.1

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 (420) hide show
  1. package/README.md +307 -0
  2. package/bin/pix.mjs +219 -0
  3. package/dist/app/app.d.ts +96 -0
  4. package/dist/app/app.js +871 -0
  5. package/dist/app/blink-controller.d.ts +23 -0
  6. package/dist/app/blink-controller.js +82 -0
  7. package/dist/app/cli.d.ts +8 -0
  8. package/dist/app/cli.js +83 -0
  9. package/dist/app/clipboard.d.ts +1 -0
  10. package/dist/app/clipboard.js +24 -0
  11. package/dist/app/command-controller.d.ts +18 -0
  12. package/dist/app/command-controller.js +58 -0
  13. package/dist/app/command-host.d.ts +44 -0
  14. package/dist/app/command-host.js +1 -0
  15. package/dist/app/command-model-actions.d.ts +12 -0
  16. package/dist/app/command-model-actions.js +176 -0
  17. package/dist/app/command-navigation-actions.d.ts +19 -0
  18. package/dist/app/command-navigation-actions.js +267 -0
  19. package/dist/app/command-registry.d.ts +32 -0
  20. package/dist/app/command-registry.js +225 -0
  21. package/dist/app/command-runtime.d.ts +5 -0
  22. package/dist/app/command-runtime.js +32 -0
  23. package/dist/app/command-session-actions.d.ts +20 -0
  24. package/dist/app/command-session-actions.js +295 -0
  25. package/dist/app/constants.d.ts +52 -0
  26. package/dist/app/constants.js +103 -0
  27. package/dist/app/conversation-entry-renderer.d.ts +21 -0
  28. package/dist/app/conversation-entry-renderer.js +81 -0
  29. package/dist/app/conversation-shell-renderer.d.ts +5 -0
  30. package/dist/app/conversation-shell-renderer.js +43 -0
  31. package/dist/app/conversation-tool-renderer.d.ts +16 -0
  32. package/dist/app/conversation-tool-renderer.js +216 -0
  33. package/dist/app/conversation-viewport.d.ts +55 -0
  34. package/dist/app/conversation-viewport.js +252 -0
  35. package/dist/app/dcp-stats.d.ts +2 -0
  36. package/dist/app/dcp-stats.js +116 -0
  37. package/dist/app/editor-layout-renderer.d.ts +31 -0
  38. package/dist/app/editor-layout-renderer.js +211 -0
  39. package/dist/app/editor-panels.d.ts +4 -0
  40. package/dist/app/editor-panels.js +130 -0
  41. package/dist/app/extension-actions-controller.d.ts +22 -0
  42. package/dist/app/extension-actions-controller.js +60 -0
  43. package/dist/app/extension-event-bus.d.ts +3 -0
  44. package/dist/app/extension-event-bus.js +23 -0
  45. package/dist/app/extension-ui-controller.d.ts +57 -0
  46. package/dist/app/extension-ui-controller.js +532 -0
  47. package/dist/app/file-link-opener.d.ts +2 -0
  48. package/dist/app/file-link-opener.js +66 -0
  49. package/dist/app/file-links.d.ts +10 -0
  50. package/dist/app/file-links.js +117 -0
  51. package/dist/app/guards.d.ts +3 -0
  52. package/dist/app/guards.js +9 -0
  53. package/dist/app/icons.d.ts +37 -0
  54. package/dist/app/icons.js +97 -0
  55. package/dist/app/id.d.ts +1 -0
  56. package/dist/app/id.js +4 -0
  57. package/dist/app/image-click-targets.d.ts +5 -0
  58. package/dist/app/image-click-targets.js +32 -0
  59. package/dist/app/image-opener.d.ts +2 -0
  60. package/dist/app/image-opener.js +64 -0
  61. package/dist/app/input-action-controller.d.ts +47 -0
  62. package/dist/app/input-action-controller.js +209 -0
  63. package/dist/app/input-controller.d.ts +60 -0
  64. package/dist/app/input-controller.js +425 -0
  65. package/dist/app/input-paste-handler.d.ts +27 -0
  66. package/dist/app/input-paste-handler.js +146 -0
  67. package/dist/app/menu-items-controller.d.ts +32 -0
  68. package/dist/app/menu-items-controller.js +182 -0
  69. package/dist/app/message-content.d.ts +8 -0
  70. package/dist/app/message-content.js +115 -0
  71. package/dist/app/model-ref.d.ts +13 -0
  72. package/dist/app/model-ref.js +50 -0
  73. package/dist/app/model-usage-controller.d.ts +35 -0
  74. package/dist/app/model-usage-controller.js +99 -0
  75. package/dist/app/model-usage-status.d.ts +125 -0
  76. package/dist/app/model-usage-status.js +749 -0
  77. package/dist/app/mouse-controller.d.ts +182 -0
  78. package/dist/app/mouse-controller.js +968 -0
  79. package/dist/app/native-modifiers.d.ts +3 -0
  80. package/dist/app/native-modifiers.js +60 -0
  81. package/dist/app/nerd-font-controller.d.ts +11 -0
  82. package/dist/app/nerd-font-controller.js +90 -0
  83. package/dist/app/popup-action-controller.d.ts +44 -0
  84. package/dist/app/popup-action-controller.js +278 -0
  85. package/dist/app/popup-menu-controller.d.ts +183 -0
  86. package/dist/app/popup-menu-controller.js +1070 -0
  87. package/dist/app/prompt-enhancer-controller.d.ts +40 -0
  88. package/dist/app/prompt-enhancer-controller.js +215 -0
  89. package/dist/app/queued-message-controller.d.ts +62 -0
  90. package/dist/app/queued-message-controller.js +377 -0
  91. package/dist/app/render-controller.d.ts +41 -0
  92. package/dist/app/render-controller.js +378 -0
  93. package/dist/app/render-text.d.ts +19 -0
  94. package/dist/app/render-text.js +67 -0
  95. package/dist/app/request-history.d.ts +23 -0
  96. package/dist/app/request-history.js +131 -0
  97. package/dist/app/runtime.d.ts +39 -0
  98. package/dist/app/runtime.js +195 -0
  99. package/dist/app/screen-selection.d.ts +6 -0
  100. package/dist/app/screen-selection.js +9 -0
  101. package/dist/app/screen-styler.d.ts +34 -0
  102. package/dist/app/screen-styler.js +168 -0
  103. package/dist/app/scroll-controller.d.ts +51 -0
  104. package/dist/app/scroll-controller.js +207 -0
  105. package/dist/app/session-event-controller.d.ts +69 -0
  106. package/dist/app/session-event-controller.js +338 -0
  107. package/dist/app/session-history.d.ts +23 -0
  108. package/dist/app/session-history.js +164 -0
  109. package/dist/app/session-lifecycle-controller.d.ts +55 -0
  110. package/dist/app/session-lifecycle-controller.js +116 -0
  111. package/dist/app/session-search.d.ts +24 -0
  112. package/dist/app/session-search.js +215 -0
  113. package/dist/app/shell-command.d.ts +27 -0
  114. package/dist/app/shell-command.js +176 -0
  115. package/dist/app/shell-controller.d.ts +28 -0
  116. package/dist/app/shell-controller.js +124 -0
  117. package/dist/app/slash-commands.d.ts +6 -0
  118. package/dist/app/slash-commands.js +75 -0
  119. package/dist/app/startup-checks.d.ts +8 -0
  120. package/dist/app/startup-checks.js +59 -0
  121. package/dist/app/startup-info.d.ts +3 -0
  122. package/dist/app/startup-info.js +176 -0
  123. package/dist/app/status-controller.d.ts +35 -0
  124. package/dist/app/status-controller.js +105 -0
  125. package/dist/app/status-line-renderer.d.ts +68 -0
  126. package/dist/app/status-line-renderer.js +453 -0
  127. package/dist/app/subagents-files.d.ts +10 -0
  128. package/dist/app/subagents-files.js +193 -0
  129. package/dist/app/subagents-model.d.ts +23 -0
  130. package/dist/app/subagents-model.js +224 -0
  131. package/dist/app/subagents-widget-controller.d.ts +43 -0
  132. package/dist/app/subagents-widget-controller.js +311 -0
  133. package/dist/app/tab-line-renderer.d.ts +26 -0
  134. package/dist/app/tab-line-renderer.js +222 -0
  135. package/dist/app/tabs-controller.d.ts +100 -0
  136. package/dist/app/tabs-controller.js +885 -0
  137. package/dist/app/terminal-controller.d.ts +40 -0
  138. package/dist/app/terminal-controller.js +135 -0
  139. package/dist/app/terminal-edit-shortcuts.d.ts +23 -0
  140. package/dist/app/terminal-edit-shortcuts.js +138 -0
  141. package/dist/app/terminal-output-buffer.d.ts +20 -0
  142. package/dist/app/terminal-output-buffer.js +52 -0
  143. package/dist/app/toast-controller.d.ts +13 -0
  144. package/dist/app/toast-controller.js +40 -0
  145. package/dist/app/toast-renderer.d.ts +9 -0
  146. package/dist/app/toast-renderer.js +79 -0
  147. package/dist/app/todo-model.d.ts +22 -0
  148. package/dist/app/todo-model.js +179 -0
  149. package/dist/app/todo-widget-controller.d.ts +21 -0
  150. package/dist/app/todo-widget-controller.js +59 -0
  151. package/dist/app/tool-block-renderer.d.ts +26 -0
  152. package/dist/app/tool-block-renderer.js +439 -0
  153. package/dist/app/types.d.ts +550 -0
  154. package/dist/app/types.js +1 -0
  155. package/dist/app/update.d.ts +36 -0
  156. package/dist/app/update.js +315 -0
  157. package/dist/app/voice-controller.d.ts +52 -0
  158. package/dist/app/voice-controller.js +600 -0
  159. package/dist/app/workspace-actions-controller.d.ts +40 -0
  160. package/dist/app/workspace-actions-controller.js +215 -0
  161. package/dist/app/workspace-undo.d.ts +44 -0
  162. package/dist/app/workspace-undo.js +215 -0
  163. package/dist/config.d.ts +62 -0
  164. package/dist/config.js +527 -0
  165. package/dist/context-progress-bar.d.ts +17 -0
  166. package/dist/context-progress-bar.js +48 -0
  167. package/dist/default-pix-config.d.ts +1 -0
  168. package/dist/default-pix-config.js +375 -0
  169. package/dist/fuzzy.d.ts +23 -0
  170. package/dist/fuzzy.js +165 -0
  171. package/dist/input-editor-files.d.ts +15 -0
  172. package/dist/input-editor-files.js +62 -0
  173. package/dist/input-editor.d.ts +186 -0
  174. package/dist/input-editor.js +835 -0
  175. package/dist/main.d.ts +1 -0
  176. package/dist/main.js +12 -0
  177. package/dist/markdown-format.d.ts +22 -0
  178. package/dist/markdown-format.js +542 -0
  179. package/dist/sdk.d.ts +3 -0
  180. package/dist/sdk.js +1 -0
  181. package/dist/syntax-highlight.d.ts +22 -0
  182. package/dist/syntax-highlight.js +528 -0
  183. package/dist/terminal-width.d.ts +6 -0
  184. package/dist/terminal-width.js +245 -0
  185. package/dist/theme.d.ts +56 -0
  186. package/dist/theme.js +118 -0
  187. package/dist/tool-renderers/apply-patch.d.ts +2 -0
  188. package/dist/tool-renderers/apply-patch.js +36 -0
  189. package/dist/tool-renderers/ast.d.ts +2 -0
  190. package/dist/tool-renderers/ast.js +5 -0
  191. package/dist/tool-renderers/compress.d.ts +2 -0
  192. package/dist/tool-renderers/compress.js +126 -0
  193. package/dist/tool-renderers/index.d.ts +3 -0
  194. package/dist/tool-renderers/index.js +44 -0
  195. package/dist/tool-renderers/question.d.ts +2 -0
  196. package/dist/tool-renderers/question.js +88 -0
  197. package/dist/tool-renderers/read.d.ts +2 -0
  198. package/dist/tool-renderers/read.js +36 -0
  199. package/dist/tool-renderers/repo.d.ts +2 -0
  200. package/dist/tool-renderers/repo.js +13 -0
  201. package/dist/tool-renderers/shell.d.ts +3 -0
  202. package/dist/tool-renderers/shell.js +27 -0
  203. package/dist/tool-renderers/skill.d.ts +2 -0
  204. package/dist/tool-renderers/skill.js +132 -0
  205. package/dist/tool-renderers/subagents.d.ts +2 -0
  206. package/dist/tool-renderers/subagents.js +51 -0
  207. package/dist/tool-renderers/todo.d.ts +2 -0
  208. package/dist/tool-renderers/todo.js +9 -0
  209. package/dist/tool-renderers/types.d.ts +42 -0
  210. package/dist/tool-renderers/types.js +1 -0
  211. package/dist/tool-renderers/utils.d.ts +31 -0
  212. package/dist/tool-renderers/utils.js +230 -0
  213. package/dist/tool-renderers/web.d.ts +3 -0
  214. package/dist/tool-renderers/web.js +9 -0
  215. package/dist/tool-renderers/write.d.ts +2 -0
  216. package/dist/tool-renderers/write.js +42 -0
  217. package/dist/ui.d.ts +56 -0
  218. package/dist/ui.js +94 -0
  219. package/docs/release.md +81 -0
  220. package/extensions/question/contract.ts +100 -0
  221. package/extensions/question/index.ts +34 -0
  222. package/extensions/question/render.ts +28 -0
  223. package/extensions/question/result.ts +86 -0
  224. package/extensions/question/tool-description.ts +11 -0
  225. package/extensions/question/tui.ts +629 -0
  226. package/extensions/question/types.ts +123 -0
  227. package/extensions/session-title/config.ts +169 -0
  228. package/extensions/session-title/index.ts +459 -0
  229. package/extensions/terminal-bell/index.ts +315 -0
  230. package/external/pi-tools-suite/README.md +242 -0
  231. package/external/pi-tools-suite/index.ts +1 -0
  232. package/external/pi-tools-suite/licenses/ollama-pi-web-search.MIT +21 -0
  233. package/external/pi-tools-suite/licenses/opencode-mystatus-MIT.txt +21 -0
  234. package/external/pi-tools-suite/package.json +53 -0
  235. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +194 -0
  236. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +80 -0
  237. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +26 -0
  238. package/external/pi-tools-suite/src/antigravity-auth/headers.ts +20 -0
  239. package/external/pi-tools-suite/src/antigravity-auth/index.ts +104 -0
  240. package/external/pi-tools-suite/src/antigravity-auth/models.ts +86 -0
  241. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +305 -0
  242. package/external/pi-tools-suite/src/antigravity-auth/payload.ts +423 -0
  243. package/external/pi-tools-suite/src/antigravity-auth/status.ts +78 -0
  244. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +302 -0
  245. package/external/pi-tools-suite/src/antigravity-auth/types.ts +113 -0
  246. package/external/pi-tools-suite/src/ast-grep/index.ts +6 -0
  247. package/external/pi-tools-suite/src/ast-grep/render.ts +70 -0
  248. package/external/pi-tools-suite/src/ast-grep/schema.ts +109 -0
  249. package/external/pi-tools-suite/src/ast-grep/tool.ts +345 -0
  250. package/external/pi-tools-suite/src/ast-grep/types.ts +55 -0
  251. package/external/pi-tools-suite/src/ast-grep/utils.ts +65 -0
  252. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +222 -0
  253. package/external/pi-tools-suite/src/async-subagents/commands.ts +518 -0
  254. package/external/pi-tools-suite/src/async-subagents/constants.ts +11 -0
  255. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +74 -0
  256. package/external/pi-tools-suite/src/async-subagents/core/attachment-bridge.ts +133 -0
  257. package/external/pi-tools-suite/src/async-subagents/core/cleanup.ts +66 -0
  258. package/external/pi-tools-suite/src/async-subagents/core/concurrency.ts +90 -0
  259. package/external/pi-tools-suite/src/async-subagents/core/config.ts +819 -0
  260. package/external/pi-tools-suite/src/async-subagents/core/log-limits.ts +166 -0
  261. package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +133 -0
  262. package/external/pi-tools-suite/src/async-subagents/core/paths.ts +47 -0
  263. package/external/pi-tools-suite/src/async-subagents/core/pi-invocation.ts +35 -0
  264. package/external/pi-tools-suite/src/async-subagents/core/presets.ts +67 -0
  265. package/external/pi-tools-suite/src/async-subagents/core/process.ts +15 -0
  266. package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +66 -0
  267. package/external/pi-tools-suite/src/async-subagents/core/registry.ts +224 -0
  268. package/external/pi-tools-suite/src/async-subagents/core/retry.ts +191 -0
  269. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +259 -0
  270. package/external/pi-tools-suite/src/async-subagents/core/sessions.ts +138 -0
  271. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +688 -0
  272. package/external/pi-tools-suite/src/async-subagents/core/state.ts +281 -0
  273. package/external/pi-tools-suite/src/async-subagents/core/stop.ts +131 -0
  274. package/external/pi-tools-suite/src/async-subagents/core/structured-result.ts +237 -0
  275. package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +34 -0
  276. package/external/pi-tools-suite/src/async-subagents/core/types.ts +150 -0
  277. package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +184 -0
  278. package/external/pi-tools-suite/src/async-subagents/core/utils.ts +11 -0
  279. package/external/pi-tools-suite/src/async-subagents/format.ts +41 -0
  280. package/external/pi-tools-suite/src/async-subagents/index.ts +422 -0
  281. package/external/pi-tools-suite/src/async-subagents/lib.ts +88 -0
  282. package/external/pi-tools-suite/src/async-subagents/live.ts +10 -0
  283. package/external/pi-tools-suite/src/async-subagents/polling.ts +83 -0
  284. package/external/pi-tools-suite/src/async-subagents/render.ts +230 -0
  285. package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +77 -0
  286. package/external/pi-tools-suite/src/async-subagents/tasks.ts +120 -0
  287. package/external/pi-tools-suite/src/async-subagents/tools/cleanup.ts +99 -0
  288. package/external/pi-tools-suite/src/async-subagents/tools/result.ts +179 -0
  289. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +372 -0
  290. package/external/pi-tools-suite/src/async-subagents/tools/status.ts +60 -0
  291. package/external/pi-tools-suite/src/async-subagents/tools/stop.ts +79 -0
  292. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +152 -0
  293. package/external/pi-tools-suite/src/async-subagents/tools/wait.ts +67 -0
  294. package/external/pi-tools-suite/src/async-subagents/types.ts +45 -0
  295. package/external/pi-tools-suite/src/async-subagents/ui.ts +5 -0
  296. package/external/pi-tools-suite/src/compress/commands.ts +440 -0
  297. package/external/pi-tools-suite/src/compress/compress-tool.ts +368 -0
  298. package/external/pi-tools-suite/src/compress/compression-blocks.ts +524 -0
  299. package/external/pi-tools-suite/src/compress/config.ts +310 -0
  300. package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +498 -0
  301. package/external/pi-tools-suite/src/compress/index.ts +397 -0
  302. package/external/pi-tools-suite/src/compress/prompts.ts +269 -0
  303. package/external/pi-tools-suite/src/compress/pruner-candidates.ts +176 -0
  304. package/external/pi-tools-suite/src/compress/pruner-compression-blocks.ts +260 -0
  305. package/external/pi-tools-suite/src/compress/pruner-message-ids.ts +147 -0
  306. package/external/pi-tools-suite/src/compress/pruner-metadata.ts +268 -0
  307. package/external/pi-tools-suite/src/compress/pruner-nudge.ts +315 -0
  308. package/external/pi-tools-suite/src/compress/pruner-tools.ts +263 -0
  309. package/external/pi-tools-suite/src/compress/pruner-types.ts +25 -0
  310. package/external/pi-tools-suite/src/compress/pruner.ts +92 -0
  311. package/external/pi-tools-suite/src/compress/state.ts +486 -0
  312. package/external/pi-tools-suite/src/compress/ui.ts +308 -0
  313. package/external/pi-tools-suite/src/config.ts +176 -0
  314. package/external/pi-tools-suite/src/context-usage.ts +31 -0
  315. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +355 -0
  316. package/external/pi-tools-suite/src/index.ts +46 -0
  317. package/external/pi-tools-suite/src/lib/lsp.ts +62 -0
  318. package/external/pi-tools-suite/src/lib/project.ts +42 -0
  319. package/external/pi-tools-suite/src/lib/tool-args.ts +137 -0
  320. package/external/pi-tools-suite/src/lsp/_shared/config.ts +156 -0
  321. package/external/pi-tools-suite/src/lsp/_shared/glob.ts +60 -0
  322. package/external/pi-tools-suite/src/lsp/_shared/output.ts +102 -0
  323. package/external/pi-tools-suite/src/lsp/_shared/paths.ts +138 -0
  324. package/external/pi-tools-suite/src/lsp/_shared/runner.ts +64 -0
  325. package/external/pi-tools-suite/src/lsp/_shared/template.ts +23 -0
  326. package/external/pi-tools-suite/src/lsp/_shared/trust.ts +116 -0
  327. package/external/pi-tools-suite/src/lsp/_shared/types.ts +98 -0
  328. package/external/pi-tools-suite/src/lsp/async.ts +29 -0
  329. package/external/pi-tools-suite/src/lsp/child-process.ts +81 -0
  330. package/external/pi-tools-suite/src/lsp/client.ts +340 -0
  331. package/external/pi-tools-suite/src/lsp/constants.ts +9 -0
  332. package/external/pi-tools-suite/src/lsp/diagnostics-store.ts +64 -0
  333. package/external/pi-tools-suite/src/lsp/documents.ts +24 -0
  334. package/external/pi-tools-suite/src/lsp/index.ts +31 -0
  335. package/external/pi-tools-suite/src/lsp/lsp-utils.ts +37 -0
  336. package/external/pi-tools-suite/src/lsp/manager.ts +190 -0
  337. package/external/pi-tools-suite/src/lsp/mutation-events.ts +78 -0
  338. package/external/pi-tools-suite/src/lsp/renderer.ts +1 -0
  339. package/external/pi-tools-suite/src/lsp/tool-result.ts +6 -0
  340. package/external/pi-tools-suite/src/lsp/tsserver.ts +107 -0
  341. package/external/pi-tools-suite/src/lsp/types.ts +15 -0
  342. package/external/pi-tools-suite/src/model-tools/apply-patch.ts +590 -0
  343. package/external/pi-tools-suite/src/model-tools/index.ts +430 -0
  344. package/external/pi-tools-suite/src/model-tools/path-utils.ts +6 -0
  345. package/external/pi-tools-suite/src/model-tools/tool-args.ts +11 -0
  346. package/external/pi-tools-suite/src/prompt-commands/index.ts +349 -0
  347. package/external/pi-tools-suite/src/repo-discovery/index.ts +384 -0
  348. package/external/pi-tools-suite/src/repo-discovery/project.ts +7 -0
  349. package/external/pi-tools-suite/src/startup-section.ts +13 -0
  350. package/external/pi-tools-suite/src/terminal-bell/index.ts +309 -0
  351. package/external/pi-tools-suite/src/todo/index.ts +201 -0
  352. package/external/pi-tools-suite/src/todo/state/auto-clear.ts +13 -0
  353. package/external/pi-tools-suite/src/todo/state/invariants.ts +21 -0
  354. package/external/pi-tools-suite/src/todo/state/persistence.ts +94 -0
  355. package/external/pi-tools-suite/src/todo/state/replay.ts +38 -0
  356. package/external/pi-tools-suite/src/todo/state/selectors.ts +49 -0
  357. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +362 -0
  358. package/external/pi-tools-suite/src/todo/state/state.ts +18 -0
  359. package/external/pi-tools-suite/src/todo/state/store.ts +52 -0
  360. package/external/pi-tools-suite/src/todo/state/task-graph.ts +57 -0
  361. package/external/pi-tools-suite/src/todo/todo.ts +487 -0
  362. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +143 -0
  363. package/external/pi-tools-suite/src/todo/tool/types.ts +188 -0
  364. package/external/pi-tools-suite/src/todo/view/format.ts +18 -0
  365. package/external/pi-tools-suite/src/todo/view/labels.ts +13 -0
  366. package/external/pi-tools-suite/src/tool-descriptions.ts +369 -0
  367. package/external/pi-tools-suite/src/usage/index.ts +152 -0
  368. package/external/pi-tools-suite/src/usage/lib/copilot.ts +535 -0
  369. package/external/pi-tools-suite/src/usage/lib/google.ts +478 -0
  370. package/external/pi-tools-suite/src/usage/lib/openai.ts +268 -0
  371. package/external/pi-tools-suite/src/usage/lib/types.ts +114 -0
  372. package/external/pi-tools-suite/src/usage/lib/utils.ts +134 -0
  373. package/external/pi-tools-suite/src/usage/lib/zhipu.ts +228 -0
  374. package/external/pi-tools-suite/src/web-search/index.ts +397 -0
  375. package/package.json +89 -0
  376. package/skills/context7/SKILL.md +69 -0
  377. package/skills/context7/scripts/context7.sh +73 -0
  378. package/skills/handoff/SKILL.md +15 -0
  379. package/skills/pdf/LICENSE.txt +30 -0
  380. package/skills/pdf/SKILL.md +314 -0
  381. package/skills/pdf/forms.md +294 -0
  382. package/skills/pdf/reference.md +612 -0
  383. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  384. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  385. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  386. package/skills/pdf/scripts/create_validation_image.py +37 -0
  387. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  388. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  389. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  390. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  391. package/skills/playwright-cli/SKILL.md +388 -0
  392. package/skills/playwright-cli/references/element-attributes.md +23 -0
  393. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  394. package/skills/playwright-cli/references/request-mocking.md +87 -0
  395. package/skills/playwright-cli/references/running-code.md +241 -0
  396. package/skills/playwright-cli/references/session-management.md +225 -0
  397. package/skills/playwright-cli/references/spec-driven-testing.md +305 -0
  398. package/skills/playwright-cli/references/storage-state.md +275 -0
  399. package/skills/playwright-cli/references/test-generation.md +134 -0
  400. package/skills/playwright-cli/references/tracing.md +139 -0
  401. package/skills/playwright-cli/references/video-recording.md +143 -0
  402. package/skills/simplify/SKILL.md +51 -0
  403. package/skills/skill-creator/LICENSE.txt +202 -0
  404. package/skills/skill-creator/SKILL.md +485 -0
  405. package/skills/skill-creator/agents/analyzer.md +274 -0
  406. package/skills/skill-creator/agents/comparator.md +202 -0
  407. package/skills/skill-creator/agents/grader.md +223 -0
  408. package/skills/skill-creator/assets/eval_review.html +146 -0
  409. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  410. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  411. package/skills/skill-creator/references/schemas.md +430 -0
  412. package/skills/skill-creator/scripts/__init__.py +0 -0
  413. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  414. package/skills/skill-creator/scripts/generate_report.py +326 -0
  415. package/skills/skill-creator/scripts/improve_description.py +247 -0
  416. package/skills/skill-creator/scripts/package_skill.py +136 -0
  417. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  418. package/skills/skill-creator/scripts/run_eval.py +310 -0
  419. package/skills/skill-creator/scripts/run_loop.py +328 -0
  420. package/skills/skill-creator/scripts/utils.py +47 -0
@@ -0,0 +1,228 @@
1
+ /**
2
+ * 智谱 AI 额度查询模块
3
+ *
4
+ * [输入]: API Key
5
+ * [输出]: 格式化的额度使用情况
6
+ * [定位]: 被 usage.ts 调用,处理智谱 AI 账号
7
+ * [同步]: usage.ts, types.ts, utils.ts
8
+ */
9
+
10
+ import {
11
+ type QueryResult,
12
+ type ZhipuAuthData,
13
+ HIGH_USAGE_THRESHOLD,
14
+ } from "./types";
15
+ import {
16
+ formatDuration,
17
+ createProgressBar,
18
+ calcRemainPercent,
19
+ fetchWithTimeout,
20
+ safeMax,
21
+ maskString,
22
+ } from "./utils";
23
+
24
+ // ============================================================================
25
+ // 类型定义
26
+ // ============================================================================
27
+
28
+ interface UsageLimitItem {
29
+ /** 限制类型:TOKENS_LIMIT(Token) / TIME_LIMIT(MCP搜索次数) */
30
+ type: "TIME_LIMIT" | "TOKENS_LIMIT";
31
+ /** 总配额/限制数 */
32
+ usage: number;
33
+ /** 当前已使用 */
34
+ currentValue: number;
35
+ /** 使用百分比 */
36
+ percentage: number;
37
+ /** 下次重置时间戳 (ms,仅 TOKENS_LIMIT 有效) */
38
+ nextResetTime?: number;
39
+ }
40
+
41
+ interface QuotaLimitResponse {
42
+ code: number;
43
+ msg: string;
44
+ data: {
45
+ limits: UsageLimitItem[];
46
+ };
47
+ success: boolean;
48
+ }
49
+
50
+ interface PlatformConfig {
51
+ apiUrl: string;
52
+ apiError: (status: number, text: string) => string;
53
+ accountLabel: string;
54
+ }
55
+
56
+ // ============================================================================
57
+ // API 调用
58
+ // ============================================================================
59
+
60
+ const ZHIPU_QUOTA_QUERY_URL =
61
+ "https://bigmodel.cn/api/monitor/usage/quota/limit";
62
+ const ZAI_QUOTA_QUERY_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
63
+
64
+ const ZHIPU_CONFIG: PlatformConfig = {
65
+ apiUrl: ZHIPU_QUOTA_QUERY_URL,
66
+ apiError: (status, text) => `Zhipu API request failed (${status}): ${text}`,
67
+ accountLabel: "Coding Plan",
68
+ };
69
+
70
+ const ZAI_CONFIG: PlatformConfig = {
71
+ apiUrl: ZAI_QUOTA_QUERY_URL,
72
+ apiError: (status, text) => `Z.ai API request failed (${status}): ${text}`,
73
+ accountLabel: "Z.ai",
74
+ };
75
+
76
+ /**
77
+ * 获取智谱/Z.ai 使用情况
78
+ */
79
+ async function fetchUsage(
80
+ apiKey: string,
81
+ config: PlatformConfig,
82
+ ): Promise<QuotaLimitResponse> {
83
+ const response = await fetchWithTimeout(config.apiUrl, {
84
+ method: "GET",
85
+ headers: {
86
+ Authorization: apiKey,
87
+ "Content-Type": "application/json",
88
+ "User-Agent": "OpenCode-Status-Plugin/1.0",
89
+ },
90
+ });
91
+
92
+ if (!response.ok) {
93
+ const errorText = await response.text();
94
+ throw new Error(config.apiError(response.status, errorText));
95
+ }
96
+
97
+ const data = (await response.json()) as QuotaLimitResponse;
98
+
99
+ if (!data.success || data.code !== 200) {
100
+ throw new Error(config.apiError(data.code, data.msg || "Unknown error"));
101
+ }
102
+
103
+ return data;
104
+ }
105
+
106
+ // ============================================================================
107
+ // 格式化输出
108
+ // ============================================================================
109
+
110
+ /**
111
+ * 格式化智谱 AI 使用情况
112
+ */
113
+ function formatZhipuUsage(
114
+ data: QuotaLimitResponse,
115
+ apiKey: string,
116
+ accountLabel: string,
117
+ ): string {
118
+ const lines: string[] = [];
119
+ const limits = data.data.limits;
120
+
121
+ // 标题行:Account: API Key (Plan) - 显示脱敏后的 key
122
+ const maskedKey = maskString(apiKey);
123
+ lines.push(`Account: ${maskedKey} (${accountLabel})`);
124
+ lines.push("");
125
+
126
+ // 空数组检查
127
+ if (!limits || limits.length === 0) {
128
+ lines.push("No quota data available");
129
+ return lines.join("\n");
130
+ }
131
+
132
+ // 查找 TOKENS_LIMIT(5小时 Token 限额)
133
+ const tokensLimit = limits.find((l) => l.type === "TOKENS_LIMIT");
134
+ if (tokensLimit) {
135
+ const remainPercent = calcRemainPercent(tokensLimit.percentage);
136
+ const progressBar = createProgressBar(remainPercent);
137
+
138
+ lines.push("5-hour token limit");
139
+ lines.push(`${progressBar} ${remainPercent}% remaining`);
140
+ if (Number.isFinite(tokensLimit.currentValue) && Number.isFinite(tokensLimit.usage)) {
141
+ lines.push(
142
+ `Used: ${(tokensLimit.currentValue / 1000000).toFixed(1)}M / ${(tokensLimit.usage / 1000000).toFixed(1)}M`,
143
+ );
144
+ }
145
+
146
+ // 重置时间
147
+ if (tokensLimit.nextResetTime) {
148
+ const resetSeconds = Math.max(
149
+ 0,
150
+ Math.floor((tokensLimit.nextResetTime - Date.now()) / 1000),
151
+ );
152
+ lines.push(`Resets in: ${formatDuration(resetSeconds)}`);
153
+ }
154
+ }
155
+
156
+ // 查找 TIME_LIMIT(MCP 搜索次数)
157
+ const timeLimit = limits.find((l) => l.type === "TIME_LIMIT");
158
+ if (timeLimit) {
159
+ if (tokensLimit) lines.push(""); // 分隔符
160
+
161
+ const remainPercent = calcRemainPercent(timeLimit.percentage);
162
+ const progressBar = createProgressBar(remainPercent);
163
+
164
+ lines.push("MCP monthly quota");
165
+ lines.push(`${progressBar} ${remainPercent}% remaining`);
166
+ lines.push(`Used: ${timeLimit.currentValue} / ${timeLimit.usage}`);
167
+ }
168
+
169
+ // 警告:如果任一限额使用率超过阈值
170
+ const maxPercentage = safeMax(limits.map((l) => l.percentage));
171
+ if (maxPercentage >= HIGH_USAGE_THRESHOLD) {
172
+ lines.push("");
173
+ lines.push("⚠️ Rate limit reached!");
174
+ }
175
+
176
+ return lines.join("\n");
177
+ }
178
+
179
+ // ============================================================================
180
+ // 导出接口
181
+ // ============================================================================
182
+
183
+ export type { ZhipuAuthData };
184
+
185
+ async function queryUsage(
186
+ authData: ZhipuAuthData | undefined,
187
+ config: PlatformConfig,
188
+ ): Promise<QueryResult | null> {
189
+ // 检查账号是否存在且有效
190
+ if (!authData || authData.type !== "api" || !authData.key) {
191
+ return null;
192
+ }
193
+
194
+ try {
195
+ const usage = await fetchUsage(authData.key, config);
196
+ return {
197
+ success: true,
198
+ output: formatZhipuUsage(usage, authData.key, config.accountLabel),
199
+ };
200
+ } catch (err) {
201
+ return {
202
+ success: false,
203
+ error: err instanceof Error ? err.message : String(err),
204
+ };
205
+ }
206
+ }
207
+
208
+ /**
209
+ * 查询智谱 AI 账号额度
210
+ * @param authData 智谱认证数据
211
+ * @returns 查询结果,如果账号不存在或无效返回 null
212
+ */
213
+ export async function queryZhipuUsage(
214
+ authData: ZhipuAuthData | undefined,
215
+ ): Promise<QueryResult | null> {
216
+ return queryUsage(authData, ZHIPU_CONFIG);
217
+ }
218
+
219
+ /**
220
+ * 查询 Z.ai 账号额度
221
+ * @param authData Z.ai 认证数据
222
+ * @returns 查询结果,如果账号不存在或无效返回 null
223
+ */
224
+ export async function queryZaiUsage(
225
+ authData: ZhipuAuthData | undefined,
226
+ ): Promise<QueryResult | null> {
227
+ return queryUsage(authData, ZAI_CONFIG);
228
+ }
@@ -0,0 +1,397 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead } from "@earendil-works/pi-coding-agent";
3
+ import { Type } from "typebox";
4
+
5
+ import { WEB_SEARCH_TOOL_DESCRIPTIONS } from "../tool-descriptions";
6
+
7
+ interface SearchResult {
8
+ title: string;
9
+ url: string;
10
+ content: string;
11
+ }
12
+
13
+ interface SearchResponse {
14
+ results: SearchResult[];
15
+ }
16
+
17
+ interface FetchResponse {
18
+ title: string;
19
+ content: string;
20
+ links?: string[];
21
+ }
22
+
23
+ type Operation = "Search" | "Fetch";
24
+
25
+ const DEFAULT_OLLAMA_HOST = "http://localhost:11434";
26
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
27
+ const MAX_REQUEST_TIMEOUT_MS = 120_000;
28
+ const REQUEST_TIMEOUT_ENV = "PI_WEB_SEARCH_TIMEOUT_MS";
29
+ const MAX_ERROR_BODY_CHARS = 1_200;
30
+
31
+ function normalizeOllamaHost(host: string | undefined): string {
32
+ const trimmed = host?.trim();
33
+ if (!trimmed) return DEFAULT_OLLAMA_HOST;
34
+ return /^https?:\/\//i.test(trimmed) ? trimmed.replace(/\/+$/, "") : `http://${trimmed.replace(/\/+$/, "")}`;
35
+ }
36
+
37
+ function getOllamaHost(): string {
38
+ return normalizeOllamaHost(process.env.OLLAMA_HOST);
39
+ }
40
+
41
+ function parseTimeoutMs(value: unknown, source: string): number {
42
+ const timeoutMs = typeof value === "string" ? Number(value.trim()) : value;
43
+ if (!Number.isFinite(timeoutMs) || !Number.isInteger(timeoutMs) || (timeoutMs as number) < 1 || (timeoutMs as number) > MAX_REQUEST_TIMEOUT_MS) {
44
+ throw new Error(`${source} must be an integer between 1 and ${MAX_REQUEST_TIMEOUT_MS} milliseconds.`);
45
+ }
46
+ return timeoutMs as number;
47
+ }
48
+
49
+ function resolveRequestTimeoutMs(timeoutMs: number | undefined): number {
50
+ if (timeoutMs !== undefined) return parseTimeoutMs(timeoutMs, "timeout_ms");
51
+
52
+ const envTimeout = process.env[REQUEST_TIMEOUT_ENV]?.trim();
53
+ if (envTimeout) return parseTimeoutMs(envTimeout, REQUEST_TIMEOUT_ENV);
54
+
55
+ return DEFAULT_REQUEST_TIMEOUT_MS;
56
+ }
57
+
58
+ function createRequestSignal(parentSignal: AbortSignal | undefined, timeoutMs: number) {
59
+ const controller = new AbortController();
60
+ let timedOut = false;
61
+
62
+ const timeout = setTimeout(() => {
63
+ timedOut = true;
64
+ controller.abort();
65
+ }, timeoutMs);
66
+
67
+ const abortFromParent = () => controller.abort(parentSignal?.reason);
68
+ if (parentSignal?.aborted) abortFromParent();
69
+ else parentSignal?.addEventListener("abort", abortFromParent, { once: true });
70
+
71
+ return {
72
+ signal: controller.signal,
73
+ timedOut: () => timedOut,
74
+ cleanup: () => {
75
+ clearTimeout(timeout);
76
+ parentSignal?.removeEventListener("abort", abortFromParent);
77
+ },
78
+ };
79
+ }
80
+
81
+ function collectErrorText(error: unknown): string {
82
+ const parts: string[] = [];
83
+ const seen = new Set<unknown>();
84
+
85
+ function visit(value: unknown, depth: number) {
86
+ if (!value || depth > 4 || seen.has(value)) return;
87
+ seen.add(value);
88
+
89
+ if (value instanceof Error) {
90
+ parts.push(`${value.name}: ${value.message}`);
91
+ }
92
+
93
+ if (typeof value === "object") {
94
+ const record = value as { cause?: unknown; code?: unknown; errno?: unknown };
95
+ if (typeof record.code === "string") parts.push(record.code);
96
+ if (typeof record.errno === "string") parts.push(record.errno);
97
+ visit(record.cause, depth + 1);
98
+ } else if (typeof value === "string") {
99
+ parts.push(value);
100
+ }
101
+ }
102
+
103
+ visit(error, 0);
104
+ return parts.join(" ");
105
+ }
106
+
107
+ function errorIncludes(error: unknown, ...needles: string[]): boolean {
108
+ const text = collectErrorText(error).toUpperCase();
109
+ return needles.some((needle) => text.includes(needle.toUpperCase()));
110
+ }
111
+
112
+ function isConnectionRefused(error: unknown): boolean {
113
+ return errorIncludes(error, "ECONNREFUSED");
114
+ }
115
+
116
+ function isAbortError(error: unknown): boolean {
117
+ return error instanceof Error && (error.name === "AbortError" || errorIncludes(error, "ABORT_ERR"));
118
+ }
119
+
120
+ function endpointName(operation: Operation): "web_search" | "web_fetch" {
121
+ return operation === "Search" ? "web_search" : "web_fetch";
122
+ }
123
+
124
+ function operationNoun(operation: Operation): "search" | "fetch" {
125
+ return operation === "Search" ? "search" : "fetch";
126
+ }
127
+
128
+ function formatErrorBody(body: string): string {
129
+ const normalized = body.trim().replace(/\s+/g, " ");
130
+ if (!normalized) return "";
131
+ return normalized.length > MAX_ERROR_BODY_CHARS ? `${normalized.slice(0, MAX_ERROR_BODY_CHARS)}…` : normalized;
132
+ }
133
+
134
+ function createHttpError(response: Response, operation: Operation, host: string, body: string): Error {
135
+ const apiName = endpointName(operation);
136
+ const bodySuffix = formatErrorBody(body);
137
+ const withBody = bodySuffix ? ` Response: ${bodySuffix}` : "";
138
+
139
+ if (response.status === 401) {
140
+ return new Error(`Unauthorized by Ollama ${apiName} API at ${host}. Run \`ollama signin\` to authenticate.`);
141
+ }
142
+
143
+ if (response.status === 403) {
144
+ return new Error(`Ollama ${apiName} API at ${host} denied the request (HTTP 403). Check \`ollama signin\` and account access.${withBody}`);
145
+ }
146
+
147
+ if (response.status === 404 || response.status === 405) {
148
+ return new Error(
149
+ `Ollama ${apiName} endpoint is not available at ${host} (HTTP ${response.status}). ` +
150
+ `Update Ollama and make sure experimental web ${operationNoun(operation)} is enabled.${withBody}`,
151
+ );
152
+ }
153
+
154
+ if (response.status >= 500) {
155
+ return new Error(`Ollama ${apiName} API at ${host} failed (HTTP ${response.status}).${withBody || ` ${response.statusText}`}`);
156
+ }
157
+
158
+ return new Error(`Ollama ${apiName} API at ${host} returned HTTP ${response.status}.${withBody || ` ${response.statusText}`}`);
159
+ }
160
+
161
+ async function readJsonResponse<T>(response: Response, operation: Operation, host: string): Promise<T> {
162
+ const body = await response.text().catch(() => "");
163
+
164
+ if (!response.ok) throw createHttpError(response, operation, host, body);
165
+ if (!body.trim()) throw new Error(`Ollama ${endpointName(operation)} API at ${host} returned an empty response.`);
166
+
167
+ try {
168
+ return JSON.parse(body) as T;
169
+ } catch (error) {
170
+ const parseMessage = error instanceof Error ? error.message : String(error);
171
+ const bodySuffix = formatErrorBody(body);
172
+ throw new Error(`Ollama ${endpointName(operation)} API at ${host} returned invalid JSON: ${parseMessage}.${bodySuffix ? ` Body: ${bodySuffix}` : ""}`);
173
+ }
174
+ }
175
+
176
+ function normalizeOllamaError(error: unknown, operation: Operation, host: string, timeoutMs: number, timedOut: boolean, parentSignal: AbortSignal | undefined): Error {
177
+ const apiName = endpointName(operation);
178
+
179
+ if (timedOut) {
180
+ return new Error(
181
+ `Ollama ${apiName} request to ${host} timed out after ${timeoutMs}ms. ` +
182
+ `Increase timeout_ms or ${REQUEST_TIMEOUT_ENV} if the web endpoint is slow.`,
183
+ );
184
+ }
185
+
186
+ if (isAbortError(error) && parentSignal?.aborted) {
187
+ return new Error(`Ollama ${apiName} request was cancelled.`);
188
+ }
189
+
190
+ if (isConnectionRefused(error)) {
191
+ return new Error(`Could not connect to Ollama at ${host}. Make sure Ollama is running, OLLAMA_HOST is correct, and ${apiName} is enabled.`);
192
+ }
193
+
194
+ if (errorIncludes(error, "ENOTFOUND", "EAI_AGAIN")) {
195
+ return new Error(`Could not resolve Ollama host ${host}. Check OLLAMA_HOST.`);
196
+ }
197
+
198
+ if (errorIncludes(error, "ECONNRESET", "ETIMEDOUT", "UND_ERR_SOCKET", "UND_ERR_CONNECT_TIMEOUT")) {
199
+ const details = collectErrorText(error);
200
+ return new Error(`Connection to Ollama at ${host} failed while calling ${apiName}.${details ? ` ${details}` : ""}`);
201
+ }
202
+
203
+ if (error instanceof TypeError && error.message.toLowerCase().includes("fetch")) {
204
+ return new Error(`Request to Ollama at ${host} failed while calling ${apiName}: ${error.message}`);
205
+ }
206
+
207
+ return error instanceof Error ? error : new Error(String(error));
208
+ }
209
+
210
+ async function postOllamaJson<T>(host: string, endpoint: "web_search" | "web_fetch", body: Record<string, unknown>, operation: Operation, signal: AbortSignal | undefined, timeoutMs: number): Promise<T> {
211
+ const requestSignal = createRequestSignal(signal, timeoutMs);
212
+
213
+ try {
214
+ const response = await fetch(`${host}/api/experimental/${endpoint}`, {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify(body),
218
+ signal: requestSignal.signal,
219
+ });
220
+
221
+ return await readJsonResponse<T>(response, operation, host);
222
+ } catch (error) {
223
+ throw normalizeOllamaError(error, operation, host, timeoutMs, requestSignal.timedOut(), signal);
224
+ } finally {
225
+ requestSignal.cleanup();
226
+ }
227
+ }
228
+
229
+ function isRecord(value: unknown): value is Record<string, unknown> {
230
+ return typeof value === "object" && value !== null && !Array.isArray(value);
231
+ }
232
+
233
+ function optionalString(value: unknown): string | undefined {
234
+ return typeof value === "string" ? value : undefined;
235
+ }
236
+
237
+ function parseSearchResponse(data: unknown): SearchResponse {
238
+ if (!isRecord(data) || !Array.isArray(data.results)) {
239
+ throw new Error("Ollama web_search API returned an unexpected response: missing results array.");
240
+ }
241
+
242
+ return {
243
+ results: data.results.map((item, index) => {
244
+ if (!isRecord(item)) throw new Error(`Ollama web_search API returned an invalid result at index ${index}.`);
245
+
246
+ const url = optionalString(item.url);
247
+ if (!url) throw new Error(`Ollama web_search API returned an invalid result at index ${index}: missing url.`);
248
+
249
+ return {
250
+ title: optionalString(item.title) || "Untitled",
251
+ url,
252
+ content: optionalString(item.content) || "",
253
+ };
254
+ }),
255
+ };
256
+ }
257
+
258
+ function parseFetchResponse(data: unknown): FetchResponse {
259
+ if (!isRecord(data) || typeof data.content !== "string") {
260
+ throw new Error("Ollama web_fetch API returned an unexpected response: missing content string.");
261
+ }
262
+
263
+ return {
264
+ title: optionalString(data.title) || "Untitled",
265
+ content: data.content,
266
+ links: Array.isArray(data.links) ? data.links.filter((link): link is string => typeof link === "string") : undefined,
267
+ };
268
+ }
269
+
270
+ function truncateForTool(text: string): { text: string; truncated: boolean } {
271
+ const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
272
+ if (!truncation.truncated) return { text: truncation.content, truncated: false };
273
+
274
+ return {
275
+ text: [
276
+ truncation.content,
277
+ `[Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).]`,
278
+ ].join("\n\n"),
279
+ truncated: true,
280
+ };
281
+ }
282
+
283
+ function formatSearchResults(results: SearchResult[]): string {
284
+ if (results.length === 0) return "No results found.";
285
+
286
+ return results
287
+ .map((result, index) => {
288
+ const snippet = result.content ? `\n ${result.content}` : "";
289
+ return `${index + 1}. ${result.title}\n URL: ${result.url}${snippet}`;
290
+ })
291
+ .join("\n\n");
292
+ }
293
+
294
+ function formatFetchResult(data: FetchResponse): string {
295
+ const links = data.links ?? [];
296
+ const visibleLinks = links.slice(0, 10);
297
+ const hiddenLinkCount = Math.max(0, links.length - visibleLinks.length);
298
+
299
+ return [
300
+ `Title: ${data.title}`,
301
+ "",
302
+ "Content:",
303
+ data.content,
304
+ "",
305
+ `Links found: ${links.length}`,
306
+ ...visibleLinks.map((link) => ` - ${link}`),
307
+ hiddenLinkCount > 0 ? ` … ${hiddenLinkCount} more link(s) omitted` : undefined,
308
+ ].filter((line): line is string => line !== undefined).join("\n");
309
+ }
310
+
311
+ function contentByteLength(text: string): number {
312
+ return new TextEncoder().encode(text).byteLength;
313
+ }
314
+
315
+ function timeoutParameterDescription(): string {
316
+ return `Request timeout in milliseconds (default: ${DEFAULT_REQUEST_TIMEOUT_MS}; env override: ${REQUEST_TIMEOUT_ENV}; max: ${MAX_REQUEST_TIMEOUT_MS})`;
317
+ }
318
+
319
+ function timeoutParameter() {
320
+ return Type.Optional(
321
+ Type.Number({
322
+ description: timeoutParameterDescription(),
323
+ default: DEFAULT_REQUEST_TIMEOUT_MS,
324
+ minimum: 1,
325
+ maximum: MAX_REQUEST_TIMEOUT_MS,
326
+ }),
327
+ );
328
+ }
329
+
330
+ function searchResultDetails(data: SearchResponse, host: string, timeoutMs: number, truncated: boolean) {
331
+ return {
332
+ results: data.results,
333
+ resultCount: data.results.length,
334
+ host,
335
+ timeoutMs,
336
+ truncated,
337
+ };
338
+ }
339
+
340
+ function fetchResultDetails(data: FetchResponse, host: string, timeoutMs: number, truncated: boolean) {
341
+ return {
342
+ title: data.title,
343
+ content: data.content,
344
+ contentBytes: contentByteLength(data.content),
345
+ links: data.links ?? [],
346
+ linkCount: data.links?.length ?? 0,
347
+ host,
348
+ timeoutMs,
349
+ truncated,
350
+ };
351
+ }
352
+
353
+ export default function webSearch(pi: ExtensionAPI) {
354
+ pi.registerTool({
355
+ ...WEB_SEARCH_TOOL_DESCRIPTIONS.webSearch,
356
+ parameters: Type.Object({
357
+ query: Type.String({ description: "The search query to execute" }),
358
+ max_results: Type.Optional(Type.Number({ description: "Maximum number of search results to return (default: 5)", default: 5 })),
359
+ timeout_ms: timeoutParameter(),
360
+ }),
361
+ async execute(_toolCallId, params, signal) {
362
+ const maxResults = params.max_results ?? 5;
363
+ const host = getOllamaHost();
364
+ const timeoutMs = resolveRequestTimeoutMs(params.timeout_ms);
365
+
366
+ const rawData = await postOllamaJson<unknown>(host, "web_search", { query: params.query, max_results: maxResults }, "Search", signal, timeoutMs);
367
+ const data = parseSearchResponse(rawData);
368
+ const formatted = truncateForTool(formatSearchResults(data.results));
369
+
370
+ return {
371
+ content: [{ type: "text", text: formatted.text }],
372
+ details: searchResultDetails(data, host, timeoutMs, formatted.truncated),
373
+ };
374
+ },
375
+ });
376
+
377
+ pi.registerTool({
378
+ ...WEB_SEARCH_TOOL_DESCRIPTIONS.webFetch,
379
+ parameters: Type.Object({
380
+ url: Type.String({ description: "URL to fetch and extract content from" }),
381
+ timeout_ms: timeoutParameter(),
382
+ }),
383
+ async execute(_toolCallId, params, signal) {
384
+ const host = getOllamaHost();
385
+ const timeoutMs = resolveRequestTimeoutMs(params.timeout_ms);
386
+
387
+ const rawData = await postOllamaJson<unknown>(host, "web_fetch", { url: params.url }, "Fetch", signal, timeoutMs);
388
+ const data = parseFetchResponse(rawData);
389
+ const formatted = truncateForTool(formatFetchResult(data));
390
+
391
+ return {
392
+ content: [{ type: "text", text: formatted.text }],
393
+ details: fetchResultDetails(data, host, timeoutMs, formatted.truncated),
394
+ };
395
+ },
396
+ });
397
+ }