@jingyi0605/codingns4dsh 0.1.0 → 0.1.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.
Files changed (213) hide show
  1. package/README.en.md +306 -0
  2. package/README.md +190 -993
  3. package/data/build/dist/client/account-bar.d.ts +2 -2
  4. package/data/build/dist/client/account-bar.js +96 -39
  5. package/data/build/dist/client/bundle.js +13722 -3611
  6. package/data/build/dist/client/cli-catalog.js +20 -3
  7. package/data/build/dist/client/cli-slots.d.ts +5 -0
  8. package/data/build/dist/client/cli-slots.js +57 -7
  9. package/data/build/dist/client/debug/ui.d.ts +1 -1
  10. package/data/build/dist/client/debug/ui.js +62 -32
  11. package/data/build/dist/client/dsh-runtime-version.d.ts +9 -2
  12. package/data/build/dist/client/dsh-runtime-version.js +24 -2
  13. package/data/build/dist/client/features/cli-adapters.d.ts +1 -1
  14. package/data/build/dist/client/features/cli-adapters.js +19 -16
  15. package/data/build/dist/client/features/debug.js +2 -2
  16. package/data/build/dist/client/features/index.d.ts +2 -1
  17. package/data/build/dist/client/features/index.js +3 -1
  18. package/data/build/dist/client/features/lan-access-panel.d.ts +1 -1
  19. package/data/build/dist/client/features/lan-access-panel.js +8 -10
  20. package/data/build/dist/client/features/lan-access.js +1 -1
  21. package/data/build/dist/client/features/login-protection-panel.d.ts +1 -1
  22. package/data/build/dist/client/features/login-protection-panel.js +7 -9
  23. package/data/build/dist/client/features/reverse-proxy-panel.d.ts +1 -1
  24. package/data/build/dist/client/features/reverse-proxy-panel.js +99 -11
  25. package/data/build/dist/client/features/reverse-proxy.d.ts +1 -1
  26. package/data/build/dist/client/features/reverse-proxy.js +19 -18
  27. package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +1 -1
  28. package/data/build/dist/client/features/terminal-enhancement-panel.js +62 -55
  29. package/data/build/dist/client/features/terminal-enhancement.d.ts +1 -1
  30. package/data/build/dist/client/features/terminal-enhancement.js +3 -3
  31. package/data/build/dist/client/features/types.d.ts +14 -3
  32. package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +1 -1
  33. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +31 -9
  34. package/data/build/dist/client/features/workspace-session-enhancement.js +72 -11
  35. package/data/build/dist/client/git-management.d.ts +16 -0
  36. package/data/build/dist/client/git-management.js +757 -0
  37. package/data/build/dist/client/index.d.ts +3 -2
  38. package/data/build/dist/client/index.js +152 -27
  39. package/data/build/dist/client/locale.js +91 -29
  40. package/data/build/dist/client/quick-phrase-layout.d.ts +49 -0
  41. package/data/build/dist/client/quick-phrase-layout.js +95 -0
  42. package/data/build/dist/client/quick-phrase-slot.d.ts +7 -0
  43. package/data/build/dist/client/quick-phrase-slot.js +370 -0
  44. package/data/build/dist/client/remote-web-context.js +1 -1
  45. package/data/build/dist/client/settings-bridge.d.ts +9 -6
  46. package/data/build/dist/client/settings-bridge.js +51 -27
  47. package/data/build/dist/client/settings-section.d.ts +3 -2
  48. package/data/build/dist/client/settings-section.js +33 -19
  49. package/data/build/dist/client/subscription-slot.d.ts +3 -0
  50. package/data/build/dist/client/subscription-slot.js +108 -16
  51. package/data/build/dist/client/terminal/model.d.ts +14 -2
  52. package/data/build/dist/client/terminal/model.js +109 -21
  53. package/data/build/dist/client/terminal/recovery.d.ts +50 -0
  54. package/data/build/dist/client/terminal/recovery.js +101 -0
  55. package/data/build/dist/client/terminal/ui.d.ts +2 -2
  56. package/data/build/dist/client/terminal/ui.js +49 -11
  57. package/data/build/dist/client/terminal/xterm-view.d.ts +2 -2
  58. package/data/build/dist/client/terminal/xterm-view.js +2 -1
  59. package/data/build/dist/client/theme.d.ts +6 -1
  60. package/data/build/dist/client/theme.js +32 -1
  61. package/data/build/dist/client/workspace-session-archive-dom.d.ts +12 -0
  62. package/data/build/dist/client/workspace-session-archive-dom.js +27 -5
  63. package/data/build/dist/client/workspace-session-visibility-dom.d.ts +30 -0
  64. package/data/build/dist/client/workspace-session-visibility-dom.js +604 -0
  65. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +50 -0
  66. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +139 -0
  67. package/data/build/dist/dsh-capabilities/client/conversation-adapter.d.ts +7 -0
  68. package/data/build/dist/dsh-capabilities/client/conversation-adapter.js +6 -0
  69. package/data/build/dist/dsh-capabilities/client/locale-adapter.d.ts +9 -0
  70. package/data/build/dist/dsh-capabilities/client/locale-adapter.js +4 -0
  71. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +10 -0
  72. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +14 -0
  73. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +8 -0
  74. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +16 -0
  75. package/data/build/dist/dsh-capabilities/client/sidebar-adapter.d.ts +7 -0
  76. package/data/build/dist/dsh-capabilities/client/sidebar-adapter.js +5 -0
  77. package/data/build/dist/dsh-capabilities/client/theme-adapter.d.ts +6 -0
  78. package/data/build/dist/dsh-capabilities/client/theme-adapter.js +4 -0
  79. package/data/build/dist/dsh-capabilities/client/typert-adapter.d.ts +5 -0
  80. package/data/build/dist/dsh-capabilities/client/typert-adapter.js +4 -0
  81. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +19 -0
  82. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +45 -0
  83. package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.d.ts +19 -0
  84. package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.js +13 -0
  85. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +8 -0
  86. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.js +47 -0
  87. package/data/build/dist/dsh-capabilities/host/typert-adapter.d.ts +6 -0
  88. package/data/build/dist/dsh-capabilities/host/typert-adapter.js +4 -0
  89. package/data/build/dist/dsh-capabilities/index.d.ts +6 -0
  90. package/data/build/dist/dsh-capabilities/index.js +6 -0
  91. package/data/build/dist/dsh-capabilities/matrix.d.ts +15 -0
  92. package/data/build/dist/dsh-capabilities/matrix.js +32 -0
  93. package/data/build/dist/dsh-capabilities/registry.d.ts +22 -0
  94. package/data/build/dist/dsh-capabilities/registry.js +196 -0
  95. package/data/build/dist/dsh-capabilities/routes.d.ts +10 -0
  96. package/data/build/dist/dsh-capabilities/routes.js +34 -0
  97. package/data/build/dist/dsh-capabilities/settings-store.d.ts +29 -0
  98. package/data/build/dist/dsh-capabilities/settings-store.js +5 -0
  99. package/data/build/dist/dsh-capabilities/types.d.ts +50 -0
  100. package/data/build/dist/dsh-capabilities/types.js +23 -0
  101. package/data/build/dist/features/registry.d.ts +8 -2
  102. package/data/build/dist/features/registry.js +104 -1
  103. package/data/build/dist/host/auth-session.d.ts +8 -1
  104. package/data/build/dist/host/auth-session.js +28 -6
  105. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +2 -0
  106. package/data/build/dist/host/cli-adapters/claude-driver.js +19 -4
  107. package/data/build/dist/host/cli-adapters/claude-model-options.d.ts +14 -0
  108. package/data/build/dist/host/cli-adapters/claude-model-options.js +157 -0
  109. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +11 -0
  110. package/data/build/dist/host/cli-adapters/codex-driver.js +166 -14
  111. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  112. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +15 -0
  113. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +120 -29
  114. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +4 -1
  115. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +25 -7
  116. package/data/build/dist/host/cli-adapters/feature.js +74 -8
  117. package/data/build/dist/host/cli-adapters/gemini-driver.js +2 -2
  118. package/data/build/dist/host/cli-adapters/grok-driver.js +2 -2
  119. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +4 -2
  120. package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -9
  121. package/data/build/dist/host/cli-adapters/kimi-driver.js +1 -1
  122. package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts +17 -0
  123. package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +88 -0
  124. package/data/build/dist/host/cli-adapters/legacy-session-settings.d.ts +24 -0
  125. package/data/build/dist/host/cli-adapters/legacy-session-settings.js +162 -0
  126. package/data/build/dist/host/cli-adapters/official-provider-subscription.d.ts +29 -0
  127. package/data/build/dist/host/cli-adapters/official-provider-subscription.js +361 -0
  128. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +2 -0
  129. package/data/build/dist/host/cli-adapters/opencode-driver.js +97 -12
  130. package/data/build/dist/host/cli-adapters/pi-driver.js +69 -23
  131. package/data/build/dist/host/cli-adapters/provider-registry.d.ts +33 -0
  132. package/data/build/dist/host/cli-adapters/provider-registry.js +83 -0
  133. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +25 -3
  134. package/data/build/dist/host/cli-adapters/provider-subscription.js +346 -30
  135. package/data/build/dist/host/cli-adapters/registry.d.ts +6 -0
  136. package/data/build/dist/host/cli-adapters/registry.js +170 -13
  137. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +10 -0
  138. package/data/build/dist/host/cli-adapters/session-store.d.ts +7 -0
  139. package/data/build/dist/host/cli-adapters/session-store.js +39 -1
  140. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
  141. package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +3 -0
  142. package/data/build/dist/host/cli-adapters/stream-normalizer.js +22 -2
  143. package/data/build/dist/host/control-api-client.d.ts +3 -3
  144. package/data/build/dist/host/control-api-client.js +2 -1
  145. package/data/build/dist/host/credential-store.js +11 -1
  146. package/data/build/dist/host/dsh-device-runtime.d.ts +4 -0
  147. package/data/build/dist/host/dsh-device-runtime.js +20 -9
  148. package/data/build/dist/host/features/auth.js +8 -5
  149. package/data/build/dist/host/features/debug.js +1 -1
  150. package/data/build/dist/host/features/git-management.d.ts +5 -0
  151. package/data/build/dist/host/features/git-management.js +290 -0
  152. package/data/build/dist/host/features/host-status.js +1 -1
  153. package/data/build/dist/host/features/index.d.ts +1 -0
  154. package/data/build/dist/host/features/index.js +3 -0
  155. package/data/build/dist/host/features/terminal-process.js +1 -1
  156. package/data/build/dist/host/features/terminal-status.d.ts +3 -1
  157. package/data/build/dist/host/features/terminal-status.js +3 -1
  158. package/data/build/dist/host/index.d.ts +7 -3
  159. package/data/build/dist/host/index.js +76 -11
  160. package/data/build/dist/host/lan-access-dsh.js +18 -5
  161. package/data/build/dist/host/native-session-bridge.d.ts +23 -1
  162. package/data/build/dist/host/native-session-bridge.js +157 -10
  163. package/data/build/dist/host/rpc-table.d.ts +1 -1
  164. package/data/build/dist/host/rpc.js +60 -10
  165. package/data/build/dist/host/session-migration-repair.d.ts +19 -0
  166. package/data/build/dist/host/session-migration-repair.js +395 -0
  167. package/data/build/dist/host/settings.d.ts +8 -1
  168. package/data/build/dist/host/settings.js +94 -3
  169. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
  170. package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
  171. package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
  172. package/data/build/dist/host/terminal/controller-factory.js +26 -7
  173. package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
  174. package/data/build/dist/host/terminal/runtime-manager.js +3 -0
  175. package/data/build/dist/host/terminal/startup.d.ts +2 -0
  176. package/data/build/dist/host/terminal/startup.js +2 -1
  177. package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
  178. package/data/build/dist/host/terminal/terminal-controller.js +101 -15
  179. package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
  180. package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
  181. package/data/build/dist/host/terminal/terminal-service.js +10 -0
  182. package/data/build/dist/index.d.ts +6 -2
  183. package/data/build/dist/index.js +8 -2
  184. package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
  185. package/data/build/dist/shared/contracts/config.d.ts +31 -1
  186. package/data/build/dist/shared/contracts/config.js +25 -0
  187. package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
  188. package/data/build/dist/shared/contracts/errors.d.ts +1 -1
  189. package/data/build/dist/shared/contracts/feature.d.ts +6 -0
  190. package/data/build/dist/shared/contracts/git.d.ts +82 -0
  191. package/data/build/dist/shared/contracts/git.js +3 -0
  192. package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
  193. package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
  194. package/data/build/dist/shared/contracts/version.d.ts +2 -2
  195. package/data/build/dist/shared/contracts/version.js +4 -6
  196. package/data/build/dist/shared/debug.d.ts +14 -0
  197. package/data/build/dist/shared/debug.js +53 -0
  198. package/data/build/dist/shared/index.d.ts +7 -4
  199. package/data/build/dist/shared/index.js +2 -1
  200. package/data/build/dist/transport/debug.d.ts +1 -1
  201. package/data/build/dist/transport/debug.js +3 -30
  202. package/data/build/dist/transport/dsh-session.js +3 -3
  203. package/data/build/dist/typert.host.js +4 -2
  204. package/data/build/dist/typert.remote-client.d.ts +11 -0
  205. package/data/build/dist/typert.remote-client.js +7 -0
  206. package/dsh.bundle.patch +1 -1
  207. package/package.json +17 -5
  208. package/profile/README.md +4 -4
  209. package/profile/package.json +6 -4
  210. package/profile/scripts/check-dsh-install.mjs +2 -2
  211. package/profile/version.json +2 -2
  212. package/scripts/check-dsh-install.mjs +2 -2
  213. package/version.json +2 -2
