@toddzheng024/dscode-desktop 0.0.0-stage → 0.7.34
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/LICENSE +21 -0
- package/README.md +120 -2
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/bin/apply_patch +9 -0
- package/cordis.patch.yml +433 -0
- package/docs/CHANGELOG.md +649 -0
- package/docs/account-login.md +44 -0
- package/docs/auto-review.md +128 -0
- package/docs/browser-use.md +784 -0
- package/docs/computer-use.md +81 -0
- package/docs/custom-providers.md +88 -0
- package/docs/demo.md +158 -0
- package/docs/distribution.md +58 -0
- package/docs/dscode-ultra.md +75 -0
- package/docs/email.md +296 -0
- package/docs/exec.md +40 -0
- package/docs/hub-distribution.md +212 -0
- package/docs/memory.md +121 -0
- package/docs/opencode-go.md +61 -0
- package/docs/plugin-hub.md +68 -0
- package/docs/releases/0.2.0.md +28 -0
- package/docs/releases/0.3.0.md +21 -0
- package/docs/releases/0.4.0.md +20 -0
- package/docs/releases/0.5.0.md +19 -0
- package/docs/releases/0.6.0.md +20 -0
- package/docs/releases/0.7.0.md +22 -0
- package/docs/releases/0.7.1.md +20 -0
- package/docs/releases/0.7.10.md +10 -0
- package/docs/releases/0.7.11.md +17 -0
- package/docs/releases/0.7.12.md +11 -0
- package/docs/releases/0.7.13.md +12 -0
- package/docs/releases/0.7.14.md +11 -0
- package/docs/releases/0.7.15.md +11 -0
- package/docs/releases/0.7.16.md +10 -0
- package/docs/releases/0.7.17.md +12 -0
- package/docs/releases/0.7.18.md +13 -0
- package/docs/releases/0.7.19.md +8 -0
- package/docs/releases/0.7.2.md +19 -0
- package/docs/releases/0.7.20.md +10 -0
- package/docs/releases/0.7.21.md +9 -0
- package/docs/releases/0.7.22.md +11 -0
- package/docs/releases/0.7.23.md +14 -0
- package/docs/releases/0.7.24.md +11 -0
- package/docs/releases/0.7.25.md +8 -0
- package/docs/releases/0.7.26.md +18 -0
- package/docs/releases/0.7.27.md +8 -0
- package/docs/releases/0.7.28.md +22 -0
- package/docs/releases/0.7.29.md +12 -0
- package/docs/releases/0.7.3.md +23 -0
- package/docs/releases/0.7.30.md +19 -0
- package/docs/releases/0.7.31.md +14 -0
- package/docs/releases/0.7.32.md +14 -0
- package/docs/releases/0.7.33.md +37 -0
- package/docs/releases/0.7.34.md +24 -0
- package/docs/releases/0.7.4.md +16 -0
- package/docs/releases/0.7.5.md +18 -0
- package/docs/releases/0.7.6.md +29 -0
- package/docs/releases/0.7.7.md +36 -0
- package/docs/releases/0.7.8.md +14 -0
- package/docs/releases/0.7.9.md +15 -0
- package/docs/session-bridge.md +80 -0
- package/docs/session-cards.md +53 -0
- package/docs/session-communication.md +186 -0
- package/docs/session-metrics.md +41 -0
- package/docs/skills.md +81 -0
- package/docs/triggers.md +338 -0
- package/docs/tui-commands.md +204 -0
- package/extensions/browser/manifest.json +22 -0
- package/extensions/browser/popup.css +6 -0
- package/extensions/browser/popup.html +11 -0
- package/extensions/browser/popup.mjs +47 -0
- package/extensions/browser/protocol.mjs +168 -0
- package/extensions/browser/worker.mjs +94 -0
- package/icon.svg +7 -0
- package/locale/en.json +6 -0
- package/locale/zh-cn.json +6 -0
- package/node_modules/@deepseek-ai/node-addon-system/LICENSE +21 -0
- package/node_modules/@deepseek-ai/node-addon-system/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system/README.md +15 -0
- package/node_modules/@deepseek-ai/node-addon-system/README.zh.md +15 -0
- package/node_modules/@deepseek-ai/node-addon-system/lib/flock.d.ts +13 -0
- package/node_modules/@deepseek-ai/node-addon-system/lib/flock.js +51 -0
- package/node_modules/@deepseek-ai/node-addon-system/lib/index.d.ts +73 -0
- package/node_modules/@deepseek-ai/node-addon-system/lib/index.js +98 -0
- package/node_modules/@deepseek-ai/node-addon-system/package.json +45 -0
- package/node_modules/@deepseek-ai/node-addon-system/src/flock.c +164 -0
- package/node_modules/@deepseek-ai/node-addon-system/src/main.c +298 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/LICENSE +28 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.md +9 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.zh.md +9 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/bin/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/package.json +31 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/prebuilds.json +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/LICENSE +28 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.md +9 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.zh.md +9 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/bin/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/package.json +31 -0
- package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/prebuilds.json +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/LICENSE +28 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.md +11 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.zh.md +11 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/glibc/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/landlock-run +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/musl/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/package.json +31 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/prebuilds.json +24 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/LICENSE +28 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.i18n.yaml +6 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.md +11 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.zh.md +11 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/glibc/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/landlock-run +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/musl/system.node +0 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/package.json +31 -0
- package/node_modules/@deepseek-ai/node-addon-system-linux-x64/prebuilds.json +24 -0
- package/package.json +156 -4
- package/plugins/account/index.mjs +178 -0
- package/plugins/account/state.mjs +123 -0
- package/plugins/auto-review/audit.mjs +24 -0
- package/plugins/auto-review/index.mjs +267 -0
- package/plugins/auto-review/policy.mjs +92 -0
- package/plugins/browser/SKILL.md +158 -0
- package/plugins/browser/access.mjs +143 -0
- package/plugins/browser/config.mjs +60 -0
- package/plugins/browser/connection.mjs +385 -0
- package/plugins/browser/desktop-client.mjs +282 -0
- package/plugins/browser/desktop-host.mjs +74 -0
- package/plugins/browser/document-identity.mjs +57 -0
- package/plugins/browser/extension-relay.mjs +66 -0
- package/plugins/browser/files.mjs +32 -0
- package/plugins/browser/index.mjs +235 -0
- package/plugins/browser/mcp-entry.mjs +18 -0
- package/plugins/browser/mcp-tools.mjs +21 -0
- package/plugins/browser/presentation.mjs +45 -0
- package/plugins/browser/preview.mjs +77 -0
- package/plugins/browser/review.mjs +20 -0
- package/plugins/browser/screenshots.mjs +35 -0
- package/plugins/clipboard-image/clipboard-image.swift +56 -0
- package/plugins/clipboard-image/index.mjs +70 -0
- package/plugins/code-review/baseline.mjs +143 -0
- package/plugins/code-review/git.mjs +163 -0
- package/plugins/code-review/index.mjs +236 -0
- package/plugins/compaction/engine.mjs +304 -0
- package/plugins/compaction/tetris.mjs +65 -0
- package/plugins/compaction/threshold.mjs +99 -0
- package/plugins/computer-use/DESKTOP-SKILL.md +64 -0
- package/plugins/computer-use/desktop-host.mjs +75 -0
- package/plugins/computer-use/desktop-skill.mjs +28 -0
- package/plugins/computer-use/desktop-upstream.mjs +3 -0
- package/plugins/computer-use/index.mjs +37 -0
- package/plugins/cordis-config/plain.mjs +31 -0
- package/plugins/credentials/index.mjs +73 -0
- package/plugins/custom/adapter.mjs +110 -0
- package/plugins/custom/config.mjs +85 -0
- package/plugins/custom/desktop-client.mjs +150 -0
- package/plugins/custom/desktop-host.mjs +78 -0
- package/plugins/custom/images.mjs +32 -0
- package/plugins/custom/index.mjs +145 -0
- package/plugins/custom/wire.mjs +179 -0
- package/plugins/desktop/client.mjs +1041 -0
- package/plugins/desktop/index.mjs +10 -0
- package/plugins/desktop/shell-environment.mjs +9 -0
- package/plugins/desktop-workspace/hooks.mjs +29 -0
- package/plugins/desktop-workspace/index.mjs +37 -0
- package/plugins/dscode/board.mjs +260 -0
- package/plugins/dscode/child-target.mjs +11 -0
- package/plugins/dscode/control.mjs +17 -0
- package/plugins/dscode/desktop-client.mjs +55 -0
- package/plugins/dscode/desktop-host.mjs +37 -0
- package/plugins/dscode/index.mjs +270 -0
- package/plugins/email/cli.mjs +32 -0
- package/plugins/email/contacts.mjs +36 -0
- package/plugins/email/desktop-client.mjs +117 -0
- package/plugins/email/desktop-host.mjs +54 -0
- package/plugins/email/desktop-service.mjs +106 -0
- package/plugins/email/gmail-oauth.mjs +69 -0
- package/plugins/email/gmail-store.mjs +2 -0
- package/plugins/email/gmail.mjs +194 -0
- package/plugins/email/imap.mjs +136 -0
- package/plugins/email/inbox.d.mts +25 -0
- package/plugins/email/inbox.mjs +76 -0
- package/plugins/email/smtp.mjs +70 -0
- package/plugins/email/store.mjs +32 -0
- package/plugins/email-tools/index.mjs +46 -0
- package/plugins/exec/cli.mjs +64 -0
- package/plugins/exec/index.mjs +112 -0
- package/plugins/grok/adapter.mjs +114 -0
- package/plugins/grok/auth.mjs +76 -0
- package/plugins/grok/billing.mjs +147 -0
- package/plugins/grok/index.mjs +123 -0
- package/plugins/grok/models.mjs +129 -0
- package/plugins/grok/status.mjs +29 -0
- package/plugins/grok/wire.mjs +268 -0
- package/plugins/hub/catalog.mjs +82 -0
- package/plugins/hub/desktop-client.mjs +114 -0
- package/plugins/hub/desktop-host.mjs +129 -0
- package/plugins/i18n/messages.d.mts +21 -0
- package/plugins/i18n/messages.mjs +537 -0
- package/plugins/jev/approval.mjs +131 -0
- package/plugins/jev/client.mjs +74 -0
- package/plugins/jev/index.mjs +100 -0
- package/plugins/memory/content.mjs +57 -0
- package/plugins/memory/desktop-host.mjs +3 -0
- package/plugins/memory/index.mjs +168 -0
- package/plugins/memory/pipeline.mjs +79 -0
- package/plugins/memory/store.mjs +93 -0
- package/plugins/message-source/kind.mjs +20 -0
- package/plugins/opencode-go/adapter.mjs +96 -0
- package/plugins/opencode-go/command.mjs +128 -0
- package/plugins/opencode-go/index.mjs +121 -0
- package/plugins/opencode-go/models.mjs +58 -0
- package/plugins/opencode-go/oauth.mjs +194 -0
- package/plugins/opencode-go/search.mjs +86 -0
- package/plugins/opencode-go/usage.mjs +120 -0
- package/plugins/opencode-go/version.mjs +10 -0
- package/plugins/opencode-go/wire.mjs +73 -0
- package/plugins/openrouter/adapter.mjs +232 -0
- package/plugins/openrouter/index.mjs +124 -0
- package/plugins/openrouter/models.mjs +157 -0
- package/plugins/openrouter/search.mjs +113 -0
- package/plugins/openrouter/wire.mjs +101 -0
- package/plugins/providers/catalog.mjs +160 -0
- package/plugins/providers/chat-messages.mjs +120 -0
- package/plugins/providers/chat-stream.mjs +174 -0
- package/plugins/providers/desktop-client.mjs +84 -0
- package/plugins/providers/desktop-host.mjs +63 -0
- package/plugins/providers/effort.mjs +35 -0
- package/plugins/providers/http-errors.mjs +50 -0
- package/plugins/providers/openrouter-account.mjs +171 -0
- package/plugins/session-bridge/client.mjs +106 -0
- package/plugins/session-bridge/communication.mjs +260 -0
- package/plugins/session-bridge/index.mjs +67 -0
- package/plugins/session-bridge/mailbox.mjs +267 -0
- package/plugins/session-bridge/paths.mjs +21 -0
- package/plugins/session-bridge/server.mjs +172 -0
- package/plugins/session-bridge/tasks.mjs +229 -0
- package/plugins/session-cards/content.mjs +64 -0
- package/plugins/session-cards/index.mjs +38 -0
- package/plugins/session-cards/manager.mjs +140 -0
- package/plugins/session-metrics/attribution.mjs +30 -0
- package/plugins/session-metrics/balance.mjs +81 -0
- package/plugins/session-metrics/desktop-client.mjs +62 -0
- package/plugins/session-metrics/desktop-host.mjs +46 -0
- package/plugins/session-metrics/index.mjs +97 -0
- package/plugins/session-metrics/pricing.mjs +84 -0
- package/plugins/session-metrics/rate.mjs +59 -0
- package/plugins/session-metrics/store.mjs +75 -0
- package/plugins/session-metrics/turns.mjs +134 -0
- package/plugins/session-metrics/view.mjs +272 -0
- package/plugins/time-marks/desktop-host.mjs +2 -0
- package/plugins/time-marks/index.mjs +116 -0
- package/plugins/time-marks/marks.mjs +152 -0
- package/plugins/triggers/cli.mjs +558 -0
- package/plugins/triggers/commands.mjs +143 -0
- package/plugins/triggers/config.mjs +264 -0
- package/plugins/triggers/desktop-client.mjs +155 -0
- package/plugins/triggers/desktop-host.mjs +88 -0
- package/plugins/triggers/desktop-management.mjs +97 -0
- package/plugins/triggers/desktop-scheduler.mjs +125 -0
- package/plugins/triggers/host.mjs +24 -0
- package/plugins/triggers/index.mjs +27 -0
- package/plugins/triggers/job-cli.mjs +110 -0
- package/plugins/triggers/jobs.mjs +156 -0
- package/plugins/triggers/launchd.mjs +101 -0
- package/plugins/triggers/lease.mjs +24 -0
- package/plugins/triggers/log.mjs +104 -0
- package/plugins/triggers/management.mjs +181 -0
- package/plugins/triggers/options.mjs +117 -0
- package/plugins/triggers/overlay.mjs +7 -0
- package/plugins/triggers/poll.mjs +65 -0
- package/plugins/triggers/run.mjs +157 -0
- package/plugins/triggers/schedule.mjs +40 -0
- package/plugins/triggers/scheduler-service.mjs +46 -0
- package/plugins/triggers/scheduler.mjs +92 -0
- package/plugins/triggers/session-run.mjs +125 -0
- package/plugins/triggers/session.mjs +48 -0
- package/plugins/triggers/source-emit.mjs +19 -0
- package/plugins/triggers/source-host.mjs +78 -0
- package/plugins/triggers/source-ingress.mjs +70 -0
- package/plugins/triggers/source-sandbox.mjs +35 -0
- package/plugins/triggers/sources.mjs +45 -0
- package/plugins/triggers/spool.mjs +145 -0
- package/plugins/triggers/tools.mjs +67 -0
- package/plugins/triggers/unattended.mjs +18 -0
- package/plugins/tui-tools/composition.mjs +28 -0
- package/plugins/tui-tools/desktop-host.mjs +2 -0
- package/plugins/tui-tools/doctor-cli.mjs +22 -0
- package/plugins/tui-tools/doctor.mjs +149 -0
- package/plugins/tui-tools/hook-sources.mjs +113 -0
- package/plugins/tui-tools/hooks.mjs +23 -0
- package/plugins/tui-tools/index.mjs +269 -0
- package/plugins/tui-tools/sandbox-runner.mjs +123 -0
- package/plugins/tui-tools/shell.mjs +27 -0
- package/plugins/tui-tools/update.mjs +111 -0
- package/plugins/tui-tools/workspace-discovery.mjs +129 -0
- package/plugins/ultra/policy.mjs +42 -0
- package/plugins/worktree-subagent/worktree.mjs +56 -0
- package/runtime-sources.json +94 -0
- package/vendor/bash/LICENSE +21 -0
- package/vendor/bash/index.js +717 -0
- package/vendor/bash/types/background.d.ts +50 -0
- package/vendor/bash/types/index.d.ts +38 -0
- package/vendor/bash/types/render.d.ts +57 -0
- package/vendor/command-goal/LICENSE +21 -0
- package/vendor/command-goal/index.js +210 -0
- package/vendor/command-goal/types/index.d.ts +10 -0
- package/vendor/computer-use/LICENSE +21 -0
- package/vendor/computer-use/lib/approval-policy.js +12 -0
- package/vendor/computer-use/lib/approval-policy.js.map +1 -0
- package/vendor/computer-use/lib/artifacts.js +79 -0
- package/vendor/computer-use/lib/artifacts.js.map +1 -0
- package/vendor/computer-use/lib/backend.js +3 -0
- package/vendor/computer-use/lib/backend.js.map +1 -0
- package/vendor/computer-use/lib/client.js +369 -0
- package/vendor/computer-use/lib/client.js.map +1 -0
- package/vendor/computer-use/lib/config.js +159 -0
- package/vendor/computer-use/lib/config.js.map +1 -0
- package/vendor/computer-use/lib/confirmations.js +99 -0
- package/vendor/computer-use/lib/confirmations.js.map +1 -0
- package/vendor/computer-use/lib/diff.js +58 -0
- package/vendor/computer-use/lib/diff.js.map +1 -0
- package/vendor/computer-use/lib/errors.js +22 -0
- package/vendor/computer-use/lib/errors.js.map +1 -0
- package/vendor/computer-use/lib/exposure.js +244 -0
- package/vendor/computer-use/lib/exposure.js.map +1 -0
- package/vendor/computer-use/lib/index.js +66 -0
- package/vendor/computer-use/lib/index.js.map +1 -0
- package/vendor/computer-use/lib/leases.js +262 -0
- package/vendor/computer-use/lib/leases.js.map +1 -0
- package/vendor/computer-use/lib/providers/macos.js +243 -0
- package/vendor/computer-use/lib/providers/macos.js.map +1 -0
- package/vendor/computer-use/lib/providers/native-helper.js +726 -0
- package/vendor/computer-use/lib/providers/native-helper.js.map +1 -0
- package/vendor/computer-use/lib/providers/unsupported.js +51 -0
- package/vendor/computer-use/lib/providers/unsupported.js.map +1 -0
- package/vendor/computer-use/lib/service.js +608 -0
- package/vendor/computer-use/lib/service.js.map +1 -0
- package/vendor/computer-use/lib/skill.js +136 -0
- package/vendor/computer-use/lib/skill.js.map +1 -0
- package/vendor/computer-use/lib/target-resolver.js +180 -0
- package/vendor/computer-use/lib/target-resolver.js.map +1 -0
- package/vendor/computer-use/lib/tools.js +511 -0
- package/vendor/computer-use/lib/tools.js.map +1 -0
- package/vendor/computer-use/lib/types/approval-policy.d.ts +13 -0
- package/vendor/computer-use/lib/types/approval-policy.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/artifacts.d.ts +10 -0
- package/vendor/computer-use/lib/types/artifacts.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/backend.d.ts +124 -0
- package/vendor/computer-use/lib/types/backend.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/client/index.d.ts +83 -0
- package/vendor/computer-use/lib/types/client/index.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/config.d.ts +94 -0
- package/vendor/computer-use/lib/types/config.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/confirmations.d.ts +23 -0
- package/vendor/computer-use/lib/types/confirmations.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/diff.d.ts +5 -0
- package/vendor/computer-use/lib/types/diff.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/errors.d.ts +15 -0
- package/vendor/computer-use/lib/types/errors.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/exposure.d.ts +30 -0
- package/vendor/computer-use/lib/types/exposure.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/index.d.ts +21 -0
- package/vendor/computer-use/lib/types/index.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/leases.d.ts +81 -0
- package/vendor/computer-use/lib/types/leases.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/providers/macos.d.ts +39 -0
- package/vendor/computer-use/lib/types/providers/macos.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/providers/native-helper.d.ts +48 -0
- package/vendor/computer-use/lib/types/providers/native-helper.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/providers/unsupported.d.ts +23 -0
- package/vendor/computer-use/lib/types/providers/unsupported.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/service.d.ts +58 -0
- package/vendor/computer-use/lib/types/service.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/skill.d.ts +14 -0
- package/vendor/computer-use/lib/types/skill.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/target-resolver.d.ts +38 -0
- package/vendor/computer-use/lib/types/target-resolver.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/tools.d.ts +6 -0
- package/vendor/computer-use/lib/types/tools.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/types.d.ts +270 -0
- package/vendor/computer-use/lib/types/types.d.ts.map +1 -0
- package/vendor/computer-use/lib/types/web.d.ts +33 -0
- package/vendor/computer-use/lib/types/web.d.ts.map +1 -0
- package/vendor/computer-use/lib/types.js +9 -0
- package/vendor/computer-use/lib/types.js.map +1 -0
- package/vendor/computer-use/lib/web.js +188 -0
- package/vendor/computer-use/lib/web.js.map +1 -0
- package/vendor/computer-use/native/macos/Sources/Fixture/main.swift +412 -0
- package/vendor/computer-use/native/macos/Sources/Helper/CursorImage.swift +11 -0
- package/vendor/computer-use/native/macos/Sources/Helper/CursorMotion.swift +67 -0
- package/vendor/computer-use/native/macos/Sources/Helper/CursorOverlay.swift +840 -0
- package/vendor/computer-use/native/macos/Sources/Helper/TargetedPointer.swift +327 -0
- package/vendor/computer-use/native/macos/Sources/Helper/WindowNumberMatcher.swift +29 -0
- package/vendor/computer-use/native/macos/Sources/Helper/main.swift +1244 -0
- package/vendor/computer-use/native/macos/Sources/Monitor/main.swift +153 -0
- package/vendor/computer-use/native/macos/bin/dsh-computer-use-helper +0 -0
- package/vendor/computer-use/native/macos/manifest.json +14 -0
- package/vendor/persistent/LICENSE +21 -0
- package/vendor/persistent/index.js +411 -0
- package/vendor/persistent/types/index.d.ts +24 -0
- package/vendor/subagent/LICENSE +21 -0
- package/vendor/subagent/index.js +694 -0
- package/vendor/subagent/invariant.js +52 -0
- package/vendor/subagent/model-selection-settings.js +68 -0
- package/vendor/subagent/types/index.d.ts +82 -0
- package/vendor/subagent/types/invariant.d.ts +16 -0
- package/vendor/subagent/types/list-models.d.ts +10 -0
- package/vendor/subagent/types/model-selection-settings.d.ts +45 -0
- package/vendor/subagent/types/model-selection-state.d.ts +48 -0
- package/vendor/subagent/types/model-selection.d.ts +81 -0
- package/vendor/subagent-core/LICENSE +21 -0
- package/vendor/subagent-core/index.js +3202 -0
- package/vendor/subagent-core/invariant.js +76 -0
- package/vendor/subagent-core/typert.host.d.ts +3 -0
- package/vendor/subagent-core/typert.host.js +978 -0
- package/vendor/subagent-core/typert.remote-client.d.ts +25 -0
- package/vendor/subagent-core/typert.remote-client.js +114 -0
- package/vendor/subagent-core/types/archive-admission.d.ts +16 -0
- package/vendor/subagent-core/types/archive-admission.js +151 -0
- package/vendor/subagent-core/types/assistant-output.d.ts +48 -0
- package/vendor/subagent-core/types/assistant-output.js +73 -0
- package/vendor/subagent-core/types/catalog.d.ts +84 -0
- package/vendor/subagent-core/types/catalog.js +109 -0
- package/vendor/subagent-core/types/child-agent.d.ts +156 -0
- package/vendor/subagent-core/types/child-agent.js +213 -0
- package/vendor/subagent-core/types/client.d.ts +8 -0
- package/vendor/subagent-core/types/client.js +7 -0
- package/vendor/subagent-core/types/continuation-activation.d.ts +267 -0
- package/vendor/subagent-core/types/continuation-activation.js +700 -0
- package/vendor/subagent-core/types/continuation-messages.d.ts +62 -0
- package/vendor/subagent-core/types/continuation-messages.js +106 -0
- package/vendor/subagent-core/types/continuation.d.ts +124 -0
- package/vendor/subagent-core/types/continuation.js +455 -0
- package/vendor/subagent-core/types/control-types.d.ts +135 -0
- package/vendor/subagent-core/types/control-types.js +8 -0
- package/vendor/subagent-core/types/control.d.ts +45 -0
- package/vendor/subagent-core/types/control.js +79 -0
- package/vendor/subagent-core/types/depth.d.ts +31 -0
- package/vendor/subagent-core/types/depth.js +39 -0
- package/vendor/subagent-core/types/descriptor.d.ts +144 -0
- package/vendor/subagent-core/types/descriptor.js +193 -0
- package/vendor/subagent-core/types/error.d.ts +11 -0
- package/vendor/subagent-core/types/error.js +14 -0
- package/vendor/subagent-core/types/inbox.d.ts +43 -0
- package/vendor/subagent-core/types/inbox.js +61 -0
- package/vendor/subagent-core/types/index.d.ts +320 -0
- package/vendor/subagent-core/types/index.js +475 -0
- package/vendor/subagent-core/types/internal.d.ts +58 -0
- package/vendor/subagent-core/types/internal.js +58 -0
- package/vendor/subagent-core/types/invariant.d.ts +13 -0
- package/vendor/subagent-core/types/invariant.js +91 -0
- package/vendor/subagent-core/types/lifecycle.d.ts +113 -0
- package/vendor/subagent-core/types/lifecycle.js +180 -0
- package/vendor/subagent-core/types/list-children.d.ts +36 -0
- package/vendor/subagent-core/types/list-children.js +166 -0
- package/vendor/subagent-core/types/out-of-process.d.ts +118 -0
- package/vendor/subagent-core/types/out-of-process.js +219 -0
- package/vendor/subagent-core/types/projection-types.d.ts +82 -0
- package/vendor/subagent-core/types/projection-types.js +7 -0
- package/vendor/subagent-core/types/projection.d.ts +100 -0
- package/vendor/subagent-core/types/projection.js +157 -0
- package/vendor/subagent-core/types/run-settlement.d.ts +17 -0
- package/vendor/subagent-core/types/run-settlement.js +70 -0
- package/vendor/subagent-core/types/types.d.ts +377 -0
- package/vendor/subagent-core/types/types.js +19 -0
- package/vendor/subagent-driver/LICENSE +21 -0
- package/vendor/subagent-driver/index.js +255 -0
- package/vendor/subagent-driver/types/index.d.ts +33 -0
- package/vendor/subagent-driver/types/structured.d.ts +42 -0
- package/vendor/subagent-fork/LICENSE +21 -0
- package/vendor/subagent-fork/index.js +61 -0
- package/vendor/subagent-fork/types/index.d.ts +20 -0
- package/vendor/subagent-spawn/LICENSE +21 -0
- package/vendor/subagent-spawn/index.js +45 -0
- package/vendor/subagent-spawn/types/index.d.ts +19 -0
- package/vendor/terminal/LICENSE +21 -0
- package/vendor/terminal/index.js +1093 -0
- package/vendor/terminal/types/config.d.ts +82 -0
- package/vendor/terminal/types/index.d.ts +36 -0
- package/vendor/terminal/types/sanitize.d.ts +47 -0
- package/vendor/terminal/types/session.d.ts +87 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { serializeMessages } from '../providers/chat-messages.mjs';
|
|
3
|
+
export { serializeMessages } from '../providers/chat-messages.mjs';
|
|
4
|
+
export { sseData, mapUsage, translate } from '../providers/chat-stream.mjs';
|
|
5
|
+
export { errorCode, errorMessage, retryAfterMs } from '../providers/http-errors.mjs';
|
|
6
|
+
import { OPENROUTER_EFFORTS } from '../providers/catalog.mjs';
|
|
7
|
+
import { ultraRequest } from '../ultra/policy.mjs';
|
|
8
|
+
|
|
9
|
+
// OpenRouter chat completions on the wire: request bodies, the SSE stream, and
|
|
10
|
+
// the translation into harness stream chunks. Pure functions; the adapter owns I/O.
|
|
11
|
+
|
|
12
|
+
export const PROVIDER = 'openrouter';
|
|
13
|
+
/** `replayState.response.kind` for responses this adapter produced. */
|
|
14
|
+
export const REPLAY_KIND = 'dscode-openrouter';
|
|
15
|
+
// OpenRouter serves DeepSeek V4 thinking as none/high/xhigh; the route keeps the
|
|
16
|
+
// official off/low/high/max detents (and Ultra on max) with the spelling it accepts.
|
|
17
|
+
const DEEPSEEK_WIRE = Object.freeze({ ...OPENROUTER_EFFORTS, ultra: OPENROUTER_EFFORTS.max });
|
|
18
|
+
const DEEPSEEK_V4 = /^deepseek\/deepseek-v4/;
|
|
19
|
+
// Alibaba caches only at explicit breakpoints.
|
|
20
|
+
const EXPLICIT_CACHE_MODELS = /^qwen\//;
|
|
21
|
+
const DEFAULT_LEVELS = ['low', 'medium', 'high'];
|
|
22
|
+
// OpenRouter picks the upstream endpoint, but only among endpoints that honour every
|
|
23
|
+
// request parameter (by default it silently drops unsupported ones) and that do not
|
|
24
|
+
// serve fp4-quantized weights. INT4 stays: Kimi ships native INT4 weights.
|
|
25
|
+
const ROUTING = Object.freeze({ require_parameters: true, quantizations: Object.freeze(['int4', 'int8', 'fp6', 'fp8', 'mxfp8', 'fp16', 'bf16', 'fp32', 'unknown']) });
|
|
26
|
+
const NAMES = { off: 'Off', minimal: 'Minimal', low: 'Low', medium: 'Medium', high: 'High', xhigh: 'XHigh', max: 'Max', ultra: 'Ultra' };
|
|
27
|
+
export const ULTRA_DESCRIPTION = 'DSCODE: max reasoning plus deliberate subagent collaboration; higher total token use.';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reasoning controls a model offers through this route.
|
|
31
|
+
* @param model - OpenRouter model id.
|
|
32
|
+
* @param entry - the model's listing entry, if any.
|
|
33
|
+
* @returns `{ levels, defaultEffort?, wire }` with harness effort ids and their wire
|
|
34
|
+
* spelling, or undefined when the model has no reasoning control.
|
|
35
|
+
*/
|
|
36
|
+
export function modelReasoning(model, entry) {
|
|
37
|
+
if (DEEPSEEK_V4.test(model)) return { levels: ['off', 'low', 'high', 'max', 'ultra'], defaultEffort: 'high', wire: DEEPSEEK_WIRE };
|
|
38
|
+
const reasoning = entry?.reasoning;
|
|
39
|
+
if (!reasoning) return undefined;
|
|
40
|
+
// No allowlist means OpenRouter maps any standard level; offer the common three.
|
|
41
|
+
const levels = reasoning.efforts ?? DEFAULT_LEVELS;
|
|
42
|
+
const ultra = levels.includes('max');
|
|
43
|
+
const wire = Object.fromEntries([...(reasoning.mandatory ? [] : [['off', 'none']]), ...levels.map(level => [level, level]), ...(ultra ? [['ultra', 'max']] : [])]);
|
|
44
|
+
// Like the official route, requests default to high; a model without high keeps its own default.
|
|
45
|
+
const defaultEffort = levels.includes('high') ? 'high' : levels.includes(reasoning.defaultEffort) ? reasoning.defaultEffort : undefined;
|
|
46
|
+
return { levels: Object.keys(wire), ...(defaultEffort ? { defaultEffort } : {}), wire };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Display metadata for one harness effort id. */
|
|
50
|
+
export function effortInfo(id) {
|
|
51
|
+
return { id, name: NAMES[id] ?? id, ...(id === 'ultra' ? { description: ULTRA_DESCRIPTION } : {}) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A title needs no deliberation: reasoning off when the model allows it, else its lowest level. */
|
|
55
|
+
function titleEffort(reasoning) {
|
|
56
|
+
return reasoning.wire.off !== undefined ? 'off' : reasoning.levels.find(level => level !== 'ultra');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cacheMarked = content => typeof content === 'string'
|
|
60
|
+
? [{ type: 'text', text: content, cache_control: { type: 'ephemeral' } }]
|
|
61
|
+
: content.map((part, index) => index === content.findLastIndex(candidate => candidate.type === 'text') ? { ...part, cache_control: { type: 'ephemeral' } } : part);
|
|
62
|
+
|
|
63
|
+
/** Explicit cache breakpoints for models that cache only at them: the system prompt and the latest user turn. */
|
|
64
|
+
export function withCacheBreakpoints(messages, model) {
|
|
65
|
+
if (!EXPLICIT_CACHE_MODELS.test(model)) return messages;
|
|
66
|
+
const targets = new Set([messages.findIndex(message => message.role === 'system'), messages.findLastIndex(message => message.role === 'user')]);
|
|
67
|
+
return messages.map((message, index) => targets.has(index) && message.content !== '' ? { ...message, content: cacheMarked(message.content) } : message);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The chat-completions body for one harness request.
|
|
72
|
+
* @param options - harness request (images already prepared into `images`).
|
|
73
|
+
* @param context - `{ entry, images }`: the model's listing entry and prepared request images.
|
|
74
|
+
*/
|
|
75
|
+
export function requestBody(options, { entry, images } = {}) {
|
|
76
|
+
const reasoning = modelReasoning(options.model, entry);
|
|
77
|
+
const effort = options.purpose === 'session-title' && reasoning ? titleEffort(reasoning) : options.reasoningEffort;
|
|
78
|
+
const wireEffort = effort === undefined ? undefined : reasoning?.wire[effort];
|
|
79
|
+
if (effort !== undefined && reasoning !== undefined && wireEffort === undefined) {
|
|
80
|
+
throw new LlmError(`OpenRouter model "${options.model}" does not offer reasoning effort "${effort}"`, 'UNSUPPORTED_REASONING_EFFORT');
|
|
81
|
+
}
|
|
82
|
+
let messages = serializeMessages(options.messages, { model: options.model, system: options.system, images, dialect: { provider: options.provider ?? PROVIDER } });
|
|
83
|
+
messages = withCacheBreakpoints(ultraRequest(options, messages), options.model);
|
|
84
|
+
// Delegation tools are offered at every effort; workflow and ralph never are.
|
|
85
|
+
const tools = (options.tools ?? []).filter(tool => tool.name !== 'workflow' && tool.name !== 'ralph')
|
|
86
|
+
.map(tool => ({ type: 'function', function: { name: tool.name, description: tool.description, parameters: tool.parameters } }));
|
|
87
|
+
return {
|
|
88
|
+
model: options.model,
|
|
89
|
+
messages,
|
|
90
|
+
stream: true,
|
|
91
|
+
...(tools.length > 0 ? { tools } : {}),
|
|
92
|
+
...(wireEffort !== undefined ? { reasoning: { effort: wireEffort } } : {}),
|
|
93
|
+
// Endpoints declare `max_tokens`; `max_completion_tokens` would fail `require_parameters` almost everywhere.
|
|
94
|
+
...(options.maxTokens !== undefined ? { max_tokens: options.maxTokens } : {}),
|
|
95
|
+
...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
|
|
96
|
+
...(options.stop !== undefined ? { stop: options.stop } : {}),
|
|
97
|
+
// The sticky-routing key: one session keeps one upstream endpoint and its warm cache.
|
|
98
|
+
...(options.sessionId !== undefined ? { session_id: String(options.sessionId).slice(0, 256) } : {}),
|
|
99
|
+
provider: { require_parameters: ROUTING.require_parameters, quantizations: [...ROUTING.quantizations] },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Model providers `/provider` switches between. DeepSeek's official API is the
|
|
2
|
+
// native `llm-deepseek` route; OpenRouter is DSCODE's own adapter
|
|
3
|
+
// (plugins/openrouter), which serves OpenRouter's live model listing and is always
|
|
4
|
+
// registered, as are the Grok and OpenCode Go routes. This module ships beside the TUI too, so it imports nothing.
|
|
5
|
+
|
|
6
|
+
export const PROVIDERS = Object.freeze([
|
|
7
|
+
{ id: 'deepseek-official', name: 'DeepSeek', aliases: ['deepseek', 'deepseek-official', 'official'], credentialRef: 'DEEPSEEK_API_KEY', defaultModel: 'deepseek-flash' },
|
|
8
|
+
// The optional management key reads account data only; it cannot call models.
|
|
9
|
+
{ id: 'openrouter', name: 'OpenRouter', aliases: ['openrouter', 'open-router'], credentialRef: 'OPENROUTER_API_KEY', managementRef: 'OPENROUTER_MANAGEMENT_KEY', defaultModel: 'deepseek/deepseek-v4-flash' },
|
|
10
|
+
// The Grok subscription rail: the token is the local 'grok login', read-only (plugins/grok).
|
|
11
|
+
{ id: 'grok', name: 'Grok', aliases: ['grok', 'xai', 'x-ai'], credentialRef: 'GROK_CLI_TOKEN', defaultModel: 'grok-4.6' },
|
|
12
|
+
// The OpenCode Go subscription: its chat-completions models, authorised only by an OpenCode
|
|
13
|
+
// account login. `login` names the command that sets the credential up; there is no key to paste.
|
|
14
|
+
{ id: 'opencode-go', name: 'OpenCode Go', aliases: ['opencode-go', 'opencode', 'go'], credentialRef: 'OPENCODE_OAUTH', login: '/opencode login', defaultModel: 'kimi-k3' },
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
// The pi-ai adapter served OpenRouter until 0.7.6, from this settings section.
|
|
18
|
+
const PI_AI_NS = 'llm-pi-ai';
|
|
19
|
+
|
|
20
|
+
// OpenRouter serves DeepSeek V4 thinking as none/high/xhigh. DeepSeek itself
|
|
21
|
+
// answers `low` as high and `max` as xhigh, so the route offers the official
|
|
22
|
+
// low/high/max detents (and Ultra on top of max) with the wire spelling OpenRouter
|
|
23
|
+
// accepts; session cards and delegated children that ask for `low` keep working.
|
|
24
|
+
export const OPENROUTER_EFFORTS = Object.freeze({ off: 'none', low: 'high', high: 'high', max: 'xhigh' });
|
|
25
|
+
|
|
26
|
+
/** The DeepSeek models the OpenRouter route serves, with their official-route counterparts. */
|
|
27
|
+
export const OPENROUTER_MODELS = Object.freeze([
|
|
28
|
+
{ id: 'deepseek/deepseek-v4-flash', name: 'DeepSeek V4 Flash', official: ['deepseek-flash', 'deepseek-v4-flash'] },
|
|
29
|
+
{ id: 'deepseek/deepseek-v4-pro', name: 'DeepSeek V4 Pro', official: ['deepseek-v4-pro'] },
|
|
30
|
+
{ id: 'deepseek/deepseek-v4-flash-vision-exp', name: 'DeepSeek V4 Flash Vision Exp', official: ['deepseek-v4-flash-vision-exp'] },
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Remove the `openrouter` profile earlier builds wrote into the pi-ai section. The
|
|
35
|
+
* pi-ai adapter is no longer mounted, so the profile is inert, and it would claim the
|
|
36
|
+
* route a second time if that adapter were ever mounted again.
|
|
37
|
+
* Never throws: /model and /provider must open regardless.
|
|
38
|
+
* @returns whether the settings changed.
|
|
39
|
+
*/
|
|
40
|
+
export async function migrateOpenRouterProfile(settings) {
|
|
41
|
+
try {
|
|
42
|
+
const descriptor = settings?.describe?.({ redactSecrets: true }).find(entry => entry.ns === PI_AI_NS);
|
|
43
|
+
if (!descriptor || settings.writable !== true || descriptor.value?.providers?.openrouter === undefined) return false;
|
|
44
|
+
await settings.mutate(PI_AI_NS, [{ op: 'unset', path: ['providers', 'openrouter'] }], descriptor.revision);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function providerSpec(id) {
|
|
52
|
+
if (canonicalProvider(id) !== id) {
|
|
53
|
+
const provider = PROVIDERS.find(provider => provider.id === canonicalProvider(id));
|
|
54
|
+
return { ...provider, id, name: `DSCODE ${provider.name}` };
|
|
55
|
+
}
|
|
56
|
+
return PROVIDERS.find(provider => provider.id === id) ?? (typeof id === 'string' && /^custom-[a-zA-Z0-9-]+$/.test(id) ? { id, name: 'Custom', aliases: [id] } : undefined);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Desktop keeps its enhanced adapter separate from the native pi-ai route. */
|
|
60
|
+
export const canonicalProvider = provider => provider === 'dscode-openrouter' ? 'openrouter' : provider === 'dscode-opencode-go' ? 'opencode-go' : provider;
|
|
61
|
+
export const isOpenRouterRoute = provider => canonicalProvider(provider) === 'openrouter';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a `/provider` or `/login` argument.
|
|
65
|
+
* @param raw - text after the command name.
|
|
66
|
+
* @returns a provider id, `undefined` for no argument, or `null` when unrecognized. Callers must not echo the text: it may be a pasted key.
|
|
67
|
+
*/
|
|
68
|
+
export function providerArgument(raw) {
|
|
69
|
+
const value = String(raw ?? '').trim().toLowerCase();
|
|
70
|
+
if (value === '') return undefined;
|
|
71
|
+
if (value === 'custom') return 'custom';
|
|
72
|
+
return PROVIDERS.find(provider => provider.aliases.includes(value))?.id ?? null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Split a `provider/model` label at the first slash: OpenRouter model ids carry their own `vendor/` segment. */
|
|
76
|
+
export function splitModelLabel(label) {
|
|
77
|
+
const text = typeof label === 'string' ? label : '';
|
|
78
|
+
const cut = text.indexOf('/');
|
|
79
|
+
return cut > 0 ? { provider: text.slice(0, cut), model: text.slice(cut + 1) } : { provider: '', model: text };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The switchable provider a label names, defaulting to DeepSeek for unknown or bare labels. */
|
|
83
|
+
export function providerOfLabel(label) {
|
|
84
|
+
return providerSpec(splitModelLabel(label).provider)?.id ?? PROVIDERS[0].id;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Provider id leading a footer header (`provider: model @ effort`), if any. */
|
|
88
|
+
export function providerOfHeader(header) {
|
|
89
|
+
return typeof header === 'string' ? header.match(/^([^\s:/]+): /)?.[1] : undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// OpenCode Go names DeepSeek models by the official ids, without OpenRouter's vendor segment.
|
|
93
|
+
const OPENCODE_GO_OFFICIAL = Object.freeze({ 'deepseek-flash': 'deepseek-v4-flash' });
|
|
94
|
+
|
|
95
|
+
function counterpart(from, model, to) {
|
|
96
|
+
from = canonicalProvider(from);
|
|
97
|
+
to = canonicalProvider(to);
|
|
98
|
+
if (from === to) return model;
|
|
99
|
+
if (from === 'deepseek-official' && to === 'openrouter') return OPENROUTER_MODELS.find(entry => entry.official.includes(model))?.id;
|
|
100
|
+
if (from === 'openrouter' && to === 'deepseek-official') return OPENROUTER_MODELS.find(entry => entry.id === model)?.official[0];
|
|
101
|
+
if (from === 'deepseek-official' && to === 'opencode-go') return OPENCODE_GO_OFFICIAL[model] ?? model;
|
|
102
|
+
if (from === 'opencode-go' && to === 'deepseek-official') return model;
|
|
103
|
+
// OpenRouter's `vendor/model` ids mostly end in Go's own id.
|
|
104
|
+
if (from === 'openrouter' && to === 'opencode-go') return model.slice(model.indexOf('/') + 1);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The model a provider switch lands on: the current model's counterpart, else the
|
|
110
|
+
* provider default, else its first model. The effort carries over only when the
|
|
111
|
+
* target offers it; otherwise the model's own default applies.
|
|
112
|
+
* @param rows - model directory rows (`provider`, `model`, `reasoning`).
|
|
113
|
+
* @returns `{ row, effort }`, or `undefined` when the provider serves no model yet.
|
|
114
|
+
*/
|
|
115
|
+
export function pickModel(rows, provider, currentLabel, effort) {
|
|
116
|
+
const candidates = rows.filter(row => row.provider === provider);
|
|
117
|
+
if (candidates.length === 0) return undefined;
|
|
118
|
+
const current = splitModelLabel(currentLabel);
|
|
119
|
+
const wanted = counterpart(current.provider, current.model, provider);
|
|
120
|
+
const row = candidates.find(candidate => candidate.model === wanted)
|
|
121
|
+
?? candidates.find(candidate => candidate.model === providerSpec(provider)?.defaultModel)
|
|
122
|
+
?? candidates[0];
|
|
123
|
+
const offered = row.reasoning?.efforts.map(level => level.id) ?? [];
|
|
124
|
+
return { row, effort: effort && offered.includes(effort) ? effort : undefined };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Credential status of a provider-settings row.
|
|
129
|
+
* @returns `saved`, `env`, `account` (signed in with an account instead of a key), `missing`,
|
|
130
|
+
* `readonly` (an empty read-only source), `error`, or `unavailable` (no row).
|
|
131
|
+
*/
|
|
132
|
+
export function credentialState(row) {
|
|
133
|
+
if (!row) return 'unavailable';
|
|
134
|
+
const credential = row.credential;
|
|
135
|
+
if (credential?.kind === 'error') return 'error';
|
|
136
|
+
if (credential?.kind !== 'facts') return 'missing';
|
|
137
|
+
if (credential.configured) return credential.source === 'env' ? 'env' : credential.source === 'account' ? 'account' : 'saved';
|
|
138
|
+
return credential.writable ? 'missing' : 'readonly';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Prepare a provider before a switch. Both routes are always registered; switching
|
|
143
|
+
* to OpenRouter only clears the inert pi-ai profile earlier builds wrote.
|
|
144
|
+
* @param settings - the host settings service.
|
|
145
|
+
* @returns whether the settings changed.
|
|
146
|
+
*/
|
|
147
|
+
export async function ensureProviderRoute(settings, provider) {
|
|
148
|
+
return provider === 'openrouter' ? migrateOpenRouterProfile(settings) : false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Wait for a route's models to reach the model directory. */
|
|
152
|
+
export async function waitForModels(loadModels, provider, { attempts = 30, delayMs = 100 } = {}) {
|
|
153
|
+
let directory;
|
|
154
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
155
|
+
directory = await loadModels();
|
|
156
|
+
if (directory.rows.some(row => row.provider === provider)) return directory;
|
|
157
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
158
|
+
}
|
|
159
|
+
return directory;
|
|
160
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { requestImageHandleText, textOnlyImageText } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
|
|
3
|
+
const TOOL_RESULT_IMAGE_TEXT = 'Attached image(s) from tool result:';
|
|
4
|
+
// Legacy OpenRouter defaults; other protocols supply an explicit dialect.
|
|
5
|
+
const REASONING_CONTENT_MODELS = /^(?:deepseek\/deepseek-v4|moonshotai\/kimi-k2\.6|xiaomi\/mimo-v2\.6)/;
|
|
6
|
+
|
|
7
|
+
const textOf = blocks => blocks.filter(block => block.type === 'text').map(block => block.text).join('');
|
|
8
|
+
|
|
9
|
+
function contentParts(blocks, images) {
|
|
10
|
+
const parts = [];
|
|
11
|
+
for (const block of blocks) {
|
|
12
|
+
if (block.type === 'text' && block.text.length > 0) parts.push({ type: 'text', text: block.text });
|
|
13
|
+
else if (block.type === 'image') {
|
|
14
|
+
const version = images?.versions.get(block.attachment.attachmentId);
|
|
15
|
+
if (version === undefined) { parts.push({ type: 'text', text: textOnlyImageText(block.attachment) }); continue; }
|
|
16
|
+
parts.push({ type: 'text', text: requestImageHandleText(block.attachment, version, images.access?.(block.attachment)) });
|
|
17
|
+
parts.push({ type: 'image_url', image_url: { url: `data:${version.mediaType};base64,${Buffer.from(version.data).toString('base64')}` } });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return parts;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const collapse = parts => parts.every(part => part.type === 'text') ? parts.map(part => part.text).join('') : parts;
|
|
24
|
+
|
|
25
|
+
/** Reasoning details this adapter (or the pi-ai adapter before it) stored for a same-model replay. */
|
|
26
|
+
function replayedDetails(message) {
|
|
27
|
+
const blocks = message.source?.replayState?.blocks;
|
|
28
|
+
if (!Array.isArray(blocks) || blocks.length !== message.content.length) return [];
|
|
29
|
+
const details = [];
|
|
30
|
+
blocks.forEach((block, index) => {
|
|
31
|
+
if (message.content[index]?.type !== 'reasoning') return;
|
|
32
|
+
if (Array.isArray(block?.reasoningDetails)) details.push(...block.reasoningDetails);
|
|
33
|
+
else if (typeof block?.thinkingSignature === 'string') {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(block.thinkingSignature);
|
|
36
|
+
if (Array.isArray(parsed)) details.push(...parsed);
|
|
37
|
+
} catch { /* a plain field-name signature carries no details */ }
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return details;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function serializeAssistant(message, model, dialect) {
|
|
44
|
+
const text = textOf(message.content);
|
|
45
|
+
const calls = message.content.filter(block => block.type === 'tool-call')
|
|
46
|
+
.map(block => ({ id: block.id, type: 'function', function: { name: block.name, arguments: block.arguments } }));
|
|
47
|
+
// An empty assistant turn (reasoning only) has nothing a provider accepts.
|
|
48
|
+
if (text.length === 0 && calls.length === 0) return undefined;
|
|
49
|
+
// Reasoning replays only to the model that produced it.
|
|
50
|
+
const sameModel = message.source?.provider === dialect.provider && message.source?.model === model;
|
|
51
|
+
const reasoning = sameModel ? message.content.filter(block => block.type === 'reasoning').map(block => block.text).join('') : '';
|
|
52
|
+
if (dialect.reasoningContent !== undefined) {
|
|
53
|
+
// A pass-through route speaks the upstream's own field: the reasoning text itself, or an
|
|
54
|
+
// empty one for models that reject a tool-call turn without it.
|
|
55
|
+
const field = reasoning.length > 0 ? reasoning : dialect.reasoningContent(model) ? '' : undefined;
|
|
56
|
+
return { role: 'assistant', content: text, ...(calls.length > 0 ? { tool_calls: calls } : {}), ...(field !== undefined ? { reasoning_content: field } : {}) };
|
|
57
|
+
}
|
|
58
|
+
const details = sameModel ? replayedDetails(message) : [];
|
|
59
|
+
return {
|
|
60
|
+
role: 'assistant',
|
|
61
|
+
content: text,
|
|
62
|
+
...(calls.length > 0 ? { tool_calls: calls } : {}),
|
|
63
|
+
...(details.length > 0 ? { reasoning_details: details } : reasoning.length > 0 ? { reasoning } : {}),
|
|
64
|
+
...(REASONING_CONTENT_MODELS.test(model) ? { reasoning_content: '' } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Harness history as OpenRouter chat messages. Each tool result becomes a `tool`
|
|
70
|
+
* message; its images follow in one user message, which `tool` content cannot carry.
|
|
71
|
+
* @param images - prepared request images (`versions` by attachment id, `access`), when the request has any.
|
|
72
|
+
* @param dialect - another chat-completions route reusing this serializer: its `provider` id (whose
|
|
73
|
+
* reasoning replays), and `reasoningContent(model)` to replay reasoning as `reasoning_content`.
|
|
74
|
+
*/
|
|
75
|
+
export function serializeMessages(messages, { model, system, images, dialect = { provider: 'openrouter' } } = {}) {
|
|
76
|
+
const wire = [];
|
|
77
|
+
if (system !== undefined && system.length > 0) wire.push({ role: 'system', content: system });
|
|
78
|
+
let pendingImages = [];
|
|
79
|
+
const flush = () => {
|
|
80
|
+
if (pendingImages.length === 0) return;
|
|
81
|
+
wire.push({ role: 'user', content: [{ type: 'text', text: TOOL_RESULT_IMAGE_TEXT }, ...pendingImages] });
|
|
82
|
+
pendingImages = [];
|
|
83
|
+
};
|
|
84
|
+
const appendResult = (callId, content) => {
|
|
85
|
+
const nested = contentParts(content, images);
|
|
86
|
+
const text = nested.filter(part => part.type === 'text').map(part => part.text).join('');
|
|
87
|
+
const attached = nested.filter(part => part.type !== 'text');
|
|
88
|
+
wire.push({ role: 'tool', tool_call_id: callId, content: text || (attached.length > 0 ? '(see attached image)' : '(no output)') });
|
|
89
|
+
pendingImages.push(...attached);
|
|
90
|
+
};
|
|
91
|
+
for (const message of messages) {
|
|
92
|
+
if (message.role === 'system') {
|
|
93
|
+
flush();
|
|
94
|
+
const text = textOf(message.content);
|
|
95
|
+
if (text.length > 0) wire.push({ role: 'system', content: text });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (message.role === 'tool') {
|
|
99
|
+
appendResult(message.toolCallId, message.content);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (message.role === 'assistant') {
|
|
103
|
+
flush();
|
|
104
|
+
const entry = serializeAssistant(message, model, dialect);
|
|
105
|
+
if (entry) wire.push(entry);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const results = message.content.filter(block => block.type === 'tool-result');
|
|
109
|
+
const parts = contentParts(message.content.filter(block => block.type !== 'tool-result'), images);
|
|
110
|
+
if (parts.length > 0 || results.length === 0) {
|
|
111
|
+
flush();
|
|
112
|
+
wire.push({ role: 'user', content: collapse(parts) });
|
|
113
|
+
}
|
|
114
|
+
for (const result of results) {
|
|
115
|
+
appendResult(result.toolCallId, result.content);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
flush();
|
|
119
|
+
return wire;
|
|
120
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { LlmError, EMPTY_RESPONSE_CODE } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { errorCode, errorMessage } from './http-errors.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SSE `data:` payloads from a response body, skipping `:` keep-alive comments.
|
|
6
|
+
* @param onActivity - called for every received chunk, comments included.
|
|
7
|
+
*/
|
|
8
|
+
export async function* sseData(body, onActivity) {
|
|
9
|
+
const decoder = new TextDecoder();
|
|
10
|
+
let buffer = '', data = [];
|
|
11
|
+
const lines = function* (final) {
|
|
12
|
+
let newline;
|
|
13
|
+
while ((newline = buffer.indexOf('\n')) >= 0 || final && buffer.length > 0) {
|
|
14
|
+
let line = newline >= 0 ? buffer.slice(0, newline) : buffer;
|
|
15
|
+
buffer = newline >= 0 ? buffer.slice(newline + 1) : '';
|
|
16
|
+
if (line.endsWith('\r')) line = line.slice(0, -1);
|
|
17
|
+
if (line === '') {
|
|
18
|
+
// A bare `data:` line assembles to whitespace; the SSE spec reads it as a newline, but every consumer here parses JSON.
|
|
19
|
+
const payload = data.join('\n');
|
|
20
|
+
if (payload.trim() !== '') yield payload;
|
|
21
|
+
data = [];
|
|
22
|
+
} else if (line.startsWith('data:')) data.push(line.slice(line.startsWith('data: ') ? 6 : 5));
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
for await (const chunk of body) {
|
|
26
|
+
onActivity?.();
|
|
27
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
28
|
+
yield* lines(false);
|
|
29
|
+
}
|
|
30
|
+
buffer += decoder.decode();
|
|
31
|
+
yield* lines(true);
|
|
32
|
+
const tail = data.join('\n');
|
|
33
|
+
if (tail.trim() !== '') yield tail;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Map OpenRouter usage to disjoint harness counts (`prompt_tokens` includes cache reads and writes). */
|
|
37
|
+
export function mapUsage(usage) {
|
|
38
|
+
const valid = value => Number.isSafeInteger(value) && value >= 0;
|
|
39
|
+
const prompt = usage?.prompt_tokens, completion = usage?.completion_tokens;
|
|
40
|
+
if (!valid(prompt) || !valid(completion)) return undefined;
|
|
41
|
+
const read = valid(usage.prompt_tokens_details?.cached_tokens) ? usage.prompt_tokens_details.cached_tokens : 0;
|
|
42
|
+
const write = valid(usage.prompt_tokens_details?.cache_write_tokens) ? usage.prompt_tokens_details.cache_write_tokens : 0;
|
|
43
|
+
const reasoning = usage.completion_tokens_details?.reasoning_tokens;
|
|
44
|
+
return {
|
|
45
|
+
inputTokens: Math.max(0, prompt - read - write),
|
|
46
|
+
outputTokens: completion,
|
|
47
|
+
...(usage.total_tokens === undefined || usage.total_tokens === prompt + completion ? { totalTokens: prompt + completion } : {}),
|
|
48
|
+
...(read > 0 ? { cacheReadTokens: read } : {}),
|
|
49
|
+
...(write > 0 ? { cacheWriteTokens: write } : {}),
|
|
50
|
+
...(valid(reasoning) && reasoning > 0 ? { reasoningTokens: reasoning } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Streamed reasoning details merged into the blocks a replay sends back unmodified. */
|
|
55
|
+
function mergeDetails(items) {
|
|
56
|
+
const merged = [];
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
if (item === null || typeof item !== 'object') continue;
|
|
59
|
+
const last = merged.at(-1);
|
|
60
|
+
if (last && last.type === item.type && item.type !== 'reasoning.encrypted' && (last.index ?? 0) === (item.index ?? 0)) {
|
|
61
|
+
if (typeof item.text === 'string') last.text = (last.text ?? '') + item.text;
|
|
62
|
+
if (typeof item.summary === 'string') last.summary = (last.summary ?? '') + item.summary;
|
|
63
|
+
for (const key of ['id', 'format', 'signature']) if (item[key] !== undefined && item[key] !== null && item[key] !== '') last[key] = item[key];
|
|
64
|
+
} else merged.push({ ...item });
|
|
65
|
+
}
|
|
66
|
+
return merged;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function closeBlock(block) {
|
|
70
|
+
if (block.kind === 'tool-call') return { type: 'tool-call', id: block.callId ?? '', name: block.name ?? '', arguments: block.text };
|
|
71
|
+
return { type: block.kind, text: block.text };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function finishReason(reason, blocks, label) {
|
|
75
|
+
if (reason === 'length') return { kind: 'max-tokens' };
|
|
76
|
+
if (reason === 'content_filter') return { kind: 'error', failure: { message: `${label} stopped the response for content filtering`, code: 'CONTENT_FILTER' } };
|
|
77
|
+
if (reason === 'tool_calls' || blocks.some(block => block.kind === 'tool-call') && (reason === undefined || reason === 'stop')) return { kind: 'tool-calls' };
|
|
78
|
+
if (reason === undefined || reason === null || reason === 'stop' || reason === 'end') {
|
|
79
|
+
return blocks.length === 0 ? { kind: 'error', failure: { message: 'model returned a completed response with no content', code: EMPTY_RESPONSE_CODE } } : { kind: 'stop' };
|
|
80
|
+
}
|
|
81
|
+
return { kind: 'error', failure: { message: `model stopped: ${reason}`, code: String(reason).toUpperCase() } };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Translate SSE payloads into harness chunks. Block ends, usage and the finish are
|
|
86
|
+
* held until `[DONE]`; the finish of a successful response carries the replay state:
|
|
87
|
+
* reasoning details per block, the generation id, the serving provider and the billed cost.
|
|
88
|
+
* An error inside the stream throws with its routed code.
|
|
89
|
+
* @param kind - the replay kind stamped on the response; `label` names the route in errors.
|
|
90
|
+
*/
|
|
91
|
+
export async function* translate(payloads, { model, kind = 'dscode-openrouter', label = 'OpenRouter' }) {
|
|
92
|
+
let nextIndex = 0, textBlock, reasoningBlock, finish, usage, cost, id, provider;
|
|
93
|
+
const tools = new Map();
|
|
94
|
+
const order = [];
|
|
95
|
+
const open = kind => {
|
|
96
|
+
const block = { index: nextIndex++, kind, text: '', details: [] };
|
|
97
|
+
order.push(block);
|
|
98
|
+
return block;
|
|
99
|
+
};
|
|
100
|
+
for await (const payload of payloads) {
|
|
101
|
+
if (payload === '[DONE]') {
|
|
102
|
+
for (const block of order) yield { type: 'block-end', index: block.index, block: closeBlock(block) };
|
|
103
|
+
if (usage) yield { type: 'usage', usage };
|
|
104
|
+
const reason = finishReason(finish, order, label);
|
|
105
|
+
const succeeded = reason.kind === 'stop' || reason.kind === 'tool-calls' || reason.kind === 'max-tokens';
|
|
106
|
+
yield {
|
|
107
|
+
type: 'finish', reason,
|
|
108
|
+
...(succeeded ? { replayState: {
|
|
109
|
+
response: { kind, version: 1, model, ...(id ? { id } : {}), ...(provider ? { provider } : {}), ...(cost !== undefined ? { cost } : {}) },
|
|
110
|
+
blocks: order.map(block => block.kind === 'reasoning' && block.details.length > 0 ? { type: 'reasoning', reasoningDetails: mergeDetails(block.details) } : { type: block.kind }),
|
|
111
|
+
} } : {}),
|
|
112
|
+
};
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let chunk;
|
|
116
|
+
try {
|
|
117
|
+
chunk = JSON.parse(payload);
|
|
118
|
+
} catch {
|
|
119
|
+
throw new LlmError(`malformed ${label} stream payload: ${payload.slice(0, 120)}`, 'MALFORMED_RESPONSE');
|
|
120
|
+
}
|
|
121
|
+
if (chunk?.error) {
|
|
122
|
+
const status = Number.isInteger(chunk.error.code) ? chunk.error.code : undefined;
|
|
123
|
+
throw new LlmError(errorMessage(chunk.error, `${label} stream error`), errorCode(undefined, chunk.error), status === undefined ? {} : { status });
|
|
124
|
+
}
|
|
125
|
+
if (typeof chunk?.id === 'string') id ??= chunk.id;
|
|
126
|
+
if (typeof chunk?.provider === 'string') provider ??= chunk.provider;
|
|
127
|
+
for (const choice of chunk?.choices ?? []) {
|
|
128
|
+
const delta = choice.delta ?? {};
|
|
129
|
+
const details = Array.isArray(delta.reasoning_details) ? delta.reasoning_details : [];
|
|
130
|
+
let reasoning = typeof delta.reasoning === 'string' ? delta.reasoning : typeof delta.reasoning_content === 'string' ? delta.reasoning_content : '';
|
|
131
|
+
if (reasoning.length === 0) reasoning = details.map(detail => detail?.type === 'reasoning.text' ? detail.text : detail?.type === 'reasoning.summary' ? detail.summary : '').filter(text => typeof text === 'string').join('');
|
|
132
|
+
if (reasoning.length > 0 || details.length > 0) {
|
|
133
|
+
if (!reasoningBlock) {
|
|
134
|
+
reasoningBlock = open('reasoning');
|
|
135
|
+
yield { type: 'block-start', index: reasoningBlock.index, blockType: 'reasoning' };
|
|
136
|
+
}
|
|
137
|
+
reasoningBlock.details.push(...details);
|
|
138
|
+
if (reasoning.length > 0) {
|
|
139
|
+
reasoningBlock.text += reasoning;
|
|
140
|
+
yield { type: 'reasoning-delta', index: reasoningBlock.index, text: reasoning };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (typeof delta.content === 'string' && delta.content.length > 0) {
|
|
144
|
+
if (!textBlock) {
|
|
145
|
+
textBlock = open('text');
|
|
146
|
+
yield { type: 'block-start', index: textBlock.index, blockType: 'text' };
|
|
147
|
+
}
|
|
148
|
+
textBlock.text += delta.content;
|
|
149
|
+
yield { type: 'text-delta', index: textBlock.index, text: delta.content };
|
|
150
|
+
}
|
|
151
|
+
for (const call of Array.isArray(delta.tool_calls) ? delta.tool_calls : []) {
|
|
152
|
+
const key = call.index ?? call.id;
|
|
153
|
+
let block = tools.get(key);
|
|
154
|
+
if (!block) {
|
|
155
|
+
block = open('tool-call');
|
|
156
|
+
tools.set(key, block);
|
|
157
|
+
yield { type: 'block-start', index: block.index, blockType: 'tool-call' };
|
|
158
|
+
}
|
|
159
|
+
// id and name arrive once; an empty or null repeat is no update.
|
|
160
|
+
if (typeof call.id === 'string' && call.id.length > 0) block.callId = call.id;
|
|
161
|
+
if (typeof call.function?.name === 'string' && call.function.name.length > 0) block.name = call.function.name;
|
|
162
|
+
const fragment = typeof call.function?.arguments === 'string' ? call.function.arguments : '';
|
|
163
|
+
block.text += fragment;
|
|
164
|
+
yield { type: 'tool-call-delta', index: block.index, id: block.callId ?? '', ...(block.name !== undefined ? { name: block.name } : {}), argumentsDelta: fragment };
|
|
165
|
+
}
|
|
166
|
+
if (typeof choice.finish_reason === 'string') finish = choice.finish_reason;
|
|
167
|
+
}
|
|
168
|
+
if (chunk?.usage) {
|
|
169
|
+
usage = mapUsage(chunk.usage) ?? usage;
|
|
170
|
+
if (Number.isFinite(chunk.usage.cost) && chunk.usage.cost >= 0) cost = chunk.usage.cost;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
throw new LlmError(`${label} stream ended without [DONE]`, 'TRANSPORT');
|
|
174
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
globalThis.__ModuleLoader__.load({
|
|
2
|
+
id: '@toddzheng024/dscode-desktop',
|
|
3
|
+
factory: require => {
|
|
4
|
+
const { createElement: h, useState, useEffect, useRef } = require('react');
|
|
5
|
+
const control = { padding: '8px 10px', border: '1px solid #8886', borderRadius: 6, background: 'transparent', color: 'inherit', font: 'inherit' };
|
|
6
|
+
function Accounts({ execute }) {
|
|
7
|
+
const [state, setState] = useState(null), [key, setKey] = useState(''), [busy, setBusy] = useState(false);
|
|
8
|
+
const [error, setError] = useState(''), [notice, setNotice] = useState(''), [confirm, setConfirm] = useState(null);
|
|
9
|
+
const [refreshError, setRefreshError] = useState('');
|
|
10
|
+
const pending = useRef(null), foreground = useRef(false), alive = useRef(true);
|
|
11
|
+
const run = async (action, args = {}, background = false) => {
|
|
12
|
+
if (background ? pending.current : foreground.current) return;
|
|
13
|
+
if (!background) { foreground.current = true; setBusy(true); setError(''); }
|
|
14
|
+
const previous = pending.current;
|
|
15
|
+
const operation = (async () => {
|
|
16
|
+
if (previous) await previous;
|
|
17
|
+
if (!alive.current) return;
|
|
18
|
+
try {
|
|
19
|
+
const value = await execute(action, args);
|
|
20
|
+
if (!alive.current) return;
|
|
21
|
+
setState(value); setRefreshError('');
|
|
22
|
+
if (action === 'status' && !value.opencode.pending) setNotice('');
|
|
23
|
+
if (action !== 'status') { setNotice(value.notice ?? ''); setConfirm(null); }
|
|
24
|
+
if (action === 'save-openrouter' || action === 'remove-openrouter') setKey('');
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (alive.current) {
|
|
27
|
+
if (background) setRefreshError(`Unable to refresh accounts: ${error.message}`);
|
|
28
|
+
else setError(error.message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
pending.current = operation;
|
|
33
|
+
try { await operation; } finally {
|
|
34
|
+
if (pending.current === operation) pending.current = null;
|
|
35
|
+
if (!background) { foreground.current = false; if (alive.current) setBusy(false); }
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
useEffect(() => { alive.current = true; void run('status'); return () => { alive.current = false; }; }, []);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!state?.opencode.pending) return;
|
|
41
|
+
const timer = setInterval(() => { void run('status', {}, true); }, 2000);
|
|
42
|
+
return () => clearInterval(timer);
|
|
43
|
+
}, [state?.opencode.pending]);
|
|
44
|
+
const button = (text, action, disabled = false) => h('button', { type: 'button', style: control, disabled: busy || disabled, onClick: action }, text);
|
|
45
|
+
const destructive = (id, label) => button(confirm === id ? `Confirm ${label.toLowerCase()}` : label, () => confirm === id ? run(id) : setConfirm(id));
|
|
46
|
+
return h('section', { 'aria-label': 'DSCODE account settings', style: { padding: 20, display: 'grid', gap: 16, maxWidth: 720 } },
|
|
47
|
+
h('h2', { style: { margin: 0 } }, 'DSCODE accounts'),
|
|
48
|
+
error && h('div', { role: 'alert' }, error),
|
|
49
|
+
refreshError && h('div', { role: 'alert' }, refreshError),
|
|
50
|
+
notice && h('div', { role: 'status', style: { whiteSpace: 'pre-wrap' } }, notice),
|
|
51
|
+
button('Refresh account status', () => run('status')),
|
|
52
|
+
state && h('fieldset', { disabled: busy, style: { border: '1px solid #8886', borderRadius: 8, padding: 16 } },
|
|
53
|
+
h('legend', null, 'DSCODE OpenRouter'),
|
|
54
|
+
h('p', null, state.openrouter.configured ? `Configured (${state.openrouter.source ?? 'saved'})` : 'No API key saved'),
|
|
55
|
+
h('p', null, 'The key is shared with DSCODE terminal sessions and native routes using the same credential. Select DSCODE OpenRouter in the model picker for DSCODE reasoning controls.'),
|
|
56
|
+
h('label', null, 'OpenRouter API key', h('input', { 'aria-label': 'OpenRouter API key', type: 'password', autoComplete: 'new-password', style: { ...control, display: 'block', width: '100%', boxSizing: 'border-box' },
|
|
57
|
+
value: key, disabled: !state.openrouter.writable, onChange: event => { setKey(event.target.value); setConfirm(null); } })),
|
|
58
|
+
h('div', { style: { display: 'flex', gap: 8, marginTop: 12 } },
|
|
59
|
+
button('Save OpenRouter key', () => run('save-openrouter', { key }), !state.openrouter.writable || !key.trim()),
|
|
60
|
+
state.openrouter.configured && state.openrouter.writable && destructive('remove-openrouter', 'Remove OpenRouter key'))),
|
|
61
|
+
state && h('fieldset', { disabled: busy, style: { border: '1px solid #8886', borderRadius: 8, padding: 16 } },
|
|
62
|
+
h('legend', null, 'Grok'),
|
|
63
|
+
h('p', null, state.grok.configured ? 'Local Grok CLI login available' : 'No usable local Grok CLI login'),
|
|
64
|
+
h('p', null, 'Run grok login in a terminal, then refresh this status. DSCODE reads ~/.grok/auth.json without changing or refreshing its tokens.')),
|
|
65
|
+
state && h('fieldset', { disabled: busy, style: { border: '1px solid #8886', borderRadius: 8, padding: 16 } },
|
|
66
|
+
h('legend', null, 'OpenCode Go'),
|
|
67
|
+
h('p', { style: { whiteSpace: 'pre-wrap' } }, state.opencode.lines.join('\n')),
|
|
68
|
+
h('p', null, 'Sign in opens the OpenCode consent page in your browser. It identifies the OpenCode CLI client used for this login.'),
|
|
69
|
+
h('div', { style: { display: 'flex', gap: 8 } },
|
|
70
|
+
button('Sign in to OpenCode Go', () => run('login-opencode'), state.opencode.pending),
|
|
71
|
+
state.opencode.pending && button('Cancel OpenCode login', () => run('cancel-opencode')),
|
|
72
|
+
state.opencode.configured && destructive('logout-opencode', 'Sign out of OpenCode Go'))));
|
|
73
|
+
}
|
|
74
|
+
return { inject: ['slots', 'connection'], apply(ctx) {
|
|
75
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'dscode-accounts', order: 12, label: () => 'DSCODE accounts',
|
|
76
|
+
inject: () => ({ execute: async (action, args = {}) => {
|
|
77
|
+
const response = await ctx.connection.rpc.call('/api', 'dscode-accounts', { action, ...args });
|
|
78
|
+
if (!response.ok) throw Error(response.error.message);
|
|
79
|
+
return response.value;
|
|
80
|
+
} }),
|
|
81
|
+
}, Accounts));
|
|
82
|
+
} };
|
|
83
|
+
},
|
|
84
|
+
});
|