@deepseek-harness-tui/dsh-tui 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -256
- package/bin/dsh-tui.js +172 -5
- package/cordis.patch.yml +16 -4
- package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
- package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
- package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
- package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
- package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
- package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
- package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
- package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
- package/dsh-ecosystem-spec/registry/README.md +14 -14
- package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
- package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
- package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
- package/lib/types/cc/hyperlink.d.ts +7 -1
- package/lib/types/cc/hyperlink.d.ts.map +1 -1
- package/lib/types/cc/hyperlink.js +3 -2
- package/lib/types/cc/markdown.d.ts.map +1 -1
- package/lib/types/cc/markdown.js +33 -3
- package/lib/types/cc/sessionColors.d.ts +27 -0
- package/lib/types/cc/sessionColors.d.ts.map +1 -0
- package/lib/types/cc/sessionColors.js +39 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +6 -0
- package/lib/types/components/AutoRecapRow.d.ts +23 -0
- package/lib/types/components/AutoRecapRow.d.ts.map +1 -0
- package/lib/types/components/AutoRecapRow.js +23 -0
- package/lib/types/components/ColorPicker.d.ts +17 -0
- package/lib/types/components/ColorPicker.d.ts.map +1 -0
- package/lib/types/components/ColorPicker.js +20 -0
- package/lib/types/components/CommandSuggestions.d.ts +3 -1
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/ContextBarView.d.ts +33 -0
- package/lib/types/components/ContextBarView.d.ts.map +1 -0
- package/lib/types/components/ContextBarView.js +46 -0
- package/lib/types/components/EffortInputBorder.d.ts +12 -1
- package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
- package/lib/types/components/EffortInputBorder.js +22 -5
- package/lib/types/components/FileActionsPanel.d.ts +22 -0
- package/lib/types/components/FileActionsPanel.d.ts.map +1 -0
- package/lib/types/components/FileActionsPanel.js +28 -0
- package/lib/types/components/FileSuggestions.d.ts +3 -1
- package/lib/types/components/FileSuggestions.d.ts.map +1 -1
- package/lib/types/components/MessageList.d.ts +3 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +13 -6
- package/lib/types/components/ModelPicker.d.ts +26 -4
- package/lib/types/components/ModelPicker.d.ts.map +1 -1
- package/lib/types/components/ModelPicker.js +26 -6
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +365 -57
- package/lib/types/components/RecapPanel.d.ts +25 -0
- package/lib/types/components/RecapPanel.d.ts.map +1 -0
- package/lib/types/components/RecapPanel.js +55 -0
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
- package/lib/types/components/ScrollbarGutter.js +55 -4
- package/lib/types/components/SubagentCard.d.ts.map +1 -1
- package/lib/types/components/SubagentCard.js +6 -4
- package/lib/types/components/SuggestionCard.d.ts +4 -2
- package/lib/types/components/SuggestionCard.d.ts.map +1 -1
- package/lib/types/components/TimelineRail.d.ts.map +1 -1
- package/lib/types/components/TimelineRail.js +9 -2
- package/lib/types/components/design-system/ThemeProvider.d.ts +7 -0
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemeProvider.js +2 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +36 -8
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +7 -3
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +41 -9
- package/lib/types/dsh-adapter/channel.d.ts +101 -2
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +969 -93
- package/lib/types/dsh-adapter/compat/index.d.ts +8 -6
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/index.js +8 -6
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +150 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.js +518 -41
- package/lib/types/dsh-adapter/contract.d.ts +1 -1
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +1 -0
- package/lib/types/dsh-adapter/extension-events.d.ts +1 -1
- package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.d.ts +10 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +5 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +317 -8
- package/lib/types/dsh-adapter/presets.d.ts +18 -0
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +31 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts +29 -1
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +89 -5
- package/lib/types/dsh-adapter/questions.d.ts +19 -2
- package/lib/types/dsh-adapter/questions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/questions.js +53 -10
- package/lib/types/dsh-adapter/recap.d.ts +49 -0
- package/lib/types/dsh-adapter/recap.d.ts.map +1 -0
- package/lib/types/dsh-adapter/recap.js +121 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts +314 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessionTree.js +954 -0
- package/lib/types/dsh-adapter/shortcuts.d.ts +15 -44
- package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
- package/lib/types/dsh-adapter/shortcuts.js +28 -165
- package/lib/types/dsh-adapter/types.d.ts +1 -1
- package/lib/types/dsh-adapter/types.d.ts.map +1 -1
- package/lib/types/herdr.js +1 -1
- package/lib/types/i18n.d.ts +604 -176
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +206 -72
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +21 -0
- package/lib/types/migrationPrefs.d.ts +60 -0
- package/lib/types/migrationPrefs.d.ts.map +1 -0
- package/lib/types/migrationPrefs.js +97 -0
- package/lib/types/modelGroups.d.ts +72 -0
- package/lib/types/modelGroups.d.ts.map +1 -0
- package/lib/types/modelGroups.js +97 -0
- package/lib/types/modelRecents.d.ts +39 -0
- package/lib/types/modelRecents.d.ts.map +1 -0
- package/lib/types/modelRecents.js +97 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts +11 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -1
- package/lib/types/native-ts/yoga-layout/index.js +31 -1
- package/lib/types/oauth.d.ts +15 -0
- package/lib/types/oauth.d.ts.map +1 -0
- package/lib/types/oauth.js +14 -0
- package/lib/types/plugin-spec/registry.d.ts +1 -1
- package/lib/types/plugin-spec/registry.js +1 -1
- package/lib/types/reload.d.ts +109 -0
- package/lib/types/reload.d.ts.map +1 -0
- package/lib/types/reload.js +118 -0
- package/lib/types/screens/Chat.d.ts +3 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +588 -64
- package/lib/types/screens/SessionTree.d.ts +27 -0
- package/lib/types/screens/SessionTree.d.ts.map +1 -0
- package/lib/types/screens/SessionTree.js +555 -0
- package/lib/types/screens/StatusLine.d.ts +0 -9
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +215 -52
- package/lib/types/screens/StatusMetrics.d.ts +35 -3
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
- package/lib/types/screens/StatusMetrics.js +63 -8
- package/lib/types/screens/chatOverlay.d.ts +17 -1
- package/lib/types/screens/chatOverlay.d.ts.map +1 -1
- package/lib/types/screens/chatOverlay.js +3 -1
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +22 -4
- package/lib/types/update.d.ts +159 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +496 -30
- package/lib/types/utils/debug.d.ts +1 -1
- package/lib/types/utils/debug.js +3 -3
- package/lib/types/utils/fileTarget.d.ts +46 -0
- package/lib/types/utils/fileTarget.d.ts.map +1 -0
- package/lib/types/utils/fileTarget.js +178 -0
- package/lib/types/utils/keymap.d.ts +134 -0
- package/lib/types/utils/keymap.d.ts.map +1 -0
- package/lib/types/utils/keymap.js +311 -0
- package/lib/types/utils/mentions.d.ts +25 -2
- package/lib/types/utils/mentions.d.ts.map +1 -1
- package/lib/types/utils/mentions.js +69 -10
- package/lib/types/utils/openExternal.d.ts +50 -0
- package/lib/types/utils/openExternal.d.ts.map +1 -0
- package/lib/types/utils/openExternal.js +170 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +21 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +140 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +11 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +44 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.d.ts +14 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.js +67 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.d.ts +64 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.js +170 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.d.ts +79 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.js +196 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.d.ts +68 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +193 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.d.ts +32 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.js +101 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.d.ts +31 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.js +68 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.d.ts +69 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.js +129 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +70 -0
- package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/command/LICENSE +21 -21
- package/node_modules/@dsh-std/command/README.md +26 -26
- package/node_modules/@dsh-std/command/README.zh.md +28 -28
- package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/command/package.json +22 -22
- package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/node_modules/@dsh-std/connection/README.md +42 -42
- package/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/node_modules/@dsh-std/connection/package.json +20 -20
- package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/core/LICENSE +21 -21
- package/node_modules/@dsh-std/core/README.md +13 -13
- package/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/core/package.json +21 -21
- package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
- package/node_modules/@dsh-std/manifest/LICENSE +21 -21
- package/node_modules/@dsh-std/manifest/README.md +5 -5
- package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
- package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/manifest/package.json +20 -20
- package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
- package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/messages/LICENSE +21 -21
- package/node_modules/@dsh-std/messages/README.md +5 -5
- package/node_modules/@dsh-std/messages/README.zh.md +7 -7
- package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/messages/package.json +20 -20
- package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
- package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
- package/node_modules/@dsh-std/presentation/LICENSE +21 -21
- package/node_modules/@dsh-std/presentation/README.md +27 -27
- package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
- package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
- package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/presentation/package.json +25 -25
- package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/storage/LICENSE +21 -21
- package/node_modules/@dsh-std/storage/README.md +5 -5
- package/node_modules/@dsh-std/storage/README.zh.md +7 -7
- package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/storage/package.json +20 -20
- package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
- package/package.json +17 -5
- package/presets/liangshen/.dsh-tui-managed.json +1 -1
- package/presets/liangshen/instruction-hint.mjs +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `AuthInteraction` bridge: pi-ai OAuth flows speak prompts and events;
|
|
3
|
+
* every interactive surface in this ecosystem speaks `ctx.userQuestions`.
|
|
4
|
+
*
|
|
5
|
+
* `prompt()` maps one pi-ai prompt onto one question — `select` prompts carry
|
|
6
|
+
* their options, text/secret/manual-code prompts expect the custom-answer
|
|
7
|
+
* input row.
|
|
8
|
+
*
|
|
9
|
+
* `notify()` restores the host contract pi-ai's browser flows assume: the
|
|
10
|
+
* `auth_url` / `device_code` events expect the *host* to open the browser
|
|
11
|
+
* (their authorize URLs run hundreds of characters — wrapped across panel
|
|
12
|
+
* lines they are unclickable, and hand-copying picks up wrap artifacts that
|
|
13
|
+
* corrupt the encoded `redirect_uri`, which the provider then rejects). So
|
|
14
|
+
* the bridge opens the URL itself, then shows one waiting panel carrying
|
|
15
|
+
* copy / reopen / cancel actions until the flow settles. The device-code
|
|
16
|
+
* path gets the same treatment: its short code is the thing to copy, and
|
|
17
|
+
* the verification page is auto-opened too.
|
|
18
|
+
*
|
|
19
|
+
* One `AbortController` owns the whole run: pi-ai reads `interaction.signal`
|
|
20
|
+
* to abort the flow, every ask request carries it so surfaces close their
|
|
21
|
+
* panels, and the waiting panel's cancel action fires it.
|
|
22
|
+
*
|
|
23
|
+
* Nothing here assumes a browser or GUI *exists* (TUI-RUN-001): when no
|
|
24
|
+
* opener or clipboard helper is available the panel degrades to showing the
|
|
25
|
+
* URL / code verbatim with a clear note, and where no question provider is
|
|
26
|
+
* registered at all, `ask` rejects and the login command turns that into a
|
|
27
|
+
* clear refusal.
|
|
28
|
+
*
|
|
29
|
+
* @module dsh-auth/interaction
|
|
30
|
+
*/
|
|
31
|
+
import { copyToClipboard, openInBrowser } from './opener.js';
|
|
32
|
+
/** Human-readable copy for one notify event. */
|
|
33
|
+
export function describeEvent(event) {
|
|
34
|
+
switch (event.type) {
|
|
35
|
+
case 'auth_url':
|
|
36
|
+
return `Open this URL to authorize (a local callback completes sign-in):\n${event.url}`;
|
|
37
|
+
case 'device_code':
|
|
38
|
+
return `Visit ${event.verificationUri} and enter this code:\n ${event.userCode}`;
|
|
39
|
+
case 'info':
|
|
40
|
+
case 'progress':
|
|
41
|
+
return event.message;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const CANCEL = 'Cancel sign-in';
|
|
45
|
+
const OPEN_AGAIN = 'Open browser again';
|
|
46
|
+
const COPY_LINK = 'Copy authorization link';
|
|
47
|
+
const COPY_CODE = 'Copy code';
|
|
48
|
+
/** One answer row, or a thrown error when the surface returned nothing usable. */
|
|
49
|
+
function singleAnswer(answer) {
|
|
50
|
+
const row = answer.answers[0];
|
|
51
|
+
if (row === undefined)
|
|
52
|
+
throw new Error('dsh-auth: the question surface returned no answer');
|
|
53
|
+
return { selected: row.selected[0], custom: row.custom };
|
|
54
|
+
}
|
|
55
|
+
function authUrlView(url, instructions, opened) {
|
|
56
|
+
const lead = opened
|
|
57
|
+
? 'Authorization page opened in your browser — complete the sign-in there.\n'
|
|
58
|
+
+ 'If it did not open, copy the link below and open it by hand.'
|
|
59
|
+
: `Open this URL to authorize${instructions === undefined ? '' : ` — ${instructions}`}\n`
|
|
60
|
+
+ 'Copy the link below (it is too long to select reliably once wrapped):';
|
|
61
|
+
return { body: `${lead}\n${url}`, copyLabel: COPY_LINK, copyText: url, reopenUrl: url };
|
|
62
|
+
}
|
|
63
|
+
function deviceCodeView(userCode, verificationUri, opened) {
|
|
64
|
+
const lead = opened
|
|
65
|
+
? `Enter this code on the page opened in your browser (${verificationUri}):`
|
|
66
|
+
: `Visit ${verificationUri} and enter this code:`;
|
|
67
|
+
return { body: `${lead}\n ${userCode}`, copyLabel: COPY_CODE, copyText: userCode, reopenUrl: verificationUri };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The bridge for one login run. At most one waiting panel exists at a time;
|
|
71
|
+
* `settle()` retires it when the flow ends (any outcome), so a completed or
|
|
72
|
+
* failed login never leaves an interactive dead end on screen.
|
|
73
|
+
*/
|
|
74
|
+
export class QuestionBridge {
|
|
75
|
+
ask;
|
|
76
|
+
runAbort;
|
|
77
|
+
signal;
|
|
78
|
+
openUrl;
|
|
79
|
+
copy;
|
|
80
|
+
waiting;
|
|
81
|
+
waitingClosed = false;
|
|
82
|
+
constructor(ask, runAbort, helpers = {}) {
|
|
83
|
+
this.ask = ask;
|
|
84
|
+
this.runAbort = runAbort;
|
|
85
|
+
this.signal = runAbort.signal;
|
|
86
|
+
this.openUrl = helpers.openUrl ?? openInBrowser;
|
|
87
|
+
this.copy = helpers.copyText ?? copyToClipboard;
|
|
88
|
+
}
|
|
89
|
+
async prompt(prompt) {
|
|
90
|
+
const secretNote = prompt.type === 'secret' ? ' (input is not masked on this surface — mind your screen)' : '';
|
|
91
|
+
const question = prompt.type === 'select'
|
|
92
|
+
? {
|
|
93
|
+
id: 'dsh-auth-prompt',
|
|
94
|
+
header: 'dsh-auth',
|
|
95
|
+
question: prompt.message,
|
|
96
|
+
options: prompt.options.map(option => ({
|
|
97
|
+
label: option.label,
|
|
98
|
+
...(option.description === undefined ? {} : { description: option.description }),
|
|
99
|
+
})),
|
|
100
|
+
}
|
|
101
|
+
: {
|
|
102
|
+
id: 'dsh-auth-prompt',
|
|
103
|
+
header: 'dsh-auth',
|
|
104
|
+
question: prompt.message,
|
|
105
|
+
...(prompt.placeholder === undefined
|
|
106
|
+
? (secretNote === '' ? {} : { detail: secretNote.slice(1) })
|
|
107
|
+
: { detail: `${prompt.placeholder}${secretNote}` }),
|
|
108
|
+
};
|
|
109
|
+
const answer = singleAnswer(await this.ask({ questions: [question], signal: prompt.signal ?? this.signal }));
|
|
110
|
+
if (prompt.type === 'select') {
|
|
111
|
+
const label = answer.selected;
|
|
112
|
+
const option = label === undefined ? undefined : prompt.options.find(candidate => candidate.label === label);
|
|
113
|
+
if (option === undefined) {
|
|
114
|
+
if (answer.custom !== undefined && answer.custom !== '')
|
|
115
|
+
return answer.custom;
|
|
116
|
+
throw new Error('dsh-auth: the selection answer did not match an offered option');
|
|
117
|
+
}
|
|
118
|
+
return option.id;
|
|
119
|
+
}
|
|
120
|
+
const custom = answer.custom?.trim();
|
|
121
|
+
if (custom === undefined || custom === '')
|
|
122
|
+
throw new Error('dsh-auth: the text answer was empty');
|
|
123
|
+
return custom;
|
|
124
|
+
}
|
|
125
|
+
notify(event) {
|
|
126
|
+
if (event.type === 'auth_url') {
|
|
127
|
+
if (this.waiting !== undefined)
|
|
128
|
+
return;
|
|
129
|
+
const opened = this.openUrl(event.url);
|
|
130
|
+
void this.runWaitingPanel(authUrlView(event.url, event.instructions, opened));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (event.type === 'device_code') {
|
|
134
|
+
if (this.waiting !== undefined)
|
|
135
|
+
return;
|
|
136
|
+
const opened = this.openUrl(event.verificationUri);
|
|
137
|
+
void this.runWaitingPanel(deviceCodeView(event.userCode, event.verificationUri, opened));
|
|
138
|
+
}
|
|
139
|
+
// info / progress lines are transient; the waiting panel already tells
|
|
140
|
+
// the user what to do, and the flow's next prompt carries its own copy.
|
|
141
|
+
}
|
|
142
|
+
/** Close the waiting panel (if any) and stop answering for this run. */
|
|
143
|
+
async settle() {
|
|
144
|
+
this.waitingClosed = true;
|
|
145
|
+
this.waiting?.controller.abort();
|
|
146
|
+
await this.waiting?.answer;
|
|
147
|
+
this.waiting = undefined;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Show the waiting panel until the flow settles or the user cancels,
|
|
151
|
+
* re-asking after a copy/reopen action with a one-line status prefix.
|
|
152
|
+
*/
|
|
153
|
+
async runWaitingPanel(view) {
|
|
154
|
+
let body = view.body;
|
|
155
|
+
while (!this.waitingClosed) {
|
|
156
|
+
const controller = new AbortController();
|
|
157
|
+
const answer = this.ask({
|
|
158
|
+
questions: [{
|
|
159
|
+
id: 'dsh-auth-waiting',
|
|
160
|
+
header: 'dsh-auth',
|
|
161
|
+
question: 'Waiting for authorization…',
|
|
162
|
+
detail: body,
|
|
163
|
+
options: [
|
|
164
|
+
{ label: view.copyLabel },
|
|
165
|
+
...(view.reopenUrl === undefined ? [] : [{ label: OPEN_AGAIN }]),
|
|
166
|
+
{ label: CANCEL },
|
|
167
|
+
],
|
|
168
|
+
}],
|
|
169
|
+
signal: controller.signal,
|
|
170
|
+
})
|
|
171
|
+
.then(response => singleAnswer(response).selected)
|
|
172
|
+
.catch(() => undefined);
|
|
173
|
+
this.waiting = { controller, answer };
|
|
174
|
+
const picked = await answer;
|
|
175
|
+
this.waiting = undefined;
|
|
176
|
+
if (picked === undefined || this.waitingClosed)
|
|
177
|
+
return;
|
|
178
|
+
if (picked === CANCEL) {
|
|
179
|
+
this.runAbort.abort('user cancelled sign-in');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (picked === view.copyLabel) {
|
|
183
|
+
const copied = await this.copy(view.copyText);
|
|
184
|
+
body = `${copied ? 'Copied — paste it where you need it.' : 'Copy failed — no clipboard helper answered; select the text manually.'}\n${view.body}`;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (picked === OPEN_AGAIN && view.reopenUrl !== undefined) {
|
|
188
|
+
this.openUrl(view.reopenUrl);
|
|
189
|
+
body = `Reopened in your browser.\n${view.body}`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort host-side conveniences for OAuth flows: opening the default
|
|
3
|
+
* browser and copying short text to the clipboard.
|
|
4
|
+
*
|
|
5
|
+
* pi-ai's flows emit an `auth_url` event whose instructions assume the host
|
|
6
|
+
* opens the browser itself ("A browser window should open…"), because the
|
|
7
|
+
* authorize URL is hundreds of characters — unclickable and practically
|
|
8
|
+
* uncopyable once a terminal panel wraps it (wrap artifacts inside the
|
|
9
|
+
* encoded `redirect_uri` are exactly how hand-copied URLs die with
|
|
10
|
+
* `invalid_authorize_request`). These helpers restore that host contract.
|
|
11
|
+
* Every path is best-effort and never throws: a missing opener or clipboard
|
|
12
|
+
* degrades to the URL staying on screen with a copy action.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-auth/opener
|
|
15
|
+
*/
|
|
16
|
+
/** The browser-opening command for this platform, or undefined when unknown. */
|
|
17
|
+
export declare function openerFor(url: string): {
|
|
18
|
+
command: string;
|
|
19
|
+
args: readonly string[];
|
|
20
|
+
verbatim: boolean;
|
|
21
|
+
} | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Open `url` in the default browser, detached; the TUI keeps running.
|
|
24
|
+
* @returns whether the opener process could be spawned at all (not whether
|
|
25
|
+
* a browser eventually appeared — that is not observable here).
|
|
26
|
+
*/
|
|
27
|
+
export declare function openInBrowser(url: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Copy `text` to the system clipboard, trying this platform's helpers in
|
|
30
|
+
* order. Resolves whether one succeeded; never rejects.
|
|
31
|
+
*/
|
|
32
|
+
export declare function copyToClipboard(text: string): Promise<boolean>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort host-side conveniences for OAuth flows: opening the default
|
|
3
|
+
* browser and copying short text to the clipboard.
|
|
4
|
+
*
|
|
5
|
+
* pi-ai's flows emit an `auth_url` event whose instructions assume the host
|
|
6
|
+
* opens the browser itself ("A browser window should open…"), because the
|
|
7
|
+
* authorize URL is hundreds of characters — unclickable and practically
|
|
8
|
+
* uncopyable once a terminal panel wraps it (wrap artifacts inside the
|
|
9
|
+
* encoded `redirect_uri` are exactly how hand-copied URLs die with
|
|
10
|
+
* `invalid_authorize_request`). These helpers restore that host contract.
|
|
11
|
+
* Every path is best-effort and never throws: a missing opener or clipboard
|
|
12
|
+
* degrades to the URL staying on screen with a copy action.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-auth/opener
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from 'node:child_process';
|
|
17
|
+
/** The browser-opening command for this platform, or undefined when unknown. */
|
|
18
|
+
export function openerFor(url) {
|
|
19
|
+
if (process.platform === 'win32') {
|
|
20
|
+
// cmd.exe parses `&` as a command separator, so an unquoted authorize
|
|
21
|
+
// URL (hundreds of chars of `?a=1&b=2…`) is truncated at the first `&`
|
|
22
|
+
// and the browser opens a param-less page — OpenAI answers "missing
|
|
23
|
+
// required parameter". Node only quotes argv containing spaces/quotes,
|
|
24
|
+
// which a URL has neither of, so the protection must be explicit: the
|
|
25
|
+
// whole `start` command rides as ONE verbatim argv token, with the URL
|
|
26
|
+
// double-quoted inside it (`start` treats a leading quoted arg as the
|
|
27
|
+
// window title, hence the empty "" first).
|
|
28
|
+
return {
|
|
29
|
+
command: 'cmd.exe',
|
|
30
|
+
args: ['/d', '/s', '/c', `start "" "${url}"`],
|
|
31
|
+
verbatim: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (process.platform === 'darwin')
|
|
35
|
+
return { command: 'open', args: [url], verbatim: false };
|
|
36
|
+
return { command: 'xdg-open', args: [url], verbatim: false };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Open `url` in the default browser, detached; the TUI keeps running.
|
|
40
|
+
* @returns whether the opener process could be spawned at all (not whether
|
|
41
|
+
* a browser eventually appeared — that is not observable here).
|
|
42
|
+
*/
|
|
43
|
+
export function openInBrowser(url) {
|
|
44
|
+
const opener = openerFor(url);
|
|
45
|
+
if (opener === undefined)
|
|
46
|
+
return false;
|
|
47
|
+
try {
|
|
48
|
+
const child = spawn(opener.command, [...opener.args], {
|
|
49
|
+
detached: true,
|
|
50
|
+
stdio: 'ignore',
|
|
51
|
+
windowsHide: true,
|
|
52
|
+
...(opener.verbatim ? { windowsVerbatimArguments: true } : {}),
|
|
53
|
+
});
|
|
54
|
+
child.on('error', () => undefined);
|
|
55
|
+
child.unref();
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Feed `text` to one clipboard helper's stdin; resolves whether it exited 0. */
|
|
63
|
+
function pipeTo(command, args, text) {
|
|
64
|
+
return new Promise(resolve => {
|
|
65
|
+
let child;
|
|
66
|
+
try {
|
|
67
|
+
child = spawn(command, [...args], { stdio: ['pipe', 'ignore', 'ignore'], windowsHide: true });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
resolve(false);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
child.on('error', () => resolve(false));
|
|
74
|
+
child.on('close', code => resolve(code === 0));
|
|
75
|
+
child.stdin.on('error', () => resolve(false));
|
|
76
|
+
child.stdin.end(text);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Clipboard helper candidates per platform, in try order. */
|
|
80
|
+
function clipboardCandidates() {
|
|
81
|
+
if (process.platform === 'win32')
|
|
82
|
+
return [{ command: 'clip.exe', args: [] }];
|
|
83
|
+
if (process.platform === 'darwin')
|
|
84
|
+
return [{ command: 'pbcopy', args: [] }];
|
|
85
|
+
return [
|
|
86
|
+
{ command: 'wl-copy', args: [] },
|
|
87
|
+
{ command: 'xclip', args: ['-selection', 'clipboard'] },
|
|
88
|
+
{ command: 'xsel', args: ['--clipboard', '--input'] },
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Copy `text` to the system clipboard, trying this platform's helpers in
|
|
93
|
+
* order. Resolves whether one succeeded; never rejects.
|
|
94
|
+
*/
|
|
95
|
+
export async function copyToClipboard(text) {
|
|
96
|
+
for (const candidate of clipboardCandidates()) {
|
|
97
|
+
if (await pipeTo(candidate.command, candidate.args, text))
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider routes this plugin owns: the pi-ai catalog providers that ship an
|
|
3
|
+
* OAuth flow, mounted with their catalog models, wire implementations, and
|
|
4
|
+
* OAuth flow objects untouched. Credential resolution is not wrapped at all —
|
|
5
|
+
* the adapter's collection carries this plugin's `CredentialStore`
|
|
6
|
+
* (`PiAiAuthInjection`), so requests resolve the stored OAuth credential
|
|
7
|
+
* through the provider's own auth and refresh tokens under the store's lock.
|
|
8
|
+
*
|
|
9
|
+
* The hand-built profile mirrors what an empty llm-pi-ai settings profile
|
|
10
|
+
* resolves to for a catalog route, including the image-request defaults
|
|
11
|
+
* (llm-pi-ai's own `DEFAULT_*` constants are not exported; the values below
|
|
12
|
+
* are those constants, and the comment on each says so).
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-auth/profiles
|
|
15
|
+
*/
|
|
16
|
+
import type { Provider } from '@earendil-works/pi-ai';
|
|
17
|
+
import type { ResolvedPiAiProviderProfile } from '@deepseek-ai/dsh-llm-pi-ai';
|
|
18
|
+
/** Provider routes this build mounts, in picker order. */
|
|
19
|
+
export declare const OAUTH_PROVIDER_IDS: readonly ["openai-codex", "anthropic", "xai"];
|
|
20
|
+
/** One routable provider id. */
|
|
21
|
+
export type OAuthProviderId = (typeof OAUTH_PROVIDER_IDS)[number];
|
|
22
|
+
/** The OAuth flow object for one mounted route (login/refresh/toAuth live here). */
|
|
23
|
+
export declare function oauthOf(route: Provider): NonNullable<Provider['auth']['oauth']>;
|
|
24
|
+
/**
|
|
25
|
+
* Build the resolved profile one route registers under. Mirrors the fields
|
|
26
|
+
* `PiAiAdapter` reads: identity for selectors, the idle timeout, retry
|
|
27
|
+
* policy, and image budgets it forwards per request, and the catalog provider
|
|
28
|
+
* with its own models. Everything optional stays absent — the adapter treats
|
|
29
|
+
* absence as "provider defaults" exactly as an empty llm-pi-ai profile would.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildOAuthProfile(id: string): ResolvedPiAiProviderProfile;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider routes this plugin owns: the pi-ai catalog providers that ship an
|
|
3
|
+
* OAuth flow, mounted with their catalog models, wire implementations, and
|
|
4
|
+
* OAuth flow objects untouched. Credential resolution is not wrapped at all —
|
|
5
|
+
* the adapter's collection carries this plugin's `CredentialStore`
|
|
6
|
+
* (`PiAiAuthInjection`), so requests resolve the stored OAuth credential
|
|
7
|
+
* through the provider's own auth and refresh tokens under the store's lock.
|
|
8
|
+
*
|
|
9
|
+
* The hand-built profile mirrors what an empty llm-pi-ai settings profile
|
|
10
|
+
* resolves to for a catalog route, including the image-request defaults
|
|
11
|
+
* (llm-pi-ai's own `DEFAULT_*` constants are not exported; the values below
|
|
12
|
+
* are those constants, and the comment on each says so).
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-auth/profiles
|
|
15
|
+
*/
|
|
16
|
+
import { builtinProviders } from '@earendil-works/pi-ai/providers/all';
|
|
17
|
+
import { resolveRetryPolicy } from '@deepseek-ai/dsh-llm';
|
|
18
|
+
/** Provider routes this build mounts, in picker order. */
|
|
19
|
+
export const OAUTH_PROVIDER_IDS = ['openai-codex', 'anthropic', 'xai'];
|
|
20
|
+
/** llm-pi-ai `DEFAULT_MAX_REQUEST_IMAGE_BYTES` (20 MiB, base64 payload bound). */
|
|
21
|
+
const MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
22
|
+
/** llm-pi-ai `DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET` (2048×2048 pixels). */
|
|
23
|
+
const REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048;
|
|
24
|
+
/** llm-pi-ai `DEFAULT_REQUEST_IMAGE_MAX_BYTES` (1 MiB per image). */
|
|
25
|
+
const REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
|
|
26
|
+
/** llm-pi-ai `DEFAULT_STREAM_IDLE_TIMEOUT_MS` (5 minutes). */
|
|
27
|
+
const STREAM_IDLE_TIMEOUT_MS = 300_000;
|
|
28
|
+
/** The installed pi-ai catalog provider for one route, whose OAuth flow we mount. */
|
|
29
|
+
function catalogProviderOf(id) {
|
|
30
|
+
const found = builtinProviders().find(provider => provider.id === id);
|
|
31
|
+
if (found === undefined) {
|
|
32
|
+
throw new Error(`dsh-auth: the installed pi-ai catalog ships no provider "${id}"; `
|
|
33
|
+
+ 'remove it from this plugin\'s providers config or update pi-ai');
|
|
34
|
+
}
|
|
35
|
+
return found;
|
|
36
|
+
}
|
|
37
|
+
/** The OAuth flow object for one mounted route (login/refresh/toAuth live here). */
|
|
38
|
+
export function oauthOf(route) {
|
|
39
|
+
const oauth = route.auth.oauth;
|
|
40
|
+
if (oauth === undefined) {
|
|
41
|
+
throw new Error(`dsh-auth: pi-ai provider "${route.id}" ships no OAuth flow`);
|
|
42
|
+
}
|
|
43
|
+
return oauth;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the resolved profile one route registers under. Mirrors the fields
|
|
47
|
+
* `PiAiAdapter` reads: identity for selectors, the idle timeout, retry
|
|
48
|
+
* policy, and image budgets it forwards per request, and the catalog provider
|
|
49
|
+
* with its own models. Everything optional stays absent — the adapter treats
|
|
50
|
+
* absence as "provider defaults" exactly as an empty llm-pi-ai profile would.
|
|
51
|
+
*/
|
|
52
|
+
export function buildOAuthProfile(id) {
|
|
53
|
+
if (!OAUTH_PROVIDER_IDS.includes(id)) {
|
|
54
|
+
throw new Error(`dsh-auth: "${id}" is not an OAuth provider this build mounts (${OAUTH_PROVIDER_IDS.join(', ')})`);
|
|
55
|
+
}
|
|
56
|
+
const catalog = catalogProviderOf(id);
|
|
57
|
+
return {
|
|
58
|
+
provider: id,
|
|
59
|
+
displayName: catalog.name,
|
|
60
|
+
streamIdleTimeoutMs: STREAM_IDLE_TIMEOUT_MS,
|
|
61
|
+
retryPolicy: resolveRetryPolicy(undefined, `dsh-auth: provider "${id}" retryPolicy`),
|
|
62
|
+
configuredMaxTokens: new Map(),
|
|
63
|
+
piProvider: catalog,
|
|
64
|
+
maxRequestImageBytes: MAX_REQUEST_IMAGE_BYTES,
|
|
65
|
+
requestImagePixelBudget: REQUEST_IMAGE_PIXEL_BUDGET,
|
|
66
|
+
requestImageMaxBytes: REQUEST_IMAGE_MAX_BYTES,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `ctx.dshAuth` service: the programmatic surface over this plugin's
|
|
3
|
+
* mounted OAuth routes. UIs (the dsh-tui /provider wizard, a web settings
|
|
4
|
+
* page) enumerate providers with masked sign-in state and drive login/logout
|
|
5
|
+
* without touching the credential file or the pi-ai flow objects; the `/auth`
|
|
6
|
+
* command in `command.ts` is a thin textual veneer over the same api.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-auth/service
|
|
9
|
+
*/
|
|
10
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { ResolvedPiAiProviderProfile } from '@deepseek-ai/dsh-llm-pi-ai';
|
|
13
|
+
import { CredentialFile } from './credentials.js';
|
|
14
|
+
import { type AskFn } from './interaction.js';
|
|
15
|
+
/** One provider's sign-in state; never carries token material. */
|
|
16
|
+
export interface DshAuthSignInStatus {
|
|
17
|
+
provider: string;
|
|
18
|
+
/** Route display name (selectors, pickers). */
|
|
19
|
+
label: string;
|
|
20
|
+
/** The OAuth flow's own name, e.g. "OpenAI (ChatGPT Plus/Pro)". */
|
|
21
|
+
oauthLabel: string;
|
|
22
|
+
/** The flow's login-call-to-action label, when it ships one. */
|
|
23
|
+
loginLabel: string | undefined;
|
|
24
|
+
signedIn: boolean;
|
|
25
|
+
expiresAt: number | undefined;
|
|
26
|
+
/** Signed in, but the stored access token has expired (refresh may still work). */
|
|
27
|
+
expired: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** The outcome of a successful login. */
|
|
30
|
+
export interface DshAuthLoginResult {
|
|
31
|
+
provider: string;
|
|
32
|
+
oauthLabel: string;
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
}
|
|
35
|
+
/** The service api consumed by commands and UIs. */
|
|
36
|
+
export interface DshAuthApi {
|
|
37
|
+
/** Every mounted provider with masked sign-in state. */
|
|
38
|
+
providers(): Promise<readonly DshAuthSignInStatus[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Run one provider's OAuth login. `provider` omitted asks the interactive
|
|
41
|
+
* surface to choose among providers not currently signed in.
|
|
42
|
+
* @throws Error when no interactive surface is present, the provider is
|
|
43
|
+
* unknown, a login is already running, or the flow itself fails.
|
|
44
|
+
*/
|
|
45
|
+
login(provider?: string, signal?: AbortSignal): Promise<DshAuthLoginResult>;
|
|
46
|
+
/** Remove one provider's stored credential; resolves whether one existed. */
|
|
47
|
+
logout(provider: string): Promise<boolean>;
|
|
48
|
+
}
|
|
49
|
+
/** Cordis service holder; `api` is set by the plugin's apply. */
|
|
50
|
+
export declare class DshAuthService extends Service {
|
|
51
|
+
api: DshAuthApi | undefined;
|
|
52
|
+
constructor(ctx: Context);
|
|
53
|
+
}
|
|
54
|
+
/** Everything the api factory needs; all cordis surface is injected, so tests run without a host. */
|
|
55
|
+
export interface DshAuthApiDeps {
|
|
56
|
+
profiles: ReadonlyMap<string, ResolvedPiAiProviderProfile>;
|
|
57
|
+
store: CredentialFile;
|
|
58
|
+
/** The interactive ask surface, resolved per call so mounting order never matters. */
|
|
59
|
+
resolveAsk: () => AskFn | undefined;
|
|
60
|
+
logger: {
|
|
61
|
+
warn(message: string): void;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The api implementation. One login runs per provider at a time (an in-flight
|
|
66
|
+
* map, not a global lock: providers sign in independently); a second login
|
|
67
|
+
* attempt for the same provider fails fast instead of stacking two flows.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createDshAuthApi(deps: DshAuthApiDeps): DshAuthApi;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `ctx.dshAuth` service: the programmatic surface over this plugin's
|
|
3
|
+
* mounted OAuth routes. UIs (the dsh-tui /provider wizard, a web settings
|
|
4
|
+
* page) enumerate providers with masked sign-in state and drive login/logout
|
|
5
|
+
* without touching the credential file or the pi-ai flow objects; the `/auth`
|
|
6
|
+
* command in `command.ts` is a thin textual veneer over the same api.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-auth/service
|
|
9
|
+
*/
|
|
10
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
11
|
+
import { asStoredCredential } from './credentials.js';
|
|
12
|
+
import { oauthOf } from './profiles.js';
|
|
13
|
+
import { QuestionBridge } from './interaction.js';
|
|
14
|
+
/** Cordis service holder; `api` is set by the plugin's apply. */
|
|
15
|
+
export class DshAuthService extends Service {
|
|
16
|
+
api;
|
|
17
|
+
constructor(ctx) {
|
|
18
|
+
super(ctx, 'dshAuth');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Select a provider interactively among `candidates`. */
|
|
22
|
+
async function chooseProvider(ask, candidates, signal) {
|
|
23
|
+
const answer = await ask({
|
|
24
|
+
questions: [{
|
|
25
|
+
id: 'dsh-auth-provider',
|
|
26
|
+
header: 'dsh-auth',
|
|
27
|
+
question: 'Sign in with which provider?',
|
|
28
|
+
options: candidates.map(row => ({ label: row.oauthLabel, description: row.provider })),
|
|
29
|
+
}],
|
|
30
|
+
signal,
|
|
31
|
+
});
|
|
32
|
+
const row = answer.answers[0];
|
|
33
|
+
const label = row?.selected[0];
|
|
34
|
+
const chosen = candidates.find(candidate => candidate.oauthLabel === label);
|
|
35
|
+
if (chosen === undefined)
|
|
36
|
+
throw new Error('dsh-auth: no provider was chosen');
|
|
37
|
+
return chosen.provider;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The api implementation. One login runs per provider at a time (an in-flight
|
|
41
|
+
* map, not a global lock: providers sign in independently); a second login
|
|
42
|
+
* attempt for the same provider fails fast instead of stacking two flows.
|
|
43
|
+
*/
|
|
44
|
+
export function createDshAuthApi(deps) {
|
|
45
|
+
const inflight = new Map();
|
|
46
|
+
const statusOf = async () => {
|
|
47
|
+
const described = new Map((await deps.store.describe()).map(row => [row.provider, row]));
|
|
48
|
+
return [...deps.profiles.entries()].map(([id, profile]) => {
|
|
49
|
+
const oauth = oauthOf(profile.piProvider);
|
|
50
|
+
const row = described.get(id);
|
|
51
|
+
return {
|
|
52
|
+
provider: id,
|
|
53
|
+
label: profile.displayName,
|
|
54
|
+
oauthLabel: oauth.name,
|
|
55
|
+
loginLabel: oauth.loginLabel,
|
|
56
|
+
signedIn: row !== undefined && !row.expired,
|
|
57
|
+
expiresAt: row?.expiresAt,
|
|
58
|
+
expired: row?.expired ?? false,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const loginOne = async (provider, ask, signal) => {
|
|
63
|
+
const profile = deps.profiles.get(provider);
|
|
64
|
+
if (profile === undefined) {
|
|
65
|
+
throw new Error(`dsh-auth: unknown provider "${provider}" (mounted: ${[...deps.profiles.keys()].join(', ')})`);
|
|
66
|
+
}
|
|
67
|
+
const oauth = oauthOf(profile.piProvider);
|
|
68
|
+
const runAbort = new AbortController();
|
|
69
|
+
if (signal !== undefined) {
|
|
70
|
+
if (signal.aborted)
|
|
71
|
+
runAbort.abort(signal.reason);
|
|
72
|
+
else
|
|
73
|
+
signal.addEventListener('abort', () => runAbort.abort(signal.reason), { once: true });
|
|
74
|
+
}
|
|
75
|
+
const bridge = new QuestionBridge(ask, runAbort);
|
|
76
|
+
try {
|
|
77
|
+
const returned = await oauth.login(bridge);
|
|
78
|
+
const normalized = asStoredCredential(returned);
|
|
79
|
+
if (normalized === undefined) {
|
|
80
|
+
throw new Error(`dsh-auth: the ${oauth.name} flow returned an unusable credential; nothing was stored`);
|
|
81
|
+
}
|
|
82
|
+
const stored = normalized;
|
|
83
|
+
await deps.store.modify(provider, async () => stored);
|
|
84
|
+
return { provider, oauthLabel: oauth.name, expiresAt: stored.expires };
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await bridge.settle();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
providers: statusOf,
|
|
92
|
+
login: async (provider, signal) => {
|
|
93
|
+
const ask = deps.resolveAsk();
|
|
94
|
+
let target = provider;
|
|
95
|
+
if (target === undefined) {
|
|
96
|
+
if (ask === undefined) {
|
|
97
|
+
throw new Error('dsh-auth: provider selection needs an interactive surface; name the provider: /auth login <provider>');
|
|
98
|
+
}
|
|
99
|
+
const statuses = await statusOf();
|
|
100
|
+
const candidates = statuses.filter(row => !row.signedIn);
|
|
101
|
+
if (candidates.length === 0)
|
|
102
|
+
throw new Error('dsh-auth: every mounted provider is already signed in');
|
|
103
|
+
target = await chooseProvider(ask, candidates, signal);
|
|
104
|
+
}
|
|
105
|
+
else if (!deps.profiles.has(target)) {
|
|
106
|
+
throw new Error(`dsh-auth: unknown provider "${target}" (mounted: ${[...deps.profiles.keys()].join(', ')})`);
|
|
107
|
+
}
|
|
108
|
+
if (ask === undefined) {
|
|
109
|
+
throw new Error(`dsh-auth: signing in to "${target}" needs an interactive surface (run inside dsh-tui or the web client); `
|
|
110
|
+
+ 'this plugin refuses to assume a browser on this machine');
|
|
111
|
+
}
|
|
112
|
+
const existing = inflight.get(target);
|
|
113
|
+
if (existing !== undefined) {
|
|
114
|
+
throw new Error(`dsh-auth: a login for "${target}" is already running`);
|
|
115
|
+
}
|
|
116
|
+
const run = loginOne(target, ask, signal).finally(() => { inflight.delete(target); });
|
|
117
|
+
inflight.set(target, run);
|
|
118
|
+
return run;
|
|
119
|
+
},
|
|
120
|
+
logout: async (provider) => {
|
|
121
|
+
if (!deps.profiles.has(provider)) {
|
|
122
|
+
throw new Error(`dsh-auth: unknown provider "${provider}" (mounted: ${[...deps.profiles.keys()].join(', ')})`);
|
|
123
|
+
}
|
|
124
|
+
const existed = (await deps.store.read(provider)) !== undefined;
|
|
125
|
+
await deps.store.delete(provider);
|
|
126
|
+
return existed;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|