synabun 2.0.0 → 2.0.5

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 (334) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +46 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/start-launcher.js +88 -1
  14. package/mcp-server/package-lock.json +2 -2
  15. package/mcp-server/package.json +1 -3
  16. package/neural-interface/i18n/en.json +1500 -153
  17. package/neural-interface/i18n/pt-BR.json +1498 -152
  18. package/neural-interface/lib/assistant-api.js +62 -6
  19. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  20. package/neural-interface/lib/assistant-catalog.js +179 -27
  21. package/neural-interface/lib/assistant-config.js +31 -2
  22. package/neural-interface/lib/assistant-dispatch.js +62 -13
  23. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  24. package/neural-interface/lib/assistant-memory.js +24 -10
  25. package/neural-interface/lib/assistant-persona.js +6 -2
  26. package/neural-interface/lib/assistant-pricing.js +21 -6
  27. package/neural-interface/lib/assistant-runtime.js +181 -12
  28. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  29. package/neural-interface/lib/assistant-test-density.js +142 -0
  30. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  31. package/neural-interface/lib/assistant-user-context.js +908 -0
  32. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  33. package/neural-interface/lib/assistant-user-learn.js +404 -0
  34. package/neural-interface/lib/assistant-user-threads.js +226 -0
  35. package/neural-interface/lib/assistant-user-writer.js +181 -0
  36. package/neural-interface/lib/browser-background-tab.js +134 -0
  37. package/neural-interface/lib/browser-screenshot.js +678 -61
  38. package/neural-interface/lib/claude-agent-bridge.js +33 -6
  39. package/neural-interface/lib/claude-model-catalog.js +30 -2
  40. package/neural-interface/lib/claude-temporary.js +46 -7
  41. package/neural-interface/lib/cloudflared/install.js +533 -0
  42. package/neural-interface/lib/cloudflared/manager.js +545 -0
  43. package/neural-interface/lib/cloudflared/pins.js +115 -0
  44. package/neural-interface/lib/cloudflared/pins.json +63 -0
  45. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  46. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  47. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  48. package/neural-interface/lib/editor-completion.js +714 -0
  49. package/neural-interface/lib/editor-config.js +149 -0
  50. package/neural-interface/lib/editor-files-api.js +268 -0
  51. package/neural-interface/lib/editor-git-api.js +134 -0
  52. package/neural-interface/lib/file-explorer-api.js +1039 -0
  53. package/neural-interface/lib/file-explorer-system.js +355 -0
  54. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  55. package/neural-interface/lib/file-explorer-watch.js +239 -0
  56. package/neural-interface/lib/git-diff-api.js +103 -0
  57. package/neural-interface/lib/git-read.js +202 -0
  58. package/neural-interface/lib/greeting/api.js +89 -0
  59. package/neural-interface/lib/greeting/budgets.js +90 -0
  60. package/neural-interface/lib/greeting/engine.js +461 -0
  61. package/neural-interface/lib/greeting/facts.js +320 -0
  62. package/neural-interface/lib/greeting/sources.js +252 -0
  63. package/neural-interface/lib/greeting/writer.js +139 -0
  64. package/neural-interface/lib/http-guards.js +189 -13
  65. package/neural-interface/lib/machine-names.js +88 -0
  66. package/neural-interface/lib/memory-api.js +31 -7
  67. package/neural-interface/lib/native-loop-providers.js +10 -5
  68. package/neural-interface/lib/plugin-installer.js +4 -2
  69. package/neural-interface/lib/project-storage.js +5 -7
  70. package/neural-interface/lib/request-gate.js +589 -0
  71. package/neural-interface/lib/ui-blob-api.js +108 -0
  72. package/neural-interface/lib/ui-blob-store.js +68 -0
  73. package/neural-interface/package-lock.json +701 -2
  74. package/neural-interface/package.json +13 -2
  75. package/neural-interface/public/install-app.html +9 -6
  76. package/neural-interface/public/offline.html +3 -2
  77. package/neural-interface/public/onboarding.html +12 -1
  78. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  79. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  80. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  81. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  82. package/neural-interface/public/shared/assistant/asst-mascot.js +18 -58
  83. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  84. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  85. package/neural-interface/public/shared/assistant/asst-render.js +162 -19
  86. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  87. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  88. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  89. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  90. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  91. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  92. package/neural-interface/public/shared/assistant/asst-styles.js +158 -4
  93. package/neural-interface/public/shared/blob-store.js +111 -22
  94. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  95. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  96. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  97. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  98. package/neural-interface/public/shared/constants.js +6 -1
  99. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  100. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  101. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  102. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  103. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  104. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  105. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  106. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  107. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  108. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  109. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  110. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  111. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  112. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  113. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  114. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  115. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  116. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  117. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  118. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  119. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  120. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  121. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  122. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  123. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  124. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  125. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  126. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  127. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  128. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  129. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  130. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  131. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  132. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  133. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  134. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  135. package/neural-interface/public/shared/file-links.js +339 -0
  136. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  137. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  138. package/neural-interface/public/shared/html-shell.js +160 -165
  139. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  140. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  141. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  142. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  143. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  144. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  145. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  146. package/neural-interface/public/shared/pwa-install-page.js +29 -1
  147. package/neural-interface/public/shared/pwa-install.css +3 -3
  148. package/neural-interface/public/shared/pwa-install.js +100 -1
  149. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  150. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  151. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  152. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  153. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  154. package/neural-interface/public/shared/start-bridge.js +238 -19
  155. package/neural-interface/public/shared/styles.css +1414 -721
  156. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  157. package/neural-interface/public/shared/tunnel-view.js +167 -0
  158. package/neural-interface/public/shared/ui-claude-panel.js +157 -16
  159. package/neural-interface/public/shared/ui-detail.js +15 -0
  160. package/neural-interface/public/shared/ui-explorer.js +22 -0
  161. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  162. package/neural-interface/public/shared/ui-file-links.js +103 -0
  163. package/neural-interface/public/shared/ui-help.js +26 -0
  164. package/neural-interface/public/shared/ui-invite.js +126 -116
  165. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  166. package/neural-interface/public/shared/ui-loading.js +159 -15
  167. package/neural-interface/public/shared/ui-menubar.js +12 -30
  168. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  169. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  170. package/neural-interface/public/shared/ui-navbar.js +45 -21
  171. package/neural-interface/public/shared/ui-settings.js +455 -488
  172. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  173. package/neural-interface/public/shared/ui-sync.js +16 -0
  174. package/neural-interface/public/shared/ui-terminal.js +23 -5
  175. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  176. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  177. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  178. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  179. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  180. package/neural-interface/public/shared/worker-end.js +251 -0
  181. package/neural-interface/public/sw.js +2 -2
  182. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  183. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  184. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  185. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  186. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  187. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  188. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  189. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  190. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  191. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  192. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  193. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  194. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  195. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  196. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  197. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  198. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  199. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  200. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  201. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  202. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  203. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  204. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  205. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  206. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  207. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  208. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  209. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  210. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  211. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  212. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  309. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  310. package/neural-interface/scripts/build-vendor.mjs +78 -2
  311. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  312. package/neural-interface/server.js +873 -1028
  313. package/neural-interface/templates/rulesets/README.md +1 -1
  314. package/package.json +2 -8
  315. package/setup.js +44 -7
  316. package/benchmarks/browser-live-results.json +0 -79
  317. package/benchmarks/browser-v2-results.json +0 -258
  318. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  319. package/neural-interface/public/category-logos/claude.svg +0 -1
  320. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  321. package/neural-interface/public/category-logos/openai.svg +0 -1
  322. package/neural-interface/public/category-logos/synabun.png +0 -0
  323. package/neural-interface/public/claude.svg +0 -1
  324. package/neural-interface/public/gallery.svg +0 -6
  325. package/neural-interface/public/leonardoai.svg +0 -1
  326. package/neural-interface/public/mcp.svg +0 -4
  327. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  328. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  329. package/neural-interface/public/sessionicon.svg +0 -8
  330. package/neural-interface/public/toolbar-album.svg +0 -8
  331. package/neural-interface/public/toolbar-question.svg +0 -6
  332. package/neural-interface/public/toolbar-sessions.svg +0 -6
  333. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  334. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,63 @@
