synabun 2.0.9 → 2.0.10
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 +29 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +46 -4
- package/launcher.mjs +9 -3
- package/lib/start-launcher.js +6 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/neural-interface/i18n/en.json +218 -7
- package/neural-interface/i18n/pt-BR.json +220 -9
- package/neural-interface/lib/account-usage.js +5 -3
- package/neural-interface/lib/app-controls-api.js +161 -0
- package/neural-interface/lib/assistant-api.js +21 -7
- package/neural-interface/lib/assistant-brains/opencode.js +13 -1
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +44 -0
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +57 -0
- package/neural-interface/lib/assistant-catalog.js +17 -2
- package/neural-interface/lib/assistant-dispatch.js +3 -0
- package/neural-interface/lib/assistant-envelope.js +19 -1
- package/neural-interface/lib/assistant-runtime.js +127 -8
- package/neural-interface/lib/claude-model-catalog.js +64 -14
- package/neural-interface/lib/codex-settings-schema.js +109 -47
- package/neural-interface/lib/editor-completion-opencode.js +132 -35
- package/neural-interface/lib/exec-loop-command.js +8 -1
- package/neural-interface/lib/launch-cache.js +293 -0
- package/neural-interface/lib/login-start.js +524 -0
- package/neural-interface/lib/native-loop-providers.js +18 -3
- package/neural-interface/lib/native-loop-runtime.js +19 -0
- package/neural-interface/lib/opencode-db-schema.js +75 -0
- package/neural-interface/lib/opencode-history.js +16 -1
- package/neural-interface/lib/opencode-protocol.js +173 -0
- package/neural-interface/lib/opencode-provider-view.js +81 -0
- package/neural-interface/lib/opencode-runtime-config.js +10 -3
- package/neural-interface/lib/opencode-serve-records.js +606 -0
- package/neural-interface/lib/opencode-v2-client.js +33 -6
- package/neural-interface/lib/opencode-v2-ws-requests.js +19 -5
- package/neural-interface/lib/opencode-worker-activity.js +229 -0
- package/neural-interface/lib/opencode2-client.js +760 -0
- package/neural-interface/lib/opencode2-proxy.js +116 -0
- package/neural-interface/lib/opencode2-translate.js +1008 -0
- package/neural-interface/lib/process-identity.js +361 -0
- package/neural-interface/lib/session-cache-builder.js +26 -5
- package/neural-interface/lib/session-title-generator.js +18 -0
- package/neural-interface/lib/style-guide/api.js +4 -3
- package/neural-interface/lib/untrusted-file-headers.js +27 -0
- package/neural-interface/package-lock.json +1343 -45
- package/neural-interface/package.json +5 -1
- package/neural-interface/public/index.html +2 -2
- package/neural-interface/public/shared/agent-runtime-options.js +55 -11
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +172 -17
- package/neural-interface/public/shared/assistant/asst-api.js +11 -3
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +195 -80
- package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
- package/neural-interface/public/shared/assistant/asst-hosts.js +49 -2
- package/neural-interface/public/shared/assistant/asst-menu.js +77 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +197 -33
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +28 -10
- package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
- package/neural-interface/public/shared/assistant/asst-render.js +382 -36
- package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
- package/neural-interface/public/shared/assistant/asst-scenes.js +528 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
- package/neural-interface/public/shared/assistant/asst-state.js +14 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +43 -0
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +13 -1
- package/neural-interface/public/shared/cc-dropdown.js +480 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +13 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +15 -3
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +149 -35
- package/neural-interface/public/shared/cp/cp-markdown.js +4 -57
- package/neural-interface/public/shared/data-cache.js +223 -0
- package/neural-interface/public/shared/desktop-bridge.js +11 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
- package/neural-interface/public/shared/desktop-titlebar.js +214 -28
- package/neural-interface/public/shared/desktop-update-view.js +15 -5
- package/neural-interface/public/shared/file-links.js +73 -8
- package/neural-interface/public/shared/html-shell.js +74 -80
- package/neural-interface/public/shared/launch-prefetch.js +59 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +3 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +7 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +93 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +3 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +16 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +60 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +48 -19
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +195 -19
- package/neural-interface/public/shared/opencode-providers.js +39 -0
- package/neural-interface/public/shared/schedules-view.js +318 -0
- package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
- package/neural-interface/public/shared/settings/settings-app.js +243 -0
- package/neural-interface/public/shared/settings/settings-data.js +88 -0
- package/neural-interface/public/shared/settings/settings-ia.js +17 -0
- package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
- package/neural-interface/public/shared/styles.css +540 -371
- package/neural-interface/public/shared/ui-assistant.js +27 -5
- package/neural-interface/public/shared/ui-automation-studio.js +67 -27
- package/neural-interface/public/shared/ui-bookmarks.js +2 -1
- package/neural-interface/public/shared/ui-claude-panel.js +76 -8
- package/neural-interface/public/shared/ui-codex-panel.js +1 -0
- package/neural-interface/public/shared/ui-detail.js +6 -6
- package/neural-interface/public/shared/ui-file-links.js +1 -0
- package/neural-interface/public/shared/ui-image-gallery.js +6 -5
- package/neural-interface/public/shared/ui-invite.js +18 -2
- package/neural-interface/public/shared/ui-link.js +9 -9
- package/neural-interface/public/shared/ui-menubar.js +232 -33
- package/neural-interface/public/shared/ui-native-loop-router.js +2 -0
- package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
- package/neural-interface/public/shared/ui-schedules-studio.js +2099 -869
- package/neural-interface/public/shared/ui-sessions.js +15 -4
- package/neural-interface/public/shared/ui-settings.js +87 -36
- package/neural-interface/public/shared/ui-sidebar.js +4 -4
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +55 -4
- package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
- package/neural-interface/public/shared/ui-skills.js +70 -12
- package/neural-interface/public/shared/ui-sync.js +7 -0
- package/neural-interface/public/shared/ui-terminal.js +36 -20
- package/neural-interface/public/shared/ui-trash.js +8 -1
- package/neural-interface/public/shared/ui-tutorial-steps.js +7 -3
- package/neural-interface/public/shared/ui-tutorial.js +15 -5
- package/neural-interface/public/shared/ui-update.js +83 -14
- package/neural-interface/public/shared/ui-whiteboard.js +2 -1
- package/neural-interface/public/variant/2d/main.js +4 -1
- package/neural-interface/public/variant/3d/main.js +3 -0
- package/neural-interface/public/vendor/highlight/LICENSE +29 -0
- package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
- package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
- package/neural-interface/public/vendor/highlight/manifest.json +5 -0
- package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
- package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
- package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
- package/neural-interface/public/vendor/marked/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
- package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
- package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -0
- package/neural-interface/scripts/build-vendor.mjs +84 -3
- package/neural-interface/server.js +1099 -286
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/synabun/modules/schedule.md +1 -1
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// OpenCode 2 client — the OpenCode 1 SDK's interface over OpenCode 2's API.
|
|
3
|
+
//
|
|
4
|
+
// Every OpenCode client in SynaBun is built by lib/opencode-v2-client.js
|
|
5
|
+
// (`connect`, `createClientInstance`) from the pinned @opencode-ai/sdk, which
|
|
6
|
+
// speaks OpenCode 1. For an OpenCode 2 serve it builds this instead: an object
|
|
7
|
+
// with the same groups and methods SynaBun calls on the SDK client
|
|
8
|
+
// (`session.prompt`, `global.event`, `permission.reply`, …), the same
|
|
9
|
+
// arguments, and the same `{ data, error, response }` results, in OpenCode 1's
|
|
10
|
+
// shapes (lib/opencode2-translate.js). So the wrapper, and everything above it
|
|
11
|
+
// (the OpenCode panel, the Assistant's OpenCode brain, workers, loops, the
|
|
12
|
+
// usage meter), runs unchanged on either major.
|
|
13
|
+
//
|
|
14
|
+
// What OpenCode 2 needs that OpenCode 1 did not:
|
|
15
|
+
// • HTTP Basic auth (user `opencode`, the launch's password) on every call;
|
|
16
|
+
// • the project folder as `location[directory]` (OpenCode 1: `directory`);
|
|
17
|
+
// • a prompt carries no model, agent or system prompt: the session is switched
|
|
18
|
+
// to the model and agent first (only when they change), and a system prompt
|
|
19
|
+
// is the session's instruction entry `synabun-system`;
|
|
20
|
+
// • a prompt returns at once: the synchronous `session.prompt` waits on
|
|
21
|
+
// /api/experimental/session/:id/wait and then reads the reply, and so does
|
|
22
|
+
// `session.command` (a slash command only queues its prompt);
|
|
23
|
+
// • a permission or a question is answered under its session, which the
|
|
24
|
+
// event translator remembers (or the pending lists say).
|
|
25
|
+
// Things OpenCode 2 does not have answer like an older serve would: an empty
|
|
26
|
+
// list (todos, LSP, formatters, symbols) or an error that says so (share).
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
import { basicAuthHeader } from './opencode-protocol.js';
|
|
30
|
+
import {
|
|
31
|
+
answersToForm,
|
|
32
|
+
createEventTranslator,
|
|
33
|
+
formToQuestionV1,
|
|
34
|
+
messagesToV1,
|
|
35
|
+
modelRefToV2,
|
|
36
|
+
permissionToV1,
|
|
37
|
+
rulesToV1,
|
|
38
|
+
rulesToV2,
|
|
39
|
+
sessionToV1,
|
|
40
|
+
v2ToolName,
|
|
41
|
+
} from './opencode2-translate.js';
|
|
42
|
+
|
|
43
|
+
const SYSTEM_ENTRY = 'synabun-system';
|
|
44
|
+
const PAGE_LIMIT = 200;
|
|
45
|
+
const MAX_PAGES = 50;
|
|
46
|
+
const WAIT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
47
|
+
|
|
48
|
+
const str = (v) => (typeof v === 'string' ? v : v == null ? '' : String(v));
|
|
49
|
+
const obj = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? v : {});
|
|
50
|
+
|
|
51
|
+
function notSupported(what) {
|
|
52
|
+
const error = { _tag: 'NotSupportedError', message: `${what} is not available on OpenCode 2.` };
|
|
53
|
+
return { data: undefined, error, response: { status: 501, ok: false } };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A diff mode as OpenCode 1 names it ('git', 'branch') → OpenCode 2's (`working`, `branch`, `committed`). */
|
|
57
|
+
export function vcsModeToV2(mode) {
|
|
58
|
+
if (mode === 'branch' || mode === 'committed' || mode === 'working') return mode;
|
|
59
|
+
return 'working';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `baseUrl` of the serve and the launch's `password`. `version` is the serve's
|
|
64
|
+
* (sessions carry it), `fetchImpl` a fetch (tests pass a fixture's).
|
|
65
|
+
*/
|
|
66
|
+
export function createOpencode2Client({ baseUrl, password, fetchImpl = globalThis.fetch, version = '' } = {}) {
|
|
67
|
+
if (!baseUrl) throw new Error('createOpencode2Client: baseUrl required');
|
|
68
|
+
const root = String(baseUrl).replace(/\/+$/, '');
|
|
69
|
+
const auth = password ? basicAuthHeader(password) : null;
|
|
70
|
+
const translator = createEventTranslator({ version });
|
|
71
|
+
// What each session was last switched to (model, agent, system prompt), so a
|
|
72
|
+
// prompt switches only what changed. `system` is undefined until this client
|
|
73
|
+
// has set or removed the session's entry: a session made by another client (a
|
|
74
|
+
// resumed one) may still hold the instructions of an earlier turn, so the
|
|
75
|
+
// first prompt here always says what it wants, an empty one included.
|
|
76
|
+
const sessionState = new Map();
|
|
77
|
+
const stateOf = (sessionID) => {
|
|
78
|
+
let state = sessionState.get(sessionID);
|
|
79
|
+
if (!state) { state = { model: null, agent: null, system: undefined }; sessionState.set(sessionID, state); }
|
|
80
|
+
return state;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function url(path, query) {
|
|
84
|
+
const search = new URLSearchParams();
|
|
85
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
86
|
+
if (value === undefined || value === null || value === '') continue;
|
|
87
|
+
search.append(key, String(value));
|
|
88
|
+
}
|
|
89
|
+
const qs = search.toString();
|
|
90
|
+
return `${root}${path}${qs ? `?${qs}` : ''}`;
|
|
91
|
+
}
|
|
92
|
+
const located = (directory, query = {}) => (directory ? { 'location[directory]': directory, ...query } : query);
|
|
93
|
+
|
|
94
|
+
/** One request → `{ data, error, response }`, like the SDK's (a network failure rejects). */
|
|
95
|
+
async function call(method, path, { query, body, signal, raw = false } = {}) {
|
|
96
|
+
const headers = { accept: raw ? '*/*' : 'application/json' };
|
|
97
|
+
if (auth) headers.authorization = auth;
|
|
98
|
+
if (body !== undefined) headers['content-type'] = 'application/json';
|
|
99
|
+
const res = await fetchImpl(url(path, query), {
|
|
100
|
+
method,
|
|
101
|
+
headers,
|
|
102
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
103
|
+
...(signal ? { signal } : {}),
|
|
104
|
+
});
|
|
105
|
+
const type = String(res.headers?.get?.('content-type') || '');
|
|
106
|
+
const response = { status: res.status, ok: res.ok, headers: res.headers };
|
|
107
|
+
if (res.status === 204) return { data: undefined, error: undefined, response };
|
|
108
|
+
if (raw && res.ok) return { data: await res.text(), error: undefined, response };
|
|
109
|
+
if (/text\/html/i.test(type)) {
|
|
110
|
+
// OpenCode 2 answers every path outside its API with its web app.
|
|
111
|
+
return { data: undefined, error: { _tag: 'NotOpencodeApiError', message: `OpenCode answered ${method} ${path} with a web page, not its API.` }, response: { ...response, ok: false, status: res.ok ? 502 : res.status } };
|
|
112
|
+
}
|
|
113
|
+
let parsed;
|
|
114
|
+
const text = await res.text();
|
|
115
|
+
try { parsed = text ? JSON.parse(text) : undefined; } catch { parsed = text; }
|
|
116
|
+
if (!res.ok) return { data: undefined, error: parsed ?? { message: `HTTP ${res.status}` }, response };
|
|
117
|
+
return { data: parsed, error: undefined, response };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** `{ data }` with `data` mapped, errors passed through. */
|
|
121
|
+
const map = (result, fn) => (result.error ? result : { ...result, data: fn(result.data) });
|
|
122
|
+
const ok = (result, data) => (result.error ? result : { ...result, data });
|
|
123
|
+
|
|
124
|
+
// ── Sessions ──
|
|
125
|
+
|
|
126
|
+
const noteAll = (rows) => rows.map((info) => translator.noteSession(info));
|
|
127
|
+
|
|
128
|
+
async function listSessions({ directory, roots, search, limit, parentID } = {}) {
|
|
129
|
+
const max = Number(limit) > 0 ? Number(limit) : Infinity;
|
|
130
|
+
const out = [];
|
|
131
|
+
let cursor = null;
|
|
132
|
+
let last = null;
|
|
133
|
+
for (let page = 0; page < MAX_PAGES && out.length < max; page++) {
|
|
134
|
+
// A cursor carries its own order and filters: OpenCode 2 refuses either next to one.
|
|
135
|
+
const query = cursor
|
|
136
|
+
? { cursor, limit: String(Math.min(PAGE_LIMIT, Number.isFinite(max) ? max - out.length : PAGE_LIMIT)) }
|
|
137
|
+
: {
|
|
138
|
+
directory,
|
|
139
|
+
search,
|
|
140
|
+
limit: String(Math.min(PAGE_LIMIT, Number.isFinite(max) ? max - out.length : PAGE_LIMIT)),
|
|
141
|
+
order: 'desc',
|
|
142
|
+
...(parentID ? { parentID } : (roots === true || roots === 'true' ? { parentID: 'null' } : {})),
|
|
143
|
+
};
|
|
144
|
+
last = await call('GET', '/api/session', { query });
|
|
145
|
+
if (last.error) return last;
|
|
146
|
+
const rows = Array.isArray(last.data?.data) ? last.data.data : [];
|
|
147
|
+
out.push(...rows);
|
|
148
|
+
cursor = last.data?.cursor?.next || null;
|
|
149
|
+
// OpenCode 2 hands out a next cursor even on the last page: a short page is the end.
|
|
150
|
+
if (!cursor || rows.length < Number(query.limit)) break;
|
|
151
|
+
}
|
|
152
|
+
const sessions = noteAll(out);
|
|
153
|
+
sessions.sort((a, b) => (b.time?.updated || 0) - (a.time?.updated || 0));
|
|
154
|
+
return { data: sessions, error: undefined, response: last?.response || { status: 200, ok: true } };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function getSession(sessionID) {
|
|
158
|
+
return map(await call('GET', `/api/session/${encodeURIComponent(sessionID)}`), (body) => translator.noteSession(body?.data));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function readMessages(sessionID, { limit } = {}) {
|
|
162
|
+
const items = [];
|
|
163
|
+
let cursor = null;
|
|
164
|
+
let last = null;
|
|
165
|
+
const max = Number(limit) > 0 ? Number(limit) : Infinity;
|
|
166
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
167
|
+
last = await call('GET', `/api/session/${encodeURIComponent(sessionID)}/message`, { query: cursor ? { cursor, limit: String(PAGE_LIMIT) } : { order: 'desc', limit: String(PAGE_LIMIT) } });
|
|
168
|
+
if (last.error) return last;
|
|
169
|
+
const rows = Array.isArray(last.data?.data) ? last.data.data : [];
|
|
170
|
+
items.push(...rows);
|
|
171
|
+
// A page is newest first; `next` walks back in time (and is there even on the last page).
|
|
172
|
+
cursor = last.data?.cursor?.next || null;
|
|
173
|
+
if (!cursor || rows.length < PAGE_LIMIT || items.length >= max * 4) break;
|
|
174
|
+
}
|
|
175
|
+
return { data: items, error: undefined, response: last?.response || { status: 200, ok: true } };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function messagesV1(sessionID, { directory, limit } = {}) {
|
|
179
|
+
const read = await readMessages(sessionID, { limit });
|
|
180
|
+
if (read.error) return read;
|
|
181
|
+
const dir = directory || translator.sessionDirectory(sessionID);
|
|
182
|
+
let rows = messagesToV1(read.data, { sessionID, directory: dir });
|
|
183
|
+
if (Number(limit) > 0 && rows.length > Number(limit)) rows = rows.slice(-Number(limit));
|
|
184
|
+
return { ...read, data: rows };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Switch a session to the model / agent / system prompt a request names, when they differ from the last ones. */
|
|
188
|
+
async function prepare(sessionID, { model, variant, agent, system } = {}) {
|
|
189
|
+
const state = stateOf(sessionID);
|
|
190
|
+
const ref = modelRefToV2(model, variant);
|
|
191
|
+
if (ref) {
|
|
192
|
+
const key = `${ref.providerID}/${ref.id}#${ref.variant || ''}`;
|
|
193
|
+
if (state.model !== key) {
|
|
194
|
+
const switched = await call('POST', `/api/session/${encodeURIComponent(sessionID)}/model`, { body: { model: ref } });
|
|
195
|
+
if (switched.error) return switched;
|
|
196
|
+
state.model = key;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (agent && state.agent !== agent) {
|
|
200
|
+
const switched = await call('POST', `/api/session/${encodeURIComponent(sessionID)}/agent`, { body: { agent: String(agent) } });
|
|
201
|
+
if (switched.error) return switched;
|
|
202
|
+
state.agent = String(agent);
|
|
203
|
+
}
|
|
204
|
+
const nextSystem = typeof system === 'string' ? system : '';
|
|
205
|
+
if (state.system !== nextSystem) {
|
|
206
|
+
const path = `/api/experimental/session/${encodeURIComponent(sessionID)}/instructions/entries/${SYSTEM_ENTRY}`;
|
|
207
|
+
const written = nextSystem
|
|
208
|
+
? await call('PUT', path, { body: { value: nextSystem } })
|
|
209
|
+
: await call('DELETE', path);
|
|
210
|
+
// Only an entry that is not there may fail to go: any other failure would
|
|
211
|
+
// run the turn under instructions it did not ask for.
|
|
212
|
+
if (written.error && (nextSystem || written.response?.status !== 404)) return written;
|
|
213
|
+
state.system = nextSystem;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* OpenCode 1 prompt parts → OpenCode 2's prompt body, and the synthetic text
|
|
220
|
+
* parts (context for the model the transcript never draws, like the
|
|
221
|
+
* greeting's note), which OpenCode 2 takes as synthetic messages.
|
|
222
|
+
*/
|
|
223
|
+
function promptBody({ parts, messageID, format } = {}) {
|
|
224
|
+
const texts = [];
|
|
225
|
+
const files = [];
|
|
226
|
+
const agents = [];
|
|
227
|
+
const synthetic = [];
|
|
228
|
+
for (const part of Array.isArray(parts) ? parts : []) {
|
|
229
|
+
if (!part || typeof part !== 'object') continue;
|
|
230
|
+
if (part.type === 'text' && typeof part.text === 'string' && part.synthetic === true) synthetic.push(part.text);
|
|
231
|
+
else if (part.type === 'text' && typeof part.text === 'string') texts.push(part.text);
|
|
232
|
+
else if (part.type === 'file' && part.url) files.push({ uri: str(part.url), ...(part.filename ? { name: str(part.filename) } : {}) });
|
|
233
|
+
else if (part.type === 'agent' && part.name) agents.push({ name: str(part.name) });
|
|
234
|
+
else if (part.type === 'subtask' && part.prompt) texts.push(str(part.prompt));
|
|
235
|
+
}
|
|
236
|
+
// OpenCode 2 has no structured-output mode: the schema becomes an instruction.
|
|
237
|
+
if (format?.type === 'json_schema' && format.schema) {
|
|
238
|
+
texts.push(`Reply with only a JSON value that matches this JSON schema, and nothing else:\n${JSON.stringify(format.schema)}`);
|
|
239
|
+
}
|
|
240
|
+
const body = { text: texts.join('\n\n') };
|
|
241
|
+
if (files.length) body.files = files;
|
|
242
|
+
if (agents.length) body.agents = agents;
|
|
243
|
+
if (messageID && /^msg_/.test(String(messageID))) body.id = String(messageID);
|
|
244
|
+
return { body, synthetic };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Send a prompt; resolves the SDK-shaped result and, when one was queued, the time OpenCode queued it. */
|
|
248
|
+
async function sendPrompt(params = {}, options = {}) {
|
|
249
|
+
const { sessionID, model, variant, agent, system, noReply } = params;
|
|
250
|
+
if (!sessionID) return { result: { data: undefined, error: { message: 'sessionID required' }, response: { status: 400, ok: false } } };
|
|
251
|
+
const failed = await prepare(sessionID, { model, variant, agent, system });
|
|
252
|
+
if (failed) return { result: failed };
|
|
253
|
+
const { body, synthetic } = promptBody(params);
|
|
254
|
+
const base = `/api/session/${encodeURIComponent(sessionID)}`;
|
|
255
|
+
// Context first (it must not start a turn of its own), then the prompt.
|
|
256
|
+
for (const text of noReply === true ? [...synthetic, body.text] : synthetic) {
|
|
257
|
+
if (!text) continue;
|
|
258
|
+
const added = await call('POST', `${base}/synthetic`, { body: { text, resume: false }, signal: options?.signal });
|
|
259
|
+
if (added.error) return { result: added };
|
|
260
|
+
}
|
|
261
|
+
if (noReply === true) return { result: { data: undefined, error: undefined, response: { status: 204, ok: true } } };
|
|
262
|
+
const sent = await call('POST', `${base}/prompt`, { body, signal: options?.signal });
|
|
263
|
+
if (sent.error) return { result: sent };
|
|
264
|
+
return {
|
|
265
|
+
result: { data: undefined, error: undefined, response: { ...sent.response, status: 204 } },
|
|
266
|
+
queuedAt: Number(sent.data?.data?.time?.created) || null,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function promptAsync(params = {}, options = {}) {
|
|
271
|
+
return (await sendPrompt(params, options)).result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Wait until the turn of something queued at `since` (or later) is over: an
|
|
276
|
+
* error result, or null. /wait answers once the session is idle, which may be
|
|
277
|
+
* before a prompt queued a moment ago has started, so the session must have
|
|
278
|
+
* gone idle since then. `quietEnds`: nothing queued and nothing running after
|
|
279
|
+
* `since` is the end too (a slash command that queued no prompt).
|
|
280
|
+
*/
|
|
281
|
+
async function waitForTurn(sessionID, { since, signal, directory, quietEnds = false } = {}) {
|
|
282
|
+
const base = `/api/session/${encodeURIComponent(sessionID)}`;
|
|
283
|
+
for (let round = 0; round < 1000; round++) {
|
|
284
|
+
const waited = await call('POST', `/api/experimental/session/${encodeURIComponent(sessionID)}/wait`, { signal });
|
|
285
|
+
if (waited.error) return waited;
|
|
286
|
+
if (!since) return null;
|
|
287
|
+
const info = await call('GET', base, { signal });
|
|
288
|
+
if (info.error) return info;
|
|
289
|
+
if (Number(info.data?.data?.time?.idle) >= since) return null;
|
|
290
|
+
if (quietEnds) {
|
|
291
|
+
const [queued, active] = await Promise.all([
|
|
292
|
+
call('GET', `${base}/inbox`, { signal }),
|
|
293
|
+
call('GET', '/api/session/active', { query: located(directory || translator.sessionDirectory(sessionID)), signal }),
|
|
294
|
+
]);
|
|
295
|
+
const pending = Array.isArray(queued.data?.data) && queued.data.data.length > 0;
|
|
296
|
+
if (!queued.error && !active.error && !pending && !obj(active.data?.data)[sessionID]) return null;
|
|
297
|
+
}
|
|
298
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 200));
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* The assistant message a turn begun at `before` ended with, as `{ data: { info, parts } }`. `latest`: when
|
|
305
|
+
* the turn left none, the session's last assistant message (a command that queued nothing answers with none).
|
|
306
|
+
*/
|
|
307
|
+
async function replyOf(sessionID, { directory, before, latest = true }) {
|
|
308
|
+
const transcript = await messagesV1(sessionID, { directory, limit: 50 });
|
|
309
|
+
if (transcript.error) return transcript;
|
|
310
|
+
const rows = transcript.data;
|
|
311
|
+
let reply = null;
|
|
312
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
313
|
+
if (rows[i].info.role === 'assistant' && (rows[i].info.time?.created || 0) >= before - 1000) { reply = rows[i]; break; }
|
|
314
|
+
if (rows[i].info.role === 'user') break;
|
|
315
|
+
}
|
|
316
|
+
if (!reply && latest) reply = rows.filter((row) => row.info.role === 'assistant').pop() || null;
|
|
317
|
+
// A turn of several steps answers with its last step, which holds the final text.
|
|
318
|
+
return { data: reply || { info: null, parts: [] }, error: undefined, response: { status: 200, ok: true } };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function prompt(params = {}, options = {}) {
|
|
322
|
+
const { sessionID } = params;
|
|
323
|
+
const before = Date.now();
|
|
324
|
+
const { result: sent, queuedAt } = await sendPrompt(params, options);
|
|
325
|
+
if (sent.error) return sent;
|
|
326
|
+
if (params.noReply === true) return { data: { info: null, parts: [] }, error: undefined, response: { status: 200, ok: true } };
|
|
327
|
+
const signal = options?.signal || AbortSignal.timeout(WAIT_TIMEOUT_MS);
|
|
328
|
+
const failed = await waitForTurn(sessionID, { since: queuedAt, signal });
|
|
329
|
+
if (failed) return failed;
|
|
330
|
+
return replyOf(sessionID, { directory: params.directory, before });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* A slash command (/review, /init, a skill, an MCP prompt). OpenCode 1
|
|
335
|
+
* answered when the command's turn was over; OpenCode 2 answers as soon as
|
|
336
|
+
* the command has queued its prompt, so the turn is waited for here (a stop
|
|
337
|
+
* ends it; `options.signal` gives up waiting). The command's attachments
|
|
338
|
+
* (screenshots, files, @ mentions) go as OpenCode 2's prompt fields, which
|
|
339
|
+
* the command's prompt carries.
|
|
340
|
+
*/
|
|
341
|
+
async function runCommand({ sessionID, command, arguments: args, agent, model, variant, parts, directory } = {}, options = {}) {
|
|
342
|
+
const failed = await prepare(sessionID, { model, variant, agent });
|
|
343
|
+
if (failed) return failed;
|
|
344
|
+
const { body: attached } = promptBody({ parts });
|
|
345
|
+
const body = { name: String(command || ''), text: String(args || '') };
|
|
346
|
+
if (attached.files) body.files = attached.files;
|
|
347
|
+
if (attached.agents) body.agents = attached.agents;
|
|
348
|
+
const signal = options?.signal || AbortSignal.timeout(WAIT_TIMEOUT_MS);
|
|
349
|
+
const before = Date.now();
|
|
350
|
+
const sent = await call('POST', `/api/session/${encodeURIComponent(sessionID)}/command`, { body, signal });
|
|
351
|
+
if (sent.error) return sent;
|
|
352
|
+
const waited = await waitForTurn(sessionID, { since: before, signal, directory, quietEnds: true });
|
|
353
|
+
if (waited) return waited;
|
|
354
|
+
return replyOf(sessionID, { directory, before, latest: false });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ── Events ──
|
|
358
|
+
|
|
359
|
+
/** `global.event()`: OpenCode 2's /api/event, translated, as `{ stream }` of `{ directory, payload }`. */
|
|
360
|
+
async function globalEvent(options = {}) {
|
|
361
|
+
const headers = { accept: 'text/event-stream' };
|
|
362
|
+
if (auth) headers.authorization = auth;
|
|
363
|
+
const res = await fetchImpl(url('/api/event'), { headers, ...(options?.signal ? { signal: options.signal } : {}) });
|
|
364
|
+
const type = String(res.headers?.get?.('content-type') || '');
|
|
365
|
+
if (!res.ok || !/text\/event-stream/i.test(type)) {
|
|
366
|
+
try { await res.body?.cancel?.(); } catch { /* nothing to release */ }
|
|
367
|
+
const why = res.status === 401 ? 'OpenCode 2 refused the password' : (/text\/html/i.test(type) ? 'OpenCode answered with a web page' : `HTTP ${res.status}`);
|
|
368
|
+
throw new Error(`OpenCode 2 event stream did not open: ${why}`);
|
|
369
|
+
}
|
|
370
|
+
const reader = res.body.getReader();
|
|
371
|
+
const decoder = new TextDecoder();
|
|
372
|
+
async function* stream() {
|
|
373
|
+
let buffer = '';
|
|
374
|
+
try {
|
|
375
|
+
while (true) {
|
|
376
|
+
const { value, done } = await reader.read();
|
|
377
|
+
if (done) break;
|
|
378
|
+
buffer += decoder.decode(value, { stream: true });
|
|
379
|
+
let cut;
|
|
380
|
+
while ((cut = buffer.search(/\r?\n\r?\n/)) >= 0) {
|
|
381
|
+
const frame = buffer.slice(0, cut);
|
|
382
|
+
buffer = buffer.slice(cut).replace(/^\r?\n\r?\n/, '');
|
|
383
|
+
const data = frame.split(/\r?\n/).filter((line) => line.startsWith('data:')).map((line) => line.slice(5).replace(/^ /, '')).join('\n');
|
|
384
|
+
if (!data) continue;
|
|
385
|
+
let event;
|
|
386
|
+
try { event = JSON.parse(data); } catch { continue; }
|
|
387
|
+
for (const translated of translator.translate(event)) yield translated;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} finally {
|
|
391
|
+
try { reader.releaseLock(); } catch { /* already released */ }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return { stream: stream() };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ── Asks ──
|
|
398
|
+
|
|
399
|
+
async function permissionSessionOf(requestID, directory) {
|
|
400
|
+
const known = translator.permissionSession(requestID);
|
|
401
|
+
if (known) return known;
|
|
402
|
+
const listed = await call('GET', '/api/permission/request', { query: located(directory) });
|
|
403
|
+
const found = (Array.isArray(listed.data?.data) ? listed.data.data : []).find((row) => row?.id === requestID);
|
|
404
|
+
if (found?.sessionID) translator.notePermission(requestID, found.sessionID);
|
|
405
|
+
return found?.sessionID || null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function formOf(requestID, directory) {
|
|
409
|
+
const known = translator.form(requestID);
|
|
410
|
+
if (known) return known;
|
|
411
|
+
const listed = await call('GET', '/api/form', { query: located(directory) });
|
|
412
|
+
const found = (Array.isArray(listed.data?.data) ? listed.data.data : []).find((row) => row?.id === requestID);
|
|
413
|
+
if (found) translator.noteForm(found);
|
|
414
|
+
return found || null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ── Providers and models ──
|
|
418
|
+
|
|
419
|
+
function modelToV1(model) {
|
|
420
|
+
const caps = obj(model.capabilities);
|
|
421
|
+
const input = Array.isArray(caps.input) ? caps.input : ['text'];
|
|
422
|
+
const output = Array.isArray(caps.output) ? caps.output : ['text'];
|
|
423
|
+
const cost = (Array.isArray(model.cost) ? model.cost : []).find((c) => !c?.tier) || (Array.isArray(model.cost) ? model.cost[0] : null) || {};
|
|
424
|
+
const variants = {};
|
|
425
|
+
for (const variant of Array.isArray(model.variants) ? model.variants : []) {
|
|
426
|
+
if (variant?.id) variants[variant.id] = { ...obj(variant.settings) };
|
|
427
|
+
}
|
|
428
|
+
const flags = (list) => ({ text: list.includes('text'), audio: list.includes('audio'), image: list.includes('image'), video: list.includes('video'), pdf: list.includes('pdf') });
|
|
429
|
+
const out = {
|
|
430
|
+
id: str(model.id),
|
|
431
|
+
providerID: str(model.providerID),
|
|
432
|
+
api: { id: str(model.modelID || model.id), url: str(model.settings?.baseURL), npm: str(model.package) },
|
|
433
|
+
name: str(model.name || model.id),
|
|
434
|
+
...(model.family ? { family: str(model.family) } : {}),
|
|
435
|
+
capabilities: {
|
|
436
|
+
temperature: caps.temperature !== false,
|
|
437
|
+
reasoning: !!(model.compatibility?.reasoningField || (Array.isArray(model.variants) && model.variants.length)),
|
|
438
|
+
attachment: input.some((kind) => kind !== 'text'),
|
|
439
|
+
toolcall: caps.tools !== false,
|
|
440
|
+
input: flags(input),
|
|
441
|
+
output: flags(output),
|
|
442
|
+
interleaved: false,
|
|
443
|
+
},
|
|
444
|
+
cost: { input: Number(cost.input) || 0, output: Number(cost.output) || 0, cache: { read: Number(cost.cache?.read) || 0, write: Number(cost.cache?.write) || 0 } },
|
|
445
|
+
limit: { context: Number(model.limit?.context) || 0, ...(model.limit?.input ? { input: Number(model.limit.input) } : {}), output: Number(model.limit?.output) || 0 },
|
|
446
|
+
status: str(model.status) || 'active',
|
|
447
|
+
options: {},
|
|
448
|
+
headers: obj(model.headers),
|
|
449
|
+
release_date: model.time?.released ? new Date(Number(model.time.released)).toISOString().slice(0, 10) : '',
|
|
450
|
+
};
|
|
451
|
+
if (Object.keys(variants).length) out.variants = variants;
|
|
452
|
+
return out;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** OpenCode 1's provider list from OpenCode 2's providers, enabled models and default model. */
|
|
456
|
+
async function providerCatalog(directory) {
|
|
457
|
+
const [providers, models, fallback] = await Promise.all([
|
|
458
|
+
call('GET', '/api/provider', { query: located(directory) }),
|
|
459
|
+
call('GET', '/api/model', { query: located(directory) }),
|
|
460
|
+
call('GET', '/api/model/default', { query: located(directory) }),
|
|
461
|
+
]);
|
|
462
|
+
if (providers.error) return { error: providers.error, response: providers.response };
|
|
463
|
+
if (models.error) return { error: models.error, response: models.response };
|
|
464
|
+
const byProvider = new Map();
|
|
465
|
+
for (const provider of Array.isArray(providers.data?.data) ? providers.data.data : []) {
|
|
466
|
+
byProvider.set(provider.id, {
|
|
467
|
+
id: str(provider.id),
|
|
468
|
+
name: str(provider.name || provider.id),
|
|
469
|
+
source: provider.activation === 'enabled' ? 'config' : 'api',
|
|
470
|
+
env: [],
|
|
471
|
+
options: {},
|
|
472
|
+
models: {},
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
for (const model of Array.isArray(models.data?.data) ? models.data.data : []) {
|
|
476
|
+
if (!model?.id || !model.providerID || model.enabled === false) continue;
|
|
477
|
+
let provider = byProvider.get(model.providerID);
|
|
478
|
+
if (!provider) {
|
|
479
|
+
provider = { id: str(model.providerID), name: str(model.providerID), source: 'api', env: [], options: {}, models: {} };
|
|
480
|
+
byProvider.set(model.providerID, provider);
|
|
481
|
+
}
|
|
482
|
+
provider.models[model.id] = modelToV1(model);
|
|
483
|
+
}
|
|
484
|
+
const all = [...byProvider.values()];
|
|
485
|
+
const connected = all.filter((provider) => Object.keys(provider.models).length).map((provider) => provider.id);
|
|
486
|
+
const defaults = {};
|
|
487
|
+
const fallbackModel = fallback.data?.data;
|
|
488
|
+
if (fallbackModel?.providerID && fallbackModel?.id) defaults[fallbackModel.providerID] = fallbackModel.id;
|
|
489
|
+
for (const provider of all) {
|
|
490
|
+
if (!defaults[provider.id]) {
|
|
491
|
+
const first = Object.keys(provider.models)[0];
|
|
492
|
+
if (first) defaults[provider.id] = first;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return { all, connected, defaults, response: providers.response };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ── The SDK-shaped client ──
|
|
499
|
+
|
|
500
|
+
const session = {
|
|
501
|
+
list: (p = {}) => listSessions(p),
|
|
502
|
+
create: async (p = {}) => {
|
|
503
|
+
const body = {
|
|
504
|
+
...(p.directory ? { location: { directory: String(p.directory) } } : {}),
|
|
505
|
+
...(p.parentID ? { parentID: String(p.parentID) } : {}),
|
|
506
|
+
...(p.title ? { title: String(p.title) } : {}),
|
|
507
|
+
...(p.agent ? { agent: String(p.agent) } : {}),
|
|
508
|
+
...(p.model ? { model: modelRefToV2(p.model.id ? { providerID: p.model.providerID, modelID: p.model.id, variant: p.model.variant } : p.model) } : {}),
|
|
509
|
+
...(p.metadata ? { metadata: p.metadata } : {}),
|
|
510
|
+
};
|
|
511
|
+
const rules = rulesToV2(p.permission);
|
|
512
|
+
if (rules && rules.length) body.permissions = rules;
|
|
513
|
+
if (body.model === null) delete body.model;
|
|
514
|
+
const created = await call('POST', '/api/session', { body });
|
|
515
|
+
return map(created, (res) => {
|
|
516
|
+
const info = translator.noteSession(res?.data);
|
|
517
|
+
if (info?.id) {
|
|
518
|
+
// A new session holds no instruction entry of SynaBun's.
|
|
519
|
+
stateOf(info.id).system = '';
|
|
520
|
+
if (body.agent) stateOf(info.id).agent = body.agent;
|
|
521
|
+
}
|
|
522
|
+
return info;
|
|
523
|
+
});
|
|
524
|
+
},
|
|
525
|
+
get: ({ sessionID } = {}) => getSession(sessionID),
|
|
526
|
+
update: async ({ sessionID, title, permission, metadata, time } = {}) => {
|
|
527
|
+
// OpenCode 2's update takes a title, metadata and permissions: archiving is not in its API.
|
|
528
|
+
if (time && typeof time === 'object' && 'archived' in time) return notSupported('Archiving a session');
|
|
529
|
+
const body = {};
|
|
530
|
+
if (typeof title === 'string') body.title = title;
|
|
531
|
+
if (metadata && typeof metadata === 'object') body.metadata = metadata;
|
|
532
|
+
const rules = rulesToV2(permission);
|
|
533
|
+
if (rules) body.permissions = rules;
|
|
534
|
+
const patched = await call('PATCH', `/api/session/${encodeURIComponent(sessionID)}`, { body });
|
|
535
|
+
if (patched.error) return patched;
|
|
536
|
+
return getSession(sessionID);
|
|
537
|
+
},
|
|
538
|
+
delete: async ({ sessionID } = {}) => {
|
|
539
|
+
const removed = await call('DELETE', `/api/session/${encodeURIComponent(sessionID)}`);
|
|
540
|
+
if (!removed.error) sessionState.delete(sessionID);
|
|
541
|
+
return ok(removed, true);
|
|
542
|
+
},
|
|
543
|
+
abort: async ({ sessionID } = {}) => map(await call('POST', `/api/session/${encodeURIComponent(sessionID)}/interrupt`), (body) => body?.interrupted !== false),
|
|
544
|
+
messages: ({ sessionID, directory, limit } = {}) => messagesV1(sessionID, { directory, limit }),
|
|
545
|
+
message: async ({ sessionID, messageID, directory } = {}) => {
|
|
546
|
+
const read = await call('GET', `/api/session/${encodeURIComponent(sessionID)}/message/${encodeURIComponent(messageID)}`);
|
|
547
|
+
return map(read, (body) => messagesToV1([body?.data].filter(Boolean), { sessionID, directory: directory || translator.sessionDirectory(sessionID) })[0] || null);
|
|
548
|
+
},
|
|
549
|
+
prompt: (p, o) => prompt(p, o),
|
|
550
|
+
promptAsync: (p, o) => promptAsync(p, o),
|
|
551
|
+
status: async ({ directory } = {}) => {
|
|
552
|
+
const active = await call('GET', '/api/session/active', { query: located(directory) });
|
|
553
|
+
return map(active, (body) => {
|
|
554
|
+
const out = {};
|
|
555
|
+
for (const [id, status] of Object.entries(obj(body?.data))) out[id] = status?.type === 'running' ? { type: 'busy' } : { type: 'idle' };
|
|
556
|
+
return out;
|
|
557
|
+
});
|
|
558
|
+
},
|
|
559
|
+
children: ({ sessionID, directory } = {}) => listSessions({ parentID: sessionID, directory }),
|
|
560
|
+
todo: async () => ({ data: [], error: undefined, response: { status: 200, ok: true } }),
|
|
561
|
+
diff: async ({ sessionID, messageID } = {}) => {
|
|
562
|
+
const diff = await call('GET', `/api/session/${encodeURIComponent(sessionID)}/diff`, { query: messageID ? { from: messageID } : {} });
|
|
563
|
+
return map(diff, (body) => (Array.isArray(body?.data) ? body.data : []).map((row) => ({ file: str(row.file), patch: str(row.patch), additions: Number(row.additions) || 0, deletions: Number(row.deletions) || 0, status: row.status })));
|
|
564
|
+
},
|
|
565
|
+
fork: async ({ sessionID, messageID } = {}) => map(await call('POST', `/api/session/${encodeURIComponent(sessionID)}/fork`, { body: messageID ? { before: messageID } : {} }), (body) => translator.noteSession(body?.data)),
|
|
566
|
+
revert: async ({ sessionID, messageID } = {}) => {
|
|
567
|
+
const staged = await call('POST', `/api/session/${encodeURIComponent(sessionID)}/revert/stage`, { body: { messageID, files: true } });
|
|
568
|
+
if (staged.error) return staged;
|
|
569
|
+
return getSession(sessionID);
|
|
570
|
+
},
|
|
571
|
+
unrevert: async ({ sessionID } = {}) => {
|
|
572
|
+
const cleared = await call('DELETE', `/api/session/${encodeURIComponent(sessionID)}/revert`);
|
|
573
|
+
if (cleared.error) return cleared;
|
|
574
|
+
return getSession(sessionID);
|
|
575
|
+
},
|
|
576
|
+
share: async () => notSupported('Sharing a session'),
|
|
577
|
+
unshare: async () => notSupported('Sharing a session'),
|
|
578
|
+
summarize: async ({ sessionID } = {}) => ok(await call('POST', `/api/session/${encodeURIComponent(sessionID)}/compact`, { body: {} }), true),
|
|
579
|
+
init: async ({ sessionID } = {}) => ok(await call('POST', `/api/session/${encodeURIComponent(sessionID)}/command`, { body: { name: 'init', text: '' } }), true),
|
|
580
|
+
command: (p, o) => runCommand(p, o),
|
|
581
|
+
shell: async ({ sessionID, command, agent, model } = {}) => {
|
|
582
|
+
const failed = await prepare(sessionID, { model, agent });
|
|
583
|
+
if (failed) return failed;
|
|
584
|
+
return ok(await call('POST', `/api/session/${encodeURIComponent(sessionID)}/shell`, { body: { command: String(command || '') } }), { info: null, parts: [] });
|
|
585
|
+
},
|
|
586
|
+
deleteMessage: async () => notSupported('Deleting one message'),
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const client = {
|
|
590
|
+
protocol: 2,
|
|
591
|
+
global: {
|
|
592
|
+
event: globalEvent,
|
|
593
|
+
health: async () => map(await call('GET', '/api/info'), (body) => ({ healthy: true, version: str(body?.version) })),
|
|
594
|
+
},
|
|
595
|
+
event: { subscribe: globalEvent },
|
|
596
|
+
session,
|
|
597
|
+
v2: {
|
|
598
|
+
session: {
|
|
599
|
+
compact: async ({ sessionID } = {}) => ok(await call('POST', `/api/session/${encodeURIComponent(sessionID)}/compact`, { body: {} }), true),
|
|
600
|
+
context: async ({ sessionID, directory } = {}) => {
|
|
601
|
+
const read = await call('GET', `/api/session/${encodeURIComponent(sessionID)}/context`);
|
|
602
|
+
return map(read, (body) => messagesToV1(Array.isArray(body?.data) ? body.data : [], { sessionID, directory: directory || translator.sessionDirectory(sessionID) }));
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
permission: {
|
|
607
|
+
list: async ({ directory } = {}) => map(await call('GET', '/api/permission/request', { query: located(directory) }), (body) => (Array.isArray(body?.data) ? body.data : []).filter((row) => row?.id).map((row) => {
|
|
608
|
+
translator.notePermission(row.id, row.sessionID);
|
|
609
|
+
return permissionToV1(row);
|
|
610
|
+
})),
|
|
611
|
+
reply: async ({ requestID, reply, message, directory } = {}) => {
|
|
612
|
+
const sessionID = await permissionSessionOf(requestID, directory);
|
|
613
|
+
if (!sessionID) return { data: undefined, error: { _tag: 'PermissionNotFoundError', message: `No pending OpenCode permission ${requestID}.` }, response: { status: 404, ok: false } };
|
|
614
|
+
const decision = ['once', 'always', 'reject'].includes(reply) ? reply : 'reject';
|
|
615
|
+
return ok(await call('POST', `/api/session/${encodeURIComponent(sessionID)}/permission/${encodeURIComponent(requestID)}/reply`, { body: { decision, ...(message ? { message: String(message) } : {}) } }), true);
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
question: {
|
|
619
|
+
list: async ({ directory } = {}) => map(await call('GET', '/api/form', { query: located(directory) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((form) => {
|
|
620
|
+
const question = formToQuestionV1(form);
|
|
621
|
+
if (question) translator.noteForm(form);
|
|
622
|
+
return question;
|
|
623
|
+
}).filter(Boolean)),
|
|
624
|
+
reply: async ({ requestID, answers, directory } = {}) => {
|
|
625
|
+
const form = await formOf(requestID, directory);
|
|
626
|
+
if (!form) return { data: undefined, error: { _tag: 'QuestionNotFoundError', message: `No pending OpenCode question ${requestID}.` }, response: { status: 404, ok: false } };
|
|
627
|
+
return ok(await call('POST', `/api/session/${encodeURIComponent(form.sessionID)}/form/${encodeURIComponent(requestID)}/reply`, { body: { answer: answersToForm(form, answers) } }), true);
|
|
628
|
+
},
|
|
629
|
+
reject: async ({ requestID, directory } = {}) => {
|
|
630
|
+
const form = await formOf(requestID, directory);
|
|
631
|
+
if (!form) return { data: undefined, error: { _tag: 'QuestionNotFoundError', message: `No pending OpenCode question ${requestID}.` }, response: { status: 404, ok: false } };
|
|
632
|
+
return ok(await call('DELETE', `/api/session/${encodeURIComponent(form.sessionID)}/form/${encodeURIComponent(requestID)}`), true);
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
mcp: {
|
|
636
|
+
status: async ({ directory } = {}) => map(await call('GET', '/api/mcp', { query: located(directory) }), (body) => {
|
|
637
|
+
const out = {};
|
|
638
|
+
for (const server of Array.isArray(body?.data) ? body.data : []) {
|
|
639
|
+
if (!server?.name) continue;
|
|
640
|
+
const status = obj(server.status);
|
|
641
|
+
out[server.name] = { status: str(status.status) || 'failed', ...(status.error ? { error: str(status.error) } : {}) };
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
}),
|
|
645
|
+
connect: async ({ name, directory } = {}) => ok(await call('POST', `/api/experimental/mcp/${encodeURIComponent(name)}/connect`, { query: located(directory) }), true),
|
|
646
|
+
disconnect: async ({ name, directory } = {}) => ok(await call('POST', `/api/experimental/mcp/${encodeURIComponent(name)}/disconnect`, { query: located(directory) }), true),
|
|
647
|
+
add: async ({ name, config, directory } = {}) => {
|
|
648
|
+
const c = obj(config);
|
|
649
|
+
const server = c.url || c.type === 'remote'
|
|
650
|
+
? { type: 'remote', url: str(c.url), ...(c.headers ? { headers: obj(c.headers) } : {}), ...(c.enabled === false ? { disabled: true } : {}) }
|
|
651
|
+
: { type: 'local', command: Array.isArray(c.command) ? c.command.map(str) : [str(c.command), ...(Array.isArray(c.args) ? c.args.map(str) : [])].filter(Boolean), ...(c.environment || c.env ? { environment: obj(c.environment || c.env) } : {}), ...(c.enabled === false ? { disabled: true } : {}) };
|
|
652
|
+
if (Number(c.timeout) > 0) server.timeout = { startup: Number(c.timeout), catalog: Number(c.timeout), execution: Number(c.timeout) };
|
|
653
|
+
const added = await call('PUT', `/api/experimental/mcp/${encodeURIComponent(name)}`, { query: located(directory), body: { config: server } });
|
|
654
|
+
if (added.error) return added;
|
|
655
|
+
return client.mcp.status({ directory });
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
command: {
|
|
659
|
+
list: async ({ directory } = {}) => map(await call('GET', '/api/command', { query: located(directory) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((command) => ({ name: str(command.name), ...(command.description ? { description: str(command.description) } : {}), source: 'command', template: '', hints: [] }))),
|
|
660
|
+
},
|
|
661
|
+
app: {
|
|
662
|
+
agents: async ({ directory } = {}) => map(await call('GET', '/api/agent', { query: located(directory) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((agent) => ({
|
|
663
|
+
name: str(agent.id || agent.name),
|
|
664
|
+
...(agent.description ? { description: str(agent.description) } : {}),
|
|
665
|
+
mode: str(agent.mode) || 'all',
|
|
666
|
+
hidden: agent.hidden === true,
|
|
667
|
+
...(agent.color ? { color: str(agent.color) } : {}),
|
|
668
|
+
permission: rulesToV1(agent.permissions) || [],
|
|
669
|
+
...(agent.model?.id ? { model: { providerID: str(agent.model.providerID), modelID: str(agent.model.id) } } : {}),
|
|
670
|
+
...(agent.model?.variant ? { variant: str(agent.model.variant) } : {}),
|
|
671
|
+
...(agent.system ? { prompt: str(agent.system) } : {}),
|
|
672
|
+
...(agent.steps ? { steps: Number(agent.steps) } : {}),
|
|
673
|
+
options: {},
|
|
674
|
+
}))),
|
|
675
|
+
skills: async ({ directory } = {}) => map(await call('GET', '/api/skill', { query: located(directory) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((skill) => ({ name: str(skill.name), description: str(skill.description), location: str(skill.path), content: str(skill.content) }))),
|
|
676
|
+
},
|
|
677
|
+
config: {
|
|
678
|
+
get: async ({ directory } = {}) => map(await call('GET', '/api/config', { query: located(directory) }), (body) => {
|
|
679
|
+
// OpenCode 2 lists the config documents it read; their merge is the config.
|
|
680
|
+
const merged = {};
|
|
681
|
+
for (const entry of Array.isArray(body) ? body : (Array.isArray(body?.data) ? body.data : [])) {
|
|
682
|
+
if (entry?.type !== 'document' || !entry.info) continue;
|
|
683
|
+
const info = entry.info;
|
|
684
|
+
if (info.model) merged.model = typeof info.model === 'string' ? info.model : `${info.model.providerID}/${info.model.model}`;
|
|
685
|
+
if (info.default_agent) merged.default_agent = info.default_agent;
|
|
686
|
+
if (info.agents) merged.agent = { ...(merged.agent || {}), ...Object.fromEntries(Object.entries(info.agents).map(([name, agent]) => [name, { ...agent, ...(agent.system ? { prompt: agent.system } : {}) }])) };
|
|
687
|
+
if (info.mcp?.servers) merged.mcp = { ...(merged.mcp || {}), ...info.mcp.servers };
|
|
688
|
+
if (info.permissions) merged.permission = rulesToV1(info.permissions);
|
|
689
|
+
if (Array.isArray(info.plugins)) merged.plugin = info.plugins.map((p) => (typeof p === 'string' ? p : p.package));
|
|
690
|
+
if (info.providers) merged.provider = { ...(merged.provider || {}), ...info.providers };
|
|
691
|
+
}
|
|
692
|
+
return merged;
|
|
693
|
+
}),
|
|
694
|
+
providers: async ({ directory } = {}) => {
|
|
695
|
+
const catalog = await providerCatalog(directory);
|
|
696
|
+
if (catalog.error) return { data: undefined, error: catalog.error, response: catalog.response };
|
|
697
|
+
return { data: { providers: catalog.all.filter((p) => Object.keys(p.models).length), default: catalog.defaults }, error: undefined, response: catalog.response };
|
|
698
|
+
},
|
|
699
|
+
},
|
|
700
|
+
provider: {
|
|
701
|
+
list: async ({ directory } = {}) => {
|
|
702
|
+
const catalog = await providerCatalog(directory);
|
|
703
|
+
if (catalog.error) return { data: undefined, error: catalog.error, response: catalog.response };
|
|
704
|
+
return { data: { all: catalog.all, default: catalog.defaults, connected: catalog.connected }, error: undefined, response: catalog.response };
|
|
705
|
+
},
|
|
706
|
+
auth: async () => ({ data: {}, error: undefined, response: { status: 200, ok: true } }),
|
|
707
|
+
},
|
|
708
|
+
find: {
|
|
709
|
+
files: async ({ query, directory, limit, type } = {}) => map(await call('GET', '/api/fs/find', { query: located(directory, { query: str(query), ...(type ? { type } : { type: 'file' }), ...(limit ? { limit: String(limit) } : {}) }) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((entry) => str(entry?.path || entry?.name || entry))),
|
|
710
|
+
text: async () => ({ data: [], error: undefined, response: { status: 200, ok: true } }),
|
|
711
|
+
symbols: async () => ({ data: [], error: undefined, response: { status: 200, ok: true } }),
|
|
712
|
+
},
|
|
713
|
+
file: {
|
|
714
|
+
list: async ({ path, directory } = {}) => map(await call('GET', '/api/fs/list', { query: located(directory, { path }) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((entry) => ({
|
|
715
|
+
name: str(entry.name),
|
|
716
|
+
path: str(entry.path || entry.name),
|
|
717
|
+
absolute: str(entry.absolute || entry.path),
|
|
718
|
+
type: entry.type === 'directory' ? 'directory' : 'file',
|
|
719
|
+
ignored: entry.ignored === true,
|
|
720
|
+
}))),
|
|
721
|
+
read: async ({ path, directory } = {}) => {
|
|
722
|
+
const safe = String(path || '').split('/').map(encodeURIComponent).join('/');
|
|
723
|
+
return map(await call('GET', `/api/fs/read/${safe}`, { query: located(directory), raw: true }), (content) => ({ type: 'text', content: str(content) }));
|
|
724
|
+
},
|
|
725
|
+
status: async ({ directory } = {}) => map(await call('GET', '/api/vcs/status', { query: located(directory) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((row) => ({ path: str(row.file || row.path), added: Number(row.additions) || 0, removed: Number(row.deletions) || 0, status: row.status || 'modified' }))),
|
|
726
|
+
},
|
|
727
|
+
vcs: {
|
|
728
|
+
get: async ({ directory } = {}) => map(await call('GET', '/api/vcs', { query: located(directory) }), (body) => {
|
|
729
|
+
const branch = body?.data?.branch;
|
|
730
|
+
return { branch: typeof branch === 'string' ? branch : str(branch?.name) };
|
|
731
|
+
}),
|
|
732
|
+
status: async ({ directory } = {}) => map(await call('GET', '/api/vcs/status', { query: located(directory) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((row) => ({ file: str(row.file || row.path), additions: Number(row.additions) || 0, deletions: Number(row.deletions) || 0, status: row.status || 'modified' }))),
|
|
733
|
+
// OpenCode 1's 'git' (the working tree against HEAD) is OpenCode 2's 'working', 'branch' (against the
|
|
734
|
+
// default branch) its 'branch'. OpenCode 2 refuses a diff that names no mode.
|
|
735
|
+
diff: async ({ directory, mode } = {}) => map(await call('GET', '/api/vcs/diff', { query: located(directory, { mode: vcsModeToV2(mode) }) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((row) => ({ file: str(row.file), patch: str(row.patch), additions: Number(row.additions) || 0, deletions: Number(row.deletions) || 0, status: row.status }))),
|
|
736
|
+
},
|
|
737
|
+
worktree: {
|
|
738
|
+
list: async ({ directory } = {}) => map(await call('GET', '/api/worktree', { query: located(directory) }), (body) => (Array.isArray(body?.data) ? body.data : []).map((row) => str(row?.directory || row))),
|
|
739
|
+
create: async () => notSupported('Creating a worktree from SynaBun'),
|
|
740
|
+
remove: async () => notSupported('Removing a worktree from SynaBun'),
|
|
741
|
+
reset: async () => notSupported('Resetting a worktree from SynaBun'),
|
|
742
|
+
},
|
|
743
|
+
lsp: { status: async () => ({ data: [], error: undefined, response: { status: 200, ok: true } }) },
|
|
744
|
+
formatter: { status: async () => ({ data: [], error: undefined, response: { status: 200, ok: true } }) },
|
|
745
|
+
path: {
|
|
746
|
+
get: async ({ directory } = {}) => {
|
|
747
|
+
const info = await call('GET', '/api/info');
|
|
748
|
+
return map(info, () => ({ home: '', state: '', config: '', worktree: str(directory), directory: str(directory) }));
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
// A stateless one-shot (editor autocomplete, titles): OpenCode 2's generator.
|
|
752
|
+
generate: async ({ prompt: text, model, directory, signal } = {}) => {
|
|
753
|
+
const ref = modelRefToV2(model);
|
|
754
|
+
return map(await call('POST', '/api/experimental/generate', { query: located(directory), body: { prompt: String(text || ''), ...(ref ? { model: ref } : {}) }, signal }), (body) => ({ text: str(body?.data?.text) }));
|
|
755
|
+
},
|
|
756
|
+
};
|
|
757
|
+
return client;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export const OPENCODE2_CLIENT_INTERNALS = Object.freeze({ SYSTEM_ENTRY, v2ToolName });
|