@jingyi0605/codingns4dsh 0.1.0 → 0.1.1

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 (216) 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/dsh-runtime-version.d.ts +3 -2
  149. package/data/build/dist/host/dsh-runtime-version.js +71 -2
  150. package/data/build/dist/host/features/auth.js +8 -5
  151. package/data/build/dist/host/features/debug.js +1 -1
  152. package/data/build/dist/host/features/git-management.d.ts +5 -0
  153. package/data/build/dist/host/features/git-management.js +290 -0
  154. package/data/build/dist/host/features/host-status.js +1 -1
  155. package/data/build/dist/host/features/index.d.ts +1 -0
  156. package/data/build/dist/host/features/index.js +3 -0
  157. package/data/build/dist/host/features/terminal-process.js +1 -1
  158. package/data/build/dist/host/features/terminal-status.d.ts +3 -1
  159. package/data/build/dist/host/features/terminal-status.js +3 -1
  160. package/data/build/dist/host/index.d.ts +7 -3
  161. package/data/build/dist/host/index.js +79 -15
  162. package/data/build/dist/host/lan-access-dsh.js +18 -5
  163. package/data/build/dist/host/native-session-bridge.d.ts +23 -1
  164. package/data/build/dist/host/native-session-bridge.js +157 -10
  165. package/data/build/dist/host/rpc-table.d.ts +1 -1
  166. package/data/build/dist/host/rpc.js +60 -10
  167. package/data/build/dist/host/session-migration-repair.d.ts +19 -0
  168. package/data/build/dist/host/session-migration-repair.js +395 -0
  169. package/data/build/dist/host/settings.d.ts +8 -1
  170. package/data/build/dist/host/settings.js +94 -3
  171. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
  172. package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
  173. package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
  174. package/data/build/dist/host/terminal/controller-factory.js +26 -7
  175. package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
  176. package/data/build/dist/host/terminal/runtime-manager.js +3 -0
  177. package/data/build/dist/host/terminal/startup.d.ts +2 -0
  178. package/data/build/dist/host/terminal/startup.js +2 -1
  179. package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
  180. package/data/build/dist/host/terminal/terminal-controller.js +101 -15
  181. package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
  182. package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
  183. package/data/build/dist/host/terminal/terminal-service.js +10 -0
  184. package/data/build/dist/index.d.ts +6 -2
  185. package/data/build/dist/index.js +8 -2
  186. package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
  187. package/data/build/dist/shared/contracts/config.d.ts +31 -1
  188. package/data/build/dist/shared/contracts/config.js +25 -0
  189. package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
  190. package/data/build/dist/shared/contracts/errors.d.ts +1 -1
  191. package/data/build/dist/shared/contracts/feature.d.ts +6 -0
  192. package/data/build/dist/shared/contracts/git.d.ts +82 -0
  193. package/data/build/dist/shared/contracts/git.js +3 -0
  194. package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
  195. package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
  196. package/data/build/dist/shared/contracts/version.d.ts +2 -2
  197. package/data/build/dist/shared/contracts/version.js +4 -6
  198. package/data/build/dist/shared/debug.d.ts +14 -0
  199. package/data/build/dist/shared/debug.js +53 -0
  200. package/data/build/dist/shared/index.d.ts +7 -4
  201. package/data/build/dist/shared/index.js +2 -1
  202. package/data/build/dist/transport/debug.d.ts +1 -1
  203. package/data/build/dist/transport/debug.js +3 -30
  204. package/data/build/dist/transport/dsh-session.js +3 -3
  205. package/data/build/dist/typert.host.d.ts +1 -1
  206. package/data/build/dist/typert.host.js +6 -3
  207. package/data/build/dist/typert.remote-client.d.ts +11 -0
  208. package/data/build/dist/typert.remote-client.js +7 -0
  209. package/dsh.bundle.patch +1 -1
  210. package/package.json +13 -5
  211. package/profile/README.md +4 -4
  212. package/profile/package.json +6 -4
  213. package/profile/scripts/check-dsh-install.mjs +2 -2
  214. package/profile/version.json +2 -2
  215. package/scripts/check-dsh-install.mjs +2 -2
  216. package/version.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { CodingNsControlApiError } from './control-api-client.js';