1
+ {
2
+ "version": "2026.10.0",
3
+ "baseUrl": "https://github.com/cloudflare/cloudflared/releases/download/2026.10.0/",
4
+ "release": "https://github.com/cloudflare/cloudflared/releases/tag/2026.10.0",
5
+ "license": "Apache-2.0",
6
+ "origin": "Cloudflare's own release files, the stable release of 2026-10-05. `sha256` is the digest GitHub publishes for the release asset (the `digest` of the asset in the releases API). `binary.sha256` is the checksum in the release's own notes (SHA256 Checksums): for the macOS archives that is the checksum of the cloudflared inside, for the other files it is the file itself. Each file was downloaded on 2026-10-13 and both values, and the sizes, were computed from it and found equal to what is published.",
7
+ "platforms": {
8
+ "darwin-arm64": {
9
+ "file": "cloudflared-darwin-arm64.tgz",
10
+ "sha256": "a2f79ff7b9420aa537d74af239f376da170bbabeb529aec416002adac6a72e70",
11
+ "size": 19809074,
12
+ "archive": "tgz",
13
+ "member": "cloudflared",
14
+ "binary": {
15
+ "sha256": "72edfd3eea463aef4d5cb89e2e209cecb048cc756c2b01915de2e0ad7cb39830",
16
+ "size": 39364384,
17
+ "format": "macho"
18
+ }
19
+ },
20
+ "darwin-x64": {
21
+ "file": "cloudflared-darwin-amd64.tgz",
22
+ "sha256": "903845b81828c8cb3c5d13d816a2de71c06a3da5785469df8eb0e1b736d92f9f",
23
+ "size": 21741581,
24
+ "archive": "tgz",
25
+ "member": "cloudflared",
26
+ "binary": {
27
+ "sha256": "0560c9ab7281ac3f746055323623ed23bc0405b6dab9400474020cba33a978da",
28
+ "size": 42328240,
29
+ "format": "macho"
30
+ }
31
+ },
32
+ "linux-x64": {
33
+ "file": "cloudflared-linux-amd64",
34
+ "sha256": "d33ff2d14475178d2012c2c56beba87389ac5ded27649519f198a7d3134a99db",
35
+ "size": 40129756,
36
+ "binary": {
37
+ "sha256": "d33ff2d14475178d2012c2c56beba87389ac5ded27649519f198a7d3134a99db",
38
+ "size": 40129756,
39
+ "format": "elf"
40
+ }
41
+ },
42
+ "linux-arm64": {
43
+ "file": "cloudflared-linux-arm64",
44
+ "sha256": "e6422b9d4f72d3194bc5a38676f13667c06666523217b842a877d72a80b5ac08",
45
+ "size": 37687584,
46
+ "binary": {
47
+ "sha256": "e6422b9d4f72d3194bc5a38676f13667c06666523217b842a877d72a80b5ac08",
48
+ "size": 37687584,
49
+ "format": "elf"
50
+ }
51
+ },
52
+ "win32-x64": {
53
+ "file": "cloudflared-windows-amd64.exe",
54
+ "sha256": "86aee4017b26625cee8484c113558f48effa4cd47f7aa05fcf425604e5d2b23c",
55
+ "size": 55365048,
56
+ "binary": {
57
+ "sha256": "86aee4017b26625cee8484c113558f48effa4cd47f7aa05fcf425604e5d2b23c",
58
+ "size": 55365048,
59
+ "format": "pe"
60
+ }
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,265 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — the public address (a Cloudflare quick tunnel)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // `cloudflared tunnel --url http://localhost:<port>`: no Cloudflare account, an
6
+ // address under trycloudflare.com for as long as the process runs. One tunnel
7
+ // at a time. What server.js keeps of it are three thin routes (/api/tunnel/…)
8
+ // and the address itself, which it needs for its Host and remote rules: it
9
+ // gets every change through `onUrl`, the moment cloudflared prints the address.
10
+ //
11
+ // start() answers at once. Behind it: the copy of cloudflared to run
12
+ // (manager.js: a download on a machine that has none), then the
13
+ // process, then its address. A second start while one is on its
14
+ // way, or running, changes nothing.
15
+ // stop() ends the process, and whatever a start was still waiting for.
16
+ // status() what is happening, for the page: idle, downloading (with
17
+ // progress), starting, running (the address, which copy runs and
18
+ // its version), failed (a code and a sentence; a start tries again).
19
+ // shutdown() stop(), then waits (a bounded time) until cloudflared has
20
+ // really exited. killNow(): at the server's exit, kill it now.
21
+ //
22
+ // A copy of SynaBun's own is started with --no-autoupdate: it is a pinned file,
23
+ // and cloudflared would otherwise replace itself where it stands, inside an
24
+ // installed application too. The user's own cloudflared is started as before.
25
+ // Whatever copy it is, the manager checks it once more right before the start
26
+ // (confirm: the very file its check read, by its real path), and that path is
27
+ // what is started, at once.
28
+ //
29
+ // An ended process is not forgotten: it is asked to end (SIGTERM), killed when
30
+ // it is still there a moment later (SIGKILL), and kept until it has exited.
31
+
32
+ import { spawn as nodeSpawn } from 'node:child_process';
33
+
34
+ const exited = (proc) => !proc.pid || proc.exitCode !== null || proc.signalCode !== null;
35
+
36
+ /**
37
+ * End a child process for sure: SIGTERM now, SIGKILL when it is still there `killAfterMs` later. Resolves once it
38
+ * has exited (at once for one that is gone, or never started). How long to wait for that is the caller's choice.
39
+ */
40
+ export function endChild(proc, { killAfterMs = 1000 } = {}) {
41
+ return new Promise((done) => {
42
+ if (!proc || exited(proc)) { done(); return; }
43
+ let timer = null;
44
+ proc.once('exit', () => { clearTimeout(timer); done(); });
45
+ try { proc.kill('SIGTERM'); } catch { /* gone already */ }
46
+ timer = setTimeout(() => {
47
+ if (!exited(proc)) { try { proc.kill('SIGKILL'); } catch { /* gone already */ } }
48
+ }, killAfterMs);
49
+ });
50
+ }
51
+
52
+ /** The address of a quick tunnel, as cloudflared prints it. */
53
+ const TUNNEL_URL = /https:\/\/([a-z0-9-]+)\.trycloudflare\.com/g;
54
+ /** Names under trycloudflare.com that are Cloudflare's own, never a tunnel's (they appear in its error lines). */
55
+ const NOT_A_TUNNEL = new Set(['api', 'www']);
56
+ const OUTPUT_KEPT = 4096;
57
+
58
+ /** The first quick-tunnel address in a piece of cloudflared's output, or null. */
59
+ export function tunnelUrlIn(text) {
60
+ TUNNEL_URL.lastIndex = 0;
61
+ for (let match = TUNNEL_URL.exec(text); match; match = TUNNEL_URL.exec(text)) {
62
+ if (!NOT_A_TUNNEL.has(match[1])) return match[0];
63
+ }
64
+ return null;
65
+ }
66
+
67
+ /** The arguments cloudflared is started with for a copy from `source` (manager.js). */
68
+ export function tunnelArguments(port, source) {
69
+ return ['tunnel', ...(source === 'system' ? [] : ['--no-autoupdate']), '--url', `http://localhost:${port}`];
70
+ }
71
+
72
+ /** What cloudflared said last that reads like a reason: its last error line, without the timestamp. */
73
+ function lastReason(output) {
74
+ const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
75
+ const said = [...lines].reverse().find((line) => /\b(?:ERR|error|failed)\b/i.test(line)) || '';
76
+ return said.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z?\s+/, '').replace(/^(?:ERR|INF|WRN)\s+/, '').slice(0, 240);
77
+ }
78
+
79
+ /**
80
+ * @param {object} o
81
+ * @param {{ ensure: Function, inspect: Function, status: Function, cancel: Function, confirm?: Function, killNow?: Function }} o.cloudflared manager.js
82
+ * @param {number | string} o.port the port of this server
83
+ * @param {(url: string | null) => void} [o.onUrl] every change of the address: set when it is known, null when it is gone
84
+ * @param {typeof nodeSpawn} [o.spawn]
85
+ * @param {number} [o.urlTimeoutMs] how long a started cloudflared may take to print its address
86
+ * @param {number} [o.killAfterMs] how long an ended cloudflared has to exit before it is killed
87
+ * @param {(line: string) => void} [o.log]
88
+ */
89
+ export function createQuickTunnel({ cloudflared, port, onUrl = () => {}, spawn = nodeSpawn, urlTimeoutMs = 90_000, killAfterMs = 1000, log = () => {} }) {
90
+ let phase = 'idle'; // idle | preparing | starting | running | failed
91
+ let child = null;
92
+ let url = null;
93
+ let copy = null; // { source, version } of the cloudflared that runs
94
+ let error = null; // { code, message, reason } of the start that failed, until the next one (`reason`: cloudflared's own words, when it gave any)
95
+ let attempt = 0; // a start that is not the current attempt any more does nothing
96
+ let preparing = null; // the AbortController of the start that is getting its copy ready
97
+ const ending = new Map(); // a process that was asked to end → its endChild(): kept until it has really exited
98
+
99
+ /** Ask a process to end, kill it when it does not, and keep it until it has exited. */
100
+ function end(proc) {
101
+ if (!proc) return Promise.resolve();
102
+ if (!ending.has(proc)) ending.set(proc, endChild(proc, { killAfterMs }).then(() => { ending.delete(proc); }));
103
+ return ending.get(proc);
104
+ }
105
+
106
+ const setUrl = (value) => {
107
+ if (url === value) return;
108
+ url = value;
109
+ try { onUrl(value); } catch { /* the address is still ours to report */ }
110
+ };
111
+
112
+ function fail(code, message, reason = null) {
113
+ phase = 'failed';
114
+ error = { code, message, reason };
115
+ copy = null;
116
+ setUrl(null);
117
+ log(` Tunnel: ${code === 'ended' ? 'stopped' : 'not started'} (${code}): ${message}`);
118
+ }
119
+
120
+ function launch(found, mine) {
121
+ // Checked once more as it is about to run (the manager's confirm: the very file its check read, still where
122
+ // nobody else can change it), and started at once by the real path that was checked: nothing runs in between.
123
+ let path = found.path;
124
+ try {
125
+ if (typeof cloudflared.confirm === 'function') path = cloudflared.confirm(found);
126
+ } catch (failure) {
127
+ return fail(typeof failure?.code === 'string' ? failure.code : 'launch', failure?.message || String(failure));
128
+ }
129
+ let proc;
130
+ try {
131
+ proc = spawn(path, tunnelArguments(port, found.source), { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
132
+ } catch (failure) {
133
+ return fail('launch', `cloudflared could not be started (${failure?.code || failure?.message || failure}).`);
134
+ }
135
+ child = proc;
136
+ copy = { source: found.source, version: found.version };
137
+ phase = 'starting';
138
+ let output = '';
139
+ const timer = setTimeout(() => {
140
+ if (mine !== attempt || phase !== 'starting') return;
141
+ child = null;
142
+ end(proc);
143
+ fail('no_address', `cloudflared gave no address within ${Math.round(urlTimeoutMs / 1000)} seconds.`, lastReason(output) || null);
144
+ }, urlTimeoutMs);
145
+ timer.unref?.();
146
+ // cloudflared prints the address to stderr, in pieces that can end anywhere: the search runs over what
147
+ // came last, not over one piece.
148
+ const read = (data) => {
149
+ output = (output + data.toString()).slice(-OUTPUT_KEPT);
150
+ if (mine !== attempt || phase !== 'starting') return;
151
+ const address = tunnelUrlIn(output);
152
+ if (!address) return;
153
+ clearTimeout(timer);
154
+ phase = 'running';
155
+ setUrl(address);
156
+ log(` Tunnel: ${address} (cloudflared ${found.version}, ${found.source})`);
157
+ };
158
+ proc.stdout?.on('data', read);
159
+ proc.stderr?.on('data', read);
160
+ proc.on('error', (failure) => {
161
+ clearTimeout(timer);
162
+ if (mine !== attempt || child !== proc) return;
163
+ child = null;
164
+ fail('launch', `cloudflared could not be started (${failure?.code || failure?.message || failure}).`);
165
+ });
166
+ proc.on('close', (code, signal) => {
167
+ clearTimeout(timer);
168
+ if (mine !== attempt || child !== proc) return; // stopped on request, or already accounted for
169
+ child = null;
170
+ const said = lastReason(output) || null;
171
+ if (phase === 'running') fail('ended', 'The tunnel stopped by itself. Start it again for a new address.', said);
172
+ else fail('exited', said || `cloudflared stopped before it gave an address (${signal || `exit ${code}`}).`, said);
173
+ });
174
+ return undefined;
175
+ }
176
+
177
+ function answer(message) {
178
+ return { ok: true, state: phase, url, message };
179
+ }
180
+
181
+ function start() {
182
+ if (phase === 'running') return { ...answer('Already running.') };
183
+ if (phase === 'preparing' || phase === 'starting') return answer('Tunnel starting...');
184
+ const mine = ++attempt;
185
+ phase = 'preparing';
186
+ error = null;
187
+ copy = null;
188
+ setUrl(null);
189
+ // What the preparation runs (a hash, a `cloudflared --version`, a download) stops with this, on stop().
190
+ const ready = new AbortController();
191
+ preparing = ready;
192
+ const done = () => { if (preparing === ready) preparing = null; };
193
+ Promise.resolve().then(() => cloudflared.ensure({ signal: ready.signal })).then((found) => {
194
+ done();
195
+ if (mine === attempt) launch(found, mine);
196
+ }, (failure) => {
197
+ done();
198
+ if (mine !== attempt) return;
199
+ fail(typeof failure?.code === 'string' ? failure.code : 'unknown', failure?.message || String(failure));
200
+ });
201
+ return answer('Tunnel starting...');
202
+ }
203
+
204
+ function stop() {
205
+ const was = phase;
206
+ attempt++;
207
+ const proc = child;
208
+ child = null;
209
+ phase = 'idle';
210
+ error = null;
211
+ copy = null;
212
+ setUrl(null);
213
+ if (preparing) { preparing.abort(); preparing = null; }
214
+ if (was === 'preparing') { try { cloudflared.cancel(); } catch { /* nothing was running */ } }
215
+ end(proc);
216
+ return { ok: true, state: phase, message: was === 'idle' || was === 'failed' ? 'Not running.' : 'Tunnel stopped.' };
217
+ }
218
+
219
+ /**
220
+ * stop(), and a wait until cloudflared has really exited (killed when it does not end by itself): at most
221
+ * `timeoutMs`, so the server's shutdown never waits on it for long. `exited` says whether it did by then.
222
+ */
223
+ async function shutdown({ timeoutMs = 2000 } = {}) {
224
+ const stopped = stop();
225
+ let timer = null;
226
+ const exitedInTime = await Promise.race([
227
+ Promise.all([...ending.values()]).then(() => true),
228
+ new Promise((done) => { timer = setTimeout(() => done(false), timeoutMs); }),
229
+ ]);
230
+ clearTimeout(timer);
231
+ return { ...stopped, exited: exitedInTime && ending.size === 0 };
232
+ }
233
+
234
+ /** At the server's exit, when nothing can be waited for: every cloudflared this started that is still there is killed now. */
235
+ function killNow() {
236
+ stop();
237
+ for (const proc of ending.keys()) { if (!exited(proc)) { try { proc.kill('SIGKILL'); } catch { /* gone already */ } } }
238
+ try { cloudflared.killNow?.(); } catch { /* nothing of its own runs */ }
239
+ }
240
+
241
+ async function status() {
242
+ // While a start waits for the copy, the manager is asked what it knows, not sent to look again.
243
+ let tool;
244
+ try { tool = phase === 'preparing' ? cloudflared.status() : await cloudflared.inspect(); } catch (failure) { tool = { ...cloudflared.status(), state: 'failed', error: { code: 'unknown', message: failure?.message || String(failure) } }; }
245
+ const downloading = phase === 'preparing' && tool.state === 'downloading';
246
+ return {
247
+ ok: true,
248
+ // What a page that predates the download reads: can a tunnel be started here, is one up, is one on its way.
249
+ available: tool.state !== 'unsupported',
250
+ running: phase === 'running' || phase === 'starting',
251
+ starting: phase === 'preparing' || phase === 'starting',
252
+ url,
253
+ state: downloading ? 'downloading' : phase === 'preparing' ? 'starting' : phase,
254
+ error,
255
+ cloudflared: {
256
+ ...tool,
257
+ // The copy that runs, when one does; otherwise the one a start would run.
258
+ source: copy?.source ?? tool.source,
259
+ version: copy?.version ?? tool.version,
260
+ },
261
+ };
262
+ }
263
+
264
+ return { start, stop, status, url: () => url, phase: () => phase, shutdown, killNow };
265
+ }
@@ -0,0 +1,264 @@
1
+ // ── AI autocomplete through an endpoint of the user's own ──
2
+ // A local Ollama, or anything that speaks the OpenAI completions or chat
3
+ // protocol. What is sent (docs/design/code-editor/SPEC.md, section 9.6):
4
+ //
5
+ // ollama + fim POST {url}/api/generate { model, prompt: prefix, suffix, stream, options, keep_alive }
6
+ // ollama + chat POST {url}/api/chat { model, messages, stream, options, keep_alive }
7
+ // openai + fim POST {url} { model, prompt: prefix, suffix, max_tokens, temperature, stream }
8
+ // openai + chat POST {url} { model, messages, max_tokens, temperature, stream }
9
+ //
10
+ // `Authorization: Bearer` is added when SYNABUN_EDITOR_AI_KEY is set; the key is
11
+ // never part of an error, in any form: nothing an endpoint says about a failure
12
+ // (an error body, an error inside a stream) is passed on. An endpoint may repeat
13
+ // the request's Authorization header as sent, URL-encoded, JSON-escaped or in a
14
+ // form nobody listed, so its words are only read to pick one of a few sentences
15
+ // of this file's own (endpointFailure). Every failure of a call leaves here as
16
+ // an EndpointFailure with such a sentence. The answer is read as it streams
17
+ // (server-sent events, one JSON object per line, or one JSON body) until it
18
+ // ends, until the caller says it holds enough, or until the request is aborted.
19
+
20
+ const MAX_TOKENS = 256;
21
+ const TEMPERATURE = 0.1;
22
+ const MAX_REPLY_CHARS = 16_000;
23
+
24
+ const trimSlashes = (url) => String(url || '').replace(/\/+$/, '');
25
+
26
+ const REDACTED = '[redacted]';
27
+ // What follows "Bearer" in a header an endpoint echoes: the characters a token is made of (lib/editor-config.js).
28
+ const BEARER_TOKEN = /\b(Bearer)\s+[A-Za-z0-9._~+/=:@-]+/gi;
29
+
30
+ /**
31
+ * A text without the endpoint's key as it was sent: the key itself wherever it stands, and whatever follows
32
+ * "Bearer" (a credential whoever it belongs to). It knows the key in one form only, so it is not what keeps the
33
+ * key out of an endpoint's errors (nothing an endpoint says is passed on: endpointFailure); it is a second rule
34
+ * for text of this server's own.
35
+ * @param {string} text
36
+ * @param {string} [key] the endpoint's key (SYNABUN_EDITOR_AI_KEY), when one is set
37
+ */
38
+ export function redactKey(text, key = '') {
39
+ let out = String(text ?? '');
40
+ if (key) out = out.split(String(key)).join(REDACTED);
41
+ return out.replace(BEARER_TOKEN, `$1 ${REDACTED}`);
42
+ }
43
+
44
+ /** A failure of an endpoint call, in words of this file only: safe for the page, a test's row and the log. */
45
+ export class EndpointFailure extends Error {
46
+ /** @param {string} message @param {{ status?: number | null, kind?: string | null }} [o] */
47
+ constructor(message, { status = null, kind = null } = {}) {
48
+ super(message);
49
+ this.name = 'EndpointFailure';
50
+ this.status = status;
51
+ this.kind = kind;
52
+ }
53
+ }
54
+
55
+ // What a failure is called, by what the answer mentions and by its status. The first sentence whose words are
56
+ // found wins; with no words found, the first whose statuses hold this one.
57
+ const FAILURE_KINDS = [
58
+ { kind: 'model', says: 'it does not have that model', statuses: [], words: /model.{0,60}(?:not found|does not exist|doesn't exist|unknown|not supported|not available)|(?:no such|unknown|invalid) model|try pulling/i },
59
+ { kind: 'key', says: 'it refused the key', statuses: [401, 403], words: /unauthori[sz]ed|incorrect api key|invalid.{0,12}(?:api.?key|token|credential)|authentication|not authenticated|forbidden|permission denied/i },
60
+ { kind: 'rate', says: 'it takes no more requests for now', statuses: [429], words: /rate.?limit|too many requests|quota|insufficient.{0,12}(?:credit|balance|funds|quota)/i },
61
+ { kind: 'long', says: 'the request is too long for the model', statuses: [413], words: /context.{0,20}(?:length|window)|too long|maximum.{0,20}tokens|too many tokens/i },
62
+ { kind: 'busy', says: 'it is busy or still loading the model', statuses: [502, 503, 504], words: /overloaded|unavailable|loading model|still loading|timed? ?out/i },
63
+ { kind: 'address', says: 'nothing takes completions at that address', statuses: [404, 405], words: null },
64
+ ];
65
+ // Why a request never got an answer, by the code of what ended it (fetch's `cause`).
66
+ const NETWORK_KINDS = {
67
+ ECONNREFUSED: 'nothing is listening at its address',
68
+ ENOTFOUND: 'its address is not a name that is known',
69
+ EAI_AGAIN: 'its address could not be looked up',
70
+ ECONNRESET: 'it closed the connection',
71
+ ETIMEDOUT: 'it did not answer in time',
72
+ UND_ERR_CONNECT_TIMEOUT: 'it did not answer in time',
73
+ UND_ERR_SOCKET: 'it closed the connection',
74
+ CERT_HAS_EXPIRED: 'its certificate is not accepted',
75
+ DEPTH_ZERO_SELF_SIGNED_CERT: 'its certificate is not accepted',
76
+ SELF_SIGNED_CERT_IN_CHAIN: 'its certificate is not accepted',
77
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: 'its certificate is not accepted',
78
+ ERR_TLS_CERT_ALTNAME_INVALID: 'its certificate is not accepted',
79
+ ERR_INVALID_URL: 'its address is not a URL',
80
+ };
81
+ const READ_FOR_KIND = 4000;
82
+
83
+ /**
84
+ * What an endpoint's failure is called. `said` (its error body, or the error inside its stream) is read for what
85
+ * it mentions and for nothing else: not a character of it is in the result.
86
+ * @param {number | null} status the HTTP status, or null for an error an answer carried in its stream
87
+ * @param {string} [said]
88
+ * @returns {EndpointFailure}
89
+ */
90
+ export function endpointFailure(status, said = '') {
91
+ const words = String(said ?? '').slice(0, READ_FOR_KIND);
92
+ const code = Number.isInteger(status) ? status : null;
93
+ const found = FAILURE_KINDS.find((each) => each.words && each.words.test(words))
94
+ || (code === null ? null : FAILURE_KINDS.find((each) => each.statuses.includes(code)));
95
+ const lead = code === null ? 'The endpoint reported an error' : `The endpoint answered ${code}`;
96
+ return new EndpointFailure(found ? `${lead}: ${found.says}` : lead, { status: code, kind: found?.kind ?? null });
97
+ }
98
+
99
+ /**
100
+ * Whatever ended an endpoint call, as an EndpointFailure: its own when it is one; a request that got no answer by
101
+ * the code of what ended it; anything else without its words (they are not this file's).
102
+ */
103
+ export function asEndpointFailure(error) {
104
+ if (error instanceof EndpointFailure) return error;
105
+ // fetch says "fetch failed" and keeps the reason in `cause` (several of them when a name has several addresses).
106
+ const causes = [error?.cause, error, error?.cause?.cause, ...(Array.isArray(error?.cause?.errors) ? error.cause.errors : [])];
107
+ const code = causes.map((each) => each?.code).find((each) => typeof each === 'string' && Object.hasOwn(NETWORK_KINDS, each));
108
+ return new EndpointFailure(code ? `The endpoint could not be reached: ${NETWORK_KINDS[code]}` : 'The endpoint could not be asked', { kind: code ? 'network' : null });
109
+ }
110
+
111
+ /**
112
+ * The HTTP request of one completion.
113
+ * @param {object} o
114
+ * @param {{ kind: 'ollama' | 'openai', url: string, model: string, shape: 'fim' | 'chat' }} o.endpoint `ai.endpoint`
115
+ * @param {string} [o.key] the bearer token, when one is set
116
+ * @param {{ prefix: string, suffix: string }} o.request
117
+ * @param {{ system: string, user: string }} o.prompt the chat messages (lib/editor-completion.js buildChatPrompt)
118
+ * @returns {{ url: string, headers: object, body: object }}
119
+ */
120
+ export function endpointRequest({ endpoint, key = '', request, prompt }) {
121
+ const headers = { 'Content-Type': 'application/json', Accept: 'text/event-stream, application/x-ndjson, application/json' };
122
+ if (key) headers.Authorization = `Bearer ${key}`;
123
+ const messages = [{ role: 'system', content: prompt.system }, { role: 'user', content: prompt.user }];
124
+ if (endpoint.kind === 'ollama') {
125
+ // The address is the server's; a pasted /api/generate or /api/chat is not doubled.
126
+ const base = trimSlashes(endpoint.url).replace(/\/api\/(?:generate|chat)$/, '');
127
+ const common = { model: endpoint.model, stream: true, options: { temperature: TEMPERATURE, num_predict: MAX_TOKENS }, keep_alive: '30m' };
128
+ return endpoint.shape === 'chat'
129
+ ? { url: `${base}/api/chat`, headers, body: { ...common, messages } }
130
+ : { url: `${base}/api/generate`, headers, body: { ...common, prompt: request.prefix, suffix: request.suffix } };
131
+ }
132
+ const common = { model: endpoint.model, max_tokens: MAX_TOKENS, temperature: TEMPERATURE, stream: true };
133
+ return endpoint.shape === 'chat'
134
+ ? { url: endpoint.url, headers, body: { ...common, messages } }
135
+ : { url: endpoint.url, headers, body: { ...common, prompt: request.prefix, suffix: request.suffix } };
136
+ }
137
+
138
+ /**
139
+ * What one object of an answer holds, whichever of the four protocols it is.
140
+ * @param {object} chunk
141
+ * @returns {{ text: string, done: boolean, tokens: { input: number, output: number } | null, error: string | null }}
142
+ * `error`: what the failure is called (endpointFailure), never the endpoint's own words
143
+ */
144
+ export function readChunk(chunk) {
145
+ const out = { text: '', done: false, tokens: null, error: null };
146
+ if (!chunk || typeof chunk !== 'object') return out;
147
+ if (chunk.error) {
148
+ let said = '';
149
+ try { said = typeof chunk.error === 'string' ? chunk.error : JSON.stringify(chunk.error); } catch { said = ''; }
150
+ out.error = endpointFailure(null, said).message;
151
+ return out;
152
+ }
153
+ const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : null;
154
+ const text = chunk.response ?? chunk.message?.content ?? choice?.text ?? choice?.delta?.content ?? choice?.message?.content;
155
+ if (typeof text === 'string') out.text = text;
156
+ out.done = chunk.done === true || (choice != null && choice.finish_reason != null);
157
+ const input = chunk.prompt_eval_count ?? chunk.usage?.prompt_tokens;
158
+ const output = chunk.eval_count ?? chunk.usage?.completion_tokens;
159
+ if (Number.isFinite(input) || Number.isFinite(output)) out.tokens = { input: Math.round(input || 0), output: Math.round(output || 0) };
160
+ return out;
161
+ }
162
+
163
+ /**
164
+ * Read an answer to its end, or until `isDone(text)` says it holds enough.
165
+ * @param {Response} res
166
+ * @param {{ isDone?: (text: string) => boolean }} [o]
167
+ * @returns {Promise<{ text: string, tokens: { input: number, output: number } | null, stoppedEarly: boolean }>}
168
+ * rejects with an EndpointFailure for an error the answer carries, and for an answer that cannot be read
169
+ */
170
+ export async function readEndpointAnswer(res, { isDone = () => false } = {}) {
171
+ let text = '';
172
+ let tokens = null;
173
+ let pending = ''; // the line that is still arriving
174
+ let unparsed = ''; // lines that were not objects on their own: one JSON body spread over several lines
175
+ let finished = false;
176
+
177
+ const take = (chunk) => {
178
+ const part = readChunk(chunk);
179
+ if (part.error) throw new EndpointFailure(part.error, { kind: 'reported' });
180
+ text += part.text;
181
+ if (part.tokens) tokens = part.tokens;
182
+ if (part.done) finished = true;
183
+ };
184
+ const line = (raw) => {
185
+ let value = raw.trim();
186
+ if (!value || value.startsWith(':') || value.startsWith('event:') || value.startsWith('id:') || value.startsWith('retry:')) return;
187
+ if (value.startsWith('data:')) value = value.slice(5).trim();
188
+ if (value === '[DONE]') { finished = true; return; }
189
+ let chunk;
190
+ try { chunk = JSON.parse(value); } catch { unparsed += `${raw}\n`; return; }
191
+ take(chunk);
192
+ };
193
+
194
+ const reader = res.body?.getReader?.();
195
+ if (!reader) {
196
+ for (const each of (await res.text()).split('\n')) line(each);
197
+ } else {
198
+ const decoder = new TextDecoder();
199
+ let stoppedEarly = false;
200
+ try {
201
+ for (;;) {
202
+ const { value, done } = await reader.read();
203
+ if (done) break;
204
+ pending += decoder.decode(value, { stream: true });
205
+ const lines = pending.split('\n');
206
+ pending = lines.pop();
207
+ for (const each of lines) line(each);
208
+ if (finished) break;
209
+ if (text.length > MAX_REPLY_CHARS || isDone(text)) { stoppedEarly = true; break; }
210
+ }
211
+ } finally {
212
+ // Whatever is still on its way is not wanted: the connection is given back.
213
+ try { await reader.cancel(); } catch { /* already closed */ }
214
+ }
215
+ if (stoppedEarly) return { text, tokens, stoppedEarly: true };
216
+ if (pending) line(pending);
217
+ }
218
+ if (unparsed.trim() && !text) {
219
+ let whole;
220
+ try { whole = JSON.parse(unparsed); } catch { throw new EndpointFailure('The endpoint did not answer in a form this editor reads', { kind: 'form' }); }
221
+ take(whole);
222
+ }
223
+ return { text, tokens, stoppedEarly: false };
224
+ }
225
+
226
+ /** @param {{ fetchImpl?: typeof fetch }} [deps] */
227
+ export function createEndpointCompletionEngine({ fetchImpl = (...args) => fetch(...args) } = {}) {
228
+ return {
229
+ /**
230
+ * One completion.
231
+ * @param {object} o
232
+ * @param {{ kind: string, url: string, model: string, shape: string }} o.endpoint
233
+ * @param {string} [o.key]
234
+ * @param {{ prefix: string, suffix: string }} o.request
235
+ * @param {{ system: string, user: string }} o.prompt
236
+ * @param {AbortSignal} [o.signal]
237
+ * @param {(text: string) => boolean} [o.isDone] the first stop condition: the answer so far holds all that would be shown
238
+ * @returns {Promise<{ text: string, tokens: { input: number, output: number } | null }>} rejects with an
239
+ * EndpointFailure (its message holds nothing the endpoint said), or with the abort when `signal` fired
240
+ */
241
+ async complete({ endpoint, key = '', request, prompt, signal, isDone } = {}) {
242
+ const { url, headers, body } = endpointRequest({ endpoint, key, request, prompt });
243
+ // Our own controller, so an answer that holds enough can be cut off without the caller aborting.
244
+ const own = new AbortController();
245
+ const onAbort = () => own.abort();
246
+ if (signal?.aborted) own.abort();
247
+ signal?.addEventListener('abort', onAbort, { once: true });
248
+ try {
249
+ const res = await fetchImpl(url, { method: 'POST', headers, body: JSON.stringify(body), signal: own.signal });
250
+ // What it says is read for what the failure is called, and stays here.
251
+ if (!res.ok) throw endpointFailure(res.status, await res.text().catch(() => ''));
252
+ const answer = await readEndpointAnswer(res, { isDone });
253
+ return { text: answer.text, tokens: answer.tokens };
254
+ } catch (error) {
255
+ // The caller gave up: that is not the endpoint's failure.
256
+ if (signal?.aborted) throw error;
257
+ throw asEndpointFailure(error);
258
+ } finally {
259
+ signal?.removeEventListener('abort', onAbort);
260
+ own.abort();
261
+ }
262
+ },
263
+ };
264
+ }