@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
|
@@ -45,13 +45,18 @@ const providers_repo_1 = require("../storage/providers-repo");
|
|
|
45
45
|
function listProviders(providersPath, configPath) {
|
|
46
46
|
const providers = (0, providers_repo_1.readProvidersFile)(providersPath);
|
|
47
47
|
const names = Object.keys(providers.providers).sort();
|
|
48
|
-
const
|
|
49
|
-
? (0, config_repo_1.readStructuredConfig)(configPath).
|
|
48
|
+
const currentModelProvider = configPath && fs.existsSync(configPath)
|
|
49
|
+
? (0, config_repo_1.readStructuredConfig)(configPath).currentModelProvider
|
|
50
50
|
: null;
|
|
51
|
-
const
|
|
51
|
+
const currentModel = configPath && fs.existsSync(configPath)
|
|
52
|
+
? (0, config_repo_1.readStructuredConfig)(configPath).currentModel
|
|
53
|
+
: null;
|
|
54
|
+
const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentModelProvider, providers);
|
|
52
55
|
const items = names.map((name) => ({
|
|
53
56
|
name,
|
|
54
57
|
profile: providers.providers[name].profile,
|
|
58
|
+
modelProvider: providers.providers[name].profile,
|
|
59
|
+
model: providers.providers[name].model ?? null,
|
|
55
60
|
providerType: (0, providers_1.isCopilotBridgeProvider)(providers.providers[name]) ? "copilot" : "direct",
|
|
56
61
|
isActive: liveState.providerResolvable && liveState.mappedProvider === name,
|
|
57
62
|
note: providers.providers[name].note ?? null,
|
|
@@ -61,7 +66,8 @@ function listProviders(providersPath, configPath) {
|
|
|
61
66
|
data: {
|
|
62
67
|
providers: items,
|
|
63
68
|
count: items.length,
|
|
64
|
-
|
|
69
|
+
currentModel,
|
|
70
|
+
currentModelProvider,
|
|
65
71
|
activeProvider: liveState.mappedProvider,
|
|
66
72
|
activeProviderResolvable: liveState.providerResolvable,
|
|
67
73
|
activeProviderCandidates: liveState.mappedProviders,
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.removeProvider = removeProvider;
|
|
4
4
|
const errors_1 = require("../domain/errors");
|
|
5
|
-
const config_1 = require("../domain/config");
|
|
6
5
|
const config_repo_1 = require("../storage/config-repo");
|
|
7
6
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
7
|
+
const providers_1 = require("../domain/providers");
|
|
8
8
|
const run_mutation_1 = require("./run-mutation");
|
|
9
9
|
/**
|
|
10
10
|
* Removes a provider from the managed providers registry.
|
|
@@ -17,22 +17,48 @@ function removeProvider(args) {
|
|
|
17
17
|
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`);
|
|
18
18
|
}
|
|
19
19
|
const nextProviders = { ...providers.providers };
|
|
20
|
-
// Delete against a copied object so the original parsed state stays untouched.
|
|
21
20
|
delete nextProviders[args.providerName];
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
const activeModelProvider = document.currentModelProvider;
|
|
22
|
+
const linkedProviders = Object.entries(providers.providers)
|
|
23
|
+
.filter(([, provider]) => provider.profile === activeModelProvider)
|
|
24
|
+
.map(([name]) => name)
|
|
25
|
+
.sort();
|
|
26
|
+
const removingActiveProvider = activeModelProvider === current.profile && linkedProviders.length === 1;
|
|
27
|
+
const switchTargetName = args.switchToProvider ?? null;
|
|
28
|
+
const switchTarget = switchTargetName ? nextProviders[switchTargetName] ?? null : null;
|
|
29
|
+
if (removingActiveProvider && !switchTargetName) {
|
|
30
|
+
throw (0, errors_1.cliError)("PROFILE_IN_USE", `Provider "${args.providerName}" is the active route and requires --switch-to <provider-name>.`, {
|
|
31
|
+
provider: args.providerName,
|
|
32
|
+
activeModelProvider,
|
|
33
|
+
linkedProviders,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (switchTargetName && !switchTarget) {
|
|
37
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${switchTargetName}" was not found.`, {
|
|
38
|
+
provider: switchTargetName,
|
|
39
|
+
availableProviders: Object.keys(nextProviders).sort(),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const switchTargetModel = switchTarget?.model ?? document.currentModel ?? null;
|
|
43
|
+
if (switchTargetName && !switchTargetModel) {
|
|
44
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Provider "${switchTargetName}" has no model to switch with.`, {
|
|
45
|
+
provider: switchTargetName,
|
|
46
|
+
suggestion: "Run `codexs edit <provider> --model <name>` first.",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const switchTargetProjection = switchTarget
|
|
50
|
+
? (0, providers_1.isCopilotBridgeProvider)(switchTarget)
|
|
51
|
+
? (0, providers_1.buildCopilotModelProviderProjection)(switchTarget.runtime)
|
|
52
|
+
: switchTarget.baseUrl
|
|
53
|
+
? (0, providers_1.buildDirectModelProviderProjection)(switchTarget.profile, switchTarget.baseUrl)
|
|
54
|
+
: null
|
|
55
|
+
: null;
|
|
56
|
+
if (switchTargetName && !switchTargetProjection) {
|
|
57
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Provider "${switchTargetName}" requires base_url before it can become active.`, {
|
|
58
|
+
provider: switchTargetName,
|
|
59
|
+
suggestion: "Run `codexs edit <provider> --base-url <url>` first.",
|
|
60
|
+
});
|
|
27
61
|
}
|
|
28
|
-
const lifecycle = (0, config_1.planProfileLifecycleOutcome)({
|
|
29
|
-
providerName: args.providerName,
|
|
30
|
-
oldProfile: current.profile,
|
|
31
|
-
newProfile: null,
|
|
32
|
-
activeProfile: document.activeProfile,
|
|
33
|
-
remainingLinksByProfile,
|
|
34
|
-
switchToProfile: args.switchToProfile ?? null,
|
|
35
|
-
});
|
|
36
62
|
return (0, run_mutation_1.runMutation)({
|
|
37
63
|
lockPath: args.lockPath,
|
|
38
64
|
backupsDir: args.backupsDir,
|
|
@@ -44,17 +70,24 @@ function removeProvider(args) {
|
|
|
44
70
|
],
|
|
45
71
|
mutate: () => {
|
|
46
72
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
setCurrentModel: switchTarget ? switchTargetModel : undefined,
|
|
74
|
+
setCurrentModelProvider: switchTarget ? switchTarget.profile : undefined,
|
|
75
|
+
upsertModelProviders: switchTarget && switchTargetProjection
|
|
76
|
+
? { [switchTarget.profile]: switchTargetProjection }
|
|
77
|
+
: undefined,
|
|
78
|
+
deleteLegacyProfile: Boolean(switchTarget),
|
|
79
|
+
deleteLegacyProfilesByName: switchTarget ? [switchTarget.profile] : [],
|
|
80
|
+
scrubModelProviderEnvKeys: switchTarget ? [switchTarget.profile] : [],
|
|
49
81
|
});
|
|
50
82
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: nextProviders });
|
|
51
83
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
52
84
|
return {
|
|
53
85
|
provider: args.providerName,
|
|
86
|
+
switchedTo: switchTargetName,
|
|
54
87
|
createdProfileSections: configPlan.createdProfileSections,
|
|
55
88
|
deletedProfileSections: configPlan.deletedProfileSections,
|
|
56
|
-
keptSharedProfiles:
|
|
57
|
-
switchedActiveProfile:
|
|
89
|
+
keptSharedProfiles: [],
|
|
90
|
+
switchedActiveProfile: Boolean(switchTarget),
|
|
58
91
|
adoptedProfiles: [],
|
|
59
92
|
repairedProfiles: [],
|
|
60
93
|
};
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -47,12 +47,13 @@ const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
|
47
47
|
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
48
48
|
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
49
49
|
const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
50
|
+
const codex_version_1 = require("../runtime/codex-version");
|
|
50
51
|
/**
|
|
51
52
|
* Performs consistency checks across config.toml, providers.json, and the local Codex CLI.
|
|
52
53
|
*/
|
|
53
54
|
async function runDoctor(args) {
|
|
54
55
|
const issues = [];
|
|
55
|
-
let
|
|
56
|
+
let currentModelProvider = null;
|
|
56
57
|
let providers = null;
|
|
57
58
|
let document = null;
|
|
58
59
|
if (!fs.existsSync(args.configPath)) {
|
|
@@ -64,11 +65,11 @@ async function runDoctor(args) {
|
|
|
64
65
|
}
|
|
65
66
|
else {
|
|
66
67
|
document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
67
|
-
|
|
68
|
-
if (!
|
|
68
|
+
currentModelProvider = document.currentModelProvider;
|
|
69
|
+
if (!currentModelProvider) {
|
|
69
70
|
issues.push({
|
|
70
|
-
code: "
|
|
71
|
-
message: "config.toml has no top-level
|
|
71
|
+
code: "MODEL_PROVIDER_MISSING",
|
|
72
|
+
message: "config.toml has no top-level model_provider.",
|
|
72
73
|
file: args.configPath,
|
|
73
74
|
});
|
|
74
75
|
}
|
|
@@ -118,8 +119,8 @@ async function runDoctor(args) {
|
|
|
118
119
|
message: `Copilot bridge runtime state is unreadable: ${runtimeStateInspection.parseError ?? "unknown parse failure"}`,
|
|
119
120
|
});
|
|
120
121
|
}
|
|
121
|
-
if (document?.
|
|
122
|
-
const matches = (0, providers_1.findProvidersByProfile)(providers, document.
|
|
122
|
+
if (document?.currentModelProvider && providers) {
|
|
123
|
+
const matches = (0, providers_1.findProvidersByProfile)(providers, document.currentModelProvider);
|
|
123
124
|
if (matches.length === 1) {
|
|
124
125
|
const activeProvider = providers.providers[matches[0]];
|
|
125
126
|
if ((0, providers_1.isCopilotBridgeProvider)(activeProvider)) {
|
|
@@ -163,11 +164,11 @@ async function runDoctor(args) {
|
|
|
163
164
|
...runtimeState,
|
|
164
165
|
});
|
|
165
166
|
}
|
|
166
|
-
else if (!document?.
|
|
167
|
+
else if (!document?.currentModelProvider || runtimeProvider.profile !== document.currentModelProvider) {
|
|
167
168
|
issues.push({
|
|
168
169
|
code: "BRIDGE_STATE_STALE",
|
|
169
|
-
message: "Copilot bridge runtime state exists for a provider that is not the current active
|
|
170
|
-
|
|
170
|
+
message: "Copilot bridge runtime state exists for a provider that is not the current active model_provider.",
|
|
171
|
+
activeModelProvider: document?.currentModelProvider ?? null,
|
|
171
172
|
runtimeProvider: runtimeState.provider,
|
|
172
173
|
runtimeProfile: runtimeProvider.profile,
|
|
173
174
|
...runtimeState,
|
|
@@ -175,8 +176,8 @@ async function runDoctor(args) {
|
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
// Drift inspection still runs when files are missing so status output can explain partial state.
|
|
178
|
-
const drift = (0, runtime_state_1.inspectLiveStateDrift)(
|
|
179
|
-
const codexCheck = (0, codex_probe_1.probeCodexRuntime)();
|
|
179
|
+
const drift = (0, runtime_state_1.inspectLiveStateDrift)(currentModelProvider, providers);
|
|
180
|
+
const codexCheck = (0, codex_probe_1.probeCodexRuntime)(codex_version_1.MIN_SUPPORTED_CODEX_VERSION);
|
|
180
181
|
if (!codexCheck.ok) {
|
|
181
182
|
const message = codexCheck.reason === "missing"
|
|
182
183
|
? "codex CLI is not available on PATH."
|
|
@@ -229,28 +230,28 @@ function mapBridgeDiagnosticCode(cause) {
|
|
|
229
230
|
*/
|
|
230
231
|
function renderConfigIssueMessage(issue) {
|
|
231
232
|
switch (issue.code) {
|
|
232
|
-
case "
|
|
233
|
-
return
|
|
234
|
-
case "UNMANAGED_ACTIVE_PROFILE":
|
|
235
|
-
return `Active profile "${issue.profile}" is not mapped by providers.json.`;
|
|
236
|
-
case "SHARED_PROFILE_REFERENCE":
|
|
237
|
-
return `Profile "${issue.profile}" is shared by multiple providers.`;
|
|
238
|
-
case "ORPHANED_PROFILE_SECTION":
|
|
239
|
-
return `Profile section "${issue.profile}" is not linked to any provider.`;
|
|
233
|
+
case "MODEL_MISSING":
|
|
234
|
+
return "Top-level model is missing from config.toml.";
|
|
240
235
|
case "MODEL_PROVIDER_MISSING":
|
|
241
|
-
return
|
|
242
|
-
case "MODEL_PROVIDER_NAME_MISMATCH":
|
|
243
|
-
return `Profile "${issue.profile}" must use matching model_provider name "${issue.profile}", found "${issue.modelProvider}".`;
|
|
236
|
+
return "Top-level model_provider is missing from config.toml.";
|
|
244
237
|
case "MODEL_PROVIDER_SECTION_MISSING":
|
|
245
|
-
return `Model provider section "${issue.modelProvider}"
|
|
238
|
+
return `Model provider section "${issue.modelProvider}" is missing from config.toml.`;
|
|
246
239
|
case "MODEL_PROVIDER_BASE_URL_MISSING":
|
|
247
|
-
return `Model provider section "${issue.modelProvider}"
|
|
248
|
-
case "
|
|
249
|
-
return `
|
|
240
|
+
return `Model provider section "${issue.modelProvider}" is missing base_url.`;
|
|
241
|
+
case "LEGACY_PROFILE_SELECTOR":
|
|
242
|
+
return `Legacy top-level profile selector "${issue.profile}" is still present.`;
|
|
243
|
+
case "LEGACY_PROFILE_SECTION":
|
|
244
|
+
return `Legacy profile section "${issue.profile}" is still present.`;
|
|
245
|
+
case "LEGACY_MODEL_PROVIDER_ENV_KEY":
|
|
246
|
+
return `Model provider "${issue.modelProvider}" still contains legacy env_key wiring.`;
|
|
247
|
+
case "PROVIDER_BASE_URL_MISMATCH":
|
|
248
|
+
return issue.providerType === "direct"
|
|
249
|
+
? `Direct provider "${issue.provider}" baseUrl does not match config.toml model provider "${issue.modelProvider}" base_url.`
|
|
250
|
+
: String(issue.code ?? "UNKNOWN_ISSUE");
|
|
250
251
|
case "AUTH_JSON_INVALID":
|
|
251
252
|
return String(issue.message ?? issue.reason ?? "auth.json is invalid.");
|
|
252
253
|
case "DESTRUCTIVE_REMOVE_BLOCKED":
|
|
253
|
-
return `Provider "${issue.provider}" cannot be removed while "${issue.
|
|
254
|
+
return `Provider "${issue.provider}" cannot be removed while "${issue.activeModelProvider}" remains active.`;
|
|
254
255
|
default:
|
|
255
256
|
return String(issue.code ?? "UNKNOWN_ISSUE");
|
|
256
257
|
}
|
package/dist/app/setup-codex.js
CHANGED
|
@@ -42,9 +42,9 @@ const codex_cli_1 = require("../runtime/codex-cli");
|
|
|
42
42
|
const config_repo_1 = require("../storage/config-repo");
|
|
43
43
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
44
44
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
45
|
+
const codex_version_1 = require("../runtime/codex-version");
|
|
45
46
|
const run_doctor_1 = require("./run-doctor");
|
|
46
47
|
const run_mutation_1 = require("./run-mutation");
|
|
47
|
-
const MIN_CODEX_VERSION = "0.0.1";
|
|
48
48
|
/**
|
|
49
49
|
* Migrates unmanaged Codex config profiles into a managed providers.json registry.
|
|
50
50
|
*/
|
|
@@ -55,10 +55,10 @@ async function migrateCodex(args) {
|
|
|
55
55
|
cause: available.cause,
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
const version = (0, codex_cli_1.checkCodexVersion)(
|
|
58
|
+
const version = (0, codex_cli_1.checkCodexVersion)(codex_version_1.MIN_SUPPORTED_CODEX_VERSION);
|
|
59
59
|
if (!version.ok) {
|
|
60
60
|
throw (0, errors_1.cliError)("CODEX_VERSION_UNSUPPORTED", "codex CLI version is below the supported minimum.", {
|
|
61
|
-
minimumVersion:
|
|
61
|
+
minimumVersion: codex_version_1.MIN_SUPPORTED_CODEX_VERSION,
|
|
62
62
|
currentVersion: version.currentVersion ?? null,
|
|
63
63
|
cause: version.cause,
|
|
64
64
|
});
|
package/dist/app/show-config.js
CHANGED
|
@@ -27,7 +27,9 @@ function showConfig(args) {
|
|
|
27
27
|
}
|
|
28
28
|
return {
|
|
29
29
|
data: {
|
|
30
|
-
|
|
30
|
+
currentModel: document.currentModel,
|
|
31
|
+
currentModelProvider: document.currentModelProvider,
|
|
32
|
+
legacyProfile: document.legacyProfile,
|
|
31
33
|
selectedProfile,
|
|
32
34
|
profiles: profiles.map((profile) => ({
|
|
33
35
|
...profile,
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.switchProvider = switchProvider;
|
|
4
4
|
const errors_1 = require("../domain/errors");
|
|
5
|
-
const providers_1 = require("../domain/providers");
|
|
6
5
|
const config_repo_1 = require("../storage/config-repo");
|
|
7
6
|
const auth_repo_1 = require("../storage/auth-repo");
|
|
8
7
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
@@ -10,8 +9,9 @@ const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
|
10
9
|
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
11
10
|
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
12
11
|
const run_mutation_1 = require("./run-mutation");
|
|
12
|
+
const providers_1 = require("../domain/providers");
|
|
13
13
|
/**
|
|
14
|
-
* Switches the active Codex
|
|
14
|
+
* Switches the active Codex route to the target provider.
|
|
15
15
|
*/
|
|
16
16
|
async function switchProvider(args) {
|
|
17
17
|
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
@@ -21,7 +21,15 @@ async function switchProvider(args) {
|
|
|
21
21
|
availableProviders: Object.keys(providers.providers).sort(),
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
-
const document = (0, config_repo_1.
|
|
24
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
25
|
+
const resolvedModel = provider.model ?? document.currentModel;
|
|
26
|
+
if (!resolvedModel) {
|
|
27
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Provider "${args.providerName}" has no model to switch with.`, {
|
|
28
|
+
provider: args.providerName,
|
|
29
|
+
modelProvider: provider.profile,
|
|
30
|
+
suggestion: "Run `codexs edit <provider> --model <name>` or `codexs add <provider> --model <name>`.",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
25
33
|
if ((0, providers_1.isCopilotBridgeProvider)(provider)) {
|
|
26
34
|
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
|
|
27
35
|
if (!installStatus.installed) {
|
|
@@ -55,10 +63,14 @@ async function switchProvider(args) {
|
|
|
55
63
|
],
|
|
56
64
|
mutate: () => {
|
|
57
65
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
58
|
-
|
|
66
|
+
setCurrentModel: resolvedModel,
|
|
67
|
+
setCurrentModelProvider: provider.profile,
|
|
59
68
|
upsertModelProviders: {
|
|
60
69
|
[provider.profile]: (0, providers_1.buildCopilotModelProviderProjection)(nextProvider.runtime),
|
|
61
70
|
},
|
|
71
|
+
deleteLegacyProfile: true,
|
|
72
|
+
deleteLegacyProfilesByName: [provider.profile],
|
|
73
|
+
scrubModelProviderEnvKeys: [provider.profile],
|
|
62
74
|
});
|
|
63
75
|
if (bridge.portChanged) {
|
|
64
76
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, {
|
|
@@ -72,6 +84,8 @@ async function switchProvider(args) {
|
|
|
72
84
|
(0, auth_repo_1.writeOpenAiApiKeyAuth)(args.authPath, provider.apiKey);
|
|
73
85
|
return {
|
|
74
86
|
provider: args.providerName,
|
|
87
|
+
model: resolvedModel,
|
|
88
|
+
modelProvider: nextProvider.profile,
|
|
75
89
|
profile: nextProvider.profile,
|
|
76
90
|
portChanged: bridge.portChanged,
|
|
77
91
|
bridgePort: bridge.port,
|
|
@@ -96,13 +110,30 @@ async function switchProvider(args) {
|
|
|
96
110
|
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
97
111
|
],
|
|
98
112
|
mutate: () => {
|
|
113
|
+
const directBaseUrl = provider.baseUrl?.trim() ?? "";
|
|
114
|
+
if (!directBaseUrl) {
|
|
115
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Provider "${args.providerName}" requires base_url before switching.`, {
|
|
116
|
+
provider: args.providerName,
|
|
117
|
+
modelProvider: provider.profile,
|
|
118
|
+
suggestion: "Run `codexs edit <provider> --base-url <url>`.",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
99
121
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
100
|
-
|
|
122
|
+
setCurrentModel: resolvedModel,
|
|
123
|
+
setCurrentModelProvider: provider.profile,
|
|
124
|
+
upsertModelProviders: {
|
|
125
|
+
[provider.profile]: (0, providers_1.buildDirectModelProviderProjection)(provider.profile, directBaseUrl),
|
|
126
|
+
},
|
|
127
|
+
deleteLegacyProfile: true,
|
|
128
|
+
deleteLegacyProfilesByName: [provider.profile],
|
|
129
|
+
scrubModelProviderEnvKeys: [provider.profile],
|
|
101
130
|
});
|
|
102
131
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
103
132
|
(0, auth_repo_1.writeOpenAiApiKeyAuth)(args.authPath, provider.apiKey);
|
|
104
133
|
return {
|
|
105
134
|
provider: args.providerName,
|
|
135
|
+
model: resolvedModel,
|
|
136
|
+
modelProvider: provider.profile,
|
|
106
137
|
profile: provider.profile,
|
|
107
138
|
};
|
|
108
139
|
},
|
package/dist/cli/output.js
CHANGED
|
@@ -85,14 +85,21 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
85
85
|
lines.push("No providers configured.");
|
|
86
86
|
}
|
|
87
87
|
else {
|
|
88
|
-
const
|
|
88
|
+
const currentModel = typeof data?.currentModel === "string" ? data.currentModel : null;
|
|
89
|
+
const currentModelProvider = typeof data?.currentModelProvider === "string" ? data.currentModelProvider : null;
|
|
89
90
|
const activeProviderResolvable = data?.activeProviderResolvable !== false;
|
|
90
91
|
const activeCandidates = Array.isArray(data?.activeProviderCandidates) ? data?.activeProviderCandidates : [];
|
|
91
|
-
if (
|
|
92
|
-
lines.push(`Current
|
|
92
|
+
if (currentModel) {
|
|
93
|
+
lines.push(`Current model: ${currentModel}`);
|
|
94
|
+
}
|
|
95
|
+
if (currentModelProvider) {
|
|
96
|
+
lines.push(`Current model provider: ${currentModelProvider}`);
|
|
93
97
|
if (!activeProviderResolvable && activeCandidates.length > 1) {
|
|
94
98
|
lines.push(`Current provider: ambiguous (${activeCandidates.join(", ")})`);
|
|
95
99
|
}
|
|
100
|
+
else if (!activeProviderResolvable) {
|
|
101
|
+
lines.push("Current provider: unmanaged or unresolved");
|
|
102
|
+
}
|
|
96
103
|
}
|
|
97
104
|
for (const provider of providers) {
|
|
98
105
|
const tags = Array.isArray(provider.tags) && provider.tags.length > 0
|
|
@@ -100,7 +107,7 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
100
107
|
: "";
|
|
101
108
|
const note = provider.note ? ` note=${provider.note}` : "";
|
|
102
109
|
const current = provider.isActive ? " current" : "";
|
|
103
|
-
lines.push(`${provider.name} [${String(provider.providerType ?? "direct")}]${current} -> ${provider.
|
|
110
|
+
lines.push(`${provider.name} [${String(provider.providerType ?? "direct")}]${current} -> ${provider.modelProvider}${provider.model ? ` model=${provider.model}` : ""}${tags}${note}`);
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
break;
|
|
@@ -122,13 +129,18 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
122
129
|
break;
|
|
123
130
|
}
|
|
124
131
|
case "current":
|
|
125
|
-
lines.push(`Current
|
|
132
|
+
lines.push(`Current model: ${String(data?.model ?? "")}`);
|
|
133
|
+
lines.push(`Current model provider: ${String(data?.modelProvider ?? "")}`);
|
|
134
|
+
if (data?.provider) {
|
|
135
|
+
lines.push(`Managed provider: ${String(data.provider)}`);
|
|
136
|
+
}
|
|
126
137
|
break;
|
|
127
138
|
case "status":
|
|
128
139
|
lines.push("Status summary:");
|
|
129
140
|
lines.push(` target runtime: ${String(data?.codexDir ?? "")}`);
|
|
130
141
|
lines.push(` tool home: ${String(data?.storage?.toolHome?.root ?? "")}`);
|
|
131
|
-
lines.push(` current
|
|
142
|
+
lines.push(` current model: ${String(data?.currentModel ?? "(none)")}`);
|
|
143
|
+
lines.push(` current model provider: ${String(data?.currentModelProvider ?? "(none)")}`);
|
|
132
144
|
lines.push(` mapped provider: ${renderStatusMappedProvider(data)}`);
|
|
133
145
|
lines.push(` provider path: ${renderStatusProviderPath(data)}`);
|
|
134
146
|
lines.push(` runtime health: ${renderStatusHealth(data)}`);
|
|
@@ -136,7 +148,9 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
136
148
|
lines.push(` next step: ${renderStatusNextStep(data, warnings)}`);
|
|
137
149
|
break;
|
|
138
150
|
case "config-show": {
|
|
139
|
-
lines.push(`
|
|
151
|
+
lines.push(`currentModel: ${String(data?.currentModel ?? "")}`);
|
|
152
|
+
lines.push(`currentModelProvider: ${String(data?.currentModelProvider ?? "")}`);
|
|
153
|
+
lines.push(`legacyProfile: ${String(data?.legacyProfile ?? "")}`);
|
|
140
154
|
const profiles = data?.profiles ?? [];
|
|
141
155
|
for (const profile of profiles) {
|
|
142
156
|
lines.push(`${String(profile.name)} managed=${String(profile.managed)} active=${String(profile.isActive)} source=${String(profile.source)} model=${String(profile.model ?? "")} modelProvider=${String(profile.modelProvider ?? "")} baseUrl=${String(profile.baseUrl ?? "")}`);
|
|
@@ -151,7 +165,8 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
151
165
|
break;
|
|
152
166
|
}
|
|
153
167
|
case "switch":
|
|
154
|
-
lines.push(`Switched to provider ${String(data?.provider ?? "")} using
|
|
168
|
+
lines.push(`Switched to provider ${String(data?.provider ?? "")} using model provider ${String(data?.modelProvider ?? data?.profile ?? "")}.`);
|
|
169
|
+
lines.push(`Model: ${String(data?.model ?? "")}`);
|
|
155
170
|
lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
|
|
156
171
|
break;
|
|
157
172
|
case "import":
|
|
@@ -180,7 +195,7 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
180
195
|
}
|
|
181
196
|
lines.push(`login launched: ${String(data?.loginLaunched ?? false)}`);
|
|
182
197
|
lines.push(`auth ready: ${String(data?.authReady ?? false)}`);
|
|
183
|
-
lines.push("next step: run `codexs add <provider> --copilot --profile <
|
|
198
|
+
lines.push("next step: run `codexs add <provider> --copilot --profile <model-provider-id>` and then `codexs switch <provider>`.");
|
|
184
199
|
break;
|
|
185
200
|
case "migrate":
|
|
186
201
|
lines.push(`Migrated providers in ${String(data?.codexDir ?? "")} using ${String(data?.strategy ?? "")}.`);
|
|
@@ -259,11 +274,8 @@ function renderStatusHealth(data) {
|
|
|
259
274
|
if (!activeProviderResolvable || liveState.reason === "shared-profile") {
|
|
260
275
|
return "active provider ambiguous";
|
|
261
276
|
}
|
|
262
|
-
if (issues.some((issue) => issue.code === "
|
|
263
|
-
return "
|
|
264
|
-
}
|
|
265
|
-
if (issues.some((issue) => issue.code === "ACTIVE_PROVIDER_UNRESOLVED")) {
|
|
266
|
-
return "active provider ambiguous";
|
|
277
|
+
if (issues.some((issue) => issue.code === "PROVIDER_BASE_URL_MISMATCH")) {
|
|
278
|
+
return "provider projection drift";
|
|
267
279
|
}
|
|
268
280
|
if (activePathUsesCopilot && copilotSdk.installed === false) {
|
|
269
281
|
return "copilot sdk missing";
|
|
@@ -311,6 +323,9 @@ function renderStatusNextStep(data, warnings) {
|
|
|
311
323
|
if (!data?.provider) {
|
|
312
324
|
return "run `codexs switch <provider>` after adding or adopting a managed provider";
|
|
313
325
|
}
|
|
326
|
+
if (Array.isArray(data?.issues) && (data?.issues).some((issue) => issue.code === "LEGACY_PROFILE_SELECTOR" || issue.code === "LEGACY_PROFILE_SECTION" || issue.code === "LEGACY_MODEL_PROVIDER_ENV_KEY")) {
|
|
327
|
+
return "run `codexs switch <provider>` to reproject the active route and clean legacy fields";
|
|
328
|
+
}
|
|
314
329
|
return "run `codexs doctor` if you need a deeper diagnostic pass";
|
|
315
330
|
}
|
|
316
331
|
/**
|
|
@@ -331,10 +346,13 @@ function renderDoctorIssueNextStep(issue) {
|
|
|
331
346
|
case "BRIDGE_HEALTHCHECK_FAILED":
|
|
332
347
|
return "reselect the provider with `codexs switch <provider>` or inspect bridge state";
|
|
333
348
|
case "UNMANAGED_ACTIVE_PROFILE":
|
|
334
|
-
return "switch to a managed provider or adopt the active
|
|
335
|
-
case "
|
|
336
|
-
case "
|
|
337
|
-
|
|
349
|
+
return "switch to a managed provider or adopt the active route with `codexs migrate`";
|
|
350
|
+
case "LEGACY_PROFILE_SELECTOR":
|
|
351
|
+
case "LEGACY_PROFILE_SECTION":
|
|
352
|
+
case "LEGACY_MODEL_PROVIDER_ENV_KEY":
|
|
353
|
+
return "rerun `codexs switch <provider>` to project top-level model/model_provider and remove legacy fields";
|
|
354
|
+
case "PROVIDER_BASE_URL_MISMATCH":
|
|
355
|
+
return "rerun `codexs edit <provider> --base-url <url>` or `codexs switch <provider>` to repair the runtime projection";
|
|
338
356
|
default:
|
|
339
357
|
return "inspect the issue details and rerun `codexs doctor` after fixing the state";
|
|
340
358
|
}
|
|
@@ -95,7 +95,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
case "current":
|
|
98
|
-
return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
|
|
98
|
+
return (0, get_current_profile_1.getCurrentProfile)(paths.configPath, paths.providersPath);
|
|
99
99
|
case "status":
|
|
100
100
|
return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath, {
|
|
101
101
|
runtimeDir: paths.runtimeDir,
|
|
@@ -495,7 +495,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
495
495
|
providersPath: paths.providersPath,
|
|
496
496
|
configPath: paths.configPath,
|
|
497
497
|
providerName,
|
|
498
|
-
switchToProfile,
|
|
498
|
+
switchToProvider: switchToProfile,
|
|
499
499
|
});
|
|
500
500
|
}
|
|
501
501
|
case "doctor":
|
package/dist/commands/help.js
CHANGED
|
@@ -29,7 +29,7 @@ function buildHelpText(commandName) {
|
|
|
29
29
|
return [
|
|
30
30
|
"codex-switch",
|
|
31
31
|
"",
|
|
32
|
-
"Manage and switch local Codex provider/
|
|
32
|
+
"Manage and switch local Codex provider/model-provider routing safely.",
|
|
33
33
|
"Primary workflows: direct providers use init -> add -> switch -> status -> doctor.",
|
|
34
34
|
"Primary workflows: Copilot providers use init -> login copilot -> add --copilot -> switch -> status -> doctor.",
|
|
35
35
|
"Advanced adopt flows use migrate only when you already have Codex runtime state to import.",
|
|
@@ -65,12 +65,12 @@ function buildHelpText(commandName) {
|
|
|
65
65
|
"",
|
|
66
66
|
"Examples:",
|
|
67
67
|
" codexs init",
|
|
68
|
-
" codexs add packycode --profile packycode --api-key sk-xxx",
|
|
68
|
+
" codexs add packycode --profile packycode --model gpt-5 --api-key sk-xxx --base-url https://api.example/v1",
|
|
69
69
|
" codexs switch packycode",
|
|
70
70
|
" codexs status",
|
|
71
71
|
" codexs doctor",
|
|
72
72
|
" codexs login copilot",
|
|
73
|
-
" codexs add copilot-main --copilot --profile copilot-main",
|
|
73
|
+
" codexs add copilot-main --copilot --profile copilot-main --model gpt-5",
|
|
74
74
|
" codexs migrate",
|
|
75
75
|
" codexs config show",
|
|
76
76
|
" codexs backups list",
|