@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.
- package/README.en.md +306 -0
- package/README.md +190 -993
- package/data/build/dist/client/account-bar.d.ts +2 -2
- package/data/build/dist/client/account-bar.js +96 -39
- package/data/build/dist/client/bundle.js +13722 -3611
- package/data/build/dist/client/cli-catalog.js +20 -3
- package/data/build/dist/client/cli-slots.d.ts +5 -0
- package/data/build/dist/client/cli-slots.js +57 -7
- package/data/build/dist/client/debug/ui.d.ts +1 -1
- package/data/build/dist/client/debug/ui.js +62 -32
- package/data/build/dist/client/dsh-runtime-version.d.ts +9 -2
- package/data/build/dist/client/dsh-runtime-version.js +24 -2
- package/data/build/dist/client/features/cli-adapters.d.ts +1 -1
- package/data/build/dist/client/features/cli-adapters.js +19 -16
- package/data/build/dist/client/features/debug.js +2 -2
- package/data/build/dist/client/features/index.d.ts +2 -1
- package/data/build/dist/client/features/index.js +3 -1
- package/data/build/dist/client/features/lan-access-panel.d.ts +1 -1
- package/data/build/dist/client/features/lan-access-panel.js +8 -10
- package/data/build/dist/client/features/lan-access.js +1 -1
- package/data/build/dist/client/features/login-protection-panel.d.ts +1 -1
- package/data/build/dist/client/features/login-protection-panel.js +7 -9
- package/data/build/dist/client/features/reverse-proxy-panel.d.ts +1 -1
- package/data/build/dist/client/features/reverse-proxy-panel.js +99 -11
- package/data/build/dist/client/features/reverse-proxy.d.ts +1 -1
- package/data/build/dist/client/features/reverse-proxy.js +19 -18
- package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +1 -1
- package/data/build/dist/client/features/terminal-enhancement-panel.js +62 -55
- package/data/build/dist/client/features/terminal-enhancement.d.ts +1 -1
- package/data/build/dist/client/features/terminal-enhancement.js +3 -3
- package/data/build/dist/client/features/types.d.ts +14 -3
- package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +1 -1
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +31 -9
- package/data/build/dist/client/features/workspace-session-enhancement.js +72 -11
- package/data/build/dist/client/git-management.d.ts +16 -0
- package/data/build/dist/client/git-management.js +757 -0
- package/data/build/dist/client/index.d.ts +3 -2
- package/data/build/dist/client/index.js +152 -27
- package/data/build/dist/client/locale.js +91 -29
- package/data/build/dist/client/quick-phrase-layout.d.ts +49 -0
- package/data/build/dist/client/quick-phrase-layout.js +95 -0
- package/data/build/dist/client/quick-phrase-slot.d.ts +7 -0
- package/data/build/dist/client/quick-phrase-slot.js +370 -0
- package/data/build/dist/client/remote-web-context.js +1 -1
- package/data/build/dist/client/settings-bridge.d.ts +9 -6
- package/data/build/dist/client/settings-bridge.js +51 -27
- package/data/build/dist/client/settings-section.d.ts +3 -2
- package/data/build/dist/client/settings-section.js +33 -19
- package/data/build/dist/client/subscription-slot.d.ts +3 -0
- package/data/build/dist/client/subscription-slot.js +108 -16
- package/data/build/dist/client/terminal/model.d.ts +14 -2
- package/data/build/dist/client/terminal/model.js +109 -21
- package/data/build/dist/client/terminal/recovery.d.ts +50 -0
- package/data/build/dist/client/terminal/recovery.js +101 -0
- package/data/build/dist/client/terminal/ui.d.ts +2 -2
- package/data/build/dist/client/terminal/ui.js +49 -11
- package/data/build/dist/client/terminal/xterm-view.d.ts +2 -2
- package/data/build/dist/client/terminal/xterm-view.js +2 -1
- package/data/build/dist/client/theme.d.ts +6 -1
- package/data/build/dist/client/theme.js +32 -1
- package/data/build/dist/client/workspace-session-archive-dom.d.ts +12 -0
- package/data/build/dist/client/workspace-session-archive-dom.js +27 -5
- package/data/build/dist/client/workspace-session-visibility-dom.d.ts +30 -0
- package/data/build/dist/client/workspace-session-visibility-dom.js +604 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +50 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +139 -0
- package/data/build/dist/dsh-capabilities/client/conversation-adapter.d.ts +7 -0
- package/data/build/dist/dsh-capabilities/client/conversation-adapter.js +6 -0
- package/data/build/dist/dsh-capabilities/client/locale-adapter.d.ts +9 -0
- package/data/build/dist/dsh-capabilities/client/locale-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +10 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +14 -0
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +8 -0
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +16 -0
- package/data/build/dist/dsh-capabilities/client/sidebar-adapter.d.ts +7 -0
- package/data/build/dist/dsh-capabilities/client/sidebar-adapter.js +5 -0
- package/data/build/dist/dsh-capabilities/client/theme-adapter.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/client/theme-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/client/typert-adapter.d.ts +5 -0
- package/data/build/dist/dsh-capabilities/client/typert-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +19 -0
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +45 -0
- package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.d.ts +19 -0
- package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.js +13 -0
- package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +8 -0
- package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.js +47 -0
- package/data/build/dist/dsh-capabilities/host/typert-adapter.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/host/typert-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/index.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/index.js +6 -0
- package/data/build/dist/dsh-capabilities/matrix.d.ts +15 -0
- package/data/build/dist/dsh-capabilities/matrix.js +32 -0
- package/data/build/dist/dsh-capabilities/registry.d.ts +22 -0
- package/data/build/dist/dsh-capabilities/registry.js +196 -0
- package/data/build/dist/dsh-capabilities/routes.d.ts +10 -0
- package/data/build/dist/dsh-capabilities/routes.js +34 -0
- package/data/build/dist/dsh-capabilities/settings-store.d.ts +29 -0
- package/data/build/dist/dsh-capabilities/settings-store.js +5 -0
- package/data/build/dist/dsh-capabilities/types.d.ts +50 -0
- package/data/build/dist/dsh-capabilities/types.js +23 -0
- package/data/build/dist/features/registry.d.ts +8 -2
- package/data/build/dist/features/registry.js +104 -1
- package/data/build/dist/host/auth-session.d.ts +8 -1
- package/data/build/dist/host/auth-session.js +28 -6
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/claude-driver.js +19 -4
- package/data/build/dist/host/cli-adapters/claude-model-options.d.ts +14 -0
- package/data/build/dist/host/cli-adapters/claude-model-options.js +157 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +11 -0
- package/data/build/dist/host/cli-adapters/codex-driver.js +166 -14
- package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +15 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +120 -29
- package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +4 -1
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +25 -7
- package/data/build/dist/host/cli-adapters/feature.js +74 -8
- package/data/build/dist/host/cli-adapters/gemini-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/grok-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +4 -2
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -9
- package/data/build/dist/host/cli-adapters/kimi-driver.js +1 -1
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts +17 -0
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +88 -0
- package/data/build/dist/host/cli-adapters/legacy-session-settings.d.ts +24 -0
- package/data/build/dist/host/cli-adapters/legacy-session-settings.js +162 -0
- package/data/build/dist/host/cli-adapters/official-provider-subscription.d.ts +29 -0
- package/data/build/dist/host/cli-adapters/official-provider-subscription.js +361 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.js +97 -12
- package/data/build/dist/host/cli-adapters/pi-driver.js +69 -23
- package/data/build/dist/host/cli-adapters/provider-registry.d.ts +33 -0
- package/data/build/dist/host/cli-adapters/provider-registry.js +83 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +25 -3
- package/data/build/dist/host/cli-adapters/provider-subscription.js +346 -30
- package/data/build/dist/host/cli-adapters/registry.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/registry.js +170 -13
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +10 -0
- package/data/build/dist/host/cli-adapters/session-store.d.ts +7 -0
- package/data/build/dist/host/cli-adapters/session-store.js +39 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.js +22 -2
- package/data/build/dist/host/control-api-client.d.ts +3 -3
- package/data/build/dist/host/control-api-client.js +2 -1
- package/data/build/dist/host/credential-store.js +11 -1
- package/data/build/dist/host/dsh-device-runtime.d.ts +4 -0
- package/data/build/dist/host/dsh-device-runtime.js +20 -9
- package/data/build/dist/host/features/auth.js +8 -5
- package/data/build/dist/host/features/debug.js +1 -1
- package/data/build/dist/host/features/git-management.d.ts +5 -0
- package/data/build/dist/host/features/git-management.js +290 -0
- package/data/build/dist/host/features/host-status.js +1 -1
- package/data/build/dist/host/features/index.d.ts +1 -0
- package/data/build/dist/host/features/index.js +3 -0
- package/data/build/dist/host/features/terminal-process.js +1 -1
- package/data/build/dist/host/features/terminal-status.d.ts +3 -1
- package/data/build/dist/host/features/terminal-status.js +3 -1
- package/data/build/dist/host/index.d.ts +7 -3
- package/data/build/dist/host/index.js +76 -11
- package/data/build/dist/host/lan-access-dsh.js +18 -5
- package/data/build/dist/host/native-session-bridge.d.ts +23 -1
- package/data/build/dist/host/native-session-bridge.js +157 -10
- package/data/build/dist/host/rpc-table.d.ts +1 -1
- package/data/build/dist/host/rpc.js +60 -10
- package/data/build/dist/host/session-migration-repair.d.ts +19 -0
- package/data/build/dist/host/session-migration-repair.js +395 -0
- package/data/build/dist/host/settings.d.ts +8 -1
- package/data/build/dist/host/settings.js +94 -3
- package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
- package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
- package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
- package/data/build/dist/host/terminal/controller-factory.js +26 -7
- package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
- package/data/build/dist/host/terminal/runtime-manager.js +3 -0
- package/data/build/dist/host/terminal/startup.d.ts +2 -0
- package/data/build/dist/host/terminal/startup.js +2 -1
- package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
- package/data/build/dist/host/terminal/terminal-controller.js +101 -15
- package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
- package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
- package/data/build/dist/host/terminal/terminal-service.js +10 -0
- package/data/build/dist/index.d.ts +6 -2
- package/data/build/dist/index.js +8 -2
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
- package/data/build/dist/shared/contracts/config.d.ts +31 -1
- package/data/build/dist/shared/contracts/config.js +25 -0
- package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
- package/data/build/dist/shared/contracts/errors.d.ts +1 -1
- package/data/build/dist/shared/contracts/feature.d.ts +6 -0
- package/data/build/dist/shared/contracts/git.d.ts +82 -0
- package/data/build/dist/shared/contracts/git.js +3 -0
- package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
- package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
- package/data/build/dist/shared/contracts/version.d.ts +2 -2
- package/data/build/dist/shared/contracts/version.js +4 -6
- package/data/build/dist/shared/debug.d.ts +14 -0
- package/data/build/dist/shared/debug.js +53 -0
- package/data/build/dist/shared/index.d.ts +7 -4
- package/data/build/dist/shared/index.js +2 -1
- package/data/build/dist/transport/debug.d.ts +1 -1
- package/data/build/dist/transport/debug.js +3 -30
- package/data/build/dist/transport/dsh-session.js +3 -3
- package/data/build/dist/typert.host.js +4 -2
- package/data/build/dist/typert.remote-client.d.ts +11 -0
- package/data/build/dist/typert.remote-client.js +7 -0
- package/dsh.bundle.patch +1 -1
- package/package.json +17 -5
- package/profile/README.md +4 -4
- package/profile/package.json +6 -4
- package/profile/scripts/check-dsh-install.mjs +2 -2
- package/profile/version.json +2 -2
- package/scripts/check-dsh-install.mjs +2 -2
- 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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
19
|
+
const detected = await this.detect();
|
|
20
|
+
if (!detected.installed || detected.command === null)
|
|
15
21
|
return emptyCatalog();
|
|
16
|
-
|
|
17
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
}
|