@jingyi0605/codingns4dsh 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1114 -0
- package/assets/provider-icons/claude-code.png +0 -0
- package/assets/provider-icons/codex.png +0 -0
- package/assets/provider-icons/command-code.svg +5 -0
- package/assets/provider-icons/deepseek-harness.svg +9 -0
- package/assets/provider-icons/gemini.png +0 -0
- package/assets/provider-icons/grok.png +0 -0
- package/assets/provider-icons/kimi.png +0 -0
- package/assets/provider-icons/opencode.png +0 -0
- package/assets/provider-icons/pi.svg +7 -0
- package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +97 -0
- package/data/build/dist/bootstrap/dsh-connection-adapter.js +169 -0
- package/data/build/dist/bootstrap/index.d.ts +38 -0
- package/data/build/dist/bootstrap/index.js +44 -0
- package/data/build/dist/client/account-bar.d.ts +9 -0
- package/data/build/dist/client/account-bar.js +398 -0
- package/data/build/dist/client/bundle.js +26167 -0
- package/data/build/dist/client/cli-catalog.d.ts +24 -0
- package/data/build/dist/client/cli-catalog.js +108 -0
- package/data/build/dist/client/cli-slots.d.ts +38 -0
- package/data/build/dist/client/cli-slots.js +297 -0
- package/data/build/dist/client/debug/ui.d.ts +12 -0
- package/data/build/dist/client/debug/ui.js +451 -0
- package/data/build/dist/client/dsh-h5-bootstrap.d.ts +64 -0
- package/data/build/dist/client/dsh-h5-bootstrap.js +293 -0
- package/data/build/dist/client/dsh-runtime-version.d.ts +3 -0
- package/data/build/dist/client/dsh-runtime-version.js +11 -0
- package/data/build/dist/client/external-tool-stream.d.ts +24 -0
- package/data/build/dist/client/external-tool-stream.js +114 -0
- package/data/build/dist/client/features/cli-adapters.d.ts +7 -0
- package/data/build/dist/client/features/cli-adapters.js +215 -0
- package/data/build/dist/client/features/debug.d.ts +4 -0
- package/data/build/dist/client/features/debug.js +24 -0
- package/data/build/dist/client/features/index.d.ts +33 -0
- package/data/build/dist/client/features/index.js +44 -0
- package/data/build/dist/client/features/lan-access-panel.d.ts +5 -0
- package/data/build/dist/client/features/lan-access-panel.js +146 -0
- package/data/build/dist/client/features/lan-access.d.ts +9 -0
- package/data/build/dist/client/features/lan-access.js +30 -0
- package/data/build/dist/client/features/login-protection-panel.d.ts +5 -0
- package/data/build/dist/client/features/login-protection-panel.js +91 -0
- package/data/build/dist/client/features/login-protection-session.d.ts +6 -0
- package/data/build/dist/client/features/login-protection-session.js +25 -0
- package/data/build/dist/client/features/login-protection.d.ts +4 -0
- package/data/build/dist/client/features/login-protection.js +23 -0
- package/data/build/dist/client/features/reverse-proxy-panel.d.ts +9 -0
- package/data/build/dist/client/features/reverse-proxy-panel.js +159 -0
- package/data/build/dist/client/features/reverse-proxy.d.ts +12 -0
- package/data/build/dist/client/features/reverse-proxy.js +112 -0
- package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +5 -0
- package/data/build/dist/client/features/terminal-enhancement-panel.js +160 -0
- package/data/build/dist/client/features/terminal-enhancement.d.ts +4 -0
- package/data/build/dist/client/features/terminal-enhancement.js +24 -0
- package/data/build/dist/client/features/types.d.ts +58 -0
- package/data/build/dist/client/features/types.js +2 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +5 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +66 -0
- package/data/build/dist/client/features/workspace-session-enhancement.d.ts +4 -0
- package/data/build/dist/client/features/workspace-session-enhancement.js +101 -0
- package/data/build/dist/client/h5-bootstrap-entry.d.ts +2 -0
- package/data/build/dist/client/h5-bootstrap-entry.js +17 -0
- package/data/build/dist/client/index.d.ts +31 -0
- package/data/build/dist/client/index.js +98 -0
- package/data/build/dist/client/lan-access.d.ts +28 -0
- package/data/build/dist/client/lan-access.js +68 -0
- package/data/build/dist/client/locale.d.ts +20 -0
- package/data/build/dist/client/locale.js +393 -0
- package/data/build/dist/client/provider-icon-assets.d.ts +2 -0
- package/data/build/dist/client/provider-icon-assets.js +23 -0
- package/data/build/dist/client/provider-icons.d.ts +19 -0
- package/data/build/dist/client/provider-icons.js +39 -0
- package/data/build/dist/client/remote-web-context.d.ts +57 -0
- package/data/build/dist/client/remote-web-context.js +939 -0
- package/data/build/dist/client/session-adapter-cache.d.ts +18 -0
- package/data/build/dist/client/session-adapter-cache.js +84 -0
- package/data/build/dist/client/settings-bridge.d.ts +32 -0
- package/data/build/dist/client/settings-bridge.js +126 -0
- package/data/build/dist/client/settings-section.d.ts +30 -0
- package/data/build/dist/client/settings-section.js +93 -0
- package/data/build/dist/client/subscription-slot.d.ts +18 -0
- package/data/build/dist/client/subscription-slot.js +223 -0
- package/data/build/dist/client/terminal/index.d.ts +4 -0
- package/data/build/dist/client/terminal/index.js +3 -0
- package/data/build/dist/client/terminal/model.d.ts +116 -0
- package/data/build/dist/client/terminal/model.js +435 -0
- package/data/build/dist/client/terminal/styles.d.ts +21 -0
- package/data/build/dist/client/terminal/styles.js +51 -0
- package/data/build/dist/client/terminal/ui.d.ts +19 -0
- package/data/build/dist/client/terminal/ui.js +257 -0
- package/data/build/dist/client/terminal/xterm-view.d.ts +13 -0
- package/data/build/dist/client/terminal/xterm-view.js +213 -0
- package/data/build/dist/client/theme.d.ts +56 -0
- package/data/build/dist/client/theme.js +186 -0
- package/data/build/dist/client/workspace-session-archive-dom.d.ts +29 -0
- package/data/build/dist/client/workspace-session-archive-dom.js +525 -0
- package/data/build/dist/client/workspace-session-fiber.d.ts +3 -0
- package/data/build/dist/client/workspace-session-fiber.js +33 -0
- package/data/build/dist/client/workspace-session-logo-dom.d.ts +26 -0
- package/data/build/dist/client/workspace-session-logo-dom.js +128 -0
- package/data/build/dist/features/index.d.ts +3 -0
- package/data/build/dist/features/index.js +2 -0
- package/data/build/dist/features/registry.d.ts +74 -0
- package/data/build/dist/features/registry.js +382 -0
- package/data/build/dist/features/resource-scope/index.d.ts +39 -0
- package/data/build/dist/features/resource-scope/index.js +161 -0
- package/data/build/dist/host/auth-session.d.ts +42 -0
- package/data/build/dist/host/auth-session.js +186 -0
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts +14 -0
- package/data/build/dist/host/cli-adapters/claude-driver.js +103 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +45 -0
- package/data/build/dist/host/cli-adapters/codex-driver.js +534 -0
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +38 -0
- package/data/build/dist/host/cli-adapters/command-code-driver.js +394 -0
- package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts +19 -0
- package/data/build/dist/host/cli-adapters/command-code-subscription.js +126 -0
- package/data/build/dist/host/cli-adapters/driver.d.ts +41 -0
- package/data/build/dist/host/cli-adapters/driver.js +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +37 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +180 -0
- package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +48 -0
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +373 -0
- package/data/build/dist/host/cli-adapters/feature.d.ts +7 -0
- package/data/build/dist/host/cli-adapters/feature.js +335 -0
- package/data/build/dist/host/cli-adapters/gemini-driver.d.ts +24 -0
- package/data/build/dist/host/cli-adapters/gemini-driver.js +427 -0
- package/data/build/dist/host/cli-adapters/grok-driver.d.ts +38 -0
- package/data/build/dist/host/cli-adapters/grok-driver.js +372 -0
- package/data/build/dist/host/cli-adapters/http-sse-client.d.ts +22 -0
- package/data/build/dist/host/cli-adapters/http-sse-client.js +99 -0
- package/data/build/dist/host/cli-adapters/interaction-events.d.ts +11 -0
- package/data/build/dist/host/cli-adapters/interaction-events.js +80 -0
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +65 -0
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +256 -0
- package/data/build/dist/host/cli-adapters/kimi-driver.d.ts +21 -0
- package/data/build/dist/host/cli-adapters/kimi-driver.js +382 -0
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts +22 -0
- package/data/build/dist/host/cli-adapters/model-catalog.js +89 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +55 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.js +728 -0
- package/data/build/dist/host/cli-adapters/pi-driver.d.ts +43 -0
- package/data/build/dist/host/cli-adapters/pi-driver.js +312 -0
- package/data/build/dist/host/cli-adapters/process-utils.d.ts +16 -0
- package/data/build/dist/host/cli-adapters/process-utils.js +27 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +82 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.js +610 -0
- package/data/build/dist/host/cli-adapters/registry.d.ts +95 -0
- package/data/build/dist/host/cli-adapters/registry.js +659 -0
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts +24 -0
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +119 -0
- package/data/build/dist/host/cli-adapters/session-probe.d.ts +20 -0
- package/data/build/dist/host/cli-adapters/session-probe.js +139 -0
- package/data/build/dist/host/cli-adapters/session-store.d.ts +68 -0
- package/data/build/dist/host/cli-adapters/session-store.js +180 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +45 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js +205 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +25 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.js +83 -0
- package/data/build/dist/host/cli-adapters/tool-observation.d.ts +9 -0
- package/data/build/dist/host/cli-adapters/tool-observation.js +39 -0
- package/data/build/dist/host/control-api-client.d.ts +84 -0
- package/data/build/dist/host/control-api-client.js +130 -0
- package/data/build/dist/host/credential-store.d.ts +54 -0
- package/data/build/dist/host/credential-store.js +114 -0
- package/data/build/dist/host/debug.d.ts +79 -0
- package/data/build/dist/host/debug.js +295 -0
- package/data/build/dist/host/dsh-device-runtime.d.ts +39 -0
- package/data/build/dist/host/dsh-device-runtime.js +118 -0
- package/data/build/dist/host/dsh-gateway-feature.d.ts +5 -0
- package/data/build/dist/host/dsh-gateway-feature.js +55 -0
- package/data/build/dist/host/dsh-runtime-version.d.ts +12 -0
- package/data/build/dist/host/dsh-runtime-version.js +69 -0
- package/data/build/dist/host/features/auth.d.ts +11 -0
- package/data/build/dist/host/features/auth.js +273 -0
- package/data/build/dist/host/features/debug.d.ts +5 -0
- package/data/build/dist/host/features/debug.js +90 -0
- package/data/build/dist/host/features/host-status.d.ts +12 -0
- package/data/build/dist/host/features/host-status.js +54 -0
- package/data/build/dist/host/features/index.d.ts +19 -0
- package/data/build/dist/host/features/index.js +29 -0
- package/data/build/dist/host/features/lan-access-dsh.d.ts +8 -0
- package/data/build/dist/host/features/lan-access-dsh.js +45 -0
- package/data/build/dist/host/features/terminal-process.d.ts +5 -0
- package/data/build/dist/host/features/terminal-process.js +110 -0
- package/data/build/dist/host/features/terminal-status.d.ts +18 -0
- package/data/build/dist/host/features/terminal-status.js +56 -0
- package/data/build/dist/host/features/types.d.ts +41 -0
- package/data/build/dist/host/features/types.js +2 -0
- package/data/build/dist/host/index.d.ts +43 -0
- package/data/build/dist/host/index.js +135 -0
- package/data/build/dist/host/lan-access-dsh.d.ts +83 -0
- package/data/build/dist/host/lan-access-dsh.js +831 -0
- package/data/build/dist/host/native-session-bridge.d.ts +130 -0
- package/data/build/dist/host/native-session-bridge.js +317 -0
- package/data/build/dist/host/relay-tunnel-runtime.d.ts +97 -0
- package/data/build/dist/host/relay-tunnel-runtime.js +361 -0
- package/data/build/dist/host/remote-web-runtime.d.ts +78 -0
- package/data/build/dist/host/remote-web-runtime.js +559 -0
- package/data/build/dist/host/rpc-table.d.ts +32 -0
- package/data/build/dist/host/rpc-table.js +47 -0
- package/data/build/dist/host/rpc.d.ts +18 -0
- package/data/build/dist/host/rpc.js +248 -0
- package/data/build/dist/host/settings.d.ts +19 -0
- package/data/build/dist/host/settings.js +80 -0
- package/data/build/dist/host/terminal/attachment-registry.d.ts +17 -0
- package/data/build/dist/host/terminal/attachment-registry.js +49 -0
- package/data/build/dist/host/terminal/backends/conpty-backend.d.ts +39 -0
- package/data/build/dist/host/terminal/backends/conpty-backend.js +160 -0
- package/data/build/dist/host/terminal/backends/local-pty-backend.d.ts +36 -0
- package/data/build/dist/host/terminal/backends/local-pty-backend.js +178 -0
- package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +40 -0
- package/data/build/dist/host/terminal/backends/tmux-backend.js +191 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-client.d.ts +27 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-client.js +154 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-process.d.ts +10 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-process.js +147 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-protocol.d.ts +92 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-protocol.js +134 -0
- package/data/build/dist/host/terminal/controller-factory.d.ts +37 -0
- package/data/build/dist/host/terminal/controller-factory.js +82 -0
- package/data/build/dist/host/terminal/index.d.ts +17 -0
- package/data/build/dist/host/terminal/index.js +17 -0
- package/data/build/dist/host/terminal/runtime-adapter.d.ts +77 -0
- package/data/build/dist/host/terminal/runtime-adapter.js +18 -0
- package/data/build/dist/host/terminal/runtime-manager.d.ts +41 -0
- package/data/build/dist/host/terminal/runtime-manager.js +179 -0
- package/data/build/dist/host/terminal/shell-detection.d.ts +23 -0
- package/data/build/dist/host/terminal/shell-detection.js +105 -0
- package/data/build/dist/host/terminal/startup.d.ts +33 -0
- package/data/build/dist/host/terminal/startup.js +100 -0
- package/data/build/dist/host/terminal/terminal-controller.d.ts +78 -0
- package/data/build/dist/host/terminal/terminal-controller.js +324 -0
- package/data/build/dist/host/terminal/terminal-process-service.d.ts +30 -0
- package/data/build/dist/host/terminal/terminal-process-service.js +194 -0
- package/data/build/dist/host/terminal/terminal-process-store.d.ts +45 -0
- package/data/build/dist/host/terminal/terminal-process-store.js +183 -0
- package/data/build/dist/host/terminal/terminal-service.d.ts +87 -0
- package/data/build/dist/host/terminal/terminal-service.js +528 -0
- package/data/build/dist/host/terminal/terminal-store.d.ts +49 -0
- package/data/build/dist/host/terminal/terminal-store.js +242 -0
- package/data/build/dist/index.d.ts +37 -0
- package/data/build/dist/index.js +26 -0
- package/data/build/dist/shared/contracts/auth.d.ts +122 -0
- package/data/build/dist/shared/contracts/auth.js +8 -0
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +192 -0
- package/data/build/dist/shared/contracts/cli-adapter.js +2 -0
- package/data/build/dist/shared/contracts/command-code.d.ts +5 -0
- package/data/build/dist/shared/contracts/command-code.js +2 -0
- package/data/build/dist/shared/contracts/config.d.ts +113 -0
- package/data/build/dist/shared/contracts/config.js +86 -0
- package/data/build/dist/shared/contracts/debug-config.d.ts +28 -0
- package/data/build/dist/shared/contracts/debug-config.js +78 -0
- package/data/build/dist/shared/contracts/debug.d.ts +75 -0
- package/data/build/dist/shared/contracts/debug.js +121 -0
- package/data/build/dist/shared/contracts/dsh-device.d.ts +72 -0
- package/data/build/dist/shared/contracts/dsh-device.js +2 -0
- package/data/build/dist/shared/contracts/errors.d.ts +33 -0
- package/data/build/dist/shared/contracts/errors.js +41 -0
- package/data/build/dist/shared/contracts/feature.d.ts +71 -0
- package/data/build/dist/shared/contracts/feature.js +2 -0
- package/data/build/dist/shared/contracts/host-status.d.ts +14 -0
- package/data/build/dist/shared/contracts/host-status.js +2 -0
- package/data/build/dist/shared/contracts/lan-access-dsh.d.ts +30 -0
- package/data/build/dist/shared/contracts/lan-access-dsh.js +2 -0
- package/data/build/dist/shared/contracts/peer-host.d.ts +37 -0
- package/data/build/dist/shared/contracts/peer-host.js +9 -0
- package/data/build/dist/shared/contracts/signaling.d.ts +67 -0
- package/data/build/dist/shared/contracts/signaling.js +2 -0
- package/data/build/dist/shared/contracts/subscription.d.ts +69 -0
- package/data/build/dist/shared/contracts/subscription.js +2 -0
- package/data/build/dist/shared/contracts/terminal-process.d.ts +65 -0
- package/data/build/dist/shared/contracts/terminal-process.js +2 -0
- package/data/build/dist/shared/contracts/terminal.d.ts +127 -0
- package/data/build/dist/shared/contracts/terminal.js +2 -0
- package/data/build/dist/shared/contracts/transport.d.ts +45 -0
- package/data/build/dist/shared/contracts/transport.js +3 -0
- package/data/build/dist/shared/contracts/version.d.ts +19 -0
- package/data/build/dist/shared/contracts/version.js +74 -0
- package/data/build/dist/shared/index.d.ts +22 -0
- package/data/build/dist/shared/index.js +9 -0
- package/data/build/dist/transport/carrier.d.ts +38 -0
- package/data/build/dist/transport/carrier.js +268 -0
- package/data/build/dist/transport/debug.d.ts +22 -0
- package/data/build/dist/transport/debug.js +57 -0
- package/data/build/dist/transport/dsh-envelope.d.ts +41 -0
- package/data/build/dist/transport/dsh-envelope.js +107 -0
- package/data/build/dist/transport/dsh-gateway.d.ts +64 -0
- package/data/build/dist/transport/dsh-gateway.js +254 -0
- package/data/build/dist/transport/dsh-session.d.ts +55 -0
- package/data/build/dist/transport/dsh-session.js +256 -0
- package/data/build/dist/transport/dsh-transport.d.ts +57 -0
- package/data/build/dist/transport/dsh-transport.js +125 -0
- package/data/build/dist/transport/frame.d.ts +132 -0
- package/data/build/dist/transport/frame.js +168 -0
- package/data/build/dist/transport/index.d.ts +11 -0
- package/data/build/dist/transport/index.js +11 -0
- package/data/build/dist/transport/multiplexer.d.ts +63 -0
- package/data/build/dist/transport/multiplexer.js +323 -0
- package/data/build/dist/transport/webrtc-client.d.ts +65 -0
- package/data/build/dist/transport/webrtc-client.js +255 -0
- package/data/build/dist/transport/webrtc-host.d.ts +58 -0
- package/data/build/dist/transport/webrtc-host.js +252 -0
- package/data/build/dist/typert.host.d.ts +25 -0
- package/data/build/dist/typert.host.js +150 -0
- package/dsh.bundle.patch +12 -0
- package/package.json +135 -0
- package/profile/README.md +27 -0
- package/profile/cordis.patch.yml +2 -0
- package/profile/package.json +20 -0
- package/profile/scripts/check-dsh-install.mjs +71 -0
- package/profile/version.json +5 -0
- package/scripts/check-dsh-install.mjs +88 -0
- package/version.json +6 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
import { JsonRpcProcess } from './json-rpc-process.js';
|
|
5
|
+
import { detectBinary, emptyCatalog, isRecord, textValue, usageChunk } from './rpc-driver-utils.js';
|
|
6
|
+
import { CODEX_CATALOG, isProviderDefaultModel } from './model-catalog.js';
|
|
7
|
+
import { probeStoredSession, readFirstJsonRecord } from './session-probe.js';
|
|
8
|
+
import { firstToolText, normalizeToolStatus, serializeToolValue } from './tool-observation.js';
|
|
9
|
+
import { isQuestionEvent, questionAnswersRecord, readAgentQuestions } from './interaction-events.js';
|
|
10
|
+
/** Codex app-server 的 JSON-RPC 驱动,Host 只暴露统一文本流,不暴露线程和 token。 */
|
|
11
|
+
export class CodexAppServerDriver {
|
|
12
|
+
descriptor = { id: 'codex', name: 'Codex', protocol: 'json-rpc', capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage', 'permission', 'questions', 'steer'] };
|
|
13
|
+
binaries;
|
|
14
|
+
runSpawnSync;
|
|
15
|
+
runSpawn;
|
|
16
|
+
sessionRoots;
|
|
17
|
+
cachedBinary = null;
|
|
18
|
+
processes = new Set();
|
|
19
|
+
sessions = new Map();
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.binaries = options.binaries ?? ['codex'];
|
|
22
|
+
this.runSpawnSync = options.spawnSync ?? spawnSync;
|
|
23
|
+
this.runSpawn = options.spawn ?? spawn;
|
|
24
|
+
const codexHome = process.env.CODEX_HOME ?? join(homedir(), '.codex');
|
|
25
|
+
this.sessionRoots = options.sessionRoots ?? [join(codexHome, 'sessions'), join(codexHome, 'archived_sessions')];
|
|
26
|
+
}
|
|
27
|
+
async detect() {
|
|
28
|
+
const result = await detectBinary({ binaries: this.binaries, spawnSync: this.runSpawnSync });
|
|
29
|
+
if (result.installed)
|
|
30
|
+
this.cachedBinary = result.command;
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
async listModels() {
|
|
34
|
+
const command = this.cachedBinary ?? (await this.detect()).command;
|
|
35
|
+
if (command === null)
|
|
36
|
+
return emptyCatalog();
|
|
37
|
+
const rpc = new JsonRpcProcess({ command, args: ['app-server'], spawn: this.runSpawn });
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timer = setTimeout(() => controller.abort(), 12_000);
|
|
40
|
+
try {
|
|
41
|
+
await rpc.request('initialize', {
|
|
42
|
+
clientInfo: { name: 'codingns4dsh', version: '0.1.0' },
|
|
43
|
+
capabilities: {},
|
|
44
|
+
}, { signal: controller.signal });
|
|
45
|
+
rpc.notify('initialized', {});
|
|
46
|
+
// config/read 让 Codex 完成一次配置加载;model/list 才返回带 effort 元数据的目录。
|
|
47
|
+
await rpc.request('config/read', {}, { signal: controller.signal });
|
|
48
|
+
const response = await rpc.request('model/list', {}, { signal: controller.signal });
|
|
49
|
+
const catalog = parseCodexCatalog(response);
|
|
50
|
+
return catalog.groups.length > 0 ? catalog : CODEX_CATALOG;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return CODEX_CATALOG;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
rpc.dispose();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async probeSession(input) {
|
|
61
|
+
return probeStoredSession(input, {
|
|
62
|
+
roots: this.sessionRoots,
|
|
63
|
+
matches: (path, entry, id) => entry.isFile() && basename(path).endsWith(`${id}.jsonl`),
|
|
64
|
+
validate: async (path, id) => {
|
|
65
|
+
const record = await readFirstJsonRecord(path);
|
|
66
|
+
return isRecord(record?.payload) && record.payload.id === id;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async *executeTurn(input) {
|
|
71
|
+
const command = this.cachedBinary ?? (await this.detect()).command;
|
|
72
|
+
if (command === null)
|
|
73
|
+
throw new Error('Codex 未安装');
|
|
74
|
+
const session = await this.getSession(input, command);
|
|
75
|
+
const rpc = session.rpc;
|
|
76
|
+
try {
|
|
77
|
+
if (session.threadId === '') {
|
|
78
|
+
const thread = input.providerSessionId
|
|
79
|
+
? await rpc.request('thread/resume', { threadId: input.providerSessionId, cwd: input.cwd ?? process.cwd(), ...(!isProviderDefaultModel(input.modelId) ? { model: input.modelId } : {}) }, { signal: input.signal, killOnAbort: false })
|
|
80
|
+
: await rpc.request('thread/start', { cwd: input.cwd ?? process.cwd(), ...(!isProviderDefaultModel(input.modelId) ? { model: input.modelId } : {}) }, { signal: input.signal, killOnAbort: false });
|
|
81
|
+
session.threadId = readId(thread) ?? input.providerSessionId ?? input.sessionId;
|
|
82
|
+
session.providerSessionId = session.threadId;
|
|
83
|
+
}
|
|
84
|
+
yield { type: 'session-binding', providerSessionId: session.providerSessionId };
|
|
85
|
+
const eventQueue = createCodexTurnEventQueue();
|
|
86
|
+
let activeTurnId = null;
|
|
87
|
+
let turnStartResolved = false;
|
|
88
|
+
const notificationsBeforeTurnStart = [];
|
|
89
|
+
let terminalReason = null;
|
|
90
|
+
const acceptNotification = (message, allowUnidentifiedTool) => {
|
|
91
|
+
if (!isCodexNotificationForTurn(message, session.threadId, activeTurnId, allowUnidentifiedTool))
|
|
92
|
+
return;
|
|
93
|
+
const turnId = readTurnId(message);
|
|
94
|
+
if (turnId !== null) {
|
|
95
|
+
activeTurnId = turnId;
|
|
96
|
+
session.turnId = turnId;
|
|
97
|
+
}
|
|
98
|
+
eventQueue.push(message);
|
|
99
|
+
const reason = readCodexTerminalReason(message);
|
|
100
|
+
if (reason !== null) {
|
|
101
|
+
terminalReason = reason;
|
|
102
|
+
eventQueue.close();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const onNotification = (message) => {
|
|
106
|
+
// thread/resume 后,Codex 可能把旧回合的 item 通知迟到到达,并且
|
|
107
|
+
// 与本次 turn/start 的响应交错。响应返回前不能把这些通知当成当前回合,
|
|
108
|
+
// 否则旧工具历史会在流式界面被追加到当前消息末尾。
|
|
109
|
+
if (!turnStartResolved) {
|
|
110
|
+
notificationsBeforeTurnStart.push(message);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
acceptNotification(message, true);
|
|
114
|
+
};
|
|
115
|
+
// 与父仓库 CodexRuntimeAdapter 一致:监听器必须先于 turn/start 注册,
|
|
116
|
+
// 否则响应前到达的 turn/started 或文本通知也会丢失。
|
|
117
|
+
const removeNotificationListener = rpc.addNotificationListener(onNotification);
|
|
118
|
+
const onAbort = () => {
|
|
119
|
+
terminalReason = 'cancel';
|
|
120
|
+
eventQueue.close();
|
|
121
|
+
};
|
|
122
|
+
if (input.signal?.aborted)
|
|
123
|
+
onAbort();
|
|
124
|
+
else
|
|
125
|
+
input.signal?.addEventListener('abort', onAbort, { once: true });
|
|
126
|
+
try {
|
|
127
|
+
const response = await rpc.request('turn/start', {
|
|
128
|
+
threadId: session.threadId,
|
|
129
|
+
input: [{ type: 'text', text: input.prompt }],
|
|
130
|
+
...(input.effortId ? { effort: input.effortId } : {}),
|
|
131
|
+
}, { signal: input.signal, killOnAbort: false });
|
|
132
|
+
const responseTurnId = readTurnId(response);
|
|
133
|
+
if (responseTurnId !== null) {
|
|
134
|
+
activeTurnId = responseTurnId;
|
|
135
|
+
session.turnId = responseTurnId;
|
|
136
|
+
}
|
|
137
|
+
turnStartResolved = true;
|
|
138
|
+
// 响应前缓存的通知可能来自 thread/resume 的旧历史。没有 turnId 的工具
|
|
139
|
+
// 事件无法证明属于本次回合,因此只能丢弃;有明确 turnId 的事件仍按
|
|
140
|
+
// 原有规则处理。
|
|
141
|
+
for (const message of notificationsBeforeTurnStart.splice(0))
|
|
142
|
+
acceptNotification(message, false);
|
|
143
|
+
// 某些 app-server 会直接在 turn/start 响应中返回终态。父仓库把它
|
|
144
|
+
// 归一化成 turn/completed,这里复用同一规则,避免永久等待通知。
|
|
145
|
+
const responseTerminal = buildCodexCompletionNotification(response, session.threadId);
|
|
146
|
+
if (responseTerminal !== null)
|
|
147
|
+
onNotification(responseTerminal);
|
|
148
|
+
for await (const message of eventQueue.iterable) {
|
|
149
|
+
const chunk = codexMessageToChunk(message);
|
|
150
|
+
if (chunk !== null)
|
|
151
|
+
yield chunk;
|
|
152
|
+
}
|
|
153
|
+
if (input.signal?.aborted)
|
|
154
|
+
await this.interrupt(input.sessionId);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (!input.signal?.aborted)
|
|
158
|
+
throw error;
|
|
159
|
+
await this.interrupt(input.sessionId);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
input.signal?.removeEventListener('abort', onAbort);
|
|
163
|
+
removeNotificationListener();
|
|
164
|
+
eventQueue.close();
|
|
165
|
+
}
|
|
166
|
+
yield { type: 'finish', reason: input.signal?.aborted ? 'cancel' : terminalReason ?? 'stop' };
|
|
167
|
+
}
|
|
168
|
+
finally { /* app-server 在会话结束前保持连接。 */ }
|
|
169
|
+
}
|
|
170
|
+
/** 将新输入 steer 到当前 Codex turn。 */
|
|
171
|
+
async steer(sessionId, prompt) {
|
|
172
|
+
const session = this.sessions.get(sessionId);
|
|
173
|
+
if (session === undefined || session.threadId === '' || session.turnId === null)
|
|
174
|
+
throw new Error('Codex 会话未运行');
|
|
175
|
+
const result = await session.rpc.request('turn/steer', { threadId: session.threadId, turnId: session.turnId, input: [{ type: 'text', text: prompt }] }, { killOnAbort: false });
|
|
176
|
+
const turnId = readId(result);
|
|
177
|
+
if (turnId)
|
|
178
|
+
session.turnId = turnId;
|
|
179
|
+
}
|
|
180
|
+
/** 中断当前 Codex turn,但保留 app-server 线程。 */
|
|
181
|
+
async interrupt(sessionId) {
|
|
182
|
+
const session = this.sessions.get(sessionId);
|
|
183
|
+
if (session === undefined || session.threadId === '' || session.turnId === null)
|
|
184
|
+
return;
|
|
185
|
+
try {
|
|
186
|
+
await session.rpc.request('turn/interrupt', { threadId: session.threadId, turnId: session.turnId }, { killOnAbort: false });
|
|
187
|
+
}
|
|
188
|
+
catch { /* app-server 可能已经发出 turn/completed */ }
|
|
189
|
+
}
|
|
190
|
+
/** 回传原生权限请求;审批值只在 Host 进程中流转。 */
|
|
191
|
+
respondPermission(sessionId, response) {
|
|
192
|
+
const session = this.sessions.get(sessionId);
|
|
193
|
+
const resolve = session?.pendingPermissions.get(response.requestId);
|
|
194
|
+
if (resolve === undefined || session === undefined)
|
|
195
|
+
throw new Error('Codex 权限请求不存在');
|
|
196
|
+
session.pendingPermissions.delete(response.requestId);
|
|
197
|
+
resolve({
|
|
198
|
+
approved: response.approved,
|
|
199
|
+
...(response.reason?.trim() ? { reason: response.reason.trim().slice(0, 512) } : {}),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/** 回传 requestUserInput 的结构化回答。 */
|
|
203
|
+
respondQuestion(sessionId, response) {
|
|
204
|
+
const session = this.sessions.get(sessionId);
|
|
205
|
+
const resolve = session?.pendingQuestions.get(response.requestId);
|
|
206
|
+
if (resolve === undefined || session === undefined)
|
|
207
|
+
throw new Error('Codex 问题请求不存在');
|
|
208
|
+
session.pendingQuestions.delete(response.requestId);
|
|
209
|
+
resolve({ answers: questionAnswersRecord(response) });
|
|
210
|
+
}
|
|
211
|
+
dispose() {
|
|
212
|
+
for (const session of this.sessions.values()) {
|
|
213
|
+
for (const resolve of session.pendingPermissions.values())
|
|
214
|
+
resolve({ approved: false });
|
|
215
|
+
session.pendingPermissions.clear();
|
|
216
|
+
for (const resolve of session.pendingQuestions.values())
|
|
217
|
+
resolve({ answers: {} });
|
|
218
|
+
session.pendingQuestions.clear();
|
|
219
|
+
session.rpc.dispose();
|
|
220
|
+
}
|
|
221
|
+
this.sessions.clear();
|
|
222
|
+
this.processes.clear();
|
|
223
|
+
this.cachedBinary = null;
|
|
224
|
+
}
|
|
225
|
+
async getSession(input, command) {
|
|
226
|
+
const previous = this.sessions.get(input.sessionId);
|
|
227
|
+
if (previous !== undefined && previous.cwd === input.cwd)
|
|
228
|
+
return previous;
|
|
229
|
+
previous?.rpc.dispose();
|
|
230
|
+
const rpc = new JsonRpcProcess({ command, args: ['app-server'], cwd: input.cwd, spawn: this.runSpawn });
|
|
231
|
+
const session = {
|
|
232
|
+
rpc,
|
|
233
|
+
cwd: input.cwd,
|
|
234
|
+
threadId: '',
|
|
235
|
+
turnId: null,
|
|
236
|
+
providerSessionId: input.providerSessionId ?? input.sessionId,
|
|
237
|
+
pendingPermissions: new Map(),
|
|
238
|
+
pendingQuestions: new Map(),
|
|
239
|
+
};
|
|
240
|
+
this.processes.add(rpc);
|
|
241
|
+
this.sessions.set(input.sessionId, session);
|
|
242
|
+
await rpc.request('initialize', { clientInfo: { name: 'codingns4dsh', version: '0.1.0' }, capabilities: {} }, { signal: input.signal, killOnAbort: false });
|
|
243
|
+
rpc.notify('initialized', {});
|
|
244
|
+
rpc.setServerRequestHandler((request) => {
|
|
245
|
+
const requestId = readRequestId(request);
|
|
246
|
+
if (requestId === null)
|
|
247
|
+
return { approved: false };
|
|
248
|
+
const params = isRecord(request.params) ? request.params : request;
|
|
249
|
+
const item = isRecord(params.item) ? params.item : params;
|
|
250
|
+
const method = typeof request.method === 'string' ? request.method : '';
|
|
251
|
+
const type = typeof item.type === 'string' ? item.type : '';
|
|
252
|
+
if (isQuestionEvent(`${method} ${type}`)) {
|
|
253
|
+
return new Promise((resolve) => session.pendingQuestions.set(requestId, resolve));
|
|
254
|
+
}
|
|
255
|
+
return new Promise((resolve) => session.pendingPermissions.set(requestId, resolve));
|
|
256
|
+
});
|
|
257
|
+
return session;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function parseCodexCatalog(value) {
|
|
261
|
+
const root = isRecord(value) && isRecord(value.data) ? value.data : value;
|
|
262
|
+
const models = Array.isArray(root)
|
|
263
|
+
? root
|
|
264
|
+
: isRecord(root) && Array.isArray(root.models) ? root.models
|
|
265
|
+
: isRecord(root) && Array.isArray(root.data) ? root.data
|
|
266
|
+
: [];
|
|
267
|
+
const items = models.flatMap((entry) => {
|
|
268
|
+
if (!isRecord(entry))
|
|
269
|
+
return [];
|
|
270
|
+
if (entry.hidden === true)
|
|
271
|
+
return [];
|
|
272
|
+
const id = typeof entry.model === 'string' ? entry.model.trim() : typeof entry.id === 'string' ? entry.id.trim() : '';
|
|
273
|
+
if (!id)
|
|
274
|
+
return [];
|
|
275
|
+
const efforts = Array.isArray(entry.supportedReasoningEfforts)
|
|
276
|
+
? [...new Set(entry.supportedReasoningEfforts.flatMap((item) => {
|
|
277
|
+
const effort = typeof item === 'string'
|
|
278
|
+
? item
|
|
279
|
+
: isRecord(item) && typeof item.reasoningEffort === 'string'
|
|
280
|
+
? item.reasoningEffort
|
|
281
|
+
: null;
|
|
282
|
+
return effort?.trim() ? [effort.trim()] : [];
|
|
283
|
+
}))]
|
|
284
|
+
: [];
|
|
285
|
+
return [{
|
|
286
|
+
id,
|
|
287
|
+
name: typeof entry.displayName === 'string' && entry.displayName.trim() ? entry.displayName : id,
|
|
288
|
+
efforts,
|
|
289
|
+
}];
|
|
290
|
+
});
|
|
291
|
+
if (items.length === 0)
|
|
292
|
+
return emptyCatalog();
|
|
293
|
+
return {
|
|
294
|
+
groups: [{ id: 'codex', name: 'Codex', models: [...new Map(items.map((item) => [item.id, item])).values()] }],
|
|
295
|
+
currentModel: null,
|
|
296
|
+
currentEffort: null,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function codexMessageToChunk(message) {
|
|
300
|
+
const params = isRecord(message.params) ? message.params : message;
|
|
301
|
+
const item = isRecord(params.item) ? params.item : params;
|
|
302
|
+
const method = typeof message.method === 'string' ? message.method : '';
|
|
303
|
+
const type = typeof item.type === 'string' ? item.type : '';
|
|
304
|
+
const text = textValue(params.delta ?? params.text ?? params.content ?? params.message);
|
|
305
|
+
if (isQuestionEvent(`${method} ${type}`)) {
|
|
306
|
+
const requestId = readRequestId(message) ?? readRequestId(params);
|
|
307
|
+
const questions = readAgentQuestions(params.questions ?? item.questions ?? params);
|
|
308
|
+
if (requestId !== null && questions.length > 0)
|
|
309
|
+
return { type: 'question-request', requestId, questions };
|
|
310
|
+
}
|
|
311
|
+
if (method.includes('permission') || method.includes('Approval') || type.includes('permission') || type.includes('approval')) {
|
|
312
|
+
const requestId = readRequestId(message) ?? readRequestId(params);
|
|
313
|
+
const detail = text ?? (typeof params.command === 'string' ? params.command : typeof params.description === 'string' ? params.description : null);
|
|
314
|
+
if (requestId !== null)
|
|
315
|
+
return { type: 'permission-request', requestId, kind: typeof params.kind === 'string' ? params.kind : 'unknown', ...(detail ? { detail } : {}) };
|
|
316
|
+
}
|
|
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;
|
|
321
|
+
if (isCodexToolEvent(method, type)) {
|
|
322
|
+
const name = firstToolText(item.name, item.toolName, item.tool, item.command !== undefined ? 'command_execution' : undefined, type);
|
|
323
|
+
const callId = firstToolText(item.callId, item.call_id, item.toolCallId, item.id, params.itemId);
|
|
324
|
+
const agentId = firstToolText(item.agentId, item.agent_id, type.includes('agent') || type.includes('collab') ? item.id : undefined);
|
|
325
|
+
const input = serializeToolValue(item.arguments ?? item.input ?? item.command);
|
|
326
|
+
const rawOutput = item.result ?? item.output ?? item.aggregated_output;
|
|
327
|
+
const explicitError = serializeToolValue(item.error);
|
|
328
|
+
const output = serializeToolValue(rawOutput);
|
|
329
|
+
const exitCodeFailed = typeof item.exitCode === 'number' && item.exitCode !== 0 || typeof item.exit_code === 'number' && item.exit_code !== 0;
|
|
330
|
+
const failed = explicitError !== undefined || exitCodeFailed || method.includes('failed') || normalizeToolStatus(item.status ?? item.state, 'running') === 'failed';
|
|
331
|
+
const fallback = failed ? 'failed' : method.includes('completed') || output !== undefined ? 'completed' : 'running';
|
|
332
|
+
const detail = serializeToolValue(item.detail ?? item.description);
|
|
333
|
+
return {
|
|
334
|
+
type: 'tool-event',
|
|
335
|
+
toolName: name ?? (agentId ? 'subagent' : 'tool'),
|
|
336
|
+
status: normalizeToolStatus(item.status ?? item.state, fallback),
|
|
337
|
+
...(callId ? { callId } : {}),
|
|
338
|
+
...(input !== undefined ? { input } : {}),
|
|
339
|
+
...(failed
|
|
340
|
+
? { error: explicitError ?? output ?? `exit code ${String(item.exitCode ?? item.exit_code)}` }
|
|
341
|
+
: output === undefined ? {} : { output }),
|
|
342
|
+
...(!failed && output !== undefined ? { outputMode: 'snapshot' } : {}),
|
|
343
|
+
...(agentId ? { agentId } : {}),
|
|
344
|
+
...(detail !== undefined ? { detail } : {}),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return codexUsageChunk(params);
|
|
348
|
+
}
|
|
349
|
+
/** Codex app-server 的 tokenUsage 使用未缓存输入和缓存输入两个独立桶。 */
|
|
350
|
+
function codexUsageChunk(params) {
|
|
351
|
+
const tokenUsage = isRecord(params.tokenUsage) ? params.tokenUsage : isRecord(params.token_usage) ? params.token_usage : null;
|
|
352
|
+
if (tokenUsage === null)
|
|
353
|
+
return usageChunk(params);
|
|
354
|
+
const latest = isRecord(tokenUsage.last)
|
|
355
|
+
? tokenUsage.last
|
|
356
|
+
: isRecord(tokenUsage.lastUsage)
|
|
357
|
+
? tokenUsage.lastUsage
|
|
358
|
+
: isRecord(tokenUsage.last_token_usage)
|
|
359
|
+
? tokenUsage.last_token_usage
|
|
360
|
+
: isRecord(tokenUsage.total)
|
|
361
|
+
? tokenUsage.total
|
|
362
|
+
: tokenUsage;
|
|
363
|
+
const uncachedInputTokens = optionalToken(latest.inputTokens ?? latest.input_tokens);
|
|
364
|
+
const cacheReadTokens = optionalToken(latest.cachedInputTokens ?? latest.cached_input_tokens);
|
|
365
|
+
const usage = usageChunk({
|
|
366
|
+
inputTokens: uncachedInputTokens ?? 0,
|
|
367
|
+
...(uncachedInputTokens === undefined ? {} : { uncachedInputTokens }),
|
|
368
|
+
...(latest.outputTokens === undefined && latest.output_tokens === undefined ? {} : { outputTokens: latest.outputTokens ?? latest.output_tokens }),
|
|
369
|
+
...(cacheReadTokens === undefined ? {} : { cacheReadTokens }),
|
|
370
|
+
...(latest.totalTokens === undefined && latest.total_tokens === undefined ? {} : { totalTokens: latest.totalTokens ?? latest.total_tokens }),
|
|
371
|
+
});
|
|
372
|
+
if (usage === null)
|
|
373
|
+
return null;
|
|
374
|
+
if (usage.type !== 'usage')
|
|
375
|
+
return null;
|
|
376
|
+
const contextWindowValue = optionalToken(tokenUsage.contextWindow
|
|
377
|
+
?? tokenUsage.context_window
|
|
378
|
+
?? tokenUsage.modelContextWindow
|
|
379
|
+
?? tokenUsage.model_context_window
|
|
380
|
+
?? latest.contextWindow
|
|
381
|
+
?? latest.context_window
|
|
382
|
+
?? latest.modelContextWindow
|
|
383
|
+
?? latest.model_context_window
|
|
384
|
+
?? params.contextWindow
|
|
385
|
+
?? params.context_window);
|
|
386
|
+
const contextWindow = contextWindowValue !== undefined && contextWindowValue > 0 ? contextWindowValue : undefined;
|
|
387
|
+
const contextTokens = usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
388
|
+
return {
|
|
389
|
+
...usage,
|
|
390
|
+
...(contextWindow === undefined ? {} : {
|
|
391
|
+
contextWindow,
|
|
392
|
+
contextTokens,
|
|
393
|
+
contextUsageRatio: Number(Math.min(1, contextTokens / contextWindow).toFixed(6)),
|
|
394
|
+
}),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function optionalToken(value) {
|
|
398
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
399
|
+
}
|
|
400
|
+
function readRequestId(value) {
|
|
401
|
+
if (!isRecord(value))
|
|
402
|
+
return null;
|
|
403
|
+
for (const key of ['requestId', 'request_id', 'id'])
|
|
404
|
+
if (typeof value[key] === 'string' || typeof value[key] === 'number')
|
|
405
|
+
return String(value[key]);
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
function readTurnId(message) {
|
|
409
|
+
if (!isRecord(message))
|
|
410
|
+
return null;
|
|
411
|
+
const params = isRecord(message.params) ? message.params : message;
|
|
412
|
+
for (const key of ['turnId', 'turn_id'])
|
|
413
|
+
if (typeof params[key] === 'string')
|
|
414
|
+
return params[key];
|
|
415
|
+
if (isRecord(params.turn) && typeof params.turn.id === 'string')
|
|
416
|
+
return params.turn.id;
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* 复用父仓库 CodexRuntimeAdapter 的事件队列结构:终止时先排空已入队事件,
|
|
421
|
+
* 再结束异步迭代,保证 turn/completed 前到达的最后一个文本片段不会丢失。
|
|
422
|
+
*/
|
|
423
|
+
function createCodexTurnEventQueue() {
|
|
424
|
+
const values = [];
|
|
425
|
+
const waiters = [];
|
|
426
|
+
let closed = false;
|
|
427
|
+
return {
|
|
428
|
+
iterable: {
|
|
429
|
+
[Symbol.asyncIterator]() {
|
|
430
|
+
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
|
+
},
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
push(message) {
|
|
443
|
+
if (closed)
|
|
444
|
+
return;
|
|
445
|
+
const waiter = waiters.shift();
|
|
446
|
+
if (waiter !== undefined)
|
|
447
|
+
waiter({ done: false, value: message });
|
|
448
|
+
else
|
|
449
|
+
values.push(message);
|
|
450
|
+
},
|
|
451
|
+
close() {
|
|
452
|
+
if (closed)
|
|
453
|
+
return;
|
|
454
|
+
closed = true;
|
|
455
|
+
while (waiters.length > 0)
|
|
456
|
+
waiters.shift()?.({ done: true, value: undefined });
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/** 只接收当前 thread/turn 的事件,避免子 Agent 的完成通知提前结束父轮次。 */
|
|
461
|
+
function isCodexNotificationForTurn(message, threadId, turnId, allowUnidentifiedTool) {
|
|
462
|
+
const params = isRecord(message.params) ? message.params : null;
|
|
463
|
+
const notificationThreadId = readScopedId(params, ['threadId', 'thread_id'], 'thread');
|
|
464
|
+
const notificationTurnId = readTurnId(message);
|
|
465
|
+
if (notificationThreadId !== null && notificationThreadId !== threadId)
|
|
466
|
+
return false;
|
|
467
|
+
// turn/start 响应前的无 turnId 工具通知无法证明属于本次回合,通常是
|
|
468
|
+
// thread/resume 的迟到历史;响应后则把它交给当前开放的 DSH step 处理。
|
|
469
|
+
if (notificationTurnId === null && isCodexToolNotification(message) && !allowUnidentifiedTool)
|
|
470
|
+
return false;
|
|
471
|
+
return turnId === null || notificationTurnId === null || notificationTurnId === turnId;
|
|
472
|
+
}
|
|
473
|
+
function readCodexTerminalReason(message) {
|
|
474
|
+
if (message.method === 'error') {
|
|
475
|
+
const params = isRecord(message.params) ? message.params : null;
|
|
476
|
+
return params?.willRetry === true ? null : 'error';
|
|
477
|
+
}
|
|
478
|
+
if (message.method !== 'turn/completed')
|
|
479
|
+
return null;
|
|
480
|
+
const params = isRecord(message.params) ? message.params : null;
|
|
481
|
+
const turn = isRecord(params?.turn) ? params.turn : null;
|
|
482
|
+
if (turn?.status === 'failed')
|
|
483
|
+
return 'error';
|
|
484
|
+
if (turn?.status === 'interrupted' || turn?.status === 'cancelled')
|
|
485
|
+
return 'cancel';
|
|
486
|
+
return 'stop';
|
|
487
|
+
}
|
|
488
|
+
/** 将 turn/start 响应里的终态归一化成父仓库使用的 turn/completed 通知。 */
|
|
489
|
+
function buildCodexCompletionNotification(value, threadId) {
|
|
490
|
+
if (!isRecord(value) || !isRecord(value.turn))
|
|
491
|
+
return null;
|
|
492
|
+
const status = value.turn.status;
|
|
493
|
+
if (status !== 'completed' && status !== 'failed' && status !== 'interrupted' && status !== 'cancelled')
|
|
494
|
+
return null;
|
|
495
|
+
return { method: 'turn/completed', params: { threadId, turn: value.turn } };
|
|
496
|
+
}
|
|
497
|
+
function readScopedId(value, keys, nestedKey) {
|
|
498
|
+
if (value === null)
|
|
499
|
+
return null;
|
|
500
|
+
for (const key of keys)
|
|
501
|
+
if (typeof value[key] === 'string' && value[key].trim())
|
|
502
|
+
return value[key].trim();
|
|
503
|
+
const nested = value[nestedKey];
|
|
504
|
+
return isRecord(nested) && typeof nested.id === 'string' && nested.id.trim() ? nested.id.trim() : null;
|
|
505
|
+
}
|
|
506
|
+
function isCodexToolEvent(method, type) {
|
|
507
|
+
if (method.includes('command') || method.includes('tool') || method.includes('agent'))
|
|
508
|
+
return true;
|
|
509
|
+
const normalized = type.replace(/[_-]/gu, '').toLowerCase();
|
|
510
|
+
return ['commandexecution', 'filechange', 'mcptoolcall', 'functioncall', 'customtoolcall', 'dynamictoolcall'].includes(normalized);
|
|
511
|
+
}
|
|
512
|
+
function isCodexToolNotification(message) {
|
|
513
|
+
// 带 id 的消息是 Codex 发起的 JSON-RPC 服务请求(例如权限审批),
|
|
514
|
+
// 必须交给 serverRequestHandler,不能被当成实时工具通知过滤掉。
|
|
515
|
+
if (message.id !== undefined && message.id !== null)
|
|
516
|
+
return false;
|
|
517
|
+
const params = isRecord(message.params) ? message.params : null;
|
|
518
|
+
const item = isRecord(params?.item) ? params.item : params;
|
|
519
|
+
const method = typeof message.method === 'string' ? message.method : '';
|
|
520
|
+
const type = typeof item?.type === 'string' ? item.type : '';
|
|
521
|
+
return isCodexToolEvent(method, type);
|
|
522
|
+
}
|
|
523
|
+
function readId(value) {
|
|
524
|
+
if (!isRecord(value))
|
|
525
|
+
return null;
|
|
526
|
+
if (typeof value.threadId === 'string')
|
|
527
|
+
return value.threadId;
|
|
528
|
+
if (typeof value.id === 'string')
|
|
529
|
+
return value.id;
|
|
530
|
+
if (isRecord(value.thread) && typeof value.thread.id === 'string')
|
|
531
|
+
return value.thread.id;
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
//# sourceMappingURL=codex-driver.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import type { CodingNsCliModelCatalog, CodingNsAgentEvent, CodingNsCliTurnInput } from '../../shared/contracts/cli-adapter.js';
|
|
3
|
+
import type { CodingNsCliDriver, CodingNsCliSessionProbeInput, CodingNsCliSessionProbeResult } from './driver.js';
|
|
4
|
+
export interface CommandCodeDriverOptions {
|
|
5
|
+
readonly homeDirectory?: string;
|
|
6
|
+
readonly binaries?: readonly string[];
|
|
7
|
+
readonly spawnSync?: typeof spawnSync;
|
|
8
|
+
readonly spawn?: typeof spawn;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Command Code 驱动:沿用附件中的 `--session + -p + --output-format json` 方式。
|
|
12
|
+
* 它只负责 CLI 进程和事件转换,不把 Command Code 私有事件泄漏给上层。
|
|
13
|
+
*/
|
|
14
|
+
export declare class CommandCodeDriver implements CodingNsCliDriver {
|
|
15
|
+
readonly descriptor: {
|
|
16
|
+
readonly id: "command-code";
|
|
17
|
+
readonly name: "Command Code";
|
|
18
|
+
readonly protocol: "command";
|
|
19
|
+
readonly capabilities: readonly ["models", "stream", "resume", "interrupt", "tool-events", "reasoning", "usage"];
|
|
20
|
+
};
|
|
21
|
+
private readonly homeDirectory;
|
|
22
|
+
private readonly binaries;
|
|
23
|
+
private readonly runSpawnSync;
|
|
24
|
+
private readonly runSpawn;
|
|
25
|
+
private cachedBinary;
|
|
26
|
+
private readonly processes;
|
|
27
|
+
constructor(options?: CommandCodeDriverOptions);
|
|
28
|
+
detect(): Promise<{
|
|
29
|
+
installed: boolean;
|
|
30
|
+
version: string | null;
|
|
31
|
+
command: string | null;
|
|
32
|
+
}>;
|
|
33
|
+
listModels(): Promise<CodingNsCliModelCatalog>;
|
|
34
|
+
probeSession(_input: CodingNsCliSessionProbeInput): Promise<CodingNsCliSessionProbeResult>;
|
|
35
|
+
executeTurn(input: CodingNsCliTurnInput): AsyncIterable<CodingNsAgentEvent>;
|
|
36
|
+
dispose(): void;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=command-code-driver.d.ts.map
|