@minniexcode/codex-switch 0.0.7 → 0.0.8

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.
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.setCopilotInstallerSpawnImplementation = setCopilotInstallerSpawnImplementation;
37
+ exports.resetCopilotInstallerSpawnImplementation = resetCopilotInstallerSpawnImplementation;
38
+ exports.getCopilotRuntimeInstallDir = getCopilotRuntimeInstallDir;
39
+ exports.getCopilotSdkPackageName = getCopilotSdkPackageName;
40
+ exports.probeCopilotSdkInstall = probeCopilotSdkInstall;
41
+ exports.installCopilotSdk = installCopilotSdk;
42
+ const fs = __importStar(require("node:fs"));
43
+ const os = __importStar(require("node:os"));
44
+ const path = __importStar(require("node:path"));
45
+ const node_child_process_1 = require("node:child_process");
46
+ const errors_1 = require("../domain/errors");
47
+ const COPILOT_SDK_PACKAGE = "@github/copilot-sdk";
48
+ const COPILOT_SDK_VERSION = "latest";
49
+ let spawnImplementation = node_child_process_1.spawnSync;
50
+ /**
51
+ * Overrides the spawn implementation for runtime installer tests.
52
+ */
53
+ function setCopilotInstallerSpawnImplementation(spawnLike) {
54
+ spawnImplementation = spawnLike;
55
+ }
56
+ /**
57
+ * Restores the default spawn implementation after tests.
58
+ */
59
+ function resetCopilotInstallerSpawnImplementation() {
60
+ spawnImplementation = node_child_process_1.spawnSync;
61
+ }
62
+ /**
63
+ * Returns the user-level runtime directory used to lazily install the Copilot SDK.
64
+ */
65
+ function getCopilotRuntimeInstallDir() {
66
+ const override = process.env.CODEX_SWITCH_COPILOT_RUNTIME_DIR;
67
+ if (override && override.trim() !== "") {
68
+ return path.resolve(override);
69
+ }
70
+ return path.join(os.homedir(), ".codex-switch", "runtimes", "copilot");
71
+ }
72
+ /**
73
+ * Returns the package name used by the Copilot runtime installer.
74
+ */
75
+ function getCopilotSdkPackageName() {
76
+ return COPILOT_SDK_PACKAGE;
77
+ }
78
+ /**
79
+ * Reports whether the optional Copilot SDK runtime is currently installed.
80
+ */
81
+ function probeCopilotSdkInstall() {
82
+ const installDir = getCopilotRuntimeInstallDir();
83
+ const packageJsonPath = path.join(installDir, "node_modules", "@github", "copilot-sdk", "package.json");
84
+ if (!fs.existsSync(packageJsonPath)) {
85
+ return {
86
+ installed: false,
87
+ installDir,
88
+ packageName: COPILOT_SDK_PACKAGE,
89
+ packageVersion: null,
90
+ };
91
+ }
92
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
93
+ return {
94
+ installed: true,
95
+ installDir,
96
+ packageName: COPILOT_SDK_PACKAGE,
97
+ packageVersion: packageJson.version ?? null,
98
+ };
99
+ }
100
+ /**
101
+ * Installs the optional Copilot SDK into the user-level runtime directory.
102
+ */
103
+ function installCopilotSdk() {
104
+ const installDir = getCopilotRuntimeInstallDir();
105
+ fs.mkdirSync(installDir, { recursive: true });
106
+ const packageJsonPath = path.join(installDir, "package.json");
107
+ if (!fs.existsSync(packageJsonPath)) {
108
+ fs.writeFileSync(packageJsonPath, `${JSON.stringify({ name: "codex-switch-copilot-runtime", private: true, version: "0.0.0" }, null, 2)}\n`, "utf8");
109
+ }
110
+ const command = process.platform === "win32" ? "npm.cmd" : "npm";
111
+ const result = spawnImplementation(command, ["install", "--no-save", `${COPILOT_SDK_PACKAGE}@${COPILOT_SDK_VERSION}`], {
112
+ cwd: installDir,
113
+ stdio: "pipe",
114
+ encoding: "utf8",
115
+ shell: false,
116
+ });
117
+ if (result.status !== 0) {
118
+ throw (0, errors_1.cliError)("COPILOT_SDK_INSTALL_FAILED", "Failed to install the optional Copilot SDK runtime.", {
119
+ installDir,
120
+ packageName: COPILOT_SDK_PACKAGE,
121
+ cause: result.stderr || result.stdout || `npm exited with status ${String(result.status)}`,
122
+ });
123
+ }
124
+ return probeCopilotSdkInstall();
125
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getCopilotSdkEntrypoint = getCopilotSdkEntrypoint;
37
+ exports.loadCopilotSdk = loadCopilotSdk;
38
+ const path = __importStar(require("node:path"));
39
+ const errors_1 = require("../domain/errors");
40
+ const copilot_installer_1 = require("./copilot-installer");
41
+ /**
42
+ * Dynamically resolves the lazily installed Copilot SDK entrypoint.
43
+ */
44
+ function getCopilotSdkEntrypoint() {
45
+ return path.join((0, copilot_installer_1.getCopilotRuntimeInstallDir)(), "node_modules", "@github", "copilot-sdk");
46
+ }
47
+ /**
48
+ * Loads the Copilot SDK only when a Copilot runtime path is exercised.
49
+ */
50
+ async function loadCopilotSdk() {
51
+ const status = (0, copilot_installer_1.probeCopilotSdkInstall)();
52
+ if (!status.installed) {
53
+ throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
54
+ installDir: status.installDir,
55
+ packageName: status.packageName,
56
+ });
57
+ }
58
+ return Promise.resolve(`${getCopilotSdkEntrypoint()}`).then(s => __importStar(require(s)));
59
+ }
@@ -55,6 +55,9 @@ function writeTextFileAtomic(filePath, contents) {
55
55
  // Use the current process id in the temp name to reduce collision risk.
56
56
  const tempPath = `${filePath}.tmp-${process.pid}`;
57
57
  fs.writeFileSync(tempPath, contents, "utf8");
58
+ if (fs.existsSync(filePath)) {
59
+ fs.rmSync(filePath, { force: true });
60
+ }
58
61
  fs.renameSync(tempPath, filePath);
59
62
  }