1
2
  /**
2
3
  * Host 侧认证协调器。
3
4
  * 只管理状态和依赖边界,不创建 HTTP、计时器或 WebRTC 资源。
@@ -16,6 +17,7 @@ export class CodingNsAuthSession {
16
17
  };
17
18
  accessToken = null;
18
19
  credential = null;
20
+ refreshPromise = null;
19
21
  constructor(client, credentials, controlBaseUrl) {
20
22
  this.client = client;
21
23
  this.credentials = credentials;
@@ -43,6 +45,21 @@ export class CodingNsAuthSession {
43
45
  getControlClient() {
44
46
  return this.client;
45
47
  }
48
+ /**
49
+ * 执行需要 access token 的请求;服务端拒绝旧 token 时只续期一次并重试。
50
+ * refresh token 仍然只留在 Host,调用方无需复制认证恢复逻辑。
51
+ */
52
+ async withAccessToken(operation) {
53
+ try {
54
+ return await operation(this.requireAccessToken());
55
+ }
56
+ catch (error) {
57
+ if (!(error instanceof CodingNsControlApiError) || error.status !== 401)
58
+ throw error;
59
+ await this.refresh();
60
+ return operation(this.requireAccessToken());
61
+ }
62
+ }
46
63
  /** 使用保存的 refresh token 恢复会话;没有凭据时保持 logged_out。 */
47
64
  async restore() {
48
65
  const credential = await this.credentials.read();
@@ -68,6 +85,12 @@ export class CodingNsAuthSession {
68
85
  }
69
86
  }
