@minniexcode/codex-switch 0.1.4 → 0.2.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.AI.md +5 -4
- package/README.CN.md +9 -8
- package/README.md +9 -8
- package/dist/app/add-provider.js +10 -16
- package/dist/app/bridge.js +8 -13
- package/dist/app/get-status.js +15 -12
- package/dist/app/run-doctor.js +17 -18
- package/dist/app/switch-provider.js +6 -11
- package/dist/commands/handlers.js +32 -69
- package/dist/domain/providers.js +9 -9
- package/dist/runtime/copilot-adapter.js +126 -1
- package/dist/runtime/copilot-bridge-worker.js +3 -0
- package/dist/runtime/copilot-bridge.js +198 -56
- package/dist/runtime/copilot-http-bridge-worker.js +228 -0
- package/dist/runtime/copilot-token.js +294 -0
- package/docs/Design/codex-switch-v0.1.5-design.md +17 -0
- package/docs/Design/codex-switch-v0.2.0-design.md +56 -0
- package/docs/PRD/codex-switch-prd-v0.1.5.md +42 -0
- package/docs/Tests/testing.md +2 -2
- package/docs/cli-usage.md +11 -7
- package/docs/codex-switch-product-overview.md +24 -23
- package/docs/codex-switch-technical-architecture.md +2 -2
- package/package.json +2 -2
package/README.AI.md
CHANGED
|
@@ -6,7 +6,7 @@ This file summarizes the current operational contract for AI agents, automation
|
|
|
6
6
|
|
|
7
7
|
- Package: `@minniexcode/codex-switch`
|
|
8
8
|
- CLI name: `codexs`
|
|
9
|
-
- Current repository version: `0.1.
|
|
9
|
+
- Current repository version: `0.1.5`
|
|
10
10
|
- Version status: development line
|
|
11
11
|
- Runtime contract target: Codex `0.134.0+`
|
|
12
12
|
|
|
@@ -22,7 +22,7 @@ Direct provider workflow:
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
codexs init
|
|
25
|
-
codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
|
|
25
|
+
codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
|
|
26
26
|
codexs switch <provider>
|
|
27
27
|
codexs status
|
|
28
28
|
codexs doctor
|
|
@@ -33,7 +33,7 @@ GitHub Copilot workflow:
|
|
|
33
33
|
```bash
|
|
34
34
|
codexs init
|
|
35
35
|
codexs login copilot
|
|
36
|
-
codexs add <provider> --copilot --model <model>
|
|
36
|
+
codexs add <provider> --copilot --profile <model-provider-id> --model <model>
|
|
37
37
|
codexs switch <provider>
|
|
38
38
|
codexs status
|
|
39
39
|
codexs doctor
|
|
@@ -130,10 +130,11 @@ Important behavioral constraints:
|
|
|
130
130
|
- `login copilot` requires a real TTY and does not support `--json`.
|
|
131
131
|
- `login copilot` currently installs the local Copilot SDK when needed, tries the bundled runtime CLI first, falls back to `PATH` when necessary, and rechecks auth readiness before reporting success.
|
|
132
132
|
- `add --copilot` assumes SDK install and upstream Copilot auth are already ready.
|
|
133
|
+
- Non-interactive automation should pass `--profile` explicitly. In TTY mode, `add` and `edit` can prompt for missing required fields.
|
|
133
134
|
- `migrate` remains interactive when provider adoption requires human input.
|
|
134
135
|
- `status` is the main dual-path summary command.
|
|
135
136
|
- `doctor` is the deeper repair-oriented diagnostic command.
|
|
136
|
-
- The current `0.1.
|
|
137
|
+
- The current `0.1.5` line focuses on Copilot Bridge process visibility, Responses commentary/reasoning stream events, defensive SDK-event normalization, and unknown-event redaction hardening rather than command-surface expansion.
|
|
137
138
|
|
|
138
139
|
## Safety Notes
|
|
139
140
|
|
package/README.CN.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
## 版本定位
|
|
8
8
|
|
|
9
|
-
当前包版本:`0.1.
|
|
9
|
+
当前包版本:`0.1.5`
|
|
10
10
|
|
|
11
|
-
这是当前仓库开发线。`0.1.
|
|
11
|
+
这是当前仓库开发线。`0.1.5` 是 Copilot Bridge 过程可见性补丁,聚焦于 commentary/reasoning 流式信号、SDK 事件防御性归一化,以及未知运行态事件的更安全脱敏,同时不扩展 provider 命令面。
|
|
12
12
|
|
|
13
13
|
## 安装
|
|
14
14
|
|
|
@@ -34,7 +34,7 @@ Direct provider 主路径:
|
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
codexs init
|
|
37
|
-
codexs add my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
|
|
37
|
+
codexs add my-provider --profile my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
|
|
38
38
|
codexs switch my-provider
|
|
39
39
|
codexs status
|
|
40
40
|
codexs doctor
|
|
@@ -45,7 +45,7 @@ GitHub Copilot 主路径:
|
|
|
45
45
|
```bash
|
|
46
46
|
codexs init
|
|
47
47
|
codexs login copilot
|
|
48
|
-
codexs add copilot-main --copilot --model gpt-4.1
|
|
48
|
+
codexs add copilot-main --copilot --profile copilot-main --model gpt-4.1
|
|
49
49
|
codexs switch copilot-main
|
|
50
50
|
codexs status
|
|
51
51
|
codexs doctor
|
|
@@ -56,6 +56,7 @@ codexs doctor
|
|
|
56
56
|
- `init` 负责初始化 `codex-switch` 的 tool home 与受管状态文件。
|
|
57
57
|
- `login copilot` 负责上游 Copilot onboarding 和登录可用性检查。
|
|
58
58
|
- `add --copilot` 不负责替你登录,它假设上游 Copilot 已经 ready。
|
|
59
|
+
- 非交互调用请显式传入 `--profile`;在 TTY 模式下,`add` 和 `edit` 可以补问缺失的必填项。
|
|
59
60
|
- `switch` 会把选中的 provider 投影到目标 Codex runtime 的顶层 `model` 与 `model_provider`。
|
|
60
61
|
- `status` 是切换后的主读取命令。
|
|
61
62
|
- `doctor` 是主诊断命令,用于解释问题和下一步修复动作。
|
|
@@ -116,8 +117,8 @@ codexs current
|
|
|
116
117
|
codexs status
|
|
117
118
|
codexs config show [profile]
|
|
118
119
|
codexs config list-profiles
|
|
119
|
-
codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
|
|
120
|
-
codexs add <provider> --copilot --model <model>
|
|
120
|
+
codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
|
|
121
|
+
codexs add <provider> --copilot --profile <model-provider-id> --model <model>
|
|
121
122
|
codexs edit <provider>
|
|
122
123
|
codexs switch <provider>
|
|
123
124
|
codexs remove <provider> [--force] [--switch-to <provider>]
|
|
@@ -209,8 +210,8 @@ npm pack --dry-run
|
|
|
209
210
|
- [Design 0.1.1](./docs/Design/codex-switch-v0.1.1-design.md)
|
|
210
211
|
- [PRD 0.1.2](./docs/PRD/codex-switch-prd-v0.1.2.md)
|
|
211
212
|
- [Design 0.1.2](./docs/Design/codex-switch-v0.1.2-design.md)
|
|
212
|
-
- [PRD 0.1.
|
|
213
|
-
- [Design 0.1.
|
|
213
|
+
- [PRD 0.1.5](./docs/PRD/codex-switch-prd-v0.1.5.md)
|
|
214
|
+
- [Design 0.1.5](./docs/Design/codex-switch-v0.1.5-design.md)
|
|
214
215
|
|
|
215
216
|
## License
|
|
216
217
|
|
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ Chinese version: [README.CN.md](./README.CN.md)
|
|
|
8
8
|
|
|
9
9
|
## Version
|
|
10
10
|
|
|
11
|
-
Current package version: `0.1.
|
|
11
|
+
Current package version: `0.1.5`
|
|
12
12
|
|
|
13
|
-
This is the current repository development line. `0.1.
|
|
13
|
+
This is the current repository development line. `0.1.5` is a Copilot Bridge process-visibility patch, focused on streaming commentary/reasoning signals, defensive SDK-event normalization, and safer redaction for unknown runtime events while keeping the provider surface unchanged.
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
@@ -36,7 +36,7 @@ Direct provider workflow:
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
codexs init
|
|
39
|
-
codexs add my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
|
|
39
|
+
codexs add my-provider --profile my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
|
|
40
40
|
codexs switch my-provider
|
|
41
41
|
codexs status
|
|
42
42
|
codexs doctor
|
|
@@ -47,7 +47,7 @@ GitHub Copilot workflow:
|
|
|
47
47
|
```bash
|
|
48
48
|
codexs init
|
|
49
49
|
codexs login copilot
|
|
50
|
-
codexs add copilot-main --copilot --model gpt-4.1
|
|
50
|
+
codexs add copilot-main --copilot --profile copilot-main --model gpt-4.1
|
|
51
51
|
codexs switch copilot-main
|
|
52
52
|
codexs status
|
|
53
53
|
codexs doctor
|
|
@@ -58,6 +58,7 @@ Notes:
|
|
|
58
58
|
- `init` prepares the `codex-switch` tool home and managed state.
|
|
59
59
|
- `login copilot` handles upstream Copilot onboarding and auth readiness.
|
|
60
60
|
- `add --copilot` does not perform login for you; it assumes Copilot login is already ready.
|
|
61
|
+
- For non-interactive use, pass `--profile` explicitly. In TTY mode, `add` and `edit` can prompt for missing required fields.
|
|
61
62
|
- Copilot support is an experimental local bridge. The managed installer defaults to `@github/copilot-sdk@1.0.2`, Copilot runtime paths require Node.js `>=20`, and runtime checks separately reject older or prerelease SDK installs while validating API shape when the client or session is used.
|
|
62
63
|
- `switch` projects the selected provider into the target Codex runtime as top-level `model` plus `model_provider`.
|
|
63
64
|
- `status` is the main read command after switching.
|
|
@@ -125,8 +126,8 @@ codexs current
|
|
|
125
126
|
codexs status
|
|
126
127
|
codexs config show [profile]
|
|
127
128
|
codexs config list-profiles
|
|
128
|
-
codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
|
|
129
|
-
codexs add <provider> --copilot --model <model>
|
|
129
|
+
codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
|
|
130
|
+
codexs add <provider> --copilot --profile <model-provider-id> --model <model>
|
|
130
131
|
codexs edit <provider>
|
|
131
132
|
codexs switch <provider>
|
|
132
133
|
codexs remove <provider> [--force] [--switch-to <provider>]
|
|
@@ -218,10 +219,10 @@ npm pack --dry-run
|
|
|
218
219
|
- [PRD 0.1.1](./docs/PRD/codex-switch-prd-v0.1.1.md)
|
|
219
220
|
- [PRD 0.1.2](./docs/PRD/codex-switch-prd-v0.1.2.md)
|
|
220
221
|
- [PRD 0.1.3](./docs/PRD/codex-switch-prd-v0.1.3.md)
|
|
221
|
-
- [PRD 0.1.
|
|
222
|
+
- [PRD 0.1.5](./docs/PRD/codex-switch-prd-v0.1.5.md)
|
|
222
223
|
- [Design 0.1.2](./docs/Design/codex-switch-v0.1.2-design.md)
|
|
223
224
|
- [Design 0.1.3](./docs/Design/codex-switch-v0.1.3-design.md)
|
|
224
|
-
- [Design 0.1.
|
|
225
|
+
- [Design 0.1.5](./docs/Design/codex-switch-v0.1.5-design.md)
|
|
225
226
|
|
|
226
227
|
## License
|
|
227
228
|
|
package/dist/app/add-provider.js
CHANGED
|
@@ -40,8 +40,7 @@ const errors_1 = require("../domain/errors");
|
|
|
40
40
|
const config_repo_1 = require("../storage/config-repo");
|
|
41
41
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
42
42
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
43
|
-
const
|
|
44
|
-
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
43
|
+
const copilot_token_1 = require("../runtime/copilot-token");
|
|
45
44
|
const run_mutation_1 = require("./run-mutation");
|
|
46
45
|
/**
|
|
47
46
|
* Adds a new provider record to the managed providers registry.
|
|
@@ -56,34 +55,29 @@ async function addProvider(args) {
|
|
|
56
55
|
const bridgePort = args.bridgePort ?? 41415;
|
|
57
56
|
const runtime = args.copilot
|
|
58
57
|
? {
|
|
59
|
-
kind: "copilot-
|
|
58
|
+
kind: "copilot-http-proxy",
|
|
60
59
|
upstream: "github-copilot",
|
|
61
60
|
bridgeHost,
|
|
62
61
|
bridgePort,
|
|
63
62
|
bridgePath: "/v1",
|
|
64
63
|
premiumRequests: true,
|
|
65
|
-
authSource: "
|
|
66
|
-
sdkInstallMode: "lazy",
|
|
64
|
+
authSource: "github-pat",
|
|
67
65
|
}
|
|
68
66
|
: undefined;
|
|
69
67
|
if (args.copilot) {
|
|
70
|
-
(0,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
installDir: installStatus.installDir,
|
|
75
|
-
packageName: installStatus.packageName,
|
|
76
|
-
suggestion: "Run `codexs login copilot` to install the Copilot SDK and complete login.",
|
|
68
|
+
const githubToken = (0, copilot_token_1.readGithubToken)(args.toolHomeDir);
|
|
69
|
+
if (!githubToken) {
|
|
70
|
+
throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "GitHub Copilot authentication is required. Run `codexs login copilot` first.", {
|
|
71
|
+
suggestion: "Run `codexs login copilot` to complete GitHub Copilot login.",
|
|
77
72
|
});
|
|
78
73
|
}
|
|
79
74
|
try {
|
|
80
|
-
await (0,
|
|
75
|
+
await (0, copilot_token_1.exchangeForCopilotToken)(githubToken);
|
|
81
76
|
}
|
|
82
77
|
catch (error) {
|
|
83
78
|
const normalized = (0, errors_1.normalizeError)(error);
|
|
84
|
-
if (normalized.code === "COPILOT_AUTH_REQUIRED") {
|
|
85
|
-
throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "
|
|
86
|
-
...(normalized.details ?? {}),
|
|
79
|
+
if (normalized.code === "COPILOT_AUTH_REQUIRED" || normalized.code === "COPILOT_TOKEN_EXCHANGE_FAILED") {
|
|
80
|
+
throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "GitHub token is invalid or expired. Run `codexs login copilot` to re-authenticate.", {
|
|
87
81
|
suggestion: "Run `codexs login copilot` to complete GitHub Copilot login.",
|
|
88
82
|
});
|
|
89
83
|
}
|
package/dist/app/bridge.js
CHANGED
|
@@ -10,8 +10,7 @@ const providers_repo_1 = require("../storage/providers-repo");
|
|
|
10
10
|
const interactive_1 = require("../interaction/interactive");
|
|
11
11
|
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
12
12
|
const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
13
|
-
const
|
|
14
|
-
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
13
|
+
const copilot_token_1 = require("../runtime/copilot-token");
|
|
15
14
|
const DEFAULT_BRIDGE_PORT = 41415;
|
|
16
15
|
/**
|
|
17
16
|
* Starts or reuses the managed Copilot bridge for one provider.
|
|
@@ -28,8 +27,8 @@ async function startBridge(args) {
|
|
|
28
27
|
commandName: "start",
|
|
29
28
|
preferRuntimeState: false,
|
|
30
29
|
});
|
|
31
|
-
await requireBridgeRuntimeReadiness(args.runtimesDir);
|
|
32
|
-
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider, args.runtimeDir, args.runtimesDir);
|
|
30
|
+
await requireBridgeRuntimeReadiness(args.runtimesDir, args.toolHomeDir);
|
|
31
|
+
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider, args.runtimeDir, args.runtimesDir, args.toolHomeDir);
|
|
33
32
|
const nextProvider = bridge.portChanged ? rewriteBridgeProviderPort(target.provider, bridge.port) : target.provider;
|
|
34
33
|
if (bridge.portChanged) {
|
|
35
34
|
try {
|
|
@@ -252,16 +251,12 @@ async function promptForCopilotBridgeSelection(runtime, targets, commandName) {
|
|
|
252
251
|
/**
|
|
253
252
|
* Verifies that the local Copilot bridge prerequisites are available before startup.
|
|
254
253
|
*/
|
|
255
|
-
async function requireBridgeRuntimeReadiness(
|
|
256
|
-
(0,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
|
|
260
|
-
installDir: installStatus.installDir,
|
|
261
|
-
packageName: installStatus.packageName,
|
|
262
|
-
});
|
|
254
|
+
async function requireBridgeRuntimeReadiness(_runtimesDir, toolHomeDir) {
|
|
255
|
+
const githubToken = (0, copilot_token_1.readGithubToken)(toolHomeDir);
|
|
256
|
+
if (!githubToken) {
|
|
257
|
+
throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "GitHub Copilot authentication is required. Run `codexs login copilot` first.");
|
|
263
258
|
}
|
|
264
|
-
await (0,
|
|
259
|
+
await (0, copilot_token_1.exchangeForCopilotToken)(githubToken);
|
|
265
260
|
}
|
|
266
261
|
/**
|
|
267
262
|
* Rewrites one Copilot bridge provider record with a recovered runtime port.
|
package/dist/app/get-status.js
CHANGED
|
@@ -41,9 +41,8 @@ const providers_1 = require("../domain/providers");
|
|
|
41
41
|
const config_repo_1 = require("../storage/config-repo");
|
|
42
42
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
43
43
|
const auth_repo_1 = require("../storage/auth-repo");
|
|
44
|
-
const
|
|
44
|
+
const copilot_token_1 = require("../runtime/copilot-token");
|
|
45
45
|
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
46
|
-
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
47
46
|
const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
48
47
|
/**
|
|
49
48
|
* Reports the current on-disk runtime state and how it maps back to managed providers.
|
|
@@ -73,7 +72,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
|
|
|
73
72
|
const activeProvider = liveState.providerResolvable && providers && liveState.mappedProvider
|
|
74
73
|
? providers.providers[liveState.mappedProvider]
|
|
75
74
|
: null;
|
|
76
|
-
const copilotInstall = (0,
|
|
75
|
+
const copilotInstall = { installed: Boolean((0, copilot_token_1.readGithubToken)(options?.toolHomeDir)), source: "github-pat" };
|
|
77
76
|
const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)(options?.runtimeDir);
|
|
78
77
|
const runtimeState = runtimeStateInspection.state;
|
|
79
78
|
const runtimeStateProvider = runtimeState && providers ? providers.providers[runtimeState.provider] ?? null : null;
|
|
@@ -107,12 +106,18 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
|
|
|
107
106
|
}
|
|
108
107
|
: null;
|
|
109
108
|
const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
|
|
110
|
-
? await (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
? await (async () => {
|
|
110
|
+
const token = (0, copilot_token_1.readGithubToken)(options?.toolHomeDir);
|
|
111
|
+
if (!token)
|
|
112
|
+
return { ready: false, source: "github-pat", mode: "token", error: "No GitHub token found" };
|
|
113
|
+
try {
|
|
114
|
+
await (0, copilot_token_1.exchangeForCopilotToken)(token);
|
|
115
|
+
return { ready: true, source: "github-pat", mode: "token" };
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return { ready: false, source: "github-pat", mode: "token", error: error instanceof Error ? error.message : String(error) };
|
|
119
|
+
}
|
|
120
|
+
})()
|
|
116
121
|
: null;
|
|
117
122
|
if (liveState.canBackfillActiveProvider) {
|
|
118
123
|
// Surface unmanaged live state without mutating anything during a read-only status call.
|
|
@@ -147,9 +152,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
|
|
|
147
152
|
runtimeProvider: activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider) ? activeProvider.runtime?.kind ?? null : null,
|
|
148
153
|
copilotSdk: {
|
|
149
154
|
installed: copilotInstall.installed,
|
|
150
|
-
|
|
151
|
-
packageName: copilotInstall.packageName,
|
|
152
|
-
packageVersion: copilotInstall.packageVersion ?? null,
|
|
155
|
+
source: copilotInstall.source,
|
|
153
156
|
},
|
|
154
157
|
copilotAuth,
|
|
155
158
|
copilotBridge,
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -43,9 +43,8 @@ const errors_1 = require("../domain/errors");
|
|
|
43
43
|
const codex_probe_1 = require("../runtime/codex-probe");
|
|
44
44
|
const auth_repo_1 = require("../storage/auth-repo");
|
|
45
45
|
const providers_1 = require("../domain/providers");
|
|
46
|
-
const
|
|
46
|
+
const copilot_token_1 = require("../runtime/copilot-token");
|
|
47
47
|
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
48
|
-
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
49
48
|
const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
50
49
|
const codex_version_1 = require("../runtime/codex-version");
|
|
51
50
|
/**
|
|
@@ -125,25 +124,25 @@ async function runDoctor(args) {
|
|
|
125
124
|
if (matches.length === 1) {
|
|
126
125
|
const activeProvider = providers.providers[matches[0]];
|
|
127
126
|
if ((0, providers_1.isCopilotBridgeProvider)(activeProvider)) {
|
|
128
|
-
const
|
|
129
|
-
if (!
|
|
127
|
+
const githubToken = (0, copilot_token_1.readGithubToken)(args.toolHomeDir);
|
|
128
|
+
if (!githubToken) {
|
|
130
129
|
issues.push({
|
|
131
|
-
code: "
|
|
132
|
-
message: "
|
|
133
|
-
installDir: installStatus.installDir,
|
|
134
|
-
packageName: installStatus.packageName,
|
|
130
|
+
code: "COPILOT_AUTH_REQUIRED",
|
|
131
|
+
message: "GitHub Copilot authentication is required. Run `codexs login copilot`.",
|
|
135
132
|
});
|
|
136
133
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
134
|
+
else {
|
|
135
|
+
try {
|
|
136
|
+
await (0, copilot_token_1.exchangeForCopilotToken)(githubToken);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const normalized = (0, errors_1.normalizeError)(error);
|
|
140
|
+
issues.push({
|
|
141
|
+
code: normalized.code,
|
|
142
|
+
message: normalized.message,
|
|
143
|
+
...(normalized.details ?? {}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
147
146
|
}
|
|
148
147
|
const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider, runtimeState, args.runtimeDir);
|
|
149
148
|
if (!bridge.ok) {
|
|
@@ -6,8 +6,7 @@ const config_repo_1 = require("../storage/config-repo");
|
|
|
6
6
|
const auth_repo_1 = require("../storage/auth-repo");
|
|
7
7
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
8
8
|
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
9
|
-
const
|
|
10
|
-
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
9
|
+
const copilot_token_1 = require("../runtime/copilot-token");
|
|
11
10
|
const run_mutation_1 = require("./run-mutation");
|
|
12
11
|
const providers_1 = require("../domain/providers");
|
|
13
12
|
/**
|
|
@@ -33,16 +32,12 @@ async function switchProvider(args) {
|
|
|
33
32
|
});
|
|
34
33
|
}
|
|
35
34
|
if ((0, providers_1.isCopilotBridgeProvider)(provider)) {
|
|
36
|
-
(0,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
|
|
40
|
-
installDir: installStatus.installDir,
|
|
41
|
-
packageName: installStatus.packageName,
|
|
42
|
-
});
|
|
35
|
+
const githubToken = (0, copilot_token_1.readGithubToken)(args.toolHomeDir);
|
|
36
|
+
if (!githubToken) {
|
|
37
|
+
throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "GitHub Copilot authentication is required. Run `codexs login copilot` first.");
|
|
43
38
|
}
|
|
44
|
-
await (0,
|
|
45
|
-
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(args.providerName, provider, args.runtimeDir, args.runtimesDir);
|
|
39
|
+
await (0, copilot_token_1.exchangeForCopilotToken)(githubToken);
|
|
40
|
+
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(args.providerName, provider, args.runtimeDir, args.runtimesDir, args.toolHomeDir);
|
|
46
41
|
const nextProvider = bridge.portChanged
|
|
47
42
|
? (0, providers_1.cleanProviderRecord)({
|
|
48
43
|
...provider,
|
|
@@ -60,9 +60,7 @@ const providers_1 = require("../domain/providers");
|
|
|
60
60
|
const add_interactive_1 = require("../interaction/add-interactive");
|
|
61
61
|
const interactive_1 = require("../interaction/interactive");
|
|
62
62
|
const prompt_1 = require("../interaction/prompt");
|
|
63
|
-
const
|
|
64
|
-
const copilot_cli_1 = require("../runtime/copilot-cli");
|
|
65
|
-
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
63
|
+
const copilot_token_1 = require("../runtime/copilot-token");
|
|
66
64
|
const config_repo_1 = require("../storage/config-repo");
|
|
67
65
|
const codex_paths_1 = require("../storage/codex-paths");
|
|
68
66
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
@@ -100,6 +98,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
100
98
|
return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath, {
|
|
101
99
|
runtimeDir: paths.runtimeDir,
|
|
102
100
|
runtimesDir: paths.runtimesDir,
|
|
101
|
+
toolHomeDir: paths.toolHomeDir,
|
|
103
102
|
});
|
|
104
103
|
case "bridge-start": {
|
|
105
104
|
const providerName = parsed.positionals[0] ?? null;
|
|
@@ -108,6 +107,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
108
107
|
configPath: paths.configPath,
|
|
109
108
|
runtimeDir: paths.runtimeDir,
|
|
110
109
|
runtimesDir: paths.runtimesDir,
|
|
110
|
+
toolHomeDir: paths.toolHomeDir,
|
|
111
111
|
providerName,
|
|
112
112
|
runtime,
|
|
113
113
|
json: ctx.options.json,
|
|
@@ -156,80 +156,41 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
156
156
|
supportedUpstreams: ["copilot", "github-copilot"],
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
runtime.writeLine("Installing Copilot SDK runtime...");
|
|
173
|
-
(0, copilot_installer_1.installCopilotSdk)(paths.runtimesDir);
|
|
174
|
-
installedNow = true;
|
|
175
|
-
}
|
|
176
|
-
const availability = (0, copilot_cli_1.checkCopilotCliAvailable)(paths.runtimesDir);
|
|
177
|
-
try {
|
|
178
|
-
await (0, copilot_adapter_1.readCopilotAuthState)(paths.runtimesDir);
|
|
179
|
-
return {
|
|
180
|
-
data: {
|
|
181
|
-
upstream: "github-copilot",
|
|
182
|
-
sdkInstalled: true,
|
|
183
|
-
sdkInstalledNow: installedNow,
|
|
184
|
-
authReady: true,
|
|
185
|
-
loginLaunched: false,
|
|
186
|
-
cliSource: availability.ok ? availability.source ?? null : null,
|
|
187
|
-
cliCommand: availability.command ?? null,
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
const normalized = (0, errors_1.normalizeError)(error);
|
|
193
|
-
if (normalized.code !== "COPILOT_AUTH_REQUIRED") {
|
|
194
|
-
throw error;
|
|
159
|
+
// Check if already authenticated
|
|
160
|
+
const existingToken = (0, copilot_token_1.readGithubToken)(paths.toolHomeDir);
|
|
161
|
+
if (existingToken) {
|
|
162
|
+
try {
|
|
163
|
+
await (0, copilot_token_1.exchangeForCopilotToken)(existingToken);
|
|
164
|
+
return {
|
|
165
|
+
data: {
|
|
166
|
+
upstream: "github-copilot",
|
|
167
|
+
authReady: true,
|
|
168
|
+
loginLaunched: false,
|
|
169
|
+
authSource: "github-pat",
|
|
170
|
+
},
|
|
171
|
+
};
|
|
195
172
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
throw (0, errors_1.cliError)("COPILOT_CLI_MISSING", "The official Copilot CLI could not be resolved from the installed runtime or PATH.", {
|
|
199
|
-
cause: availability.cause,
|
|
200
|
-
source: availability.source ?? null,
|
|
201
|
-
command: availability.command ?? null,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
try {
|
|
205
|
-
(0, copilot_cli_1.runCopilotLogin)({ runtimesDir: paths.runtimesDir });
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
throw (0, errors_1.cliError)("COPILOT_LOGIN_LAUNCH_FAILED", "Failed to launch `copilot login`.", {
|
|
209
|
-
cause: error instanceof Error ? error.message : String(error),
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
try {
|
|
213
|
-
await (0, copilot_adapter_1.readCopilotAuthState)(paths.runtimesDir);
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
const normalized = (0, errors_1.normalizeError)(error);
|
|
217
|
-
if (normalized.code === "COPILOT_AUTH_REQUIRED") {
|
|
218
|
-
throw (0, errors_1.cliError)("COPILOT_LOGIN_RECHECK_FAILED", "Copilot login completed but auth readiness recheck still failed.", {
|
|
219
|
-
...(normalized.details ?? {}),
|
|
220
|
-
});
|
|
173
|
+
catch {
|
|
174
|
+
// Token is invalid, proceed with new login
|
|
221
175
|
}
|
|
222
|
-
throw error;
|
|
223
176
|
}
|
|
177
|
+
// Start GitHub OAuth Device Flow
|
|
178
|
+
runtime.writeLine("Starting GitHub authentication...");
|
|
179
|
+
const deviceFlow = await (0, copilot_token_1.startDeviceFlow)();
|
|
180
|
+
runtime.writeLine(`\nPlease visit: ${deviceFlow.verificationUri}`);
|
|
181
|
+
runtime.writeLine(`And enter code: ${deviceFlow.userCode}\n`);
|
|
182
|
+
runtime.writeLine("Waiting for authorization...");
|
|
183
|
+
const githubPat = await (0, copilot_token_1.pollDeviceFlowToken)(deviceFlow.deviceCode, deviceFlow.interval, deviceFlow.expiresIn);
|
|
184
|
+
// Validate the token by doing a test exchange
|
|
185
|
+
await (0, copilot_token_1.exchangeForCopilotToken)(githubPat);
|
|
186
|
+
(0, copilot_token_1.writeGithubToken)(githubPat, paths.toolHomeDir);
|
|
187
|
+
runtime.writeLine("GitHub Copilot authentication successful!");
|
|
224
188
|
return {
|
|
225
189
|
data: {
|
|
226
190
|
upstream: "github-copilot",
|
|
227
|
-
sdkInstalled: true,
|
|
228
|
-
sdkInstalledNow: installedNow,
|
|
229
191
|
authReady: true,
|
|
230
192
|
loginLaunched: true,
|
|
231
|
-
|
|
232
|
-
cliCommand: availability.command ?? null,
|
|
193
|
+
authSource: "github-pat",
|
|
233
194
|
},
|
|
234
195
|
};
|
|
235
196
|
}
|
|
@@ -267,6 +228,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
267
228
|
authPath: paths.authPath,
|
|
268
229
|
runtimeDir: paths.runtimeDir,
|
|
269
230
|
runtimesDir: paths.runtimesDir,
|
|
231
|
+
toolHomeDir: paths.toolHomeDir,
|
|
270
232
|
providerName,
|
|
271
233
|
});
|
|
272
234
|
}
|
|
@@ -507,6 +469,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
507
469
|
authPath: paths.authPath,
|
|
508
470
|
runtimeDir: paths.runtimeDir,
|
|
509
471
|
runtimesDir: paths.runtimesDir,
|
|
472
|
+
toolHomeDir: paths.toolHomeDir,
|
|
510
473
|
});
|
|
511
474
|
case "migrate": {
|
|
512
475
|
let codexDir = ctx.options.codexDir;
|
package/dist/domain/providers.js
CHANGED
|
@@ -88,7 +88,7 @@ function cleanProviderRecord(record) {
|
|
|
88
88
|
next.tags = record.tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0);
|
|
89
89
|
}
|
|
90
90
|
if (record.runtime) {
|
|
91
|
-
|
|
91
|
+
const cleanedRuntime = {
|
|
92
92
|
kind: record.runtime.kind,
|
|
93
93
|
upstream: record.runtime.upstream,
|
|
94
94
|
bridgeHost: record.runtime.bridgeHost.trim(),
|
|
@@ -96,8 +96,11 @@ function cleanProviderRecord(record) {
|
|
|
96
96
|
bridgePath: record.runtime.bridgePath,
|
|
97
97
|
premiumRequests: record.runtime.premiumRequests,
|
|
98
98
|
authSource: record.runtime.authSource,
|
|
99
|
-
sdkInstallMode: record.runtime.sdkInstallMode,
|
|
100
99
|
};
|
|
100
|
+
if (record.runtime.sdkInstallMode) {
|
|
101
|
+
cleanedRuntime.sdkInstallMode = record.runtime.sdkInstallMode;
|
|
102
|
+
}
|
|
103
|
+
next.runtime = cleanedRuntime;
|
|
101
104
|
}
|
|
102
105
|
return next;
|
|
103
106
|
}
|
|
@@ -148,10 +151,10 @@ function isRuntimeBackedProvider(provider) {
|
|
|
148
151
|
return Boolean(provider.runtime);
|
|
149
152
|
}
|
|
150
153
|
/**
|
|
151
|
-
* Returns whether one provider uses the GitHub Copilot
|
|
154
|
+
* Returns whether one provider uses the GitHub Copilot bridge runtime.
|
|
152
155
|
*/
|
|
153
156
|
function isCopilotBridgeProvider(provider) {
|
|
154
|
-
return provider.runtime?.kind === "copilot-sdk-bridge";
|
|
157
|
+
return provider.runtime?.kind === "copilot-http-proxy" || provider.runtime?.kind === "copilot-sdk-bridge";
|
|
155
158
|
}
|
|
156
159
|
/**
|
|
157
160
|
* Builds the canonical local bridge URL for one Copilot runtime provider.
|
|
@@ -194,7 +197,7 @@ function validateProviderRuntime(name, runtime) {
|
|
|
194
197
|
throw new Error(`Provider "${name}" has an invalid runtime block.`);
|
|
195
198
|
}
|
|
196
199
|
const record = runtime;
|
|
197
|
-
if (record.kind !== "copilot-sdk-bridge") {
|
|
200
|
+
if (record.kind !== "copilot-http-proxy" && record.kind !== "copilot-sdk-bridge") {
|
|
198
201
|
throw new Error(`Provider "${name}" has an unsupported runtime kind.`);
|
|
199
202
|
}
|
|
200
203
|
if (record.upstream !== "github-copilot") {
|
|
@@ -212,10 +215,7 @@ function validateProviderRuntime(name, runtime) {
|
|
|
212
215
|
if (record.premiumRequests !== true) {
|
|
213
216
|
throw new Error(`Provider "${name}" must enable runtime premiumRequests.`);
|
|
214
217
|
}
|
|
215
|
-
if (record.authSource !== "official-sdk") {
|
|
218
|
+
if (record.authSource !== "github-pat" && record.authSource !== "official-sdk") {
|
|
216
219
|
throw new Error(`Provider "${name}" has an invalid runtime authSource.`);
|
|
217
220
|
}
|
|
218
|
-
if (record.sdkInstallMode !== "lazy") {
|
|
219
|
-
throw new Error(`Provider "${name}" has an invalid runtime sdkInstallMode.`);
|
|
220
|
-
}
|
|
221
221
|
}
|