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.
- package/CHANGELOG.md +57 -0
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +46 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-compact.mjs +11 -0
- package/hooks/claude-code/prompt-submit.mjs +60 -184
- package/hooks/claude-code/session-start.mjs +73 -16
- package/hooks/claude-code/shared.mjs +20 -0
- package/hooks/claude-code/stop.mjs +5 -5
- package/lib/start-launcher.js +88 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/neural-interface/i18n/en.json +1500 -153
- package/neural-interface/i18n/pt-BR.json +1498 -152
- package/neural-interface/lib/assistant-api.js +62 -6
- package/neural-interface/lib/assistant-brains/claude.js +4 -0
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +31 -2
- package/neural-interface/lib/assistant-dispatch.js +62 -13
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +6 -2
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-runtime.js +181 -12
- package/neural-interface/lib/assistant-task-prompt.js +13 -3
- package/neural-interface/lib/assistant-test-density.js +142 -0
- package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
- package/neural-interface/lib/assistant-user-context.js +908 -0
- package/neural-interface/lib/assistant-user-knowledge.js +328 -0
- package/neural-interface/lib/assistant-user-learn.js +404 -0
- package/neural-interface/lib/assistant-user-threads.js +226 -0
- package/neural-interface/lib/assistant-user-writer.js +181 -0
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-agent-bridge.js +33 -6
- package/neural-interface/lib/claude-model-catalog.js +30 -2
- package/neural-interface/lib/claude-temporary.js +46 -7
- package/neural-interface/lib/cloudflared/install.js +533 -0
- package/neural-interface/lib/cloudflared/manager.js +545 -0
- package/neural-interface/lib/cloudflared/pins.js +115 -0
- package/neural-interface/lib/cloudflared/pins.json +63 -0
- package/neural-interface/lib/cloudflared/tunnel.js +265 -0
- package/neural-interface/lib/editor-completion-endpoint.js +264 -0
- package/neural-interface/lib/editor-completion-opencode.js +436 -0
- package/neural-interface/lib/editor-completion.js +714 -0
- package/neural-interface/lib/editor-config.js +149 -0
- package/neural-interface/lib/editor-files-api.js +268 -0
- package/neural-interface/lib/editor-git-api.js +134 -0
- package/neural-interface/lib/file-explorer-api.js +1039 -0
- package/neural-interface/lib/file-explorer-system.js +355 -0
- package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
- package/neural-interface/lib/file-explorer-watch.js +239 -0
- package/neural-interface/lib/git-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +202 -0
- package/neural-interface/lib/greeting/api.js +89 -0
- package/neural-interface/lib/greeting/budgets.js +90 -0
- package/neural-interface/lib/greeting/engine.js +461 -0
- package/neural-interface/lib/greeting/facts.js +320 -0
- package/neural-interface/lib/greeting/sources.js +252 -0
- package/neural-interface/lib/greeting/writer.js +139 -0
- package/neural-interface/lib/http-guards.js +189 -13
- package/neural-interface/lib/machine-names.js +88 -0
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-loop-providers.js +10 -5
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/package-lock.json +701 -2
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/install-app.html +9 -6
- package/neural-interface/public/offline.html +3 -2
- package/neural-interface/public/onboarding.html +12 -1
- package/neural-interface/public/shared/agent-runtime-options.js +22 -6
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +18 -58
- package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
- package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
- package/neural-interface/public/shared/assistant/asst-render.js +162 -19
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +23 -329
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
- package/neural-interface/public/shared/assistant/asst-state.js +185 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +158 -4
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +15 -0
- package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
- package/neural-interface/public/shared/editor/editor-ai.js +619 -0
- package/neural-interface/public/shared/editor/editor-commands.js +212 -0
- package/neural-interface/public/shared/editor/editor-compare.js +103 -0
- package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
- package/neural-interface/public/shared/editor/editor-doc.js +257 -0
- package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
- package/neural-interface/public/shared/editor/editor-git.js +95 -0
- package/neural-interface/public/shared/editor/editor-languages.js +156 -0
- package/neural-interface/public/shared/editor/editor-lint.js +229 -0
- package/neural-interface/public/shared/editor/editor-loader.js +51 -0
- package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
- package/neural-interface/public/shared/editor/editor-menu.js +233 -0
- package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
- package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
- package/neural-interface/public/shared/editor/editor-palette.js +125 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
- package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
- package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
- package/neural-interface/public/shared/editor/editor-safety.js +500 -0
- package/neural-interface/public/shared/editor/editor-search.js +352 -0
- package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
- package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
- package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
- package/neural-interface/public/shared/editor/editor-theme.js +244 -0
- package/neural-interface/public/shared/editor/editor-view.js +332 -0
- package/neural-interface/public/shared/explorer/fx-api.js +216 -0
- package/neural-interface/public/shared/explorer/fx-links.js +204 -0
- package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
- package/neural-interface/public/shared/explorer/fx-main.js +865 -0
- package/neural-interface/public/shared/explorer/fx-names.js +87 -0
- package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
- package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
- package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
- package/neural-interface/public/shared/file-links.js +339 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
- package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
- package/neural-interface/public/shared/html-shell.js +160 -165
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +29 -1
- package/neural-interface/public/shared/pwa-install.css +3 -3
- package/neural-interface/public/shared/pwa-install.js +100 -1
- package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
- package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
- package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
- package/neural-interface/public/shared/settings/settings-ia.js +18 -4
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/start-bridge.js +238 -19
- package/neural-interface/public/shared/styles.css +1414 -721
- package/neural-interface/public/shared/synabun-mascot.js +20 -56
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +157 -16
- package/neural-interface/public/shared/ui-detail.js +15 -0
- package/neural-interface/public/shared/ui-explorer.js +22 -0
- package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
- package/neural-interface/public/shared/ui-file-links.js +103 -0
- package/neural-interface/public/shared/ui-help.js +26 -0
- package/neural-interface/public/shared/ui-invite.js +126 -116
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +159 -15
- package/neural-interface/public/shared/ui-menubar.js +12 -30
- package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
- package/neural-interface/public/shared/ui-navbar.js +45 -21
- package/neural-interface/public/shared/ui-settings.js +455 -488
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sync.js +16 -0
- package/neural-interface/public/shared/ui-terminal.js +23 -5
- package/neural-interface/public/shared/ui-tunnel.js +130 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
- package/neural-interface/public/shared/ui-whiteboard.js +12 -0
- package/neural-interface/public/shared/ui-worker-end.js +245 -0
- package/neural-interface/public/shared/ui-workspaces.js +41 -27
- package/neural-interface/public/shared/worker-end.js +251 -0
- package/neural-interface/public/sw.js +2 -2
- package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
- package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
- package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
- package/neural-interface/scripts/build-vendor.mjs +78 -2
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +873 -1028
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +2 -8
- package/setup.js +44 -7
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/neural-interface/lib/codex-capabilities.d.ts +0 -41
- package/neural-interface/public/category-logos/claude.svg +0 -1
- package/neural-interface/public/category-logos/gemini-color.svg +0 -1
- package/neural-interface/public/category-logos/openai.svg +0 -1
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude.svg +0 -1
- package/neural-interface/public/gallery.svg +0 -6
- package/neural-interface/public/leonardoai.svg +0 -1
- package/neural-interface/public/mcp.svg +0 -4
- package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
- package/neural-interface/public/pin-svgrepo-com.svg +0 -25
- package/neural-interface/public/sessionicon.svg +0 -8
- package/neural-interface/public/toolbar-album.svg +0 -8
- package/neural-interface/public/toolbar-question.svg +0 -6
- package/neural-interface/public/toolbar-sessions.svg +0 -6
- package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
// ── AI autocomplete through OpenCode models: a dedicated, lean `opencode serve` ──
|
|
2
|
+
// A fill-in request must be short and quick. On the shared serve (port 4096) it
|
|
3
|
+
// carries the user's instruction files and MCP tool definitions, 4,700 to 9,300
|
|
4
|
+
// input tokens before a line of code, and every request would show up as a
|
|
5
|
+
// session in the OpenCode panel. So completions get a serve of their own:
|
|
6
|
+
//
|
|
7
|
+
// - its own XDG_CONFIG_HOME (DATA_HOME/data/editor-complete/) whose config
|
|
8
|
+
// holds what makes the user's models reachable (their `provider` blocks and
|
|
9
|
+
// the provider allow / deny lists) and one agent, `synabun-complete`: the
|
|
10
|
+
// completion instruction as its prompt, every tool denied. No MCP servers,
|
|
11
|
+
// no instruction files, no plugins. A provider block that names a file by
|
|
12
|
+
// a path relative to the user's configuration ({file:./key}) is written
|
|
13
|
+
// with that path made whole: here it would be looked for in this folder;
|
|
14
|
+
// - OPENCODE_DISABLE_CLAUDE_CODE and OPENCODE_DISABLE_PROJECT_CONFIG, so no
|
|
15
|
+
// CLAUDE.md, AGENTS.md or opencode.json of a folder above is read either;
|
|
16
|
+
// - the user's own data home, so stored provider credentials keep working.
|
|
17
|
+
//
|
|
18
|
+
// Measured on OpenCode 1.18.34 (2026-10-12): about 200 input tokens of overhead
|
|
19
|
+
// per request against 5,500 with the tools left on, and 810 with an AGENTS.md
|
|
20
|
+
// one folder up and project configuration left on.
|
|
21
|
+
//
|
|
22
|
+
// Started on the first request, stopped after ten idle minutes and at server
|
|
23
|
+
// shutdown. One request is one session in an empty working folder: create,
|
|
24
|
+
// prompt with the model and the agent, read the text parts, delete. A start is
|
|
25
|
+
// over, and the serve takes requests, only once the sessions a crash left in
|
|
26
|
+
// that folder are deleted: every request that arrives meanwhile waits for the
|
|
27
|
+
// same start, so no request's own session is ever taken for a leftover. A start
|
|
28
|
+
// that fails after its process was spawned (the serve never answers, its client
|
|
29
|
+
// cannot be built, a stop is asked for meanwhile) stops that process and fails
|
|
30
|
+
// for everybody who waited for it; stop() ends a start that is under way too.
|
|
31
|
+
// There is no fallback to the shared serve. Everything that touches a process,
|
|
32
|
+
// a port or the network is injected: tests run against a fake.
|
|
33
|
+
|
|
34
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
35
|
+
import { isAbsolute, join, resolve, sep } from 'node:path';
|
|
36
|
+
|
|
37
|
+
export const COMPLETION_AGENT = 'synabun-complete';
|
|
38
|
+
/** Every session of this serve carries this title: no title is generated, and leftovers of a crash are recognisable. */
|
|
39
|
+
export const COMPLETION_SESSION_TITLE = 'synabun-editor-complete';
|
|
40
|
+
export const IDLE_STOP_MS = 10 * 60_000;
|
|
41
|
+
export const START_TIMEOUT_MS = 15_000;
|
|
42
|
+
/** How long a start waits for the sessions a crash left behind to be listed and deleted. */
|
|
43
|
+
export const SWEEP_TIMEOUT_MS = 5000;
|
|
44
|
+
|
|
45
|
+
// What of the user's OpenCode configuration decides which models can be reached.
|
|
46
|
+
const CARRIED_KEYS = ['provider', 'disabled_providers', 'enabled_providers'];
|
|
47
|
+
// Reasoning settings a model may offer, the lightest first: a completion wants the least.
|
|
48
|
+
const LIGHT_VARIANTS = ['none', 'minimal', 'low'];
|
|
49
|
+
|
|
50
|
+
const isObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
51
|
+
|
|
52
|
+
// OpenCode replaces {file:path} in a configuration with the file's text. A path that is neither absolute nor
|
|
53
|
+
// from the home folder (~/) is read from the folder the configuration is in.
|
|
54
|
+
const FILE_REFERENCE = /\{file:([^}]+)\}/g;
|
|
55
|
+
const isRelativeReference = (path) => !(path.startsWith('~/') || isAbsolute(path) || /^[A-Za-z]:[\\/]/.test(path));
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A value of the user's configuration with its relative {file:…} references written from `dir`.
|
|
59
|
+
* @param {any} value
|
|
60
|
+
* @param {string | null} dir the folder the user's configuration is in; without it the references stay as they are
|
|
61
|
+
* @param {{ relative: number }} seen counts the relative references met
|
|
62
|
+
*/
|
|
63
|
+
function rebaseFileReferences(value, dir, seen) {
|
|
64
|
+
if (typeof value === 'string') {
|
|
65
|
+
return value.replace(FILE_REFERENCE, (whole, path) => {
|
|
66
|
+
if (!isRelativeReference(path)) return whole;
|
|
67
|
+
seen.relative++;
|
|
68
|
+
if (!dir) return whole;
|
|
69
|
+
const absolute = resolve(dir, path);
|
|
70
|
+
return `{file:${sep === '/' ? absolute : absolute.split(sep).join('/')}}`;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(value)) return value.map((item) => rebaseFileReferences(item, dir, seen));
|
|
74
|
+
if (isObject(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, rebaseFileReferences(item, dir, seen)]));
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The configuration of the dedicated serve.
|
|
80
|
+
* @param {object | null} userConfig the user's own OpenCode configuration (~/.config/opencode/config.json)
|
|
81
|
+
* @param {{ system: string, configDir?: string | null, log?: (tag: string, message: string) => void }} o `system`:
|
|
82
|
+
* the completion instruction, the agent's whole prompt; `configDir`: the folder the user's configuration is in,
|
|
83
|
+
* which a relative {file:…} reference of a provider is resolved against. Without it, a provider that holds such
|
|
84
|
+
* a reference is left out (and logged): from the serve's own folder it would name another file, or none.
|
|
85
|
+
*/
|
|
86
|
+
export function buildCompletionServeConfig(userConfig, { system, configDir = null, log = () => {} }) {
|
|
87
|
+
const config = { $schema: 'https://opencode.ai/config.json', autoupdate: false, share: 'disabled', snapshot: false };
|
|
88
|
+
for (const key of CARRIED_KEYS) {
|
|
89
|
+
const value = isObject(userConfig) ? userConfig[key] : undefined;
|
|
90
|
+
if (key !== 'provider') { if (Array.isArray(value)) config[key] = structuredClone(value); continue; }
|
|
91
|
+
if (!isObject(value)) continue;
|
|
92
|
+
const providers = {};
|
|
93
|
+
for (const [id, definition] of Object.entries(value)) {
|
|
94
|
+
const seen = { relative: 0 };
|
|
95
|
+
const carried = rebaseFileReferences(structuredClone(definition), configDir, seen);
|
|
96
|
+
if (seen.relative && !configDir) {
|
|
97
|
+
log('editor-complete:serve', `the provider "${id}" reads a file by a path relative to the OpenCode configuration, whose folder is not known here: it is left out of the completion server`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
providers[id] = carried;
|
|
101
|
+
}
|
|
102
|
+
if (Object.keys(providers).length || !Object.keys(value).length) config.provider = providers;
|
|
103
|
+
}
|
|
104
|
+
config.agent = {
|
|
105
|
+
[COMPLETION_AGENT]: {
|
|
106
|
+
description: 'SynaBun code editor autocomplete',
|
|
107
|
+
mode: 'primary',
|
|
108
|
+
prompt: system,
|
|
109
|
+
temperature: 0.1,
|
|
110
|
+
permission: { '*': 'deny' },
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
return config;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The environment of the dedicated serve: the server's own, pointed at the lean configuration and told to read
|
|
118
|
+
* nothing else.
|
|
119
|
+
* @param {object} base the environment to start from (process.env)
|
|
120
|
+
* @param {{ configHome: string }} o the XDG_CONFIG_HOME that holds opencode/config.json
|
|
121
|
+
*/
|
|
122
|
+
export function completionServeEnvironment(base, { configHome }) {
|
|
123
|
+
const env = { ...base };
|
|
124
|
+
// Anything that would add configuration on top of the folder below.
|
|
125
|
+
for (const key of ['OPENCODE_CONFIG', 'OPENCODE_CONFIG_CONTENT', 'OPENCODE_CONFIG_DIR', 'CLAUDECODE']) delete env[key];
|
|
126
|
+
return {
|
|
127
|
+
...env,
|
|
128
|
+
XDG_CONFIG_HOME: configHome,
|
|
129
|
+
OPENCODE_DISABLE_CLAUDE_CODE: '1',
|
|
130
|
+
OPENCODE_DISABLE_PROJECT_CONFIG: '1',
|
|
131
|
+
OPENCODE_DISABLE_EXTERNAL_SKILLS: '1',
|
|
132
|
+
OPENCODE_DISABLE_AUTOUPDATE: '1',
|
|
133
|
+
OPENCODE_DISABLE_LSP_DOWNLOAD: '1',
|
|
134
|
+
FORCE_COLOR: '0',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The lightest reasoning setting among a model's variants, or undefined when it offers none of them. */
|
|
139
|
+
export function lightestVariant(names) {
|
|
140
|
+
const list = Array.isArray(names) ? names : [];
|
|
141
|
+
return LIGHT_VARIANTS.find((name) => list.includes(name));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** `{ providerID, modelID }` of a "provider/model" id; the model's own id may hold slashes. */
|
|
145
|
+
export function splitModelId(id) {
|
|
146
|
+
const text = String(id ?? '');
|
|
147
|
+
const slash = text.indexOf('/');
|
|
148
|
+
if (slash <= 0 || slash >= text.length - 1) throw new Error(`Not a model id: ${text || '(empty)'}`);
|
|
149
|
+
return { providerID: text.slice(0, slash), modelID: text.slice(slash + 1) };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Each connected model's variant names, from the serve's /provider answer: Map("provider/model" → string[]). */
|
|
153
|
+
export function variantsByModel(providers) {
|
|
154
|
+
const out = new Map();
|
|
155
|
+
const body = isObject(providers?.data) && (providers.data.all || providers.data.connected) ? providers.data : providers;
|
|
156
|
+
const connected = Array.isArray(body?.connected) ? new Set(body.connected.map(String)) : null;
|
|
157
|
+
for (const provider of Array.isArray(body?.all) ? body.all : []) {
|
|
158
|
+
const pid = provider?.id;
|
|
159
|
+
if (!pid || (connected && !connected.has(pid))) continue;
|
|
160
|
+
for (const [mid, model] of Object.entries(isObject(provider.models) ? provider.models : {})) {
|
|
161
|
+
if (isObject(model?.variants)) out.set(`${pid}/${model.id || mid}`, Object.keys(model.variants));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The text and the token counts of a prompt's answer (`POST /session/{id}/message`).
|
|
169
|
+
* @returns {{ text: string, tokens: { input: number, output: number }, error: string | null }} `input` counts the
|
|
170
|
+
* tokens read from and written to the provider's cache too; `output` counts reasoning
|
|
171
|
+
*/
|
|
172
|
+
export function readPromptResult(data) {
|
|
173
|
+
const parts = Array.isArray(data?.parts) ? data.parts : [];
|
|
174
|
+
const text = parts.filter((part) => part?.type === 'text' && typeof part.text === 'string').map((part) => part.text).join('');
|
|
175
|
+
const t = data?.info?.tokens || {};
|
|
176
|
+
const n = (v) => Math.max(0, Math.round(Number(v) || 0));
|
|
177
|
+
const failure = data?.info?.error;
|
|
178
|
+
const error = failure ? String(failure.data?.message || failure.message || failure.name || 'The model call failed') : null;
|
|
179
|
+
return {
|
|
180
|
+
text,
|
|
181
|
+
tokens: { input: n(t.input) + n(t.cache?.read) + n(t.cache?.write), output: n(t.output) + n(t.reasoning) },
|
|
182
|
+
error,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function abortError() {
|
|
187
|
+
const error = new Error('The request was aborted');
|
|
188
|
+
error.name = 'AbortError';
|
|
189
|
+
return error;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @param {object} deps
|
|
194
|
+
* @param {string} deps.dataDir DATA_HOME/data/editor-complete: holds opencode/config.json and the empty work folder
|
|
195
|
+
* @param {string} deps.system the completion instruction (the agent's prompt)
|
|
196
|
+
* @param {() => object | null} deps.readUserConfig the user's OpenCode configuration, read at each start
|
|
197
|
+
* @param {() => string | null} [deps.userConfigDir] the folder that configuration is in (relative {file:…}
|
|
198
|
+
* references of its providers are resolved against it)
|
|
199
|
+
* @param {() => number | null} deps.allocPort a free port (server.js allocOpencodeIsoPort)
|
|
200
|
+
* @param {(o: { port: number, env: object, cwd: string }) => object} deps.spawnServe starts `opencode serve` on the
|
|
201
|
+
* port and returns its process (`on('exit')`, `kill()`)
|
|
202
|
+
* @param {(proc: object) => void} [deps.killServe] stops that process (default: SIGTERM)
|
|
203
|
+
* @param {(o: { port: number }) => object} deps.createClient lib/opencode-v2-client.js createClientInstance
|
|
204
|
+
* @param {typeof fetch} [deps.fetchImpl]
|
|
205
|
+
* @param {object} [deps.env] the environment the serve starts from
|
|
206
|
+
* @param {number} [deps.idleMs] the serve is stopped after this long without a request
|
|
207
|
+
* @param {number} [deps.startTimeoutMs]
|
|
208
|
+
* @param {number} [deps.sweepTimeoutMs] how long a start waits for the leftover sessions to be listed and deleted
|
|
209
|
+
* @param {number} [deps.healthPollMs]
|
|
210
|
+
*/
|
|
211
|
+
export function createOpencodeCompletionEngine({
|
|
212
|
+
dataDir, system, readUserConfig = () => null, userConfigDir = () => null, allocPort, spawnServe,
|
|
213
|
+
killServe = (proc) => { try { proc.kill('SIGTERM'); } catch { /* gone */ } },
|
|
214
|
+
createClient, fetchImpl = (...args) => fetch(...args), env = process.env, now = Date.now,
|
|
215
|
+
idleMs = IDLE_STOP_MS, startTimeoutMs = START_TIMEOUT_MS, sweepTimeoutMs = SWEEP_TIMEOUT_MS, healthPollMs = 100, log = () => {},
|
|
216
|
+
} = {}) {
|
|
217
|
+
if (!dataDir || !system) throw new Error('createOpencodeCompletionEngine requires dataDir and system');
|
|
218
|
+
if (typeof allocPort !== 'function' || typeof spawnServe !== 'function' || typeof createClient !== 'function') {
|
|
219
|
+
throw new Error('createOpencodeCompletionEngine requires allocPort, spawnServe and createClient');
|
|
220
|
+
}
|
|
221
|
+
const workDir = join(dataDir, 'work');
|
|
222
|
+
let entry = null; // the serve that takes requests: { port, proc, client, variants, providers, exited }
|
|
223
|
+
let starting = null; // the start under way, shared by everything that waits for it (it ends when the serve is ready)
|
|
224
|
+
let spawned = null; // the serve of that start, from its spawn until it is ready or given up: stop() ends it too
|
|
225
|
+
let epoch = 0; // moves on with every stop: a start that finishes after one is not kept
|
|
226
|
+
let idleTimer = null;
|
|
227
|
+
let lastUsed = 0;
|
|
228
|
+
let starts = 0;
|
|
229
|
+
const deleting = new Set(); // session deletions on their way: a stop waits for them (drain)
|
|
230
|
+
|
|
231
|
+
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
232
|
+
const serveUrl = (port, path) => `http://127.0.0.1:${port}${path}${path.includes('?') ? '&' : '?'}directory=${encodeURIComponent(workDir)}`;
|
|
233
|
+
|
|
234
|
+
function touch() {
|
|
235
|
+
lastUsed = now();
|
|
236
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
237
|
+
idleTimer = setTimeout(() => { idleTimer = null; stop('idle'); }, idleMs);
|
|
238
|
+
idleTimer.unref?.();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** End one serve: its client's event stream, and its process when that still runs. */
|
|
242
|
+
function end(serve) {
|
|
243
|
+
try { serve.client?.stop?.(); } catch { /* never started its event stream */ }
|
|
244
|
+
if (serve.proc && !serve.exited && !serve.killed) { serve.killed = true; killServe(serve.proc); }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function stop(reason = 'stop') {
|
|
248
|
+
epoch++;
|
|
249
|
+
if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
|
|
250
|
+
const gone = entry;
|
|
251
|
+
entry = null;
|
|
252
|
+
// A start that has spawned its process and is not ready yet: the process goes now, and the start fails for
|
|
253
|
+
// everybody who waits for it as soon as it looks again (start()).
|
|
254
|
+
const half = spawned;
|
|
255
|
+
spawned = null;
|
|
256
|
+
if (half) {
|
|
257
|
+
log('editor-complete:serve', `stopping the completion server that was starting on port ${half.port} (${reason})`);
|
|
258
|
+
end(half);
|
|
259
|
+
half.halt?.();
|
|
260
|
+
}
|
|
261
|
+
if (!gone) return !!half;
|
|
262
|
+
log('editor-complete:serve', `stopping the completion server on port ${gone.port} (${reason})`);
|
|
263
|
+
end(gone);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function start() {
|
|
268
|
+
const mine = epoch;
|
|
269
|
+
const port = allocPort();
|
|
270
|
+
if (port == null) throw new Error('No free port for the completion server');
|
|
271
|
+
mkdirSync(join(dataDir, 'opencode'), { recursive: true });
|
|
272
|
+
mkdirSync(workDir, { recursive: true });
|
|
273
|
+
let userConfig = null;
|
|
274
|
+
try { userConfig = readUserConfig(); } catch (error) { log('editor-complete:serve', `could not read the OpenCode configuration: ${error?.message || error}`); }
|
|
275
|
+
let configDir = null;
|
|
276
|
+
try { configDir = userConfigDir() || null; } catch { configDir = null; }
|
|
277
|
+
writeFileSync(join(dataDir, 'opencode', 'config.json'), `${JSON.stringify(buildCompletionServeConfig(userConfig, { system, configDir, log }), null, 2)}\n`, 'utf8');
|
|
278
|
+
|
|
279
|
+
const started = { port, proc: null, client: null, variants: new Map(), providers: null, providersAt: 0, exited: false, killed: false, halt: null };
|
|
280
|
+
// What a stop wakes: a start that waits (for the serve to answer, for the next look) goes on at once.
|
|
281
|
+
const halted = new Promise((done) => { started.halt = done; });
|
|
282
|
+
started.proc = spawnServe({ port, env: completionServeEnvironment(env, { configHome: dataDir }), cwd: workDir });
|
|
283
|
+
starts++;
|
|
284
|
+
spawned = started;
|
|
285
|
+
// From here on a process runs. Whatever goes wrong before the serve is ready, it does not stay behind.
|
|
286
|
+
try {
|
|
287
|
+
started.proc?.on?.('exit', () => {
|
|
288
|
+
started.exited = true;
|
|
289
|
+
if (entry === started) { entry = null; if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; } }
|
|
290
|
+
});
|
|
291
|
+
started.proc?.on?.('error', () => { started.exited = true; });
|
|
292
|
+
|
|
293
|
+
// Both are checked before and after each wait: a stop may have been asked for, or the process may be gone.
|
|
294
|
+
const stillWanted = () => {
|
|
295
|
+
if (mine !== epoch) throw new Error('The completion server was stopped while it was starting');
|
|
296
|
+
if (started.exited) throw new Error('The completion server stopped while it was starting. Is OpenCode installed?');
|
|
297
|
+
};
|
|
298
|
+
const deadline = now() + startTimeoutMs;
|
|
299
|
+
for (;;) {
|
|
300
|
+
stillWanted();
|
|
301
|
+
try {
|
|
302
|
+
const res = await Promise.race([fetchImpl(`http://127.0.0.1:${port}/global/health`, { signal: AbortSignal.timeout(2000) }), halted]);
|
|
303
|
+
if (res?.ok) break;
|
|
304
|
+
} catch { /* not up yet */ }
|
|
305
|
+
stillWanted();
|
|
306
|
+
if (now() >= deadline) throw new Error(`The completion server did not answer within ${Math.round(startTimeoutMs / 1000)} seconds`);
|
|
307
|
+
await Promise.race([sleep(healthPollMs), halted]);
|
|
308
|
+
}
|
|
309
|
+
stillWanted();
|
|
310
|
+
|
|
311
|
+
started.client = createClient({ port });
|
|
312
|
+
// It answers, and takes no request yet. First the sessions a crash left behind go: a sweep that ran beside the
|
|
313
|
+
// first requests would find their sessions in its listing and delete them under the model call. Meanwhile,
|
|
314
|
+
// which lighter reasoning setting each model offers; worth a short wait, as without it the first completions
|
|
315
|
+
// think for longer.
|
|
316
|
+
await Promise.race([Promise.all([sweep(started), Promise.race([loadProviders(started).catch(() => null), sleep(1500)])]), halted]);
|
|
317
|
+
stillWanted();
|
|
318
|
+
entry = started;
|
|
319
|
+
touch();
|
|
320
|
+
log('editor-complete:serve', `completion server ready on port ${port}`);
|
|
321
|
+
return started;
|
|
322
|
+
} catch (error) {
|
|
323
|
+
end(started);
|
|
324
|
+
throw error;
|
|
325
|
+
} finally {
|
|
326
|
+
if (spawned === started) spawned = null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function ensure() {
|
|
331
|
+
if (entry) { touch(); return Promise.resolve(entry); }
|
|
332
|
+
if (!starting) starting = start().finally(() => { starting = null; });
|
|
333
|
+
return starting;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function loadProviders(serve, { signal } = {}) {
|
|
337
|
+
if (serve.providers && now() - serve.providersAt < 60_000) return serve.providers;
|
|
338
|
+
const res = await fetchImpl(serveUrl(serve.port, '/provider'), { signal: signal || AbortSignal.timeout(8000) });
|
|
339
|
+
if (!res.ok) throw new Error(`The completion server answered ${res.status} for its model list`);
|
|
340
|
+
serve.providers = await res.json();
|
|
341
|
+
serve.providersAt = now();
|
|
342
|
+
serve.variants = variantsByModel(serve.providers);
|
|
343
|
+
return serve.providers;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Delete the sessions a crash left behind in the work folder (a request deletes its own). Part of a start: the
|
|
348
|
+
* listing is asked for before the serve takes requests, so nothing in it is a request's own session. It never
|
|
349
|
+
* throws, and it does not hold a start for longer than `sweepTimeoutMs`: a listing that has not answered by
|
|
350
|
+
* then is given up on, and deletes nothing when it does answer (by then it may hold sessions of requests).
|
|
351
|
+
*/
|
|
352
|
+
async function sweep(serve) {
|
|
353
|
+
let phase = 'listing';
|
|
354
|
+
const work = (async () => {
|
|
355
|
+
const listed = await serve.client.session.list({ directory: workDir });
|
|
356
|
+
if (phase !== 'listing') return;
|
|
357
|
+
phase = 'deleting';
|
|
358
|
+
const rows = Array.isArray(listed?.data) ? listed.data : [];
|
|
359
|
+
for (const row of rows.filter((r) => r?.title === COMPLETION_SESSION_TITLE).slice(0, 200)) {
|
|
360
|
+
await serve.client.session.delete({ sessionID: row.id, directory: workDir }).catch(() => {});
|
|
361
|
+
}
|
|
362
|
+
})().catch((error) => { log('editor-complete:serve', `could not delete the sessions a crash left behind: ${error?.message || error}`); });
|
|
363
|
+
let timer = null;
|
|
364
|
+
const late = await Promise.race([work.then(() => false), new Promise((done) => { timer = setTimeout(() => done(true), sweepTimeoutMs); timer.unref?.(); })]);
|
|
365
|
+
clearTimeout(timer);
|
|
366
|
+
if (!late) return;
|
|
367
|
+
if (phase === 'listing') {
|
|
368
|
+
phase = 'abandoned';
|
|
369
|
+
log('editor-complete:serve', 'the sessions a crash left behind were not listed in time: none is deleted at this start');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// Listed in time, still being deleted: those are leftovers and nothing else. A stop waits for them (drain).
|
|
373
|
+
deleting.add(work);
|
|
374
|
+
work.finally(() => deleting.delete(work));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* One completion.
|
|
379
|
+
* @param {{ model: string, user: string, signal?: AbortSignal }} o `model`: "provider/model"; `user`: the user
|
|
380
|
+
* message (the system message is the agent's prompt)
|
|
381
|
+
* @returns {Promise<{ text: string, tokens: { input: number, output: number } }>}
|
|
382
|
+
*/
|
|
383
|
+
async function complete({ model, user, signal } = {}) {
|
|
384
|
+
const ref = splitModelId(model);
|
|
385
|
+
if (signal?.aborted) throw abortError();
|
|
386
|
+
const serve = await ensure();
|
|
387
|
+
if (signal?.aborted) throw abortError();
|
|
388
|
+
const created = await serve.client.session.create({ directory: workDir, title: COMPLETION_SESSION_TITLE });
|
|
389
|
+
const sessionID = created?.data?.id;
|
|
390
|
+
if (!sessionID) throw new Error('OpenCode did not create a session for the completion');
|
|
391
|
+
// The page gave up: the model call is cancelled in the serve, not left to finish unseen.
|
|
392
|
+
const onAbort = () => { Promise.resolve(serve.client.session.abort({ sessionID, directory: workDir })).catch(() => {}); };
|
|
393
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
394
|
+
try {
|
|
395
|
+
if (signal?.aborted) throw abortError();
|
|
396
|
+
const response = await serve.client.session.prompt({
|
|
397
|
+
sessionID, directory: workDir, model: ref, agent: COMPLETION_AGENT,
|
|
398
|
+
variant: lightestVariant(serve.variants.get(model)),
|
|
399
|
+
parts: [{ type: 'text', text: user }], signal,
|
|
400
|
+
});
|
|
401
|
+
const result = readPromptResult(response?.data);
|
|
402
|
+
if (result.error) throw new Error(result.error);
|
|
403
|
+
return { text: result.text, tokens: result.tokens };
|
|
404
|
+
} finally {
|
|
405
|
+
signal?.removeEventListener('abort', onAbort);
|
|
406
|
+
if (entry === serve) touch();
|
|
407
|
+
// Not waited for: the answer is not held back by the tidying up.
|
|
408
|
+
const gone = Promise.resolve().then(() => serve.client.session.delete({ sessionID, directory: workDir })).catch(() => {});
|
|
409
|
+
deleting.add(gone);
|
|
410
|
+
gone.finally(() => deleting.delete(gone));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Wait, for at most `timeoutMs`, until the sessions of finished requests are deleted (before a stop). */
|
|
415
|
+
async function drain(timeoutMs = 1000) {
|
|
416
|
+
if (!deleting.size) return;
|
|
417
|
+
await Promise.race([Promise.allSettled([...deleting]), sleep(timeoutMs)]);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
complete,
|
|
422
|
+
/**
|
|
423
|
+
* The serve's /provider answer (every known provider, and which are connected), or null while the serve is
|
|
424
|
+
* not running and `start` is not set.
|
|
425
|
+
*/
|
|
426
|
+
async providers({ start: startIt = false, signal } = {}) {
|
|
427
|
+
const serve = startIt ? await ensure() : entry;
|
|
428
|
+
return serve ? loadProviders(serve, { signal }) : null;
|
|
429
|
+
},
|
|
430
|
+
running: () => !!entry,
|
|
431
|
+
drain,
|
|
432
|
+
stop,
|
|
433
|
+
status: () => ({ running: !!entry, starting: !!starting, port: entry?.port ?? null, lastUsed: lastUsed || null, starts }),
|
|
434
|
+
workDir,
|
|
435
|
+
};
|
|
436
|
+
}
|