@minniexcode/codex-switch 0.0.10 → 0.0.11

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.
Files changed (38) hide show
  1. package/README.AI.md +52 -15
  2. package/README.CN.md +81 -48
  3. package/README.md +75 -34
  4. package/dist/app/add-provider.js +29 -15
  5. package/dist/app/bridge.js +15 -14
  6. package/dist/app/edit-provider.js +1 -1
  7. package/dist/app/get-status.js +13 -6
  8. package/dist/app/import-providers.js +1 -1
  9. package/dist/app/init-codex.js +13 -14
  10. package/dist/app/remove-provider.js +1 -1
  11. package/dist/app/run-doctor.js +12 -5
  12. package/dist/app/run-mutation.js +3 -2
  13. package/dist/app/setup-codex.js +3 -1
  14. package/dist/app/switch-provider.js +11 -13
  15. package/dist/cli.js +34 -2
  16. package/dist/commands/args.js +2 -2
  17. package/dist/commands/dispatch.js +40 -0
  18. package/dist/commands/handlers.js +121 -156
  19. package/dist/commands/help.js +2 -0
  20. package/dist/commands/registry.js +28 -9
  21. package/dist/domain/backups.js +4 -4
  22. package/dist/domain/config.js +110 -5
  23. package/dist/domain/providers.js +12 -0
  24. package/dist/domain/runtime-state.js +81 -5
  25. package/dist/runtime/copilot-adapter.js +12 -12
  26. package/dist/runtime/copilot-bridge.js +392 -44
  27. package/dist/runtime/copilot-cli.js +84 -12
  28. package/dist/runtime/copilot-installer.js +10 -9
  29. package/dist/runtime/copilot-sdk-loader.js +5 -5
  30. package/dist/storage/backup-repo.js +4 -4
  31. package/dist/storage/codex-paths.js +34 -8
  32. package/dist/storage/lock-repo.js +2 -4
  33. package/dist/storage/runtime-state-repo.js +14 -13
  34. package/dist/storage/tool-config-repo.js +111 -0
  35. package/docs/Design/codex-switch-v0.0.11-design.md +824 -0
  36. package/docs/PRD/codex-switch-prd-v0.0.11.md +577 -0
  37. package/docs/cli-usage.md +166 -295
  38. package/package.json +1 -1
@@ -117,8 +117,15 @@ function parseStructuredConfig(configContent) {
117
117
  name: modelProviderHeaderMatch[1],
118
118
  sectionStart: line.start,
119
119
  sectionEnd: configContent.length,
120
+ managedFieldInsertIndex: configContent.length,
120
121
  baseUrlValueRange: null,
121
122
  baseUrl: null,
123
+ nameValueRange: null,
124
+ providerName: null,
125
+ requiresOpenAiAuthValueRange: null,
126
+ requiresOpenAiAuth: null,
127
+ wireApiValueRange: null,
128
+ wireApi: null,
122
129
  };
123
130
  modelProviders.push(currentModelProvider);
124
131
  inRoot = false;
@@ -173,6 +180,30 @@ function parseStructuredConfig(configContent) {
173
180
  end: line.start + baseUrlMatch.valueEnd,
174
181
  };
175
182
  }
183
+ const nameMatch = matchKeyValueLine(line.content, "name");
184
+ if (nameMatch) {
185
+ currentModelProvider.providerName = nameMatch.value;
186
+ currentModelProvider.nameValueRange = {
187
+ start: line.start + nameMatch.valueStart,
188
+ end: line.start + nameMatch.valueEnd,
189
+ };
190
+ }
191
+ const requiresOpenAiAuthMatch = matchBooleanKeyValueLine(line.content, "requires_openai_auth");
192
+ if (requiresOpenAiAuthMatch) {
193
+ currentModelProvider.requiresOpenAiAuth = requiresOpenAiAuthMatch.value;
194
+ currentModelProvider.requiresOpenAiAuthValueRange = {
195
+ start: line.start + requiresOpenAiAuthMatch.valueStart,
196
+ end: line.start + requiresOpenAiAuthMatch.valueEnd,
197
+ };
198
+ }
199
+ const wireApiMatch = matchKeyValueLine(line.content, "wire_api");
200
+ if (wireApiMatch) {
201
+ currentModelProvider.wireApi = wireApiMatch.value;
202
+ currentModelProvider.wireApiValueRange = {
203
+ start: line.start + wireApiMatch.valueStart,
204
+ end: line.start + wireApiMatch.valueEnd,
205
+ };
206
+ }
176
207
  }
177
208
  }
178
209
  return {
@@ -184,7 +215,10 @@ function parseStructuredConfig(configContent) {
184
215
  ...profile,
185
216
  managedFieldInsertIndex: findManagedFieldInsertIndex(configContent, profile.sectionStart, profile.sectionEnd),
186
217
  })),
