@minniexcode/codex-switch 0.0.8 → 0.0.10
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 -3
- package/README.CN.md +25 -3
- package/README.md +3 -2
- package/dist/app/add-provider.js +1 -12
- package/dist/app/bridge.js +295 -0
- package/dist/app/edit-provider.js +1 -17
- package/dist/app/get-status.js +32 -2
- package/dist/app/list-providers.js +0 -1
- package/dist/app/run-doctor.js +45 -38
- package/dist/app/setup-codex.js +27 -17
- package/dist/app/show-config.js +1 -5
- package/dist/app/switch-provider.js +33 -20
- package/dist/cli/output.js +4 -6
- package/dist/cli.js +1 -1
- package/dist/commands/handlers.js +223 -39
- package/dist/commands/help.js +1 -0
- package/dist/commands/registry.js +48 -4
- package/dist/domain/config.js +4 -68
- package/dist/domain/providers.js +0 -5
- package/dist/domain/runtime-state.js +2 -1
- package/dist/domain/setup.js +58 -3
- package/dist/interaction/add-interactive.js +55 -1
- package/dist/interaction/interactive.js +1 -5
- package/dist/runtime/copilot-adapter.js +44 -1
- package/dist/runtime/copilot-bridge-worker.js +1 -1
- package/dist/runtime/copilot-bridge.js +60 -19
- package/dist/runtime/copilot-cli.js +70 -0
- package/dist/runtime/copilot-installer.js +49 -2
- package/dist/storage/auth-repo.js +28 -77
- package/dist/storage/config-repo.js +1 -36
- package/dist/storage/runtime-state-repo.js +32 -0
- package/docs/Design/codex-switch-copilot-integration-design.md +517 -0
- package/docs/Design/codex-switch-v0.0.10-design.md +669 -0
- package/docs/Design/codex-switch-v0.0.9-design.md +182 -0
- package/docs/PRD/codex-switch-prd-v0.0.10.md +406 -0
- package/docs/PRD/codex-switch-prd-v0.0.9.md +166 -0
- package/docs/Tests/testing-bridge-v0.0.9.md +367 -0
- package/docs/cli-usage.md +38 -14
- package/docs/codex-switch-product-overview.md +2 -2
- package/docs/codex-switch-technical-architecture.md +6 -5
- package/package.json +1 -1
- /package/docs/{test-report-0.0.5.md → Tests/test-report-0.0.5.md} +0 -0
- /package/docs/{test-report-0.0.7.md → Tests/test-report-0.0.7.md} +0 -0
- /package/docs/{testing.md → Tests/testing.md} +0 -0
package/README.AI.md
CHANGED
|
@@ -54,7 +54,8 @@ Shared flags:
|
|
|
54
54
|
Operational model:
|
|
55
55
|
|
|
56
56
|
- `providers.json` is the management-state source of truth
|
|
57
|
-
- `config.toml`
|
|
57
|
+
- `config.toml` is the managed runtime-routing file
|
|
58
|
+
- `auth.json` is the active auth projection file for direct providers and is also inspected by status/doctor
|
|
58
59
|
- `backups/latest.json` tracks the latest rollback state
|
|
59
60
|
- mutating commands should back up first and run under a lightweight file lock
|
|
60
61
|
|
|
@@ -93,12 +94,13 @@ codexs status --json
|
|
|
93
94
|
Current package version in this repository:
|
|
94
95
|
|
|
95
96
|
```text
|
|
96
|
-
0.0.
|
|
97
|
+
0.0.10
|
|
97
98
|
```
|
|
98
99
|
|
|
99
100
|
Recent version summary:
|
|
100
101
|
|
|
101
|
-
- `0.0.
|
|
102
|
+
- `0.0.10`: `init` / `migrate` command split finalized, `setup` deprecated, and the managed provider model reduced to static profile plus `base_url` configuration
|
|
103
|
+
- `0.0.7`: command-surface refactor, env_key/auth-mirror model corrections, and the initial `setup` split toward `init` plus `migrate`
|
|
102
104
|
- `0.0.4`: setup/show/edit/backups list/specific rollback/import merge and clearer CLI semantics
|
|
103
105
|
- `0.0.3`: interactive TTY flows and improved help
|
|
104
106
|
- `0.0.2`: mutation orchestration, backups, rollback, locks, drift detection improvements
|
package/README.CN.md
CHANGED
|
@@ -17,30 +17,43 @@
|
|
|
17
17
|
|
|
18
18
|
## 现在可以做什么
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
当前命令面如下:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
+
codexs init
|
|
24
|
+
codexs migrate
|
|
23
25
|
codexs list
|
|
26
|
+
codexs show <provider>
|
|
24
27
|
codexs current
|
|
25
|
-
codexs switch <provider>
|
|
26
28
|
codexs status
|
|
29
|
+
codexs edit <provider>
|
|
30
|
+
codexs switch <provider>
|
|
27
31
|
codexs import <file>
|
|
28
32
|
codexs export <file>
|
|
29
33
|
codexs add <provider>
|
|
30
34
|
codexs remove <provider>
|
|
35
|
+
codexs backups list
|
|
31
36
|
codexs doctor
|
|
32
37
|
codexs rollback
|
|
38
|
+
codexs setup
|
|
33
39
|
```
|
|
34
40
|
|
|
35
41
|
对应能力包括:
|
|
36
42
|
|
|
43
|
+
- 初始化空的受管 `providers.json`
|
|
44
|
+
- 从已有 `config.toml` adopt 可管理的 runtime profile
|
|
37
45
|
- 查看本地已管理的 provider
|
|
46
|
+
- 查看单个 provider 详情
|
|
38
47
|
- 查看当前激活的 profile
|
|
48
|
+
- 查看本地运行态摘要
|
|
49
|
+
- 编辑已有 provider
|
|
39
50
|
- 安全切换 provider
|
|
40
51
|
- 导入和导出 provider 映射
|
|
41
52
|
- 新增和删除 provider
|
|
53
|
+
- 查看备份列表
|
|
42
54
|
- 检查配置漂移和常见本地问题
|
|
43
55
|
- 在变更前自动备份,并在失败时回滚
|
|
56
|
+
- 保留 `setup` 作为弃用入口,并引导到 `init` / `migrate`
|
|
44
57
|
|
|
45
58
|
## 简单用法
|
|
46
59
|
|
|
@@ -65,6 +78,8 @@ codexs --help
|
|
|
65
78
|
典型使用方式:
|
|
66
79
|
|
|
67
80
|
```bash
|
|
81
|
+
codexs init
|
|
82
|
+
codexs migrate
|
|
68
83
|
codexs list
|
|
69
84
|
codexs current
|
|
70
85
|
codexs add my-provider --profile my-provider --api-key sk-xxx
|
|
@@ -111,7 +126,8 @@ codexs status --json
|
|
|
111
126
|
存储模型:
|
|
112
127
|
|
|
113
128
|
- `providers.json` 是管理态的单一事实来源
|
|
114
|
-
- `config.toml`
|
|
129
|
+
- `config.toml` 是受管的运行时路由文件
|
|
130
|
+
- `auth.json` 是独立的 Codex 认证状态文件,`status` / `doctor` 只读检查它
|
|
115
131
|
- `backups/latest.json` 记录最近一次可回滚窗口
|
|
116
132
|
|
|
117
133
|
注意:`providers.json` 可能包含 API key,应视为本地敏感文件。
|
|
@@ -128,6 +144,12 @@ codexs status --json
|
|
|
128
144
|
|
|
129
145
|
## 最近 3 个版本更新
|
|
130
146
|
|
|
147
|
+
### 0.0.10
|
|
148
|
+
|
|
149
|
+
- 正式拆分 `setup`:新增 `init` 和 `migrate`,`setup` 变为弃用命令
|
|
150
|
+
- 增加 `show`、`edit`、`backups list` 等对当前命令面的整理
|
|
151
|
+
- 清理 provider/runtime 管理语义,CLI 只负责静态 profile 与 `base_url` 层配置
|
|
152
|
+
|
|
131
153
|
### 0.0.3
|
|
132
154
|
|
|
133
155
|
- 为 `add`、`switch`、`remove`、`import`、`export`、`rollback` 增加了交互式 TTY 流程
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ What it does:
|
|
|
18
18
|
- Run diagnostics and detect local drift
|
|
19
19
|
- List backups and roll back to a previous managed state
|
|
20
20
|
|
|
21
|
-
Current version: `0.0.
|
|
21
|
+
Current version: `0.0.10`
|
|
22
22
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
@@ -117,7 +117,8 @@ Managed files:
|
|
|
117
117
|
Notes:
|
|
118
118
|
|
|
119
119
|
- `providers.json` is the managed provider registry
|
|
120
|
-
- `config.toml`
|
|
120
|
+
- `config.toml` is the managed runtime-routing file
|
|
121
|
+
- `auth.json` is the active auth projection file; direct-provider switches rewrite `OPENAI_API_KEY`, while `status` and `doctor` inspect its state
|
|
121
122
|
- mutating commands back up before writing
|
|
122
123
|
- rollback is available after failed or undesired changes
|
|
123
124
|
|
package/dist/app/add-provider.js
CHANGED
|
@@ -41,7 +41,6 @@ const errors_1 = require("../domain/errors");
|
|
|
41
41
|
const config_repo_1 = require("../storage/config-repo");
|
|
42
42
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
43
43
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
44
|
-
const auth_repo_1 = require("../storage/auth-repo");
|
|
45
44
|
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
46
45
|
const run_mutation_1 = require("./run-mutation");
|
|
47
46
|
/**
|
|
@@ -54,7 +53,7 @@ function addProvider(args) {
|
|
|
54
53
|
throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Provider "${args.providerName}" already exists.`);
|
|
55
54
|
}
|
|
56
55
|
const bridgeHost = args.bridgeHost ?? "127.0.0.1";
|
|
57
|
-
const bridgePort = args.bridgePort ??
|
|
56
|
+
const bridgePort = args.bridgePort ?? 41415;
|
|
58
57
|
const runtime = args.copilot
|
|
59
58
|
? {
|
|
60
59
|
kind: "copilot-sdk-bridge",
|
|
@@ -102,14 +101,12 @@ function addProvider(args) {
|
|
|
102
101
|
? {
|
|
103
102
|
[args.profile]: {
|
|
104
103
|
baseUrl: args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined,
|
|
105
|
-
envKey: (0, config_1.buildManagedProfileEnvKey)(args.profile),
|
|
106
104
|
},
|
|
107
105
|
}
|
|
108
106
|
: undefined;
|
|
109
107
|
if (existingProfile) {
|
|
110
108
|
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
|
|
111
109
|
}
|
|
112
|
-
const envKey = existingModelProvider?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(args.profile);
|
|
113
110
|
const apiKey = args.copilot ? args.bridgeApiKey ?? crypto.randomBytes(24).toString("hex") : args.apiKey;
|
|
114
111
|
const baseUrl = args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined;
|
|
115
112
|
const next = {
|
|
@@ -118,7 +115,6 @@ function addProvider(args) {
|
|
|
118
115
|
[args.providerName]: (0, providers_1.cleanProviderRecord)({
|
|
119
116
|
profile: args.profile,
|
|
120
117
|
apiKey,
|
|
121
|
-
envKey,
|
|
122
118
|
baseUrl,
|
|
123
119
|
note: args.note ?? undefined,
|
|
124
120
|
tags: args.tags,
|
|
@@ -134,7 +130,6 @@ function addProvider(args) {
|
|
|
134
130
|
files: [
|
|
135
131
|
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
136
132
|
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
137
|
-
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
138
133
|
],
|
|
139
134
|
mutate: () => {
|
|
140
135
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
@@ -144,15 +139,9 @@ function addProvider(args) {
|
|
|
144
139
|
// Persist only the normalized provider payload so later reads are deterministic.
|
|
145
140
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
|
|
146
141
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
147
|
-
if (document.activeProfile === args.profile) {
|
|
148
|
-
const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(document, next);
|
|
149
|
-
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
150
|
-
(0, auth_repo_1.writeAuthFile)(args.authPath, next.providers[activeProviderName], existingAuth ?? undefined);
|
|
151
|
-
}
|
|
152
142
|
return {
|
|
153
143
|
provider: args.providerName,
|
|
154
144
|
profile: args.profile,
|
|
155
|
-
envKey,
|
|
156
145
|
runtimeKind: runtime?.kind ?? null,
|
|
157
146
|
createdProfileSections: configPlan.createdProfileSections,
|
|
158
147
|
createdModelProviderSections: configPlan.createdModelProviderSections,
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startBridge = startBridge;
|
|
4
|
+
exports.stopBridge = stopBridge;
|
|
5
|
+
exports.statusBridge = statusBridge;
|
|
6
|
+
const errors_1 = require("../domain/errors");
|
|
7
|
+
const providers_1 = require("../domain/providers");
|
|
8
|
+
const config_repo_1 = require("../storage/config-repo");
|
|
9
|
+
const providers_repo_1 = require("../storage/providers-repo");
|
|
10
|
+
const interactive_1 = require("../interaction/interactive");
|
|
11
|
+
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
12
|
+
const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
13
|
+
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
14
|
+
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
15
|
+
const DEFAULT_BRIDGE_PORT = 41415;
|
|
16
|
+
/**
|
|
17
|
+
* Starts or reuses the managed Copilot bridge for one provider.
|
|
18
|
+
*/
|
|
19
|
+
async function startBridge(args) {
|
|
20
|
+
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
21
|
+
const target = await resolveBridgeTarget({
|
|
22
|
+
requestedProviderName: args.providerName ?? null,
|
|
23
|
+
providers,
|
|
24
|
+
configPath: args.configPath,
|
|
25
|
+
runtime: args.runtime,
|
|
26
|
+
json: args.json,
|
|
27
|
+
commandName: "start",
|
|
28
|
+
preferRuntimeState: false,
|
|
29
|
+
});
|
|
30
|
+
await requireBridgeRuntimeReadiness();
|
|
31
|
+
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider);
|
|
32
|
+
const nextProvider = bridge.portChanged ? rewriteBridgeProviderPort(target.provider, bridge.port) : target.provider;
|
|
33
|
+
if (bridge.portChanged) {
|
|
34
|
+
try {
|
|
35
|
+
persistRecoveredBridgePort({
|
|
36
|
+
providersPath: args.providersPath,
|
|
37
|
+
configPath: args.configPath,
|
|
38
|
+
providers,
|
|
39
|
+
providerName: target.providerName,
|
|
40
|
+
previousProvider: target.provider,
|
|
41
|
+
provider: nextProvider,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (!bridge.reused) {
|
|
46
|
+
(0, copilot_bridge_1.stopCopilotBridge)();
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
data: {
|
|
53
|
+
provider: target.providerName,
|
|
54
|
+
profile: nextProvider.profile,
|
|
55
|
+
baseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(nextProvider.runtime),
|
|
56
|
+
host: bridge.host,
|
|
57
|
+
port: bridge.port,
|
|
58
|
+
reused: bridge.reused,
|
|
59
|
+
portChanged: bridge.portChanged,
|
|
60
|
+
defaultPort: DEFAULT_BRIDGE_PORT,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Stops the managed Copilot bridge.
|
|
66
|
+
*/
|
|
67
|
+
async function stopBridge(args) {
|
|
68
|
+
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
69
|
+
const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
70
|
+
if (!state && !args.providerName) {
|
|
71
|
+
return {
|
|
72
|
+
data: {
|
|
73
|
+
provider: null,
|
|
74
|
+
stopped: true,
|
|
75
|
+
hadRuntimeState: false,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (!state && args.providerName) {
|
|
80
|
+
resolveNamedBridgeProvider(providers, args.providerName);
|
|
81
|
+
return {
|
|
82
|
+
data: {
|
|
83
|
+
provider: args.providerName,
|
|
84
|
+
stopped: true,
|
|
85
|
+
hadRuntimeState: false,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const target = await resolveBridgeTarget({
|
|
90
|
+
requestedProviderName: args.providerName ?? null,
|
|
91
|
+
providers,
|
|
92
|
+
configPath: args.configPath,
|
|
93
|
+
runtime: args.runtime,
|
|
94
|
+
json: args.json,
|
|
95
|
+
commandName: "stop",
|
|
96
|
+
preferRuntimeState: true,
|
|
97
|
+
});
|
|
98
|
+
if (args.providerName && state?.provider && state.provider !== args.providerName) {
|
|
99
|
+
throw (0, errors_1.cliError)("BRIDGE_PROVIDER_MISMATCH", `Bridge runtime state belongs to "${state.provider}" not "${args.providerName}".`, {
|
|
100
|
+
stateProvider: state.provider,
|
|
101
|
+
requestedProvider: args.providerName,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
(0, copilot_bridge_1.stopCopilotBridge)();
|
|
105
|
+
return {
|
|
106
|
+
data: {
|
|
107
|
+
provider: target.providerName,
|
|
108
|
+
stopped: true,
|
|
109
|
+
hadRuntimeState: Boolean(state),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Reports the managed Copilot bridge state.
|
|
115
|
+
*/
|
|
116
|
+
async function statusBridge(args) {
|
|
117
|
+
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
118
|
+
const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
119
|
+
const target = await resolveBridgeTarget({
|
|
120
|
+
requestedProviderName: args.providerName ?? null,
|
|
121
|
+
providers,
|
|
122
|
+
configPath: args.configPath,
|
|
123
|
+
runtime: args.runtime,
|
|
124
|
+
json: args.json,
|
|
125
|
+
commandName: "status",
|
|
126
|
+
preferRuntimeState: true,
|
|
127
|
+
});
|
|
128
|
+
const provider = target.provider;
|
|
129
|
+
const runtimeStatus = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(provider);
|
|
130
|
+
const expectedBaseUrl = (0, providers_1.buildCopilotBridgeBaseUrl)(provider.runtime);
|
|
131
|
+
if (args.providerName && state?.provider && state.provider !== args.providerName) {
|
|
132
|
+
throw (0, errors_1.cliError)("BRIDGE_PROVIDER_MISMATCH", `Bridge runtime state belongs to "${state.provider}" not "${args.providerName}".`, {
|
|
133
|
+
stateProvider: state.provider,
|
|
134
|
+
requestedProvider: args.providerName,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
data: {
|
|
139
|
+
provider: target.providerName,
|
|
140
|
+
profile: provider.profile,
|
|
141
|
+
runtimeState: state,
|
|
142
|
+
expectedBaseUrl,
|
|
143
|
+
matches: Boolean(state && state.provider === target.providerName && state.baseUrl === expectedBaseUrl),
|
|
144
|
+
active: runtimeStatus.ok,
|
|
145
|
+
health: runtimeStatus,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Resolves the Copilot provider target for one bridge command.
|
|
151
|
+
*/
|
|
152
|
+
async function resolveBridgeTarget(args) {
|
|
153
|
+
if (args.requestedProviderName) {
|
|
154
|
+
return resolveNamedBridgeProvider(args.providers, args.requestedProviderName);
|
|
155
|
+
}
|
|
156
|
+
if (args.preferRuntimeState) {
|
|
157
|
+
const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
158
|
+
if (runtimeState?.provider && args.providers.providers[runtimeState.provider]) {
|
|
159
|
+
return resolveNamedBridgeProvider(args.providers, runtimeState.provider);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const activeTarget = resolveActiveCopilotBridgeProvider(args.providers, args.configPath);
|
|
163
|
+
if (activeTarget) {
|
|
164
|
+
return activeTarget;
|
|
165
|
+
}
|
|
166
|
+
const copilotTargets = listCopilotBridgeProviders(args.providers);
|
|
167
|
+
if (copilotTargets.length === 1) {
|
|
168
|
+
return copilotTargets[0];
|
|
169
|
+
}
|
|
170
|
+
if ((0, interactive_1.canPrompt)(args.runtime, args.json)) {
|
|
171
|
+
const selected = await promptForCopilotBridgeSelection(args.runtime, copilotTargets, args.commandName);
|
|
172
|
+
return resolveNamedBridgeProvider(args.providers, selected);
|
|
173
|
+
}
|
|
174
|
+
throw (0, errors_1.cliError)("BRIDGE_TARGET_UNRESOLVED", `Unable to resolve a Copilot provider for bridge ${args.commandName}.`, {
|
|
175
|
+
availableProviders: copilotTargets.map((entry) => entry.providerName),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Resolves the active provider when the current top-level profile maps to one Copilot bridge provider.
|
|
180
|
+
*/
|
|
181
|
+
function resolveActiveCopilotBridgeProvider(providers, configPath) {
|
|
182
|
+
try {
|
|
183
|
+
const currentProfile = (0, config_repo_1.readCurrentProfile)(configPath);
|
|
184
|
+
const matches = Object.entries(providers.providers)
|
|
185
|
+
.filter(([, provider]) => provider.profile === currentProfile && (0, providers_1.isCopilotBridgeProvider)(provider))
|
|
186
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
187
|
+
if (matches.length === 1) {
|
|
188
|
+
return {
|
|
189
|
+
providerName: matches[0][0],
|
|
190
|
+
provider: matches[0][1],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Resolves one named provider and enforces the Copilot bridge runtime kind.
|
|
201
|
+
*/
|
|
202
|
+
function resolveNamedBridgeProvider(providers, providerName) {
|
|
203
|
+
const provider = providers.providers[providerName];
|
|
204
|
+
if (!provider) {
|
|
205
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${providerName}" was not found.`, {
|
|
206
|
+
provider: providerName,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (!(0, providers_1.isCopilotBridgeProvider)(provider)) {
|
|
210
|
+
throw (0, errors_1.cliError)("BRIDGE_PROVIDER_MISMATCH", `Provider "${providerName}" is not a Copilot bridge provider.`, {
|
|
211
|
+
provider: providerName,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return { providerName, provider };
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Lists all configured Copilot bridge providers in stable order.
|
|
218
|
+
*/
|
|
219
|
+
function listCopilotBridgeProviders(providers) {
|
|
220
|
+
return Object.entries(providers.providers)
|
|
221
|
+
.filter(([, provider]) => (0, providers_1.isCopilotBridgeProvider)(provider))
|
|
222
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
223
|
+
.map(([providerName, provider]) => ({ providerName, provider }));
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Uses a Copilot-only provider picker instead of the generic provider selector.
|
|
227
|
+
*/
|
|
228
|
+
async function promptForCopilotBridgeSelection(runtime, targets, commandName) {
|
|
229
|
+
if (targets.length === 0) {
|
|
230
|
+
throw (0, errors_1.cliError)("BRIDGE_TARGET_UNRESOLVED", `No Copilot bridge providers are configured for bridge ${commandName}.`);
|
|
231
|
+
}
|
|
232
|
+
return runtime.selectOne(`Choose a Copilot provider to ${commandName}`, targets.map((target) => ({
|
|
233
|
+
value: target.providerName,
|
|
234
|
+
label: target.providerName,
|
|
235
|
+
hint: target.provider.profile,
|
|
236
|
+
})));
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Verifies that the local Copilot bridge prerequisites are available before startup.
|
|
240
|
+
*/
|
|
241
|
+
async function requireBridgeRuntimeReadiness() {
|
|
242
|
+
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
243
|
+
if (!installStatus.installed) {
|
|
244
|
+
throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
|
|
245
|
+
installDir: installStatus.installDir,
|
|
246
|
+
packageName: installStatus.packageName,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
await (0, copilot_adapter_1.readCopilotAuthState)();
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Rewrites one Copilot bridge provider record with a recovered runtime port.
|
|
253
|
+
*/
|
|
254
|
+
function rewriteBridgeProviderPort(provider, port) {
|
|
255
|
+
return (0, providers_1.cleanProviderRecord)({
|
|
256
|
+
...provider,
|
|
257
|
+
baseUrl: `http://${provider.runtime.bridgeHost}:${port}${provider.runtime.bridgePath}`,
|
|
258
|
+
runtime: {
|
|
259
|
+
...provider.runtime,
|
|
260
|
+
bridgePort: port,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Persists the recovered bridge port to both providers.json and config.toml.
|
|
266
|
+
*/
|
|
267
|
+
function persistRecoveredBridgePort(args) {
|
|
268
|
+
const previousProviders = {
|
|
269
|
+
providers: {
|
|
270
|
+
...args.providers.providers,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
const nextProviders = {
|
|
274
|
+
providers: {
|
|
275
|
+
...args.providers.providers,
|
|
276
|
+
[args.providerName]: args.provider,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
(0, providers_repo_1.writeProvidersFile)(args.providersPath, nextProviders);
|
|
280
|
+
try {
|
|
281
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
282
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
283
|
+
upsertModelProviders: {
|
|
284
|
+
[args.provider.profile]: {
|
|
285
|
+
baseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(args.provider.runtime),
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
(0, providers_repo_1.writeProvidersFile)(args.providersPath, previousProviders);
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -7,7 +7,6 @@ const providers_1 = require("../domain/providers");
|
|
|
7
7
|
const config_repo_1 = require("../storage/config-repo");
|
|
8
8
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
9
9
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
10
|
-
const auth_repo_1 = require("../storage/auth-repo");
|
|
11
10
|
const run_mutation_1 = require("./run-mutation");
|
|
12
11
|
/**
|
|
13
12
|
* Updates selected fields on a single managed provider.
|
|
@@ -63,23 +62,15 @@ function editProvider(args) {
|
|
|
63
62
|
upsertModelProviders = {
|
|
64
63
|
[newProfile]: {
|
|
65
64
|
baseUrl: args.baseUrl ?? undefined,
|
|
66
|
-
envKey: (0, config_1.buildManagedProfileEnvKey)(newProfile),
|
|
67
65
|
},
|
|
68
66
|
};
|
|
69
67
|
}
|
|
70
68
|
else {
|
|
71
69
|
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
|
|
72
70
|
}
|
|
73
|
-
const nextEnvKey = args.profile !== undefined && args.profile !== current.profile
|
|
74
|
-
? targetModelProviderSection?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(newProfile)
|
|
75
|
-
: current.envKey;
|
|
76
|
-
if (nextEnvKey !== current.envKey) {
|
|
77
|
-
updatedFields.push("envKey");
|
|
78
|
-
}
|
|
79
71
|
const nextRecord = (0, providers_1.cleanProviderRecord)({
|
|
80
72
|
profile: newProfile,
|
|
81
73
|
apiKey: args.apiKey ?? current.apiKey,
|
|
82
|
-
envKey: nextEnvKey,
|
|
83
74
|
baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
|
|
84
75
|
note: args.note === null ? undefined : args.note ?? current.note,
|
|
85
76
|
tags: args.tags ?? current.tags,
|
|
@@ -90,7 +81,7 @@ function editProvider(args) {
|
|
|
90
81
|
...(args.model !== undefined && args.model !== null ? { model: args.model } : {}),
|
|
91
82
|
},
|
|
92
83
|
};
|
|
93
|
-
if (args.model !== undefined &&
|
|
84
|
+
if (args.model !== undefined && targetSection?.model !== args.model && !updatedFields.includes("model")) {
|
|
94
85
|
updatedFields.push("model");
|
|
95
86
|
}
|
|
96
87
|
}
|
|
@@ -125,7 +116,6 @@ function editProvider(args) {
|
|
|
125
116
|
files: [
|
|
126
117
|
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
127
118
|
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
128
|
-
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
129
119
|
],
|
|
130
120
|
mutate: () => {
|
|
131
121
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
@@ -143,12 +133,6 @@ function editProvider(args) {
|
|
|
143
133
|
// Write providers first so the registry and config move together inside the managed backup boundary.
|
|
144
134
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, nextProviders);
|
|
145
135
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
146
|
-
const updatedDocument = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
147
|
-
if (updatedDocument.activeProfile) {
|
|
148
|
-
const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(updatedDocument, nextProviders);
|
|
149
|
-
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
150
|
-
(0, auth_repo_1.writeAuthFile)(args.authPath, nextProviders.providers[activeProviderName], existingAuth ?? undefined);
|
|
151
|
-
}
|
|
152
136
|
return {
|
|
153
137
|
provider: args.providerName,
|
|
154
138
|
updatedFields,
|
package/dist/app/get-status.js
CHANGED
|
@@ -44,6 +44,7 @@ const auth_repo_1 = require("../storage/auth-repo");
|
|
|
44
44
|
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
45
45
|
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
46
46
|
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
47
|
+
const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
47
48
|
/**
|
|
48
49
|
* Reports the current on-disk runtime state and how it maps back to managed providers.
|
|
49
50
|
*/
|
|
@@ -55,7 +56,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
55
56
|
const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
|
|
56
57
|
let configViews = [];
|
|
57
58
|
let consistencyIssues = [];
|
|
58
|
-
const authState = (0, auth_repo_1.
|
|
59
|
+
const authState = (0, auth_repo_1.readAuthFileState)(authPath);
|
|
59
60
|
if (configExists) {
|
|
60
61
|
const document = (0, config_repo_1.readStructuredConfig)(configPath);
|
|
61
62
|
currentProfile = document.activeProfile;
|
|
@@ -69,7 +70,32 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
69
70
|
const activeProviderCandidates = currentProfile && providers ? (0, providers_1.findProvidersByProfile)(providers, currentProfile) : [];
|
|
70
71
|
const activeProvider = activeProviderCandidates.length === 1 && providers ? providers.providers[activeProviderCandidates[0]] : null;
|
|
71
72
|
const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
72
|
-
const
|
|
73
|
+
const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)();
|
|
74
|
+
const runtimeState = runtimeStateInspection.state;
|
|
75
|
+
const runtimeStateProvider = runtimeState && providers ? providers.providers[runtimeState.provider] ?? null : null;
|
|
76
|
+
const bridgeProbeTarget = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
|
|
77
|
+
? activeProvider
|
|
78
|
+
: runtimeStateProvider && (0, providers_1.isCopilotBridgeProvider)(runtimeStateProvider)
|
|
79
|
+
? runtimeStateProvider
|
|
80
|
+
: null;
|
|
81
|
+
const copilotBridge = !runtimeStateInspection.valid && runtimeStateInspection.exists
|
|
82
|
+
? {
|
|
83
|
+
ok: false,
|
|
84
|
+
runtime: "copilot-bridge",
|
|
85
|
+
reason: "failed",
|
|
86
|
+
cause: runtimeStateInspection.parseError ?? "Failed to parse Copilot bridge runtime state.",
|
|
87
|
+
}
|
|
88
|
+
: bridgeProbeTarget
|
|
89
|
+
? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(bridgeProbeTarget, runtimeState)
|
|
90
|
+
: runtimeState
|
|
91
|
+
? {
|
|
92
|
+
ok: false,
|
|
93
|
+
runtime: "copilot-bridge",
|
|
94
|
+
reason: "failed",
|
|
95
|
+
cause: "Copilot bridge runtime state exists but no matching managed Copilot provider is active.",
|
|
96
|
+
details: runtimeState,
|
|
97
|
+
}
|
|
98
|
+
: null;
|
|
73
99
|
const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
|
|
74
100
|
? await (0, copilot_adapter_1.readCopilotAuthState)().catch((error) => ({
|
|
75
101
|
ready: false,
|
|
@@ -82,6 +108,9 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
82
108
|
// Surface unmanaged live state without mutating anything during a read-only status call.
|
|
83
109
|
warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
|
|
84
110
|
}
|
|
111
|
+
if (runtimeStateInspection.exists && !runtimeStateInspection.valid) {
|
|
112
|
+
warnings.push(`Copilot bridge runtime state is unreadable: ${runtimeStateInspection.parseError ?? "unknown parse failure"}`);
|
|
113
|
+
}
|
|
85
114
|
return {
|
|
86
115
|
warnings,
|
|
87
116
|
data: {
|
|
@@ -103,6 +132,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
103
132
|
},
|
|
104
133
|
copilotAuth,
|
|
105
134
|
copilotBridge,
|
|
135
|
+
copilotRuntimeState: runtimeState,
|
|
106
136
|
liveState,
|
|
107
137
|
auth: authState,
|
|
108
138
|
configProfiles: configViews,
|
|
@@ -11,7 +11,6 @@ function listProviders(providersPath) {
|
|
|
11
11
|
const items = names.map((name) => ({
|
|
12
12
|
name,
|
|
13
13
|
profile: providers.providers[name].profile,
|
|
14
|
-
envKey: providers.providers[name].envKey,
|
|
15
14
|
note: providers.providers[name].note ?? null,
|
|
16
15
|
tags: providers.providers[name].tags ?? [],
|
|
17
16
|
}));
|