sneakoscope 8.1.2 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/README.md +96 -58
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/bin/fast-inline.js +5 -51
  5. package/dist/cli/command-manifest-lite.js +2 -1
  6. package/dist/cli/command-registry.js +2 -1
  7. package/dist/cli/global-mode-router.js +1 -1
  8. package/dist/cli/install-helpers-codex-lb-config.js +112 -914
  9. package/dist/cli/install-helpers-codex-lb-shared.js +3 -44
  10. package/dist/cli/install-helpers-install-support.js +4 -20
  11. package/dist/cli/install-helpers.js +36 -2716
  12. package/dist/cli/router.js +9 -9
  13. package/dist/commands/bridge-command-output.js +111 -0
  14. package/dist/commands/bridge.js +320 -0
  15. package/dist/commands/codex-app.js +39 -123
  16. package/dist/commands/doctor-helpers.js +380 -0
  17. package/dist/commands/doctor-profile.js +3 -0
  18. package/dist/commands/doctor.js +114 -873
  19. package/dist/commands/mad-sks.js +0 -4
  20. package/dist/config/skills-manifest.json +1 -1
  21. package/dist/core/agents/agent-effort-policy.js +11 -21
  22. package/dist/core/agents/agent-recursion-guard.js +26 -0
  23. package/dist/core/agents/agent-runner-codex-exec.js +7 -9
  24. package/dist/core/architecture-hardening/contracts/contracts.js +225 -161
  25. package/dist/core/architecture-hardening/state/state-service.js +49 -13
  26. package/dist/core/auto-review.js +15 -9
  27. package/dist/core/codex/codex-config-guard.js +8 -0
  28. package/dist/core/codex-adapter.js +8 -10
  29. package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -29
  30. package/dist/core/codex-app/codex-app-launcher.js +10 -12
  31. package/dist/core/codex-app/codex-app-restart.js +10 -12
  32. package/dist/core/codex-app/menubar/constants.js +2 -5
  33. package/dist/core/codex-app/menubar/global-install.js +3 -2
  34. package/dist/core/codex-app/menubar/installer/result.js +23 -0
  35. package/dist/core/codex-app/menubar/installer/runtime.js +28 -0
  36. package/dist/core/codex-app/menubar/installer.js +5 -96
  37. package/dist/core/codex-app/menubar/migration.js +1 -42
  38. package/dist/core/codex-app/openrouter-provider.js +0 -78
  39. package/dist/core/codex-app.js +97 -360
  40. package/dist/core/codex-control/codex-app-server-runtime-env.js +8 -28
  41. package/dist/core/codex-control/codex-current-image-path-real-probe.js +2 -3
  42. package/dist/core/codex-control/codex-current-web-search-probe.js +2 -3
  43. package/dist/core/codex-control/codex-sdk-adapter.js +5 -48
  44. package/dist/core/codex-control/codex-task-runner.js +16 -60
  45. package/dist/core/codex-control/desktop-bridge-launch-guard.js +227 -0
  46. package/dist/core/codex-exec-output-schema.js +11 -13
  47. package/dist/core/codex-lb/bridge-contracts.js +2 -0
  48. package/dist/core/codex-lb/bridge-runtime-validation/capability.js +104 -0
  49. package/dist/core/codex-lb/bridge-runtime-validation/catalog.js +52 -0
  50. package/dist/core/codex-lb/bridge-runtime-validation/shared.js +109 -0
  51. package/dist/core/codex-lb/bridge-runtime-validation/status.js +250 -0
  52. package/dist/core/codex-lb/bridge-runtime-validation.js +106 -0
  53. package/dist/core/codex-lb/capability-runner.js +505 -201
  54. package/dist/core/codex-lb/codex-lb-env.js +179 -12
  55. package/dist/core/codex-lb/combined-catalog/builder.js +86 -0
  56. package/dist/core/codex-lb/combined-catalog/contracts.js +5 -0
  57. package/dist/core/codex-lb/combined-catalog/normalize.js +170 -0
  58. package/dist/core/codex-lb/combined-catalog/persistence.js +207 -0
  59. package/dist/core/codex-lb/combined-catalog/shared.js +12 -0
  60. package/dist/core/codex-lb/combined-catalog/storage.js +171 -0
  61. package/dist/core/codex-lb/combined-catalog.js +12 -0
  62. package/dist/core/codex-lb/desktop-bridge/client.js +4 -1
  63. package/dist/core/codex-lb/desktop-bridge/header-policy.js +48 -63
  64. package/dist/core/codex-lb/desktop-bridge/http-forward.js +115 -83
  65. package/dist/core/codex-lb/desktop-bridge/launchd.js +20 -13
  66. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +4 -1
  67. package/dist/core/codex-lb/desktop-bridge/security.js +381 -267
  68. package/dist/core/codex-lb/desktop-bridge/server.js +148 -19
  69. package/dist/core/codex-lb/desktop-bridge/state.js +56 -70
  70. package/dist/core/codex-lb/desktop-bridge/types.js +6 -1
  71. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +281 -106
  72. package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +245 -0
  73. package/dist/core/codex-lb/desktop-bridge-migration/historical-toml.js +56 -0
  74. package/dist/core/codex-lb/desktop-bridge-migration/metadata.js +73 -0
  75. package/dist/core/codex-lb/desktop-bridge-migration/receipt.js +52 -0
  76. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +106 -0
  77. package/dist/core/codex-lb/desktop-bridge-migration/types.js +1 -0
  78. package/dist/core/codex-lb/desktop-bridge-migration.js +238 -0
  79. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +257 -0
  80. package/dist/core/codex-lb/desktop-controller-v3/diagnostics.js +62 -0
  81. package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +203 -0
  82. package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +293 -0
  83. package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +144 -0
  84. package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +182 -0
  85. package/dist/core/codex-lb/desktop-controller-v3/shared.js +279 -0
  86. package/dist/core/codex-lb/desktop-controller-v3/status.js +269 -0
  87. package/dist/core/codex-lb/desktop-controller-v3/types.js +1 -0
  88. package/dist/core/codex-lb/desktop-controller-v3/verification.js +89 -0
  89. package/dist/core/codex-lb/desktop-controller-v3.js +67 -0
  90. package/dist/core/codex-lb/desktop-controller.js +2 -1730
  91. package/dist/core/codex-lb/desktop-service.js +462 -627
  92. package/dist/core/codex-lb/migration-receipt.js +560 -118
  93. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +37 -53
  94. package/dist/core/codex-lb/probes/bridge-probe.js +125 -64
  95. package/dist/core/codex-lb/probes/catalog-probe.js +36 -136
  96. package/dist/core/codex-lb/probes/computer-use-probe.js +63 -39
  97. package/dist/core/codex-lb/probes/image-generation-probe.js +98 -41
  98. package/dist/core/codex-lb/probes/probe-evidence.js +32 -33
  99. package/dist/core/codex-lb/probes/voice-realtime-probe.js +69 -43
  100. package/dist/core/codex-lb/provider-credentials/configuration.js +127 -0
  101. package/dist/core/codex-lb/provider-credentials/locks.js +11 -0
  102. package/dist/core/codex-lb/provider-credentials/private-snapshot.js +28 -0
  103. package/dist/core/codex-lb/provider-credentials/removal.js +33 -0
  104. package/dist/core/codex-lb/provider-credentials/resolution.js +59 -0
  105. package/dist/core/codex-lb/provider-credentials/runtime.js +55 -0
  106. package/dist/core/codex-lb/provider-credentials/types.js +1 -0
  107. package/dist/core/codex-lb/provider-credentials/validation-store.js +137 -0
  108. package/dist/core/codex-lb/provider-credentials.js +5 -0
  109. package/dist/core/codex-lb/provider-registry.js +323 -0
  110. package/dist/core/codex-lb/provider-route-policy.js +118 -0
  111. package/dist/core/codex-lb/request-route-resolver.js +172 -0
  112. package/dist/core/codex-lb/route-index.js +148 -0
  113. package/dist/core/codex-lb/trusted-deep-evidence.js +158 -286
  114. package/dist/core/commands/basic-cli.js +1 -1
  115. package/dist/core/commands/check-command.js +3 -10
  116. package/dist/core/commands/gate-result-contract.js +10 -14
  117. package/dist/core/commands/intent-normalization/normalizer.js +1 -1
  118. package/dist/core/commands/mad-sks-command.js +2 -6
  119. package/dist/core/commands/release-command.js +11 -1
  120. package/dist/core/commands/telegram-command.js +46 -6
  121. package/dist/core/doctor/doctor-readiness-matrix.js +9 -7
  122. package/dist/core/doctor/skill-legacy-surface.js +5 -5
  123. package/dist/core/evidence/v2/evidence-key.js +30 -3
  124. package/dist/core/feature-fixtures.js +8 -1
  125. package/dist/core/feature-registry.js +3 -2
  126. package/dist/core/hooks-runtime/stop-finalization.js +11 -1
  127. package/dist/core/hooks-runtime/subagent-context.js +15 -1
  128. package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +1 -1
  129. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  130. package/dist/core/hooks-runtime.js +73 -87
  131. package/dist/core/image-ux-review/imagegen-adapter.js +108 -94
  132. package/dist/core/image-ux-review/real-callout-extractor.js +144 -14
  133. package/dist/core/imagegen/desktop-bridge-imagegen-target.js +77 -0
  134. package/dist/core/imagegen/imagegen-capability.js +43 -151
  135. package/dist/core/init/skills/filesystem.js +89 -0
  136. package/dist/core/init/skills/inventory.js +97 -0
  137. package/dist/core/init/skills/manifest.js +151 -0
  138. package/dist/core/init/skills.js +5 -325
  139. package/dist/core/init.js +0 -63
  140. package/dist/core/install/installed-package-smoke.js +1 -1
  141. package/dist/core/managed-assets/managed-assets-manifest.js +0 -1
  142. package/dist/core/policy/latest-version-guidance.js +15 -2
  143. package/dist/core/preflight/parallel-preflight-engine.js +27 -51
  144. package/dist/core/provider/provider-badge.js +9 -4
  145. package/dist/core/provider/provider-context.js +57 -107
  146. package/dist/core/providers/openrouter/openrouter-account.js +3 -1
  147. package/dist/core/providers/openrouter/openrouter-secret-store.js +91 -22
  148. package/dist/core/release/desktop-bridge-release-evidence.js +457 -0
  149. package/dist/core/release/gate-manifest.js +0 -1
  150. package/dist/core/release/main-push-guard.js +13 -0
  151. package/dist/core/release/main-push-receipt.js +11 -0
  152. package/dist/core/release/physical-release-gates.js +664 -61
  153. package/dist/core/release/publish-preflight.js +3 -3
  154. package/dist/core/release/release-gate-batch-runner.js +19 -6
  155. package/dist/core/release/release-gate-dag.js +33 -6
  156. package/dist/core/release/release-gate-node.js +1 -3
  157. package/dist/core/release/release-gate-output-contract.js +55 -0
  158. package/dist/core/release/release-pack-contract.js +1 -1
  159. package/dist/core/release/release-pack-receipt.js +209 -8
  160. package/dist/core/release/release-real-contract.js +1 -1
  161. package/dist/core/release/stage-publish.js +106 -28
  162. package/dist/core/retention/temp-lease.js +204 -0
  163. package/dist/core/retention.js +18 -195
  164. package/dist/core/routes.js +1 -2
  165. package/dist/core/safety/intent-contract/intent-contract.js +3 -3
  166. package/dist/core/subagents/naruto-host-credentials.js +6 -1
  167. package/dist/core/subagents/official-subagent-config.js +27 -9
  168. package/dist/core/subagents/official-subagent-runner.js +14 -31
  169. package/dist/core/subagents/wave-lifecycle.js +36 -8
  170. package/dist/core/subagents/wave-parent-guidance.js +2 -1
  171. package/dist/core/telegram/doctor.js +2 -2
  172. package/dist/core/telegram/liveness.js +10 -4
  173. package/dist/core/triwiki/context-graph/compiler/index.js +18 -18
  174. package/dist/core/update/update-migration-state/fast-mode-config.js +172 -0
  175. package/dist/core/update/update-migration-state/session-state-split.js +247 -0
  176. package/dist/core/update/update-migration-state/simple-stages.js +46 -0
  177. package/dist/core/update/update-migration-state.js +4 -481
  178. package/dist/core/version.js +1 -1
  179. package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +28 -4
  180. package/dist/native/sks-menubar/Sources/OperationModels.swift +112 -2
  181. package/dist/native/sks-menubar/Sources/ProviderRouteExplanation.swift +21 -0
  182. package/dist/native/sks-menubar/Sources/ProvidersBridgeCatalog.swift +140 -0
  183. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +107 -470
  184. package/dist/native/sks-menubar/Sources/ProvidersReliability.swift +27 -197
  185. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +335 -269
  186. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +233 -437
  187. package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +16 -7
  188. package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +39 -9
  189. package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
  190. package/dist/scripts/canonical-test-runner.js +7 -1
  191. package/dist/scripts/check-runtime-schemas.js +7 -2
  192. package/dist/scripts/codex-sdk-real-smoke-check.js +3 -1
  193. package/dist/scripts/desktop-bridge-capabilities-check.js +123 -0
  194. package/dist/scripts/{codex-lb-desktop-bridge-latency-check.js → desktop-bridge-latency-check.js} +61 -16
  195. package/dist/scripts/desktop-bridge-real-evidence-check.js +64 -0
  196. package/dist/scripts/{codex-lb-desktop-bridge-check.js → desktop-bridge-transport-check.js} +92 -155
  197. package/dist/scripts/desktop-bridge-transport-client.js +116 -0
  198. package/dist/scripts/desktop-bridge-unification-check.js +276 -0
  199. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -8
  200. package/dist/scripts/gate-policy-audit-check.js +52 -2
  201. package/dist/scripts/provider-badge-context-check.js +55 -12
  202. package/dist/scripts/publish-preflight.js +1 -2
  203. package/dist/scripts/release-affected-selector-check.js +1 -2
  204. package/dist/scripts/release-aggressive-resource-governor-check.js +1 -1
  205. package/dist/scripts/release-cache-glob-hashing-check.js +1 -0
  206. package/dist/scripts/release-cache-version-neutral-fixture-check.js +1 -0
  207. package/dist/scripts/release-full-parallelism-blackbox.js +1 -1
  208. package/dist/scripts/release-gate-batch-runner-check.js +9 -0
  209. package/dist/scripts/release-gate-dag-runner-check.js +6 -0
  210. package/dist/scripts/release-gate-dag-runner.js +4 -2
  211. package/dist/scripts/release-metadata-check.js +16 -3
  212. package/dist/scripts/release-pack-receipt.js +29 -6
  213. package/dist/scripts/release-physical-gates-check.js +39 -0
  214. package/dist/scripts/release-real-check.js +7 -5
  215. package/package.json +6 -8
  216. package/release-gates.v2.json +156 -157
  217. package/runtime-required-scripts.json +11 -11
  218. package/schemas/codex/desktop-bridge-status-v3.schema.json +286 -0
  219. package/schemas/codex/desktop-capabilities-v3.schema.json +198 -0
  220. package/schemas/release/release-gate-node.schema.json +2 -3
  221. package/dist/cli/codex-lb-setup-warning-output.js +0 -17
  222. package/dist/cli/install-helpers-codex-lb-chain.js +0 -529
  223. package/dist/commands/codex-lb.js +0 -1232
  224. package/dist/core/architecture-hardening/migration/migration.js +0 -111
  225. package/dist/core/codex-app/auxiliary-oauth-policy.js +0 -36
  226. package/dist/core/codex-app/catalog-compat/catalog-service.js +0 -104
  227. package/dist/core/codex-app/child-policy/child-policy.js +0 -35
  228. package/dist/core/codex-app/desktop-routing-snapshot.js +0 -233
  229. package/dist/core/codex-app/glm-model-profile.js +0 -1
  230. package/dist/core/codex-app/glm-profile-installer.js +0 -191
  231. package/dist/core/codex-app/glm-profile-schema.js +0 -42
  232. package/dist/core/codex-app/multi-provider-router.js +0 -391
  233. package/dist/core/codex-app/openrouter-activate.js +0 -405
  234. package/dist/core/codex-app/openrouter-model-catalog.js +0 -217
  235. package/dist/core/codex-app/provider-child-policy.js +0 -64
  236. package/dist/core/codex-app/provider-mode.js +0 -91
  237. package/dist/core/codex-app/provider-session-policy.js +0 -85
  238. package/dist/core/codex-app/session-policy/session-pinning.js +0 -68
  239. package/dist/core/codex-control/codex-lb-launch-recovery.js +0 -319
  240. package/dist/core/codex-lb/codex-lb-setup.js +0 -293
  241. package/dist/core/codex-lb/codex-lb-tool-catalog.js +0 -456
  242. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +0 -290
  243. package/dist/core/codex-lb/desktop-center-credentials.js +0 -1083
  244. package/dist/core/codex-lb/desktop-mode.js +0 -32
  245. package/dist/core/codex-lb/desktop-real-evidence.js +0 -168
  246. package/dist/core/codex-lb/legacy-migration.js +0 -576
  247. package/dist/core/codex-lb/native-openai-transport/transport.js +0 -68
  248. package/dist/core/codex-lb/provider-routing/provider-router.js +0 -58
  249. package/dist/core/codex-lb/routing-truth.js +0 -327
  250. package/dist/core/imagegen/codex-lb-imagegen-target.js +0 -92
  251. package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +0 -118
  252. package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +0 -17
  253. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +0 -331
  254. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +0 -260
  255. package/dist/scripts/codex-lb-catalog-passthrough-check.js +0 -120
  256. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +0 -169
  257. package/dist/scripts/codex-lb-desktop-capabilities-check.js +0 -287
  258. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +0 -123
  259. package/dist/scripts/codex-lb-fast-mode-truth-check.js +0 -114
  260. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +0 -152
  261. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +0 -231
