sneakoscope 4.7.1 → 4.7.3

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.
@@ -12,12 +12,16 @@ import { initProject, installSkills } from '../core/init.js';
12
12
  import { context7ConfigToml, DOLLAR_SKILL_NAMES, GETDESIGN_REFERENCE, hasContext7ConfigText, RECOMMENDED_SKILLS } from '../core/routes.js';
13
13
  import { checkZellijCapability } from '../core/zellij/zellij-capability.js';
14
14
  import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
15
+ import { restartCodexApp } from '../core/codex-app/codex-app-restart.js';
15
16
  import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
16
17
  import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
17
18
  import { GLM_CODEX_CONFIG_PROFILE_ID, GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } from '../core/providers/glm/glm-52-profile.js';
18
19
  import { GLM_52_OPENROUTER_MODEL } from '../core/providers/glm/glm-52-settings.js';
19
20
  import { buildCodexLbSetupPlan, codexLbPersistenceSummary, installCodexLbShellProfileSnippet, selectedCodexLbPersistenceModes } from '../core/codex-lb/codex-lb-setup.js';
20
21
  import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
22
+ const CODEX_LB_PROVIDER_NAME = 'openai';
23
+ const CODEX_LB_PROVIDER_ENV_KEY = 'CODEX_LB_API_KEY';
24
+ const CODEX_LB_CANONICAL_FAST_SERVICE_TIER = 'priority';
21
25
  const DEFAULT_CODEX_APP_PLUGINS = [
22
26
  ['browser', 'openai-bundled'],
23
27
  ['chrome', 'openai-bundled'],
@@ -207,7 +211,7 @@ async function reportPostinstallCodexLbAuth() {
207
211
  console.log(`codex-lb auth: repair skipped (${codexLbAuth.status}${codexLbAuth.error ? `: ${codexLbAuth.error}` : ''}).`);
208
212
  const reconcile = codexLbAuth.auth_reconcile;
209
213
  if (reconcile?.status === 'oauth_preserved') {
210
- console.log(`codex-lb auth: ChatGPT OAuth preserved as backup; codex-lb key stays in env_key (OpenAI OAuth not required, backup at ${reconcile.backup_path ?? 'unknown'}).`);
214
+ console.log(`codex-lb auth: ChatGPT OAuth preserved as backup; run \`sks codex-lb use-codex-lb\` to switch the App to codex-lb API-key auth (backup at ${reconcile.backup_path ?? 'unknown'}).`);
211
215
  }
212
216
  else if (reconcile?.status === 'oauth_restored') {
213
217
  console.log(`codex-lb auth: restored ChatGPT OAuth from ${reconcile.backup_path ?? 'unknown'} while keeping codex-lb selected.`);
@@ -428,7 +432,9 @@ export async function configureCodexLb(opts = {}) {
428
432
  const appliedActions = [];
429
433
  await ensureDir(path.dirname(configPath));
430
434
  const current = await readText(configPath, '');
431
- const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, baseUrl, useDefaultProvider));
435
+ const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, baseUrl, useDefaultProvider), {
436
+ forceFastMode: opts.forceFastMode !== false
437
+ });
432
438
  const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'codex-lb');
433
439
  if (!safeWrite.ok)
434
440
  return { ok: false, status: safeWrite.status, config_path: configPath, env_path: envPath, backup_path: safeWrite.backup_path };
@@ -462,9 +468,12 @@ export async function configureCodexLb(opts = {}) {
462
468
  const shellProfileResult = await installCodexLbShellProfileSnippet({ home, envPath, shellProfile }).catch((err) => ({ ok: false, status: 'failed', files: [], error: err.message }));
463
469
  if (shellProfile !== 'skip')
464
470
  appliedActions.push({ type: 'install_shell_profile_snippet', target: shellProfileResult.files?.join(', ') || shellProfile, ok: shellProfileResult.ok === true, status: shellProfileResult.status });
465
- const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home, force: true });
466
471
  const codexLb = await codexLbStatus({ ...opts, home, configPath, envPath });
467
- const authReconcile = await reconcileCodexLbAuthConflict({ ...opts, home, status: codexLb }).catch((err) => ({ status: 'failed', reason: 'exception', error: err.message }));
472
+ const forceCodexLbApiKeyAuth = opts.forceCodexLbApiKeyAuth === true || opts.authMode === 'codex-lb';
473
+ const authReconcile = await reconcileCodexLbAuthConflict({ ...opts, home, status: codexLb, forceCodexLbApiKeyAuth }).catch((err) => ({ status: 'failed', reason: 'exception', error: err.message }));
474
+ const codexLogin = forceCodexLbApiKeyAuth
475
+ ? { ok: ['apikey_forced', 'apikey_auth_active'].includes(authReconcile.status), status: authReconcile.status, ...(authReconcile.reason ? { reason: authReconcile.reason } : {}), error: authReconcile.error || null }
476
+ : await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home, force: true });
468
477
  const finalCodexLb = await codexLbStatus({ ...opts, home, configPath, envPath });
469
478
  const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
470
479
  const afterState = await captureCodexLbSetupWriteState({ home, configPath, envPath, shellProfile });