70
87
  async refresh() {
88
+ if (this.refreshPromise !== null)
89
+ return this.refreshPromise;
90
+ this.refreshPromise = this.refreshInternal().finally(() => { this.refreshPromise = null; });
91
+ return this.refreshPromise;
92
+ }
93
+ async refreshInternal() {
71
94
  const credential = this.credential ?? await this.credentials.read();
72
95
  if (!credential) {
73
96
  this.reset('logged_out');
@@ -94,24 +117,23 @@ export class CodingNsAuthSession {
94
117
  this.reset('logged_out');
95
118
  }
96
119
  async getDevices() {
97
- const token = this.requireAccessToken();
98
- const devices = await this.client.getDevices(token);
120
+ const devices = await this.withAccessToken((accessToken) => this.client.getDevices(accessToken));
99
121
  this.state = { ...this.state, currentDevice: devices.currentDevice };
100
122
  return devices;
101
123
  }
102
124
  async listHostBindings() {
103
- const response = await this.client.listHostBindings(this.requireAccessToken());
125
+ const response = await this.withAccessToken((accessToken) => this.client.listHostBindings(accessToken));
104
126
  const binding = response.bindings[0] ?? null;
105
127
  this.state = { ...this.state, binding };
106
128
  return response.bindings;
107
129
  }
108
130
  async bindHost(request) {
109
- const response = await this.client.bindHost(this.requireAccessToken(), request);
131
+ const response = await this.withAccessToken((accessToken) => this.client.bindHost(accessToken, request));
110
132
  this.state = { ...this.state, binding: response.binding };
111
133
  return response.binding;
112
134
  }
113
135
  async unbindHost(bindingId) {
114
- const response = await this.client.unbindHost(this.requireAccessToken(), bindingId);
136
+ const response = await this.withAccessToken((accessToken) => this.client.unbindHost(accessToken, bindingId));
115
137
  if (this.state.binding?.bindingId === response.binding.bindingId) {
116
138
  this.state = { ...this.state, binding: null };
117
139
  }
@@ -122,7 +144,7 @@ export class CodingNsAuthSession {
122
144
  const domain = tunnelDomain?.trim() || this.state.binding?.tunnelDomain;
123
145
  if (!domain)
124
146
  throw new Error('Codingns4DSH 尚未绑定 Host');
125
- return this.client.createSignalingTicket(this.requireAccessToken(), { tunnelDomain: domain });
147
+ return this.withAccessToken((accessToken) => this.client.createSignalingTicket(accessToken, { tunnelDomain: domain }));
126
148
  }
127
149
  async saveResponse(response) {
128
150
  this.accessToken = response.accessToken;
@@ -3,6 +3,8 @@ import type { CodingNsCliSessionProbeInput, CodingNsCliSessionProbeResult } from
3
3
  import { StandardStreamDriver, type StandardStreamDriverOptions } from './standard-stream-driver.js';
4
4
  export declare class ClaudeCodeDriver extends StandardStreamDriver {
5
5
  private readonly sessionRoots;
6
+ private readonly claudeConfigDir;
7
+ private readonly discoveryFetch;
6
8
  constructor(options?: StandardStreamDriverOptions);
7
9
  listModels(): Promise<import("../../shared/contracts/cli-adapter.js").CodingNsCliModelCatalog>;
8
10
  probeSession(input: CodingNsCliSessionProbeInput): Promise<CodingNsCliSessionProbeResult>;
@@ -1,20 +1,35 @@
1
1
  import { homedir } from 'node:os';
2
2
  import { basename, join } from 'node:path';
3
3
  import { StandardStreamDriver, emptyCatalog } from './standard-stream-driver.js';
4
- import { CLAUDE_CATALOG, enrichEfforts, isProviderDefaultModel } from './model-catalog.js';
4
+ import { CLAUDE_CATALOG, isProviderDefaultModel } from './model-catalog.js';
5
+ import { discoverClaudeModelCatalog } from './claude-model-options.js';
5
6
  import { probeStoredSession, readFirstJsonRecord } from './session-probe.js';
6
7
  import { firstToolText, isToolRecord, serializeToolValue } from './tool-observation.js';
7
8
  export class ClaudeCodeDriver extends StandardStreamDriver {
8
9
  sessionRoots;
10
+ claudeConfigDir;
11
+ discoveryFetch;
9
12
  constructor(options = {}) {
10
13
  super({ id: 'claude-code', name: 'Claude Code', protocol: 'stream-json', capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage'] }, { binaries: ['claude'] }, options);
11
14
  this.sessionRoots = options.sessionRoots ?? [join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'), 'projects')];
15
+ this.claudeConfigDir = options.claudeConfigDir;
16
+ this.discoveryFetch = options.fetch;
12
17
  }
13
18
  async listModels() {
14
- if (!(await this.detect()).installed)
19
+ const detected = await this.detect();
20
+ if (!detected.installed || detected.command === null)
15
21
  return emptyCatalog();
16
- const catalog = await super.listModels();
17
- return catalog.groups.length > 0 ? enrichEfforts(catalog, CLAUDE_CATALOG) : CLAUDE_CATALOG;
22
+ try {
23
+ return await discoverClaudeModelCatalog({
24
+ command: detected.command,
25
+ spawn: this.runSpawn,
26
+ ...(this.discoveryFetch ? { fetch: this.discoveryFetch } : {}),
27
+ ...(this.claudeConfigDir ? { configDir: this.claudeConfigDir } : {}),
28
+ });
29
+ }
30
+ catch {
31
+ return CLAUDE_CATALOG;
32
+ }
18
33
  }
19
34
  async probeSession(input) {
20
35
  return probeStoredSession(input, {
@@ -0,0 +1,14 @@
1
+ import { spawn } from 'node:child_process';
2
+ import type { CodingNsCliModelCatalog } from '../../shared/contracts/cli-adapter.js';
3
+ export interface ClaudeModelDiscoveryOptions {
4
+ readonly command: string;
5
+ readonly spawn?: typeof spawn;
6
+ readonly fetch?: typeof fetch;
7
+ readonly configDir?: string;
8
+ readonly workspaceDir?: string;
9
+ readonly env?: NodeJS.ProcessEnv;
10
+ readonly timeoutMs?: number;
11
+ }
12
+ /** 从 Claude CLI、兼容网关和本地配置合并真实模型目录。凭据只用于请求。 */
13
+ export declare function discoverClaudeModelCatalog(options: ClaudeModelDiscoveryOptions): Promise<CodingNsCliModelCatalog>;
14
+ //# sourceMappingURL=claude-model-options.d.ts.map
@@ -0,0 +1,157 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { CLAUDE_CATALOG, enrichEfforts } from './model-catalog.js';
6
+ import { terminateChildProcess } from './process-utils.js';
7
+ const DEFAULT_TIMEOUT_MS = 8_000;
8
+ const INITIALIZE_REQUEST_ID = 'codingns-model-discovery';
9
+ const DEFAULT_MODEL_ID = 'provider-default';
10
+ const ALIAS_ENV_KEYS = ['ANTHROPIC_DEFAULT_SONNET_MODEL', 'ANTHROPIC_DEFAULT_OPUS_MODEL', 'ANTHROPIC_DEFAULT_HAIKU_MODEL'];
11
+ /** 从 Claude CLI、兼容网关和本地配置合并真实模型目录。凭据只用于请求。 */
12
+ export async function discoverClaudeModelCatalog(options) {
13
+ const configDir = options.configDir ?? options.env?.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude');
14
+ const runtimeEnv = { ...process.env, ...(options.env ?? {}), ...readClaudeEnv(configDir, options.workspaceDir ?? process.cwd()) };
15
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
16
+ const tasks = [readInitializeModels(options.command, runtimeEnv, options.spawn ?? spawn, timeoutMs)];
17
+ const gatewayUrl = resolveModelsUrl(runtimeEnv.ANTHROPIC_BASE_URL);
18
+ if (gatewayUrl)
19
+ tasks.push(readGatewayModels(gatewayUrl, runtimeEnv, options.fetch ?? fetch, timeoutMs));
20
+ const settled = await Promise.allSettled(tasks);
21
+ const discovered = settled.filter((result) => result.status === 'fulfilled').flatMap((result) => result.value);
22
+ const configured = configuredModels(runtimeEnv);
23
+ const merged = mergeModels([...CLAUDE_CATALOG.groups[0].models, ...discovered, ...configured]);
24
+ return enrichEfforts({ groups: [{ id: 'claude', name: 'Claude', models: merged }], currentModel: null, currentEffort: null }, CLAUDE_CATALOG);
25
+ }
26
+ function readClaudeEnv(configDir, workspaceDir) {
27
+ const files = [join(configDir, 'settings.json'), join(configDir, 'settings.local.json'), join(workspaceDir, '.claude', 'settings.json'), join(workspaceDir, '.claude', 'settings.local.json')];
28
+ return files.reduce((result, file) => {
29
+ if (!existsSync(file))
30
+ return result;
31
+ try {
32
+ const env = JSON.parse(readFileSync(file, 'utf8')).env;
33
+ if (!env || typeof env !== 'object')
34
+ return result;
35
+ for (const [key, value] of Object.entries(env))
36
+ if (typeof value === 'string' && value.trim())
37
+ result[key] = value.trim();
38
+ }
39
+ catch { /* 损坏的配置不应阻塞模型发现。 */ }
40
+ return result;
41
+ }, {});
42
+ }
43
+ function configuredModels(env) {
44
+ const ids = [env.ANTHROPIC_MODEL, ...ALIAS_ENV_KEYS.map((key) => env[key])].filter((id) => Boolean(id?.trim())).map((id) => id.trim());
45
+ return ids.map((id) => ({ id, name: id, efforts: [] }));
46
+ }
47
+ function mergeModels(models) {
48
+ const result = [];
49
+ const seen = new Set();
50
+ for (const model of models) {
51
+ if (!model.id || seen.has(model.id))
52
+ continue;
53
+ seen.add(model.id);
54
+ result.push(model);
55
+ }
56
+ return result;
57
+ }
58
+ async function readInitializeModels(command, env, runSpawn, timeoutMs) {
59
+ const child = runSpawn(command, ['--print', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose', '--no-session-persistence', '--tools', '', '--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}'], { env: env, cwd: process.cwd(), stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true, shell: process.platform === 'win32' });
60
+ return new Promise((resolve, reject) => {
61
+ let stdout = '';
62
+ let stderr = '';
63
+ let settled = false;
64
+ const finish = (callback) => { if (settled)
65
+ return; settled = true; clearTimeout(timer); callback(); };
66
+ const timer = setTimeout(() => { terminateChildProcess(child); finish(() => reject(new Error('CLAUDE_MODEL_DISCOVERY_TIMEOUT'))); }, timeoutMs);
67
+ child.stdout.on('data', (chunk) => { stdout += String(chunk).slice(0, 1024 * 1024 - stdout.length); });
68
+ child.stderr.on('data', (chunk) => { stderr += String(chunk).slice(0, 16_384 - stderr.length); });
69
+ child.on('error', (error) => finish(() => reject(error)));
70
+ child.on('close', (code) => finish(() => {
71
+ if (code !== 0)
72
+ return reject(new Error(stderr.trim() || `CLAUDE_MODEL_DISCOVERY_FAILED:${code ?? 'unknown'}`));
73
+ try {
74
+ resolve(parseInitialize(stdout));
75
+ }
76
+ catch (error) {
77
+ reject(error);
78
+ }
79
+ }));
80
+ child.stdin.end(`${JSON.stringify({ type: 'control_request', request_id: INITIALIZE_REQUEST_ID, request: { subtype: 'initialize' } })}\n`);
81
+ });
82
+ }
83
+ function parseInitialize(output) {
84
+ const models = [];
85
+ for (const line of output.split(/\r?\n/u)) {
86
+ try {
87
+ const message = JSON.parse(line);
88
+ const raw = message.response?.response?.models;
89
+ if (message.type !== 'control_response' || message.response?.subtype !== 'success' || message.response.request_id !== INITIALIZE_REQUEST_ID || !Array.isArray(raw))
90
+ continue;
91
+ for (const item of raw) {
92
+ if (!item || typeof item !== 'object')
93
+ continue;
94
+ const value = item.value;
95
+ if (typeof value !== 'string' || !value.trim())
96
+ continue;
97
+ const id = value.trim() === 'default' ? DEFAULT_MODEL_ID : value.trim();
98
+ const displayName = item.displayName;
99
+ const efforts = item.supportedEffortLevels;
100
+ models.push({ id, name: typeof displayName === 'string' && displayName.trim() ? displayName.trim() : value.trim(), efforts: Array.isArray(efforts) ? efforts.filter((level) => typeof level === 'string') : [] });
101
+ }
102
+ }
103
+ catch { /* 忽略 CLI 输出中的非 JSON 行。 */ }
104
+ }
105
+ if (models.length === 0)
106
+ throw new Error('CLAUDE_MODELS_RESPONSE_EMPTY');
107
+ return mergeModels(models);
108
+ }
109
+ async function readGatewayModels(url, env, request, timeoutMs) {
110
+ const controller = new AbortController();
111
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
112
+ try {
113
+ const authToken = env.ANTHROPIC_AUTH_TOKEN ?? env.ANTHROPIC_API_KEY;
114
+ const headers = { accept: 'application/json', 'anthropic-version': '2023-06-01' };
115
+ if (env.ANTHROPIC_AUTH_TOKEN)
116
+ headers.authorization = `Bearer ${env.ANTHROPIC_AUTH_TOKEN}`;
117
+ if (authToken)
118
+ headers['x-api-key'] = authToken;
119
+ const response = await request(url, { headers, signal: controller.signal });
120
+ if (!response.ok)
121
+ throw new Error(`CLAUDE_MODEL_CATALOG_HTTP_${response.status}`);
122
+ const body = await response.json();
123
+ const raw = Array.isArray(body.data) ? body.data : Array.isArray(body.models) ? body.models : [];
124
+ const models = raw.flatMap((item) => {
125
+ if (!item || typeof item !== 'object')
126
+ return [];
127
+ const object = item;
128
+ const value = object.id ?? object.value;
129
+ if (typeof value !== 'string' || !value.trim())
130
+ return [];
131
+ const name = object.display_name ?? object.name;
132
+ return [{ id: value.trim(), name: typeof name === 'string' && name.trim() ? name.trim() : value.trim(), efforts: [] }];
133
+ });
134
+ if (models.length === 0)
135
+ throw new Error('CLAUDE_MODEL_CATALOG_RESPONSE_EMPTY');
136
+ return mergeModels(models);
137
+ }
138
+ finally {
139
+ clearTimeout(timer);
140
+ }
141
+ }
142
+ function resolveModelsUrl(baseUrl) {
143
+ if (!baseUrl?.trim())
144
+ return null;
145
+ try {
146
+ const url = new URL(baseUrl.trim());
147
+ const pathname = url.pathname.replace(/\/+$/u, '');
148
+ if (/\/v1\/models$/iu.test(pathname))
149
+ return url.toString();
150
+ url.pathname = /\/v1$/iu.test(pathname) ? `${pathname}/models` : `${pathname}/v1/models`;
151
+ return url.toString();
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ //# sourceMappingURL=claude-model-options.js.map
@@ -15,6 +15,7 @@ export declare class CodexAppServerDriver implements CodingNsCliDriver {
15
15
  readonly protocol: "json-rpc";
16
16
  readonly capabilities: readonly ["models", "stream", "resume", "interrupt", "tool-events", "reasoning", "usage", "permission", "questions", "steer"];
17
17
  };
18
+ readonly supportsSegmentedTurns = true;
18
19
  private readonly binaries;
19
20
  private readonly runSpawnSync;
20
21
  private readonly runSpawn;
@@ -31,6 +32,16 @@ export declare class CodexAppServerDriver implements CodingNsCliDriver {
31
32
  listModels(): Promise<CodingNsCliModelCatalog>;
32
33
  probeSession(input: CodingNsCliSessionProbeInput): Promise<CodingNsCliSessionProbeResult>;
33
34
  executeTurn(input: CodingNsCliTurnInput): AsyncIterable<CodingNsAgentEvent>;
35
+ /**
36
+ * 将一个 Codex turn 按工具完成点切成多个 DSH step。
37
+ *
38
+ * DSH 的 step 边界只能由 Agent Loop 提交;这里仅提前结束本次 llm/stream,
39
+ * 保留同一个 Codex turn 的通知队列,下一次 llm/stream 再继续消费它。
40
+ */
41
+ private executeSegmentedTurn;
42
+ private startSegmentedTurn;
43
+ private consumeSegment;
44
+ private closeSegmentedTurn;
34
45
  /** 将新输入 steer 到当前 Codex turn。 */
35
46
  steer(sessionId: string, prompt: string): Promise<void>;
36
47
  /** 中断当前 Codex turn,但保留 app-server 线程。 */
@@ -10,6 +10,7 @@ import { isQuestionEvent, questionAnswersRecord, readAgentQuestions } from './in
10
10
  /** Codex app-server 的 JSON-RPC 驱动,Host 只暴露统一文本流,不暴露线程和 token。 */
11
11
  export class CodexAppServerDriver {
12
12
  descriptor = { id: 'codex', name: 'Codex', protocol: 'json-rpc', capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage', 'permission', 'questions', 'steer'] };
13
+ supportsSegmentedTurns = true;
13
14
  binaries;
14
15
  runSpawnSync;
15
16
  runSpawn;
@@ -39,7 +40,7 @@ export class CodexAppServerDriver {
39
40
  const timer = setTimeout(() => controller.abort(), 12_000);
40
41
  try {
41
42
  await rpc.request('initialize', {
42
- clientInfo: { name: 'codingns4dsh', version: '0.1.0' },
43
+ clientInfo: { name: 'codingns4dsh', version: '0.1.1' },
43
44
  capabilities: {},
44
45
  }, { signal: controller.signal });
45
46
  rpc.notify('initialized', {});
@@ -71,6 +72,10 @@ export class CodexAppServerDriver {
71
72
  const command = this.cachedBinary ?? (await this.detect()).command;
72
73
  if (command === null)
73
74
  throw new Error('Codex 未安装');
75
+ if (input.splitToolSteps) {
76
+ yield* this.executeSegmentedTurn(input, command);
77
+ return;
78
+ }
74
79
  const session = await this.getSession(input, command);
75
80
  const rpc = session.rpc;
76
81
  try {
@@ -167,6 +172,143 @@ export class CodexAppServerDriver {
167
172
  }
168
173
  finally { /* app-server 在会话结束前保持连接。 */ }
169
174
  }
175
+ /**
176
+ * 将一个 Codex turn 按工具完成点切成多个 DSH step。
177
+ *
178
+ * DSH 的 step 边界只能由 Agent Loop 提交;这里仅提前结束本次 llm/stream,
179
+ * 保留同一个 Codex turn 的通知队列,下一次 llm/stream 再继续消费它。
180
+ */
181
+ async *executeSegmentedTurn(input, command) {
182
+ const session = await this.getSession(input, command);
183
+ const rpc = session.rpc;
184
+ let active;
185
+ try {
186
+ if (session.threadId === '') {
187
+ const thread = input.providerSessionId
188
+ ? await rpc.request('thread/resume', { threadId: input.providerSessionId, cwd: input.cwd ?? process.cwd(), ...(!isProviderDefaultModel(input.modelId) ? { model: input.modelId } : {}) }, { signal: input.signal, killOnAbort: false })
189
+ : await rpc.request('thread/start', { cwd: input.cwd ?? process.cwd(), ...(!isProviderDefaultModel(input.modelId) ? { model: input.modelId } : {}) }, { signal: input.signal, killOnAbort: false });
190
+ session.threadId = readId(thread) ?? input.providerSessionId ?? input.sessionId;
191
+ session.providerSessionId = session.threadId;
192
+ }
193
+ active = session.segmentedTurn ?? await this.startSegmentedTurn(session, input);
194
+ const isNew = session.segmentedTurn === undefined;
195
+ if (isNew)
196
+ session.segmentedTurn = active;
197
+ if (isNew)
198
+ yield { type: 'session-binding', providerSessionId: session.providerSessionId };
199
+ for await (const chunk of this.consumeSegment(active, input))
200
+ yield chunk;
201
+ if (active.done && session.segmentedTurn === active)
202
+ session.segmentedTurn = undefined;
203
+ }
204
+ catch (error) {
205
+ if (active !== undefined && !active.done)
206
+ this.closeSegmentedTurn(session, active);
207
+ if (!input.signal?.aborted)
208
+ throw error;
209
+ await this.interrupt(input.sessionId);
210
+ }
211
+ }
212
+ async startSegmentedTurn(session, input) {
213
+ const eventQueue = createCodexTurnEventQueue();
214
+ let activeTurnId = null;
215
+ let turnStartResolved = false;
216
+ const notificationsBeforeTurnStart = [];
217
+ const active = {
218
+ queue: eventQueue,
219
+ removeNotificationListener: () => undefined,
220
+ terminalReason: null,
221
+ done: false,
222
+ removeAbortListener: () => undefined,
223
+ };
224
+ const acceptNotification = (message, allowUnidentifiedTool) => {
225
+ if (!isCodexNotificationForTurn(message, session.threadId, activeTurnId, allowUnidentifiedTool))
226
+ return;
227
+ const turnId = readTurnId(message);
228
+ if (turnId !== null) {
229
+ activeTurnId = turnId;
230
+ session.turnId = turnId;
231
+ }
232
+ eventQueue.push(message);
233
+ const reason = readCodexTerminalReason(message);
234
+ if (reason !== null) {
235
+ active.terminalReason = reason;
236
+ eventQueue.close();
237
+ }
238
+ };
239
+ const onNotification = (message) => {
240
+ if (!turnStartResolved) {
241
+ notificationsBeforeTurnStart.push(message);
242
+ return;
243
+ }
244
+ acceptNotification(message, true);
245
+ };
246
+ active.removeNotificationListener = session.rpc.addNotificationListener(onNotification);
247
+ const onAbort = () => {
248
+ active.terminalReason = 'cancel';
249
+ eventQueue.close();
250
+ };
251
+ active.removeAbortListener = () => input.signal?.removeEventListener('abort', onAbort);
252
+ if (input.signal?.aborted)
253
+ onAbort();
254
+ else
255
+ input.signal?.addEventListener('abort', onAbort, { once: true });
256
+ try {
257
+ const response = await session.rpc.request('turn/start', {
258
+ threadId: session.threadId,
259
+ input: [{ type: 'text', text: input.prompt }],
260
+ ...(input.effortId ? { effort: input.effortId } : {}),
261
+ }, { signal: input.signal, killOnAbort: false });
262
+ const responseTurnId = readTurnId(response);
263
+ if (responseTurnId !== null) {
264
+ activeTurnId = responseTurnId;
265
+ session.turnId = responseTurnId;
266
+ }
267
+ turnStartResolved = true;
268
+ for (const message of notificationsBeforeTurnStart.splice(0))
269
+ acceptNotification(message, false);
270
+ const responseTerminal = buildCodexCompletionNotification(response, session.threadId);
271
+ if (responseTerminal !== null)
272
+ onNotification(responseTerminal);
273
+ return active;
274
+ }
275
+ catch (error) {
276
+ this.closeSegmentedTurn(session, active);
277
+ throw error;
278
+ }
279
+ }
280
+ async *consumeSegment(active, input) {
281
+ while (true) {
282
+ const next = await active.queue.next();
283
+ if (next.done) {
284
+ if (input.signal?.aborted)
285
+ throw new Error('请求已取消');
286
+ active.done = true;
287
+ active.removeAbortListener();
288
+ active.removeNotificationListener();
289
+ yield { type: 'finish', reason: active.terminalReason ?? 'stop' };
290
+ return;
291
+ }
292
+ if (input.signal?.aborted)
293
+ throw new Error('请求已取消');
294
+ const chunk = codexMessageToChunk(next.value);
295
+ if (chunk === null)
296
+ continue;
297
+ yield chunk;
298
+ if (chunk.type === 'tool-event' && (chunk.status === 'completed' || chunk.status === 'failed')) {
299
+ yield { type: 'step-boundary' };
300
+ return;
301
+ }
302
+ }
303
+ }
304
+ closeSegmentedTurn(session, active) {
305
+ active.done = true;
306
+ active.removeAbortListener();
307
+ active.removeNotificationListener();
308
+ active.queue.close();
309
+ if (session.segmentedTurn === active)
310
+ session.segmentedTurn = undefined;
311
+ }
170
312
  /** 将新输入 steer 到当前 Codex turn。 */
171
313
  async steer(sessionId, prompt) {
172
314
  const session = this.sessions.get(sessionId);
@@ -210,6 +352,8 @@ export class CodexAppServerDriver {
210
352
  }
211
353
  dispose() {
212
354
  for (const session of this.sessions.values()) {
355
+ if (session.segmentedTurn !== undefined)
356
+ this.closeSegmentedTurn(session, session.segmentedTurn);
213
357
  for (const resolve of session.pendingPermissions.values())
214
358
  resolve({ approved: false });
215
359
  session.pendingPermissions.clear();
@@ -226,6 +370,8 @@ export class CodexAppServerDriver {
226
370
  const previous = this.sessions.get(input.sessionId);
227
371
  if (previous !== undefined && previous.cwd === input.cwd)
228
372
  return previous;
373
+ if (previous?.segmentedTurn !== undefined)
374
+ this.closeSegmentedTurn(previous, previous.segmentedTurn);
229
375
  previous?.rpc.dispose();
230
376
  const rpc = new JsonRpcProcess({ command, args: ['app-server'], cwd: input.cwd, spawn: this.runSpawn });
231
377
  const session = {
@@ -236,10 +382,11 @@ export class CodexAppServerDriver {
236
382
  providerSessionId: input.providerSessionId ?? input.sessionId,
237
383
  pendingPermissions: new Map(),
238
384
  pendingQuestions: new Map(),
385
+ segmentedTurn: undefined,
239
386
  };
240
387
  this.processes.add(rpc);
241
388
  this.sessions.set(input.sessionId, session);
242
- await rpc.request('initialize', { clientInfo: { name: 'codingns4dsh', version: '0.1.0' }, capabilities: {} }, { signal: input.signal, killOnAbort: false });
389
+ await rpc.request('initialize', { clientInfo: { name: 'codingns4dsh', version: '0.1.1' }, capabilities: {} }, { signal: input.signal, killOnAbort: false });
243
390
  rpc.notify('initialized', {});
244
391
  rpc.setServerRequestHandler((request) => {
245
392
  const requestId = readRequestId(request);
@@ -314,10 +461,13 @@ function codexMessageToChunk(message) {
314
461
  if (requestId !== null)
315
462
  return { type: 'permission-request', requestId, kind: typeof params.kind === 'string' ? params.kind : 'unknown', ...(detail ? { detail } : {}) };
316
463
  }
317
- if (method.includes('agentMessage') || method.includes('message') && (type.includes('text') || type === ''))
318
- return text ? { type: 'text-delta', text } : null;
319
- if (method.includes('reason') || type.includes('reason'))
320
- return text ? { type: 'reasoning-delta', text } : null;
464
+ const messageId = firstToolText(params.itemId, item.id);
465
+ if (method.includes('agentMessage') || method.includes('message') && (type.includes('text') || type === '')) {
466
+ return text ? { type: 'text-delta', text, ...(messageId === undefined ? {} : { messageId }) } : null;
467
+ }
468
+ if (method.includes('reason') || type.includes('reason')) {
469
+ return text ? { type: 'reasoning-delta', text, ...(messageId === undefined ? {} : { messageId }) } : null;
470
+ }
321
471
  if (isCodexToolEvent(method, type)) {
322
472
  const name = firstToolText(item.name, item.toolName, item.tool, item.command !== undefined ? 'command_execution' : undefined, type);
323
473
  const callId = firstToolText(item.callId, item.call_id, item.toolCallId, item.id, params.itemId);
@@ -424,21 +574,23 @@ function createCodexTurnEventQueue() {
424
574
  const values = [];
425
575
  const waiters = [];
426
576
  let closed = false;
577
+ const next = () => {
578
+ const value = values.shift();
579
+ if (value !== undefined)
580
+ return Promise.resolve({ done: false, value });
581
+ if (closed)
582
+ return Promise.resolve({ done: true, value: undefined });
583
+ return new Promise((resolve) => waiters.push(resolve));
584
+ };
427
585
  return {
428
586
  iterable: {
429
587
  [Symbol.asyncIterator]() {
430
588
  return {
431
- next() {
432
- const value = values.shift();
433
- if (value !== undefined)
434
- return Promise.resolve({ done: false, value });
435
- if (closed)
436
- return Promise.resolve({ done: true, value: undefined });
437
- return new Promise((resolve) => waiters.push(resolve));
438
- },
589
+ next,
439
590
  };
440
591
  },
441
592
  },
593
+ next,
442
594
  push(message) {
443
595
  if (closed)
444
596
  return;
@@ -27,6 +27,8 @@ export interface CodingNsCliSessionProbeResult {
27
27
  */
28
28
  export interface CodingNsCliDriver {
29
29
  readonly descriptor: Omit<CodingNsCliAdapterDescriptor, 'installed' | 'enabled' | 'version' | 'command'>;
30
+ /** 驱动是否已经自行维护 Provider turn 的 step 边界。 */
31
+ readonly supportsSegmentedTurns?: boolean;
30
32
  detect(): Promise<Pick<CodingNsCliAdapterDescriptor, 'installed' | 'version' | 'command'>>;
31
33
  listModels(): Promise<CodingNsCliModelCatalog>;
32
34
  probeSession?(input: CodingNsCliSessionProbeInput): Promise<CodingNsCliSessionProbeResult>;
@@ -21,6 +21,11 @@ export declare class CodingNsDshMessageProjector {
21
21
  private readonly options;
22
22
  private readonly normalizer;
23
23
  private readonly toolHistory;
24
+ private reasoningIndex;
25
+ private textIndex;
26
+ private nextBlockIndex;
27
+ private reasoningText;
28
+ private textText;
24
29
  private finished;
25
30
  constructor(options: CodingNsDshMessageProjectorOptions);
26
31
  get isFinished(): boolean;
@@ -30,7 +35,17 @@ export declare class CodingNsDshMessageProjector {
30
35
  /** 把执行异常也交给同一投影层,确保错误正文和终态结构一致。 */
31
36
  fail(message: string, cancelled?: boolean): Promise<readonly CodingNsDshStreamChunk[]>;
32
37
  private flushUsage;
38
+ /** Provider 的 usage 到达时立即给 token-meter 一个非 surface 采样点。
39
+ *
40
+ * DSH 规范要求正式 usage 仍随 assistant/message 在 finish 前结算,因此这里不
41
+ * 提前向 LLM 流发送第二个 usage,只写入 assistant/attempt 供上下文计量投影使用。
42
+ */
43
+ private recordUsageSample;
33
44
  private project;
45
+ private appendDelta;
46
+ private openBlock;
47
+ private closeMessageBlock;
48
+ private externalToolChunk;
34
49
  private requestPermission;
35
50
  private requestQuestions;
36
51
  }