@@ -2,6 +2,8 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { spawn, spawnSync } from 'node:child_process';
5
+ import { identifyModelProvider, normalizeProviderBaseUrl, thirdPartyProvider } from './provider-registry.js';
6
+ import { OfficialProviderSubscriptionService } from './official-provider-subscription.js';
5
7
  import { JsonRpcProcess } from './json-rpc-process.js';
6
8
  import { detectBinary } from './rpc-driver-utils.js';
7
9
  /** 统一读取 Codex、Claude Code、OpenCode 订阅摘要。所有原始凭据都留在 Host。 */
@@ -11,15 +13,21 @@ export class ProviderSubscriptionService {
11
13
  claudeCode;
12
14
  opencode;
13
15
  sub2api;
16
+ deepseek;
17
+ official;
14
18
  constructor(options = {}) {
15
19
  this.commandCode = options.commandCode;
16
20
  this.codex = new CodexSubscriptionService(options.codex);
17
21
  this.claudeCode = new ClaudeCodeSubscriptionService(options.claudeCode);
18
22
  this.opencode = new OpenCodeSubscriptionService(options.opencode);
19
23
  this.sub2api = new Sub2ApiUsageService(options.sub2api);
24
+ this.deepseek = new DeepseekSubscriptionService(options.deepseek);
25
+ this.official = new OfficialProviderSubscriptionService(options.official);
20
26
  }
21
- read(adapterId) {
22
- if (adapterId === 'codex' || adapterId === 'claude-code' || adapterId === 'dsh' || adapterId === 'grok' || adapterId === 'opencode') {
27
+ read(adapterId, providerId) {
28
+ if (adapterId === 'dsh')
29
+ return this.readDsh(providerId);
30
+ if (adapterId === 'codex' || adapterId === 'claude-code' || adapterId === 'grok' || adapterId === 'opencode') {
23
31
  return this.readSub2ApiFirst(adapterId);
24
32
  }
25
33
  switch (adapterId) {
@@ -30,22 +38,103 @@ export class ProviderSubscriptionService {
30
38
  default: return Promise.resolve(null);
31
39
  }
32
40
  }
41
+ async readDsh(providerId) {
42
+ const currentProviderId = providerId ?? resolveDshDefaultProvider();
43
+ const configuredSource = resolveDshProviderSource(currentProviderId);
44
+ const matchedProvider = identifyModelProvider({ name: currentProviderId, baseUrl: configuredSource?.baseUrl });
45
+ if (matchedProvider?.reader === 'openrouter-balance' || matchedProvider?.reader === 'minimax-usage' || matchedProvider?.reader === 'zai-usage' || matchedProvider?.reader === 'github-copilot-usage') {
46
+ return this.official.read(currentProviderId, configuredSource);
47
+ }
48
+ if (matchedProvider?.reader === 'deepseek-balance' && isOfficialDeepseekSource(currentProviderId, configuredSource))
49
+ return this.deepseek.read(configuredSource ?? undefined);
50
+ if (matchedProvider?.reader === 'sub2api')
51
+ return this.sub2api.read('dsh', currentProviderId);
52
+ // 已识别的官方提供商但没有可用读取器时,不尝试把官方 API 当成 Sub2API。
53
+ if (matchedProvider !== undefined && configuredSource !== null)
54
+ return null;
55
+ if (isThirdPartyDeepseekProvider(currentProviderId))
56
+ return this.sub2api.read('dsh', currentProviderId);
57
+ // 明确配置了第三方来源时,始终沿用 sub2api 适配器,避免把失败的上游误判为官方余额。
58
+ const hasThirdPartySource = this.sub2api.hasSource('dsh');
59
+ if (hasThirdPartySource)
60
+ return this.sub2api.read('dsh');
61
+ const official = await this.deepseek.read();
62
+ return official === null || matchedProvider === undefined ? official : withProvider(official, matchedProvider, configuredSource?.baseUrl);
63
+ }
33
64
  async readSub2ApiFirst(adapterId) {
34
65
  // 已配置第三方上游时,官方额度接口没有意义;即使 Sub2API 探测失败也必须隐藏,
35
66
  // 不能把旧的官方订阅窗口误显示成当前上游的用量。
36
- const hasThirdPartySource = this.sub2api.hasSource(adapterId);
37
- const upstream = await this.sub2api.read(adapterId);
38
- if (upstream !== null)
39
- return upstream;
67
+ const hasThirdPartySource = this.sub2api.hasThirdPartySource(adapterId);
40
68
  if (hasThirdPartySource)
41
- return null;
69
+ return this.sub2api.read(adapterId);
42
70
  if (adapterId === 'codex')
43
- return this.codex.read();
71
+ return this.codex.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'openai-codex' }), ''));
44
72
  if (adapterId === 'claude-code')
45
- return this.claudeCode.read();
73
+ return this.claudeCode.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'anthropic' }), ''));
46
74
  return null;
