@openchambery/web 1.19.22-beta.1 → 2.0.0-beta.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/README.md +13 -7
- package/bin/cli-opencode.test.js +208 -0
- package/bin/cli.js +62 -9
- package/bin/lib/cli-startup.js +105 -2
- package/bin/lib/cli-startup.test.js +179 -0
- package/dist/assets/{AgentAvatar-CRaBJXKs.js → AgentAvatar-CzTtVMnI.js} +1 -1
- package/dist/assets/AssistantView-Cg1qQvVe.js +3 -0
- package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-hOi72Wdx.js} +1 -1
- package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-DCqrQkkz.js} +1 -1
- package/dist/assets/Icon-B8fSTLdP.js +1 -0
- package/dist/assets/MarkdownRenderer-Cw74oVxV.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-YM7iRmzK.js} +3 -3
- package/dist/assets/MarkstreamRendererImpl-BDy_oGJQ.js +3 -0
- package/dist/assets/MobileDetailNavigation-iX_hb_Xt.js +1 -0
- package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-CAMUupwR.js} +2 -2
- package/dist/assets/MobileSurface-CHbdPcLH.js +2 -0
- package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-Ci1ODFFX.js} +1 -1
- package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-O4cGdz5p.js} +1 -1
- package/dist/assets/OverlayScrollbar-CRfGtcmG.js +2 -0
- package/dist/assets/SettingsView-Dg8BtTV1.js +25 -0
- package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-B8d8qIQ7.js} +1 -1
- package/dist/assets/TerminalView-CMiw3s_c.js +8 -0
- package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-ByaQYRTp.js} +2 -2
- package/dist/assets/appThemeRegistry-BGGHio8T.js +1 -0
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
- package/dist/assets/{button-CI7Q-Zw_.js → button-DIIrsMjW.js} +1 -1
- package/dist/assets/{collapsible-D5Phaykh.js → collapsible-CnvUeYst.js} +1 -1
- package/dist/assets/{context-menu-CubSDW2g.js → context-menu-BGBUpZEQ.js} +1 -1
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-JGFqhlaM.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-CHEwrI93.js} +1 -1
- package/dist/assets/{diagram-DdLO8HZ9.js → diagram-rvn7_qBM.js} +1 -1
- package/dist/assets/{dialog-B6pnIYmp.js → dialog-CQ63sc9E.js} +1 -1
- package/dist/assets/es-Dsmc0A_3.js +15 -0
- package/dist/assets/fileMentionTouchSelection-BzzcWJCi.js +3 -0
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-DN6gbLhM.js} +1 -1
- package/dist/assets/fr-C_ZlZDI2.js +13 -0
- package/dist/assets/gitApi-BF8vuoZc.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-BMztRu0c.js} +1 -1
- package/dist/assets/{input-D-__fwRi.js → input-BVu-iykl.js} +1 -1
- package/dist/assets/insertionBoundaries-EcAfGT2D.js +1 -0
- package/dist/assets/ja-BqqTaWTi.js +15 -0
- package/dist/assets/ko-DP2XPM37.js +15 -0
- package/dist/assets/main-B7t9lavz.js +2 -0
- package/dist/assets/main-D2BzpnUt.css +1 -0
- package/dist/assets/main-DWP-prNB.js +169 -0
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-D9-B1wdY.js} +1 -1
- package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
- package/dist/assets/mcpQueries-CfXrAkuU.js +1 -0
- package/dist/assets/miniChat-DgOrEQbN.js +2 -0
- package/dist/assets/mobile-Co_ZOHkZ.js +2 -0
- package/dist/assets/multirun-DelyUO6k.js +4 -0
- package/dist/assets/persistence-D0y7226-.js +3 -0
- package/dist/assets/pl-BCf5uLlK.js +15 -0
- package/dist/assets/platform-CurqO28D.js +6 -0
- package/dist/assets/{projectMeta-S1hJ7oBp.js → projectMeta-CPElzJ0n.js} +2 -2
- package/dist/assets/pt-BR-W09Q2pvn.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-pOE93w86.js +2 -0
- package/dist/assets/renderMobileApp-F7ZSLcw8.js +28 -0
- package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
- package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-DSS0KxG3.js} +1 -1
- package/dist/assets/runtimeEndpointReset-x2Wgx57R.js +149 -0
- package/dist/assets/sessionChangeSummary-BUGSYcHp.js +2 -0
- package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-Bss8M20i.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-CE7W7lJ9.js} +1 -1
- package/dist/assets/src-lS1keDVV.css +2 -0
- package/dist/assets/store-jaP79mqI.js +15 -0
- package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-mDR2jXTo.js} +1 -1
- package/dist/assets/textarea-XD2oX_Ee.js +1 -0
- package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-DYeQ_Hvy.js} +1 -1
- package/dist/assets/uk-BgFzz8m2.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-CGbJbiJq.js} +1 -1
- package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-BAWHZ-t0.js} +64 -45
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-Bz4tRflF.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-hN_nLb7T.js +1 -0
- package/dist/assets/useEffectiveDirectory-Do9DQ453.js +1 -0
- package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-Bjx8zkGM.js} +1 -1
- package/dist/assets/useMobileNavigationStore-BsY_gi_K.js +414 -0
- package/dist/assets/useSessionAutoCleanup-C4g5KmDf.js +2 -0
- package/dist/assets/useUIStore-syVmbuDA.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-B5QXxBok.js} +2 -2
- package/dist/assets/vendor-opencode-client-C12gfjag.js +12 -0
- package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
- package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
- package/dist/assets/zh-CN-BhqSLgUY.js +15 -0
- package/dist/assets/zh-TW-BsaBgY-p.js +15 -0
- package/dist/index.html +12 -12
- package/dist/mini-chat.html +12 -12
- package/dist/mobile.html +12 -12
- package/package.json +3 -2
- package/server/index.js +423 -20
- package/server/lib/assistants/DOCUMENTATION.md +26 -21
- package/server/lib/assistants/assign.js +2 -1
- package/server/lib/assistants/contact-store.js +37 -19
- package/server/lib/assistants/contracts.js +11 -1
- package/server/lib/assistants/projection-history.test.js +454 -0
- package/server/lib/assistants/routes.js +11 -5
- package/server/lib/assistants/service.js +850 -294
- package/server/lib/assistants/service.test.js +588 -274
- package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
- package/server/lib/browser-provider/contract.js +312 -0
- package/server/lib/browser-provider/host.js +421 -0
- package/server/lib/browser-provider/host.test.js +304 -0
- package/server/lib/browser-provider/routes.js +122 -0
- package/server/lib/browser-provider/routes.test.js +120 -0
- package/server/lib/browser-provider/selection-store.js +43 -0
- package/server/lib/browser-provider/selection-store.test.js +35 -0
- package/server/lib/conversations/DOCUMENTATION.md +5 -5
- package/server/lib/conversations/service.js +87 -57
- package/server/lib/conversations/service.test.js +132 -100
- package/server/lib/conversations/validation.js +11 -1
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +51 -5
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- package/server/lib/event-stream/runtime.js +57 -20
- package/server/lib/event-stream/runtime.test.js +112 -0
- package/server/lib/llm/DOCUMENTATION.md +95 -84
- package/server/lib/llm/catalog.js +111 -45
- package/server/lib/llm/catalog.test.js +121 -59
- package/server/lib/llm/completions.js +41 -12
- package/server/lib/llm/completions.test.js +45 -4
- package/server/lib/llm/generate.js +516 -453
- package/server/lib/llm/generate.test.js +705 -484
- package/server/lib/llm/routes.js +8 -4
- package/server/lib/message-queue/DOCUMENTATION.md +5 -5
- package/server/lib/message-queue/opencode-adapter.js +191 -104
- package/server/lib/message-queue/opencode-adapter.test.js +328 -58
- package/server/lib/message-queue/runtime.js +15 -2
- package/server/lib/message-queue/runtime.test.js +6 -6
- package/server/lib/message-queue/worker.js +7 -1
- package/server/lib/notifications/DOCUMENTATION.md +3 -1
- package/server/lib/notifications/runtime.js +133 -31
- package/server/lib/notifications/runtime.test.js +103 -0
- package/server/lib/notifications/template-runtime.js +130 -38
- package/server/lib/notifications/template-runtime.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +111 -16
- package/server/lib/opencode/agent-document.d.ts +28 -0
- package/server/lib/opencode/agent-document.js +280 -0
- package/server/lib/opencode/agent-document.test.js +137 -0
- package/server/lib/opencode/agents.js +96 -0
- package/server/lib/opencode/auth-state-runtime.js +5 -0
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-entity-routes.js +117 -103
- package/server/lib/opencode/config-entity-routes.test.js +148 -10
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
- package/server/lib/opencode/ensure-cli.d.ts +8 -0
- package/server/lib/opencode/ensure-cli.js +238 -0
- package/server/lib/opencode/ensure-cli.test.js +119 -0
- package/server/lib/opencode/env-runtime.js +208 -111
- package/server/lib/opencode/env-runtime.test.js +211 -14
- package/server/lib/opencode/feature-routes-runtime.js +90 -5
- package/server/lib/opencode/feature-routes-runtime.test.js +25 -0
- package/server/lib/opencode/http-server-connections.js +98 -0
- package/server/lib/opencode/http-server-connections.test.js +321 -0
- package/server/lib/opencode/lifecycle.js +539 -95
- package/server/lib/opencode/lifecycle.test.js +698 -6
- package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
- package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
- package/server/lib/opencode/network-runtime.js +28 -16
- package/server/lib/opencode/network-runtime.test.js +68 -4
- package/server/lib/opencode/openchamber-routes.js +7 -1
- package/server/lib/opencode/opencode2-pin.js +139 -0
- package/server/lib/opencode/opencode2-pin.test.js +88 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
- package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
- package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
- package/server/lib/opencode/plugin-compatibility.js +15 -0
- package/server/lib/opencode/plugin-compatibility.test.js +21 -0
- package/server/lib/opencode/plugin-routes.js +3 -18
- package/server/lib/opencode/plugin-routes.test.js +9 -9
- package/server/lib/opencode/plugins.js +4 -3
- package/server/lib/opencode/plugins.test.js +12 -0
- package/server/lib/opencode/provider-catalog.js +58 -12
- package/server/lib/opencode/provider-catalog.test.js +48 -0
- package/server/lib/opencode/proxy.js +373 -28
- package/server/lib/opencode/proxy.test.js +353 -0
- package/server/lib/opencode/routes.js +333 -76
- package/server/lib/opencode/routes.test.js +315 -50
- package/server/lib/opencode/runtime-contract.js +401 -0
- package/server/lib/opencode/runtime-contract.test.js +214 -0
- package/server/lib/opencode/server-opencode-fetch.js +242 -0
- package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
- package/server/lib/opencode/server-utils-runtime.js +35 -0
- package/server/lib/opencode/shared-service.integration.test.js +66 -0
- package/server/lib/opencode/shared-service.js +147 -0
- package/server/lib/opencode/shared-service.test.js +93 -0
- package/server/lib/opencode/shared.js +3 -3
- package/server/lib/opencode/shutdown-runtime.js +15 -6
- package/server/lib/opencode/shutdown-runtime.test.js +49 -1
- package/server/lib/opencode/skill-routes.js +26 -40
- package/server/lib/opencode/skill-routes.test.js +71 -7
- package/server/lib/opencode/skills.test.js +15 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
- package/server/lib/opencode/static-routes-runtime.js +3 -1
- package/server/lib/opencode/static-routes-runtime.test.js +33 -0
- package/server/lib/opencode/upgrade-screen.d.ts +30 -0
- package/server/lib/opencode/upgrade-screen.js +299 -0
- package/server/lib/opencode/upgrade-screen.test.js +196 -0
- package/server/lib/opencode/v1-migration-gate.js +157 -0
- package/server/lib/opencode/v1-migration-gate.test.js +111 -0
- package/server/lib/opencode/v2-client.js +60 -0
- package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
- package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
- package/server/lib/opencode/watcher.js +2 -1
- package/server/lib/opencode/watcher.test.js +1 -1
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
- package/server/lib/permission-auto-accept/runtime.js +14 -7
- package/server/lib/permission-auto-accept/runtime.test.js +27 -12
- package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
- package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
- package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
- package/server/lib/scheduled-tasks/routes.js +27 -0
- package/server/lib/scheduled-tasks/routes.test.js +94 -0
- package/server/lib/scheduled-tasks/runtime.js +381 -310
- package/server/lib/scheduled-tasks/runtime.test.js +600 -309
- package/server/lib/session-goal/DOCUMENTATION.md +173 -22
- package/server/lib/session-goal/capability.js +57 -0
- package/server/lib/session-goal/capability.test.js +33 -0
- package/server/lib/session-goal/routes.js +240 -2
- package/server/lib/session-goal/routes.test.js +211 -0
- package/server/lib/session-goal/runtime.js +1617 -113
- package/server/lib/session-goal/runtime.test.js +1500 -70
- package/server/lib/session-index/DOCUMENTATION.md +18 -15
- package/server/lib/session-index/event-ingest.js +89 -16
- package/server/lib/session-index/event-ingest.test.js +73 -0
- package/server/lib/session-index/host-authority-snapshot.js +92 -0
- package/server/lib/session-index/routes.host-archive.test.js +201 -0
- package/server/lib/session-index/routes.js +63 -10
- package/server/lib/session-index/routes.test.js +8 -8
- package/server/lib/session-index/service.js +7 -1
- package/server/lib/session-index/sync-runtime.js +171 -41
- package/server/lib/session-index/sync-runtime.test.js +164 -9
- package/server/lib/session-metadata/DOCUMENTATION.md +96 -0
- package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
- package/server/lib/session-metadata/opencode-session-record.js +316 -0
- package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
- package/server/lib/session-metadata/routes.js +197 -0
- package/server/lib/session-metadata/routes.test.js +156 -0
- package/server/lib/session-metadata/session-archive.d.ts +73 -0
- package/server/lib/session-metadata/session-archive.js +654 -0
- package/server/lib/session-metadata/session-archive.test.js +359 -0
- package/server/lib/session-metadata/session-metadata-store.d.ts +38 -0
- package/server/lib/session-metadata/session-metadata-store.js +422 -0
- package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
- package/server/lib/session-metadata/session-projection.d.ts +41 -0
- package/server/lib/session-metadata/session-projection.js +279 -0
- package/server/lib/session-metadata/session-projection.test.js +64 -0
- package/server/lib/session-metadata/system-session.js +55 -0
- package/server/lib/session-metadata/system-session.test.js +55 -0
- package/server/lib/session-title/DOCUMENTATION.md +40 -7
- package/server/lib/session-title/native-runtime.test.js +167 -0
- package/server/lib/session-title/runtime.js +69 -44
- package/server/lib/session-title/session-access.js +46 -0
- package/server/lib/session-title/session-access.test.js +91 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
- package/server/lib/session-turn-pages/reconcile.service.js +1 -1
- package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
- package/server/lib/session-turn-pages/routes.js +123 -75
- package/server/lib/session-turn-pages/routes.test.js +566 -0
- package/server/lib/session-turn-pages/service.js +6 -2
- package/server/lib/session-turn-pages/service.test.js +34 -0
- package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
- package/server/lib/session-turn-pages/session-message-projection.js +281 -0
- package/server/lib/small-model/DOCUMENTATION.md +101 -146
- package/server/lib/small-model/catalog.js +84 -54
- package/server/lib/small-model/catalog.test.js +111 -9
- package/server/lib/small-model/custom-api.js +68 -0
- package/server/lib/small-model/custom-api.test.js +64 -0
- package/server/lib/small-model/index.js +125 -241
- package/server/lib/small-model/resolve.js +35 -120
- package/server/lib/small-model/resolve.test.js +20 -110
- package/server/lib/small-model/routes.js +1 -3
- package/server/lib/small-model/summary-settings.test.js +157 -253
- package/server/opencode-proxy.test.js +63 -12
- package/dist/assets/AssistantView-BUveAgc4.js +0 -3
- package/dist/assets/Icon-BgdfPhNs.js +0 -1
- package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
- package/dist/assets/MarkstreamRendererImpl-D2dsW5Lg.js +0 -3
- package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
- package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
- package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
- package/dist/assets/SettingsView-PkXIavr7.js +0 -25
- package/dist/assets/TerminalView-CDdzF4yi.js +0 -8
- package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
- package/dist/assets/es-DA3MqOo0.js +0 -15
- package/dist/assets/fileMentionTouchSelection-DNeiqns-.js +0 -4
- package/dist/assets/fr-Dbz0CdIA.js +0 -13
- package/dist/assets/gitApi-DXyC4MiV.js +0 -7
- package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
- package/dist/assets/ja-DumanVQA.js +0 -15
- package/dist/assets/ko-C0IkiUjQ.js +0 -15
- package/dist/assets/main-BPS5ylib.js +0 -2
- package/dist/assets/main-Cpd9VWYs.js +0 -169
- package/dist/assets/main-qSeeJRW1.css +0 -1
- package/dist/assets/markdown-worker-CptoICmq.js +0 -2
- package/dist/assets/markdownTheme-DOfGg-A7.js +0 -1
- package/dist/assets/miniChat-B8_KLWiY.js +0 -2
- package/dist/assets/mobile-CtktKrXn.js +0 -2
- package/dist/assets/multirun-CYfP9Cfm.js +0 -4
- package/dist/assets/persistence-Ce3AnpEr.js +0 -3
- package/dist/assets/pl-B9No6MQp.js +0 -15
- package/dist/assets/platform-0w_Q9_ji.js +0 -2
- package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
- package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtimeEndpointReset-ChOxt-J7.js +0 -147
- package/dist/assets/sessionChangeSummary-NnxcqCTI.js +0 -2
- package/dist/assets/src-rS3zCcsC.css +0 -2
- package/dist/assets/store-5B_LDnLM.js +0 -15
- package/dist/assets/textarea-bHYxZtfH.js +0 -1
- package/dist/assets/uk-C_1KkqyP.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
- package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
- package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
- package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
- package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
- package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
- package/dist/assets/zh-CN-uWILgBdt.js +0 -15
- package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
- package/server/lib/small-model/call.js +0 -427
- package/server/lib/small-model/call.test.js +0 -382
- package/server/lib/small-model/opencode-session.js +0 -339
- package/server/lib/small-model/opencode-session.test.js +0 -177
|
@@ -1,15 +1,66 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpenCode } from '@opencode/client';
|
|
2
2
|
|
|
3
3
|
// --- error classification ---
|
|
4
4
|
|
|
5
5
|
const isTransportError = (error) => {
|
|
6
6
|
if (!error) return true;
|
|
7
|
+
if (error.reason === 'Transport') return true;
|
|
7
8
|
if (error.name === 'TypeError' || error.name === 'FetchError' || error.name === 'AbortError') return true;
|
|
9
|
+
if (error.cause?.name === 'TypeError' || error.cause?.name === 'FetchError' || error.cause?.name === 'AbortError') return true;
|
|
8
10
|
const code = error.cause?.code || error.code;
|
|
9
11
|
if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ECONNRESET' || code === 'ETIMEDOUT' || code === 'EPIPE') return true;
|
|
10
12
|
return false;
|
|
11
13
|
};
|
|
12
14
|
|
|
15
|
+
// v2 client throws raw data or ClientError; recover HTTP status when present.
|
|
16
|
+
const getHttpStatus = (error) => {
|
|
17
|
+
const candidates = [error?.cause?.status, error?.status, error?.response?.status];
|
|
18
|
+
for (const value of candidates) {
|
|
19
|
+
if (Number.isFinite(value)) return value;
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const classifyPromptFailure = (error) => {
|
|
25
|
+
if (isTransportError(error)) {
|
|
26
|
+
return { ambiguous: true, status: undefined };
|
|
27
|
+
}
|
|
28
|
+
const status = getHttpStatus(error);
|
|
29
|
+
if (Number.isFinite(status)) {
|
|
30
|
+
return { ambiguous: isRetryableHttpStatus(status), status };
|
|
31
|
+
}
|
|
32
|
+
// Declared v2 HTTP errors (400/401/404/409) throw JSON without a status field.
|
|
33
|
+
return { ambiguous: false, status: 400 };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Map sanitized parts onto the official v2 prompt body (text/files/agents).
|
|
37
|
+
const toV2PromptInput = (sanitizedInput, sessionID) => {
|
|
38
|
+
const text = sanitizedInput.parts
|
|
39
|
+
.filter((part) => part.type === 'text')
|
|
40
|
+
.map((part) => part.text)
|
|
41
|
+
.join('\n');
|
|
42
|
+
const files = sanitizedInput.parts
|
|
43
|
+
.filter((part) => part.type === 'file')
|
|
44
|
+
.map((part) => ({
|
|
45
|
+
uri: part.url,
|
|
46
|
+
...(part.filename ? { name: part.filename } : {}),
|
|
47
|
+
}));
|
|
48
|
+
const agents = sanitizedInput.parts
|
|
49
|
+
.filter((part) => part.type === 'agent')
|
|
50
|
+
.map((part) => ({ name: part.name }));
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
sessionID,
|
|
54
|
+
id: sanitizedInput.messageID,
|
|
55
|
+
text,
|
|
56
|
+
...(files.length > 0 ? { files } : {}),
|
|
57
|
+
...(agents.length > 0 ? { agents } : {}),
|
|
58
|
+
...(sanitizedInput.skills?.length ? { skills: sanitizedInput.skills.map(({ id }) => ({ id, name: id })) } : {}),
|
|
59
|
+
...(sanitizedInput.metadata ? { metadata: sanitizedInput.metadata } : {}),
|
|
60
|
+
delivery: 'steer',
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
13
64
|
const isRetryableHttpStatus = (status) => {
|
|
14
65
|
if (!Number.isFinite(status)) return true;
|
|
15
66
|
return status === 408 || status === 429 || status >= 500;
|
|
@@ -84,7 +135,7 @@ export const createConversationsService = (deps) => {
|
|
|
84
135
|
|
|
85
136
|
const baseUrl = buildOpenCodeUrl('/', '').replace(/\/$/, '');
|
|
86
137
|
const authHeaders = getOpenCodeAuthHeaders();
|
|
87
|
-
return
|
|
138
|
+
return OpenCode.make({ baseUrl, headers: authHeaders });
|
|
88
139
|
};
|
|
89
140
|
|
|
90
141
|
const createAndPrompt = async ({ sanitizedInput } = {}) => {
|
|
@@ -99,19 +150,34 @@ export const createConversationsService = (deps) => {
|
|
|
99
150
|
const client = clientOrError;
|
|
100
151
|
|
|
101
152
|
// Phase 1: create session (30s timeout)
|
|
102
|
-
let
|
|
103
|
-
let upstreamStatus;
|
|
153
|
+
let session;
|
|
104
154
|
const createT = timeoutSignal(CREATE_TIMEOUT_MS);
|
|
105
155
|
try {
|
|
106
|
-
|
|
107
|
-
directory: sanitizedInput.directory,
|
|
156
|
+
session = await client.session.create({
|
|
157
|
+
location: { directory: sanitizedInput.directory },
|
|
108
158
|
...(sanitizedInput.title ? { title: sanitizedInput.title } : {}),
|
|
109
|
-
...(sanitizedInput.
|
|
110
|
-
...(sanitizedInput.
|
|
159
|
+
...(sanitizedInput.agent ? { agent: sanitizedInput.agent } : {}),
|
|
160
|
+
...(sanitizedInput.model ? {
|
|
161
|
+
model: {
|
|
162
|
+
id: sanitizedInput.model.modelID,
|
|
163
|
+
providerID: sanitizedInput.model.providerID,
|
|
164
|
+
...(sanitizedInput.variant ? { variant: sanitizedInput.variant } : {}),
|
|
165
|
+
},
|
|
166
|
+
} : {}),
|
|
111
167
|
}, {
|
|
112
168
|
signal: createT.signal,
|
|
113
169
|
});
|
|
114
170
|
} catch (error) {
|
|
171
|
+
const status = getHttpStatus(error);
|
|
172
|
+
if (Number.isFinite(status) && !isTransportError(error)) {
|
|
173
|
+
logger.warn(`[conversations] session.create client error (${status})`);
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
phase: 'create',
|
|
177
|
+
error: safeErrorMessage('create'),
|
|
178
|
+
status,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
115
181
|
logger.warn('[conversations] session.create transport error');
|
|
116
182
|
return {
|
|
117
183
|
ok: false,
|
|
@@ -122,19 +188,7 @@ export const createConversationsService = (deps) => {
|
|
|
122
188
|
createT.clear();
|
|
123
189
|
}
|
|
124
190
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (sessionResult.error) {
|
|
128
|
-
logger.warn(`[conversations] session.create SDK error (${upstreamStatus || 'no status'})`);
|
|
129
|
-
return {
|
|
130
|
-
ok: false,
|
|
131
|
-
phase: 'create',
|
|
132
|
-
error: safeErrorMessage('create'),
|
|
133
|
-
...(Number.isFinite(upstreamStatus) ? { status: upstreamStatus } : {}),
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const sessionID = sessionResult?.data?.id;
|
|
191
|
+
const sessionID = session?.id;
|
|
138
192
|
if (!sessionID) {
|
|
139
193
|
logger.warn('[conversations] session.create returned no session ID');
|
|
140
194
|
return {
|
|
@@ -144,29 +198,17 @@ export const createConversationsService = (deps) => {
|
|
|
144
198
|
};
|
|
145
199
|
}
|
|
146
200
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Phase 2: promptAsync (45s timeout, 204, does not wait for model completion)
|
|
201
|
+
// Phase 2: v2 prompt + delivery=steer (45s timeout). Host only orchestrates
|
|
202
|
+
// create + first prompt; it does not cache message body as conversation content.
|
|
150
203
|
let promptResult;
|
|
151
204
|
const promptT = timeoutSignal(PROMPT_TIMEOUT_MS);
|
|
152
205
|
try {
|
|
153
|
-
promptResult = await client.session.
|
|
154
|
-
sessionID,
|
|
155
|
-
directory: sanitizedInput.directory,
|
|
156
|
-
messageID: sanitizedInput.messageID,
|
|
157
|
-
model: {
|
|
158
|
-
providerID: sanitizedInput.model.providerID,
|
|
159
|
-
modelID: sanitizedInput.model.modelID,
|
|
160
|
-
},
|
|
161
|
-
...(sanitizedInput.agent ? { agent: sanitizedInput.agent } : {}),
|
|
162
|
-
...(sanitizedInput.variant ? { variant: sanitizedInput.variant } : {}),
|
|
163
|
-
parts: sanitizedInput.parts,
|
|
164
|
-
}, {
|
|
206
|
+
promptResult = await client.session.prompt(toV2PromptInput(sanitizedInput, sessionID), {
|
|
165
207
|
signal: promptT.signal,
|
|
166
208
|
});
|
|
167
209
|
} catch (error) {
|
|
168
|
-
const ambiguous =
|
|
169
|
-
logger.warn(`[conversations]
|
|
210
|
+
const { ambiguous, status } = classifyPromptFailure(error);
|
|
211
|
+
logger.warn(`[conversations] prompt throw (ambiguous=${ambiguous})`);
|
|
170
212
|
if (ambiguous) {
|
|
171
213
|
safeMark(markUserMessageSent, sessionID, logger);
|
|
172
214
|
}
|
|
@@ -177,37 +219,25 @@ export const createConversationsService = (deps) => {
|
|
|
177
219
|
messageID: sanitizedInput.messageID,
|
|
178
220
|
ambiguous,
|
|
179
221
|
error: safeErrorMessage('prompt'),
|
|
222
|
+
...(Number.isFinite(status) ? { status } : {}),
|
|
180
223
|
};
|
|
181
224
|
} finally {
|
|
182
225
|
promptT.clear();
|
|
183
226
|
}
|
|
184
227
|
|
|
185
228
|
// SDK result shape { data, error, response }
|
|
186
|
-
|
|
187
|
-
const httpStatus = promptResult.response?.status;
|
|
188
|
-
const ambiguous = isAmbiguousPromptError(promptResult.error, promptResult.response);
|
|
189
|
-
logger.warn(`[conversations] promptAsync SDK error (${httpStatus || 'no status'}, ambiguous=${ambiguous})`);
|
|
190
|
-
if (ambiguous) {
|
|
191
|
-
safeMark(markUserMessageSent, sessionID, logger);
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
ok: false,
|
|
195
|
-
phase: 'prompt',
|
|
196
|
-
session,
|
|
197
|
-
messageID: sanitizedInput.messageID,
|
|
198
|
-
ambiguous,
|
|
199
|
-
error: safeErrorMessage('prompt'),
|
|
200
|
-
...(Number.isFinite(httpStatus) ? { status: httpStatus } : {}),
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// promptAsync 204 success
|
|
229
|
+
// prompt success — return inbox item when present; never persist parts/text.
|
|
205
230
|
safeMark(markUserMessageSent, sessionID, logger);
|
|
206
231
|
|
|
232
|
+
const inbox = promptResult && typeof promptResult === 'object'
|
|
233
|
+
? promptResult
|
|
234
|
+
: undefined;
|
|
235
|
+
|
|
207
236
|
return {
|
|
208
237
|
ok: true,
|
|
209
238
|
session,
|
|
210
239
|
messageID: sanitizedInput.messageID,
|
|
240
|
+
...(inbox && (inbox.id || inbox.type) ? { inbox } : {}),
|
|
211
241
|
};
|
|
212
242
|
};
|
|
213
243
|
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { validateConversationInput } from './validation.js';
|
|
3
3
|
|
|
4
|
-
// Mock the
|
|
5
|
-
vi.mock('@opencode
|
|
6
|
-
|
|
4
|
+
// Mock the official v2 client before any imports
|
|
5
|
+
vi.mock('@opencode/client', () => ({
|
|
6
|
+
OpenCode: { make: vi.fn() },
|
|
7
7
|
}));
|
|
8
8
|
|
|
9
9
|
// Lazy import to allow vi.mock to take effect
|
|
10
10
|
const { createConversationsService } = await import('./service.js');
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const mockMake = (await import('@opencode/client')).OpenCode.make;
|
|
13
|
+
|
|
14
|
+
const clientError = (reason, cause) => {
|
|
15
|
+
const error = new Error(reason);
|
|
16
|
+
error.name = 'ClientError';
|
|
17
|
+
error.reason = reason;
|
|
18
|
+
if (cause !== undefined) error.cause = cause;
|
|
19
|
+
return error;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const httpError = (status, message) => {
|
|
23
|
+
const error = new Error(message);
|
|
24
|
+
error.status = status;
|
|
25
|
+
return error;
|
|
26
|
+
};
|
|
13
27
|
|
|
14
28
|
describe('conversations validation', () => {
|
|
15
29
|
it('rejects non-object body', () => {
|
|
@@ -270,7 +284,7 @@ describe('conversations service', () => {
|
|
|
270
284
|
mockBuildUrl.mockReturnValue('http://127.0.0.1:4096/');
|
|
271
285
|
mockGetHeaders.mockReturnValue({ Authorization: 'Basic test' });
|
|
272
286
|
mockWaitReady.mockResolvedValue(undefined);
|
|
273
|
-
|
|
287
|
+
mockMake.mockReset();
|
|
274
288
|
});
|
|
275
289
|
|
|
276
290
|
const baseInput = (overrides = {}) => ({
|
|
@@ -295,9 +309,9 @@ describe('conversations service', () => {
|
|
|
295
309
|
// --- create failures ---
|
|
296
310
|
|
|
297
311
|
it('returns create failure with safe error when SDK returns error', async () => {
|
|
298
|
-
|
|
312
|
+
mockMake.mockReturnValue({
|
|
299
313
|
session: {
|
|
300
|
-
create: async () =>
|
|
314
|
+
create: async () => { throw clientError('UnexpectedStatus', { status: 500 }); },
|
|
301
315
|
},
|
|
302
316
|
});
|
|
303
317
|
const service = createService();
|
|
@@ -311,7 +325,7 @@ describe('conversations service', () => {
|
|
|
311
325
|
});
|
|
312
326
|
|
|
313
327
|
it('returns create failure on transport error during create', async () => {
|
|
314
|
-
|
|
328
|
+
mockMake.mockReturnValue({
|
|
315
329
|
session: {
|
|
316
330
|
create: async () => { throw new Error('Connection refused'); },
|
|
317
331
|
},
|
|
@@ -325,15 +339,19 @@ describe('conversations service', () => {
|
|
|
325
339
|
|
|
326
340
|
// --- create: no agent/model ---
|
|
327
341
|
|
|
328
|
-
it('
|
|
329
|
-
|
|
342
|
+
it('passes agent/model/variant to session.create', async () => {
|
|
343
|
+
mockMake.mockReturnValue({
|
|
330
344
|
session: {
|
|
331
345
|
create: async (params) => {
|
|
332
|
-
expect(params.agent).
|
|
333
|
-
expect(params.model).
|
|
334
|
-
|
|
346
|
+
expect(params.agent).toBe('builder');
|
|
347
|
+
expect(params.model).toEqual({
|
|
348
|
+
id: 'gpt-4o',
|
|
349
|
+
providerID: 'openai',
|
|
350
|
+
variant: 'v1',
|
|
351
|
+
});
|
|
352
|
+
return { id: 'ses_ok' };
|
|
335
353
|
},
|
|
336
|
-
|
|
354
|
+
prompt: async () => undefined,
|
|
337
355
|
},
|
|
338
356
|
});
|
|
339
357
|
const service = createService();
|
|
@@ -343,42 +361,71 @@ describe('conversations service', () => {
|
|
|
343
361
|
expect(result.ok).toBe(true);
|
|
344
362
|
});
|
|
345
363
|
|
|
346
|
-
// ---
|
|
364
|
+
// --- prompt usage ---
|
|
347
365
|
|
|
348
|
-
it('uses
|
|
349
|
-
let
|
|
350
|
-
|
|
366
|
+
it('uses prompt with delivery=steer and does not cache message body', async () => {
|
|
367
|
+
let promptCalled = false;
|
|
368
|
+
mockMake.mockReturnValue({
|
|
351
369
|
session: {
|
|
352
|
-
create: async () => ({
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
expect(params.delivery).
|
|
356
|
-
return {
|
|
370
|
+
create: async () => ({ id: 'ses_pa' }),
|
|
371
|
+
prompt: async (params) => {
|
|
372
|
+
promptCalled = true;
|
|
373
|
+
expect(params.delivery).toBe('steer');
|
|
374
|
+
return {
|
|
375
|
+
id: 'msg_test',
|
|
376
|
+
sessionID: 'ses_pa',
|
|
377
|
+
type: 'user',
|
|
378
|
+
delivery: 'steer',
|
|
379
|
+
payload: { text: 'hello' },
|
|
380
|
+
timeCreated: 1,
|
|
381
|
+
};
|
|
357
382
|
},
|
|
358
383
|
},
|
|
359
384
|
});
|
|
360
385
|
const service = createService();
|
|
361
386
|
const result = await service.createAndPrompt({ sanitizedInput: baseInput() });
|
|
362
|
-
expect(
|
|
387
|
+
expect(promptCalled).toBe(true);
|
|
363
388
|
expect(result.ok).toBe(true);
|
|
389
|
+
expect(result.inbox).toEqual(expect.objectContaining({ id: 'msg_test', delivery: 'steer' }));
|
|
390
|
+
expect(result.parts).toBeUndefined();
|
|
391
|
+
expect(result.text).toBeUndefined();
|
|
364
392
|
});
|
|
365
393
|
|
|
366
394
|
// --- success ---
|
|
367
395
|
|
|
368
|
-
it('
|
|
396
|
+
it('validates and forwards skill ids with the first prompt', async () => {
|
|
397
|
+
const validated = validateConversationInput({
|
|
398
|
+
input: { type: 'prompt' },
|
|
399
|
+
...baseInput({ parts: [{ type: 'text', text: '[skill:release] prepare notes' }], skills: [{ id: 'release', text: 'untrusted body' }] }),
|
|
400
|
+
});
|
|
401
|
+
expect(validated.valid).toBe(true);
|
|
402
|
+
expect(validated.sanitized.skills).toEqual([{ id: 'release' }]);
|
|
403
|
+
const prompt = vi.fn(async () => undefined);
|
|
404
|
+
mockMake.mockReturnValue({ session: { create: async () => ({ id: 'ses_skill' }), prompt } });
|
|
405
|
+
const result = await createService().createAndPrompt({ sanitizedInput: validated.sanitized });
|
|
406
|
+
expect(result.ok).toBe(true);
|
|
407
|
+
expect(prompt.mock.calls[0][0]).toMatchObject({
|
|
408
|
+
text: '[skill:release] prepare notes', skills: [{ id: 'release', name: 'release' }], delivery: 'steer',
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('returns success when create + prompt succeed', async () => {
|
|
369
413
|
const sessionData = { id: 'ses_created', title: 'Test' };
|
|
370
|
-
|
|
414
|
+
mockMake.mockReturnValue({
|
|
371
415
|
session: {
|
|
372
416
|
create: async (params) => {
|
|
373
|
-
expect(params.
|
|
374
|
-
|
|
417
|
+
expect(params.location).toEqual({ directory: '/test' });
|
|
418
|
+
expect(params.model).toEqual({ id: 'gpt-4o', providerID: 'openai' });
|
|
419
|
+
return sessionData;
|
|
375
420
|
},
|
|
376
|
-
|
|
421
|
+
prompt: async (params) => {
|
|
377
422
|
expect(params.sessionID).toBe('ses_created');
|
|
378
|
-
expect(params.
|
|
379
|
-
expect(params.
|
|
380
|
-
expect(params.
|
|
381
|
-
|
|
423
|
+
expect(params.id).toBe('msg_test');
|
|
424
|
+
expect(params.text).toBe('hello');
|
|
425
|
+
expect(params.delivery).toBe('steer');
|
|
426
|
+
expect(params.model).toBeUndefined();
|
|
427
|
+
expect(params.parts).toBeUndefined();
|
|
428
|
+
return undefined;
|
|
382
429
|
},
|
|
383
430
|
},
|
|
384
431
|
});
|
|
@@ -390,14 +437,19 @@ describe('conversations service', () => {
|
|
|
390
437
|
expect(mockMarkSent).toHaveBeenCalledWith('ses_created');
|
|
391
438
|
});
|
|
392
439
|
|
|
393
|
-
it('passes agent, variant to
|
|
394
|
-
|
|
440
|
+
it('passes agent, variant to session.create and not prompt', async () => {
|
|
441
|
+
mockMake.mockReturnValue({
|
|
395
442
|
session: {
|
|
396
|
-
create: async () =>
|
|
397
|
-
promptAsync: async (params) => {
|
|
443
|
+
create: async (params) => {
|
|
398
444
|
expect(params.agent).toBe('builder');
|
|
399
|
-
expect(params.
|
|
400
|
-
return {
|
|
445
|
+
expect(params.model).toEqual({ id: 'gpt-4o', providerID: 'openai', variant: 'fast' });
|
|
446
|
+
return { id: 'ses_avd' };
|
|
447
|
+
},
|
|
448
|
+
prompt: async (params) => {
|
|
449
|
+
expect(params.agent).toBeUndefined();
|
|
450
|
+
expect(params.variant).toBeUndefined();
|
|
451
|
+
expect(params.delivery).toBe('steer');
|
|
452
|
+
return undefined;
|
|
401
453
|
},
|
|
402
454
|
},
|
|
403
455
|
});
|
|
@@ -412,14 +464,10 @@ describe('conversations service', () => {
|
|
|
412
464
|
|
|
413
465
|
it('returns prompt failure with ambiguous=false for 400 SDK error, safe error, no mark', async () => {
|
|
414
466
|
const sessionData = { id: 'ses_400' };
|
|
415
|
-
|
|
467
|
+
mockMake.mockReturnValue({
|
|
416
468
|
session: {
|
|
417
|
-
create: async () =>
|
|
418
|
-
|
|
419
|
-
data: null,
|
|
420
|
-
error: new Error('path /foo/bar does not exist'),
|
|
421
|
-
response: { status: 400 },
|
|
422
|
-
}),
|
|
469
|
+
create: async () => sessionData,
|
|
470
|
+
prompt: async () => { throw httpError(400, 'path /foo/bar does not exist'); },
|
|
423
471
|
},
|
|
424
472
|
});
|
|
425
473
|
const service = createService();
|
|
@@ -433,14 +481,10 @@ describe('conversations service', () => {
|
|
|
433
481
|
});
|
|
434
482
|
|
|
435
483
|
it('returns prompt failure with ambiguous=false for 404, no mark', async () => {
|
|
436
|
-
|
|
484
|
+
mockMake.mockReturnValue({
|
|
437
485
|
session: {
|
|
438
|
-
create: async () => ({
|
|
439
|
-
|
|
440
|
-
data: null,
|
|
441
|
-
error: new Error('Session not found'),
|
|
442
|
-
response: { status: 404 },
|
|
443
|
-
}),
|
|
486
|
+
create: async () => ({ id: 'ses_404' }),
|
|
487
|
+
prompt: async () => { throw httpError(404, 'Session not found'); },
|
|
444
488
|
},
|
|
445
489
|
});
|
|
446
490
|
const service = createService();
|
|
@@ -456,14 +500,10 @@ describe('conversations service', () => {
|
|
|
456
500
|
|
|
457
501
|
it('returns ambiguous=true for 503, safe error, marks activity', async () => {
|
|
458
502
|
const sessionData = { id: 'ses_503' };
|
|
459
|
-
|
|
503
|
+
mockMake.mockReturnValue({
|
|
460
504
|
session: {
|
|
461
|
-
create: async () =>
|
|
462
|
-
|
|
463
|
-
data: null,
|
|
464
|
-
error: new Error('Service Unavailable'),
|
|
465
|
-
response: { status: 503 },
|
|
466
|
-
}),
|
|
505
|
+
create: async () => sessionData,
|
|
506
|
+
prompt: async () => { throw clientError('UnexpectedStatus', { status: 503 }); },
|
|
467
507
|
},
|
|
468
508
|
});
|
|
469
509
|
const service = createService();
|
|
@@ -477,10 +517,10 @@ describe('conversations service', () => {
|
|
|
477
517
|
});
|
|
478
518
|
|
|
479
519
|
it('returns ambiguous=true for transport throw, marks activity', async () => {
|
|
480
|
-
|
|
520
|
+
mockMake.mockReturnValue({
|
|
481
521
|
session: {
|
|
482
|
-
create: async () => ({
|
|
483
|
-
|
|
522
|
+
create: async () => ({ id: 'ses_transport' }),
|
|
523
|
+
prompt: async () => { throw new TypeError('fetch failed'); },
|
|
484
524
|
},
|
|
485
525
|
});
|
|
486
526
|
const service = createService();
|
|
@@ -493,14 +533,10 @@ describe('conversations service', () => {
|
|
|
493
533
|
});
|
|
494
534
|
|
|
495
535
|
it('returns ambiguous=true for 408, marks activity', async () => {
|
|
496
|
-
|
|
536
|
+
mockMake.mockReturnValue({
|
|
497
537
|
session: {
|
|
498
|
-
create: async () => ({
|
|
499
|
-
|
|
500
|
-
data: null,
|
|
501
|
-
error: new Error('Request Timeout'),
|
|
502
|
-
response: { status: 408 },
|
|
503
|
-
}),
|
|
538
|
+
create: async () => ({ id: 'ses_408' }),
|
|
539
|
+
prompt: async () => { throw clientError('UnexpectedStatus', { status: 408 }); },
|
|
504
540
|
},
|
|
505
541
|
});
|
|
506
542
|
const service = createService();
|
|
@@ -511,14 +547,10 @@ describe('conversations service', () => {
|
|
|
511
547
|
});
|
|
512
548
|
|
|
513
549
|
it('returns ambiguous=true for 429, marks activity', async () => {
|
|
514
|
-
|
|
550
|
+
mockMake.mockReturnValue({
|
|
515
551
|
session: {
|
|
516
|
-
create: async () => ({
|
|
517
|
-
|
|
518
|
-
data: null,
|
|
519
|
-
error: new Error('Too Many Requests'),
|
|
520
|
-
response: { status: 429 },
|
|
521
|
-
}),
|
|
552
|
+
create: async () => ({ id: 'ses_429' }),
|
|
553
|
+
prompt: async () => { throw clientError('UnexpectedStatus', { status: 429 }); },
|
|
522
554
|
},
|
|
523
555
|
});
|
|
524
556
|
const service = createService();
|
|
@@ -529,10 +561,10 @@ describe('conversations service', () => {
|
|
|
529
561
|
});
|
|
530
562
|
|
|
531
563
|
it('does NOT mark on non-transport, non-ambiguous throw', async () => {
|
|
532
|
-
|
|
564
|
+
mockMake.mockReturnValue({
|
|
533
565
|
session: {
|
|
534
|
-
create: async () => ({
|
|
535
|
-
|
|
566
|
+
create: async () => ({ id: 'ses_perm' }),
|
|
567
|
+
prompt: async () => { throw new Error('Some permanent failure'); },
|
|
536
568
|
},
|
|
537
569
|
});
|
|
538
570
|
const service = createService();
|
|
@@ -552,10 +584,10 @@ describe('conversations service', () => {
|
|
|
552
584
|
markUserMessageSent: throwingMark,
|
|
553
585
|
waitForOpenCodeReady: mockWaitReady,
|
|
554
586
|
});
|
|
555
|
-
|
|
587
|
+
mockMake.mockReturnValue({
|
|
556
588
|
session: {
|
|
557
|
-
create: async () => ({
|
|
558
|
-
|
|
589
|
+
create: async () => ({ id: 'ses_safe' }),
|
|
590
|
+
prompt: async () => undefined,
|
|
559
591
|
},
|
|
560
592
|
});
|
|
561
593
|
const result = await svc.createAndPrompt({ sanitizedInput: baseInput() });
|
|
@@ -568,13 +600,13 @@ describe('conversations service', () => {
|
|
|
568
600
|
it('passes directory/headers through buildOpenCodeUrl and getOpenCodeAuthHeaders', async () => {
|
|
569
601
|
mockBuildUrl.mockReturnValue('http://custom:9999/');
|
|
570
602
|
mockGetHeaders.mockReturnValue({ Authorization: 'Bearer secret123' });
|
|
571
|
-
|
|
603
|
+
mockMake.mockImplementation((config) => {
|
|
572
604
|
expect(config.baseUrl).toBe('http://custom:9999');
|
|
573
605
|
expect(config.headers).toEqual({ Authorization: 'Bearer secret123' });
|
|
574
606
|
return {
|
|
575
607
|
session: {
|
|
576
|
-
create: async () => ({
|
|
577
|
-
|
|
608
|
+
create: async () => ({ id: 'ses_url' }),
|
|
609
|
+
prompt: async () => undefined,
|
|
578
610
|
},
|
|
579
611
|
};
|
|
580
612
|
});
|
|
@@ -585,10 +617,10 @@ describe('conversations service', () => {
|
|
|
585
617
|
|
|
586
618
|
it('awaits waitForOpenCodeReady before calling SDK', async () => {
|
|
587
619
|
let createCalled = false;
|
|
588
|
-
|
|
620
|
+
mockMake.mockReturnValue({
|
|
589
621
|
session: {
|
|
590
|
-
create: async () => { createCalled = true; return {
|
|
591
|
-
|
|
622
|
+
create: async () => { createCalled = true; return { id: 'ses_w' }; },
|
|
623
|
+
prompt: async () => undefined,
|
|
592
624
|
},
|
|
593
625
|
});
|
|
594
626
|
const service = createService();
|
|
@@ -600,16 +632,16 @@ describe('conversations service', () => {
|
|
|
600
632
|
// --- internal timeouts ---
|
|
601
633
|
|
|
602
634
|
it('uses internal timeout signal for session.create', async () => {
|
|
603
|
-
|
|
635
|
+
mockMake.mockReturnValue({
|
|
604
636
|
session: {
|
|
605
637
|
create: async (_params, opts) => {
|
|
606
638
|
expect(opts.signal).toBeDefined();
|
|
607
639
|
expect(opts.signal.aborted).toBe(false);
|
|
608
|
-
return {
|
|
640
|
+
return { id: 'ses_to' };
|
|
609
641
|
},
|
|
610
|
-
|
|
642
|
+
prompt: async (_params, opts) => {
|
|
611
643
|
expect(opts.signal).toBeDefined();
|
|
612
|
-
return
|
|
644
|
+
return undefined;
|
|
613
645
|
},
|
|
614
646
|
},
|
|
615
647
|
});
|
|
@@ -619,7 +651,7 @@ describe('conversations service', () => {
|
|
|
619
651
|
});
|
|
620
652
|
|
|
621
653
|
it('maps AbortError from create to create failure', async () => {
|
|
622
|
-
|
|
654
|
+
mockMake.mockReturnValue({
|
|
623
655
|
session: {
|
|
624
656
|
create: async () => { throw new DOMException('Aborted', 'AbortError'); },
|
|
625
657
|
},
|
|
@@ -631,11 +663,11 @@ describe('conversations service', () => {
|
|
|
631
663
|
expect(result.error).toBe('Failed to create session');
|
|
632
664
|
});
|
|
633
665
|
|
|
634
|
-
it('maps AbortError from
|
|
635
|
-
|
|
666
|
+
it('maps AbortError from prompt to ambiguous=true', async () => {
|
|
667
|
+
mockMake.mockReturnValue({
|
|
636
668
|
session: {
|
|
637
|
-
create: async () => ({
|
|
638
|
-
|
|
669
|
+
create: async () => ({ id: 'ses_pto' }),
|
|
670
|
+
prompt: async () => { throw new DOMException('Aborted', 'AbortError'); },
|
|
639
671
|
},
|
|
640
672
|
});
|
|
641
673
|
const service = createService();
|
|
@@ -649,9 +681,9 @@ describe('conversations service', () => {
|
|
|
649
681
|
// --- create failure no session ID ---
|
|
650
682
|
|
|
651
683
|
it('returns create failure when no session ID in response', async () => {
|
|
652
|
-
|
|
684
|
+
mockMake.mockReturnValue({
|
|
653
685
|
session: {
|
|
654
|
-
create: async () =>
|
|
686
|
+
create: async () => null,
|
|
655
687
|
},
|
|
656
688
|
});
|
|
657
689
|
const service = createService();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const ALLOWED_TOP_LEVEL_KEYS = new Set([
|
|
2
2
|
'input', 'directory', 'messageID', 'model', 'parts',
|
|
3
|
-
'title', 'parentID', 'agent', 'variant', 'metadata',
|
|
3
|
+
'title', 'parentID', 'agent', 'variant', 'metadata', 'skills',
|
|
4
4
|
]);
|
|
5
5
|
const ALLOWED_PART_TYPES = new Set(['text', 'file', 'agent']);
|
|
6
6
|
|
|
@@ -139,6 +139,15 @@ export const validateConversationInput = (body) => {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Optional fields
|
|
142
|
+
let skills;
|
|
143
|
+
if (body.skills !== undefined) {
|
|
144
|
+
if (!Array.isArray(body.skills) || body.skills.some((skill) =>
|
|
145
|
+
!skill || typeof skill.id !== 'string' || !skill.id.trim())) {
|
|
146
|
+
errors.push('skills must be an array of non-empty skill ids');
|
|
147
|
+
} else {
|
|
148
|
+
skills = body.skills.map((skill) => ({ id: skill.id }));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
142
151
|
const title = asOptionalNonEmptyString(body.title);
|
|
143
152
|
if (title === null) errors.push('title must be a non-empty string if provided');
|
|
144
153
|
|
|
@@ -168,6 +177,7 @@ export const validateConversationInput = (body) => {
|
|
|
168
177
|
modelID: body.model.modelID.trim(),
|
|
169
178
|
},
|
|
170
179
|
parts: sanitizedParts,
|
|
180
|
+
...(skills?.length ? { skills } : {}),
|
|
171
181
|
title,
|
|
172
182
|
parentID,
|
|
173
183
|
agent,
|