187
- modelProviders,
218
+ modelProviders: modelProviders.map((provider) => ({
219
+ ...provider,
220
+ managedFieldInsertIndex: findManagedFieldInsertIndex(configContent, provider.sectionStart, provider.sectionEnd),
221
+ })),
188
222
  };
189
223
  }
190
224
  /**
@@ -445,6 +479,7 @@ function planConfigMutation(document, args) {
445
479
  const section = modelProviderSectionMap.get(profileName);
446
480
  if (!section) {
447
481
  const baseUrl = fields.baseUrl?.trim() ?? "";
482
+ const providerName = fields.name?.trim() ?? "";
448
483
  if (!baseUrl) {
449
484
  throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${profileName}" requires base_url.`, {
450
485
  profile: profileName,
@@ -457,11 +492,16 @@ function planConfigMutation(document, args) {
457
492
  const prefix = document.rawText.length > 0 && !document.rawText.endsWith(document.lineEnding)
458
493
  ? document.lineEnding
459
494
  : "";
495
+ const requiresOpenAiAuth = fields.requiresOpenAiAuth;
496
+ const wireApi = fields.wireApi?.trim() ?? "";
460
497
  operations.push({
461
498
  kind: "insert-at",
462
499
  index: document.rawText.length,
463
500
  text: `${prefix}[model_providers.${profileName}]${document.lineEnding}` +
464
- `base_url = ${JSON.stringify(baseUrl)}${document.lineEnding}`,
501
+ `base_url = ${JSON.stringify(baseUrl)}${document.lineEnding}` +
502
+ (providerName ? `name = ${JSON.stringify(providerName)}${document.lineEnding}` : "") +
503
+ (requiresOpenAiAuth !== undefined ? `requires_openai_auth = ${String(requiresOpenAiAuth)}${document.lineEnding}` : "") +
504
+ (wireApi ? `wire_api = ${JSON.stringify(wireApi)}${document.lineEnding}` : ""),
465
505
  });
466
506
  createdModelProviderSections.push(profileName);
467
507
  continue;
@@ -545,11 +585,14 @@ function planSectionFieldMutation(document, section, fields, operations) {
545
585
  return updated;
546
586
  }
547
587
  /**
548
- * Plans base_url updates for one model_providers section.
588
+ * Plans managed field updates for one model_providers section.
549
589
  */