47
75
  }
48
76
  }
77
+ /** 读取官方 DeepSeek API 的余额;凭据只在 Host 内使用。 */
78
+ export class DeepseekSubscriptionService {
79
+ request;
80
+ timeoutMs;
81
+ configuredSources;
82
+ constructor(options = {}) {
83
+ this.request = options.fetch ?? fetch;
84
+ this.timeoutMs = options.timeoutMs ?? 8_000;
85
+ this.configuredSources = options.sources;
86
+ }
87
+ async read(source) {
88
+ const sources = source === undefined ? (this.configuredSources ?? resolveDeepseekSources()) : [source];
89
+ for (const candidate of sources) {
90
+ if (!isOfficialDeepseekUrl(candidate.baseUrl))
91
+ continue;
92
+ const result = await this.readSource(candidate);
93
+ if (result !== null)
94
+ return result;
95
+ }
96
+ return null;
97
+ }
98
+ async readSource(source) {
99
+ const baseUrl = deepseekApiRoot(source.baseUrl);
100
+ if (baseUrl === '' || source.apiKey.trim() === '')
101
+ return null;
102
+ const controller = new AbortController();
103
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
104
+ try {
105
+ const response = await this.request(`${baseUrl}/user/balance`, {
106
+ headers: {
107
+ Authorization: source.apiKey.trim().startsWith('Bearer ') ? source.apiKey.trim() : `Bearer ${source.apiKey.trim()}`,
108
+ Accept: 'application/json',
109
+ },
110
+ signal: controller.signal,
111
+ });
112
+ if (!response.ok)
113
+ return null;
114
+ const usage = normalizeDeepseekUsage(await response.json(), baseUrl);
115
+ if (usage === null)
116
+ return null;
117
+ return {
118
+ authenticated: true,
119
+ planType: null,
120
+ primary: null,
121
+ secondary: null,
122
+ monthly: null,
123
+ rateLimitReachedType: null,
124
+ resetCredits: null,
125
+ capturedAt: new Date().toISOString(),
126
+ provider: providerSummary(identifyModelProvider({ name: 'deepseek', baseUrl }), baseUrl, await readLogoDataUrl(identifyModelProvider({ name: 'deepseek', baseUrl })?.logoUrl ?? '', this.request, this.timeoutMs)),
127
+ deepseek: usage,
128
+ };
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ finally {
134
+ clearTimeout(timer);
135
+ }
136
+ }
137
+ }
49
138
  /** 通过 Provider 的上游 base URL 检测 Sub2API;只返回脱敏后的统计摘要。 */
50
139
  export class Sub2ApiUsageService {
51
140
  request;
@@ -56,23 +145,31 @@ export class Sub2ApiUsageService {
56
145
  this.timeoutMs = options.timeoutMs ?? 8_000;
57
146
  this.configuredSources = options.sources;
58
147
  }
59
- hasSource(adapterId) {
148
+ hasSource(adapterId, providerId) {
60
149
  const configured = this.configuredSources?.[adapterId];
61
150
  if (configured !== undefined)
62
151
  return Array.isArray(configured) ? configured.length > 0 : true;
63
- return resolveSub2ApiSources(adapterId).length > 0;
152
+ return resolveSub2ApiSources(adapterId, providerId).length > 0;
64
153
  }
65
- async read(adapterId) {
154
+ /** 只判断是否存在第三方上游;官方 Codex/Claude 源必须允许回退原生订阅读取器。 */
155
+ hasThirdPartySource(adapterId, providerId) {
66
156
  const configured = this.configuredSources?.[adapterId];
67
- const sources = configured === undefined ? resolveSub2ApiSources(adapterId) : Array.isArray(configured) ? configured : [configured];
157
+ const sources = configured === undefined
158
+ ? resolveSub2ApiSources(adapterId, providerId)
159
+ : Array.isArray(configured) ? configured : [configured];
160
+ return sources.some((source) => !isOfficialAgentSource(adapterId, source));
161
+ }
162
+ async read(adapterId, providerId) {
163
+ const configured = this.configuredSources?.[adapterId];
164
+ const sources = configured === undefined ? resolveSub2ApiSources(adapterId, providerId) : Array.isArray(configured) ? configured : [configured];
68
165
  for (const source of sources) {
69
- const result = await this.readSource(source);
166
+ const result = await this.readSource(source, providerId);
70
167
  if (result !== null)
71
168
  return result;
72
169
  }
73
170
  return null;
74
171
  }
75
- async readSource(source) {
172
+ async readSource(source, providerId) {
76
173
  const baseUrl = source.baseUrl.trim().replace(/\/+$/u, '');
77
174
  const usagePath = /\/v1$/u.test(baseUrl) ? '/usage' : '/v1/usage';
78
175
  const controller = new AbortController();
@@ -89,7 +186,9 @@ export class Sub2ApiUsageService {
89
186
  return null;
90
187
  const logoUrl = buildLogoUrl(baseUrl);
91
188
  const logoDataUrl = logoUrl === '' ? '' : await readLogoDataUrl(logoUrl, this.request, this.timeoutMs);
92
- return { authenticated: true, planType: usage.planName, primary: null, secondary: null, monthly: null, rateLimitReachedType: null, resetCredits: null, capturedAt: new Date().toISOString(), sub2api: { ...usage, logoUrl, logoDataUrl } };
189
+ const providerDefinition = identifyModelProvider({ name: providerId, baseUrl }) ?? thirdPartyProvider({ name: providerId, baseUrl });
190
+ const providerLogoDataUrl = logoDataUrl || await readLogoDataUrl(providerDefinition.logoUrl, this.request, this.timeoutMs);
191
+ return { authenticated: true, planType: usage.planName, primary: null, secondary: null, monthly: null, rateLimitReachedType: null, resetCredits: null, capturedAt: new Date().toISOString(), provider: providerSummary(providerDefinition, baseUrl, providerLogoDataUrl, 'sub2api'), sub2api: { ...usage, logoUrl, logoDataUrl } };
93
192
  }
94
193
  catch {
95
194
  return null;
@@ -124,7 +223,7 @@ export class CodexSubscriptionService {
124
223
  const controller = new AbortController();
125
224
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
126
225
  try {
127
- await rpc.request('initialize', { clientInfo: { name: 'codingns4dsh', version: '0.1.0' }, capabilities: {} }, { signal: controller.signal });
226
+ await rpc.request('initialize', { clientInfo: { name: 'codingns4dsh', version: '0.1.1' }, capabilities: {} }, { signal: controller.signal });
128
227
  rpc.notify('initialized', {});
129
228
  const result = await rpc.request('account/rateLimits/read', {}, { signal: controller.signal });
130
229
  return normalizeCodexSnapshot(result);
@@ -278,13 +377,10 @@ function hasSubscriptionWindow(value) {
278
377
  return value !== null && (value.primary !== null || value.secondary !== null || value.monthly !== null);
279
378
  }
280
379
  function hasThirdPartyCodexConfig(homeDirectory) {
281
- if (textValue(process.env.OPENAI_API_KEY) !== null)
282
- return true;
283
380
  const config = readText(join(homeDirectory, 'config.toml'));
284
- if (config === null)
285
- return false;
286
- const baseUrls = [...config.matchAll(/^\s*base_url\s*=\s*["']([^"']+)["']/gmu)].map((match) => match[1] ?? '');
287
- return baseUrls.some((value) => !isOfficialOpenAiUrl(value));
381
+ const configuredBaseUrl = config === null ? null : readCodexBaseUrl(config);
382
+ const environmentBaseUrl = textValue(process.env.OPENAI_BASE_URL);
383
+ return [configuredBaseUrl, environmentBaseUrl].some((value) => value !== null && !isOfficialOpenAiUrl(value));
288
384
  }
289
385
  function hasThirdPartyClaudeConfig(homeDirectory) {
290
386
  const baseUrl = textValue(process.env.ANTHROPIC_BASE_URL);
@@ -325,6 +421,23 @@ function readText(path) {
325
421
  return null;
326
422
  }
327
423
  }
424
+ /** 只读取 DSH 凭据文档中的指定引用,不把凭据内容写入任何返回结构。 */
425
+ function readDshCredential(name) {
426
+ const dshHome = process.env.DSH_HOME ?? join(homedir(), '.dsh');
427
+ const paths = [join(dshHome, '.credentials.yaml'), join(dshHome, '.env'), join(process.cwd(), '.env')];
428
+ const pattern = new RegExp(`^\\s*${name}\\s*:\\s*(?:"([^"]*)"|'([^']*)'|([^#\\s]+))`, 'mu');
429
+ const envPattern = new RegExp(`^\\s*${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^#\\s]+))`, 'mu');
430
+ for (const path of paths) {
431
+ const source = readText(path);
432
+ if (source === null)
433
+ continue;
434
+ const match = path.endsWith('.env') ? envPattern.exec(source) : pattern.exec(source);
435
+ const value = match?.[1] ?? match?.[2] ?? match?.[3];
436
+ if (typeof value === 'string' && value.trim() !== '')
437
+ return value.trim();
438
+ }
439
+ return null;
440
+ }
328
441
  function readJson(path) {
329
442
  if (!existsSync(path))
330
443
  return null;
@@ -350,7 +463,138 @@ function timestampValue(value) {
350
463
  return null;
351
464
  }
352
465
  function clamp(value) { return Math.max(0, Math.min(100, value)); }
353
- function resolveSub2ApiSources(adapterId) {
466
+ function normalizeDeepseekUsage(value, baseUrl) {
467
+ const root = recordValue(value);
468
+ if (root === null)
469
+ return null;
470
+ const rawBalances = root.balance_infos ?? root.balanceInfos ?? root.balances;
471
+ if (!Array.isArray(rawBalances))
472
+ return null;
473
+ const balances = rawBalances.flatMap((value) => {
474
+ const source = recordValue(value);
475
+ const currency = textValue(source?.currency);
476
+ const totalBalance = numberValue(source?.total_balance ?? source?.totalBalance);
477
+ if (currency === null || totalBalance === null)
478
+ return [];
479
+ return [{
480
+ currency,
481
+ totalBalance,
482
+ grantedBalance: numberValue(source?.granted_balance ?? source?.grantedBalance) ?? 0,
483
+ toppedUpBalance: numberValue(source?.topped_up_balance ?? source?.toppedUpBalance) ?? 0,
484
+ }];
485
+ });
486
+ if (balances.length === 0)
487
+ return null;
488
+ return {
489
+ upstreamUrl: sanitizeUpstreamUrl(baseUrl),
490
+ isAvailable: typeof root.is_available === 'boolean' ? root.is_available : typeof root.isAvailable === 'boolean' ? root.isAvailable : null,
491
+ balances,
492
+ };
493
+ }
494
+ function resolveDeepseekSources() {
495
+ const sources = [];
496
+ const add = (source) => {
497
+ if (source === null || source.baseUrl.trim() === '' || source.apiKey.trim() === '')
498
+ return;
499
+ if (!sources.some((item) => item.baseUrl === source.baseUrl && item.apiKey === source.apiKey))
500
+ sources.push(source);
501
+ };
502
+ const dshBaseUrl = textValue(process.env.DSH_BASE_URL);
503
+ const dshApiKey = textValue(process.env.DSH_API_KEY);
504
+ add(dshBaseUrl !== null && dshApiKey !== null ? { baseUrl: dshBaseUrl, apiKey: dshApiKey } : null);
505
+ const deepseekBaseUrl = textValue(process.env.DEEPSEEK_BASE_URL);
506
+ const deepseekApiKey = textValue(process.env.DEEPSEEK_API_KEY) ?? readDshCredential('DEEPSEEK_API_KEY');
507
+ add(deepseekApiKey === null ? null : { baseUrl: deepseekBaseUrl ?? 'https://api.deepseek.com', apiKey: deepseekApiKey });
508
+ add(envSource('DEEPSEEK_BASE_URL', 'DEEPSEEK_API_KEY'));
509
+ add(findConfigSource(readJson(join(homedir(), '.dsh', 'config.json'))));
510
+ return sources;
511
+ }
512
+ function resolveDshProviderSource(providerId) {
513
+ const normalized = providerId?.trim();
514
+ if (normalized === undefined || normalized === '')
515
+ return null;
516
+ const envPrefix = normalized.replace(/[^a-z0-9]+/giu, '_').toUpperCase();
517
+ const envBaseUrl = textValue(process.env[`${envPrefix}_BASE_URL`]);
518
+ const envApiKey = textValue(process.env[`${envPrefix}_API_KEY`]);
519
+ if (envBaseUrl !== null && envApiKey !== null)
520
+ return { baseUrl: envBaseUrl, apiKey: envApiKey };
521
+ const dshHome = process.env.DSH_HOME ?? join(homedir(), '.dsh');
522
+ for (const path of [join(dshHome, 'settings.yaml'), join(dshHome, 'settings.yaml.imported')]) {
523
+ const source = readDshProviderYaml(path, normalized);
524
+ if (source !== null)
525
+ return source;
526
+ }
527
+ return null;
528
+ }
529
+ function resolveDshDefaultProvider() {
530
+ const dshHome = process.env.DSH_HOME ?? join(homedir(), '.dsh');
531
+ for (const path of [join(dshHome, 'settings.yaml'), join(dshHome, 'settings.yaml.imported')]) {
532
+ const text = readText(path);
533
+ if (text === null)
534
+ continue;
535
+ const start = text.search(/^agent-default-model:\s*$/mu);
536
+ if (start < 0)
537
+ continue;
538
+ const rest = text.slice(start);
539
+ const endMatch = /^\S[^\n]*$/mu.exec(rest.slice(1));
540
+ const block = endMatch === null ? rest : rest.slice(0, endMatch.index + 1);
541
+ const provider = yamlScalar(block.match(/^\s+provider:\s*(.+)$/mu)?.[1]);
542
+ if (provider !== null)
543
+ return provider;
544
+ }
545
+ return undefined;
546
+ }
547
+ function readDshProviderYaml(path, providerId) {
548
+ const text = readText(path);
549
+ if (text === null)
550
+ return null;
551
+ const escaped = providerId.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
552
+ const start = text.search(new RegExp(`^ {4}${escaped}:\\s*$`, 'mu'));
553
+ if (start < 0)
554
+ return null;
555
+ const rest = text.slice(start);
556
+ const endMatch = /^ {4}\S[^\n]*$/mu.exec(rest.slice(1));
557
+ const block = endMatch === null ? rest : rest.slice(0, endMatch.index + 1);
558
+ const baseUrl = yamlScalar(block.match(/^\s+baseURL:\s*(.+)$/mu)?.[1]);
559
+ const keyRef = yamlScalar(block.match(/^\s+apiKeyEnv:\s*(.+)$/mu)?.[1]);
560
+ const apiKey = keyRef === null ? null : textValue(process.env[keyRef]) ?? readDshCredential(keyRef);
561
+ return baseUrl === null || apiKey === null ? null : { baseUrl, apiKey };
562
+ }
563
+ function yamlScalar(value) {
564
+ if (value === undefined)
565
+ return null;
566
+ const normalized = value.trim().replace(/^(['"])(.*)\1$/u, '$2');
567
+ return normalized === '' ? null : normalized;
568
+ }
569
+ function isOfficialDeepseekProvider(providerId) {
570
+ if (providerId === undefined)
571
+ return false;
572
+ return /^(?:deepseek(?:-official)?|official-deepseek)$/iu.test(providerId.trim());
573
+ }
574
+ /** 官方接口必须同时满足提供商名称和官方 baseURL,避免第三方代理冒充官方。 */
575
+ function isOfficialDeepseekSource(providerId, source) {
576
+ if (source === null || !isOfficialDeepseekUrl(source.baseUrl))
577
+ return false;
578
+ return providerId === undefined || isOfficialDeepseekProvider(providerId);
579
+ }
580
+ function isThirdPartyDeepseekProvider(providerId) {
581
+ return providerId !== undefined && providerId.trim() !== '' && !isOfficialDeepseekProvider(providerId);
582
+ }
583
+ function providerSummary(definition, baseUrl, logoDataUrl, capability) {
584
+ const resolved = definition ?? thirdPartyProvider({ baseUrl });
585
+ return {
586
+ id: resolved.id,
587
+ displayName: resolved.displayName,
588
+ baseUrl: sanitizeUpstreamUrl(baseUrl),
589
+ capability: capability ?? resolved.capability,
590
+ logoUrl: resolved.logoUrl,
591
+ ...(logoDataUrl === '' ? {} : { logoDataUrl }),
592
+ };
593
+ }
594
+ function withProvider(usage, definition, baseUrl = '') {
595
+ return { ...usage, provider: providerSummary(definition, baseUrl, '') };
596
+ }
597
+ function resolveSub2ApiSources(adapterId, providerId) {
354
598
  const sources = [];
355
599
  const add = (source) => {
356
600
  if (source === null || source.baseUrl.trim() === '' || source.apiKey.trim() === '')
@@ -361,9 +605,7 @@ function resolveSub2ApiSources(adapterId) {
361
605
  if (adapterId === 'codex') {
362
606
  const home = process.env.CODEX_HOME ?? join(homedir(), '.codex');
363
607
  const config = readText(join(home, 'config.toml')) ?? '';
364
- const baseUrl = config.match(/^\s*base_url\s*=\s*["']([^"']+)["']/mu)?.[1];
365
- const key = textValue(readJsonValue(join(home, 'auth.json'), 'OPENAI_API_KEY'));
366
- add(baseUrl === undefined || key === null ? null : { baseUrl, apiKey: key });
608
+ add(readCodexSource(home, config));
367
609
  }
368
610
  if (adapterId === 'claude-code') {
369
611
  const home = process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude');
@@ -385,10 +627,16 @@ function resolveSub2ApiSources(adapterId) {
385
627
  add(findConfigSource(config));
386
628
  }
387
629
  if (adapterId === 'dsh') {
388
- add(envSource('DSH_BASE_URL', 'DSH_API_KEY'));
389
- add(envSource('DEEPSEEK_BASE_URL', 'DEEPSEEK_API_KEY'));
630
+ const providerSource = resolveDshProviderSource(providerId);
631
+ add(providerSource);
632
+ // 已明确拿到当前提供商时只查询它的上游,不能把另一个配置项的余额冒充过来。
633
+ if (providerId !== undefined && !isOfficialDeepseekProvider(providerId))
634
+ return sources;
635
+ for (const source of resolveDeepseekSources()) {
636
+ if (!isOfficialDeepseekUrl(source.baseUrl))
637
+ add(source);
638
+ }
390
639
  add(envSource('OPENAI_BASE_URL', 'OPENAI_API_KEY'));
391
- add(findConfigSource(readJson(join(homedir(), '.dsh', 'config.json'))));
392
640
  }
393
641
  if (adapterId === 'grok') {
394
642
  add(envSource('GROK_BASE_URL', 'GROK_API_KEY'));
@@ -398,6 +646,74 @@ function resolveSub2ApiSources(adapterId) {
398
646
  }
399
647
  return sources;
400
648
  }
649
+ function readCodexSource(homeDirectory, config) {
650
+ const baseUrl = readCodexBaseUrl(config) ?? textValue(process.env.OPENAI_BASE_URL);
651
+ if (baseUrl === null)
652
+ return null;
653
+ const provider = tomlScalar(config, 'model_provider');
654
+ const section = provider === null ? '' : tomlSection(config, `model_providers.${provider}`);
655
+ const token = tomlScalar(section, 'experimental_bearer_token')
656
+ ?? tomlScalar(section, 'api_key')
657
+ ?? (() => {
658
+ const environmentName = tomlScalar(section, 'api_key_env');
659
+ return environmentName === null ? null : textValue(process.env[environmentName]);
660
+ })()
661
+ ?? textValue(readJsonValue(join(homeDirectory, 'auth.json'), 'OPENAI_API_KEY'))
662
+ ?? textValue(process.env.OPENAI_API_KEY);
663
+ return token === null ? null : { baseUrl, apiKey: token };
664
+ }
665
+ function readCodexBaseUrl(config) {
666
+ const provider = tomlScalar(config, 'model_provider');
667
+ const section = provider === null ? '' : tomlSection(config, `model_providers.${provider}`);
668
+ return tomlScalar(section, 'base_url') ?? tomlScalar(config, 'base_url');
669
+ }
670
+ function tomlSection(source, name) {
671
+ if (source === '')
672
+ return '';
673
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
674
+ const start = source.search(new RegExp(`^\\[${escaped}\\]\\s*$`, 'mu'));
675
+ if (start < 0)
676
+ return '';
677
+ const rest = source.slice(start);
678
+ const next = /^\[[^\n]+\]\s*$/mu.exec(rest.slice(1));
679
+ return next === null ? rest : rest.slice(0, next.index + 1);
680
+ }
681
+ function tomlScalar(source, key) {
682
+ if (source === '')
683
+ return null;
684
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
685
+ const match = new RegExp(`^\\s*${escaped}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^#\\s]+))`, 'mu').exec(source);
686
+ const value = match?.[1] ?? match?.[2] ?? match?.[3];
687
+ return textValue(value);
688
+ }
689
+ function isOfficialAgentSource(adapterId, source) {
690
+ if (adapterId === 'codex')
691
+ return isOfficialOpenAiUrl(source.baseUrl);
692
+ if (adapterId === 'claude-code')
693
+ return isOfficialAnthropicUrl(source.baseUrl);
694
+ return false;
695
+ }
696
+ function isOfficialDeepseekUrl(value) {
697
+ try {
698
+ const hostname = new URL(value).hostname.toLowerCase();
699
+ return hostname === 'api.deepseek.com' || hostname === 'api.deepseek.com.cn';
700
+ }
701
+ catch {
702
+ return false;
703
+ }
704
+ }
705
+ function deepseekApiRoot(value) {
706
+ try {
707
+ const url = new URL(value);
708
+ url.pathname = url.pathname.replace(/\/v1\/?$/u, '').replace(/\/+$/u, '');
709
+ url.search = '';
710
+ url.hash = '';
711
+ return url.toString().replace(/\/$/u, '');
712
+ }
713
+ catch {
714
+ return '';
715
+ }
716
+ }
401
717
  function defaultOpenCodeDataDirectory() {
402
718
  if (process.platform === 'win32')
403
719
  return join(process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming'), 'opencode');
@@ -28,6 +28,8 @@ export declare class CodingNsCliAdapterRegistry {
28
28
  private readonly modelTimers;
29
29
  private readonly modelGenerations;
30
30
  private readonly requestedModelCatalogs;
31
+ /** 已切到下一个 DSH step、但仍在继续产出 Provider 流的驱动迭代器。 */
32
+ private readonly segmentedTurns;
31
33
  private cacheGeneration;
32
34
  private disposed;
33
35
  constructor(drivers: readonly CodingNsCliDriver[], enabled?: Readonly<Record<string, boolean>>, options?: {
@@ -62,6 +64,8 @@ export declare class CodingNsCliAdapterRegistry {
62
64
  private findRememberedPreference;
63
65
  private rememberPreference;
64
66
  getSession(sessionId: string): CodingNsCliSessionConfig;
67
+ /** 只有驱动自己维护 Provider turn 边界时,Host 才能把它映射到 DSH step。 */
68
+ supportsSegmentedTurns(adapterId: CodingNsCliAdapterId): boolean;
65
69
  execute(input: CodingNsCliTurnInput & {
66
70
  readonly adapterId: CodingNsCliAdapterId;
67
71
  }): AsyncIterable<CodingNsAgentEvent>;
@@ -74,6 +78,8 @@ export declare class CodingNsCliAdapterRegistry {
74
78
  respondQuestion(sessionId: string, response: CodingNsAgentQuestionResponse): Promise<void>;
75
79
  steer(sessionId: string, prompt: string, followUp?: boolean): Promise<void>;
76
80
  interrupt(sessionId: string): Promise<void>;
81
+ /** 丢弃等待下一步的 Provider 流,避免取消后的旧正文进入新回合。 */
82
+ discardSegmentedTurn(sessionId: string): void;
77
83
  dispose(): Promise<void>;
78
84
  private readDetection;
79
85
  /** 同一适配器只允许一个探测;刷新失败时保留最后一次可用状态。 */