@@ -535,11 +544,21 @@ export async function codexLbStatus(opts = {}) {
535
544
  const providerConfigured = /\[model_providers\.codex-lb\]/.test(config);
536
545
  const selected = hasTopLevelCodexLbSelected(config);
537
546
  const baseUrl = codexLbProviderBaseUrl(config) || envLoad.base_url || null;
547
+ const providerName = codexLbProviderName(config);
548
+ const providerWireApi = codexLbProviderWireApi(config);
549
+ const providerSupportsWebsockets = codexLbProviderSupportsWebsockets(config);
538
550
  const providerRequiresOpenAiAuth = codexLbProviderRequiresOpenAiAuth(config);
539
551
  const providerOpenAiAuthDisabled = codexLbProviderOpenAiAuthDisabled(config);
540
552
  const providerEnvKey = codexLbProviderEnvKey(config);
541
- const providerUsesCodexLbEnvAuth = providerConfigured && providerEnvKey === 'CODEX_LB_API_KEY' && providerOpenAiAuthDisabled;
542
- const codexAppUsableWithCodexLb = providerUsesCodexLbEnvAuth && envKeyConfigured && Boolean(baseUrl);
553
+ const providerContractOk = providerConfigured
554
+ && providerName === CODEX_LB_PROVIDER_NAME
555
+ && providerWireApi === 'responses'
556
+ && providerEnvKey === CODEX_LB_PROVIDER_ENV_KEY
557
+ && providerSupportsWebsockets === true
558
+ && providerRequiresOpenAiAuth === true;
559
+ const providerUsesCodexLbEnvAuth = providerConfigured && providerEnvKey === CODEX_LB_PROVIDER_ENV_KEY && providerOpenAiAuthDisabled;
560
+ const codexAppUsableWithCodexLb = providerContractOk && envKeyConfigured && Boolean(baseUrl) && authMode.codex_app_usable;
561
+ const fastMode = codexLbFastModeConfigStatus(config);
543
562
  const launchEnvironment = await inspectCodexLbMacLaunchEnvironment(baseUrl, opts).catch((err) => ({
544
563
  checked: true,
545
564
  available: false,
@@ -547,10 +566,14 @@ export async function codexLbStatus(opts = {}) {
547
566
  error: err.message
548
567
  }));
549
568
  return {
550
- ok: providerConfigured && envKeyConfigured && Boolean(baseUrl) && providerUsesCodexLbEnvAuth,
569
+ ok: providerContractOk && envKeyConfigured && Boolean(baseUrl) && authMode.codex_app_usable,
551
570
  config_path: configPath,
552
571
  env_path: envPath,
553
572
  provider_configured: providerConfigured,
573
+ provider_name: providerName || null,
574
+ provider_wire_api: providerWireApi || null,
575
+ provider_supports_websockets: providerSupportsWebsockets,
576
+ provider_contract_ok: providerContractOk,
554
577
  provider_requires_openai_auth: providerRequiresOpenAiAuth,
555
578
  provider_openai_auth_disabled: providerOpenAiAuthDisabled,
556
579
  provider_env_key: providerEnvKey || null,
@@ -572,7 +595,8 @@ export async function codexLbStatus(opts = {}) {
572
595
  auth_path: authPath,
573
596
  auth_mode: authMode.mode,
574
597
  auth_usable_for_codex_app: authMode.codex_app_usable || codexAppUsableWithCodexLb,
575
- auth_summary: codexAppUsableWithCodexLb ? 'codex-lb provider uses CODEX_LB_API_KEY env_key; OpenAI OAuth not required' : authMode.summary,
598
+ auth_summary: codexAppUsableWithCodexLb ? `codex-lb provider uses ${authMode.mode} OpenAI-style auth through Codex App` : authMode.summary,
599
+ fast_mode: fastMode,
576
600
  launch_environment: launchEnvironment
577
601
  };
578
602
  }
@@ -584,20 +608,28 @@ export function formatCodexLbStatusText(status = {}, opts = {}) {
584
608
  '',
585
609
  `Configured: ${status.ok ? 'yes' : 'no'}`,
586
610
  `Selected: ${status.selected ? 'yes' : 'no'}`,
587
- `Provider: ${status.provider_configured ? 'yes' : 'no'}`,
588
- `Provider OpenAI OAuth: ${status.provider_openai_auth_disabled ? 'disabled for codex-lb env_key' : status.provider_requires_openai_auth ? 'required (repair recommended)' : 'missing explicit false'}`,
611
+ `Provider: ${status.provider_contract_ok ? 'codex-lb App contract ok' : status.provider_configured ? 'drifted' : 'missing'}`,
612
+ `Provider OpenAI Auth: ${status.provider_requires_openai_auth ? 'required' : 'not required/drifted'} (${status.provider_name || 'missing'})`,
589
613
  `Codex App auth: ${status.auth_usable_for_codex_app ? 'ok' : 'needs sign-in/repair'} (${status.auth_mode || 'unknown'})`
590
614
  ];
591
615
  if (status.auth_summary)
592
616
  lines.push(`Auth detail: ${status.auth_summary}`);
617
+ if (status.fast_mode) {
618
+ const fast = status.fast_mode;
619
+ lines.push(`Fast Mode: ${fast.configured ? `configured request=${fast.codex_request_service_tier} upstream=${fast.codex_lb_upstream_service_tier}` : 'not configured'}`);
620
+ if (!fast.actual_service_tier_verified)
621
+ lines.push(`Fast proof: unverified until ${fast.proof_required}. Run: ${fast.verification_command}`);
622
+ }
593
623
  lines.push(`Env file: ${status.env_file ? status.env_path : 'missing'}`);
594
624
  if (status.base_url)
595
625
  lines.push(`Base URL: ${status.base_url}`);
596
626
  lines.push(`ChatGPT backup: ${backupPresent ? `yes (${backupPath})` : 'no'}`);
597
- if (status.ok && !status.auth_usable_for_codex_app && backupPresent)
598
- lines.push('', 'Run: sks codex-lb repair to restore the ChatGPT OAuth backup while keeping codex-lb selected.');
627
+ if (status.provider_configured && !status.provider_contract_ok)
628
+ lines.push('', 'Run: sks codex-lb repair to rewrite the provider block to the current codex-lb App contract.');
629
+ else if (status.ok && !status.auth_usable_for_codex_app && backupPresent)
630
+ lines.push('', 'Run: sks codex-lb use-oauth to restore ChatGPT OAuth, or sks codex-lb use-codex-lb to force codex-lb API-key auth.');
599
631
  else if (status.ok && !status.auth_usable_for_codex_app)
600
- lines.push('', 'Sign in to Codex App/CLI again, then run: sks codex-lb repair');
632
+ lines.push('', 'Run: sks codex-lb use-codex-lb, or sign in to Codex App/CLI again for ChatGPT OAuth.');
601
633
  else if (status.ok && !status.selected)
602
634
  lines.push('', 'Run: sks codex-lb repair to activate codex-lb for Codex App.');
603
635
  else if (status.ok)
@@ -619,9 +651,9 @@ export function formatCodexLbRepairResultText(result = {}) {
619
651
  if (result.auth_reconcile?.status === 'oauth_restored')
620
652
  lines.push(`Codex App auth: ChatGPT OAuth restored from ${result.auth_reconcile.backup_path}.`);
621
653
  else if (result.auth_reconcile?.status === 'oauth_preserved')
622
- lines.push('Codex App auth: ChatGPT OAuth preserved as backup; codex-lb will use CODEX_LB_API_KEY from env_key without OpenAI OAuth.');
623
- else if (result.auth_reconcile?.status === 'apikey_auth_active')
624
- lines.push('Codex App auth: API-key auth.json is still active. Sign in again if the App asks for ChatGPT OAuth.');
654
+ lines.push('Codex App auth: ChatGPT OAuth preserved as backup; run `sks codex-lb use-codex-lb` to force codex-lb API-key auth.');
655
+ else if (['apikey_auth_active', 'apikey_forced'].includes(result.auth_reconcile?.status))
656
+ lines.push('Codex App auth: codex-lb API-key auth.json is active.');
625
657
  return `${lines.join('\n')}\n`;
626
658
  }
627
659
  function codexLbResponsesEndpoint(baseUrl = '') {
@@ -752,6 +784,45 @@ function codexLbResponseError(json, events = []) {
752
784
  }
753
785
  return null;
754
786
  }
787
+ function codexLbServiceTierEvidence(...responses) {
788
+ const values = [];
789
+ const visit = (value) => {
790
+ if (!value || typeof value !== 'object')
791
+ return;
792
+ values.push(value);
793
+ if (Array.isArray(value)) {
794
+ for (const item of value)
795
+ visit(item);
796
+ return;
797
+ }
798
+ for (const child of Object.values(value))
799
+ visit(child);
800
+ };
801
+ for (const response of responses) {
802
+ visit(response?.json);
803
+ visit(response?.events);
804
+ }
805
+ const firstString = (...keys) => {
806
+ for (const row of values) {
807
+ for (const key of keys) {
808
+ const value = row?.[key];
809
+ if (typeof value === 'string' && value.trim())
810
+ return value.trim().toLowerCase();
811
+ }
812
+ }
813
+ return null;
814
+ };
815
+ const requested = firstString('requestedServiceTier', 'requested_service_tier', 'requested_serviceTier');
816
+ const actual = firstString('actualServiceTier', 'actual_service_tier', 'actual_serviceTier');
817
+ const effective = firstString('serviceTier', 'service_tier');
818
+ return {
819
+ requested_service_tier: requested,
820
+ actual_service_tier: actual,
821
+ effective_service_tier: effective,
822
+ fast_requested: requested === CODEX_LB_CANONICAL_FAST_SERVICE_TIER || effective === CODEX_LB_CANONICAL_FAST_SERVICE_TIER,
823
+ fast_actual: actual === CODEX_LB_CANONICAL_FAST_SERVICE_TIER || effective === CODEX_LB_CANONICAL_FAST_SERVICE_TIER
824
+ };
825
+ }
755
826
  async function fetchCodexLbResponse(fetchImpl, endpoint, apiKey, body, timeoutMs) {
756
827
  const controller = new AbortController();
757
828
  const timer = setTimeout(() => controller.abort(), timeoutMs).unref?.();
@@ -802,10 +873,14 @@ export async function checkCodexLbResponseChain(status = {}, opts = {}) {
802
873
  return { ok: true, status: 'skipped', skipped: true, reason: 'fetch unavailable' };
803
874
  const model = opts.model || env.SKS_CODEX_MODEL || 'gpt-5.5';
804
875
  const timeoutMs = Number(opts.timeoutMs || env.SKS_CODEX_LB_CHAIN_CHECK_TIMEOUT_MS || 8000);
876
+ const serviceTier = opts.fastMode === true || opts.serviceTier === 'fast' || opts.serviceTier === CODEX_LB_CANONICAL_FAST_SERVICE_TIER
877
+ ? CODEX_LB_CANONICAL_FAST_SERVICE_TIER
878
+ : null;
805
879
  const baseBody = {
806
880
  model,
807
881
  instructions: 'You are running a short SKS codex-lb response-chain health check.',
808
882
  input: 'SKS codex-lb response-chain health check. Reply with OK.',
883
+ ...(serviceTier ? { service_tier: serviceTier } : {}),
809
884
  stream: true,
810
885
  store: true,
811
886
  parallel_tool_calls: false,
@@ -820,12 +895,14 @@ export async function checkCodexLbResponseChain(status = {}, opts = {}) {
820
895
  chain_unhealthy: true,
821
896
  endpoint,
822
897
  http_status: first.status,
898
+ requested_service_tier: serviceTier,
899
+ service_tier_evidence: codexLbServiceTierEvidence(first),
823
900
  error: redactSecretText(first.error_payload?.error?.message || first.error_payload?.response?.error?.message || first.text || 'codex-lb first Responses request failed', [apiKey])
824
901
  }, { endpoint, home, opts, env }), opts);
825
902
  }
826
903
  const second = await fetchCodexLbResponse(fetchImpl, endpoint, apiKey, { ...baseBody, previous_response_id: first.response_id }, timeoutMs);
827
904
  if (second.ok)
828
- return recordCodexLbChainHealth(await writeCodexLbChainCache({ ok: true, status: 'chain_ok', endpoint, response_id: first.response_id, chained_response_id: second.response_id || null, http_status: second.status }, { endpoint, home, opts, env }), opts);
905
+ return recordCodexLbChainHealth(await writeCodexLbChainCache({ ok: true, status: 'chain_ok', endpoint, response_id: first.response_id, chained_response_id: second.response_id || null, http_status: second.status, requested_service_tier: serviceTier, service_tier_evidence: codexLbServiceTierEvidence(first, second) }, { endpoint, home, opts, env }), opts);
829
906
  const previousMissing = isPreviousResponseNotFound(second.error_payload || second.json || second.text);
830
907
  return recordCodexLbChainHealth(await writeCodexLbChainCache({
831
908
  ok: false,
@@ -834,6 +911,8 @@ export async function checkCodexLbResponseChain(status = {}, opts = {}) {
834
911
  endpoint,
835
912
  response_id: first.response_id,
836
913
  http_status: second.status,
914
+ requested_service_tier: serviceTier,
915
+ service_tier_evidence: codexLbServiceTierEvidence(first, second),
837
916
  error: redactSecretText(second.error_payload?.error?.message || second.error_payload?.response?.error?.message || second.text || 'codex-lb chained Responses request failed', [apiKey])
838
917
  }, { endpoint, home, opts, env }), opts);
839
918
  }
@@ -851,6 +930,22 @@ function codexLbProviderBaseUrl(text = '') {
851
930
  const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
852
931
  return block.match(/(^|\n)\s*base_url\s*=\s*"([^"]+)"/)?.[2] || '';
853
932
  }
933
+ function codexLbProviderName(text = '') {
934
+ const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
935
+ return (block.match(/(^|\n)\s*name\s*=\s*"([^"]+)"/)?.[2] || '').trim();
936
+ }
937
+ function codexLbProviderWireApi(text = '') {
938
+ const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
939
+ return (block.match(/(^|\n)\s*wire_api\s*=\s*"([^"]+)"/)?.[2] || '').trim();
940
+ }
941
+ function codexLbProviderSupportsWebsockets(text = '') {
942
+ const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
943
+ if (/(^|\n)\s*supports_websockets\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(block))
944
+ return true;
945
+ if (/(^|\n)\s*supports_websockets\s*=\s*false\s*(?:#.*)?(?=\n|$)/.test(block))
946
+ return false;
947
+ return null;
948
+ }
854
949
  function codexLbProviderRequiresOpenAiAuth(text = '') {
855
950
  const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
856
951
  return /(^|\n)\s*requires_openai_auth\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(block);
@@ -863,6 +958,27 @@ function codexLbProviderEnvKey(text = '') {
863
958
  const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
864
959
  return block.match(/(^|\n)\s*env_key\s*=\s*"([^"]+)"/)?.[2] || '';
865
960
  }
961
+ function codexLbFastModeConfigStatus(text = '') {
962
+ const globalServiceTier = topLevelTomlString(text, 'service_tier');
963
+ const profileBlock = String(text || '').match(/(^|\n)\[profiles\.sks-fast-high\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
964
+ const profileServiceTier = profileBlock.match(/(^|\n)\s*service_tier\s*=\s*"([^"]+)"/)?.[2] || '';
965
+ const configured = globalServiceTier === 'fast' || globalServiceTier === CODEX_LB_CANONICAL_FAST_SERVICE_TIER || profileServiceTier === 'fast';
966
+ return {
967
+ schema: 'sks.codex-lb-fast-mode-config.v1',
968
+ configured,
969
+ top_level_service_tier: globalServiceTier || null,
970
+ profile_service_tier: profileServiceTier || null,
971
+ codex_request_service_tier: configured ? 'fast' : null,
972
+ codex_lb_upstream_service_tier: configured ? CODEX_LB_CANONICAL_FAST_SERVICE_TIER : null,
973
+ actual_service_tier_verified: false,
974
+ verification_command: 'sks codex-lb fast-check --json',
975
+ proof_required: 'codex-lb request log must show requestedServiceTier=priority and actualServiceTier/serviceTier=priority'
976
+ };
977
+ }
978
+ function topLevelTomlString(text = '', key) {
979
+ const topLevel = String(text || '').split(/\n\s*\[/)[0] || '';
980
+ return topLevel.match(new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]+)"\\s*(?:#.*)?(?=\\n|$)`))?.[2] || '';
981
+ }
866
982
  export async function repairCodexLbAuth(opts = {}) {
867
983
  let status = await codexLbStatus(opts);
868
984
  let configRepaired = false;
@@ -877,14 +993,17 @@ export async function repairCodexLbAuth(opts = {}) {
877
993
  status = await codexLbStatus(opts);
878
994
  }
879
995
  }
880
- if (status.env_key_configured && status.base_url && (!status.ok || !status.selected || !status.provider_uses_codex_lb_env_auth || legacyAuthMigrated || hasTopLevelCodexModeLock(currentConfig))) {
996
+ if (status.env_key_configured && status.base_url && (!status.provider_contract_ok || !status.selected || legacyAuthMigrated || hasTopLevelCodexModeLock(currentConfig) || (opts.forceCodexLbApiKeyAuth === true && !status.ok))) {
881
997
  await ensureDir(path.dirname(status.config_path));
882
- const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(currentConfig, status.base_url));
998
+ const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(currentConfig, status.base_url), {
999
+ forceFastMode: opts.forceFastMode === true || opts.forceCodexLbApiKeyAuth === true
1000
+ });
883
1001
  const safeWrite = await safeWriteCodexConfigToml(status.config_path, currentConfig, next, 'codex-lb-repair');
884
1002
  configRepaired = safeWrite.ok && safeWrite.changed === true;
885
1003
  status = await codexLbStatus(opts);
886
1004
  }
887
- if (!status.ok) {
1005
+ const canRepairAuthMode = opts.forceCodexLbApiKeyAuth === true && status.provider_contract_ok && status.env_key_configured && Boolean(status.base_url);
1006
+ if (!status.ok && !canRepairAuthMode) {
888
1007
  return {
889
1008
  ok: false,
890
1009
  status: !status.env_key_configured ? 'missing_env_key' : !status.base_url ? 'missing_base_url' : 'not_configured',
@@ -896,8 +1015,11 @@ export async function repairCodexLbAuth(opts = {}) {
896
1015
  await migrateCodexAuthKeyFormat({ home: opts.home });
897
1016
  const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
898
1017
  const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
899
- const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
900
- const authReconcile = await reconcileCodexLbAuthConflict({ ...opts, status }).catch((err) => ({ status: 'failed', reason: 'exception', error: err.message }));
1018
+ const forceCodexLbApiKeyAuth = opts.forceCodexLbApiKeyAuth === true || opts.authMode === 'codex-lb';
1019
+ const authReconcile = await reconcileCodexLbAuthConflict({ ...opts, status, forceCodexLbApiKeyAuth }).catch((err) => ({ status: 'failed', reason: 'exception', error: err.message }));
1020
+ const codexLogin = forceCodexLbApiKeyAuth
1021
+ ? { ok: ['apikey_forced', 'apikey_auth_active'].includes(authReconcile.status), status: authReconcile.status, ...(authReconcile.reason ? { reason: authReconcile.reason } : {}), error: authReconcile.error || null }
1022
+ : await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
901
1023
  const finalStatus = await codexLbStatus(opts);
902
1024
  const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
903
1025
  return {
@@ -933,7 +1055,7 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
933
1055
  };
934
1056
  }
935
1057
  await migrateCodexAuthKeyFormat({ home: opts.home });
936
- if (status.ok && (!status.selected || !status.provider_uses_codex_lb_env_auth))
1058
+ if (status.ok && (!status.selected || !status.provider_contract_ok))
937
1059
  return repairCodexLbAuth(opts);
938
1060
  if (!status.ok) {
939
1061
  if (status.base_url && (status.env_key_configured || status.provider_configured || status.selected || status.env_base_url_configured))
@@ -1017,11 +1139,11 @@ function codexAuthModeSummary(text = '') {
1017
1139
  return { mode: 'chatgpt_oauth', codex_app_usable: true, summary: 'ChatGPT OAuth token blob present' };
1018
1140
  const apiKey = parseCodexAuthApiKey(raw);
1019
1141
  if (apiKey)
1020
- return { mode: 'apikey', codex_app_usable: false, summary: 'API-key auth.json; Codex App may require ChatGPT sign-in for requires_openai_auth providers' };
1142
+ return { mode: 'apikey', codex_app_usable: true, summary: 'API-key auth.json available for requires_openai_auth providers' };
1021
1143
  try {
1022
1144
  const parsed = JSON.parse(raw);
1023
1145
  if (parsed?.auth_mode === 'browser')
1024
- return { mode: 'browser_marker', codex_app_usable: false, summary: 'browser auth marker without refresh tokens' };
1146
+ return { mode: 'browser_marker', codex_app_usable: true, summary: 'browser auth marker present; token storage is not inspectable' };
1025
1147
  }
1026
1148
  catch { }
1027
1149
  return { mode: 'unknown', codex_app_usable: false, summary: 'unrecognized auth.json shape' };
@@ -1052,9 +1174,11 @@ async function migrateCodexAuthKeyFormat(opts = {}) {
1052
1174
  return { status: 'skipped', reason: 'parse_error' };
1053
1175
  }
1054
1176
  }
1055
- // codex-lb authenticates through env_key (CODEX_LB_API_KEY) and explicitly
1056
- // disables OpenAI OAuth on the provider. SKS still preserves existing OAuth
1057
- // blobs, but OAuth is no longer a readiness requirement for codex-lb.
1177
+ // codex-lb's current Codex App contract uses a custom provider named "openai"
1178
+ // with requires_openai_auth=true. CODEX_LB_API_KEY remains SKS's persisted key
1179
+ // source, but selecting codex-lb auth must also switch Codex's OpenAI-style
1180
+ // auth.json to that API key; otherwise the App can appear configured while still
1181
+ // running under a different ChatGPT/OAuth auth path.
1058
1182
  export async function reconcileCodexLbAuthConflict(opts = {}) {
1059
1183
  const home = opts.home || process.env.HOME || os.homedir();
1060
1184
  const status = opts.status || await codexLbStatus({ ...opts, home });
@@ -1063,18 +1187,39 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
1063
1187
  if (!status.env_key_configured || !status.base_url) {
1064
1188
  return { status: 'skipped', reason: 'codex_lb_not_ready', auth_path: authPath };
1065
1189
  }
1066
- if (!(await exists(authPath))) {
1067
- return { status: 'skipped', reason: 'auth_missing', auth_path: authPath };
1068
- }
1069
- const authText = await readText(authPath, '');
1070
- if (!authText.trim()) {
1071
- return { status: 'skipped', reason: 'auth_empty', auth_path: authPath };
1072
- }
1190
+ const authExists = await exists(authPath);
1191
+ const authText = authExists ? await readText(authPath, '') : '';
1073
1192
  const envText = await readText(status.env_path, '');
1074
1193
  const apiKey = parseCodexLbEnvKey(envText);
1075
1194
  if (!apiKey) {
1076
1195
  return { status: 'skipped', reason: 'missing_env_key', auth_path: authPath };
1077
1196
  }
1197
+ const forceCodexLbApiKeyAuth = opts.forceCodexLbApiKeyAuth === true;
1198
+ const writeApiKeyAuth = async (reason, backupPathForResult = null) => {
1199
+ try {
1200
+ await writeTextAtomic(authPath, `${JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: apiKey }, null, 2)}\n`);
1201
+ await fsp.chmod(authPath, 0o600).catch(() => { });
1202
+ return {
1203
+ status: 'apikey_forced',
1204
+ reason,
1205
+ auth_path: authPath,
1206
+ backup_path: backupPathForResult || backupPath
1207
+ };
1208
+ }
1209
+ catch (err) {
1210
+ return { status: 'failed', reason: 'write_failed', auth_path: authPath, backup_path: backupPathForResult || backupPath, error: err.message };
1211
+ }
1212
+ };
1213
+ if (!authExists) {
1214
+ if (forceCodexLbApiKeyAuth)
1215
+ return writeApiKeyAuth('codex_lb_auth_selected_missing_auth');
1216
+ return { status: 'skipped', reason: 'auth_missing', auth_path: authPath };
1217
+ }
1218
+ if (!authText.trim()) {
1219
+ if (forceCodexLbApiKeyAuth)
1220
+ return writeApiKeyAuth('codex_lb_auth_selected_empty_auth');
1221
+ return { status: 'skipped', reason: 'auth_empty', auth_path: authPath };
1222
+ }
1078
1223
  if (hasChatgptOAuthTokens(authText)) {
1079
1224
  try {
1080
1225
  await ensureDir(path.dirname(backupPath));
@@ -1092,10 +1237,12 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
1092
1237
  backup_path: backupPath
1093
1238
  };
1094
1239
  }
1240
+ if (forceCodexLbApiKeyAuth)
1241
+ return writeApiKeyAuth('codex_lb_auth_selected', backupPath);
1095
1242
  if (process.env.SKS_CODEX_LB_FORCE_APIKEY_AUTH !== '1') {
1096
1243
  return {
1097
1244
  status: 'oauth_preserved',
1098
- reason: 'chatgpt_oauth_preserved_while_codex_lb_uses_env_key',
1245
+ reason: 'chatgpt_oauth_preserved_until_use_codex_lb_auth',
1099
1246
  auth_path: authPath,
1100
1247
  backup_path: backupPath
1101
1248
  };
@@ -1116,6 +1263,17 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
1116
1263
  };
1117
1264
  }
1118
1265
  const currentApiKey = parseCodexAuthApiKey(authText);
1266
+ if (forceCodexLbApiKeyAuth) {
1267
+ if (currentApiKey && currentApiKey === apiKey) {
1268
+ return {
1269
+ status: 'apikey_auth_active',
1270
+ reason: 'codex_lb_auth_selected',
1271
+ auth_path: authPath,
1272
+ backup_path: backupPath
1273
+ };
1274
+ }
1275
+ return writeApiKeyAuth('codex_lb_auth_selected_replace_existing');
1276
+ }
1119
1277
  if (currentApiKey && currentApiKey === apiKey) {
1120
1278
  const backupText = await readText(backupPath, '');
1121
1279
  if (hasChatgptOAuthTokens(backupText) && process.env.SKS_CODEX_LB_KEEP_APIKEY_AUTH !== '1') {
@@ -1309,7 +1467,7 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
1309
1467
  if (status.env_key_configured && status.base_url && !status.selected && status.auth_mode === 'chatgpt_oauth') {
1310
1468
  return { status: 'continued_to_codex', ok: false, chain_health: null, codex_lb: status, reason: 'chatgpt_oauth_active_codex_lb_unselected' };
1311
1469
  }
1312
- if (status.env_key_configured && status.base_url && (!status.provider_configured || !status.selected || !status.provider_uses_codex_lb_env_auth)) {
1470
+ if (status.env_key_configured && status.base_url && (!status.provider_configured || !status.selected || !status.provider_contract_ok)) {
1313
1471
  let promptedRestore = false;
1314
1472
  if (!status.provider_configured && canAskYesNo()) {
1315
1473
  promptedRestore = true;
@@ -1460,7 +1618,7 @@ async function maybeSyncCodexLbSharedLogin(apiKey, opts = {}) {
1460
1618
  if (!apiKey)
1461
1619
  return { ok: false, status: 'missing_env_key' };
1462
1620
  if (!shouldSyncCodexLbSharedLogin(opts)) {
1463
- return { ok: true, status: 'skipped', reason: 'codex-lb uses provider env_key auth; set SKS_CODEX_LB_SYNC_CODEX_LOGIN=1 to also rewrite Codex shared login cache.' };
1621
+ return { ok: true, status: 'skipped', reason: 'codex-lb repair preserved the current Codex App auth; run `sks codex-lb use-codex-lb` to switch the app to codex-lb API-key auth.' };
1464
1622
  }
1465
1623
  return syncCodexApiKeyLogin(apiKey, opts);
1466
1624
  }
@@ -1491,12 +1649,12 @@ export function upsertCodexLbConfig(text = '', baseUrl, selectDefault = true) {
1491
1649
  : removeTopLevelTomlKeyIfValue(text, 'model_provider', 'codex-lb');
1492
1650
  const block = [
1493
1651
  '[model_providers.codex-lb]',
1494
- 'name = "OpenAI"',
1652
+ 'name = "openai"',
1495
1653
  `base_url = "${baseUrl}"`,
1496
1654
  'wire_api = "responses"',
1497
1655
  'env_key = "CODEX_LB_API_KEY"',
1498
1656
  'supports_websockets = true',
1499
- 'requires_openai_auth = false'
1657
+ 'requires_openai_auth = true'
1500
1658
  ].join('\n');
1501
1659
  next = upsertTomlTable(next, 'model_providers.codex-lb', block);
1502
1660
  return `${next.trim()}\n`;
@@ -2601,10 +2759,10 @@ export async function selftestCodexLb(tmp) {
2601
2759
  const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
2602
2760
  const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
2603
2761
  const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
2604
- if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' || !hasTopLevelCodexLbSelected(codexLbConfig) || !codexLbConfig.includes('[model_providers.codex-lb]') || !codexLbEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbEnv.includes("CODEX_LB_API_KEY='sk-test'") || codexLbSetupJson.codex_environment?.ok !== true || codexLbSetupJson.codex_login?.status !== 'skipped' || codexLbAuth.trim())
2762
+ if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' || !hasTopLevelCodexLbSelected(codexLbConfig) || !codexLbConfig.includes('[model_providers.codex-lb]') || !codexLbEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbEnv.includes("CODEX_LB_API_KEY='sk-test'") || codexLbSetupJson.codex_environment?.ok !== true || codexLbSetupJson.codex_login?.status !== 'apikey_forced' || !codexLbAuth.includes('OPENAI_API_KEY') || !codexLbAuth.includes('sk-test'))
2605
2763
  throw new Error('selftest: codex-lb setup');
2606
- if (!codexLbConfig.includes('requires_openai_auth = false'))
2607
- throw new Error('selftest: codex-lb setup did not disable OpenAI OAuth for codex-lb env_key auth');
2764
+ if (!codexLbConfig.includes('requires_openai_auth = true') || !codexLbConfig.includes('name = "openai"'))
2765
+ throw new Error('selftest: codex-lb setup did not write current codex-lb App provider contract');
2608
2766
  const codexLbFailLaunchctl = path.join(codexLbFakeBin, 'launchctl-fail');
2609
2767
  await writeTextAtomic(codexLbFailLaunchctl, '#!/bin/sh\necho "launchctl denied" >&2\nexit 7\n');
2610
2768
  await fsp.chmod(codexLbFailLaunchctl, 0o755);
@@ -2617,8 +2775,8 @@ export async function selftestCodexLb(tmp) {
2617
2775
  const codexLbRepairSetupConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
2618
2776
  if (!hasTopLevelCodexLbSelected(codexLbRepairSetupConfig) || !codexLbRepairSetupConfig.includes('[model_providers.codex-lb]') || !codexLbRepairSetupConfig.includes('https://lb.example.test/backend-api/codex') || codexLbRepairSetupConfig.includes('sk-test'))
2619
2777
  throw new Error('selftest: init codex-lb');
2620
- if (!codexLbRepairSetupConfig.includes('requires_openai_auth = false'))
2621
- throw new Error('selftest: init codex-lb did not preserve OpenAI OAuth disablement');
2778
+ if (!codexLbRepairSetupConfig.includes('requires_openai_auth = true') || !codexLbRepairSetupConfig.includes('name = "openai"'))
2779
+ throw new Error('selftest: init codex-lb did not preserve current App provider contract');
2622
2780
  if (!hasCodexUnstableFeatureWarningSuppression(codexLbRepairSetupConfig))
2623
2781
  throw new Error('selftest: init codex-lb did not suppress Codex unstable feature warning');
2624
2782
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `${codexLbConfig}\n[mcp_servers.supabase]\nurl = "https://mcp.supabase.com/mcp?project_ref=ref&read_only=true&features=database,docs"\n`);
@@ -2636,8 +2794,8 @@ export async function selftestCodexLb(tmp) {
2636
2794
  const pcfg = await safeReadText(path.join(ptmp, '.codex', 'config.toml'));
2637
2795
  if (!hasTopLevelCodexLbSelected(pcfg) || !pcfg.includes('[model_providers.codex-lb]') || !pcfg.includes('[mcp_servers.supabase]') || !pcfg.includes('read_only=true'))
2638
2796
  throw new Error('selftest: project codex-lb');
2639
- if (!pcfg.includes('requires_openai_auth = false'))
2640
- throw new Error('selftest: project codex-lb did not copy OpenAI OAuth disablement');
2797
+ if (!pcfg.includes('requires_openai_auth = true') || !pcfg.includes('name = "openai"'))
2798
+ throw new Error('selftest: project codex-lb did not copy current App provider contract');
2641
2799
  if (!hasCodexUnstableFeatureWarningSuppression(pcfg))
2642
2800
  throw new Error('selftest: project codex-lb config did not suppress Codex unstable feature warning');
2643
2801
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
@@ -2711,8 +2869,8 @@ export async function selftestCodexLb(tmp) {
2711
2869
  throw new Error('selftest: postinstall drift auth');
2712
2870
  if (!hasTopLevelCodexLbSelected(codexLbPostBootstrapConfig) || !codexLbPostBootstrapConfig.includes('[model_providers.codex-lb]') || !codexLbPostBootstrapConfig.includes('https://lb.example.test/backend-api/codex') || codexLbPostBootstrapConfig.includes('sk-test'))
2713
2871
  throw new Error('selftest: postinstall drift config');
2714
- if (!codexLbPostBootstrapConfig.includes('requires_openai_auth = false'))
2715
- throw new Error('selftest: postinstall drift config did not restore OpenAI OAuth disablement');
2872
+ if (!codexLbPostBootstrapConfig.includes('requires_openai_auth = true') || !codexLbPostBootstrapConfig.includes('name = "openai"'))
2873
+ throw new Error('selftest: postinstall drift config did not restore current App provider contract');
2716
2874
  const doctorProject = tmpdir();
2717
2875
  await ensureDir(path.join(doctorProject, '.git'));
2718
2876
  await writeTextAtomic(path.join(doctorProject, 'package.json'), '{"name":"codex-lb-doctor-project","version":"0.0.0"}\n');
@@ -2732,8 +2890,8 @@ export async function selftestCodexLb(tmp) {
2732
2890
  const codexLbDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
2733
2891
  if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"browser"') || codexLbDoctorAuth.includes('sk-test') || !hasTopLevelCodexLbSelected(codexLbDoctorConfig) || !codexLbDoctorConfig.includes('https://lb.example.test/backend-api/codex') || !hasCodexUnstableFeatureWarningSuppression(codexLbDoctorConfig))
2734
2892
  throw new Error('selftest: doctor codex-lb');
2735
- if (!codexLbDoctorConfig.includes('requires_openai_auth = false'))
2736
- throw new Error('selftest: doctor codex-lb did not restore OpenAI OAuth disablement');
2893
+ if (!codexLbDoctorConfig.includes('requires_openai_auth = true') || !codexLbDoctorConfig.includes('name = "openai"'))
2894
+ throw new Error('selftest: doctor codex-lb did not restore current App provider contract');
2737
2895
  // codex-lb auth: ChatGPT OAuth ↔ codex-lb env_key conflict reconciliation.
2738
2896
  const oauthAuthJson = JSON.stringify({
2739
2897
  auth_mode: 'chatgpt',
@@ -2742,7 +2900,7 @@ export async function selftestCodexLb(tmp) {
2742
2900
  });
2743
2901
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), `${oauthAuthJson}\n`);
2744
2902
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\nexport CODEX_LB_API_KEY='sk-test'\n");
2745
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = false\n');
2903
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "openai"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = true\n');
2746
2904
  await fsp.rm(path.join(codexLbHome, '.codex', 'auth.chatgpt-backup.json'), { force: true });
2747
2905
  const codexLbReconcileRepair = await runProcess(process.execPath, [packagedSksEntrypoint(), 'auth', 'repair', '--json'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
2748
2906
  if (codexLbReconcileRepair.code !== 0)
@@ -2778,7 +2936,7 @@ export async function selftestCodexLb(tmp) {
2778
2936
  // codex-lb auth: release flow — restore ChatGPT OAuth from backup so the user can return to
2779
2937
  // the official ChatGPT account login. Default deselects model_provider; flags control whether
2780
2938
  // the provider stays selected and whether the backup file is removed after restore.
2781
- const codexLbReleaseConfig = 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = false\n';
2939
+ const codexLbReleaseConfig = 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "openai"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = true\n';
2782
2940
  const codexLbReleaseEnv = "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\nexport CODEX_LB_API_KEY='sk-test'\n";
2783
2941
  const codexLbReleaseApikeyAuth = '{"auth_mode":"apikey","OPENAI_API_KEY":"sk-test"}\n';
2784
2942
  const codexLbReleaseOauthBackup = `${oauthAuthJson}\n`;
@@ -2874,7 +3032,7 @@ export async function selftestCodexLb(tmp) {
2874
3032
  if (codexLbMalformedPostinstall.code !== 0 || !String(codexLbMalformedPostinstall.stdout || '').includes('codex-lb auth: stored key missing') || codexLbLoginCallsAfterMalformed !== codexLbLoginCallsBeforeMalformed)
2875
3033
  throw new Error('selftest: bad codex-lb env');
2876
3034
  await fsp.rm(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), { force: true });
2877
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), '[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nsupports_websockets = true\nrequires_openai_auth = true\n');
3035
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), '[model_providers.codex-lb]\nname = "openai"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nsupports_websockets = true\nrequires_openai_auth = true\n');
2878
3036
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-legacy"}\n');
2879
3037
  const codexLbLoginCallsBeforeLegacyPostinstall = await codexLbLoginCallCount(codexLbHome);
2880
3038
  const codexLbLegacyPostinstall = await runProcess(process.execPath, [packagedSksEntrypoint(), 'postinstall'], {
@@ -2891,7 +3049,7 @@ export async function selftestCodexLb(tmp) {
2891
3049
  if (!String(codexLbLegacyPostinstall.stdout || '').includes('codex-lb auth: restored from existing Codex login cache') || !codexLbLegacyPostinstallEnv.includes("CODEX_LB_API_KEY='sk-legacy'") || !codexLbLegacyPostinstallEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbLegacyPostinstallAuth.includes('"auth_mode":"apikey"') || !codexLbLegacyPostinstallAuth.includes('sk-legacy') || codexLbLoginCallsAfterLegacyPostinstall !== codexLbLoginCallsBeforeLegacyPostinstall)
2892
3050
  throw new Error('selftest: legacy codex-lb postinstall restore');
2893
3051
  await fsp.rm(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), { force: true });
2894
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nsupports_websockets = true\nrequires_openai_auth = true\n');
3052
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "openai"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nsupports_websockets = true\nrequires_openai_auth = true\n');
2895
3053
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-legacy-doctor"}\n');
2896
3054
  const codexLbLegacyDoctorProject = tmpdir();
2897
3055
  await ensureDir(path.join(codexLbLegacyDoctorProject, '.git'));
@@ -3074,8 +3232,8 @@ export async function selftestCodexLb(tmp) {
3074
3232
  }
3075
3233
  });
3076
3234
  const missingProviderRepairedConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
3077
- if (!missingProviderLaunch.ok || missingProviderLaunch.status !== 'present' || missingProviderLaunch.chain_health?.status !== 'chain_ok' || missingProviderLaunchCalls.length !== 2 || !hasTopLevelCodexLbSelected(missingProviderRepairedConfig) || !missingProviderRepairedConfig.includes('[model_providers.codex-lb]') || !missingProviderRepairedConfig.includes('env_key = "CODEX_LB_API_KEY"') || !missingProviderRepairedConfig.includes('supports_websockets = true') || !missingProviderRepairedConfig.includes('requires_openai_auth = false'))
3078
- throw new Error('selftest: bare sks launch did not restore codex-lb provider block from stored env with OpenAI OAuth disabled');
3235
+ if (!missingProviderLaunch.ok || missingProviderLaunch.status !== 'present' || missingProviderLaunch.chain_health?.status !== 'chain_ok' || missingProviderLaunchCalls.length !== 2 || !hasTopLevelCodexLbSelected(missingProviderRepairedConfig) || !missingProviderRepairedConfig.includes('[model_providers.codex-lb]') || !missingProviderRepairedConfig.includes('env_key = "CODEX_LB_API_KEY"') || !missingProviderRepairedConfig.includes('supports_websockets = true') || !missingProviderRepairedConfig.includes('requires_openai_auth = true') || !missingProviderRepairedConfig.includes('name = "openai"'))
3236
+ throw new Error('selftest: bare sks launch did not restore codex-lb provider block to current App contract');
3079
3237
  const chainCalls = [];
3080
3238
  const okChain = await checkCodexLbResponseChain({ base_url: 'https://lb.example.test/backend-api/codex', env_path: path.join(codexLbHome, '.codex', 'sks-codex-lb.env') }, {
3081
3239
  apiKey: 'sk-test',