@@ -1,191 +0,0 @@
1
- import path from 'node:path';
2
- import { codexLbConfigPath, ensureGlobalCodexAppGlmProfile } from '../../cli/install-helpers.js';
3
- import { readText, writeJsonAtomic, nowIso, rmrf } from '../fsx.js';
4
- import { OPENROUTER_DEFAULT_PROFILE_ID, OPENROUTER_AUTH_COMMAND, OPENROUTER_AUTH_REFRESH_INTERVAL_MS, OPENROUTER_AUTH_TIMEOUT_MS, openRouterAuthCommandArgs, OPENROUTER_PROVIDER_ID, RETIRED_GLM_DESKTOP_CONFIG_PROFILE_IDS, buildGlmCodexAppModelProfile } from './openrouter-provider.js';
5
- import { openRouterSecretPaths, resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
6
- import { escapeRegExp } from '../text/regex.js';
7
- export async function installCodexAppGlmProfile(input) {
8
- const root = path.resolve(input.root);
9
- const profile = buildGlmCodexAppModelProfile();
10
- const profilePath = path.join(root, '.sneakoscope', 'codex-app', 'glm-model-profile.json');
11
- const reportPath = path.join(root, '.sneakoscope', 'reports', 'codex-app-glm-profile.json');
12
- const key = await resolveOpenRouterApiKey({ env: input.env || process.env });
13
- const home = input.home || input.env?.HOME;
14
- const configWrite = input.apply === false
15
- ? null
16
- : await ensureGlobalCodexAppGlmProfile({ home, configPath: input.configPath, env: input.env });
17
- if (input.apply !== false) {
18
- await rmrf(profilePath).catch(() => undefined);
19
- await rmrf(reportPath).catch(() => undefined);
20
- }
21
- const configStatus = input.apply === false
22
- ? await previewCodexAppGlmConfigStatus({ home, configPath: input.configPath, ...(input.env ? { env: input.env } : {}) })
23
- : await inspectCodexAppOpenRouterConfig({ home, configPath: input.configPath, ...(input.env ? { env: input.env } : {}) });
24
- const configWriteBlockers = configWrite?.ok === false
25
- ? [`glm_codex_app_config_${configWrite.status || 'failed'}`]
26
- : [];
27
- const blockers = input.apply === false ? [] : [...configWriteBlockers, ...configStatus.blockers];
28
- const warnings = [
29
- ...key.warnings,
30
- ...(key.key ? [] : ['openrouter_key_missing_until_sks_codex_app_set_openrouter_key']),
31
- ...(input.apply === false ? ['codex_desktop_openrouter_config_not_written_apply_false'] : []),
32
- 'glm_desktop_profiles_retired_use_openrouter'
33
- ];
34
- const result = {
35
- schema: 'sks.codex-app-glm-profile-result.v1',
36
- generated_at: nowIso(),
37
- ok: blockers.length === 0,
38
- status: blockers.length === 0
39
- ? input.apply === false
40
- ? 'valid'
41
- : 'removed'
42
- : 'blocked',
43
- profile,
44
- profile_path: '.sneakoscope/codex-app/glm-model-profile.json',
45
- report_path: '.sneakoscope/reports/codex-app-glm-profile.json',
46
- config_path: configStatus.config_path,
47
- codex_config_profile: OPENROUTER_DEFAULT_PROFILE_ID,
48
- codex_reasoning_profiles: [],
49
- config_write: configWrite,
50
- config_status: configStatus,
51
- openrouter_key_source: key.source,
52
- blockers,
53
- warnings
54
- };
55
- return result;
56
- }
57
- export async function doctorCodexAppGlmProfile(input) {
58
- const root = path.resolve(input.root);
59
- const profilePath = path.join(root, '.sneakoscope', 'codex-app', 'glm-model-profile.json');
60
- const reportPath = path.join(root, '.sneakoscope', 'reports', 'codex-app-glm-profile.json');
61
- const key = await resolveOpenRouterApiKey({ env: input.env || process.env });
62
- const profile = buildGlmCodexAppModelProfile();
63
- const home = input.home || input.env?.HOME;
64
- const configStatus = await inspectCodexAppOpenRouterConfig({ home, configPath: input.configPath, ...(input.env ? { env: input.env } : {}) });
65
- const leftoverMeta = await readText(profilePath, '').catch(() => '');
66
- const blockers = [
67
- ...configStatus.blockers,
68
- ...(leftoverMeta.trim() ? ['retired_glm_model_profile_metadata_present'] : [])
69
- ];
70
- const warnings = [
71
- ...key.warnings,
72
- ...(key.key ? [] : ['openrouter_key_missing_until_sks_codex_app_set_openrouter_key']),
73
- 'glm_desktop_profiles_retired_use_openrouter'
74
- ];
75
- const result = {
76
- schema: 'sks.codex-app-glm-profile-result.v1',
77
- generated_at: nowIso(),
78
- ok: blockers.length === 0,
79
- status: blockers.length === 0 ? 'valid' : 'blocked',
80
- profile,
81
- profile_path: '.sneakoscope/codex-app/glm-model-profile.json',
82
- report_path: '.sneakoscope/reports/codex-app-glm-profile.json',
83
- config_path: configStatus.config_path,
84
- codex_config_profile: OPENROUTER_DEFAULT_PROFILE_ID,
85
- codex_reasoning_profiles: [],
86
- config_write: null,
87
- config_status: configStatus,
88
- openrouter_key_source: key.source,
89
- blockers,
90
- warnings
91
- };
92
- await writeJsonAtomic(reportPath, result).catch(() => undefined);
93
- return result;
94
- }
95
- async function previewCodexAppGlmConfigStatus(input) {
96
- const configPath = input.configPath || codexLbConfigPath(input.home);
97
- return {
98
- schema: 'sks.codex-app-glm-config-status.v1',
99
- ok: true,
100
- config_path: configPath,
101
- provider_present: false,
102
- provider_auth_present: false,
103
- profiles_present: [],
104
- profiles_missing: [],
105
- retired_profiles_remaining: [],
106
- blockers: []
107
- };
108
- }
109
- async function inspectCodexAppOpenRouterConfig(input) {
110
- const configPath = input.configPath || codexLbConfigPath(input.home);
111
- const text = await readText(configPath, '').catch(() => '');
112
- const providerBody = tomlTableBody(text, `model_providers.${OPENROUTER_PROVIDER_ID}`);
113
- const providerPresent = Boolean(providerBody);
114
- const authBody = tomlTableBody(text, `model_providers.${OPENROUTER_PROVIDER_ID}.auth`);
115
- const providerAuthPresent = Boolean(authBody);
116
- const providerEnvKeyPresent = hasTomlKey(providerBody, 'env_key');
117
- const authEnv = { ...(input.env || process.env), ...(input.home ? { HOME: input.home } : {}) };
118
- const authArgs = openRouterAuthCommandArgs(openRouterSecretPaths(authEnv).keyPath);
119
- const blockers = [];
120
- if (!providerPresent) {
121
- blockers.push('glm_codex_app_config_missing_openrouter_provider');
122
- }
123
- else {
124
- if (!hasTomlString(providerBody, 'base_url', 'https://openrouter.ai/api/v1'))
125
- blockers.push('glm_codex_app_config_invalid_openrouter_base_url');
126
- if (!hasTomlString(providerBody, 'wire_api', 'responses'))
127
- blockers.push('glm_codex_app_config_invalid_openrouter_wire_api');
128
- if (providerEnvKeyPresent)
129
- blockers.push('glm_codex_app_config_openrouter_auth_env_key_conflict');
130
- }
131
- if (!providerAuthPresent) {
132
- blockers.push('glm_codex_app_config_missing_openrouter_auth');
133
- }
134
- else {
135
- if (!hasTomlString(authBody, 'command', OPENROUTER_AUTH_COMMAND))
136
- blockers.push('glm_codex_app_config_invalid_openrouter_auth_command');
137
- if (!hasTomlStringArray(authBody, 'args', authArgs))
138
- blockers.push('glm_codex_app_config_invalid_openrouter_auth_args');
139
- if (!hasTomlInteger(authBody, 'timeout_ms', OPENROUTER_AUTH_TIMEOUT_MS))
140
- blockers.push('glm_codex_app_config_invalid_openrouter_auth_timeout');
141
- if (!hasTomlInteger(authBody, 'refresh_interval_ms', OPENROUTER_AUTH_REFRESH_INTERVAL_MS))
142
- blockers.push('glm_codex_app_config_invalid_openrouter_auth_refresh');
143
- }
144
- const retiredRemaining = [];
145
- for (const profileId of RETIRED_GLM_DESKTOP_CONFIG_PROFILE_IDS) {
146
- if (tomlTableBody(text, `profiles.${profileId}`)) {
147
- retiredRemaining.push(profileId);
148
- blockers.push(`retired_glm_desktop_profile_present:${profileId}`);
149
- }
150
- }
151
- return {
152
- schema: 'sks.codex-app-glm-config-status.v1',
153
- ok: blockers.length === 0,
154
- config_path: configPath,
155
- provider_present: providerPresent,
156
- provider_auth_present: providerAuthPresent,
157
- profiles_present: [],
158
- profiles_missing: [],
159
- retired_profiles_remaining: retiredRemaining,
160
- blockers
161
- };
162
- }
163
- function tomlTableBody(text, table) {
164
- const header = `[${table}]`;
165
- const lines = String(text || '').split('\n');
166
- const start = lines.findIndex((line) => line.trim() === header);
167
- if (start === -1)
168
- return '';
169
- const body = [];
170
- for (let index = start + 1; index < lines.length; index += 1) {
171
- const line = lines[index];
172
- if (/^\s*\[[^\]]+\]\s*$/.test(line || ''))
173
- break;
174
- body.push(line || '');
175
- }
176
- return body.join('\n');
177
- }
178
- function hasTomlString(text, key, value) {
179
- const pattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"${escapeRegExp(value)}"\\s*(?:#.*)?$`, 'm');
180
- return pattern.test(text);
181
- }
182
- function hasTomlKey(text, key) {
183
- return new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`, 'm').test(text);
184
- }
185
- function hasTomlStringArray(text, key, values) {
186
- const expected = values.map((value) => JSON.stringify(value)).join(', ');
187
- return new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*\\[${escapeRegExp(expected)}\\]\\s*(?:#.*)?$`, 'm').test(text);
188
- }
189
- function hasTomlInteger(text, key, value) {
190
- return new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*${value}\\s*(?:#.*)?$`, 'm').test(text);
191
- }
@@ -1,42 +0,0 @@
1
- import { OPENROUTER_DEFAULT_PROFILE_ID, OPENROUTER_PROVIDER_ID, GLM_CODEX_SELECTABLE_REASONING_EFFORTS, GLM_CODEX_CONFIG_REASONING_PROFILES, GLM_52_OPENROUTER_MODEL, buildGlmCodexAppModelProfile } from './openrouter-provider.js';
2
- export function validateGlmCodexAppModelProfile(value) {
3
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
4
- return { ok: false, blockers: ['glm_codex_app_profile_missing'], profile: null };
5
- }
6
- const profile = value;
7
- const expected = buildGlmCodexAppModelProfile();
8
- const blockers = [
9
- profile.schema === expected.schema ? null : 'glm_codex_app_profile_invalid_schema',
10
- profile.id === OPENROUTER_DEFAULT_PROFILE_ID ? null : 'glm_codex_app_profile_invalid_id',
11
- profile.provider === 'openrouter' ? null : 'glm_codex_app_profile_invalid_provider',
12
- profile.model === GLM_52_OPENROUTER_MODEL ? null : 'glm_codex_app_profile_invalid_model',
13
- profile.codexConfigProvider === OPENROUTER_PROVIDER_ID ? null : 'glm_codex_app_profile_invalid_codex_config_provider',
14
- profile.codexConfigProfile === OPENROUTER_DEFAULT_PROFILE_ID ? null : 'glm_codex_app_profile_invalid_codex_config_profile',
15
- hasExpectedReasoningEfforts(profile.supportedReasoningEfforts) ? null : 'glm_codex_app_profile_invalid_reasoning_efforts',
16
- hasExpectedReasoningProfiles(profile.reasoningProfiles) ? null : 'glm_codex_app_profile_invalid_reasoning_profiles',
17
- profile.mode === 'openrouter-desktop' ? null : 'glm_codex_app_profile_invalid_mode',
18
- profile.strictModelLock === false ? null : 'glm_codex_app_profile_unexpected_strict_lock',
19
- profile.gptFallbackAllowed === false ? null : 'glm_codex_app_profile_allows_gpt_fallback',
20
- profile.defaultProfile === 'speed' ? null : 'glm_codex_app_profile_default_not_speed',
21
- profile.defaultSettings?.tool_choice === 'none' ? null : 'glm_codex_app_profile_default_tools_not_omitted',
22
- profile.defaultSettings?.provider_require_parameters === false ? null : 'glm_codex_app_profile_default_requires_parameters',
23
- profile.defaultSettings?.provider_allow_fallbacks === false ? null : 'glm_codex_app_profile_allows_provider_fallback'
24
- ].filter((item) => Boolean(item));
25
- return {
26
- ok: blockers.length === 0,
27
- blockers,
28
- profile: blockers.length === 0 ? profile : null
29
- };
30
- }
31
- function hasExpectedReasoningEfforts(value) {
32
- if (!Array.isArray(value)) {
33
- return false;
34
- }
35
- return GLM_CODEX_SELECTABLE_REASONING_EFFORTS.every((effort) => value.includes(effort));
36
- }
37
- function hasExpectedReasoningProfiles(value) {
38
- if (!Array.isArray(value)) {
39
- return false;
40
- }
41
- return value.length === 0 && GLM_CODEX_CONFIG_REASONING_PROFILES.length === 0;
42
- }
@@ -1,391 +0,0 @@
1
- import os from 'node:os';
2
- import { exists, nowIso, readText } from '../fsx.js';
3
- import { ensureTrailingNewline, normalizeCodexFastModeUiConfig, safeWriteCodexConfigToml, upsertTopLevelTomlString, upsertTomlTable } from '../codex-runtime/codex-desktop-config-policy.js';
4
- import { unselectCodexLbProvider } from '../../cli/install-helpers.js';
5
- import { restartCodexApp } from './codex-app-restart.js';
6
- import { codexUserConfigPath, defaultOpenCodexCatalogPath, normalizeCodexModelId, readCodexModelCatalogFile, readTopLevelTomlString } from './codex-model-catalog.js';
7
- import { hasTomlKey, hasTomlTablePrefix, hasUnexpectedTomlKeys, isLoopbackHostname, isSksManagedCatalogPath, probeRouterModels, resolveCatalogPath, routerBlocked, tomlBoolean, tomlString, tomlTableBody, uniqueStrings } from './multi-provider-router-support.js';
8
- import { classifyCodexDesktopRouting, OPENCODEX_DESIGN_B_FORCE_HINT, opencodexDesignBBlocksRouterActivation } from './codex-desktop-routing-ownership.js';
9
- import { desktopPickerStatusFromCache, invalidateCodexModelsCache } from './codex-models-cache.js';
10
- export const MULTI_PROVIDER_ROUTER_ID = 'sks-router';
11
- export const MULTI_PROVIDER_ROUTER_NAME = 'SKS Multi-Provider Router';
12
- export const MULTI_PROVIDER_ROUTER_DEFAULT_BASE_URL = 'http://127.0.0.1:10100/v1';
13
- const MULTI_PROVIDER_ROUTER_TIMEOUT_MS = 5_000;
14
- export function normalizeMultiProviderRouterBaseUrl(value) {
15
- const raw = String(value || '').trim() || MULTI_PROVIDER_ROUTER_DEFAULT_BASE_URL;
16
- let parsed;
17
- try {
18
- parsed = new URL(raw);
19
- }
20
- catch {
21
- return { ok: false, value: null, blocker: 'multi_provider_router_base_url_invalid' };
22
- }
23
- if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
24
- return { ok: false, value: null, blocker: 'multi_provider_router_base_url_protocol_unsupported' };
25
- }
26
- if (parsed.username || parsed.password || parsed.search || parsed.hash) {
27
- return { ok: false, value: null, blocker: 'multi_provider_router_base_url_contains_credentials_or_query' };
28
- }
29
- if (!isLoopbackHostname(parsed.hostname)) {
30
- return { ok: false, value: null, blocker: 'multi_provider_router_requires_loopback' };
31
- }
32
- const normalizedPath = parsed.pathname.replace(/\/+$/, '');
33
- if (!normalizedPath || normalizedPath === '')
34
- parsed.pathname = '/v1';
35
- else if (normalizedPath === '/')
36
- parsed.pathname = '/v1';
37
- else if (normalizedPath !== '/v1') {
38
- return { ok: false, value: null, blocker: 'multi_provider_router_base_url_must_end_in_v1' };
39
- }
40
- else {
41
- parsed.pathname = '/v1';
42
- }
43
- return { ok: true, value: parsed.toString().replace(/\/$/, ''), blocker: null };
44
- }
45
- export async function multiProviderRouterStatus(input = {}) {
46
- const env = input.env || process.env;
47
- const home = input.home || env.HOME || os.homedir();
48
- const configPath = input.configPath || codexUserConfigPath({ home, env });
49
- const config = await readText(configPath, '');
50
- const providerBody = tomlTableBody(config, `model_providers.${MULTI_PROVIDER_ROUTER_ID}`);
51
- const providerPresent = Boolean(providerBody);
52
- const providerName = tomlString(providerBody, 'name');
53
- const configuredBaseUrl = tomlString(providerBody, 'base_url');
54
- const baseUrlResult = normalizeMultiProviderRouterBaseUrl(input.baseUrl || configuredBaseUrl || MULTI_PROVIDER_ROUTER_DEFAULT_BASE_URL);
55
- const baseUrl = baseUrlResult.value || String(input.baseUrl || configuredBaseUrl || MULTI_PROVIDER_ROUTER_DEFAULT_BASE_URL);
56
- const configuredCatalogPath = readTopLevelTomlString(config, 'model_catalog_json');
57
- const catalogPath = resolveCatalogPath(input.catalogPath || configuredCatalogPath || defaultOpenCodexCatalogPath({ home, env }), { home, env, configPath });
58
- const catalog = await readCodexModelCatalogFile({
59
- filePath: catalogPath,
60
- configured: Boolean(configuredCatalogPath)
61
- });
62
- const selectedProvider = readTopLevelTomlString(config, 'model_provider');
63
- const selected = selectedProvider === MULTI_PROVIDER_ROUTER_ID;
64
- const activeModel = selected ? readTopLevelTomlString(config, 'model') : null;
65
- const activeModelPresent = Boolean(activeModel && catalog.models.some((entry) => entry.model === activeModel));
66
- const providerContractOk = providerPresent
67
- && providerName === MULTI_PROVIDER_ROUTER_NAME
68
- && baseUrlResult.ok
69
- && configuredBaseUrl === baseUrl
70
- && tomlString(providerBody, 'wire_api') === 'responses'
71
- && tomlBoolean(providerBody, 'requires_openai_auth') === false
72
- && !hasTomlKey(providerBody, 'env_key')
73
- && !hasTomlKey(providerBody, 'experimental_bearer_token')
74
- && !hasTomlKey(providerBody, 'http_headers')
75
- && !hasTomlKey(providerBody, 'env_http_headers')
76
- && !hasUnexpectedTomlKeys(providerBody, ['name', 'base_url', 'wire_api', 'requires_openai_auth'])
77
- && !hasTomlTablePrefix(config, `model_providers.${MULTI_PROVIDER_ROUTER_ID}.`);
78
- const blockers = uniqueStrings([
79
- ...(providerPresent ? [] : ['multi_provider_router_provider_missing']),
80
- ...(providerPresent && !providerContractOk ? ['multi_provider_router_provider_contract_drift'] : []),
81
- ...(baseUrlResult.blocker ? [baseUrlResult.blocker] : []),
82
- ...catalog.blockers,
83
- ...(selected && !activeModel ? ['multi_provider_router_active_model_missing'] : []),
84
- ...(selected && activeModel && !activeModelPresent ? ['multi_provider_router_active_model_not_in_catalog'] : [])
85
- ]);
86
- const ready = providerContractOk && catalog.ok && (!selected || activeModelPresent);
87
- const ownership = classifyCodexDesktopRouting(config, { home, env, configPath });
88
- return {
89
- schema: 'sks.codex-app-multi-provider-router-status.v1',
90
- ok: ready,
91
- status: !providerPresent
92
- ? 'not_configured'
93
- : ready
94
- ? 'configured'
95
- : 'blocked',
96
- provider: MULTI_PROVIDER_ROUTER_ID,
97
- provider_name: MULTI_PROVIDER_ROUTER_NAME,
98
- selected_provider: selectedProvider,
99
- selected,
100
- provider_present: providerPresent,
101
- provider_contract_ok: providerContractOk,
102
- base_url: baseUrl,
103
- base_url_loopback: baseUrlResult.ok,
104
- catalog_path: catalogPath,
105
- catalog_configured: Boolean(configuredCatalogPath),
106
- catalog,
107
- models: catalog.models.map((entry) => entry.model),
108
- model_count: catalog.model_count,
109
- models_truncated: catalog.truncated,
110
- active_model: activeModel,
111
- active_model_present: activeModelPresent,
112
- runtime_verified: false,
113
- config_path: configPath,
114
- routing_ownership: ownership,
115
- desktop_picker: desktopPickerStatusFromCache({
116
- catalogOk: catalog.ok,
117
- cache: null,
118
- restartAppRequested: false
119
- }),
120
- blockers,
121
- warnings: uniqueStrings([
122
- ...catalog.warnings,
123
- ...ownership.warnings,
124
- ...(selected
125
- && activeModel
126
- && activeModelPresent
127
- && catalog.models.find((entry) => entry.model === activeModel)?.multi_agent_version !== 'v2'
128
- ? ['multi_provider_router_active_model_multi_agent_v2_missing']
129
- : [])
130
- ])
131
- };
132
- }
133
- export async function testMultiProviderRouter(input) {
134
- const env = input.env || process.env;
135
- const home = input.home || env.HOME || os.homedir();
136
- const baseUrlResult = normalizeMultiProviderRouterBaseUrl(input.baseUrl);
137
- if (!baseUrlResult.ok || !baseUrlResult.value) {
138
- return routerBlocked('sks.codex-app-multi-provider-router-test.v1', baseUrlResult.blocker || 'multi_provider_router_base_url_invalid');
139
- }
140
- const catalogPath = resolveCatalogPath(input.catalogPath || defaultOpenCodexCatalogPath({ home, env }), { home, env });
141
- const catalog = await readCodexModelCatalogFile({ filePath: catalogPath, configured: true });
142
- if (!catalog.ok) {
143
- return {
144
- ...routerBlocked('sks.codex-app-multi-provider-router-test.v1', ...catalog.blockers),
145
- base_url: baseUrlResult.value,
146
- catalog_path: catalogPath,
147
- catalog
148
- };
149
- }
150
- const model = input.model ? normalizeCodexModelId(input.model) : null;
151
- if (input.model && !model) {
152
- return routerBlocked('sks.codex-app-multi-provider-router-test.v1', 'multi_provider_router_model_invalid');
153
- }
154
- if (model && !catalog.models.some((entry) => entry.model === model)) {
155
- return {
156
- ...routerBlocked('sks.codex-app-multi-provider-router-test.v1', 'multi_provider_router_model_not_in_catalog'),
157
- base_url: baseUrlResult.value,
158
- catalog_path: catalogPath,
159
- model
160
- };
161
- }
162
- const probe = await probeRouterModels({
163
- baseUrl: baseUrlResult.value,
164
- fetchImpl: input.fetchImpl || fetch,
165
- timeoutMs: input.timeoutMs || MULTI_PROVIDER_ROUTER_TIMEOUT_MS
166
- });
167
- const liveModelPresent = !model || probe.models.includes(model);
168
- const blockers = uniqueStrings([
169
- ...probe.blockers,
170
- ...(liveModelPresent ? [] : ['multi_provider_router_model_not_live'])
171
- ]);
172
- return {
173
- schema: 'sks.codex-app-multi-provider-router-test.v1',
174
- generated_at: nowIso(),
175
- ok: blockers.length === 0,
176
- status: blockers.length === 0 ? 'connected' : 'blocked',
177
- base_url: baseUrlResult.value,
178
- catalog_path: catalogPath,
179
- model,
180
- catalog_model_count: catalog.model_count,
181
- live_model_count: probe.models.length,
182
- live_model_present: liveModelPresent,
183
- probe,
184
- blockers,
185
- warnings: catalog.warnings
186
- };
187
- }
188
- export async function useMultiProviderRouter(input) {
189
- const env = input.env || process.env;
190
- const home = input.home || env.HOME || os.homedir();
191
- const configPath = input.configPath || codexUserConfigPath({ home, env });
192
- const model = normalizeCodexModelId(input.model);
193
- if (!model)
194
- return routerBlocked('sks.codex-app-use-multi-provider-router.v1', 'multi_provider_router_model_invalid');
195
- const baseUrlResult = normalizeMultiProviderRouterBaseUrl(input.baseUrl);
196
- if (!baseUrlResult.ok || !baseUrlResult.value) {
197
- return routerBlocked('sks.codex-app-use-multi-provider-router.v1', baseUrlResult.blocker || 'multi_provider_router_base_url_invalid');
198
- }
199
- const catalogPath = resolveCatalogPath(input.catalogPath || defaultOpenCodexCatalogPath({ home, env }), { home, env, configPath });
200
- const catalog = await readCodexModelCatalogFile({ filePath: catalogPath, configured: true });
201
- if (!catalog.ok) {
202
- return {
203
- ...routerBlocked('sks.codex-app-use-multi-provider-router.v1', ...catalog.blockers),
204
- model,
205
- base_url: baseUrlResult.value,
206
- catalog_path: catalogPath,
207
- catalog
208
- };
209
- }
210
- if (!catalog.models.some((entry) => entry.model === model)) {
211
- return {
212
- ...routerBlocked('sks.codex-app-use-multi-provider-router.v1', 'multi_provider_router_model_not_in_catalog'),
213
- model,
214
- base_url: baseUrlResult.value,
215
- catalog_path: catalogPath
216
- };
217
- }
218
- const probe = await testMultiProviderRouter({
219
- baseUrl: baseUrlResult.value,
220
- catalogPath,
221
- model,
222
- home,
223
- env,
224
- ...(input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}),
225
- ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs })
226
- });
227
- if (!probe.ok) {
228
- return {
229
- schema: 'sks.codex-app-use-multi-provider-router.v1',
230
- generated_at: nowIso(),
231
- ok: false,
232
- status: 'probe_blocked',
233
- model,
234
- base_url: baseUrlResult.value,
235
- catalog_path: catalogPath,
236
- probe,
237
- blockers: probe.blockers,
238
- warnings: probe.warnings || []
239
- };
240
- }
241
- const configExistedBefore = await exists(configPath);
242
- const current = await readText(configPath, '');
243
- const ownership = classifyCodexDesktopRouting(current, { home, env, configPath });
244
- const designBBlocker = opencodexDesignBBlocksRouterActivation(ownership, {
245
- forceRoutingOverride: input.forceRoutingOverride === true
246
- });
247
- if (designBBlocker) {
248
- return {
249
- ...routerBlocked('sks.codex-app-use-multi-provider-router.v1', designBBlocker),
250
- model,
251
- base_url: baseUrlResult.value,
252
- catalog_path: catalogPath,
253
- routing_ownership: ownership,
254
- hint: OPENCODEX_DESIGN_B_FORCE_HINT
255
- };
256
- }
257
- const providerBody = tomlTableBody(current, `model_providers.${MULTI_PROVIDER_ROUTER_ID}`);
258
- const existingProviderName = tomlString(providerBody, 'name');
259
- if (providerBody && existingProviderName !== MULTI_PROVIDER_ROUTER_NAME) {
260
- return routerBlocked('sks.codex-app-use-multi-provider-router.v1', 'multi_provider_router_provider_id_conflict');
261
- }
262
- const providerContractConflict = (providerBody || hasTomlTablePrefix(current, `model_providers.${MULTI_PROVIDER_ROUTER_ID}.`)) && (hasUnexpectedTomlKeys(providerBody, ['name', 'base_url', 'wire_api', 'requires_openai_auth'])
263
- || hasTomlTablePrefix(current, `model_providers.${MULTI_PROVIDER_ROUTER_ID}.`));
264
- if (providerContractConflict) {
265
- return routerBlocked('sks.codex-app-use-multi-provider-router.v1', 'multi_provider_router_existing_provider_contract_conflict');
266
- }
267
- const configuredCatalogPath = readTopLevelTomlString(current, 'model_catalog_json');
268
- const configuredCatalogResolved = configuredCatalogPath
269
- ? resolveCatalogPath(configuredCatalogPath, { home, env, configPath })
270
- : null;
271
- const catalogConflict = configuredCatalogPath
272
- && configuredCatalogResolved !== catalogPath
273
- && !isSksManagedCatalogPath(configuredCatalogResolved, { home, env })
274
- && input.replaceCatalog !== true;
275
- if (catalogConflict) {
276
- return {
277
- ...routerBlocked('sks.codex-app-use-multi-provider-router.v1', 'multi_provider_router_user_catalog_conflict'),
278
- configured_catalog_path: configuredCatalogPath,
279
- requested_catalog_path: catalogPath,
280
- hint: 'Re-run with --replace-catalog only if replacing the current user model catalog is intentional.'
281
- };
282
- }
283
- const unselect = await unselectCodexLbProvider({
284
- home,
285
- configPath,
286
- processEnv: env
287
- }).catch((err) => ({ ok: false, status: 'failed', provider_error: err?.message || String(err) }));
288
- if (unselect?.ok !== true) {
289
- return {
290
- ...routerBlocked('sks.codex-app-use-multi-provider-router.v1', unselect?.reason === 'shared_codex_lb_auth_active'
291
- ? 'legacy_codex_lb_desktop_config_requires_migration'
292
- : String(unselect?.status || 'codex_lb_unselect_failed')),
293
- model,
294
- base_url: baseUrlResult.value,
295
- catalog_path: catalogPath,
296
- unselect,
297
- guidance: unselect?.reason === 'shared_codex_lb_auth_active'
298
- ? ['Run: sks codex-lb migrate-legacy-desktop --restart-app']
299
- : []
300
- };
301
- }
302
- const currentAfterUnselect = await readText(configPath, '');
303
- const providerBlock = [
304
- `[model_providers.${MULTI_PROVIDER_ROUTER_ID}]`,
305
- `name = ${JSON.stringify(MULTI_PROVIDER_ROUTER_NAME)}`,
306
- `base_url = ${JSON.stringify(baseUrlResult.value)}`,
307
- 'wire_api = "responses"',
308
- 'requires_openai_auth = false'
309
- ].join('\n');
310
- let next = upsertTomlTable(currentAfterUnselect, `model_providers.${MULTI_PROVIDER_ROUTER_ID}`, providerBlock);
311
- next = upsertTopLevelTomlString(next, 'model_catalog_json', catalogPath);
312
- next = upsertTopLevelTomlString(next, 'model_provider', MULTI_PROVIDER_ROUTER_ID);
313
- next = upsertTopLevelTomlString(next, 'model', model);
314
- next = normalizeCodexFastModeUiConfig(next);
315
- next = ensureTrailingNewline(next);
316
- const write = await safeWriteCodexConfigToml(configPath, currentAfterUnselect, next, 'multi-provider-router-use', {
317
- verifyUnchangedBeforeWrite: true,
318
- expectedBeforeExists: configExistedBefore
319
- });
320
- if (!write.ok) {
321
- return {
322
- ...routerBlocked('sks.codex-app-use-multi-provider-router.v1', String(write.status || 'multi_provider_router_config_write_blocked')),
323
- model,
324
- base_url: baseUrlResult.value,
325
- catalog_path: catalogPath,
326
- write
327
- };
328
- }
329
- const restart = await (input.restartImpl || restartCodexApp)({ enabled: Boolean(input.restartApp) });
330
- const cache = await invalidateCodexModelsCache({ home, env, catalogPath });
331
- const status = await multiProviderRouterStatus({ home, env, configPath });
332
- const configApplied = status.selected
333
- && status.provider_contract_ok
334
- && status.catalog.ok
335
- && status.active_model === model
336
- && status.active_model_present;
337
- const restartRequired = Boolean(input.restartApp);
338
- const restartCompleted = !restartRequired || (restart.ok && restart.skipped !== true);
339
- const operationOk = configApplied && restartCompleted;
340
- const desktopPicker = desktopPickerStatusFromCache({
341
- catalogOk: catalog.ok && configApplied,
342
- cache,
343
- restartAppRequested: Boolean(input.restartApp)
344
- });
345
- return {
346
- schema: 'sks.codex-app-use-multi-provider-router.v1',
347
- generated_at: nowIso(),
348
- ok: operationOk,
349
- status: !configApplied
350
- ? 'configuration_incomplete'
351
- : restartCompleted
352
- ? restartRequired ? 'configured_restarted' : 'configured'
353
- : 'configured_restart_blocked',
354
- provider: MULTI_PROVIDER_ROUTER_ID,
355
- model,
356
- base_url: baseUrlResult.value,
357
- catalog_path: catalogPath,
358
- config_path: configPath,
359
- probe,
360
- write,
361
- unselect,
362
- restart_app: restart,
363
- restart_ok: restart.ok && restart.skipped !== true,
364
- restart_completed: restartCompleted,
365
- restart_skipped: restart.skipped === true,
366
- config_applied: configApplied,
367
- runtime_verified: false,
368
- models_cache: cache,
369
- desktop_picker: desktopPicker,
370
- routing_ownership: ownership,
371
- router: {
372
- ...status,
373
- desktop_picker: desktopPicker
374
- },
375
- blockers: uniqueStrings([
376
- ...(configApplied ? [] : ['multi_provider_router_configuration_incomplete']),
377
- ...(restartCompleted ? [] : (restart.blockers || ['multi_provider_router_restart_blocked']))
378
- ]),
379
- warnings: uniqueStrings([
380
- ...catalog.warnings,
381
- ...ownership.warnings,
382
- ...cache.warnings,
383
- ...(catalog.models.find((entry) => entry.model === model)?.multi_agent_version === 'v2'
384
- ? []
385
- : ['multi_provider_router_model_multi_agent_v2_missing']),
386
- ...(desktopPicker.models_cache_invalidated ? [] : ['multi_provider_router_models_cache_not_invalidated']),
387
- 'multi_provider_router_runtime_not_verified',
388
- 'desktop_picker_restart_recommended_after_catalog_change'
389
- ])
390
- };
391
- }