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,456 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { ensureDir, exists, nowIso, sha256, writeTextAtomic } from '../fsx.js';
4
- import { codexLbBaseUrlSecurityBlocker, normalizeCodexLbBaseUrl } from './codex-lb-env.js';
5
- export const CODEX_LB_TOOL_CATALOG_FILENAME = 'sks-codex-lb-tool-catalog.json';
6
- export const CODEX_LB_TOOL_CATALOG_SCHEMA = 'sks.codex-lb-tool-catalog.v1';
7
- export const CODEX_LB_TOOL_CATALOG_METADATA_SCHEMA = 'sks.codex-lb-tool-catalog-metadata.v1';
8
- export const CODEX_LB_TOOL_CATALOG_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
9
- export const CODEX_LB_TOOL_CATALOG_MAX_MODELS = 128;
10
- const CODEX_LB_TOOL_CATALOG_MAX_METADATA_BYTES = 16 * 1024;
11
- const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
12
- const GPT56_MODEL_RE = /^gpt-5\.6-(?:sol|terra|luna)$/;
13
- const ensureInflight = new Map();
14
- const REQUIRED_MODEL_FIELDS = {
15
- slug: ['string'],
16
- display_name: ['string'],
17
- supported_reasoning_levels: ['array'],
18
- truncation_policy: ['object']
19
- };
20
- export function isCodexLbGpt56Model(model) {
21
- return GPT56_MODEL_RE.test(String(model || '').trim());
22
- }
23
- export function shouldBindLocalModelCatalog(mode) {
24
- return mode === 'desktop-dual-auth-compat' || mode === 'cli-provider';
25
- }
26
- export function codexLbToolCatalogPath(codexHome) {
27
- return path.join(path.resolve(codexHome), CODEX_LB_TOOL_CATALOG_FILENAME);
28
- }
29
- export function codexLbToolCatalogMetadataPath(catalogPath) {
30
- return `${path.resolve(catalogPath)}.meta.json`;
31
- }
32
- export function normalizeCodexLbToolCatalog(payload, opts = {}) {
33
- const rows = Array.isArray(payload?.models)
34
- ? payload.models
35
- : Array.isArray(payload?.data)
36
- ? payload.data
37
- : [];
38
- const maxModels = boundedPositiveInt(opts.maxModels, CODEX_LB_TOOL_CATALOG_MAX_MODELS);
39
- const limitedRows = rows.slice(0, maxModels);
40
- const patchedModels = [];
41
- const gpt56Models = [];
42
- const validationIssues = [];
43
- const models = limitedRows.map((row, index) => {
44
- if (!isPlainObject(row)) {
45
- validationIssues.push(`codex_lb_model_catalog_row_invalid:${index}:object`);
46
- return {};
47
- }
48
- let normalized;
49
- try {
50
- normalized = normalizeCodexLbModelRow(row, index);
51
- }
52
- catch (error) {
53
- validationIssues.push(error instanceof Error ? error.message : `codex_lb_model_catalog_row_invalid:${index}`);
54
- return {};
55
- }
56
- const model = String(normalized.slug || '').trim();
57
- if (isCodexLbGpt56Model(model))
58
- gpt56Models.push(model);
59
- if (row.use_responses_lite !== false)
60
- patchedModels.push(model);
61
- validationIssues.push(...validateKnownModelFields(normalized, index));
62
- return normalized;
63
- });
64
- const compatible = models.length > 0
65
- && validationIssues.length === 0
66
- && models.every((row) => row.use_responses_lite === false);
67
- const blockers = uniqueBounded([
68
- ...(rows.length ? [] : ['codex_lb_model_catalog_empty']),
69
- ...(rows.length > maxModels ? [`codex_lb_model_catalog_model_limit_exceeded:${rows.length}:${maxModels}`] : []),
70
- ...validationIssues,
71
- ...(compatible ? [] : ['codex_lb_model_catalog_tools_transport_incompatible'])
72
- ]);
73
- return {
74
- schema: CODEX_LB_TOOL_CATALOG_SCHEMA,
75
- contract: 'codex-model-catalog-pass-through.v2',
76
- ok: blockers.length === 0,
77
- catalog: { models },
78
- model_count: models.length,
79
- gpt56_models: [...new Set(gpt56Models)].sort(),
80
- patched_models: [...new Set(patchedModels)].sort(),
81
- tools_transport: compatible ? 'full_responses' : 'unverified',
82
- blockers
83
- };
84
- }
85
- export async function inspectCodexLbToolCatalog(file, opts = {}) {
86
- const maxBytes = boundedPositiveInt(opts.maxBytes, CODEX_LB_TOOL_CATALOG_MAX_RESPONSE_BYTES);
87
- const fileSafety = await inspectSecureRegularFile(file, maxBytes, 'codex_lb_tool_catalog');
88
- if (!fileSafety.ok)
89
- return incompatibleInspection(file, fileSafety, fileSafety.blockers);
90
- let text = '';
91
- let payload = null;
92
- try {
93
- text = await fs.readFile(file, 'utf8');
94
- payload = JSON.parse(text);
95
- }
96
- catch {
97
- return incompatibleInspection(file, fileSafety, ['codex_lb_tool_catalog_json_invalid']);
98
- }
99
- const normalized = normalizeCodexLbToolCatalog(payload, opts.maxModels === undefined ? {} : { maxModels: opts.maxModels });
100
- const { catalog: _catalog, ...status } = normalized;
101
- const metadataPath = codexLbToolCatalogMetadataPath(file);
102
- const identityBlockers = [];
103
- let identityVerified = false;
104
- let cacheOrigin = null;
105
- if (opts.expectedIdentity) {
106
- const metadataSafety = await inspectSecureRegularFile(metadataPath, CODEX_LB_TOOL_CATALOG_MAX_METADATA_BYTES, 'codex_lb_tool_catalog_metadata');
107
- if (!metadataSafety.ok) {
108
- identityBlockers.push(...metadataSafety.blockers);
109
- }
110
- else {
111
- try {
112
- const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
113
- cacheOrigin = typeof metadata?.identity?.origin === 'string' ? metadata.identity.origin : null;
114
- if (metadata?.schema !== CODEX_LB_TOOL_CATALOG_METADATA_SCHEMA)
115
- identityBlockers.push('codex_lb_tool_catalog_metadata_schema_invalid');
116
- if (metadata?.catalog_sha256 !== sha256(text))
117
- identityBlockers.push('codex_lb_tool_catalog_metadata_hash_mismatch');
118
- if (!sameCatalogIdentity(metadata?.identity, opts.expectedIdentity))
119
- identityBlockers.push('codex_lb_tool_catalog_identity_mismatch');
120
- identityVerified = identityBlockers.length === 0;
121
- }
122
- catch {
123
- identityBlockers.push('codex_lb_tool_catalog_metadata_json_invalid');
124
- }
125
- }
126
- }
127
- const blockers = uniqueBounded([...normalized.blockers, ...identityBlockers]);
128
- return {
129
- ...status,
130
- ok: blockers.length === 0,
131
- path: file,
132
- exists: true,
133
- updated_at_ms: fileSafety.updated_at_ms,
134
- size_bytes: fileSafety.size_bytes,
135
- mode: fileSafety.mode,
136
- owner_uid: fileSafety.owner_uid,
137
- metadata_path: metadataPath,
138
- identity_verified: opts.expectedIdentity ? identityVerified : null,
139
- cache_origin: cacheOrigin,
140
- blockers
141
- };
142
- }
143
- export async function ensureCodexLbToolCatalog(input) {
144
- const outputPath = path.resolve(input.outputPath || codexLbToolCatalogPath(input.codexHome));
145
- const baseUrl = normalizeCodexLbBaseUrl(input.baseUrl);
146
- const apiKey = String(input.apiKey || '').trim();
147
- const transportBlocker = catalogTransportBlocker(baseUrl);
148
- if (transportBlocker || !apiKey) {
149
- return blockedResult(outputPath, [transportBlocker || 'codex_lb_api_key_missing']);
150
- }
151
- const identity = catalogIdentity(baseUrl, apiKey);
152
- const inflightKey = [
153
- outputPath,
154
- identity.base_url_sha256,
155
- identity.api_key_sha256,
156
- input.force === true ? 'force' : 'normal',
157
- boundedPositiveInt(input.maxResponseBytes, CODEX_LB_TOOL_CATALOG_MAX_RESPONSE_BYTES),
158
- boundedPositiveInt(input.maxModels, CODEX_LB_TOOL_CATALOG_MAX_MODELS)
159
- ].join(':');
160
- const existing = ensureInflight.get(inflightKey);
161
- if (existing)
162
- return existing;
163
- const promise = ensureValidatedCodexLbToolCatalog(input, outputPath, baseUrl, apiKey, identity)
164
- .finally(() => ensureInflight.delete(inflightKey));
165
- ensureInflight.set(inflightKey, promise);
166
- return promise;
167
- }
168
- async function ensureValidatedCodexLbToolCatalog(input, outputPath, baseUrl, apiKey, identity) {
169
- const now = input.now ? input.now() : Date.now();
170
- const maxAgeMs = Math.max(0, Number(input.maxAgeMs ?? DEFAULT_MAX_AGE_MS));
171
- const inspectOpts = {
172
- expectedIdentity: identity,
173
- ...(input.maxResponseBytes === undefined ? {} : { maxBytes: input.maxResponseBytes }),
174
- ...(input.maxModels === undefined ? {} : { maxModels: input.maxModels })
175
- };
176
- const cached = await inspectCodexLbToolCatalog(outputPath, inspectOpts);
177
- if (!input.force && cached.ok && cacheIsFresh(cached.updated_at_ms, now, maxAgeMs)) {
178
- return { ...cached, generated_at: nowIso(), status: 'cached_compatible', fetched: false, required: true };
179
- }
180
- try {
181
- const response = await (input.fetchImpl || fetch)(`${baseUrl}/models`, {
182
- headers: { Authorization: `Bearer ${apiKey}` },
183
- signal: AbortSignal.timeout(Math.max(250, Number(input.timeoutMs || 5000)))
184
- });
185
- if (!response.ok)
186
- throw new CatalogFetchError(`codex_lb_models_http_${response.status}`);
187
- const payload = await readJsonResponseBounded(response, boundedPositiveInt(input.maxResponseBytes, CODEX_LB_TOOL_CATALOG_MAX_RESPONSE_BYTES));
188
- const normalized = normalizeCodexLbToolCatalog(payload, input.maxModels === undefined ? {} : { maxModels: input.maxModels });
189
- if (!normalized.ok) {
190
- const fallback = await inspectCodexLbToolCatalog(outputPath, inspectOpts);
191
- if (fallback.ok) {
192
- return { ...fallback, generated_at: nowIso(), status: 'cached_compatible_after_refresh_rejected', fetched: true, required: true, refresh_blockers: normalized.blockers };
193
- }
194
- const { catalog: _catalog, ...status } = normalized;
195
- return { ...status, generated_at: nowIso(), status: 'blocked', path: outputPath, exists: await exists(outputPath), fetched: true, required: true };
196
- }
197
- await writeSecureCatalog(outputPath, normalized.catalog, identity, {
198
- upstream_etag: response.headers.get('etag'),
199
- upstream_last_modified: response.headers.get('last-modified'),
200
- client_version: String(input.clientVersion || 'runtime-discovered')
201
- });
202
- const verified = await inspectCodexLbToolCatalog(outputPath, inspectOpts);
203
- return {
204
- ...verified,
205
- generated_at: nowIso(),
206
- status: verified.ok ? 'repaired' : 'write_verification_failed',
207
- fetched: true,
208
- required: true,
209
- patched_models: normalized.patched_models,
210
- blockers: verified.ok ? [] : verified.blockers
211
- };
212
- }
213
- catch (error) {
214
- const fallback = await inspectCodexLbToolCatalog(outputPath, inspectOpts);
215
- if (fallback.ok) {
216
- return {
217
- ...fallback,
218
- generated_at: nowIso(),
219
- status: 'cached_compatible_after_refresh_failed',
220
- fetched: false,
221
- required: true,
222
- refresh_error: safeError(error, [apiKey])
223
- };
224
- }
225
- const code = error instanceof CatalogFetchError ? error.code : 'codex_lb_tool_catalog_fetch_failed';
226
- return {
227
- ...blockedResult(outputPath, uniqueBounded(['codex_lb_tool_catalog_fetch_failed', code])),
228
- status: 'fetch_failed',
229
- fetched: false,
230
- error: safeError(error, [apiKey])
231
- };
232
- }
233
- }
234
- async function readJsonResponseBounded(response, maxBytes) {
235
- const advertised = Number(response.headers.get('content-length'));
236
- if (Number.isFinite(advertised) && advertised > maxBytes)
237
- throw new CatalogFetchError('codex_lb_tool_catalog_response_too_large');
238
- if (!response.body) {
239
- const text = await response.text();
240
- if (Buffer.byteLength(text, 'utf8') > maxBytes)
241
- throw new CatalogFetchError('codex_lb_tool_catalog_response_too_large');
242
- return parseCatalogJson(text);
243
- }
244
- const reader = response.body.getReader();
245
- const chunks = [];
246
- let total = 0;
247
- try {
248
- while (true) {
249
- const { done, value } = await reader.read();
250
- if (done)
251
- break;
252
- if (!value)
253
- continue;
254
- total += value.byteLength;
255
- if (total > maxBytes) {
256
- await reader.cancel().catch(() => undefined);
257
- throw new CatalogFetchError('codex_lb_tool_catalog_response_too_large');
258
- }
259
- chunks.push(value);
260
- }
261
- }
262
- finally {
263
- reader.releaseLock();
264
- }
265
- const bytes = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), total);
266
- return parseCatalogJson(bytes.toString('utf8'));
267
- }
268
- function parseCatalogJson(text) {
269
- try {
270
- return JSON.parse(text);
271
- }
272
- catch {
273
- throw new CatalogFetchError('codex_lb_tool_catalog_response_json_invalid');
274
- }
275
- }
276
- async function writeSecureCatalog(file, catalog, identity, upstream) {
277
- const text = `${JSON.stringify(catalog, null, 2)}\n`;
278
- const metadataPath = codexLbToolCatalogMetadataPath(file);
279
- const metadata = {
280
- schema: CODEX_LB_TOOL_CATALOG_METADATA_SCHEMA,
281
- generated_at: nowIso(),
282
- catalog_schema: CODEX_LB_TOOL_CATALOG_SCHEMA,
283
- catalog_sha256: sha256(text),
284
- identity,
285
- model_count: Array.isArray(catalog?.models) ? catalog.models.length : 0,
286
- upstream_etag: upstream.upstream_etag,
287
- upstream_last_modified: upstream.upstream_last_modified,
288
- client_version: upstream.client_version,
289
- unknown_fields_preserved: true,
290
- normalizations: ['desktop_full_responses_required']
291
- };
292
- await ensureDir(path.dirname(file));
293
- await writeTextAtomic(file, text, { mode: 0o600 });
294
- const catalogSafety = await inspectSecureRegularFile(file, CODEX_LB_TOOL_CATALOG_MAX_RESPONSE_BYTES, 'codex_lb_tool_catalog');
295
- if (!catalogSafety.ok)
296
- throw new CatalogFetchError(catalogSafety.blockers[0] || 'codex_lb_tool_catalog_file_insecure');
297
- await writeTextAtomic(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, { mode: 0o600 });
298
- const metadataSafety = await inspectSecureRegularFile(metadataPath, CODEX_LB_TOOL_CATALOG_MAX_METADATA_BYTES, 'codex_lb_tool_catalog_metadata');
299
- if (!metadataSafety.ok)
300
- throw new CatalogFetchError(metadataSafety.blockers[0] || 'codex_lb_tool_catalog_metadata_file_insecure');
301
- }
302
- async function inspectSecureRegularFile(file, maxBytes, prefix) {
303
- const stat = await fs.lstat(file).catch(() => null);
304
- if (!stat)
305
- return { ok: false, exists: false, updated_at_ms: null, size_bytes: 0, mode: null, owner_uid: null, blockers: [`${prefix}_missing`] };
306
- const blockers = [];
307
- if (!stat.isFile() || stat.isSymbolicLink())
308
- blockers.push(`${prefix}_not_regular_file`);
309
- if (stat.size > maxBytes)
310
- blockers.push(`${prefix}_too_large:${stat.size}:${maxBytes}`);
311
- const ownerUid = Number.isFinite(Number(stat.uid)) ? Number(stat.uid) : null;
312
- const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
313
- if (expectedUid !== null && ownerUid !== expectedUid)
314
- blockers.push(`${prefix}_owner_mismatch`);
315
- const mode = stat.mode & 0o777;
316
- if (process.platform !== 'win32' && mode !== 0o600)
317
- blockers.push(`${prefix}_mode_insecure:${mode.toString(8)}`);
318
- return { ok: blockers.length === 0, exists: true, updated_at_ms: stat.mtimeMs, size_bytes: stat.size, mode: mode.toString(8).padStart(3, '0'), owner_uid: ownerUid, blockers };
319
- }
320
- export function normalizeCodexLbModelRow(row, index = 0) {
321
- const cloned = structuredClone(row);
322
- for (const key of Object.keys(cloned)) {
323
- if (isSensitiveCatalogField(key))
324
- delete cloned[key];
325
- }
326
- const slug = String(cloned.slug ?? cloned.id ?? cloned.model ?? cloned.name ?? '').trim();
327
- if (!slug)
328
- throw new Error(`codex_lb_model_catalog_slug_missing:${index}`);
329
- if (!Object.prototype.hasOwnProperty.call(cloned, 'slug'))
330
- cloned.slug = slug;
331
- cloned.use_responses_lite = false;
332
- return cloned;
333
- }
334
- function isSensitiveCatalogField(field) {
335
- return /(?:^|[_-])(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|cookie|set[_-]?cookie)(?:$|[_-])/i.test(field);
336
- }
337
- function validateKnownModelFields(row, index) {
338
- const issues = [];
339
- for (const [field, allowedTypes] of Object.entries(REQUIRED_MODEL_FIELDS)) {
340
- if (!Object.prototype.hasOwnProperty.call(row, field)) {
341
- issues.push(`codex_lb_model_catalog_required_field_missing:${index}:${field}`);
342
- continue;
343
- }
344
- if (!allowedTypes.includes(valueType(row[field])))
345
- issues.push(`codex_lb_model_catalog_field_type_invalid:${index}:${field}`);
346
- }
347
- if (typeof row.slug === 'string' && !row.slug.trim())
348
- issues.push(`codex_lb_model_catalog_field_empty:${index}:slug`);
349
- if (typeof row.display_name === 'string' && !row.display_name.trim())
350
- issues.push(`codex_lb_model_catalog_field_empty:${index}:display_name`);
351
- if (Array.isArray(row.supported_reasoning_levels) && row.supported_reasoning_levels.some((entry) => !isPlainObject(entry) || typeof entry.effort !== 'string' || !entry.effort.trim())) {
352
- issues.push(`codex_lb_model_catalog_reasoning_level_invalid:${index}`);
353
- }
354
- return issues;
355
- }
356
- function catalogTransportBlocker(baseUrl) {
357
- const blocker = codexLbBaseUrlSecurityBlocker(baseUrl);
358
- if (blocker)
359
- return blocker;
360
- try {
361
- const url = new URL(baseUrl);
362
- if (url.search || url.hash)
363
- return 'codex_lb_base_url_query_or_fragment_forbidden';
364
- return null;
365
- }
366
- catch {
367
- return 'codex_lb_invalid_base_url';
368
- }
369
- }
370
- function catalogIdentity(baseUrl, apiKey) {
371
- return {
372
- origin: new URL(baseUrl).origin,
373
- base_url_sha256: sha256(baseUrl),
374
- api_key_sha256: sha256(apiKey),
375
- contract: 'codex-model-catalog-pass-through.v2'
376
- };
377
- }
378
- function sameCatalogIdentity(actual, expected) {
379
- return actual?.origin === expected.origin
380
- && actual?.base_url_sha256 === expected.base_url_sha256
381
- && actual?.api_key_sha256 === expected.api_key_sha256
382
- && actual?.contract === expected.contract;
383
- }
384
- function blockedResult(outputPath, blockers) {
385
- return {
386
- schema: CODEX_LB_TOOL_CATALOG_SCHEMA,
387
- generated_at: nowIso(),
388
- ok: false,
389
- status: 'blocked',
390
- path: outputPath,
391
- exists: false,
392
- fetched: false,
393
- required: true,
394
- model_count: 0,
395
- gpt56_models: [],
396
- patched_models: [],
397
- tools_transport: 'unverified',
398
- blockers: uniqueBounded(blockers)
399
- };
400
- }
401
- function incompatibleInspection(file, safety, blockers) {
402
- return {
403
- schema: CODEX_LB_TOOL_CATALOG_SCHEMA,
404
- ok: false,
405
- path: file,
406
- exists: safety.exists === true,
407
- updated_at_ms: safety.updated_at_ms || null,
408
- size_bytes: safety.size_bytes || 0,
409
- mode: safety.mode || null,
410
- owner_uid: safety.owner_uid ?? null,
411
- metadata_path: codexLbToolCatalogMetadataPath(file),
412
- identity_verified: false,
413
- cache_origin: null,
414
- model_count: 0,
415
- gpt56_models: [],
416
- patched_models: [],
417
- tools_transport: 'unverified',
418
- blockers: uniqueBounded(blockers)
419
- };
420
- }
421
- function cacheIsFresh(updatedAtMs, now, maxAgeMs) {
422
- const updated = Number(updatedAtMs);
423
- const age = now - updated;
424
- return Number.isFinite(updated) && Number.isFinite(age) && age >= 0 && age <= maxAgeMs;
425
- }
426
- function boundedPositiveInt(value, fallback) {
427
- const parsed = Number(value);
428
- return Number.isFinite(parsed) && parsed > 0 ? Math.max(1, Math.floor(parsed)) : fallback;
429
- }
430
- function valueType(value) {
431
- if (value === null)
432
- return 'null';
433
- if (Array.isArray(value))
434
- return 'array';
435
- return typeof value === 'object' ? 'object' : typeof value;
436
- }
437
- function isPlainObject(value) {
438
- return Boolean(value && typeof value === 'object' && !Array.isArray(value));
439
- }
440
- function uniqueBounded(values) {
441
- return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))].slice(0, 64);
442
- }
443
- function safeError(error, secrets = []) {
444
- let text = String(error instanceof Error ? error.message : error || 'unknown');
445
- for (const secret of secrets.filter((value) => value.length >= 4))
446
- text = text.split(secret).join('<redacted>');
447
- return text.replace(/(?:sk|key|token|secret)[-_a-z0-9]*/gi, '<redacted>').slice(0, 300);
448
- }
449
- class CatalogFetchError extends Error {
450
- code;
451
- constructor(code) {
452
- super(code);
453
- this.code = code;
454
- this.name = 'CatalogFetchError';
455
- }
456
- }