@minniexcode/codex-switch 0.0.12 → 0.1.1
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 +37 -6
- package/README.CN.md +45 -11
- package/README.md +45 -13
- package/dist/app/add-provider.js +22 -24
- package/dist/app/edit-provider.js +34 -55
- package/dist/app/get-current-profile.js +15 -3
- package/dist/app/get-status.js +11 -8
- package/dist/app/list-config-profiles.js +3 -1
- package/dist/app/list-providers.js +10 -4
- package/dist/app/remove-provider.js +52 -19
- package/dist/app/run-doctor.js +29 -28
- package/dist/app/setup-codex.js +3 -3
- package/dist/app/show-config.js +3 -1
- package/dist/app/switch-provider.js +36 -5
- package/dist/cli/output.js +36 -18
- package/dist/commands/handlers.js +2 -2
- package/dist/commands/help.js +3 -3
- package/dist/commands/registry.js +35 -30
- package/dist/domain/config.js +250 -185
- package/dist/domain/providers.js +23 -0
- package/dist/domain/runtime-state.js +15 -15
- package/dist/domain/setup.js +3 -1
- package/dist/interaction/interactive.js +2 -2
- package/dist/runtime/codex-version.js +7 -0
- package/dist/storage/config-repo.js +6 -14
- package/docs/Design/codex-switch-v0.1.0-design.md +152 -0
- package/docs/Design/codex-switch-v0.1.1-design.md +33 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +217 -205
- package/docs/Reference/codex-config-reference.md +41 -0
- package/docs/Reference/codex-config-reference.zh-CN.md +41 -0
- package/docs/Tests/testing.md +31 -78
- package/docs/cli-usage.md +86 -27
- package/docs/codex-switch-command-design.md +649 -649
- package/docs/codex-switch-product-overview.md +81 -80
- package/docs/codex-switch-technical-architecture.md +1115 -1115
- package/package.json +51 -51
package/README.AI.md
CHANGED
|
@@ -6,12 +6,13 @@ 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.
|
|
10
|
-
- Version status:
|
|
9
|
+
- Current repository version: `0.1.1`
|
|
10
|
+
- Version status: stable release line
|
|
11
|
+
- Runtime contract target: Codex `0.134.0+`
|
|
11
12
|
|
|
12
13
|
## Product Role
|
|
13
14
|
|
|
14
|
-
`codex-switch` is a local-first TypeScript CLI that manages provider and
|
|
15
|
+
`codex-switch` is a local-first TypeScript CLI that manages provider and model-provider routing state for Codex while keeping tool-managed state separate from the target Codex runtime.
|
|
15
16
|
|
|
16
17
|
The managed source of truth is the tool home. Runtime files under the target Codex directory are projected outputs, not the main registry.
|
|
17
18
|
|
|
@@ -21,7 +22,7 @@ Direct provider workflow:
|
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
24
|
codexs init
|
|
24
|
-
codexs add <provider> --
|
|
25
|
+
codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
|
|
25
26
|
codexs switch <provider>
|
|
26
27
|
codexs status
|
|
27
28
|
codexs doctor
|
|
@@ -32,7 +33,7 @@ GitHub Copilot workflow:
|
|
|
32
33
|
```bash
|
|
33
34
|
codexs init
|
|
34
35
|
codexs login copilot
|
|
35
|
-
codexs add <provider> --copilot --
|
|
36
|
+
codexs add <provider> --copilot --model <model>
|
|
36
37
|
codexs switch <provider>
|
|
37
38
|
codexs status
|
|
38
39
|
codexs doctor
|
|
@@ -47,6 +48,36 @@ codexs migrate
|
|
|
47
48
|
|
|
48
49
|
`migrate` is not a fresh-install default. It is an advanced adopt helper for existing runtime state.
|
|
49
50
|
|
|
51
|
+
## Runtime Route Contract
|
|
52
|
+
|
|
53
|
+
For Codex `0.134.0+`, the live route is selected by:
|
|
54
|
+
|
|
55
|
+
- top-level `model`
|
|
56
|
+
- top-level `model_provider`
|
|
57
|
+
|
|
58
|
+
Important implications for automation:
|
|
59
|
+
|
|
60
|
+
- treat `model_provider` as the active provider selector
|
|
61
|
+
- treat `--profile` as an alias for a managed `model_provider` id
|
|
62
|
+
- do not describe top-level `profile` or `[profiles.*]` as the primary runtime path
|
|
63
|
+
- managed direct-provider projection does not keep `env_key` or `env_key_instructions`
|
|
64
|
+
- managed provider projection fixes `wire_api = "responses"` and `requires_openai_auth = true`
|
|
65
|
+
|
|
66
|
+
Expected managed direct-provider projection:
|
|
67
|
+
|
|
68
|
+
```toml
|
|
69
|
+
model = "gpt-5.5"
|
|
70
|
+
model_provider = "proxy"
|
|
71
|
+
|
|
72
|
+
[model_providers.proxy]
|
|
73
|
+
name = "proxy"
|
|
74
|
+
base_url = "https://proxy.example.com/v1"
|
|
75
|
+
wire_api = "responses"
|
|
76
|
+
requires_openai_auth = true
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Authentication remains projected through `auth.json` with `OPENAI_API_KEY`.
|
|
80
|
+
|
|
50
81
|
## Important Paths
|
|
51
82
|
|
|
52
83
|
Tool home:
|
|
@@ -99,7 +130,7 @@ Important behavioral constraints:
|
|
|
99
130
|
- `login copilot` requires a real TTY and does not support `--json`.
|
|
100
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.
|
|
101
132
|
- `add --copilot` assumes SDK install and upstream Copilot auth are already ready.
|
|
102
|
-
- `migrate`
|
|
133
|
+
- `migrate` remains interactive when provider adoption requires human input.
|
|
103
134
|
- `status` is the main dual-path summary command.
|
|
104
135
|
- `doctor` is the deeper repair-oriented diagnostic command.
|
|
105
136
|
|
package/README.CN.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# @minniexcode/codex-switch
|
|
2
2
|
|
|
3
|
-
`@minniexcode/codex-switch` 是一个本地优先的 CLI,用来安全地管理和切换 Codex 的 provider 与
|
|
3
|
+
`@minniexcode/codex-switch` 是一个本地优先的 CLI,用来安全地管理和切换 Codex 的 provider 与 model-provider 路由配置。
|
|
4
4
|
|
|
5
5
|
它把 `codex-switch` 自己的工具状态和目标 Codex runtime 明确分开,让 provider 管理、备份与 runtime 投影有一套受管流程,而不是依赖手工改文件。
|
|
6
6
|
|
|
7
7
|
## 版本定位
|
|
8
8
|
|
|
9
|
-
当前包版本:`0.
|
|
9
|
+
当前包版本:`0.1.1`
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
这是当前稳定发布线。`0.1.1` 的目标是把公开文档和 Codex `0.134.0+` 的 runtime contract 对齐,也就是用顶层 `model` 与 `model_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 --
|
|
37
|
+
codexs add 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 --
|
|
48
|
+
codexs add copilot-main --copilot --model gpt-4.1
|
|
49
49
|
codexs switch copilot-main
|
|
50
50
|
codexs status
|
|
51
51
|
codexs doctor
|
|
@@ -56,9 +56,43 @@ codexs doctor
|
|
|
56
56
|
- `init` 负责初始化 `codex-switch` 的 tool home 与受管状态文件。
|
|
57
57
|
- `login copilot` 负责上游 Copilot onboarding 和登录可用性检查。
|
|
58
58
|
- `add --copilot` 不负责替你登录,它假设上游 Copilot 已经 ready。
|
|
59
|
+
- `switch` 会把选中的 provider 投影到目标 Codex runtime 的顶层 `model` 与 `model_provider`。
|
|
59
60
|
- `status` 是切换后的主读取命令。
|
|
60
61
|
- `doctor` 是主诊断命令,用于解释问题和下一步修复动作。
|
|
61
62
|
|
|
63
|
+
## Runtime 路由模型
|
|
64
|
+
|
|
65
|
+
对于 Codex `0.134.0+`,活动 runtime route 由 `config.toml` 顶层的 `model` 和 `model_provider` 决定。
|
|
66
|
+
|
|
67
|
+
`codex-switch` 按这套 contract 管理运行态:
|
|
68
|
+
|
|
69
|
+
- 顶层 `model` 表示当前活动模型
|
|
70
|
+
- 顶层 `model_provider` 表示当前活动 provider route
|
|
71
|
+
- 受管的 `[model_providers.<id>]` 是 runtime provider 定义投影
|
|
72
|
+
- `--profile` 只作为受管 `model_provider` id 的 alias,不再是主 runtime selector
|
|
73
|
+
|
|
74
|
+
Direct provider 的运行态投影会写入:
|
|
75
|
+
|
|
76
|
+
- 顶层 `model`
|
|
77
|
+
- 顶层 `model_provider`
|
|
78
|
+
- `[model_providers.<id>]`
|
|
79
|
+
- 带 `OPENAI_API_KEY` 的 `auth.json`
|
|
80
|
+
|
|
81
|
+
受管 direct provider 投影不会再保留 `env_key` 或 `env_key_instructions`。`switch`、`add` 和 `edit` 会在写入活动路由前清理这些旧字段。
|
|
82
|
+
|
|
83
|
+
对受管的 OpenAI-compatible route,投影后的 provider 结构固定为:
|
|
84
|
+
|
|
85
|
+
```toml
|
|
86
|
+
model = "gpt-5.5"
|
|
87
|
+
model_provider = "my-provider"
|
|
88
|
+
|
|
89
|
+
[model_providers.my-provider]
|
|
90
|
+
name = "my-provider"
|
|
91
|
+
base_url = "https://gateway.example.com/v1"
|
|
92
|
+
wire_api = "responses"
|
|
93
|
+
requires_openai_auth = true
|
|
94
|
+
```
|
|
95
|
+
|
|
62
96
|
## Advanced Adopt 路径
|
|
63
97
|
|
|
64
98
|
如果你已经有现成的 Codex runtime 状态,希望把它 adopt 到受管 `providers.json`,再使用:
|
|
@@ -82,11 +116,11 @@ codexs current
|
|
|
82
116
|
codexs status
|
|
83
117
|
codexs config show [profile]
|
|
84
118
|
codexs config list-profiles
|
|
85
|
-
codexs add <provider> --
|
|
86
|
-
codexs add <provider> --copilot --
|
|
119
|
+
codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
|
|
120
|
+
codexs add <provider> --copilot --model <model>
|
|
87
121
|
codexs edit <provider>
|
|
88
122
|
codexs switch <provider>
|
|
89
|
-
codexs remove <provider> [--force] [--switch-to <
|
|
123
|
+
codexs remove <provider> [--force] [--switch-to <provider>]
|
|
90
124
|
codexs import <file>
|
|
91
125
|
codexs export <file> [--force]
|
|
92
126
|
codexs bridge start [provider]
|
|
@@ -151,7 +185,7 @@ tool home:
|
|
|
151
185
|
当前实现边界:
|
|
152
186
|
|
|
153
187
|
- `login copilot` 必须运行在真实 TTY 下,不支持 `--json`。
|
|
154
|
-
- `migrate`
|
|
188
|
+
- `migrate` 在需要人工补齐 adopt 信息时仍然保持交互式语义。
|
|
155
189
|
- 自动化调用应尽量显式传参,并优先使用 `--json`。
|
|
156
190
|
|
|
157
191
|
## 本地开发
|
|
@@ -171,8 +205,8 @@ npm pack --dry-run
|
|
|
171
205
|
- [详细 CLI 文档](./docs/cli-usage.md)
|
|
172
206
|
- [产品概览](./docs/codex-switch-product-overview.md)
|
|
173
207
|
- [测试说明](./docs/Tests/testing.md)
|
|
174
|
-
- [PRD 0.
|
|
175
|
-
- [Release
|
|
208
|
+
- [Release PRD 0.1.1](./docs/PRD/codex-switch-prd-v0.1.1.md)
|
|
209
|
+
- [Release Design 0.1.1](./docs/Design/codex-switch-v0.1.1-design.md)
|
|
176
210
|
|
|
177
211
|
## License
|
|
178
212
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @minniexcode/codex-switch
|
|
2
2
|
|
|
3
|
-
`@minniexcode/codex-switch` is a local-first CLI for managing and switching Codex provider and
|
|
3
|
+
`@minniexcode/codex-switch` is a local-first CLI for managing and switching Codex provider and model-provider routing safely.
|
|
4
4
|
|
|
5
5
|
It keeps `codex-switch` tool state separate from the target Codex runtime, so provider management, backup flow, and runtime projection stay explicit instead of relying on manual file edits.
|
|
6
6
|
|
|
@@ -8,9 +8,9 @@ Chinese version: [README.CN.md](./README.CN.md)
|
|
|
8
8
|
|
|
9
9
|
## Version
|
|
10
10
|
|
|
11
|
-
Current package version: `0.
|
|
11
|
+
Current package version: `0.1.1`
|
|
12
12
|
|
|
13
|
-
This
|
|
13
|
+
This is the current stable release line. `0.1.1` aligns the public docs with the Codex `0.134.0+` runtime contract where top-level `model` and `model_provider` select the active route.
|
|
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 --
|
|
39
|
+
codexs add 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 --
|
|
50
|
+
codexs add copilot-main --copilot --model gpt-4.1
|
|
51
51
|
codexs switch copilot-main
|
|
52
52
|
codexs status
|
|
53
53
|
codexs doctor
|
|
@@ -58,9 +58,43 @@ 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
|
+
- `switch` projects the selected provider into the target Codex runtime as top-level `model` plus `model_provider`.
|
|
61
62
|
- `status` is the main read command after switching.
|
|
62
63
|
- `doctor` is the main repair-oriented diagnostic command.
|
|
63
64
|
|
|
65
|
+
## Runtime Routing Model
|
|
66
|
+
|
|
67
|
+
For Codex `0.134.0+`, the active runtime route is selected through top-level `model` and `model_provider` in `config.toml`.
|
|
68
|
+
|
|
69
|
+
`codex-switch` treats that route as the runtime contract:
|
|
70
|
+
|
|
71
|
+
- top-level `model` selects the active model id
|
|
72
|
+
- top-level `model_provider` selects the active provider route
|
|
73
|
+
- managed `[model_providers.<id>]` entries are the projected runtime provider definitions
|
|
74
|
+
- `--profile` is only an alias for the managed `model_provider` id, not the primary runtime selector
|
|
75
|
+
|
|
76
|
+
Direct-provider projection writes:
|
|
77
|
+
|
|
78
|
+
- top-level `model`
|
|
79
|
+
- top-level `model_provider`
|
|
80
|
+
- `[model_providers.<id>]`
|
|
81
|
+
- `auth.json` with `OPENAI_API_KEY`
|
|
82
|
+
|
|
83
|
+
Managed direct-provider projection does not keep `env_key` or `env_key_instructions` in the generated runtime config. `switch`, `add`, and `edit` clean old legacy projection fields before writing the active route.
|
|
84
|
+
|
|
85
|
+
For managed OpenAI-compatible routes, the projected provider entry keeps the fixed runtime shape:
|
|
86
|
+
|
|
87
|
+
```toml
|
|
88
|
+
model = "gpt-5.5"
|
|
89
|
+
model_provider = "my-provider"
|
|
90
|
+
|
|
91
|
+
[model_providers.my-provider]
|
|
92
|
+
name = "my-provider"
|
|
93
|
+
base_url = "https://gateway.example.com/v1"
|
|
94
|
+
wire_api = "responses"
|
|
95
|
+
requires_openai_auth = true
|
|
96
|
+
```
|
|
97
|
+
|
|
64
98
|
## Advanced Adopt Workflow
|
|
65
99
|
|
|
66
100
|
Use `migrate` only when you already have Codex runtime state that should be adopted into managed `providers.json` state:
|
|
@@ -84,11 +118,11 @@ codexs current
|
|
|
84
118
|
codexs status
|
|
85
119
|
codexs config show [profile]
|
|
86
120
|
codexs config list-profiles
|
|
87
|
-
codexs add <provider> --
|
|
88
|
-
codexs add <provider> --copilot --
|
|
121
|
+
codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
|
|
122
|
+
codexs add <provider> --copilot --model <model>
|
|
89
123
|
codexs edit <provider>
|
|
90
124
|
codexs switch <provider>
|
|
91
|
-
codexs remove <provider> [--force] [--switch-to <
|
|
125
|
+
codexs remove <provider> [--force] [--switch-to <provider>]
|
|
92
126
|
codexs import <file>
|
|
93
127
|
codexs export <file> [--force]
|
|
94
128
|
codexs bridge start [provider]
|
|
@@ -103,8 +137,6 @@ codexs doctor
|
|
|
103
137
|
|
|
104
138
|
## Runtime Model
|
|
105
139
|
|
|
106
|
-
`codex-switch` uses a dual-path model.
|
|
107
|
-
|
|
108
140
|
Tool home:
|
|
109
141
|
|
|
110
142
|
```text
|
|
@@ -155,7 +187,7 @@ Global flags:
|
|
|
155
187
|
Operational limits:
|
|
156
188
|
|
|
157
189
|
- `login copilot` requires a real TTY and does not support `--json`.
|
|
158
|
-
- `migrate`
|
|
190
|
+
- `migrate` remains interactive when provider adoption requires human input.
|
|
159
191
|
- Automation should pass explicit arguments and prefer `--json` for stable parsing.
|
|
160
192
|
|
|
161
193
|
## Local Development
|
|
@@ -175,8 +207,8 @@ npm pack --dry-run
|
|
|
175
207
|
- [Detailed CLI Usage](./docs/cli-usage.md)
|
|
176
208
|
- [Testing Guide](./docs/Tests/testing.md)
|
|
177
209
|
- [Product Overview](./docs/codex-switch-product-overview.md)
|
|
178
|
-
- [PRD 0.
|
|
179
|
-
- [Release
|
|
210
|
+
- [Release PRD 0.1.1](./docs/PRD/codex-switch-prd-v0.1.1.md)
|
|
211
|
+
- [Release Design 0.1.1](./docs/Design/codex-switch-v0.1.1-design.md)
|
|
180
212
|
|
|
181
213
|
## License
|
|
182
214
|
|
package/dist/app/add-provider.js
CHANGED
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.addProvider = addProvider;
|
|
37
37
|
const crypto = __importStar(require("node:crypto"));
|
|
38
|
-
const config_1 = require("../domain/config");
|
|
39
38
|
const providers_1 = require("../domain/providers");
|
|
40
39
|
const errors_1 = require("../domain/errors");
|
|
41
40
|
const config_repo_1 = require("../storage/config-repo");
|
|
@@ -91,36 +90,32 @@ async function addProvider(args) {
|
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
92
|
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
94
|
-
const existingProfile = document.profiles.find((profile) => profile.name === args.profile);
|
|
95
93
|
const existingModelProvider = document.modelProviders.find((entry) => entry.name === args.profile);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
const inheritedModel = document.currentModel ?? undefined;
|
|
95
|
+
const providerModel = args.model ?? inheritedModel;
|
|
96
|
+
if (!providerModel) {
|
|
97
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Provider "${args.providerName}" requires a model.`, {
|
|
99
98
|
provider: args.providerName,
|
|
99
|
+
modelProvider: args.profile,
|
|
100
|
+
missingFields: ["model"],
|
|
101
|
+
suggestion: "Pass `--model <name>` or set a top-level model in config.toml first.",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const directBaseUrl = args.baseUrl;
|
|
105
|
+
if (!args.copilot && (!directBaseUrl || directBaseUrl.trim() === "") && !existingModelProvider) {
|
|
106
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${args.profile}" requires base_url.`, {
|
|
107
|
+
profile: args.profile,
|
|
108
|
+
modelProvider: args.profile,
|
|
109
|
+
missingFields: ["base_url"],
|
|
100
110
|
});
|
|
101
111
|
}
|
|
102
|
-
const upsertProfiles = !existingProfile && args.createProfile
|
|
103
|
-
? {
|
|
104
|
-
[args.profile]: (0, config_1.validateManagedProfileCreation)(args.profile, {
|
|
105
|
-
model: args.model ?? undefined,
|
|
106
|
-
modelProvider: args.profile,
|
|
107
|
-
}),
|
|
108
|
-
}
|
|
109
|
-
: undefined;
|
|
110
112
|
const upsertModelProviders = args.copilot
|
|
111
113
|
? {
|
|
112
114
|
[args.profile]: (0, providers_1.buildCopilotModelProviderProjection)(runtime),
|
|
113
115
|
}
|
|
114
|
-
:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
baseUrl: args.baseUrl ?? undefined,
|
|
118
|
-
},
|
|
119
|
-
}
|
|
120
|
-
: undefined;
|
|
121
|
-
if (existingProfile) {
|
|
122
|
-
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
|
|
123
|
-
}
|
|
116
|
+
: {
|
|
117
|
+
[args.profile]: (0, providers_1.buildDirectModelProviderProjection)(args.profile, (directBaseUrl ?? existingModelProvider?.baseUrl ?? "").trim()),
|
|
118
|
+
};
|
|
124
119
|
const apiKey = args.copilot ? args.bridgeApiKey ?? crypto.randomBytes(24).toString("hex") : args.apiKey;
|
|
125
120
|
const baseUrl = args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined;
|
|
126
121
|
const next = {
|
|
@@ -129,6 +124,7 @@ async function addProvider(args) {
|
|
|
129
124
|
[args.providerName]: (0, providers_1.cleanProviderRecord)({
|
|
130
125
|
profile: args.profile,
|
|
131
126
|
apiKey,
|
|
127
|
+
model: providerModel,
|
|
132
128
|
baseUrl,
|
|
133
129
|
note: args.note ?? undefined,
|
|
134
130
|
tags: args.tags,
|
|
@@ -147,14 +143,16 @@ async function addProvider(args) {
|
|
|
147
143
|
],
|
|
148
144
|
mutate: () => {
|
|
149
145
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
150
|
-
upsertProfiles,
|
|
151
146
|
upsertModelProviders,
|
|
147
|
+
scrubModelProviderEnvKeys: [args.profile],
|
|
152
148
|
});
|
|
153
149
|
// Persist only the normalized provider payload so later reads are deterministic.
|
|
154
150
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
|
|
155
151
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
156
152
|
return {
|
|
157
153
|
provider: args.providerName,
|
|
154
|
+
model: providerModel,
|
|
155
|
+
modelProvider: args.profile,
|
|
158
156
|
profile: args.profile,
|
|
159
157
|
runtimeKind: runtime?.kind ?? null,
|
|
160
158
|
createdProfileSections: configPlan.createdProfileSections,
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.editProvider = editProvider;
|
|
4
4
|
const errors_1 = require("../domain/errors");
|
|
5
|
-
const config_1 = require("../domain/config");
|
|
6
5
|
const providers_1 = require("../domain/providers");
|
|
7
6
|
const config_repo_1 = require("../storage/config-repo");
|
|
8
7
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
@@ -24,6 +23,7 @@ function editProvider(args) {
|
|
|
24
23
|
}
|
|
25
24
|
const updatedFields = [];
|
|
26
25
|
const nextProfile = args.profile ?? current.profile;
|
|
26
|
+
const nextModel = args.model === null ? undefined : args.model ?? current.model;
|
|
27
27
|
if (args.profile !== undefined && args.profile !== current.profile) {
|
|
28
28
|
updatedFields.push("profile");
|
|
29
29
|
}
|
|
@@ -33,6 +33,9 @@ function editProvider(args) {
|
|
|
33
33
|
if (args.baseUrl !== undefined && (args.baseUrl ?? undefined) !== current.baseUrl) {
|
|
34
34
|
updatedFields.push("baseUrl");
|
|
35
35
|
}
|
|
36
|
+
if (args.model !== undefined && args.model !== current.model) {
|
|
37
|
+
updatedFields.push("model");
|
|
38
|
+
}
|
|
36
39
|
if (args.note !== undefined && (args.note ?? undefined) !== current.note) {
|
|
37
40
|
updatedFields.push("note");
|
|
38
41
|
}
|
|
@@ -41,73 +44,46 @@ function editProvider(args) {
|
|
|
41
44
|
}
|
|
42
45
|
const oldProfile = current.profile;
|
|
43
46
|
const newProfile = nextProfile;
|
|
44
|
-
const targetSection = document.profiles.find((profile) => profile.name === newProfile) ?? null;
|
|
45
47
|
const targetModelProviderSection = document.modelProviders.find((entry) => entry.name === newProfile) ?? null;
|
|
46
|
-
const targetProfileExists = Boolean(targetSection);
|
|
47
|
-
let upsertProfiles;
|
|
48
48
|
let upsertModelProviders;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const resolvedBaseUrl = (args.baseUrl ?? current.baseUrl ?? targetModelProviderSection?.baseUrl ?? "").trim();
|
|
50
|
+
if (!current.runtime) {
|
|
51
|
+
if (!resolvedBaseUrl) {
|
|
52
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${newProfile}" requires base_url.`, {
|
|
52
53
|
profile: newProfile,
|
|
53
|
-
|
|
54
|
+
modelProvider: newProfile,
|
|
55
|
+
missingFields: ["base_url"],
|
|
54
56
|
});
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
[newProfile]: (0,
|
|
58
|
-
model: args.model ?? undefined,
|
|
59
|
-
modelProvider: newProfile,
|
|
60
|
-
}),
|
|
58
|
+
upsertModelProviders = {
|
|
59
|
+
[newProfile]: (0, providers_1.buildDirectModelProviderProjection)(newProfile, resolvedBaseUrl),
|
|
61
60
|
};
|
|
61
|
+
}
|
|
62
|
+
else if (targetModelProviderSection || args.profile !== undefined) {
|
|
62
63
|
upsertModelProviders = {
|
|
64
|
+
...(upsertModelProviders ?? {}),
|
|
63
65
|
[newProfile]: {
|
|
64
|
-
|
|
66
|
+
...(current.runtime
|
|
67
|
+
? {
|
|
68
|
+
baseUrl: current.baseUrl ?? targetModelProviderSection?.baseUrl ?? "",
|
|
69
|
+
name: "copilot",
|
|
70
|
+
requiresOpenAiAuth: true,
|
|
71
|
+
wireApi: "responses",
|
|
72
|
+
}
|
|
73
|
+
: (0, providers_1.buildDirectModelProviderProjection)(newProfile, resolvedBaseUrl)),
|
|
65
74
|
},
|
|
66
75
|
};
|
|
67
76
|
}
|
|
68
|
-
else {
|
|
69
|
-
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
|
|
70
|
-
}
|
|
71
77
|
const nextRecord = (0, providers_1.cleanProviderRecord)({
|
|
72
78
|
profile: newProfile,
|
|
73
79
|
apiKey: args.apiKey ?? current.apiKey,
|
|
80
|
+
model: nextModel,
|
|
74
81
|
baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
|
|
75
82
|
note: args.note === null ? undefined : args.note ?? current.note,
|
|
76
83
|
tags: args.tags ?? current.tags,
|
|
84
|
+
runtime: current.runtime,
|
|
77
85
|
});
|
|
78
|
-
|
|
79
|
-
upsertProfiles = {
|
|
80
|
-
[newProfile]: {
|
|
81
|
-
...(args.model !== undefined && args.model !== null ? { model: args.model } : {}),
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
if (args.model !== undefined && targetSection?.model !== args.model && !updatedFields.includes("model")) {
|
|
85
|
-
updatedFields.push("model");
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// Compute profile link ownership after the edit so lifecycle planning can decide whether sections stay, move, or delete.
|
|
89
|
-
const remainingLinksByProfile = new Map();
|
|
90
|
-
for (const [name, provider] of Object.entries(providers.providers)) {
|
|
91
|
-
if (name === args.providerName) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const list = remainingLinksByProfile.get(provider.profile) ?? [];
|
|
95
|
-
list.push(name);
|
|
96
|
-
remainingLinksByProfile.set(provider.profile, list);
|
|
97
|
-
}
|
|
98
|
-
if (newProfile !== oldProfile) {
|
|
99
|
-
const list = remainingLinksByProfile.get(newProfile) ?? [];
|
|
100
|
-
list.push(args.providerName);
|
|
101
|
-
remainingLinksByProfile.set(newProfile, list);
|
|
102
|
-
}
|
|
103
|
-
const lifecycle = (0, config_1.planProfileLifecycleOutcome)({
|
|
104
|
-
providerName: args.providerName,
|
|
105
|
-
oldProfile,
|
|
106
|
-
newProfile,
|
|
107
|
-
activeProfile: document.activeProfile,
|
|
108
|
-
remainingLinksByProfile,
|
|
109
|
-
switchToProfile: args.switchToProfile ?? null,
|
|
110
|
-
});
|
|
86
|
+
const isActive = document.currentModelProvider === oldProfile;
|
|
111
87
|
return (0, run_mutation_1.runMutation)({
|
|
112
88
|
lockPath: args.lockPath,
|
|
113
89
|
backupsDir: args.backupsDir,
|
|
@@ -119,10 +95,12 @@ function editProvider(args) {
|
|
|
119
95
|
],
|
|
120
96
|
mutate: () => {
|
|
121
97
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
122
|
-
upsertProfiles,
|
|
123
98
|
upsertModelProviders,
|
|
124
|
-
|
|
125
|
-
|
|
99
|
+
setCurrentModel: isActive ? nextModel ?? document.currentModel : undefined,
|
|
100
|
+
setCurrentModelProvider: isActive ? newProfile : undefined,
|
|
101
|
+
deleteLegacyProfile: isActive,
|
|
102
|
+
deleteLegacyProfilesByName: isActive ? [newProfile] : [],
|
|
103
|
+
scrubModelProviderEnvKeys: [newProfile],
|
|
126
104
|
});
|
|
127
105
|
const nextProviders = {
|
|
128
106
|
providers: {
|
|
@@ -135,12 +113,13 @@ function editProvider(args) {
|
|
|
135
113
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
136
114
|
return {
|
|
137
115
|
provider: args.providerName,
|
|
116
|
+
modelProvider: newProfile,
|
|
138
117
|
updatedFields,
|
|
139
118
|
createdProfileSections: configPlan.createdProfileSections,
|
|
140
119
|
createdModelProviderSections: configPlan.createdModelProviderSections,
|
|
141
120
|
deletedProfileSections: configPlan.deletedProfileSections,
|
|
142
|
-
keptSharedProfiles:
|
|
143
|
-
switchedActiveProfile:
|
|
121
|
+
keptSharedProfiles: [],
|
|
122
|
+
switchedActiveProfile: isActive && newProfile !== oldProfile,
|
|
144
123
|
adoptedProfiles: [],
|
|
145
124
|
repairedProfiles: [],
|
|
146
125
|
};
|
|
@@ -2,13 +2,25 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getCurrentProfile = getCurrentProfile;
|
|
4
4
|
const config_repo_1 = require("../storage/config-repo");
|
|
5
|
+
const providers_repo_1 = require("../storage/providers-repo");
|
|
5
6
|
/**
|
|
6
|
-
* Returns the currently active top-level Codex
|
|
7
|
+
* Returns the currently active top-level Codex route.
|
|
7
8
|
*/
|
|
8
|
-
function getCurrentProfile(configPath) {
|
|
9
|
+
function getCurrentProfile(configPath, providersPath) {
|
|
10
|
+
const document = (0, config_repo_1.readStructuredConfig)(configPath);
|
|
11
|
+
const providers = providersPath ? (0, providers_repo_1.readProvidersFileIfExists)(providersPath) : null;
|
|
12
|
+
const providerCandidates = document.currentModelProvider && providers
|
|
13
|
+
? Object.entries(providers.providers)
|
|
14
|
+
.filter(([, provider]) => provider.profile === document.currentModelProvider)
|
|
15
|
+
.map(([name]) => name)
|
|
16
|
+
.sort()
|
|
17
|
+
: [];
|
|
9
18
|
return {
|
|
10
19
|
data: {
|
|
11
|
-
|
|
20
|
+
model: document.currentModel,
|
|
21
|
+
modelProvider: document.currentModelProvider,
|
|
22
|
+
provider: providerCandidates.length === 1 ? providerCandidates[0] : null,
|
|
23
|
+
profile: document.currentModelProvider,
|
|
12
24
|
},
|
|
13
25
|
};
|
|
14
26
|
}
|
package/dist/app/get-status.js
CHANGED
|
@@ -51,7 +51,8 @@ const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
|
51
51
|
async function getStatus(codexDir, configPath, providersPath, authPath, options) {
|
|
52
52
|
const configExists = fs.existsSync(configPath);
|
|
53
53
|
const providersExists = fs.existsSync(providersPath);
|
|
54
|
-
let
|
|
54
|
+
let currentModelProvider = null;
|
|
55
|
+
let currentModel = null;
|
|
55
56
|
const warnings = [];
|
|
56
57
|
const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
|
|
57
58
|
let configViews = [];
|
|
@@ -59,14 +60,15 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
|
|
|
59
60
|
const authState = (0, auth_repo_1.readAuthFileState)(authPath);
|
|
60
61
|
if (configExists) {
|
|
61
62
|
const document = (0, config_repo_1.readStructuredConfig)(configPath);
|
|
62
|
-
|
|
63
|
+
currentModel = document.currentModel;
|
|
64
|
+
currentModelProvider = document.currentModelProvider;
|
|
63
65
|
configViews = (0, config_1.buildManagedProfileViews)(document, providers);
|
|
64
66
|
consistencyIssues = (0, config_1.collectConfigConsistencyIssues)(document, providers);
|
|
65
|
-
if (!
|
|
66
|
-
warnings.push("config.toml exists but has no top-level
|
|
67
|
+
if (!currentModelProvider) {
|
|
68
|
+
warnings.push("config.toml exists but has no top-level model_provider.");
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
|
-
const liveState = (0, runtime_state_1.inspectLiveStateDrift)(
|
|
71
|
+
const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentModelProvider, providers);
|
|
70
72
|
const activeProviderCandidates = liveState.mappedProviders;
|
|
71
73
|
const activeProvider = liveState.providerResolvable && providers && liveState.mappedProvider
|
|
72
74
|
? providers.providers[liveState.mappedProvider]
|
|
@@ -111,7 +113,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
|
|
|
111
113
|
warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
|
|
112
114
|
}
|
|
113
115
|
if (liveState.reason === "shared-profile") {
|
|
114
|
-
warnings.push(`Current
|
|
116
|
+
warnings.push(`Current model provider "${currentModelProvider}" is shared by multiple providers in providers.json, so the active provider cannot be resolved uniquely.`);
|
|
115
117
|
}
|
|
116
118
|
if (runtimeStateInspection.exists && !runtimeStateInspection.valid) {
|
|
117
119
|
warnings.push(`Copilot bridge runtime state is unreadable: ${runtimeStateInspection.parseError ?? "unknown parse failure"}`);
|
|
@@ -130,8 +132,9 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
|
|
|
130
132
|
}),
|
|
131
133
|
configExists,
|
|
132
134
|
providersExists,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
currentModelProvider,
|
|
136
|
+
currentModelProviderMapped: liveState.modelProviderMapped,
|
|
137
|
+
currentModel,
|
|
135
138
|
provider: liveState.mappedProvider,
|
|
136
139
|
activeProviderResolvable: liveState.providerResolvable,
|
|
137
140
|
activeProviderCandidates,
|
|
@@ -22,7 +22,9 @@ function listConfigProfilesView(args) {
|
|
|
22
22
|
}));
|
|
23
23
|
return {
|
|
24
24
|
data: {
|
|
25
|
-
|
|
25
|
+
currentModel: document.currentModel,
|
|
26
|
+
currentModelProvider: document.currentModelProvider,
|
|
27
|
+
legacyProfile: document.legacyProfile,
|
|
26
28
|
profiles,
|
|
27
29
|
count: profiles.length,
|
|
28
30
|
},
|