60
63
  /**
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getCopilotBridgeStatePath = getCopilotBridgeStatePath;
37
+ exports.readCopilotBridgeState = readCopilotBridgeState;
38
+ exports.writeCopilotBridgeState = writeCopilotBridgeState;
39
+ exports.clearCopilotBridgeState = clearCopilotBridgeState;
40
+ const fs = __importStar(require("node:fs"));
41
+ const os = __importStar(require("node:os"));
42
+ const path = __importStar(require("node:path"));
43
+ const fs_utils_1 = require("./fs-utils");
44
+ /**
45
+ * Returns the user-level runtime state file used by Copilot bridge helpers.
46
+ */
47
+ function getCopilotBridgeStatePath() {
48
+ const override = process.env.CODEX_SWITCH_RUNTIME_STATE_DIR;
49
+ if (override && override.trim() !== "") {
50
+ return path.join(path.resolve(override), "copilot-bridge-state.json");
51
+ }
52
+ return path.join(os.homedir(), ".codex-switch", "runtime", "copilot-bridge-state.json");
53
+ }
54
+ /**
55
+ * Reads the Copilot bridge state manifest when present.
56
+ */
57
+ function readCopilotBridgeState() {
58
+ const statePath = getCopilotBridgeStatePath();
59
+ if (!fs.existsSync(statePath)) {
60
+ return null;
61
+ }
62
+ return JSON.parse(fs.readFileSync(statePath, "utf8"));
63
+ }
64
+ /**
65
+ * Persists the Copilot bridge state manifest.
66
+ */
67
+ function writeCopilotBridgeState(state) {
68
+ const statePath = getCopilotBridgeStatePath();
69
+ (0, fs_utils_1.ensureDir)(path.dirname(statePath));
70
+ (0, fs_utils_1.writeTextFileAtomic)(statePath, `${JSON.stringify(state, null, 2)}\n`);
71
+ }
72
+ /**
73
+ * Deletes the Copilot bridge state manifest when present.
74
+ */
75
+ function clearCopilotBridgeState() {
76
+ const statePath = getCopilotBridgeStatePath();
77
+ if (fs.existsSync(statePath)) {
78
+ fs.rmSync(statePath, { force: true });
79
+ }
80
+ }
@@ -0,0 +1,132 @@
1
+ # codex-switch `0.0.8` 设计文档
2
+
3
+ ## 文档信息
4
+
5
+ - 文档类型:详细设计文档
6
+ - 适用版本:`0.0.8`
7
+ - 目标范围:`0.0.7 -> 0.0.8`
8
+ - 对应 PRD:[`../PRD/codex-switch-prd-v0.0.8.md`](../PRD/codex-switch-prd-v0.0.8.md)
9
+
10
+ ## 1. 文档目标
11
+
12
+ 本设计把 `0.0.8` 的 Copilot runtime integration 收口到可实现的范围:
13
+
14
+ - Copilot provider 的 schema 如何表达
15
+ - SDK 的 lazy-load 与自动安装如何落地
16
+ - `add`、`switch`、`status`、`doctor` 分别承担什么职责
17
+ - `providers.json`、`config.toml`、`auth.json` 与 runtime manifest 的边界是什么
18
+ - 代码和测试应该落到哪些模块
19
+
20
+ ## 2. 版本定位
21
+
22
+ `0.0.8` 是第一个 runtime-backed provider 版本。它不会把 `codex-switch` 扩成通用插件系统,而是先用 GitHub Copilot 把 runtime integration 这条线真正跑通。
23
+
24
+ ## 3. 数据模型
25
+
26
+ `ProviderRecord` 在 `0.0.8` 中扩展为兼容 direct provider 和 runtime-backed provider:
27
+
28
+ ```ts
29
+ type ProviderRuntime =
30
+ | {
31
+ kind: "copilot-sdk-bridge";
32
+ upstream: "github-copilot";
33
+ bridgeHost: string;
34
+ bridgePort: number;
35
+ bridgePath: "/v1";
36
+ premiumRequests: true;
37
+ authSource: "official-sdk";
38
+ sdkInstallMode: "lazy";
39
+ };
40
+ ```
41
+
42
+ 语义:
43
+
44
+ - 无 `runtime`:现有 direct provider
45
+ - `runtime.kind = "copilot-sdk-bridge"`:Copilot provider
46
+ - Copilot provider 的 `apiKey` 是本地 bridge 的 shared secret,不是 GitHub token
47
+ - `baseUrl` 必须与 `http://<bridgeHost>:<bridgePort>/v1` 一致
48
+
49
+ ## 4. 命令设计
50
+
51
+ 扩展现有命令面,不新增 `copilot` 或 `proxy` 子命令:
52
+
53
+ - `codexs add <provider> --copilot --profile <name> [--bridge-host <host>] [--bridge-port <port>] [--bridge-api-key <secret>] [--install-copilot-sdk]`
54
+ - `codexs switch <provider>`
55
+ - `codexs status`
56
+ - `codexs doctor`
57
+
58
+ 规则:
59
+
60
+ - `--copilot` 与 direct provider 输入模式互斥
61
+ - `--bridge-host` 默认 `127.0.0.1`
62
+ - `--bridge-port` 默认 `4141`
63
+ - `--bridge-api-key` 若缺失则自动生成本地 secret
64
+ - SDK 自动安装只允许发生在 `add --copilot`
65
+
66
+ ## 5. Runtime 模块
67
+
68
+ 新增模块:
69
+
70
+ - `src/runtime/copilot-sdk-loader.ts`
71
+ - `src/runtime/copilot-installer.ts`
72
+ - `src/runtime/copilot-adapter.ts`
73
+ - `src/runtime/copilot-bridge.ts`
74
+ - `src/storage/runtime-state-repo.ts`
75
+
76
+ 职责:
77
+
78
+ - loader:按用户级 runtime 目录懒加载 SDK
79
+ - installer:探测与安装 SDK
80
+ - adapter:优先按官方 `CopilotClient -> createSession -> sendAndWait` 形态包装 Copilot SDK 的 probe / auth / request 能力
81
+ - bridge:管理本地 OpenAI 兼容 bridge 的状态
82
+ - runtime state repo:保存 bridge runtime manifest
83
+
84
+ 前置条件:
85
+
86
+ - 本地官方 SDK 依赖已安装并可加载
87
+ - 用户机器上已有可用的 Copilot CLI / login 状态,`codex-switch` 不接管上游账号登录
88
+
89
+ ## 6. 运行态与文件边界
90
+
91
+ - `providers.json`:保存 Copilot provider 与 bridge 配置
92
+ - `config.toml`:保存 bridge 的 `base_url` 与 `env_key`
93
+ - `auth.json`:保存 Codex 到 bridge 的 shared secret
94
+ - runtime manifest:保存 bridge 进程状态,不进入 managed backup 事务
95
+
96
+ ## 7. `switch` 时序
97
+
98
+ 1. 读取 provider
99
+ 2. 校验 `runtime` 配置
100
+ 3. probe SDK
101
+ 4. 缺失则直接报错
102
+ 5. 检查 Copilot auth state
103
+ 6. 启动或复用 bridge
104
+ 7. healthcheck 通过
105
+ 8. 再进入现有 `config.toml + auth.json` 写入事务
106
+ 9. 文件写入失败时回滚,并对本次新启动 bridge 做 best-effort 清理
107
+
108
+ ## 8. 错误语义
109
+
110
+ 新增错误码:
111
+
112
+ - `COPILOT_SDK_MISSING`
113
+ - `COPILOT_SDK_INSTALL_FAILED`
114
+ - `COPILOT_SDK_INSTALL_REQUIRES_TTY`
115
+ - `COPILOT_SDK_UNSUPPORTED`
116
+ - `COPILOT_AUTH_REQUIRED`
117
+ - `COPILOT_PREMIUM_UNAVAILABLE`
118
+ - `BRIDGE_PORT_CONFLICT`
119
+ - `BRIDGE_START_FAILED`
120
+ - `BRIDGE_HEALTHCHECK_FAILED`
121
+ - `RUNTIME_PROVIDER_INVALID`
122
+ - `PROVIDER_BASE_URL_MISMATCH`
123
+
124
+ ## 9. 测试
125
+
126
+ 需要覆盖:
127
+
128
+ - `add --copilot` 参数与写入
129
+ - 非交互缺失 SDK 的失败路径
130
+ - `--install-copilot-sdk` 的允许安装路径
131
+ - Copilot provider 的 `status` / `doctor`
132
+ - direct provider 回归