sneakoscope 4.7.0 → 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.
- package/README.md +5 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/install-helpers.js +222 -66
- package/dist/commands/codex-lb.js +143 -7
- package/dist/commands/doctor.js +10 -1
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +15 -3
- package/dist/core/codex-app/codex-app-restart.js +65 -0
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +14 -3
- package/dist/core/codex-app.js +237 -3
- package/dist/core/codex-control/codex-sdk-config-policy.js +2 -2
- package/dist/core/codex-control/codex-task-runner.js +2 -2
- package/dist/core/codex-lb/codex-lb-setup.js +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/imagegen/imagegen-capability.js +2 -2
- package/dist/core/init.js +5 -8
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
- package/dist/core/provider/provider-context.js +1 -1
- package/dist/core/routes.js +1 -1
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-app-provider-model-ui-check.js +114 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +90 -0
- package/dist/scripts/codex-lb-setup-truthfulness-check.js +6 -1
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +10 -2
- package/dist/scripts/gpt-image-2-real-file-smoke.js +2 -2
- package/dist/scripts/provider-context-config-toml-check.js +2 -2
- package/package.json +3 -1
|
@@ -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
|
|
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
|
|
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
|
|
542
|
-
|
|
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:
|
|
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 ?
|
|
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 ? '
|
|
588
|
-
`Provider OpenAI
|
|
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.
|
|
598
|
-
lines.push('', 'Run: sks codex-lb repair to
|
|
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('', '
|
|
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
|
|
623
|
-
else if (result.auth_reconcile?.status
|
|
624
|
-
lines.push('Codex App auth: API-key auth.json is
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
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
|
-
|
|
1067
|
-
|
|
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: '
|
|
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.
|
|
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
|
|
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 = "
|
|
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 =
|
|
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`;
|
|
@@ -1666,14 +1824,12 @@ export function normalizeCodexFastModeUiConfig(text = '', opts = {}) {
|
|
|
1666
1824
|
return normalizeCodexFastModeUiConfigOnce(normalizeCodexFastModeUiConfigOnce(text, opts), opts);
|
|
1667
1825
|
}
|
|
1668
1826
|
function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
|
|
1669
|
-
//
|
|
1670
|
-
//
|
|
1671
|
-
// user's own reasoning effort. SKS continues to manage its own namespaced tables below
|
|
1672
|
-
// ([features], [profiles.sks-*], [user.fast_mode], [plugins]).
|
|
1827
|
+
// Keep model and reasoning selection out of top-level config so Codex Desktop can
|
|
1828
|
+
// expose its native model/speed selectors. SKS-owned defaults live in profiles below.
|
|
1673
1829
|
let next = String(text || '');
|
|
1830
|
+
next = removeLegacyTopLevelCodexModeLocks(next);
|
|
1674
1831
|
next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
|
|
1675
1832
|
next = removeTomlTableKey(next, 'features', 'codex_hooks');
|
|
1676
|
-
next = upsertTopLevelTomlStringIfAbsent(next, 'model', 'gpt-5.5');
|
|
1677
1833
|
if (opts.forceFastMode === true) {
|
|
1678
1834
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
1679
1835
|
}
|
|
@@ -1739,7 +1895,7 @@ function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
|
1739
1895
|
return lines.filter((line, index) => {
|
|
1740
1896
|
if (index >= end)
|
|
1741
1897
|
return true;
|
|
1742
|
-
return !/^\s*model_reasoning_effort\s*=/.test(line);
|
|
1898
|
+
return !/^\s*(?:model|model_reasoning_effort)\s*=/.test(line);
|
|
1743
1899
|
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
1744
1900
|
}
|
|
1745
1901
|
function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
|
|
@@ -2603,10 +2759,10 @@ export async function selftestCodexLb(tmp) {
|
|
|
2603
2759
|
const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
2604
2760
|
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
2605
2761
|
const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
2606
|
-
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 !== '
|
|
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'))
|
|
2607
2763
|
throw new Error('selftest: codex-lb setup');
|
|
2608
|
-
if (!codexLbConfig.includes('requires_openai_auth =
|
|
2609
|
-
throw new Error('selftest: codex-lb setup did not
|
|
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');
|
|
2610
2766
|
const codexLbFailLaunchctl = path.join(codexLbFakeBin, 'launchctl-fail');
|
|
2611
2767
|
await writeTextAtomic(codexLbFailLaunchctl, '#!/bin/sh\necho "launchctl denied" >&2\nexit 7\n');
|
|
2612
2768
|
await fsp.chmod(codexLbFailLaunchctl, 0o755);
|
|
@@ -2619,8 +2775,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
2619
2775
|
const codexLbRepairSetupConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
2620
2776
|
if (!hasTopLevelCodexLbSelected(codexLbRepairSetupConfig) || !codexLbRepairSetupConfig.includes('[model_providers.codex-lb]') || !codexLbRepairSetupConfig.includes('https://lb.example.test/backend-api/codex') || codexLbRepairSetupConfig.includes('sk-test'))
|
|
2621
2777
|
throw new Error('selftest: init codex-lb');
|
|
2622
|
-
if (!codexLbRepairSetupConfig.includes('requires_openai_auth =
|
|
2623
|
-
throw new Error('selftest: init codex-lb did not preserve
|
|
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');
|
|
2624
2780
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbRepairSetupConfig))
|
|
2625
2781
|
throw new Error('selftest: init codex-lb did not suppress Codex unstable feature warning');
|
|
2626
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`);
|
|
@@ -2638,8 +2794,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
2638
2794
|
const pcfg = await safeReadText(path.join(ptmp, '.codex', 'config.toml'));
|
|
2639
2795
|
if (!hasTopLevelCodexLbSelected(pcfg) || !pcfg.includes('[model_providers.codex-lb]') || !pcfg.includes('[mcp_servers.supabase]') || !pcfg.includes('read_only=true'))
|
|
2640
2796
|
throw new Error('selftest: project codex-lb');
|
|
2641
|
-
if (!pcfg.includes('requires_openai_auth =
|
|
2642
|
-
throw new Error('selftest: project codex-lb did not copy
|
|
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');
|
|
2643
2799
|
if (!hasCodexUnstableFeatureWarningSuppression(pcfg))
|
|
2644
2800
|
throw new Error('selftest: project codex-lb config did not suppress Codex unstable feature warning');
|
|
2645
2801
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
@@ -2713,8 +2869,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
2713
2869
|
throw new Error('selftest: postinstall drift auth');
|
|
2714
2870
|
if (!hasTopLevelCodexLbSelected(codexLbPostBootstrapConfig) || !codexLbPostBootstrapConfig.includes('[model_providers.codex-lb]') || !codexLbPostBootstrapConfig.includes('https://lb.example.test/backend-api/codex') || codexLbPostBootstrapConfig.includes('sk-test'))
|
|
2715
2871
|
throw new Error('selftest: postinstall drift config');
|
|
2716
|
-
if (!codexLbPostBootstrapConfig.includes('requires_openai_auth =
|
|
2717
|
-
throw new Error('selftest: postinstall drift config did not restore
|
|
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');
|
|
2718
2874
|
const doctorProject = tmpdir();
|
|
2719
2875
|
await ensureDir(path.join(doctorProject, '.git'));
|
|
2720
2876
|
await writeTextAtomic(path.join(doctorProject, 'package.json'), '{"name":"codex-lb-doctor-project","version":"0.0.0"}\n');
|
|
@@ -2734,8 +2890,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
2734
2890
|
const codexLbDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
2735
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))
|
|
2736
2892
|
throw new Error('selftest: doctor codex-lb');
|
|
2737
|
-
if (!codexLbDoctorConfig.includes('requires_openai_auth =
|
|
2738
|
-
throw new Error('selftest: doctor codex-lb did not restore
|
|
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');
|
|
2739
2895
|
// codex-lb auth: ChatGPT OAuth ↔ codex-lb env_key conflict reconciliation.
|
|
2740
2896
|
const oauthAuthJson = JSON.stringify({
|
|
2741
2897
|
auth_mode: 'chatgpt',
|
|
@@ -2744,7 +2900,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
2744
2900
|
});
|
|
2745
2901
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), `${oauthAuthJson}\n`);
|
|
2746
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");
|
|
2747
|
-
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "
|
|
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');
|
|
2748
2904
|
await fsp.rm(path.join(codexLbHome, '.codex', 'auth.chatgpt-backup.json'), { force: true });
|
|
2749
2905
|
const codexLbReconcileRepair = await runProcess(process.execPath, [packagedSksEntrypoint(), 'auth', 'repair', '--json'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2750
2906
|
if (codexLbReconcileRepair.code !== 0)
|
|
@@ -2780,7 +2936,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
2780
2936
|
// codex-lb auth: release flow — restore ChatGPT OAuth from backup so the user can return to
|
|
2781
2937
|
// the official ChatGPT account login. Default deselects model_provider; flags control whether
|
|
2782
2938
|
// the provider stays selected and whether the backup file is removed after restore.
|
|
2783
|
-
const codexLbReleaseConfig = 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "
|
|
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';
|
|
2784
2940
|
const codexLbReleaseEnv = "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\nexport CODEX_LB_API_KEY='sk-test'\n";
|
|
2785
2941
|
const codexLbReleaseApikeyAuth = '{"auth_mode":"apikey","OPENAI_API_KEY":"sk-test"}\n';
|
|
2786
2942
|
const codexLbReleaseOauthBackup = `${oauthAuthJson}\n`;
|
|
@@ -2876,7 +3032,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
2876
3032
|
if (codexLbMalformedPostinstall.code !== 0 || !String(codexLbMalformedPostinstall.stdout || '').includes('codex-lb auth: stored key missing') || codexLbLoginCallsAfterMalformed !== codexLbLoginCallsBeforeMalformed)
|
|
2877
3033
|
throw new Error('selftest: bad codex-lb env');
|
|
2878
3034
|
await fsp.rm(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), { force: true });
|
|
2879
|
-
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), '[model_providers.codex-lb]\nname = "
|
|
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');
|
|
2880
3036
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-legacy"}\n');
|
|
2881
3037
|
const codexLbLoginCallsBeforeLegacyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
2882
3038
|
const codexLbLegacyPostinstall = await runProcess(process.execPath, [packagedSksEntrypoint(), 'postinstall'], {
|
|
@@ -2893,7 +3049,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
2893
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)
|
|
2894
3050
|
throw new Error('selftest: legacy codex-lb postinstall restore');
|
|
2895
3051
|
await fsp.rm(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), { force: true });
|
|
2896
|
-
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "
|
|
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');
|
|
2897
3053
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-legacy-doctor"}\n');
|
|
2898
3054
|
const codexLbLegacyDoctorProject = tmpdir();
|
|
2899
3055
|
await ensureDir(path.join(codexLbLegacyDoctorProject, '.git'));
|
|
@@ -3076,8 +3232,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
3076
3232
|
}
|
|
3077
3233
|
});
|
|
3078
3234
|
const missingProviderRepairedConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
3079
|
-
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 =
|
|
3080
|
-
throw new Error('selftest: bare sks launch did not restore codex-lb provider block
|
|
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');
|
|
3081
3237
|
const chainCalls = [];
|
|
3082
3238
|
const okChain = await checkCodexLbResponseChain({ base_url: 'https://lb.example.test/backend-api/codex', env_path: path.join(codexLbHome, '.codex', 'sks-codex-lb.env') }, {
|
|
3083
3239
|
apiKey: 'sk-test',
|
|
@@ -3118,8 +3274,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
3118
3274
|
});
|
|
3119
3275
|
if (brokenChain.ok || brokenChain.status !== 'previous_response_not_found' || brokenChain.chain_unhealthy !== true)
|
|
3120
3276
|
throw new Error('selftest: codex-lb response chain health check did not detect previous_response_not_found');
|
|
3121
|
-
if (
|
|
3122
|
-
throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, explicit Fast profile, Codex Git commit generation,
|
|
3277
|
+
if (!codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('remote_control = true') || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('browser_use = true') || !codexLbConfig.includes('browser_use_external = true') || !codexLbConfig.includes('guardian_approval = true') || !codexLbConfig.includes('tool_suggest = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[plugins."latex@openai-bundled"]') || !codexLbConfig.includes('[plugins."documents@openai-primary-runtime"]') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig))
|
|
3278
|
+
throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, explicit Fast profile, Codex Git commit generation, or migrate the hooks feature flag');
|
|
3123
3279
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig))
|
|
3124
3280
|
throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
3125
3281
|
const codexLbLaunch = `source ${path.join(tmp, '.codex', 'sks-codex-lb.env')} && codex '--model' 'gpt-5.5'`;
|
|
@@ -3135,7 +3291,7 @@ function hasTopLevelCodexModeLock(text = '') {
|
|
|
3135
3291
|
const lines = String(text || '').split('\n');
|
|
3136
3292
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
3137
3293
|
const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
3138
|
-
return /(^|\n)\s*model_reasoning_effort\s*=/.test(top);
|
|
3294
|
+
return /(^|\n)\s*(?:model|model_reasoning_effort)\s*=/.test(top);
|
|
3139
3295
|
}
|
|
3140
3296
|
function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
|
3141
3297
|
const lines = String(text || '').split('\n');
|