@toddzheng024/dscode-desktop 0.0.0-stage → 0.7.33
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 +641 -0
- package/docs/account-login.md +44 -0
- package/docs/auto-review.md +128 -0
- package/docs/browser-use.md +783 -0
- package/docs/computer-use.md +81 -0
- package/docs/custom-providers.md +88 -0
- package/docs/demo.md +76 -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 +190 -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 +30 -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 +98 -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 +146 -0
- package/plugins/grok/index.mjs +122 -0
- package/plugins/grok/models.mjs +129 -0
- package/plugins/grok/status.mjs +28 -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 +99 -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 +120 -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 +119 -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 +123 -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 +68 -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 +96 -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 +271 -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 +148 -0
- package/plugins/tui-tools/hook-sources.mjs +113 -0
- package/plugins/tui-tools/hooks.mjs +23 -0
- package/plugins/tui-tools/index.mjs +268 -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,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer Use, mounted through DSCODE so a retired settings API still resolves.
|
|
3
|
+
*
|
|
4
|
+
* `@anionex/dsh-computer-use` reads its configuration with `ctx.settings.register`, an API
|
|
5
|
+
* DSH removed in 0.1.7: Settings now projects forms from the profile entry itself, and the
|
|
6
|
+
* missing method made the whole Computer Use bundle fail to activate. The package is
|
|
7
|
+
* pinned at 0.3.2 with no newer release, so this row restores the method — as a read-only
|
|
8
|
+
* view over the composition config, which is where DSCODE configures Computer Use anyway —
|
|
9
|
+
* and then mounts the upstream bundle as its child, which is what makes the order
|
|
10
|
+
* dependable: the shim is installed before the provider's constructor runs.
|
|
11
|
+
*
|
|
12
|
+
* Retire this wrapper when the package reads volatile configuration, and mount
|
|
13
|
+
* `@anionex/dsh-computer-use` directly again.
|
|
14
|
+
*
|
|
15
|
+
* @module dscode-computer-use
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import ComputerUseBundle from '@anionex/dsh-computer-use';
|
|
19
|
+
|
|
20
|
+
export const name = 'dscode-computer-use';
|
|
21
|
+
export const inject = ['settings'];
|
|
22
|
+
|
|
23
|
+
/** Section handle the 0.3.x provider expects: one read of the composition config, no live writes. */
|
|
24
|
+
function staticSection(base) {
|
|
25
|
+
return { get: () => base, watch: () => () => {} };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function apply(ctx, config = {}) {
|
|
29
|
+
const settings = ctx.settings;
|
|
30
|
+
// Only ever adds what upstream no longer defines; a Settings service that carries its
|
|
31
|
+
// own `register` again is left untouched, and the shim is dropped with this row.
|
|
32
|
+
if (typeof settings.register !== 'function') {
|
|
33
|
+
settings.register = (_namespace, _schema, options = {}) => staticSection(options.base ?? {});
|
|
34
|
+
ctx.effect(() => () => { delete settings.register; });
|
|
35
|
+
}
|
|
36
|
+
ctx.plugin(ComputerUseBundle, config);
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One plain snapshot of a Cordis plugin configuration.
|
|
3
|
+
*
|
|
4
|
+
* Two shapes make a foreign entry's `config` unusable as a plain object. DSH 0.1.7 hands
|
|
5
|
+
* a `.volatile()` field to its own plugin as a reference rather than a value, and the
|
|
6
|
+
* Loader keeps a YAML `!!js` scalar as an unevaluated `{ __jsExpr }` node until the entry
|
|
7
|
+
* activates — which a reader inspecting another entry's row (the skills conflict report
|
|
8
|
+
* walks every `skill-filesystem` entry, and a preset's rows carry such expressions) sees
|
|
9
|
+
* instead of the value. References are read; an expression cannot be evaluated outside the
|
|
10
|
+
* Loader's own scope, so it reads as absent and the caller applies its own fallback.
|
|
11
|
+
*
|
|
12
|
+
* @param config - a configuration object whose fields may be references or expressions.
|
|
13
|
+
* @returns a shallow copy carrying only plainly readable values.
|
|
14
|
+
*/
|
|
15
|
+
export function plainConfig(config) {
|
|
16
|
+
if (config === null || typeof config !== 'object') return {};
|
|
17
|
+
const entries = Object.entries(config)
|
|
18
|
+
.map(([key, value]) => [key, isReference(value) ? value.get() : value])
|
|
19
|
+
.filter(([, value]) => !isExpression(value));
|
|
20
|
+
return Object.fromEntries(entries);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether a configuration field is a Cordis value reference rather than a plain value. */
|
|
24
|
+
function isReference(value) {
|
|
25
|
+
return value !== null && typeof value === 'object' && typeof value.get === 'function';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Whether a configuration field is still an unevaluated Loader expression node. */
|
|
29
|
+
function isExpression(value) {
|
|
30
|
+
return value !== null && typeof value === 'object' && '__jsExpr' in value;
|
|
31
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { LocalCredentialProvider } from '@deepseek-ai/dsh-credentials-local';
|
|
4
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
5
|
+
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment';
|
|
6
|
+
import { isCustomKey } from '../custom/config.mjs';
|
|
7
|
+
import { PROVIDERS } from '../providers/catalog.mjs';
|
|
8
|
+
import { GROK_TOKEN_REF, grokAuthState } from '../grok/auth.mjs';
|
|
9
|
+
|
|
10
|
+
// The `/provider` credentials (DeepSeek, OpenRouter and its management key, the OpenCode Go
|
|
11
|
+
// login) live in the shared store.
|
|
12
|
+
const SHARED = new Set(PROVIDERS.flatMap(provider => [provider.credentialRef, provider.managementRef].filter(Boolean)));
|
|
13
|
+
// Credentials a login command writes (the OpenCode Go account login): described as read-only
|
|
14
|
+
// so no key form is offered, while the owning plugin still stores them through `set`.
|
|
15
|
+
const LOGINS = new Set(PROVIDERS.filter(provider => provider.login).map(provider => provider.credentialRef));
|
|
16
|
+
|
|
17
|
+
// Use the native locked, watched, owner-only store. Keep this independent of
|
|
18
|
+
// the installed profile so upgrades and different projects share credentials.
|
|
19
|
+
export default class DscodeCredentials extends LocalCredentialProvider {
|
|
20
|
+
shared;
|
|
21
|
+
sharedConfig;
|
|
22
|
+
constructor(ctx, config = {}) {
|
|
23
|
+
// Retain the old profile store, including other providers and OAuth records.
|
|
24
|
+
super(ctx, { ...config, path: undefined });
|
|
25
|
+
this.sharedConfig = { ...config, path: config.path ?? join(homedir(), '.dscode', 'credentials.yaml') };
|
|
26
|
+
}
|
|
27
|
+
async *[Service.init]() {
|
|
28
|
+
const sharedContext = new Context();
|
|
29
|
+
yield () => sharedContext.fiber.dispose();
|
|
30
|
+
sharedContext.provide('launchEnvironment', launchEnvironmentOf(this.ctx));
|
|
31
|
+
sharedContext.on('credentials/reference-updated', ref => this.notifyUpdated(ref));
|
|
32
|
+
await sharedContext.plugin(LocalCredentialProvider, this.sharedConfig);
|
|
33
|
+
this.shared = sharedContext.credentials;
|
|
34
|
+
yield* super[Service.init]();
|
|
35
|
+
}
|
|
36
|
+
async resolve(ref) {
|
|
37
|
+
// dscode: the Grok subscription rail is the official CLI login, read-only. DSCODE never
|
|
38
|
+
// writes that file: the CLI owns refresh-token rotation, and two writers log the other out.
|
|
39
|
+
if (ref === GROK_TOKEN_REF) {
|
|
40
|
+
const state = grokAuthState();
|
|
41
|
+
return state.kind === 'ready' ? { value: state.credential.token, source: 'file' } : undefined;
|
|
42
|
+
}
|
|
43
|
+
if (SHARED.has(ref) || isCustomKey(ref)) {
|
|
44
|
+
const stored = await this.shared.resolve(ref);
|
|
45
|
+
if (stored?.source === 'env' || stored?.source === 'file') return stored;
|
|
46
|
+
}
|
|
47
|
+
return super.resolve(ref);
|
|
48
|
+
}
|
|
49
|
+
async describe(ref) {
|
|
50
|
+
if (ref === GROK_TOKEN_REF) {
|
|
51
|
+
const state = grokAuthState();
|
|
52
|
+
return state.kind === 'ready' ? { configured: true, source: 'file', writable: false } : { configured: false, writable: false };
|
|
53
|
+
}
|
|
54
|
+
if (LOGINS.has(ref)) {
|
|
55
|
+
return (await this.shared.resolve(ref))?.value ? { configured: true, source: 'account', writable: false } : { configured: false, writable: false };
|
|
56
|
+
}
|
|
57
|
+
if (SHARED.has(ref) || isCustomKey(ref)) {
|
|
58
|
+
const facts = await this.shared.describe(ref);
|
|
59
|
+
if (facts.source === 'env' || facts.source === 'file') return facts;
|
|
60
|
+
}
|
|
61
|
+
return super.describe(ref);
|
|
62
|
+
}
|
|
63
|
+
set(ref, value) {
|
|
64
|
+
// The CLI file is not a DSCODE store: saving here would go somewhere nothing reads.
|
|
65
|
+
if (ref === GROK_TOKEN_REF) throw new Error('GROK_CLI_TOKEN comes from ~/.grok/auth.json; run grok login instead');
|
|
66
|
+
return (SHARED.has(ref) || isCustomKey(ref)) ? this.shared.set(ref, value) : super.set(ref, value);
|
|
67
|
+
}
|
|
68
|
+
async unset(ref) {
|
|
69
|
+
// Explicit removal must not uncover a previously configured legacy key.
|
|
70
|
+
if (SHARED.has(ref) || isCustomKey(ref)) await this.shared.unset(ref);
|
|
71
|
+
await super.unset(ref);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { LlmAdapter, LlmError, ReasoningEffortId, attributionHeaders } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { isAttachmentError } from '@deepseek-ai/dsh-attachment';
|
|
3
|
+
import { sseData, translate } from '../providers/chat-stream.mjs';
|
|
4
|
+
import { errorCode, retryAfterMs } from '../providers/http-errors.mjs';
|
|
5
|
+
import { endpoint } from './config.mjs';
|
|
6
|
+
import { normalizedEvents, requestBody, REPLAY_KIND } from './wire.mjs';
|
|
7
|
+
import { prepareImages } from './images.mjs';
|
|
8
|
+
|
|
9
|
+
export function authHeaders(profile, key) {
|
|
10
|
+
if (profile.auth !== 'none' && !key) throw new LlmError('Custom provider needs an API key; edit it in /provider → Custom', 'MISSING_CREDENTIAL');
|
|
11
|
+
return { ...(profile.api === 'anthropic' ? { 'anthropic-version': '2023-06-01' } : {}),
|
|
12
|
+
...(profile.auth === 'none' ? {} : profile.auth === 'x-api-key' ? { 'x-api-key': key } : { authorization: `Bearer ${key}` }) };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** One request per service in this Host. Background calls queue behind foreground work. */
|
|
16
|
+
class RequestQueue {
|
|
17
|
+
busy = false;
|
|
18
|
+
waiting = [];
|
|
19
|
+
async acquire(signal, background) {
|
|
20
|
+
signal?.throwIfAborted();
|
|
21
|
+
if (!this.busy) { this.busy = true; return () => this.release(); }
|
|
22
|
+
await new Promise((resolve, reject) => {
|
|
23
|
+
const item = { resolve, background };
|
|
24
|
+
const abort = () => { this.waiting = this.waiting.filter(x => x !== item); reject(signal.reason); };
|
|
25
|
+
item.resolve = () => { signal?.removeEventListener('abort', abort); resolve(); };
|
|
26
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
27
|
+
this.waiting.push(item);
|
|
28
|
+
});
|
|
29
|
+
return () => this.release();
|
|
30
|
+
}
|
|
31
|
+
release() {
|
|
32
|
+
if (!this.waiting.length) { this.busy = false; return; }
|
|
33
|
+
const foreground = this.waiting.findIndex(x => !x.background);
|
|
34
|
+
this.waiting.splice(foreground < 0 ? 0 : foreground, 1)[0].resolve();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class CustomAdapter extends LlmAdapter {
|
|
39
|
+
constructor({ profile, resolveKey, resolveAttachments, resolveImageAccess, fetch = globalThis.fetch }) {
|
|
40
|
+
super(); this.profile = profile; this.resolveKey = resolveKey; this.fetch = fetch; this.queues = new Map();
|
|
41
|
+
this.resolveAttachments = resolveAttachments; this.resolveImageAccess = resolveImageAccess;
|
|
42
|
+
}
|
|
43
|
+
providerInfo(provider) { return { id: provider, name: this.profile(provider)?.name ?? provider }; }
|
|
44
|
+
async listModels(provider) {
|
|
45
|
+
return (this.profile(provider)?.models ?? []).filter(m => m.contextWindow).map(m => ({ provider, id: m.id, name: m.name, inputModalities: [...(m.inputModalities ?? ['text'])] }));
|
|
46
|
+
}
|
|
47
|
+
async resolveModel(provider, id) {
|
|
48
|
+
const p = this.profile(provider), m = p?.models.find(m => m.id === id);
|
|
49
|
+
if (!m?.contextWindow) throw new LlmError('Custom model is missing or has no context window; edit it in /provider → Custom', 'INVALID_REQUEST');
|
|
50
|
+
return { provider, id, name: m.name, inputModalities: [...(m.inputModalities ?? ['text'])], context: { contextWindow: m.contextWindow },
|
|
51
|
+
defaultMaxTokens: m.maxTokens ?? Math.min(4096, Math.floor(m.contextWindow / 4)),
|
|
52
|
+
...(p.backend === 'omlx' ? { reasoning: { efforts: [{ id: ReasoningEffortId('off'), name: 'Off' }, { id: ReasoningEffortId('high'), name: 'On' }], ...(m.thinking !== 'default' ? { defaultEffort: ReasoningEffortId(m.thinking === 'off' ? 'off' : 'high') } : {}) } } : {}) };
|
|
53
|
+
}
|
|
54
|
+
async *stream(options) {
|
|
55
|
+
const profile = this.profile(options.provider);
|
|
56
|
+
const model = profile?.models.find(m => m.id === options.model);
|
|
57
|
+
if (!model?.contextWindow) throw new LlmError('Configure this custom model and its context window first', 'INVALID_REQUEST');
|
|
58
|
+
const controller = new AbortController();
|
|
59
|
+
const signal = AbortSignal.any([controller.signal, ...(options.signal ? [options.signal] : [])]);
|
|
60
|
+
let timer, release, timedOut = false;
|
|
61
|
+
const pulse = () => { clearTimeout(timer); timer = setTimeout(() => { timedOut = true; controller.abort(); }, profile.timeoutMs); timer.unref?.(); };
|
|
62
|
+
try {
|
|
63
|
+
let queue = this.queues.get(profile.id);
|
|
64
|
+
if (!queue) { queue = new RequestQueue(); this.queues.set(profile.id, queue); }
|
|
65
|
+
release = await queue.acquire(signal, ['session-title', 'session-card', 'compaction'].includes(options.purpose));
|
|
66
|
+
signal.throwIfAborted();
|
|
67
|
+
const key = await this.resolveKey(profile);
|
|
68
|
+
const attachments = this.resolveAttachments?.();
|
|
69
|
+
const images = await prepareImages(options.messages, model, attachments, ref => attachments && this.resolveImageAccess?.(attachments, ref), signal);
|
|
70
|
+
signal.throwIfAborted();
|
|
71
|
+
const body = requestBody(profile, model, { ...options, messages: images.messages }, images);
|
|
72
|
+
pulse();
|
|
73
|
+
const response = await this.fetch(endpoint(profile), { method: 'POST', redirect: 'error', headers: { ...attributionHeaders(), ...authHeaders(profile, key), 'content-type': 'application/json', accept: 'text/event-stream' }, body: JSON.stringify(body), signal });
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
// Inspect the error only to classify context overflow/rate limits. Never
|
|
76
|
+
// attach the raw response: gateways sometimes echo their request headers.
|
|
77
|
+
const raw = await response.text();
|
|
78
|
+
let detail;
|
|
79
|
+
try { detail = JSON.parse(raw)?.error; } catch { /* status still classifies non-JSON errors */ }
|
|
80
|
+
const delay = retryAfterMs(response.headers.get('retry-after'));
|
|
81
|
+
throw new LlmError(`Custom API returned HTTP ${response.status}`, errorCode(response.status, detail), { status: response.status, ...(delay ? { providerRetryAfterMs: delay } : {}) });
|
|
82
|
+
}
|
|
83
|
+
if (!response.body || !response.headers.get('content-type')?.includes('text/event-stream')) {
|
|
84
|
+
await response.body?.cancel();
|
|
85
|
+
throw new LlmError('Custom API did not return an SSE stream', 'MALFORMED_RESPONSE');
|
|
86
|
+
}
|
|
87
|
+
const replay = {};
|
|
88
|
+
const events = normalizedEvents(sseData(response.body, pulse), profile.api, replay);
|
|
89
|
+
for await (const chunk of translate(events, { model: model.id, kind: REPLAY_KIND, label: profile.name })) {
|
|
90
|
+
if (chunk.type === 'block-end' && chunk.block.type === 'tool-call') {
|
|
91
|
+
try { const args = JSON.parse(chunk.block.arguments); if (!args || typeof args !== 'object' || Array.isArray(args) || !chunk.block.id || !chunk.block.name) throw Error(); }
|
|
92
|
+
catch { throw new LlmError('Custom model returned an invalid tool call', 'MALFORMED_RESPONSE'); }
|
|
93
|
+
}
|
|
94
|
+
if (chunk.type === 'finish' && chunk.replayState) Object.assign(chunk.replayState.response, { api: profile.api, ...replay });
|
|
95
|
+
clearTimeout(timer); yield chunk; pulse();
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (options.signal?.aborted) throw new LlmError('Custom request cancelled', 'ABORTED');
|
|
99
|
+
if (timedOut) throw new LlmError(`Custom API idle timeout after ${profile.timeoutMs}ms; the model may still be loading`, 'TIMEOUT');
|
|
100
|
+
if (isAttachmentError(error)) throw new LlmError(`Custom image input failed: ${error.code}`, error.code);
|
|
101
|
+
// Never reflect a server response or transport error that might contain credentials.
|
|
102
|
+
if (error instanceof LlmError) throw new LlmError(error.code === 'MISSING_CREDENTIAL' ? error.message : `Custom request failed: ${error.code}`, error.code, {
|
|
103
|
+
...(error.failure?.status ? { status: error.failure.status } : {}),
|
|
104
|
+
...(error.failure?.providerRetryAfterMs ? { providerRetryAfterMs: error.failure.providerRetryAfterMs } : {}),
|
|
105
|
+
...(error.failure?.offloadImages ? { offloadImages: error.failure.offloadImages } : {}),
|
|
106
|
+
});
|
|
107
|
+
throw new LlmError('Could not connect to the custom API', 'TRANSPORT');
|
|
108
|
+
} finally { clearTimeout(timer); controller.abort(); release?.(); }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { parse, stringify } from 'yaml';
|
|
6
|
+
|
|
7
|
+
export const PROTOCOLS = ['chat-completions', 'responses', 'anthropic'];
|
|
8
|
+
export const customId = () => `custom-${randomUUID()}`;
|
|
9
|
+
export const isCustomId = id => typeof id === 'string' && /^custom-[a-zA-Z0-9-]+$/.test(id);
|
|
10
|
+
export const keyRef = id => `DSCODE_CUSTOM_${id.replaceAll('-', '_').toUpperCase()}_API_KEY`;
|
|
11
|
+
export const isCustomKey = ref => /^DSCODE_CUSTOM_CUSTOM_[A-Z0-9_]+_API_KEY$/.test(ref);
|
|
12
|
+
const positive = value => Number.isSafeInteger(value) && value > 0;
|
|
13
|
+
const cleanText = value => typeof value === 'string' && value.trim() && !/[\x00-\x1f\x7f]/.test(value);
|
|
14
|
+
|
|
15
|
+
export function endpoint(profile) {
|
|
16
|
+
return profile.baseURL + ({ 'chat-completions': '/chat/completions', responses: '/responses', anthropic: '/messages' })[profile.api];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Validate persisted data and drafts with the same rules; never persist a key. */
|
|
20
|
+
export function validateProfile(input) {
|
|
21
|
+
if (!isCustomId(input?.id)) throw Error('Invalid custom provider ID');
|
|
22
|
+
if (!cleanText(input.name)) throw Error('Enter a provider name');
|
|
23
|
+
let url;
|
|
24
|
+
try { url = new URL(input.baseURL); } catch { throw Error('Enter an HTTP or HTTPS API base URL'); }
|
|
25
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw Error('Base URL must use HTTP(S), without credentials, query or fragment');
|
|
26
|
+
if (!PROTOCOLS.includes(input.api)) throw Error('Choose a supported API format');
|
|
27
|
+
if (!['none', 'bearer', 'x-api-key'].includes(input.auth)) throw Error('Choose an authentication method');
|
|
28
|
+
const models = (input.models ?? []).map(model => {
|
|
29
|
+
if (!cleanText(model.id)) throw Error('Enter a model ID');
|
|
30
|
+
for (const field of ['contextWindow', 'maxTokens']) if (model[field] !== undefined && !positive(model[field])) throw Error(`${field} must be a positive integer`);
|
|
31
|
+
if (model.maxTokens && model.contextWindow && model.maxTokens >= model.contextWindow) throw Error('Output budget must be smaller than the context window');
|
|
32
|
+
if (!['default', 'off', 'on'].includes(model.thinking ?? 'default')) throw Error('Invalid thinking setting');
|
|
33
|
+
const inputModalities = model.inputModalities ?? ['text'];
|
|
34
|
+
if (!Array.isArray(inputModalities) || !inputModalities.includes('text') || inputModalities.some(m => !['text', 'image'].includes(m)) || new Set(inputModalities).size !== inputModalities.length) throw Error('Model input must be text or text and image');
|
|
35
|
+
return {
|
|
36
|
+
id: model.id.trim(), name: cleanText(model.name) ? model.name.trim() : model.id.trim(),
|
|
37
|
+
...(model.contextWindow ? { contextWindow: model.contextWindow, contextSource: model.contextSource === 'server' ? 'server' : 'user' } : {}),
|
|
38
|
+
// A deliberately blank output budget selects the calculated default and
|
|
39
|
+
// remains a user override when the service is discovered again.
|
|
40
|
+
...(model.maxTokens ? { maxTokens: model.maxTokens, outputSource: model.outputSource === 'server' ? 'server' : 'user' }
|
|
41
|
+
: model.outputSource === 'user' ? { outputSource: 'user' } : {}),
|
|
42
|
+
thinking: model.thinking ?? 'default',
|
|
43
|
+
inputModalities: inputModalities.includes('image') ? ['text', 'image'] : ['text'],
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
if (new Set(models.map(m => m.id)).size !== models.length) throw Error('Model IDs must be unique within a provider');
|
|
47
|
+
const timeoutMs = input.timeoutMs ?? 180000;
|
|
48
|
+
if (!positive(timeoutMs)) throw Error('Request idle timeout must be positive');
|
|
49
|
+
return { id: input.id, name: input.name.trim(), baseURL: url.href.replace(/\/+$/, ''), api: input.api, auth: input.auth,
|
|
50
|
+
backend: input.backend === 'omlx' ? 'omlx' : 'generic', timeoutMs, models };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whole-file revision + cross-process lock: stale editors never overwrite another Host. */
|
|
54
|
+
export class CustomStore {
|
|
55
|
+
constructor(path = join(homedir(), '.dscode', 'providers.yaml')) { this.path = path; }
|
|
56
|
+
async read() {
|
|
57
|
+
let raw;
|
|
58
|
+
try { raw = await readFile(this.path, 'utf8'); } catch (error) { if (error.code !== 'ENOENT') throw error; raw = ''; }
|
|
59
|
+
const doc = raw ? parse(raw) : { version: 1, providers: [] };
|
|
60
|
+
if (doc?.version !== 1 || !Array.isArray(doc.providers)) throw Error('Unsupported custom provider configuration');
|
|
61
|
+
const providers = doc.providers.map(validateProfile);
|
|
62
|
+
if (new Set(providers.map(p => p.id)).size !== providers.length) throw Error('Duplicate custom provider IDs');
|
|
63
|
+
return { providers, revision: createHash('sha256').update(raw).digest('hex') };
|
|
64
|
+
}
|
|
65
|
+
async update(revision, change) {
|
|
66
|
+
await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
67
|
+
const lock = this.path + '.lock', temporary = this.path + '.' + randomUUID() + '.tmp';
|
|
68
|
+
const deadline = Date.now() + 3000;
|
|
69
|
+
for (;;) {
|
|
70
|
+
try { await mkdir(lock, { mode: 0o700 }); break; } catch (error) {
|
|
71
|
+
if (error.code !== 'EEXIST') throw error;
|
|
72
|
+
if (Date.now() >= deadline) throw Error('Custom provider configuration is locked by another process', { cause: error });
|
|
73
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const current = await this.read();
|
|
78
|
+
if (current.revision !== revision) throw Error('Custom providers changed in another window; reopen this editor');
|
|
79
|
+
const providers = (await change(current.providers)).map(validateProfile);
|
|
80
|
+
await writeFile(temporary, stringify({ version: 1, providers }), { mode: 0o600, flag: 'wx' });
|
|
81
|
+
await rename(temporary, this.path);
|
|
82
|
+
return await this.read();
|
|
83
|
+
} finally { await rm(temporary, { force: true }); await rm(lock, { recursive: true, force: true }); }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Shared Web/Desktop client. Draft keys live only in this mounted form.
|
|
2
|
+
globalThis.__ModuleLoader__.load({
|
|
3
|
+
id: '@toddzheng024/dscode-desktop',
|
|
4
|
+
factory: require => {
|
|
5
|
+
const { createElement: h, useState, useEffect, useRef } = require('react');
|
|
6
|
+
const control = { padding: '8px 10px', border: '1px solid #8886', borderRadius: 6, background: 'transparent', color: 'inherit', font: 'inherit', boxSizing: 'border-box' };
|
|
7
|
+
const row = { display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center' };
|
|
8
|
+
const resetModel = model => {
|
|
9
|
+
const next = { ...model, thinking: 'default', inputModalities: ['text'] };
|
|
10
|
+
for (const [field, source] of [['contextWindow', 'contextSource'], ['maxTokens', 'outputSource']]) {
|
|
11
|
+
if (next[source] === 'server') { delete next[field]; delete next[source]; }
|
|
12
|
+
}
|
|
13
|
+
return next;
|
|
14
|
+
};
|
|
15
|
+
function Settings({ execute }) {
|
|
16
|
+
const [snapshot, setSnapshot] = useState(null), [draft, setDraft] = useState(null), [key, setKey] = useState('');
|
|
17
|
+
const [busy, setBusy] = useState(false), [error, setError] = useState(''), [notice, setNotice] = useState(''), [report, setReport] = useState(null), [removing, setRemoving] = useState(false);
|
|
18
|
+
const pending = useRef(false), alive = useRef(true), request = useRef(null);
|
|
19
|
+
const run = async (task, cancellable = false) => {
|
|
20
|
+
if (pending.current) return;
|
|
21
|
+
const controller = cancellable ? new AbortController() : null;
|
|
22
|
+
request.current = controller;
|
|
23
|
+
pending.current = true; setBusy(true); setError(''); setNotice('');
|
|
24
|
+
let cancellationFailed = false;
|
|
25
|
+
try { await task(controller?.signal); }
|
|
26
|
+
catch (error) {
|
|
27
|
+
cancellationFailed = error.cancellationFailed === true;
|
|
28
|
+
if (alive.current && (!controller?.signal.aborted || cancellationFailed)) setError(error.message);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
request.current = null; pending.current = false;
|
|
32
|
+
if (alive.current) {
|
|
33
|
+
setBusy(false);
|
|
34
|
+
if (controller?.signal.aborted) setNotice(cancellationFailed ? 'Cancellation could not be confirmed.' : 'Request cancelled.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const select = profile => { setDraft(profile ? { ...profile, models: profile.models.map(model => ({ ...model })) } : null); setKey(''); setReport(null); setRemoving(false); setNotice(''); };
|
|
39
|
+
const accept = value => { if (!alive.current) return; setSnapshot(value); select(value.providers.find(p => p.id === draft?.id) ?? value.providers[0]); };
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
alive.current = true;
|
|
42
|
+
void run(async () => { accept(await execute('list')); });
|
|
43
|
+
return () => { alive.current = false; request.current?.abort(); };
|
|
44
|
+
}, []);
|
|
45
|
+
const change = values => { setDraft({ ...draft, ...values }); setReport(null); setRemoving(false); setNotice('Unsaved changes'); };
|
|
46
|
+
const modelChange = (index, values) => change({ models: draft.models.map((model, i) => i === index ? { ...model, ...values } : model) });
|
|
47
|
+
const button = (text, action, disabled = false, cancellable = false) => h('button', { type: 'button', style: { ...control, cursor: 'pointer' }, disabled: busy || disabled, onClick: () => run(action, cancellable) }, text);
|
|
48
|
+
const input = (label, value, onChange, options = {}) => h('label', { style: { display: 'grid', gap: 5, minWidth: 180, flex: 1 } }, label,
|
|
49
|
+
h('input', { 'aria-label': label, style: { ...control, width: '100%' }, value: value ?? '', onChange: event => onChange(event.target.value), ...options }));
|
|
50
|
+
const choice = (label, value, choices, onChange) => h('label', { style: { display: 'grid', gap: 5, flex: 1 } }, label,
|
|
51
|
+
h('select', { 'aria-label': label, value, onChange: event => onChange(event.target.value), style: control }, ...choices.map(([id, text]) => h('option', { key: id, value: id }, text))));
|
|
52
|
+
return h('section', { 'aria-label': 'DSCODE model settings', style: { padding: 20, display: 'grid', gap: 16, maxWidth: 850, color: 'inherit' } },
|
|
53
|
+
h('h2', { style: { margin: 0 } }, 'DSCODE models'),
|
|
54
|
+
h('p', { style: { margin: 0 } }, 'Connect a private or self-hosted model service. Saved models appear in the model picker.'),
|
|
55
|
+
error && h('div', { role: 'alert', style: { color: '#d85b5b', whiteSpace: 'pre-wrap' } }, error),
|
|
56
|
+
notice && h('div', { role: 'status' }, notice),
|
|
57
|
+
busy && request.current && h('button', { type: 'button', style: control, disabled: request.current.signal.aborted,
|
|
58
|
+
onClick: () => { request.current?.abort(); setNotice('Cancelling request…'); },
|
|
59
|
+
}, 'Cancel request'),
|
|
60
|
+
h('div', { style: row },
|
|
61
|
+
button('Reload (discard draft)', async () => accept(await execute('list'))),
|
|
62
|
+
button('Add provider', async () => { const value = await execute('new'); if (alive.current) select(value); }, !snapshot)),
|
|
63
|
+
h('fieldset', { disabled: busy || !snapshot, style: { border: 0, padding: 0, margin: 0, display: 'grid', gap: 16 } },
|
|
64
|
+
choice('Saved provider', snapshot?.providers.some(p => p.id === draft?.id) ? draft.id : '', [['', 'Choose a provider'], ...(snapshot?.providers ?? []).map(p => [p.id, p.name])], id => select(snapshot.providers.find(p => p.id === id))),
|
|
65
|
+
draft && h('div', { style: { display: 'grid', gap: 16 } },
|
|
66
|
+
h('div', { style: row }, input('Provider name', draft.name, name => change({ name })),
|
|
67
|
+
input('API base URL', draft.baseURL, baseURL => change({ baseURL, backend: 'generic', models: draft.models.map(resetModel) }), { placeholder: 'https://example.com/v1', type: 'url' })),
|
|
68
|
+
h('div', { style: row }, choice('API format', draft.api, [['chat-completions', 'Chat Completions'], ['responses', 'Responses'], ['anthropic', 'Anthropic Messages']], api => change({ api, auth: api === 'anthropic' ? 'x-api-key' : 'bearer', models: draft.models.map(resetModel) })),
|
|
69
|
+
choice('Authentication', draft.auth, [['bearer', 'Bearer token'], ['x-api-key', 'API key header'], ['none', 'None']], auth => change({ auth }))),
|
|
70
|
+
draft.auth !== 'none' && input('API key', key, value => { setKey(value); setReport(null); setRemoving(false); setNotice('Unsaved changes'); }, { type: 'password', autoComplete: 'off', spellCheck: false, placeholder: 'Leave blank to keep the saved key' }),
|
|
71
|
+
h('small', null, draft.credentialStatus ?? 'Not saved'),
|
|
72
|
+
h('div', { style: row }, button('Discover models', async signal => {
|
|
73
|
+
const found = await execute('discover', { profile: draft, key }, signal);
|
|
74
|
+
if (!alive.current || signal.aborted) return;
|
|
75
|
+
const models = [...draft.models];
|
|
76
|
+
for (const model of found.models) {
|
|
77
|
+
const index = models.findIndex(previous => previous.id === model.id), previous = models[index];
|
|
78
|
+
if (index < 0) models.push(model);
|
|
79
|
+
else models[index] = {
|
|
80
|
+
...previous, ...model, thinking: previous.thinking,
|
|
81
|
+
...(previous.contextSource === 'user' ? { contextWindow: previous.contextWindow, contextSource: 'user' } : {}),
|
|
82
|
+
...(previous.outputSource === 'user' ? { maxTokens: previous.maxTokens, outputSource: 'user' } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
change({ backend: found.backend, models });
|
|
86
|
+
setNotice(`Found ${found.models.length} models. Confirm the context size and image support before saving.`);
|
|
87
|
+
}, false, true), button('Add model', async () => change({ models: [...draft.models, { id: '', thinking: 'default', inputModalities: ['text'] }] }))),
|
|
88
|
+
...draft.models.map((model, index) => h('fieldset', { key: index, style: { border: '1px solid #8886', borderRadius: 8, padding: 14, display: 'grid', gap: 12 } },
|
|
89
|
+
h('legend', null, `Model ${index + 1}`),
|
|
90
|
+
h('div', { style: row }, input(`Model ${index + 1} ID`, model.id, id => change({ models: draft.models.map((entry, i) => i === index ? { ...resetModel(entry), id } : entry) })),
|
|
91
|
+
input(`Model ${index + 1} context`, model.contextWindow, value => modelChange(index, { contextWindow: value ? Number(value) : undefined, contextSource: 'user' }), { type: 'number', min: 1, step: 1 }),
|
|
92
|
+
input(`Model ${index + 1} output limit`, model.maxTokens, value => modelChange(index, { maxTokens: value ? Number(value) : undefined, outputSource: 'user' }), { type: 'number', min: 1, step: 1 })),
|
|
93
|
+
h('div', { style: row }, choice(`Model ${index + 1} thinking`, model.thinking ?? 'default', [['default', 'Provider default'], ['off', 'Off'], ['on', 'On']], thinking => modelChange(index, { thinking })),
|
|
94
|
+
h('label', null, h('input', { type: 'checkbox', 'aria-label': `Model ${index + 1} accepts images`, checked: model.inputModalities?.includes('image') ?? false,
|
|
95
|
+
onChange: event => modelChange(index, { inputModalities: event.target.checked ? ['text', 'image'] : ['text'] }) }), ' Accepts images')),
|
|
96
|
+
h('div', { style: row }, button(`Test model ${index + 1}`, async signal => {
|
|
97
|
+
setReport(null);
|
|
98
|
+
const stages = await execute('test', { profile: draft, model: model.id, key }, signal);
|
|
99
|
+
if (alive.current && !signal.aborted) setReport({ model: model.id, stages });
|
|
100
|
+
}, !model.id || !model.contextWindow, true),
|
|
101
|
+
button(`Remove model ${index + 1}`, async () => change({ models: draft.models.filter((_, i) => i !== index) }))))),
|
|
102
|
+
h('p', { style: { margin: 0, fontSize: 13 } }, 'Tests send text and tool calls to this service and may incur usage. Image support is a manual setting; the test does not verify vision.'),
|
|
103
|
+
report && h('div', null,
|
|
104
|
+
h('h3', { style: { margin: 0 } }, `Test results: ${report.model}`),
|
|
105
|
+
h('ul', { 'aria-label': 'Model test results' }, ...report.stages.map((stage, index) => h('li', { key: index }, `${stage.name}: ${stage.status}${stage.message ? ` — ${stage.message}` : ''}`)))),
|
|
106
|
+
h('div', { style: row }, button('Save provider', async () => {
|
|
107
|
+
const saved = await execute('save', { profile: draft, key, revision: snapshot.revision });
|
|
108
|
+
if (alive.current) { accept(saved); setNotice('Provider saved. Select its model in a session.'); }
|
|
109
|
+
}, draft.models.length === 0 || draft.models.some(model => !model.contextWindow)),
|
|
110
|
+
snapshot?.providers.some(p => p.id === draft.id) && button(removing ? 'Confirm remove provider and key' : 'Remove provider', async () => {
|
|
111
|
+
if (!removing) { setRemoving(true); return; }
|
|
112
|
+
const saved = await execute('remove', { id: draft.id, revision: snapshot.revision });
|
|
113
|
+
if (alive.current) { accept(saved); setNotice('Provider removed.'); }
|
|
114
|
+
})))));
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
inject: ['slots', 'connection'],
|
|
118
|
+
apply(ctx) {
|
|
119
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'dscode-models', order: 11, label: () => 'DSCODE models',
|
|
120
|
+
inject: () => ({ execute: async (action, args = {}, signal) => {
|
|
121
|
+
signal?.throwIfAborted();
|
|
122
|
+
const requestId = signal ? crypto.randomUUID() : undefined;
|
|
123
|
+
let cancellation;
|
|
124
|
+
const cancel = () => {
|
|
125
|
+
cancellation ??= ctx.connection.rpc.call('/api', 'dscode-custom', { action: 'cancel', requestId }, AbortSignal.timeout(10000))
|
|
126
|
+
.then(response => response.ok ? null : response.error.message, error => error.message);
|
|
127
|
+
};
|
|
128
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
129
|
+
let response, requestError;
|
|
130
|
+
try {
|
|
131
|
+
response = await ctx.connection.rpc.call('/api', 'dscode-custom', { action, ...args, ...(requestId ? { requestId } : {}) }, signal);
|
|
132
|
+
} catch (error) { requestError = error; }
|
|
133
|
+
finally { signal?.removeEventListener('abort', cancel); }
|
|
134
|
+
if (cancellation) {
|
|
135
|
+
const failure = await cancellation;
|
|
136
|
+
if (failure) {
|
|
137
|
+
const error = Error('Could not confirm server cancellation. The request may still be running: ' + failure);
|
|
138
|
+
error.cancellationFailed = true;
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (requestError) throw requestError;
|
|
143
|
+
if (!response.ok) throw Error(response.error.message);
|
|
144
|
+
return response.value;
|
|
145
|
+
} }),
|
|
146
|
+
}, Settings));
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { clientRequestSchema } from '@deepseek-ai/dsh-client-connection';
|
|
2
|
+
|
|
3
|
+
/** UI-only configuration transport; never registers an Agent tool or command. */
|
|
4
|
+
export async function customRequest(service, payload, signal) {
|
|
5
|
+
if (!service) throw Error('Custom model configuration is unavailable.');
|
|
6
|
+
if (!payload || typeof payload !== 'object') throw Error('Invalid custom model request.');
|
|
7
|
+
const key = payload.key ?? '';
|
|
8
|
+
if (typeof key !== 'string' || key.length > 4096) throw Error('Invalid API key.');
|
|
9
|
+
if (['save', 'remove'].includes(payload.action) && typeof payload.revision !== 'string') throw Error('Reload the saved providers before making changes.');
|
|
10
|
+
switch (payload.action) {
|
|
11
|
+
case 'list': return service.list();
|
|
12
|
+
case 'new': return service.newProfile();
|
|
13
|
+
case 'save': return service.save(payload.profile, key, payload.revision);
|
|
14
|
+
case 'remove': return service.remove(payload.id, payload.revision);
|
|
15
|
+
case 'discover': return service.discover(payload.profile, key, signal);
|
|
16
|
+
case 'test': return service.test(payload.profile, payload.model, key, signal);
|
|
17
|
+
default: throw Error('Unknown custom model action.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function apply(ctx) {
|
|
22
|
+
ctx.inject(['connection'], scope => {
|
|
23
|
+
// Electron's application-protocol proxy may keep its upstream HTTP request
|
|
24
|
+
// alive after renderer fetch aborts. Explicit cancellation covers that hop.
|
|
25
|
+
const operations = new Map();
|
|
26
|
+
const operation = id => {
|
|
27
|
+
if (typeof id !== 'string' || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(id)) throw Error('Invalid request ID.');
|
|
28
|
+
for (const [key, entry] of operations) if (entry.expires <= Date.now()) operations.delete(key);
|
|
29
|
+
let entry = operations.get(id);
|
|
30
|
+
if (!entry) {
|
|
31
|
+
if (operations.size >= 128) throw Error('Too many model requests; try again shortly.');
|
|
32
|
+
entry = { controller: new AbortController(), started: false, expires: Date.now() + 60000 };
|
|
33
|
+
operations.set(id, entry);
|
|
34
|
+
}
|
|
35
|
+
return entry;
|
|
36
|
+
};
|
|
37
|
+
scope.effect(() => {
|
|
38
|
+
const unregister = scope.connection.fetch.register({ path: '/api/dscode-custom', methods: ['POST'], requestBody: 'buffered',
|
|
39
|
+
fetch: async request => {
|
|
40
|
+
let body;
|
|
41
|
+
try { body = await request.json(); } catch { return new Response('Invalid JSON', { status: 400 }); }
|
|
42
|
+
const parsed = clientRequestSchema.safeParse(body);
|
|
43
|
+
if (!parsed.success || parsed.data.method !== 'dscode-custom') return new Response('Invalid custom model request', { status: 400 });
|
|
44
|
+
const { rpcId, payload } = parsed.data;
|
|
45
|
+
let result, tracked;
|
|
46
|
+
try {
|
|
47
|
+
// Discovery and inference enforce their own timeouts. A transport-wide
|
|
48
|
+
// deadline would cut short a valid cold start or active streamed test.
|
|
49
|
+
if (payload?.action === 'cancel') {
|
|
50
|
+
operation(payload.requestId).controller.abort();
|
|
51
|
+
result = { ok: true, value: null };
|
|
52
|
+
} else {
|
|
53
|
+
if (payload?.requestId !== undefined) {
|
|
54
|
+
if (!['discover', 'test'].includes(payload.action)) throw Error('Only discovery and tests support cancellation.');
|
|
55
|
+
const entry = operation(payload.requestId);
|
|
56
|
+
entry.controller.signal.throwIfAborted();
|
|
57
|
+
if (entry.started) throw Error('Request ID already used.');
|
|
58
|
+
tracked = entry; tracked.started = true; tracked.expires = Infinity;
|
|
59
|
+
}
|
|
60
|
+
const signal = tracked ? AbortSignal.any([request.signal, tracked.controller.signal]) : request.signal;
|
|
61
|
+
result = { ok: true, value: await customRequest(ctx.get('dscodeCustom'), payload, signal) };
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const secret = typeof payload?.key === 'string' ? payload.key : '';
|
|
65
|
+
const message = String(error.message ?? 'Custom model request failed.');
|
|
66
|
+
result = { ok: false, error: { code: 'dscode-custom', message: secret ? message.replaceAll(secret, '[redacted]') : message, details: {} } };
|
|
67
|
+
}
|
|
68
|
+
finally { if (tracked) tracked.expires = Date.now() + 60000; }
|
|
69
|
+
return Response.json({ type: 'server-response', rpcId, result }, { headers: { 'Cache-Control': 'no-store' } });
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return () => {
|
|
73
|
+
for (const entry of operations.values()) entry.controller.abort();
|
|
74
|
+
operations.clear(); unregister();
|
|
75
|
+
};
|
|
76
|
+
}, 'dscode-custom.rpc');
|
|
77
|
+
}).then(undefined, error => { ctx.logger.error(`Custom model transport failed: ${error.message}`); });
|
|
78
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { IMAGE_OFFLOAD_REQUIRED_CODE, LlmError, contentHasImage, offloadedImageText, projectOffloadedImages, requiredImageOffload } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { requestImageDimensions } from '@deepseek-ai/dsh-attachment';
|
|
3
|
+
|
|
4
|
+
const IMAGE_POLICY = Object.freeze({ maxPixels: 2048 * 2048, maxBytes: 1024 * 1024 });
|
|
5
|
+
const MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
/** Read durable images once per attachment; offloading remains a session decision. */
|
|
8
|
+
export async function prepareImages(history, model, attachments, access, signal) {
|
|
9
|
+
const messages = projectOffloadedImages(history, ref => offloadedImageText(ref, access(ref)));
|
|
10
|
+
const versions = new Map();
|
|
11
|
+
if (!messages.some(m => contentHasImage(m.content))) return { messages, versions, access };
|
|
12
|
+
if (!model.inputModalities?.includes('image')) throw new LlmError('Enable image input for this custom model before sending retained images', 'UNSUPPORTED_CONTENT');
|
|
13
|
+
if (!attachments) throw new LlmError('Image input requires the durable attachment service', 'UNSUPPORTED_CONTENT');
|
|
14
|
+
const refs = new Map();
|
|
15
|
+
const collect = blocks => {
|
|
16
|
+
for (const block of blocks) {
|
|
17
|
+
if (block.type === 'image') refs.set(block.attachment.attachmentId, block.attachment);
|
|
18
|
+
else if (block.type === 'tool-result') collect(block.content);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
for (const message of messages) collect(message.content);
|
|
22
|
+
for (const ref of refs.values()) {
|
|
23
|
+
signal?.throwIfAborted();
|
|
24
|
+
const target = { ...requestImageDimensions(ref.width, ref.height, IMAGE_POLICY.maxPixels), maxBytes: IMAGE_POLICY.maxBytes };
|
|
25
|
+
versions.set(ref.attachmentId, await attachments.readImageRequest(ref, target, signal));
|
|
26
|
+
}
|
|
27
|
+
const offloadImages = requiredImageOffload(messages,
|
|
28
|
+
{ representation: 'base64', maxBytes: MAX_REQUEST_IMAGE_BYTES, byteQuantum: 1 },
|
|
29
|
+
block => versions.get(block.attachment.attachmentId).bytes);
|
|
30
|
+
if (offloadImages > 0) throw new LlmError('Custom request images exceed the 20 MiB encoded image budget', IMAGE_OFFLOAD_REQUIRED_CODE, { offloadImages });
|
|
31
|
+
return { messages, versions, access };
|
|
32
|
+
}
|