@minniexcode/codex-switch 0.0.7 → 0.0.8
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.md +1 -1
- package/dist/app/add-provider.js +70 -3
- package/dist/app/get-status.js +24 -1
- package/dist/app/run-doctor.js +36 -1
- package/dist/app/setup-codex.js +2 -2
- package/dist/app/switch-provider.js +47 -1
- package/dist/cli.js +1 -1
- package/dist/commands/handlers.js +27 -2
- package/dist/commands/registry.js +14 -2
- package/dist/domain/providers.js +74 -0
- package/dist/runtime/copilot-adapter.js +173 -0
- package/dist/runtime/copilot-bridge-worker.js +25 -0
- package/dist/runtime/copilot-bridge.js +433 -0
- package/dist/runtime/copilot-installer.js +125 -0
- package/dist/runtime/copilot-sdk-loader.js +59 -0
- package/dist/storage/fs-utils.js +3 -0
- package/dist/storage/runtime-state-repo.js +80 -0
- package/docs/Design/codex-switch-v0.0.8-design.md +132 -0
- package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +413 -0
- package/docs/PRD/codex-switch-prd-v0.0.8.md +62 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/app/add-provider.js
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.addProvider = addProvider;
|
|
37
|
+
const crypto = __importStar(require("node:crypto"));
|
|
4
38
|
const config_1 = require("../domain/config");
|
|
5
39
|
const providers_1 = require("../domain/providers");
|
|
6
40
|
const errors_1 = require("../domain/errors");
|
|
@@ -8,6 +42,7 @@ const config_repo_1 = require("../storage/config-repo");
|
|
|
8
42
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
9
43
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
10
44
|
const auth_repo_1 = require("../storage/auth-repo");
|
|
45
|
+
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
11
46
|
const run_mutation_1 = require("./run-mutation");
|
|
12
47
|
/**
|
|
13
48
|
* Adds a new provider record to the managed providers registry.
|
|
@@ -18,6 +53,34 @@ function addProvider(args) {
|
|
|
18
53
|
if (providers.providers[args.providerName]) {
|
|
19
54
|
throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Provider "${args.providerName}" already exists.`);
|
|
20
55
|
}
|
|
56
|
+
const bridgeHost = args.bridgeHost ?? "127.0.0.1";
|
|
57
|
+
const bridgePort = args.bridgePort ?? 4141;
|
|
58
|
+
const runtime = args.copilot
|
|
59
|
+
? {
|
|
60
|
+
kind: "copilot-sdk-bridge",
|
|
61
|
+
upstream: "github-copilot",
|
|
62
|
+
bridgeHost,
|
|
63
|
+
bridgePort,
|
|
64
|
+
bridgePath: "/v1",
|
|
65
|
+
premiumRequests: true,
|
|
66
|
+
authSource: "official-sdk",
|
|
67
|
+
sdkInstallMode: "lazy",
|
|
68
|
+
}
|
|
69
|
+
: undefined;
|
|
70
|
+
if (args.copilot) {
|
|
71
|
+
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
72
|
+
if (!installStatus.installed) {
|
|
73
|
+
if (!args.installCopilotSdk) {
|
|
74
|
+
throw (0, errors_1.cliError)(args.interactive ? "COPILOT_SDK_MISSING" : "COPILOT_SDK_INSTALL_REQUIRES_TTY", args.interactive
|
|
75
|
+
? "The optional Copilot SDK runtime is not installed. Re-run with --install-copilot-sdk or confirm installation interactively."
|
|
76
|
+
: "The optional Copilot SDK runtime is not installed. Pass --install-copilot-sdk when running non-interactively.", {
|
|
77
|
+
installDir: installStatus.installDir,
|
|
78
|
+
packageName: installStatus.packageName,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
(0, copilot_installer_1.installCopilotSdk)();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
21
84
|
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
22
85
|
const existingProfile = document.profiles.find((profile) => profile.name === args.profile);
|
|
23
86
|
const existingModelProvider = document.modelProviders.find((entry) => entry.name === args.profile);
|
|
@@ -38,7 +101,7 @@ function addProvider(args) {
|
|
|
38
101
|
const upsertModelProviders = !existingModelProvider && args.createProfile
|
|
39
102
|
? {
|
|
40
103
|
[args.profile]: {
|
|
41
|
-
baseUrl: args.baseUrl ?? undefined,
|
|
104
|
+
baseUrl: args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined,
|
|
42
105
|
envKey: (0, config_1.buildManagedProfileEnvKey)(args.profile),
|
|
43
106
|
},
|
|
44
107
|
}
|
|
@@ -47,16 +110,19 @@ function addProvider(args) {
|
|
|
47
110
|
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
|
|
48
111
|
}
|
|
49
112
|
const envKey = existingModelProvider?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(args.profile);
|
|
113
|
+
const apiKey = args.copilot ? args.bridgeApiKey ?? crypto.randomBytes(24).toString("hex") : args.apiKey;
|
|
114
|
+
const baseUrl = args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined;
|
|
50
115
|
const next = {
|
|
51
116
|
providers: {
|
|
52
117
|
...providers.providers,
|
|
53
118
|
[args.providerName]: (0, providers_1.cleanProviderRecord)({
|
|
54
119
|
profile: args.profile,
|
|
55
|
-
apiKey
|
|
120
|
+
apiKey,
|
|
56
121
|
envKey,
|
|
57
|
-
baseUrl
|
|
122
|
+
baseUrl,
|
|
58
123
|
note: args.note ?? undefined,
|
|
59
124
|
tags: args.tags,
|
|
125
|
+
runtime,
|
|
60
126
|
}),
|
|
61
127
|
},
|
|
62
128
|
};
|
|
@@ -87,6 +153,7 @@ function addProvider(args) {
|
|
|
87
153
|
provider: args.providerName,
|
|
88
154
|
profile: args.profile,
|
|
89
155
|
envKey,
|
|
156
|
+
runtimeKind: runtime?.kind ?? null,
|
|
90
157
|
createdProfileSections: configPlan.createdProfileSections,
|
|
91
158
|
createdModelProviderSections: configPlan.createdModelProviderSections,
|
|
92
159
|
deletedProfileSections: configPlan.deletedProfileSections,
|
package/dist/app/get-status.js
CHANGED
|
@@ -41,10 +41,13 @@ const providers_1 = require("../domain/providers");
|
|
|
41
41
|
const config_repo_1 = require("../storage/config-repo");
|
|
42
42
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
43
43
|
const auth_repo_1 = require("../storage/auth-repo");
|
|
44
|
+
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
45
|
+
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
46
|
+
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
44
47
|
/**
|
|
45
48
|
* Reports the current on-disk runtime state and how it maps back to managed providers.
|
|
46
49
|
*/
|
|
47
|
-
function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
50
|
+
async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
48
51
|
const configExists = fs.existsSync(configPath);
|
|
49
52
|
const providersExists = fs.existsSync(providersPath);
|
|
50
53
|
let currentProfile = null;
|
|
@@ -64,6 +67,17 @@ function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
64
67
|
}
|
|
65
68
|
const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
|
|
66
69
|
const activeProviderCandidates = currentProfile && providers ? (0, providers_1.findProvidersByProfile)(providers, currentProfile) : [];
|
|
70
|
+
const activeProvider = activeProviderCandidates.length === 1 && providers ? providers.providers[activeProviderCandidates[0]] : null;
|
|
71
|
+
const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
72
|
+
const copilotBridge = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider) ? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider) : null;
|
|
73
|
+
const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
|
|
74
|
+
? await (0, copilot_adapter_1.readCopilotAuthState)().catch((error) => ({
|
|
75
|
+
ready: false,
|
|
76
|
+
source: "official-sdk",
|
|
77
|
+
mode: "session",
|
|
78
|
+
error: error instanceof Error ? error.message : String(error),
|
|
79
|
+
}))
|
|
80
|
+
: null;
|
|
67
81
|
if (liveState.canBackfillActiveProvider) {
|
|
68
82
|
// Surface unmanaged live state without mutating anything during a read-only status call.
|
|
69
83
|
warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
|
|
@@ -80,6 +94,15 @@ function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
80
94
|
provider: liveState.mappedProvider,
|
|
81
95
|
activeProviderResolvable: activeProviderCandidates.length === 1,
|
|
82
96
|
activeProviderCandidates,
|
|
97
|
+
runtimeProvider: activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider) ? activeProvider.runtime?.kind ?? null : null,
|
|
98
|
+
copilotSdk: {
|
|
99
|
+
installed: copilotInstall.installed,
|
|
100
|
+
installDir: copilotInstall.installDir,
|
|
101
|
+
packageName: copilotInstall.packageName,
|
|
102
|
+
packageVersion: copilotInstall.packageVersion ?? null,
|
|
103
|
+
},
|
|
104
|
+
copilotAuth,
|
|
105
|
+
copilotBridge,
|
|
83
106
|
liveState,
|
|
84
107
|
auth: authState,
|
|
85
108
|
configProfiles: configViews,
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -43,10 +43,13 @@ const errors_1 = require("../domain/errors");
|
|
|
43
43
|
const codex_probe_1 = require("../runtime/codex-probe");
|
|
44
44
|
const auth_repo_1 = require("../storage/auth-repo");
|
|
45
45
|
const providers_1 = require("../domain/providers");
|
|
46
|
+
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
47
|
+
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
48
|
+
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
46
49
|
/**
|
|
47
50
|
* Performs consistency checks across config.toml, providers.json, and the local Codex CLI.
|
|
48
51
|
*/
|
|
49
|
-
function runDoctor(args) {
|
|
52
|
+
async function runDoctor(args) {
|
|
50
53
|
const issues = [];
|
|
51
54
|
let currentProfile = null;
|
|
52
55
|
let providers = null;
|
|
@@ -134,6 +137,38 @@ function runDoctor(args) {
|
|
|
134
137
|
provider: matches[0],
|
|
135
138
|
});
|
|
136
139
|
}
|
|
140
|
+
if ((0, providers_1.isCopilotBridgeProvider)(activeProvider)) {
|
|
141
|
+
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
142
|
+
if (!installStatus.installed) {
|
|
143
|
+
issues.push({
|
|
144
|
+
code: "COPILOT_SDK_MISSING",
|
|
145
|
+
message: "The optional Copilot SDK runtime is not installed.",
|
|
146
|
+
installDir: installStatus.installDir,
|
|
147
|
+
packageName: installStatus.packageName,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
await (0, copilot_adapter_1.readCopilotAuthState)();
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
const normalized = (0, errors_1.normalizeError)(error);
|
|
155
|
+
issues.push({
|
|
156
|
+
code: normalized.code,
|
|
157
|
+
message: normalized.message,
|
|
158
|
+
...(normalized.details ?? {}),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider);
|
|
162
|
+
if (!bridge.ok) {
|
|
163
|
+
issues.push({
|
|
164
|
+
code: bridge.cause === "Copilot bridge state base URL does not match the provider runtime configuration."
|
|
165
|
+
? "PROVIDER_BASE_URL_MISMATCH"
|
|
166
|
+
: "BRIDGE_HEALTHCHECK_FAILED",
|
|
167
|
+
message: bridge.cause,
|
|
168
|
+
...(bridge.details ?? {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
137
172
|
}
|
|
138
173
|
}
|
|
139
174
|
// Drift inspection still runs when files are missing so status output can explain partial state.
|
package/dist/app/setup-codex.js
CHANGED
|
@@ -49,7 +49,7 @@ const MIN_CODEX_VERSION = "0.0.1";
|
|
|
49
49
|
/**
|
|
50
50
|
* Migrates unmanaged Codex config profiles into a managed providers.json registry.
|
|
51
51
|
*/
|
|
52
|
-
function migrateCodex(args) {
|
|
52
|
+
async function migrateCodex(args) {
|
|
53
53
|
const available = (0, codex_cli_1.checkCodexAvailable)();
|
|
54
54
|
if (!available.ok) {
|
|
55
55
|
throw (0, errors_1.cliError)("CODEX_NOT_INSTALLED", "codex CLI is not available.", {
|
|
@@ -148,7 +148,7 @@ function migrateCodex(args) {
|
|
|
148
148
|
},
|
|
149
149
|
});
|
|
150
150
|
// Re-run doctor on the final state so migrate returns immediate post-migration diagnostics.
|
|
151
|
-
const doctor = (0, run_doctor_1.runDoctor)({
|
|
151
|
+
const doctor = await (0, run_doctor_1.runDoctor)({
|
|
152
152
|
codexDir: args.codexDir,
|
|
153
153
|
configPath: args.configPath,
|
|
154
154
|
providersPath: args.providersPath,
|
|
@@ -2,14 +2,18 @@
|
|
|
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");
|
|
5
6
|
const config_repo_1 = require("../storage/config-repo");
|
|
6
7
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
7
8
|
const auth_repo_1 = require("../storage/auth-repo");
|
|
9
|
+
const copilot_bridge_1 = require("../runtime/copilot-bridge");
|
|
10
|
+
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
11
|
+
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
8
12
|
const run_mutation_1 = require("./run-mutation");
|
|
9
13
|
/**
|
|
10
14
|
* Switches the active Codex profile and rewrites auth.json for the target provider.
|
|
11
15
|
*/
|
|
12
|
-
function switchProvider(args) {
|
|
16
|
+
async function switchProvider(args) {
|
|
13
17
|
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
14
18
|
const provider = providers.providers[args.providerName];
|
|
15
19
|
if (!provider) {
|
|
@@ -27,6 +31,48 @@ function switchProvider(args) {
|
|
|
27
31
|
runtimeEnvKey: envKey,
|
|
28
32
|
});
|
|
29
33
|
}
|
|
34
|
+
if ((0, providers_1.isCopilotBridgeProvider)(provider)) {
|
|
35
|
+
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
36
|
+
if (!installStatus.installed) {
|
|
37
|
+
throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
|
|
38
|
+
installDir: installStatus.installDir,
|
|
39
|
+
packageName: installStatus.packageName,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
await (0, copilot_adapter_1.readCopilotAuthState)();
|
|
43
|
+
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(args.providerName, provider);
|
|
44
|
+
try {
|
|
45
|
+
return (0, run_mutation_1.runMutation)({
|
|
46
|
+
codexDir: args.codexDir,
|
|
47
|
+
backupsDir: args.backupsDir,
|
|
48
|
+
latestBackupPath: args.latestBackupPath,
|
|
49
|
+
operation: "switch",
|
|
50
|
+
files: [
|
|
51
|
+
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
52
|
+
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
53
|
+
],
|
|
54
|
+
mutate: () => {
|
|
55
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
56
|
+
setActiveProfile: provider.profile,
|
|
57
|
+
});
|
|
58
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
59
|
+
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
60
|
+
(0, auth_repo_1.writeAuthFile)(args.authPath, provider, existingAuth ?? undefined);
|
|
61
|
+
return {
|
|
62
|
+
provider: args.providerName,
|
|
63
|
+
profile: provider.profile,
|
|
64
|
+
envKey: provider.envKey,
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (!bridge.reused) {
|
|
71
|
+
(0, copilot_bridge_1.stopCopilotBridge)();
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
30
76
|
return (0, run_mutation_1.runMutation)({
|
|
31
77
|
codexDir: args.codexDir,
|
|
32
78
|
backupsDir: args.backupsDir,
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ const args_1 = require("./commands/args");
|
|
|
9
9
|
const help_1 = require("./commands/help");
|
|
10
10
|
const errors_1 = require("./domain/errors");
|
|
11
11
|
const output_1 = require("./cli/output");
|
|
12
|
-
const VERSION = "0.0.
|
|
12
|
+
const VERSION = "0.0.8";
|
|
13
13
|
/**
|
|
14
14
|
* Prints the command help text to stdout.
|
|
15
15
|
*/
|
|
@@ -58,6 +58,7 @@ const providers_1 = require("../domain/providers");
|
|
|
58
58
|
const add_interactive_1 = require("../interaction/add-interactive");
|
|
59
59
|
const interactive_1 = require("../interaction/interactive");
|
|
60
60
|
const prompt_1 = require("../interaction/prompt");
|
|
61
|
+
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
61
62
|
const config_repo_1 = require("../storage/config-repo");
|
|
62
63
|
const codex_paths_1 = require("../storage/codex-paths");
|
|
63
64
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
@@ -155,6 +156,9 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
155
156
|
if (!providerName) {
|
|
156
157
|
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
|
|
157
158
|
}
|
|
159
|
+
if ((0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk")) {
|
|
160
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--install-copilot-sdk is only supported with add --copilot.");
|
|
161
|
+
}
|
|
158
162
|
return (0, switch_provider_1.switchProvider)({
|
|
159
163
|
codexDir: paths.codexDir,
|
|
160
164
|
backupsDir: paths.backupsDir,
|
|
@@ -221,7 +225,22 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
221
225
|
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
|
|
222
226
|
let tags = parsed.commandOptions.get("--tag") ?? [];
|
|
223
227
|
let createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
224
|
-
|
|
228
|
+
const copilot = (0, args_1.hasFlag)(parsed.commandOptions, "--copilot");
|
|
229
|
+
const bridgeHost = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-host", false);
|
|
230
|
+
const bridgePortValue = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-port", false);
|
|
231
|
+
const bridgeApiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-api-key", false);
|
|
232
|
+
let installCopilotSdk = (0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk");
|
|
233
|
+
const bridgePort = bridgePortValue ? Number(bridgePortValue) : null;
|
|
234
|
+
if (copilot && apiKey) {
|
|
235
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--copilot does not allow --api-key. Use --bridge-api-key for the local bridge secret.");
|
|
236
|
+
}
|
|
237
|
+
if (bridgePortValue && (!Number.isInteger(bridgePort) || bridgePort === null || bridgePort <= 0)) {
|
|
238
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--bridge-port must be a positive integer.");
|
|
239
|
+
}
|
|
240
|
+
if (copilot && !installCopilotSdk && (0, interactive_1.canPrompt)(runtime, ctx.options.json) && !(0, copilot_installer_1.probeCopilotSdkInstall)().installed) {
|
|
241
|
+
installCopilotSdk = await runtime.confirmAction("The optional Copilot SDK runtime is not installed. Install it now?");
|
|
242
|
+
}
|
|
243
|
+
if (!providerName || !profile || (!apiKey && !copilot)) {
|
|
225
244
|
if (ctx.options.json || !runtime.isInteractive()) {
|
|
226
245
|
throw (0, add_interactive_1.createNonInteractiveAddError)();
|
|
227
246
|
}
|
|
@@ -251,12 +270,18 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
251
270
|
authPath: paths.authPath,
|
|
252
271
|
providerName,
|
|
253
272
|
profile,
|
|
254
|
-
apiKey,
|
|
273
|
+
apiKey: apiKey ?? "",
|
|
255
274
|
baseUrl,
|
|
256
275
|
model,
|
|
257
276
|
note,
|
|
258
277
|
tags,
|
|
259
278
|
createProfile,
|
|
279
|
+
copilot,
|
|
280
|
+
bridgeHost,
|
|
281
|
+
bridgePort,
|
|
282
|
+
bridgeApiKey,
|
|
283
|
+
installCopilotSdk,
|
|
284
|
+
interactive: (0, interactive_1.canPrompt)(runtime, ctx.options.json),
|
|
260
285
|
});
|
|
261
286
|
}
|
|
262
287
|
case "edit": {
|
|
@@ -128,6 +128,7 @@ exports.COMMANDS = [
|
|
|
128
128
|
usage: ["codexs status [--json] [--codex-dir <path>]"],
|
|
129
129
|
details: [
|
|
130
130
|
"Reports file presence, current profile, and whether the live profile is mapped.",
|
|
131
|
+
"When the active provider uses a local runtime bridge, status also reports bridge and SDK state.",
|
|
131
132
|
"Surfaces config consistency signals without mutating any files.",
|
|
132
133
|
"Use doctor for deeper diagnostics.",
|
|
133
134
|
],
|
|
@@ -160,6 +161,7 @@ exports.COMMANDS = [
|
|
|
160
161
|
summary: "Add a provider with explicit flags or progressive TTY prompts.",
|
|
161
162
|
usage: [
|
|
162
163
|
"codexs add <provider> --profile <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]",
|
|
164
|
+
"codexs add <provider> --copilot --profile <name> [--bridge-host <host>] [--bridge-port <port>] [--bridge-api-key <secret>] [--install-copilot-sdk]",
|
|
163
165
|
"codexs add <provider> --profile <name> --api-key <key> --create-profile --model <name> --base-url <url>",
|
|
164
166
|
"codexs add [--profile <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...]",
|
|
165
167
|
],
|
|
@@ -170,8 +172,13 @@ exports.COMMANDS = [
|
|
|
170
172
|
"Interactive tags use preset multi-select only.",
|
|
171
173
|
"Automation and non-TTY environments must pass all required values explicitly.",
|
|
172
174
|
"Creating a missing profile section requires --create-profile together with --model and --base-url.",
|
|
175
|
+
"Use --copilot to create a GitHub Copilot bridge provider backed by the official SDK.",
|
|
176
|
+
],
|
|
177
|
+
examples: [
|
|
178
|
+
"codexs add packycode --profile packycode --api-key sk-xxx",
|
|
179
|
+
"codexs add copilot-main --copilot --profile copilot-main --install-copilot-sdk",
|
|
180
|
+
"codexs add",
|
|
173
181
|
],
|
|
174
|
-
examples: ["codexs add packycode --profile packycode --api-key sk-xxx", "codexs add packycode --profile packycode", "codexs add"],
|
|
175
182
|
},
|
|
176
183
|
{
|
|
177
184
|
id: "switch",
|
|
@@ -184,6 +191,7 @@ exports.COMMANDS = [
|
|
|
184
191
|
"When <provider> is omitted in a TTY, an interactive provider selector is shown.",
|
|
185
192
|
"When <provider> is passed explicitly, switch proceeds directly without extra confirmation.",
|
|
186
193
|
"Switch updates the active config profile and rewrites auth.json from the provider envKey/apiKey pair.",
|
|
194
|
+
"Copilot bridge providers probe the optional official SDK before switching and fail fast if it is missing.",
|
|
187
195
|
"Backs up config.toml and auth.json, then rolls back on failure.",
|
|
188
196
|
],
|
|
189
197
|
examples: ["codexs switch freemodel", "codexs switch packycode --json"],
|
|
@@ -252,7 +260,11 @@ exports.COMMANDS = [
|
|
|
252
260
|
group: "recovery",
|
|
253
261
|
summary: "Run configuration and environment diagnostics.",
|
|
254
262
|
usage: ["codexs doctor [--json] [--codex-dir <path>]"],
|
|
255
|
-
details: [
|
|
263
|
+
details: [
|
|
264
|
+
"Checks the expected config files, provider/profile consistency, and Codex CLI availability.",
|
|
265
|
+
"Copilot bridge providers add runtime dependency, auth, and bridge health diagnostics.",
|
|
266
|
+
"Returns structured issues so users and AI agents can act on them.",
|
|
267
|
+
],
|
|
256
268
|
examples: ["codexs doctor", "codexs doctor --json"],
|
|
257
269
|
},
|
|
258
270
|
{
|
package/dist/domain/providers.js
CHANGED
|
@@ -6,6 +6,9 @@ exports.sortProviders = sortProviders;
|
|
|
6
6
|
exports.findProviderByProfile = findProviderByProfile;
|
|
7
7
|
exports.findProvidersByProfile = findProvidersByProfile;
|
|
8
8
|
exports.maskSecret = maskSecret;
|
|
9
|
+
exports.isRuntimeBackedProvider = isRuntimeBackedProvider;
|
|
10
|
+
exports.isCopilotBridgeProvider = isCopilotBridgeProvider;
|
|
11
|
+
exports.buildCopilotBridgeBaseUrl = buildCopilotBridgeBaseUrl;
|
|
9
12
|
/**
|
|
10
13
|
* Validates and normalizes unknown JSON into the providers.json domain model.
|
|
11
14
|
*/
|
|
@@ -42,6 +45,13 @@ function validateProvidersShape(input) {
|
|
|
42
45
|
(!Array.isArray(provider.tags) || provider.tags.some((tag) => typeof tag !== "string"))) {
|
|
43
46
|
throw new Error(`Provider "${name}" has invalid tags.`);
|
|
44
47
|
}
|
|
48
|
+
if (provider.runtime !== undefined) {
|
|
49
|
+
validateProviderRuntime(name, provider.runtime);
|
|
50
|
+
const expectedBaseUrl = buildCopilotBridgeBaseUrl(provider.runtime);
|
|
51
|
+
if (typeof provider.baseUrl !== "string" || provider.baseUrl.trim() !== expectedBaseUrl) {
|
|
52
|
+
throw new Error(`Provider "${name}" baseUrl must match runtime bridge base URL "${expectedBaseUrl}".`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
45
55
|
// Normalize provider fields during validation so the persisted format stays clean.
|
|
46
56
|
providers[name] = cleanProviderRecord({
|
|
47
57
|
profile: provider.profile,
|
|
@@ -50,6 +60,7 @@ function validateProvidersShape(input) {
|
|
|
50
60
|
baseUrl: provider.baseUrl,
|
|
51
61
|
note: provider.note,
|
|
52
62
|
tags: provider.tags,
|
|
63
|
+
runtime: provider.runtime,
|
|
53
64
|
});
|
|
54
65
|
}
|
|
55
66
|
return { providers };
|
|
@@ -72,6 +83,18 @@ function cleanProviderRecord(record) {
|
|
|
72
83
|
if (record.tags && record.tags.length > 0) {
|
|
73
84
|
next.tags = record.tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0);
|
|
74
85
|
}
|
|
86
|
+
if (record.runtime) {
|
|
87
|
+
next.runtime = {
|
|
88
|
+
kind: record.runtime.kind,
|
|
89
|
+
upstream: record.runtime.upstream,
|
|
90
|
+
bridgeHost: record.runtime.bridgeHost.trim(),
|
|
91
|
+
bridgePort: record.runtime.bridgePort,
|
|
92
|
+
bridgePath: record.runtime.bridgePath,
|
|
93
|
+
premiumRequests: record.runtime.premiumRequests,
|
|
94
|
+
authSource: record.runtime.authSource,
|
|
95
|
+
sdkInstallMode: record.runtime.sdkInstallMode,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
75
98
|
return next;
|
|
76
99
|
}
|
|
77
100
|
/**
|
|
@@ -114,3 +137,54 @@ function maskSecret(value) {
|
|
|
114
137
|
}
|
|
115
138
|
return `${value.slice(0, 3)}***${value.slice(-2)}`;
|
|
116
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns whether one provider record relies on an auxiliary runtime component.
|
|
142
|
+
*/
|
|
143
|
+
function isRuntimeBackedProvider(provider) {
|
|
144
|
+
return Boolean(provider.runtime);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Returns whether one provider uses the GitHub Copilot SDK bridge runtime.
|
|
148
|
+
*/
|
|
149
|
+
function isCopilotBridgeProvider(provider) {
|
|
150
|
+
return provider.runtime?.kind === "copilot-sdk-bridge";
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Builds the canonical local bridge URL for one Copilot runtime provider.
|
|
154
|
+
*/
|
|
155
|
+
function buildCopilotBridgeBaseUrl(runtime) {
|
|
156
|
+
return `http://${runtime.bridgeHost}:${runtime.bridgePort}${runtime.bridgePath}`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Validates one runtime-backed provider block.
|
|
160
|
+
*/
|
|
161
|
+
function validateProviderRuntime(name, runtime) {
|
|
162
|
+
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
|
|
163
|
+
throw new Error(`Provider "${name}" has an invalid runtime block.`);
|
|
164
|
+
}
|
|
165
|
+
const record = runtime;
|
|
166
|
+
if (record.kind !== "copilot-sdk-bridge") {
|
|
167
|
+
throw new Error(`Provider "${name}" has an unsupported runtime kind.`);
|
|
168
|
+
}
|
|
169
|
+
if (record.upstream !== "github-copilot") {
|
|
170
|
+
throw new Error(`Provider "${name}" has an invalid runtime upstream.`);
|
|
171
|
+
}
|
|
172
|
+
if (typeof record.bridgeHost !== "string" || record.bridgeHost.trim() === "") {
|
|
173
|
+
throw new Error(`Provider "${name}" has an invalid runtime bridgeHost.`);
|
|
174
|
+
}
|
|
175
|
+
if (typeof record.bridgePort !== "number" || !Number.isInteger(record.bridgePort) || record.bridgePort <= 0) {
|
|
176
|
+
throw new Error(`Provider "${name}" has an invalid runtime bridgePort.`);
|
|
177
|
+
}
|
|
178
|
+
if (record.bridgePath !== "/v1") {
|
|
179
|
+
throw new Error(`Provider "${name}" has an invalid runtime bridgePath.`);
|
|
180
|
+
}
|
|
181
|
+
if (record.premiumRequests !== true) {
|
|
182
|
+
throw new Error(`Provider "${name}" must enable runtime premiumRequests.`);
|
|
183
|
+
}
|
|
184
|
+
if (record.authSource !== "official-sdk") {
|
|
185
|
+
throw new Error(`Provider "${name}" has an invalid runtime authSource.`);
|
|
186
|
+
}
|
|
187
|
+
if (record.sdkInstallMode !== "lazy") {
|
|
188
|
+
throw new Error(`Provider "${name}" has an invalid runtime sdkInstallMode.`);
|
|
189
|
+
}
|
|
190
|
+
}
|