550
590
  function planModelProviderFieldMutation(section, fields, operations) {
551
591
  let updated = false;
552
592
  const baseUrlText = fields.baseUrl !== undefined ? JSON.stringify(fields.baseUrl) : null;
593
+ const nameText = fields.name !== undefined ? JSON.stringify(fields.name) : null;
594
+ const requiresOpenAiAuthText = fields.requiresOpenAiAuth !== undefined ? String(fields.requiresOpenAiAuth) : null;
595
+ const wireApiText = fields.wireApi !== undefined ? JSON.stringify(fields.wireApi) : null;
553
596
  const inserts = [];
554
597
  if (baseUrlText !== null && section.baseUrlValueRange) {
555
598
  if (section.baseUrl !== fields.baseUrl) {
@@ -564,14 +607,59 @@ function planModelProviderFieldMutation(section, fields, operations) {
564
607
  }
565
608
  else if (baseUrlText !== null) {
566
609
  inserts.push(`base_url = ${baseUrlText}`);
610
+ updated = true;
611
+ }
612
+ if (nameText !== null && section.nameValueRange) {
613
+ if (section.providerName !== fields.name) {
614
+ operations.push({
615
+ kind: "replace-range",
616
+ start: section.nameValueRange.start,
617
+ end: section.nameValueRange.end,
618
+ text: nameText,
619
+ });
620
+ updated = true;
621
+ }
622
+ }
623
+ else if (nameText !== null) {
624
+ inserts.push(`name = ${nameText}`);
625
+ updated = true;
626
+ }
627
+ if (requiresOpenAiAuthText !== null && section.requiresOpenAiAuthValueRange) {
628
+ if (section.requiresOpenAiAuth !== fields.requiresOpenAiAuth) {
629
+ operations.push({
630
+ kind: "replace-range",
631
+ start: section.requiresOpenAiAuthValueRange.start,
632
+ end: section.requiresOpenAiAuthValueRange.end,
633
+ text: requiresOpenAiAuthText,
634
+ });
635
+ updated = true;
636
+ }
637
+ }
638
+ else if (requiresOpenAiAuthText !== null) {
639
+ inserts.push(`requires_openai_auth = ${requiresOpenAiAuthText}`);
640
+ updated = true;
641
+ }
642
+ if (wireApiText !== null && section.wireApiValueRange) {
643
+ if (section.wireApi !== fields.wireApi) {
644
+ operations.push({
645
+ kind: "replace-range",
646
+ start: section.wireApiValueRange.start,
647
+ end: section.wireApiValueRange.end,
648
+ text: wireApiText,
649
+ });
650
+ updated = true;
651
+ }
652
+ }
653
+ else if (wireApiText !== null) {
654
+ inserts.push(`wire_api = ${wireApiText}`);
655
+ updated = true;
567
656
  }
568
657
  if (inserts.length > 0) {
569
658
  operations.push({
570
659
  kind: "insert-at",
571
- index: section.sectionEnd,
660
+ index: section.managedFieldInsertIndex,
572
661
  text: `${inserts.join("\n")}\n`,
573
662
  });
574
- updated = true;
575
663
  }
576
664
  return updated;
577
665
  }
@@ -616,6 +704,23 @@ function matchKeyValueLine(line, key) {
616
704
  valueEnd,
617
705
  };
618
706
  }
707
+ function matchBooleanKeyValueLine(line, key) {
708
+ const match = line.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(true|false)\\s*(#.*)?$`));
709
+ if (!match || match.index === undefined) {
710
+ return null;
711
+ }
712
+ const value = match[1] === "true";
713
+ const valueStart = line.indexOf(match[1], match.index);
714
+ if (valueStart === -1) {
715
+ return null;
716
+ }
717
+ const valueEnd = valueStart + match[1].length;
718
+ return {
719
+ value,
720
+ valueStart,
721
+ valueEnd,
722
+ };
723
+ }
619
724
  function findManagedFieldInsertIndex(rawText, sectionStart, sectionEnd) {
620
725
  const sectionText = rawText.slice(sectionStart, sectionEnd);
621
726
  const lines = splitWithOffsets(sectionText);
@@ -9,6 +9,7 @@ exports.maskSecret = maskSecret;
9
9
  exports.isRuntimeBackedProvider = isRuntimeBackedProvider;
10
10
  exports.isCopilotBridgeProvider = isCopilotBridgeProvider;
11
11
  exports.buildCopilotBridgeBaseUrl = buildCopilotBridgeBaseUrl;
12
+ exports.buildCopilotModelProviderProjection = buildCopilotModelProviderProjection;
12
13
  /**
13
14
  * Validates and normalizes unknown JSON into the providers.json domain model.
14
15
  */
@@ -150,6 +151,17 @@ function isCopilotBridgeProvider(provider) {
150
151
  function buildCopilotBridgeBaseUrl(runtime) {
151
152
  return `http://${runtime.bridgeHost}:${runtime.bridgePort}${runtime.bridgePath}`;
152
153
  }
154
+ /**
155
+ * Builds the Codex-facing custom model_provider projection for the managed Copilot bridge.
156
+ */
157
+ function buildCopilotModelProviderProjection(runtime) {
158
+ return {
159
+ baseUrl: buildCopilotBridgeBaseUrl(runtime),
160
+ name: "copilot",
161
+ requiresOpenAiAuth: true,
162
+ wireApi: "responses",
163
+ };
164
+ }
153
165
  /**
154
166
  * Validates one runtime-backed provider block.
155
167
  */
@@ -1,16 +1,92 @@
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.getStorageRoles = getStorageRoles;
4
37
  exports.inspectLiveStateDrift = inspectLiveStateDrift;
38
+ const path = __importStar(require("node:path"));
5
39
  /**
6
40
  * Returns the stable storage contract used by the CLI.
7
41
  */
8
- function getStorageRoles() {
42
+ function getStorageRoles(args) {
43
+ const toolHomeDir = path.dirname(path.resolve(args.providersPath));
44
+ const backupsDir = path.join(toolHomeDir, "backups");
45
+ const runtimeDir = args.runtimeDir ? path.resolve(args.runtimeDir) : path.join(toolHomeDir, "runtime");
46
+ const runtimesDir = args.runtimesDir ? path.resolve(args.runtimesDir) : path.join(toolHomeDir, "runtimes");
9
47
  return {
10
- managementSSOT: "providers.json",
11
- runtimeMirrors: ["config.toml"],
12
- authStateFile: "auth.json",
13
- rollbackState: "backups/latest.json",
48
+ toolHome: {
49
+ root: toolHomeDir,
50
+ toolConfig: path.join(toolHomeDir, "codex-switch.json"),
51
+ providers: path.resolve(args.providersPath),
52
+ backupsDir,
53
+ latestBackup: path.join(backupsDir, "latest.json"),
54
+ runtimeStateDir: runtimeDir,
55
+ runtimeInstallDir: runtimesDir,
56
+ },
57
+ targetRuntime: {
58
+ root: path.resolve(args.codexDir),
59
+ config: path.resolve(args.configPath),
60
+ auth: path.resolve(args.authPath),
61
+ },
62
+ managementSSOT: {
63
+ scope: "toolHome",
64
+ path: path.resolve(args.providersPath),
65
+ },
66
+ runtimeMirrors: [
67
+ {
68
+ scope: "targetRuntime",
69
+ path: path.resolve(args.configPath),
70
+ },
71
+ ],
72
+ authStateFile: {
73
+ scope: "targetRuntime",
74
+ path: path.resolve(args.authPath),
75
+ },
76
+ rollbackState: {
77
+ scope: "toolHome",
78
+ path: path.join(backupsDir, "latest.json"),
79
+ },
80
+ runtimeState: {
81
+ scope: "toolHome",
82
+ path: runtimeDir,
83
+ managedBackup: false,
84
+ },
85
+ runtimeInstall: {
86
+ scope: "toolHome",
87
+ path: runtimesDir,
88
+ managedBackup: false,
89
+ },
14
90
  };
15
91
  }
16
92
  /**
@@ -10,8 +10,8 @@ const copilot_installer_1 = require("./copilot-installer");
10
10
  /**
11
11
  * Probes whether the optional Copilot SDK runtime is installed and loadable.
12
12
  */
13
- function probeCopilotSdkRuntime() {
14
- const status = (0, copilot_installer_1.probeCopilotSdkInstall)();
13
+ function probeCopilotSdkRuntime(runtimesDir) {
14
+ const status = (0, copilot_installer_1.probeCopilotSdkInstall)(runtimesDir);
15
15
  if (!status.installed) {
16
16
  return {
17
17
  ok: false,
@@ -37,18 +37,18 @@ function probeCopilotSdkRuntime() {
37
37
  /**
38
38
  * Loads the lazily installed Copilot SDK and returns the module.
39
39
  */
40
- async function requireCopilotSdk() {
41
- return (0, copilot_sdk_loader_1.loadCopilotSdk)();
40
+ async function requireCopilotSdk(runtimesDir) {
41
+ return (0, copilot_sdk_loader_1.loadCopilotSdk)(runtimesDir);
42
42
  }
43
43
  /**
44
44
  * Probes whether the lazily installed Copilot SDK can create a usable session.
45
45
  */
46
- async function readCopilotAuthState() {
47
- const runtime = probeCopilotSdkRuntime();
46
+ async function readCopilotAuthState(runtimesDir) {
47
+ const runtime = probeCopilotSdkRuntime(runtimesDir);
48
48
  if (!runtime.ok) {
49
49
  throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", runtime.details);
50
50
  }
51
- const { client, session } = await createCopilotSession();
51
+ const { client, session } = await createCopilotSession(runtimesDir);
52
52
  await stopCopilotClient(client);
53
53
  return {
54
54
  ready: Boolean(session),
@@ -60,7 +60,7 @@ async function readCopilotAuthState() {
60
60
  * Executes a single chat-completions style request through the optional Copilot SDK when available.
61
61
  */
62
62
  async function sendCopilotChatCompletion(args) {
63
- const { client, session, sdk } = await createCopilotSession();
63
+ const { client, session, sdk } = await createCopilotSession(args.runtimesDir);
64
64
  try {
65
65
  const sendAndWait = resolveCallable(session, "sendAndWait") ?? resolveCallable(sdk, "sendAndWait");
66
66
  if (!sendAndWait) {
@@ -106,8 +106,8 @@ async function sendCopilotChatCompletion(args) {
106
106
  await stopCopilotClient(client);
107
107
  }
108
108
  }
109
- async function createCopilotSession() {
110
- const sdk = (await requireCopilotSdk());
109
+ async function createCopilotSession(runtimesDir) {
110
+ const sdk = (await requireCopilotSdk(runtimesDir));
111
111
  const client = createCopilotClient(sdk);
112
112
  const createSession = resolveCallable(client ? client : null, "createSession") ?? resolveCallable(sdk, "createSession");
113
113
  if (!createSession) {
@@ -181,11 +181,11 @@ function resolveCallable(target, name) {
181
181
  }
182
182
  const direct = target[name];
183
183
  if (typeof direct === "function") {
184
- return direct;
184
+ return direct.bind(target);
185
185
  }
186
186
  const nestedDefault = target.default;
187
187
  if (nestedDefault && typeof nestedDefault[name] === "function") {
188
- return nestedDefault[name];
188
+ return nestedDefault[name].bind(nestedDefault);
189
189
  }
190
190
  return null;
191
191
  }