@minniexcode/codex-switch 0.1.0 → 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 +141 -110
- package/README.CN.md +213 -179
- package/README.md +215 -183
- package/dist/app/add-provider.js +15 -23
- package/dist/app/edit-provider.js +30 -65
- 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 +26 -29
- 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 +29 -19
- package/dist/commands/handlers.js +2 -2
- package/dist/commands/help.js +3 -3
- package/dist/commands/registry.js +29 -29
- package/dist/domain/config.js +249 -209
- package/dist/domain/providers.js +7 -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.1-design.md +33 -0
- package/docs/Reference/codex-config-reference.md +41 -0
- package/docs/Reference/codex-config-reference.zh-CN.md +41 -0
- package/docs/cli-usage.md +282 -223
- package/docs/codex-switch-command-design.md +1 -1
- package/docs/codex-switch-product-overview.md +13 -12
- package/docs/codex-switch-technical-architecture.md +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,183 +1,215 @@
|
|
|
1
|
-
# @minniexcode/codex-switch
|
|
2
|
-
|
|
3
|
-
`@minniexcode/codex-switch` is a local-first CLI for managing and switching Codex provider and
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
Chinese version: [README.CN.md](./README.CN.md)
|
|
8
|
-
|
|
9
|
-
## Version
|
|
10
|
-
|
|
11
|
-
Current package version: `0.1.
|
|
12
|
-
|
|
13
|
-
This is the
|
|
14
|
-
|
|
15
|
-
## Install
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install -g @minniexcode/codex-switch
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Run without a global install:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npx @minniexcode/codex-switch --help
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Built CLI entrypoint:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
codexs --help
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Primary Workflows
|
|
34
|
-
|
|
35
|
-
Direct provider workflow:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
codexs init
|
|
39
|
-
codexs add my-provider --
|
|
40
|
-
codexs switch my-provider
|
|
41
|
-
codexs status
|
|
42
|
-
codexs doctor
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
GitHub Copilot workflow:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
codexs init
|
|
49
|
-
codexs login copilot
|
|
50
|
-
codexs add copilot-main --copilot --
|
|
51
|
-
codexs switch copilot-main
|
|
52
|
-
codexs status
|
|
53
|
-
codexs doctor
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Notes:
|
|
57
|
-
|
|
58
|
-
- `init` prepares the `codex-switch` tool home and managed state.
|
|
59
|
-
- `login copilot` handles upstream Copilot onboarding and auth readiness.
|
|
60
|
-
- `add --copilot` does not perform login for you; it assumes Copilot login is already ready.
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
`
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1
|
+
# @minniexcode/codex-switch
|
|
2
|
+
|
|
3
|
+
`@minniexcode/codex-switch` is a local-first CLI for managing and switching Codex provider and model-provider routing safely.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
Chinese version: [README.CN.md](./README.CN.md)
|
|
8
|
+
|
|
9
|
+
## Version
|
|
10
|
+
|
|
11
|
+
Current package version: `0.1.1`
|
|
12
|
+
|
|
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
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @minniexcode/codex-switch
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Run without a global install:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @minniexcode/codex-switch --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Built CLI entrypoint:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
codexs --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Primary Workflows
|
|
34
|
+
|
|
35
|
+
Direct provider workflow:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
codexs init
|
|
39
|
+
codexs add my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
|
|
40
|
+
codexs switch my-provider
|
|
41
|
+
codexs status
|
|
42
|
+
codexs doctor
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
GitHub Copilot workflow:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
codexs init
|
|
49
|
+
codexs login copilot
|
|
50
|
+
codexs add copilot-main --copilot --model gpt-4.1
|
|
51
|
+
codexs switch copilot-main
|
|
52
|
+
codexs status
|
|
53
|
+
codexs doctor
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Notes:
|
|
57
|
+
|
|
58
|
+
- `init` prepares the `codex-switch` tool home and managed state.
|
|
59
|
+
- `login copilot` handles upstream Copilot onboarding and auth readiness.
|
|
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`.
|
|
62
|
+
- `status` is the main read command after switching.
|
|
63
|
+
- `doctor` is the main repair-oriented diagnostic command.
|
|
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
|
+
|
|
98
|
+
## Advanced Adopt Workflow
|
|
99
|
+
|
|
100
|
+
Use `migrate` only when you already have Codex runtime state that should be adopted into managed `providers.json` state:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
codexs init
|
|
104
|
+
codexs migrate
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`migrate` is an advanced adopt helper. It is not the default first step for a fresh install.
|
|
108
|
+
|
|
109
|
+
## Command Surface
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
codexs init
|
|
113
|
+
codexs login copilot
|
|
114
|
+
codexs migrate
|
|
115
|
+
codexs list
|
|
116
|
+
codexs show <provider>
|
|
117
|
+
codexs current
|
|
118
|
+
codexs status
|
|
119
|
+
codexs config show [profile]
|
|
120
|
+
codexs config list-profiles
|
|
121
|
+
codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
|
|
122
|
+
codexs add <provider> --copilot --model <model>
|
|
123
|
+
codexs edit <provider>
|
|
124
|
+
codexs switch <provider>
|
|
125
|
+
codexs remove <provider> [--force] [--switch-to <provider>]
|
|
126
|
+
codexs import <file>
|
|
127
|
+
codexs export <file> [--force]
|
|
128
|
+
codexs bridge start [provider]
|
|
129
|
+
codexs bridge status [provider]
|
|
130
|
+
codexs bridge stop [provider]
|
|
131
|
+
codexs backups list
|
|
132
|
+
codexs rollback [backup-id]
|
|
133
|
+
codexs doctor
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`setup` still exists only as a deprecated compatibility entry that points callers to `init` or `migrate`.
|
|
137
|
+
|
|
138
|
+
## Runtime Model
|
|
139
|
+
|
|
140
|
+
Tool home:
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
~/.config/codex-switch/
|
|
144
|
+
codex-switch.json
|
|
145
|
+
providers.json
|
|
146
|
+
backups/
|
|
147
|
+
runtime/
|
|
148
|
+
runtimes/
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Target Codex runtime:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
~/.codex/
|
|
155
|
+
config.toml
|
|
156
|
+
auth.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Key points:
|
|
160
|
+
|
|
161
|
+
- `providers.json` is the managed provider registry and lives under the tool home.
|
|
162
|
+
- `codex-switch.json` stores tool-level metadata such as `defaultCodexDir`.
|
|
163
|
+
- `config.toml` remains the active runtime routing file in the target Codex directory.
|
|
164
|
+
- `auth.json` remains the active auth projection file in the target Codex directory.
|
|
165
|
+
- Direct providers rewrite `OPENAI_API_KEY` into the active runtime projection.
|
|
166
|
+
- Copilot providers keep upstream GitHub authentication in the official Copilot runtime while `codex-switch` manages local bridge state and routing.
|
|
167
|
+
|
|
168
|
+
Path controls:
|
|
169
|
+
|
|
170
|
+
- `--codex-dir <path>` targets a specific Codex runtime directory.
|
|
171
|
+
- `CODEXS_CODEX_DIR` provides the default target runtime when `--codex-dir` is not passed.
|
|
172
|
+
- `CODEXS_HOME` overrides the tool home location.
|
|
173
|
+
|
|
174
|
+
## Automation Notes
|
|
175
|
+
|
|
176
|
+
This CLI supports both human TTY usage and non-interactive automation.
|
|
177
|
+
|
|
178
|
+
Global flags:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
--json
|
|
182
|
+
--codex-dir <path>
|
|
183
|
+
--help
|
|
184
|
+
--version
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Operational limits:
|
|
188
|
+
|
|
189
|
+
- `login copilot` requires a real TTY and does not support `--json`.
|
|
190
|
+
- `migrate` remains interactive when provider adoption requires human input.
|
|
191
|
+
- Automation should pass explicit arguments and prefer `--json` for stable parsing.
|
|
192
|
+
|
|
193
|
+
## Local Development
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm run build
|
|
197
|
+
npm test
|
|
198
|
+
npx tsc --noEmit
|
|
199
|
+
node dist/cli.js --help
|
|
200
|
+
npm pack --dry-run
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Documentation
|
|
204
|
+
|
|
205
|
+
- [Chinese README](./README.CN.md)
|
|
206
|
+
- [AI README](./README.AI.md)
|
|
207
|
+
- [Detailed CLI Usage](./docs/cli-usage.md)
|
|
208
|
+
- [Testing Guide](./docs/Tests/testing.md)
|
|
209
|
+
- [Product Overview](./docs/codex-switch-product-overview.md)
|
|
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)
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
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,24 +90,19 @@ 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.",
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
const directBaseUrl = args.baseUrl;
|
|
103
|
-
|
|
104
|
-
? {
|
|
105
|
-
[args.profile]: (0, config_1.validateManagedProfileCreation)(args.profile, {
|
|
106
|
-
model: args.model ?? undefined,
|
|
107
|
-
modelProvider: args.profile,
|
|
108
|
-
}),
|
|
109
|
-
}
|
|
110
|
-
: undefined;
|
|
111
|
-
if (!args.copilot && !existingModelProvider && args.createProfile && (!directBaseUrl || directBaseUrl.trim() === "")) {
|
|
105
|
+
if (!args.copilot && (!directBaseUrl || directBaseUrl.trim() === "") && !existingModelProvider) {
|
|
112
106
|
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${args.profile}" requires base_url.`, {
|
|
113
107
|
profile: args.profile,
|
|
114
108
|
modelProvider: args.profile,
|
|
@@ -119,14 +113,9 @@ async function addProvider(args) {
|
|
|
119
113
|
? {
|
|
120
114
|
[args.profile]: (0, providers_1.buildCopilotModelProviderProjection)(runtime),
|
|
121
115
|
}
|
|
122
|
-
:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
: undefined;
|
|
127
|
-
if (existingProfile) {
|
|
128
|
-
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
|
|
129
|
-
}
|
|
116
|
+
: {
|
|
117
|
+
[args.profile]: (0, providers_1.buildDirectModelProviderProjection)(args.profile, (directBaseUrl ?? existingModelProvider?.baseUrl ?? "").trim()),
|
|
118
|
+
};
|
|
130
119
|
const apiKey = args.copilot ? args.bridgeApiKey ?? crypto.randomBytes(24).toString("hex") : args.apiKey;
|
|
131
120
|
const baseUrl = args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined;
|
|
132
121
|
const next = {
|
|
@@ -135,6 +124,7 @@ async function addProvider(args) {
|
|
|
135
124
|
[args.providerName]: (0, providers_1.cleanProviderRecord)({
|
|
136
125
|
profile: args.profile,
|
|
137
126
|
apiKey,
|
|
127
|
+
model: providerModel,
|
|
138
128
|
baseUrl,
|
|
139
129
|
note: args.note ?? undefined,
|
|
140
130
|
tags: args.tags,
|
|
@@ -153,14 +143,16 @@ async function addProvider(args) {
|
|
|
153
143
|
],
|
|
154
144
|
mutate: () => {
|
|
155
145
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
156
|
-
upsertProfiles,
|
|
157
146
|
upsertModelProviders,
|
|
147
|
+
scrubModelProviderEnvKeys: [args.profile],
|
|
158
148
|
});
|
|
159
149
|
// Persist only the normalized provider payload so later reads are deterministic.
|
|
160
150
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
|
|
161
151
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
162
152
|
return {
|
|
163
153
|
provider: args.providerName,
|
|
154
|
+
model: providerModel,
|
|
155
|
+
modelProvider: args.profile,
|
|
164
156
|
profile: args.profile,
|
|
165
157
|
runtimeKind: runtime?.kind ?? null,
|
|
166
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,87 +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
|
-
|
|
52
|
-
profile: newProfile,
|
|
53
|
-
provider: args.providerName,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
if (!args.baseUrl || args.baseUrl.trim() === "") {
|
|
49
|
+
const resolvedBaseUrl = (args.baseUrl ?? current.baseUrl ?? targetModelProviderSection?.baseUrl ?? "").trim();
|
|
50
|
+
if (!current.runtime) {
|
|
51
|
+
if (!resolvedBaseUrl) {
|
|
57
52
|
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${newProfile}" requires base_url.`, {
|
|
58
53
|
profile: newProfile,
|
|
59
54
|
modelProvider: newProfile,
|
|
60
55
|
missingFields: ["base_url"],
|
|
61
56
|
});
|
|
62
57
|
}
|
|
63
|
-
upsertProfiles = {
|
|
64
|
-
[newProfile]: (0, config_1.validateManagedProfileCreation)(newProfile, {
|
|
65
|
-
model: args.model ?? undefined,
|
|
66
|
-
modelProvider: newProfile,
|
|
67
|
-
}),
|
|
68
|
-
};
|
|
69
58
|
upsertModelProviders = {
|
|
70
|
-
[newProfile]: (0, providers_1.buildDirectModelProviderProjection)(newProfile,
|
|
59
|
+
[newProfile]: (0, providers_1.buildDirectModelProviderProjection)(newProfile, resolvedBaseUrl),
|
|
71
60
|
};
|
|
72
61
|
}
|
|
73
|
-
else {
|
|
74
|
-
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
|
|
75
|
-
}
|
|
76
|
-
if (targetProfileExists &&
|
|
77
|
-
!current.runtime &&
|
|
78
|
-
args.baseUrl !== undefined &&
|
|
79
|
-
args.baseUrl !== null) {
|
|
62
|
+
else if (targetModelProviderSection || args.profile !== undefined) {
|
|
80
63
|
upsertModelProviders = {
|
|
81
64
|
...(upsertModelProviders ?? {}),
|
|
82
|
-
[newProfile]:
|
|
65
|
+
[newProfile]: {
|
|
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)),
|
|
74
|
+
},
|
|
83
75
|
};
|
|
84
76
|
}
|
|
85
77
|
const nextRecord = (0, providers_1.cleanProviderRecord)({
|
|
86
78
|
profile: newProfile,
|
|
87
79
|
apiKey: args.apiKey ?? current.apiKey,
|
|
80
|
+
model: nextModel,
|
|
88
81
|
baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
|
|
89
82
|
note: args.note === null ? undefined : args.note ?? current.note,
|
|
90
83
|
tags: args.tags ?? current.tags,
|
|
84
|
+
runtime: current.runtime,
|
|
91
85
|
});
|
|
92
|
-
|
|
93
|
-
upsertProfiles = {
|
|
94
|
-
[newProfile]: {
|
|
95
|
-
...(args.model !== undefined && args.model !== null ? { model: args.model } : {}),
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
if (args.model !== undefined && targetSection?.model !== args.model && !updatedFields.includes("model")) {
|
|
99
|
-
updatedFields.push("model");
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Compute profile link ownership after the edit so lifecycle planning can decide whether sections stay, move, or delete.
|
|
103
|
-
const remainingLinksByProfile = new Map();
|
|
104
|
-
for (const [name, provider] of Object.entries(providers.providers)) {
|
|
105
|
-
if (name === args.providerName) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
const list = remainingLinksByProfile.get(provider.profile) ?? [];
|
|
109
|
-
list.push(name);
|
|
110
|
-
remainingLinksByProfile.set(provider.profile, list);
|
|
111
|
-
}
|
|
112
|
-
if (newProfile !== oldProfile) {
|
|
113
|
-
const list = remainingLinksByProfile.get(newProfile) ?? [];
|
|
114
|
-
list.push(args.providerName);
|
|
115
|
-
remainingLinksByProfile.set(newProfile, list);
|
|
116
|
-
}
|
|
117
|
-
const lifecycle = (0, config_1.planProfileLifecycleOutcome)({
|
|
118
|
-
providerName: args.providerName,
|
|
119
|
-
oldProfile,
|
|
120
|
-
newProfile,
|
|
121
|
-
activeProfile: document.activeProfile,
|
|
122
|
-
remainingLinksByProfile,
|
|
123
|
-
switchToProfile: args.switchToProfile ?? null,
|
|
124
|
-
});
|
|
86
|
+
const isActive = document.currentModelProvider === oldProfile;
|
|
125
87
|
return (0, run_mutation_1.runMutation)({
|
|
126
88
|
lockPath: args.lockPath,
|
|
127
89
|
backupsDir: args.backupsDir,
|
|
@@ -133,10 +95,12 @@ function editProvider(args) {
|
|
|
133
95
|
],
|
|
134
96
|
mutate: () => {
|
|
135
97
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
136
|
-
upsertProfiles,
|
|
137
98
|
upsertModelProviders,
|
|
138
|
-
|
|
139
|
-
|
|
99
|
+
setCurrentModel: isActive ? nextModel ?? document.currentModel : undefined,
|
|
100
|
+
setCurrentModelProvider: isActive ? newProfile : undefined,
|
|
101
|
+
deleteLegacyProfile: isActive,
|
|
102
|
+
deleteLegacyProfilesByName: isActive ? [newProfile] : [],
|
|
103
|
+
scrubModelProviderEnvKeys: [newProfile],
|
|
140
104
|
});
|
|
141
105
|
const nextProviders = {
|
|
142
106
|
providers: {
|
|
@@ -149,12 +113,13 @@ function editProvider(args) {
|
|
|
149
113
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
150
114
|
return {
|
|
151
115
|
provider: args.providerName,
|
|
116
|
+
modelProvider: newProfile,
|
|
152
117
|
updatedFields,
|
|
153
118
|
createdProfileSections: configPlan.createdProfileSections,
|
|
154
119
|
createdModelProviderSections: configPlan.createdModelProviderSections,
|
|
155
120
|
deletedProfileSections: configPlan.deletedProfileSections,
|
|
156
|
-
keptSharedProfiles:
|
|
157
|
-
switchedActiveProfile:
|
|
121
|
+
keptSharedProfiles: [],
|
|
122
|
+
switchedActiveProfile: isActive && newProfile !== oldProfile,
|
|
158
123
|
adoptedProfiles: [],
|
|
159
124
|
repairedProfiles: [],
|
|
160
125
|
};
|