@minniexcode/codex-switch 0.0.8 → 0.0.9
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/dist/app/add-provider.js +1 -1
- package/dist/app/bridge.js +296 -0
- package/dist/app/get-status.js +16 -1
- package/dist/app/run-doctor.js +35 -3
- package/dist/app/switch-provider.js +31 -3
- package/dist/commands/handlers.js +31 -0
- package/dist/commands/help.js +1 -0
- package/dist/commands/registry.js +42 -0
- package/dist/runtime/copilot-bridge-worker.js +1 -1
- package/dist/runtime/copilot-bridge.js +58 -17
- package/docs/Design/codex-switch-v0.0.9-design.md +182 -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/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/dist/app/add-provider.js
CHANGED
|
@@ -54,7 +54,7 @@ function addProvider(args) {
|
|
|
54
54
|
throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Provider "${args.providerName}" already exists.`);
|
|
55
55
|
}
|
|
56
56
|
const bridgeHost = args.bridgeHost ?? "127.0.0.1";
|
|
57
|
-
const bridgePort = args.bridgePort ??
|
|
57
|
+
const bridgePort = args.bridgePort ?? 41415;
|
|
58
58
|
const runtime = args.copilot
|
|
59
59
|
? {
|
|
60
60
|
kind: "copilot-sdk-bridge",
|
|
@@ -0,0 +1,296 @@
|
|
|
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
|
+
envKey: (0, config_repo_1.requireRuntimeEnvKey)(document, args.provider.profile),
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
(0, providers_repo_1.writeProvidersFile)(args.providersPath, previousProviders);
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
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
|
*/
|
|
@@ -69,7 +70,20 @@ 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 runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
74
|
+
const runtimeStateProvider = runtimeState && providers ? providers.providers[runtimeState.provider] ?? null : null;
|
|
75
|
+
const bridgeProbeTarget = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
|
|
76
|
+
? activeProvider
|
|
77
|
+
: runtimeStateProvider && (0, providers_1.isCopilotBridgeProvider)(runtimeStateProvider)
|
|
78
|
+
? runtimeStateProvider
|
|
79
|
+
: null;
|
|
80
|
+
const copilotBridge = bridgeProbeTarget ? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(bridgeProbeTarget) : runtimeState ? {
|
|
81
|
+
ok: false,
|
|
82
|
+
runtime: "copilot-bridge",
|
|
83
|
+
reason: "failed",
|
|
84
|
+
cause: "Copilot bridge runtime state exists but no matching managed Copilot provider is active.",
|
|
85
|
+
details: runtimeState,
|
|
86
|
+
} : null;
|
|
73
87
|
const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
|
|
74
88
|
? await (0, copilot_adapter_1.readCopilotAuthState)().catch((error) => ({
|
|
75
89
|
ready: false,
|
|
@@ -103,6 +117,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
103
117
|
},
|
|
104
118
|
copilotAuth,
|
|
105
119
|
copilotBridge,
|
|
120
|
+
copilotRuntimeState: runtimeState,
|
|
106
121
|
liveState,
|
|
107
122
|
auth: authState,
|
|
108
123
|
configProfiles: configViews,
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -46,6 +46,7 @@ const providers_1 = require("../domain/providers");
|
|
|
46
46
|
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
|
+
const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
49
50
|
/**
|
|
50
51
|
* Performs consistency checks across config.toml, providers.json, and the local Codex CLI.
|
|
51
52
|
*/
|
|
@@ -102,6 +103,7 @@ async function runDoctor(args) {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
const authState = (0, auth_repo_1.readManagedAuthState)(args.authPath);
|
|
106
|
+
const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
105
107
|
if (authState.exists && !authState.valid) {
|
|
106
108
|
issues.push({
|
|
107
109
|
code: "AUTH_JSON_INVALID",
|
|
@@ -161,9 +163,7 @@ async function runDoctor(args) {
|
|
|
161
163
|
const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider);
|
|
162
164
|
if (!bridge.ok) {
|
|
163
165
|
issues.push({
|
|
164
|
-
code: bridge.cause
|
|
165
|
-
? "PROVIDER_BASE_URL_MISMATCH"
|
|
166
|
-
: "BRIDGE_HEALTHCHECK_FAILED",
|
|
166
|
+
code: mapBridgeDiagnosticCode(bridge.cause),
|
|
167
167
|
message: bridge.cause,
|
|
168
168
|
...(bridge.details ?? {}),
|
|
169
169
|
});
|
|
@@ -171,6 +171,26 @@ async function runDoctor(args) {
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
if (runtimeState && providers) {
|
|
175
|
+
const runtimeProvider = providers.providers[runtimeState.provider] ?? null;
|
|
176
|
+
if (!runtimeProvider || !(0, providers_1.isCopilotBridgeProvider)(runtimeProvider)) {
|
|
177
|
+
issues.push({
|
|
178
|
+
code: "BRIDGE_STATE_STALE",
|
|
179
|
+
message: "Copilot bridge runtime state exists but no matching managed Copilot provider is available.",
|
|
180
|
+
...runtimeState,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
else if (!document?.activeProfile || runtimeProvider.profile !== document.activeProfile) {
|
|
184
|
+
issues.push({
|
|
185
|
+
code: "BRIDGE_STATE_STALE",
|
|
186
|
+
message: "Copilot bridge runtime state exists for a provider that is not the current active profile.",
|
|
187
|
+
activeProfile: document?.activeProfile ?? null,
|
|
188
|
+
runtimeProvider: runtimeState.provider,
|
|
189
|
+
runtimeProfile: runtimeProvider.profile,
|
|
190
|
+
...runtimeState,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
174
194
|
// Drift inspection still runs when files are missing so status output can explain partial state.
|
|
175
195
|
const drift = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
|
|
176
196
|
const codexCheck = (0, codex_probe_1.probeCodexRuntime)();
|
|
@@ -202,6 +222,18 @@ async function runDoctor(args) {
|
|
|
202
222
|
warnings: issues.length === 0 ? [] : [`doctor found ${issues.length} issue(s)`],
|
|
203
223
|
};
|
|
204
224
|
}
|
|
225
|
+
function mapBridgeDiagnosticCode(cause) {
|
|
226
|
+
if (cause === "Copilot bridge state manifest is missing.") {
|
|
227
|
+
return "BRIDGE_STATE_MISSING";
|
|
228
|
+
}
|
|
229
|
+
if (cause === "Copilot bridge runtime state exists but no active Copilot bridge provider is selected.") {
|
|
230
|
+
return "BRIDGE_STATE_STALE";
|
|
231
|
+
}
|
|
232
|
+
if (cause === "Copilot bridge state base URL does not match the provider runtime configuration.") {
|
|
233
|
+
return "PROVIDER_BASE_URL_MISMATCH";
|
|
234
|
+
}
|
|
235
|
+
return "BRIDGE_HEALTHCHECK_FAILED";
|
|
236
|
+
}
|
|
205
237
|
/**
|
|
206
238
|
* Maps structured config consistency issues onto stable human-readable diagnostic text.
|
|
207
239
|
*/
|
|
@@ -41,6 +41,16 @@ async function switchProvider(args) {
|
|
|
41
41
|
}
|
|
42
42
|
await (0, copilot_adapter_1.readCopilotAuthState)();
|
|
43
43
|
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(args.providerName, provider);
|
|
44
|
+
const nextProvider = bridge.portChanged
|
|
45
|
+
? (0, providers_1.cleanProviderRecord)({
|
|
46
|
+
...provider,
|
|
47
|
+
baseUrl: bridge.baseUrl,
|
|
48
|
+
runtime: {
|
|
49
|
+
...provider.runtime,
|
|
50
|
+
bridgePort: bridge.port,
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
: provider;
|
|
44
54
|
try {
|
|
45
55
|
return (0, run_mutation_1.runMutation)({
|
|
46
56
|
codexDir: args.codexDir,
|
|
@@ -54,14 +64,32 @@ async function switchProvider(args) {
|
|
|
54
64
|
mutate: () => {
|
|
55
65
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
56
66
|
setActiveProfile: provider.profile,
|
|
67
|
+
upsertModelProviders: bridge.portChanged
|
|
68
|
+
? {
|
|
69
|
+
[provider.profile]: {
|
|
70
|
+
baseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(nextProvider.runtime),
|
|
71
|
+
envKey,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
: undefined,
|
|
57
75
|
});
|
|
76
|
+
if (bridge.portChanged) {
|
|
77
|
+
(0, providers_repo_1.writeProvidersFile)(args.providersPath, {
|
|
78
|
+
providers: {
|
|
79
|
+
...providers.providers,
|
|
80
|
+
[args.providerName]: nextProvider,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
58
84
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
59
85
|
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
60
|
-
(0, auth_repo_1.writeAuthFile)(args.authPath,
|
|
86
|
+
(0, auth_repo_1.writeAuthFile)(args.authPath, nextProvider, existingAuth ?? undefined);
|
|
61
87
|
return {
|
|
62
88
|
provider: args.providerName,
|
|
63
|
-
profile:
|
|
64
|
-
envKey:
|
|
89
|
+
profile: nextProvider.profile,
|
|
90
|
+
envKey: nextProvider.envKey,
|
|
91
|
+
portChanged: bridge.portChanged,
|
|
92
|
+
bridgePort: bridge.port,
|
|
65
93
|
};
|
|
66
94
|
},
|
|
67
95
|
});
|
|
@@ -48,6 +48,7 @@ const list_providers_1 = require("../app/list-providers");
|
|
|
48
48
|
const remove_provider_1 = require("../app/remove-provider");
|
|
49
49
|
const rollback_backup_1 = require("../app/rollback-backup");
|
|
50
50
|
const run_doctor_1 = require("../app/run-doctor");
|
|
51
|
+
const bridge_1 = require("../app/bridge");
|
|
51
52
|
const setup_codex_1 = require("../app/setup-codex");
|
|
52
53
|
const show_config_1 = require("../app/show-config");
|
|
53
54
|
const show_provider_1 = require("../app/show-provider");
|
|
@@ -90,6 +91,36 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
90
91
|
return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
|
|
91
92
|
case "status":
|
|
92
93
|
return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath);
|
|
94
|
+
case "bridge-start": {
|
|
95
|
+
const providerName = parsed.positionals[0] ?? null;
|
|
96
|
+
return (0, bridge_1.startBridge)({
|
|
97
|
+
providersPath: paths.providersPath,
|
|
98
|
+
configPath: paths.configPath,
|
|
99
|
+
providerName,
|
|
100
|
+
runtime,
|
|
101
|
+
json: ctx.options.json,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
case "bridge-stop": {
|
|
105
|
+
const providerName = parsed.positionals[0] ?? null;
|
|
106
|
+
return (0, bridge_1.stopBridge)({
|
|
107
|
+
providersPath: paths.providersPath,
|
|
108
|
+
configPath: paths.configPath,
|
|
109
|
+
providerName,
|
|
110
|
+
runtime,
|
|
111
|
+
json: ctx.options.json,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
case "bridge-status": {
|
|
115
|
+
const providerName = parsed.positionals[0] ?? null;
|
|
116
|
+
return (0, bridge_1.statusBridge)({
|
|
117
|
+
providersPath: paths.providersPath,
|
|
118
|
+
configPath: paths.configPath,
|
|
119
|
+
providerName,
|
|
120
|
+
runtime,
|
|
121
|
+
json: ctx.options.json,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
93
124
|
case "init": {
|
|
94
125
|
let codexDir = ctx.options.codexDir;
|
|
95
126
|
const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
|
package/dist/commands/help.js
CHANGED
|
@@ -41,6 +41,48 @@ exports.COMMANDS = [
|
|
|
41
41
|
],
|
|
42
42
|
examples: ["codexs config list-profiles", "codexs config list-profiles --json"],
|
|
43
43
|
},
|
|
44
|
+
{
|
|
45
|
+
id: "bridge-start",
|
|
46
|
+
tokens: ["bridge", "start"],
|
|
47
|
+
handler: handlers_1.handleRegisteredCommand,
|
|
48
|
+
group: "write",
|
|
49
|
+
summary: "Start or reuse the managed Copilot bridge.",
|
|
50
|
+
usage: ["codexs bridge start [provider] [--json] [--codex-dir <path>]"],
|
|
51
|
+
details: [
|
|
52
|
+
"Resolves a Copilot bridge provider by explicit name, active provider, sole provider, or TTY selection.",
|
|
53
|
+
"Reuses a healthy bridge for the same provider and replaces a different managed provider when needed.",
|
|
54
|
+
"If the preferred port is occupied, automatically selects another free 5-digit port and persists it.",
|
|
55
|
+
],
|
|
56
|
+
examples: ["codexs bridge start", "codexs bridge start copilot-main"],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "bridge-stop",
|
|
60
|
+
tokens: ["bridge", "stop"],
|
|
61
|
+
handler: handlers_1.handleRegisteredCommand,
|
|
62
|
+
group: "recovery",
|
|
63
|
+
summary: "Stop the managed Copilot bridge.",
|
|
64
|
+
usage: ["codexs bridge stop [provider] [--json] [--codex-dir <path>]"],
|
|
65
|
+
details: [
|
|
66
|
+
"Prefers the runtime-state instance when present and uses an explicit provider as a guard.",
|
|
67
|
+
"Clears the runtime-state manifest without mutating providers.json or auth.json.",
|
|
68
|
+
"Is idempotent when no managed bridge is currently running.",
|
|
69
|
+
],
|
|
70
|
+
examples: ["codexs bridge stop", "codexs bridge stop copilot-main"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "bridge-status",
|
|
74
|
+
tokens: ["bridge", "status"],
|
|
75
|
+
handler: handlers_1.handleRegisteredCommand,
|
|
76
|
+
group: "read",
|
|
77
|
+
summary: "Inspect the managed Copilot bridge.",
|
|
78
|
+
usage: ["codexs bridge status [provider] [--json] [--codex-dir <path>]"],
|
|
79
|
+
details: [
|
|
80
|
+
"Reports runtime-state, provider binding, and whether the live worker matches the expected provider.",
|
|
81
|
+
"Prefers the runtime-state instance when one is present.",
|
|
82
|
+
"Uses an explicit provider as a guard instead of silently switching targets.",
|
|
83
|
+
],
|
|
84
|
+
examples: ["codexs bridge status", "codexs bridge status copilot-main"],
|
|
85
|
+
},
|
|
44
86
|
{
|
|
45
87
|
id: "init",
|
|
46
88
|
tokens: ["init"],
|
|
@@ -5,7 +5,7 @@ const copilot_adapter_1 = require("./copilot-adapter");
|
|
|
5
5
|
async function main() {
|
|
6
6
|
const provider = process.env.CODEX_SWITCH_BRIDGE_PROVIDER ?? "copilot";
|
|
7
7
|
const host = process.env.CODEX_SWITCH_BRIDGE_HOST ?? "127.0.0.1";
|
|
8
|
-
const port = Number(process.env.CODEX_SWITCH_BRIDGE_PORT ?? "
|
|
8
|
+
const port = Number(process.env.CODEX_SWITCH_BRIDGE_PORT ?? "41415");
|
|
9
9
|
const apiKey = process.env.CODEX_SWITCH_BRIDGE_API_KEY ?? "";
|
|
10
10
|
await (0, copilot_bridge_1.startCopilotBridgeServer)({
|
|
11
11
|
host,
|