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,13 +1,11 @@
1
1
  import dns from 'node:dns/promises';
2
2
  import net from 'node:net';
3
- import { DesktopBridgeError } from './types.js';
4
- import { assertProviderModeModel } from '../../codex-app/provider-mode.js';
5
- import { parseProviderPolicySnapshot, stableArchitectureHash, } from '../../architecture-hardening/contracts/contracts.js';
6
- import { decideProviderRoute } from '../provider-routing/provider-router.js';
7
- import { decideChildSelection } from '../../codex-app/child-policy/child-policy.js';
8
- import { assertSessionRequest, resumeSessionPin, sessionPinHash, } from '../../codex-app/session-policy/session-pinning.js';
3
+ import { canonicalizeBridgeModelId, normalizeBridgeUpstreamModelId, } from '../route-index.js';
4
+ import { DESKTOP_BRIDGE_CLIENT_PATH_PREFIX, DesktopBridgeError } from './types.js';
9
5
  const MIN_HIGH_PORT = 49_152;
10
6
  const MAX_PORT = 65_535;
7
+ const MAX_SESSION_PINS = 10_000;
8
+ const sessionPinMutationQueues = new WeakMap();
11
9
  const FORBIDDEN_REMOTE_ADDRESSES = new net.BlockList();
12
10
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('0.0.0.0', 8, 'ipv4');
13
11
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('10.0.0.0', 8, 'ipv4');
@@ -15,12 +13,7 @@ FORBIDDEN_REMOTE_ADDRESSES.addSubnet('100.64.0.0', 10, 'ipv4');
15
13
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('127.0.0.0', 8, 'ipv4');
16
14
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('169.254.0.0', 16, 'ipv4');
17
15
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('172.16.0.0', 12, 'ipv4');
18
- FORBIDDEN_REMOTE_ADDRESSES.addSubnet('192.0.0.0', 24, 'ipv4');
19
- FORBIDDEN_REMOTE_ADDRESSES.addSubnet('192.0.2.0', 24, 'ipv4');
20
16
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('192.168.0.0', 16, 'ipv4');
21
- FORBIDDEN_REMOTE_ADDRESSES.addSubnet('198.18.0.0', 15, 'ipv4');
22
- FORBIDDEN_REMOTE_ADDRESSES.addSubnet('198.51.100.0', 24, 'ipv4');
23
- FORBIDDEN_REMOTE_ADDRESSES.addSubnet('203.0.113.0', 24, 'ipv4');
24
17
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('224.0.0.0', 4, 'ipv4');
25
18
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('240.0.0.0', 4, 'ipv4');
26
19
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('::', 128, 'ipv6');
@@ -28,133 +21,103 @@ FORBIDDEN_REMOTE_ADDRESSES.addSubnet('::1', 128, 'ipv6');
28
21
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('fc00::', 7, 'ipv6');
29
22
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('fe80::', 10, 'ipv6');
30
23
  FORBIDDEN_REMOTE_ADDRESSES.addSubnet('ff00::', 8, 'ipv6');
31
- FORBIDDEN_REMOTE_ADDRESSES.addSubnet('2001:db8::', 32, 'ipv6');
32
24
  export function assertLoopbackPeer(address) {
33
25
  const normalized = String(address || '').replace(/^::ffff:/i, '');
34
26
  if (!net.isIP(normalized))
35
27
  throw new DesktopBridgeError('bridge_peer_not_ip');
36
- if (normalized !== '127.0.0.1' && normalized !== '::1') {
28
+ if (normalized !== '127.0.0.1' && normalized !== '::1')
37
29
  throw new DesktopBridgeError('bridge_non_loopback_peer');
38
- }
39
30
  }
40
31
  export function assertLoopbackListenHost(host) {
41
- if (host !== '127.0.0.1' && host !== '::1') {
32
+ if (host !== '127.0.0.1' && host !== '::1')
42
33
  throw new DesktopBridgeError('bridge_listen_host_not_loopback');
43
- }
44
34
  }
45
35
  export function assertAllowedPath(pathname, prefixes) {
46
36
  if (!pathname.startsWith('/') || pathname.includes('\\') || /%(?:2f|5c)/i.test(pathname)) {
47
37
  throw new DesktopBridgeError('bridge_path_invalid');
48
38
  }
49
- const allowed = prefixes.some((prefix) => {
50
- if (!prefix.startsWith('/'))
51
- return false;
52
- if (prefix.endsWith('/'))
53
- return pathname.startsWith(prefix);
54
- return pathname === prefix || pathname.startsWith(`${prefix}/`);
55
- });
39
+ const allowed = prefixes.some((prefix) => prefix.endsWith('/')
40
+ ? pathname.startsWith(prefix)
41
+ : pathname === prefix || pathname.startsWith(`${prefix}/`));
56
42
  if (!allowed)
57
43
  throw new DesktopBridgeError('bridge_path_not_allowed');
58
44
  }
45
+ export function desktopBridgeClientPath(capability, canonicalPath) {
46
+ if (!/^[A-Za-z0-9_-]{43}$/.test(capability))
47
+ throw new DesktopBridgeError('bridge_client_capability_invalid');
48
+ if (!canonicalPath.startsWith('/') || canonicalPath.includes('\\') || /[\r\n\0]/.test(canonicalPath)) {
49
+ throw new DesktopBridgeError('bridge_request_target_invalid');
50
+ }
51
+ return `${DESKTOP_BRIDGE_CLIENT_PATH_PREFIX}/${capability}${canonicalPath}`;
52
+ }
59
53
  function headerValues(value) {
60
- return (Array.isArray(value) ? value : value === undefined ? [] : [value])
61
- .map((entry) => entry.trim())
62
- .filter(Boolean);
54
+ return (Array.isArray(value) ? value : value === undefined ? [] : [value]).map((v) => v.trim()).filter(Boolean);
63
55
  }
64
- function singlePolicyHeader(headers, name) {
65
- const values = headerValues(headers[name]);
56
+ export function singleBridgeHeader(headers, name) {
57
+ const values = headerValues(headers[name.toLowerCase()]);
66
58
  if (values.length > 1)
67
59
  throw new DesktopBridgeError('bridge_policy_header_ambiguous');
68
60
  return values[0] || null;
69
61
  }
70
- export function assertDesktopBridgeRequestPolicy(input) {
71
- const { config } = input;
72
- if (!config.providerMode)
73
- return;
74
- const requestedMode = singlePolicyHeader(input.headers, 'x-sks-provider-mode');
75
- if (requestedMode && requestedMode !== config.providerMode) {
76
- throw new DesktopBridgeError('bridge_provider_route_cross_mode_forbidden');
77
- }
78
- const policy = config.providerPolicy;
79
- if (!policy) {
80
- if (input.model !== undefined) {
81
- try {
82
- assertProviderModeModel(config.providerMode, input.model, config.allowedModels || []);
83
- }
84
- catch (error) {
85
- throw new DesktopBridgeError(`bridge_${error.message}`);
86
- }
62
+ function codexMetadataObject(value, source) {
63
+ if (value === undefined || value === null)
64
+ return null;
65
+ let parsed = value;
66
+ if (source === 'header') {
67
+ if (typeof value !== 'string' || Buffer.byteLength(value) > 16 * 1024) {
68
+ throw new DesktopBridgeError('bridge_codex_turn_metadata_invalid');
69
+ }
70
+ try {
71
+ parsed = JSON.parse(value);
72
+ }
73
+ catch {
74
+ throw new DesktopBridgeError('bridge_codex_turn_metadata_invalid');
87
75
  }
88
- if (config.requireSessionPin)
89
- throw new DesktopBridgeError('bridge_session_policy_missing');
90
- return;
91
76
  }
92
- const credential = config.credentialReadiness || {
93
- status: 'unavailable',
94
- reason_code: 'bridge_credential_readiness_missing',
95
- };
96
- if (input.model !== undefined) {
97
- const route = decideProviderRoute({
98
- policy,
99
- credential,
100
- requestedMode: (requestedMode || config.providerMode),
101
- model: String(input.model || ''),
102
- });
103
- if (!route.ok)
104
- throw new DesktopBridgeError(`bridge_${route.blockers[0] || 'provider_route_blocked'}`);
105
- }
106
- const sessionId = singlePolicyHeader(input.headers, 'x-sks-session-id');
107
- if (!sessionId) {
108
- if (config.requireSessionPin)
109
- throw new DesktopBridgeError('bridge_session_pin_required');
110
- return;
111
- }
112
- const pin = (config.sessionPins || []).find((entry) => entry.session_id === sessionId);
113
- if (!pin)
114
- throw new DesktopBridgeError('bridge_session_pin_unknown');
115
- const resume = resumeSessionPin(pin, policy);
116
- if (!resume.ok)
117
- throw new DesktopBridgeError(`bridge_${resume.blocker || 'session_pin_blocked'}`);
118
- const childFlag = singlePolicyHeader(input.headers, 'x-sks-child-request');
119
- if (childFlag && childFlag !== '1')
120
- throw new DesktopBridgeError('bridge_child_request_header_invalid');
121
- const childHash = singlePolicyHeader(input.headers, 'x-sks-child-policy-hash') || policy.child_policy_hash;
122
- const requestModel = String(input.model === undefined ? pin.model : input.model || '');
123
- try {
124
- assertSessionRequest(pin, {
125
- mode: config.providerMode,
126
- model: childFlag === '1' ? pin.model : requestModel,
127
- childPolicyHash: childHash,
128
- });
77
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
78
+ throw new DesktopBridgeError(source === 'header'
79
+ ? 'bridge_codex_turn_metadata_invalid'
80
+ : 'bridge_codex_client_metadata_invalid');
129
81
  }
130
- catch (error) {
131
- throw new DesktopBridgeError(`bridge_${error.message}`);
132
- }
133
- if (childFlag !== '1')
134
- return;
135
- const childPolicy = config.childPolicy;
136
- if (!childPolicy)
137
- throw new DesktopBridgeError('bridge_child_policy_missing');
138
- const parentHash = singlePolicyHeader(input.headers, 'x-sks-parent-snapshot-hash');
139
- if (!parentHash || parentHash !== sessionPinHash(pin)) {
140
- throw new DesktopBridgeError('bridge_child_parent_snapshot_mismatch');
141
- }
142
- const requestedChildModel = singlePolicyHeader(input.headers, 'x-sks-child-model') || requestModel;
143
- const child = decideChildSelection({ session: pin, policy: childPolicy, requestedModel: requestedChildModel });
144
- if (!child.ok)
145
- throw new DesktopBridgeError(`bridge_${child.blockers[0] || 'child_policy_blocked'}`);
82
+ return parsed;
83
+ }
84
+ function oneCodexIdentity(values, kind) {
85
+ const canonical = values
86
+ .filter((value) => value !== undefined && value !== null && value !== '')
87
+ .map(canonicalSessionId);
88
+ if (new Set(canonical).size > 1)
89
+ throw new DesktopBridgeError('bridge_codex_session_identity_conflict');
90
+ return canonical[0] || null;
91
+ }
92
+ export function resolveCodexSessionIdentity(headers, payload = null) {
93
+ const turnMetadataValue = singleBridgeHeader(headers, 'x-codex-turn-metadata');
94
+ const turnMetadata = turnMetadataValue === null ? null : codexMetadataObject(turnMetadataValue, 'header');
95
+ const clientMetadataValue = payload?.client_metadata;
96
+ const clientMetadata = clientMetadataValue === undefined ? null : codexMetadataObject(clientMetadataValue, 'body');
97
+ const threadId = oneCodexIdentity([
98
+ singleBridgeHeader(headers, 'thread-id'),
99
+ turnMetadata?.thread_id,
100
+ clientMetadata?.thread_id,
101
+ ], 'thread');
102
+ const sessionId = oneCodexIdentity([
103
+ singleBridgeHeader(headers, 'session-id'),
104
+ turnMetadata?.session_id,
105
+ clientMetadata?.session_id,
106
+ ], 'session');
107
+ if (sessionId && !threadId)
108
+ throw new DesktopBridgeError('bridge_codex_thread_id_missing');
109
+ if (threadId && sessionId && threadId !== sessionId) {
110
+ throw new DesktopBridgeError('bridge_codex_session_identity_mismatch');
111
+ }
112
+ return { thread_id: threadId, session_id: sessionId };
146
113
  }
147
114
  function comparableOrigin(value, referer) {
148
115
  if (value === 'null')
149
- return 'null';
116
+ return value;
150
117
  try {
151
118
  const parsed = new URL(value);
152
- if (referer) {
153
- return parsed.origin !== 'null' ? parsed.origin : `${parsed.protocol}//${parsed.host}`;
154
- }
155
- if ((parsed.pathname !== '/' && parsed.pathname !== '') || parsed.search || parsed.hash) {
156
- throw new Error('origin_has_path');
157
- }
119
+ if (!referer && ((parsed.pathname !== '/' && parsed.pathname !== '') || parsed.search || parsed.hash))
120
+ throw new Error();
158
121
  return parsed.origin !== 'null' ? parsed.origin : `${parsed.protocol}//${parsed.host}`;
159
122
  }
160
123
  catch {
@@ -167,140 +130,180 @@ export function normalizeAllowedOrigin(value) {
167
130
  export function assertAllowedOrigin(headers, allowedOrigins) {
168
131
  const origins = headerValues(headers.origin);
169
132
  const referers = headerValues(headers.referer);
170
- if (origins.length === 0 && referers.length === 0)
171
- return;
172
133
  if (origins.length > 1 || referers.length > 1)
173
134
  throw new DesktopBridgeError('bridge_origin_forbidden');
174
135
  const allowed = new Set(allowedOrigins.map(normalizeAllowedOrigin));
175
- for (const origin of origins) {
176
- if (!allowed.has(comparableOrigin(origin, false)))
136
+ for (const value of origins)
137
+ if (!allowed.has(comparableOrigin(value, false)))
177
138
  throw new DesktopBridgeError('bridge_origin_forbidden');
178
- }
179
- for (const referer of referers) {
180
- if (!allowed.has(comparableOrigin(referer, true)))
139
+ for (const value of referers)
140
+ if (!allowed.has(comparableOrigin(value, true)))
181
141
  throw new DesktopBridgeError('bridge_origin_forbidden');
182
- }
183
142
  }
184
143
  export function assertWebSocketUpgrade(headers, method) {
185
- const connection = headerValues(headers.connection).join(',').toLowerCase().split(',').map((part) => part.trim());
186
- const upgrade = headerValues(headers.upgrade);
187
- const key = headerValues(headers['sec-websocket-key']);
188
- const version = headerValues(headers['sec-websocket-version']);
189
- if (method !== 'GET'
190
- || !connection.includes('upgrade')
191
- || upgrade.length !== 1
192
- || upgrade[0]?.toLowerCase() !== 'websocket'
193
- || key.length !== 1
194
- || version.length !== 1) {
144
+ const connection = headerValues(headers.connection).join(',').toLowerCase().split(',').map((v) => v.trim());
145
+ if (method !== 'GET' || !connection.includes('upgrade') || headerValues(headers.upgrade)[0]?.toLowerCase() !== 'websocket'
146
+ || headerValues(headers['sec-websocket-key']).length !== 1 || headerValues(headers['sec-websocket-version']).length !== 1) {
195
147
  throw new DesktopBridgeError('bridge_websocket_upgrade_invalid');
196
148
  }
197
149
  }
198
- function stripIpv6Brackets(hostname) {
199
- return hostname.replace(/^\[/, '').replace(/\]$/, '');
200
- }
201
- function isLoopbackAddress(address) {
202
- const normalized = address.replace(/^::ffff:/i, '');
203
- if (normalized === '::1')
204
- return true;
205
- if (net.isIP(normalized) !== 4)
206
- return false;
207
- return Number(normalized.split('.')[0]) === 127;
208
- }
209
- function isExplicitLoopbackHostname(hostname) {
210
- const normalized = stripIpv6Brackets(hostname).replace(/\.$/, '').toLowerCase();
211
- return normalized === 'localhost' || isLoopbackAddress(normalized);
212
- }
213
- function isForbiddenRemoteAddress(address, family) {
214
- const dottedMapped = address.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)?.[1];
215
- const hexMapped = address.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
216
- const mapped = dottedMapped || (hexMapped
217
- ? [
218
- Number.parseInt(hexMapped[1] || '0', 16) >> 8,
219
- Number.parseInt(hexMapped[1] || '0', 16) & 0xff,
220
- Number.parseInt(hexMapped[2] || '0', 16) >> 8,
221
- Number.parseInt(hexMapped[2] || '0', 16) & 0xff,
222
- ].join('.')
223
- : undefined);
224
- if (mapped)
225
- return FORBIDDEN_REMOTE_ADDRESSES.check(mapped, 'ipv4');
226
- return FORBIDDEN_REMOTE_ADDRESSES.check(address, family === 4 ? 'ipv4' : 'ipv6');
150
+ function canonicalPublicModel(value) {
151
+ const model = canonicalizeBridgeModelId(value);
152
+ if (!model)
153
+ throw new DesktopBridgeError('catalog_model_route_missing');
154
+ return model;
227
155
  }
228
- export function validateDesktopBridgeConfig(config) {
229
- assertLoopbackListenHost(config.listenHost);
230
- if (!Number.isInteger(config.listenPort) || config.listenPort < MIN_HIGH_PORT || config.listenPort > MAX_PORT) {
231
- throw new DesktopBridgeError('bridge_listen_port_not_high');
232
- }
233
- if (!config.gatewayKey || /[\r\n\0]/.test(config.gatewayKey)) {
234
- throw new DesktopBridgeError('bridge_gateway_key_invalid');
235
- }
236
- if (config.providerMode !== undefined) {
237
- if (config.providerMode !== 'codex-lb' && config.providerMode !== 'openrouter') {
238
- throw new DesktopBridgeError('bridge_provider_mode_invalid');
156
+ export function resolveBridgeRequestRoute(request, policy, pins) {
157
+ const publicModel = canonicalPublicModel(request.public_model);
158
+ const sessionId = request.session_id ? canonicalSessionId(request.session_id) : null;
159
+ const pin = sessionId ? pins.find((entry) => entry.thread_id === sessionId) || null : null;
160
+ if (pin && pin.public_model === publicModel) {
161
+ if (pin.public_model !== publicModel
162
+ || pin.catalog_generation !== policy.catalog_generation
163
+ || pin.route_policy_generation !== policy.policy_generation) {
164
+ throw new DesktopBridgeError('session_pin_route_unavailable');
239
165
  }
240
- if (!Array.isArray(config.allowedModels) || config.allowedModels.length === 0) {
241
- throw new DesktopBridgeError('bridge_provider_model_allowlist_empty');
166
+ const current = policy.model_routes[publicModel];
167
+ if (!current || current.provider_id !== pin.provider_id || current.upstream_model !== pin.upstream_model) {
168
+ throw new DesktopBridgeError('session_pin_route_unavailable');
242
169
  }
243
- for (const model of config.allowedModels) {
244
- try {
245
- assertProviderModeModel(config.providerMode, model, config.allowedModels);
246
- }
247
- catch (error) {
248
- throw new DesktopBridgeError(`bridge_${error.message}`);
249
- }
250
- }
251
- if (config.providerPolicy) {
252
- const policy = parseProviderPolicySnapshot(config.providerPolicy);
253
- if (policy.mode !== config.providerMode)
254
- throw new DesktopBridgeError('bridge_provider_policy_mode_mismatch');
255
- if (stableArchitectureHash([...policy.allowed_models].sort()) !== stableArchitectureHash([...(config.allowedModels || [])].sort())) {
256
- throw new DesktopBridgeError('bridge_provider_policy_model_allowlist_mismatch');
257
- }
258
- if (!config.credentialReadiness)
259
- throw new DesktopBridgeError('bridge_credential_readiness_missing');
260
- if (config.childPolicy) {
261
- if (config.childPolicy.mode !== policy.mode)
262
- throw new DesktopBridgeError('bridge_child_policy_mode_mismatch');
263
- if (config.childPolicy.policy_hash !== policy.child_policy_hash)
264
- throw new DesktopBridgeError('bridge_child_policy_snapshot_mismatch');
265
- }
266
- const sessionIds = new Set();
267
- for (const pin of config.sessionPins || []) {
268
- if (!pin || pin.schema !== 'sks.session-pin.v1')
269
- throw new DesktopBridgeError('bridge_session_pin_invalid');
270
- if (sessionIds.has(pin.session_id))
271
- throw new DesktopBridgeError('bridge_session_pin_duplicate');
272
- sessionIds.add(pin.session_id);
273
- const resume = resumeSessionPin(pin, policy);
274
- if (!resume.ok)
275
- throw new DesktopBridgeError(`bridge_${resume.blocker || 'session_pin_invalid'}`);
276
- }
277
- }
278
- else if (config.requireSessionPin) {
279
- throw new DesktopBridgeError('bridge_session_policy_missing');
170
+ return {
171
+ provider_id: pin.provider_id,
172
+ public_model: publicModel,
173
+ upstream_model: pin.upstream_model,
174
+ catalog_generation: pin.catalog_generation,
175
+ route_policy_generation: pin.route_policy_generation,
176
+ session_pin: pin,
177
+ };
178
+ }
179
+ const route = policy.model_routes[publicModel];
180
+ if (!route)
181
+ throw new DesktopBridgeError('catalog_model_route_missing');
182
+ const nextPin = sessionId ? {
183
+ thread_id: sessionId,
184
+ provider_id: route.provider_id,
185
+ public_model: publicModel,
186
+ upstream_model: route.upstream_model,
187
+ catalog_generation: policy.catalog_generation,
188
+ route_policy_generation: policy.policy_generation,
189
+ created_at: new Date().toISOString(),
190
+ } : null;
191
+ return {
192
+ provider_id: route.provider_id,
193
+ public_model: publicModel,
194
+ upstream_model: route.upstream_model,
195
+ catalog_generation: policy.catalog_generation,
196
+ route_policy_generation: policy.policy_generation,
197
+ session_pin: nextPin,
198
+ };
199
+ }
200
+ export function assertDesktopBridgeRouteContext(request, config) {
201
+ const resolver = config.resolveRequestRoute || resolveBridgeRequestRoute;
202
+ const route = resolver(request, config.routePolicy, config.providerSessionPins);
203
+ const policy = config.routePolicy;
204
+ const expected = policy.model_routes[route.public_model];
205
+ if (!expected || expected.provider_id !== route.provider_id || expected.upstream_model !== route.upstream_model) {
206
+ throw new DesktopBridgeError('catalog_model_route_missing');
207
+ }
208
+ if (route.catalog_generation !== policy.catalog_generation || route.route_policy_generation !== policy.policy_generation) {
209
+ throw new DesktopBridgeError('session_pin_route_unavailable');
210
+ }
211
+ if (request.session_id) {
212
+ const sessionId = canonicalSessionId(request.session_id);
213
+ if (!route.session_pin
214
+ || route.session_pin.thread_id !== sessionId
215
+ || route.session_pin.provider_id !== route.provider_id
216
+ || route.session_pin.public_model !== route.public_model
217
+ || route.session_pin.upstream_model !== route.upstream_model
218
+ || route.session_pin.catalog_generation !== route.catalog_generation
219
+ || route.session_pin.route_policy_generation !== route.route_policy_generation) {
220
+ throw new DesktopBridgeError('bridge_session_pin_invalid');
280
221
  }
281
222
  }
282
- if (config.gatewayAuthTransport !== 'x-codex-lb-api-key' && config.gatewayAuthTransport !== 'authorization-bearer-compat') {
283
- throw new DesktopBridgeError('bridge_gateway_auth_transport_invalid');
284
- }
285
- if (!Array.isArray(config.allowedPathPrefixes) || config.allowedPathPrefixes.length === 0) {
286
- throw new DesktopBridgeError('bridge_path_allowlist_empty');
223
+ else if (route.session_pin) {
224
+ throw new DesktopBridgeError('bridge_session_pin_invalid');
287
225
  }
288
- if (!Number.isFinite(config.connectTimeoutMs) || config.connectTimeoutMs < 100 || config.connectTimeoutMs > 120_000) {
289
- throw new DesktopBridgeError('bridge_connect_timeout_invalid');
226
+ const provider = config.providers[route.provider_id];
227
+ if (!provider || !provider.enabled)
228
+ throw new DesktopBridgeError('bridge_provider_route_unavailable');
229
+ if (provider.credential_state !== 'ready')
230
+ throw new DesktopBridgeError(`${route.provider_id.replace('-', '_')}_credential_unavailable`);
231
+ if (provider.source_catalog_generation !== null && !provider.source_catalog_generation.trim()) {
232
+ throw new DesktopBridgeError('bridge_provider_source_catalog_generation_invalid');
290
233
  }
291
- if (!Number.isFinite(config.idleTimeoutMs) || config.idleTimeoutMs < 1_000 || config.idleTimeoutMs > 86_400_000) {
292
- throw new DesktopBridgeError('bridge_idle_timeout_invalid');
234
+ if (!provider.allowed_origins.map(normalizeAllowedOrigin).includes(provider.remote.origin)) {
235
+ throw new DesktopBridgeError('bridge_provider_origin_forbidden');
293
236
  }
294
- for (const prefix of config.allowedPathPrefixes) {
295
- if (!prefix.startsWith('/') || prefix.includes('?') || prefix.includes('#') || prefix.includes('\\')) {
296
- throw new DesktopBridgeError('bridge_path_allowlist_invalid');
237
+ return route;
238
+ }
239
+ export async function resolveAndBindDesktopBridgeRouteContext(request, config) {
240
+ return withSessionPinMutation(config, async () => {
241
+ const route = assertDesktopBridgeRouteContext(request, config);
242
+ const pin = route.session_pin;
243
+ if (!pin)
244
+ return route;
245
+ const existing = config.providerSessionPins.find((entry) => entry.thread_id === pin.thread_id) || null;
246
+ if (existing && sameSessionPin(existing, pin))
247
+ return route;
248
+ const nextPins = [
249
+ ...config.providerSessionPins.filter((entry) => entry.thread_id !== pin.thread_id),
250
+ pin,
251
+ ];
252
+ const retainedPins = nextPins.length > MAX_SESSION_PINS
253
+ ? nextPins
254
+ .sort((left, right) => Date.parse(left.created_at) - Date.parse(right.created_at)
255
+ || left.thread_id.localeCompare(right.thread_id))
256
+ .slice(nextPins.length - MAX_SESSION_PINS)
257
+ : nextPins;
258
+ const persistedPins = retainedPins.sort((left, right) => left.thread_id.localeCompare(right.thread_id));
259
+ if (config.persistProviderSessionPins) {
260
+ await config.persistProviderSessionPins(persistedPins);
297
261
  }
262
+ config.providerSessionPins = persistedPins;
263
+ return route;
264
+ });
265
+ }
266
+ async function withSessionPinMutation(config, action) {
267
+ const previous = sessionPinMutationQueues.get(config) || Promise.resolve();
268
+ const run = previous.catch(() => undefined).then(action);
269
+ const tail = run.then(() => undefined, () => undefined);
270
+ sessionPinMutationQueues.set(config, tail);
271
+ try {
272
+ return await run;
298
273
  }
299
- for (const origin of config.allowedOrigins)
300
- normalizeAllowedOrigin(origin);
274
+ finally {
275
+ if (sessionPinMutationQueues.get(config) === tail)
276
+ sessionPinMutationQueues.delete(config);
277
+ }
278
+ }
279
+ function sameSessionPin(left, right) {
280
+ return left.thread_id === right.thread_id
281
+ && left.provider_id === right.provider_id
282
+ && left.public_model === right.public_model
283
+ && left.upstream_model === right.upstream_model
284
+ && left.catalog_generation === right.catalog_generation
285
+ && left.route_policy_generation === right.route_policy_generation;
286
+ }
287
+ function canonicalSessionId(value) {
288
+ const sessionId = typeof value === 'string' ? value.trim() : '';
289
+ if (!sessionId || sessionId.length > 256 || !/^[A-Za-z0-9._:/-]+$/.test(sessionId)) {
290
+ throw new DesktopBridgeError('bridge_session_id_invalid');
291
+ }
292
+ return sessionId;
293
+ }
294
+ function stripIpv6Brackets(hostname) { return hostname.replace(/^\[/, '').replace(/\]$/, ''); }
295
+ function isLoopbackAddress(address) {
296
+ const value = address.replace(/^::ffff:/i, '');
297
+ return value === '::1' || (net.isIP(value) === 4 && Number(value.split('.')[0]) === 127);
298
+ }
299
+ function isExplicitLoopbackHostname(hostname) {
300
+ const value = stripIpv6Brackets(hostname).replace(/\.$/, '').toLowerCase();
301
+ return value === 'localhost' || isLoopbackAddress(value);
302
+ }
303
+ function validateRemoteUrl(raw) {
301
304
  let remote;
302
305
  try {
303
- remote = new URL(config.remoteBaseUrl);
306
+ remote = new URL(raw);
304
307
  }
305
308
  catch {
306
309
  throw new DesktopBridgeError('bridge_remote_url_invalid');
@@ -322,74 +325,170 @@ const defaultLookup = async (hostname) => {
322
325
  return { address: row.address, family: row.family };
323
326
  });
324
327
  };
325
- export async function prepareDesktopBridgeConfig(config, lookup = defaultLookup) {
326
- const remote = validateDesktopBridgeConfig(config);
328
+ export async function resolveDesktopBridgeRemoteTarget(raw, lookup = defaultLookup) {
329
+ const remote = validateRemoteUrl(raw);
327
330
  const hostname = stripIpv6Brackets(remote.hostname);
328
- const literalFamily = net.isIP(hostname);
331
+ const family = net.isIP(hostname);
329
332
  let addresses;
330
333
  try {
331
- addresses = literalFamily
332
- ? [{ address: hostname, family: literalFamily }]
333
- : await lookup(hostname);
334
+ addresses = family ? [{ address: hostname, family: family }] : await lookup(hostname);
334
335
  }
335
336
  catch (error) {
336
337
  throw new DesktopBridgeError('bridge_remote_dns_failed', { cause: error });
337
338
  }
338
- if (addresses.length === 0)
339
- throw new DesktopBridgeError('bridge_remote_dns_empty');
340
- if (addresses.some((row) => net.isIP(row.address) !== row.family)) {
339
+ if (!addresses.length || addresses.some((row) => net.isIP(row.address) !== row.family))
341
340
  throw new DesktopBridgeError('bridge_remote_dns_invalid');
342
- }
343
- const explicitLoopback = isExplicitLoopbackHostname(hostname);
344
- if (explicitLoopback) {
345
- if (addresses.some((row) => !isLoopbackAddress(row.address))) {
346
- throw new DesktopBridgeError('bridge_remote_dns_rebinding_blocked');
347
- }
348
- }
349
- else if (addresses.some((row) => isForbiddenRemoteAddress(row.address, row.family))) {
341
+ const loopback = isExplicitLoopbackHostname(hostname);
342
+ if (loopback && addresses.some((row) => !isLoopbackAddress(row.address)))
343
+ throw new DesktopBridgeError('bridge_remote_dns_rebinding_blocked');
344
+ if (!loopback && addresses.some((row) => FORBIDDEN_REMOTE_ADDRESSES.check(row.address, row.family === 4 ? 'ipv4' : 'ipv6'))) {
350
345
  throw new DesktopBridgeError('bridge_remote_dns_private_address');
351
346
  }
352
347
  const selected = addresses[0];
353
348
  if (!selected)
354
349
  throw new DesktopBridgeError('bridge_remote_dns_empty');
355
- const port = Number(remote.port || (remote.protocol === 'https:' ? 443 : 80));
356
- const remoteTarget = {
357
- baseUrl: remote.toString().replace(/\/$/, ''),
358
- origin: remote.origin,
359
- hostname,
360
- port,
361
- secure: remote.protocol === 'https:',
362
- address: selected.address,
363
- family: selected.family,
364
- ...(literalFamily ? {} : { tlsServername: hostname }),
350
+ const target = {
351
+ baseUrl: remote.toString().replace(/\/$/, ''), origin: remote.origin, hostname,
352
+ port: Number(remote.port || (remote.protocol === 'https:' ? 443 : 80)), secure: remote.protocol === 'https:',
353
+ address: selected.address, family: selected.family, ...(family ? {} : { tlsServername: hostname }),
365
354
  };
366
- return { ...config, remoteBaseUrl: remoteTarget.baseUrl, remote: remoteTarget };
355
+ return target;
367
356
  }
368
- export function validatePreparedDesktopBridgeConfig(config) {
369
- const remoteUrl = validateDesktopBridgeConfig(config);
370
- const expectedHostname = stripIpv6Brackets(remoteUrl.hostname);
371
- const expectedPort = Number(remoteUrl.port || (remoteUrl.protocol === 'https:' ? 443 : 80));
372
- if (config.remote.origin !== remoteUrl.origin
373
- || config.remote.hostname !== expectedHostname
374
- || config.remote.port !== expectedPort
375
- || config.remote.secure !== (remoteUrl.protocol === 'https:')
376
- || net.isIP(config.remote.address) !== config.remote.family) {
377
- throw new DesktopBridgeError('bridge_prepared_config_invalid');
378
- }
379
- if (isExplicitLoopbackHostname(expectedHostname)) {
380
- if (!isLoopbackAddress(config.remote.address)) {
381
- throw new DesktopBridgeError('bridge_remote_dns_rebinding_blocked');
357
+ async function prepareProvider(provider, lookup) {
358
+ const remote = validateRemoteUrl(provider.base_url);
359
+ const hostname = stripIpv6Brackets(remote.hostname);
360
+ if (!provider.allowed_origins.map(normalizeAllowedOrigin).includes(remote.origin))
361
+ throw new DesktopBridgeError('bridge_provider_origin_forbidden');
362
+ if (!provider.enabled) {
363
+ return {
364
+ ...provider,
365
+ base_url: remote.toString().replace(/\/$/, ''),
366
+ remote: {
367
+ baseUrl: remote.toString().replace(/\/$/, ''), origin: remote.origin, hostname,
368
+ port: Number(remote.port || (remote.protocol === 'https:' ? 443 : 80)), secure: remote.protocol === 'https:',
369
+ address: '0.0.0.0', family: 4, ...(net.isIP(hostname) ? {} : { tlsServername: hostname }),
370
+ },
371
+ };
372
+ }
373
+ const target = await resolveDesktopBridgeRemoteTarget(provider.base_url, lookup);
374
+ return { ...provider, base_url: target.baseUrl, remote: target };
375
+ }
376
+ function assertRegistryAndPolicy(config, registry) {
377
+ if (registry.schema !== 'sks.desktop-bridge-provider-registry.v1' || !registry.generation || !Number.isFinite(Date.parse(registry.created_at))) {
378
+ throw new DesktopBridgeError('bridge_provider_registry_invalid');
379
+ }
380
+ const ids = ['codex-lb', 'openrouter'];
381
+ if (Object.keys(registry.providers).length !== ids.length)
382
+ throw new DesktopBridgeError('bridge_provider_registry_invalid');
383
+ for (const id of ids) {
384
+ const provider = registry.providers[id];
385
+ if (!provider || provider.provider_id !== id || !provider.credential_generation || !Array.isArray(provider.allowed_origins) || !provider.allowed_origins.length) {
386
+ throw new DesktopBridgeError('bridge_provider_registry_invalid');
387
+ }
388
+ if (provider.source_catalog_generation !== null
389
+ && (typeof provider.source_catalog_generation !== 'string' || !provider.source_catalog_generation.trim())) {
390
+ throw new DesktopBridgeError('bridge_provider_source_catalog_generation_invalid');
391
+ }
392
+ if (id === 'openrouter' && provider.auth_transport !== 'openrouter-bearer')
393
+ throw new DesktopBridgeError('bridge_provider_auth_transport_mismatch');
394
+ if (id === 'codex-lb' && provider.auth_transport === 'openrouter-bearer')
395
+ throw new DesktopBridgeError('bridge_provider_auth_transport_mismatch');
396
+ validateRemoteUrl(provider.base_url);
397
+ }
398
+ const policy = config.routePolicy;
399
+ if (policy.schema !== 'sks.bridge-routing-policy.v1' || policy.fallback !== 'none' || !policy.catalog_generation || !policy.policy_generation) {
400
+ throw new DesktopBridgeError('bridge_route_policy_invalid');
401
+ }
402
+ for (const [model, route] of Object.entries(policy.model_routes)) {
403
+ if (canonicalizeBridgeModelId(model) !== model
404
+ || !ids.includes(route.provider_id)
405
+ || normalizeBridgeUpstreamModelId(route.upstream_model) !== route.upstream_model) {
406
+ throw new DesktopBridgeError('bridge_route_policy_invalid');
382
407
  }
383
408
  }
384
- else if (isForbiddenRemoteAddress(config.remote.address, config.remote.family)) {
385
- throw new DesktopBridgeError('bridge_remote_dns_private_address');
409
+ const pinIds = new Set();
410
+ for (const pin of config.providerSessionPins) {
411
+ const keys = Object.keys(pin);
412
+ if (keys.some((key) => ![
413
+ 'thread_id', 'provider_id', 'public_model', 'upstream_model',
414
+ 'catalog_generation', 'route_policy_generation', 'created_at',
415
+ ].includes(key))
416
+ || canonicalSessionId(pin.thread_id) !== pin.thread_id
417
+ || pinIds.has(pin.thread_id)
418
+ || !ids.includes(pin.provider_id)
419
+ || canonicalizeBridgeModelId(pin.public_model) !== pin.public_model
420
+ || normalizeBridgeUpstreamModelId(pin.upstream_model) !== pin.upstream_model
421
+ || !pin.catalog_generation
422
+ || !pin.route_policy_generation
423
+ || !Number.isFinite(Date.parse(pin.created_at))) {
424
+ throw new DesktopBridgeError('bridge_session_pin_invalid');
425
+ }
426
+ pinIds.add(pin.thread_id);
427
+ }
428
+ }
429
+ export function validateDesktopBridgeConfig(config) {
430
+ assertLoopbackListenHost(config.listenHost);
431
+ if (!Number.isInteger(config.listenPort) || config.listenPort < MIN_HIGH_PORT || config.listenPort > MAX_PORT)
432
+ throw new DesktopBridgeError('bridge_listen_port_not_high');
433
+ if (!config.allowedPathPrefixes.length)
434
+ throw new DesktopBridgeError('bridge_path_allowlist_empty');
435
+ if (!Number.isFinite(config.connectTimeoutMs) || config.connectTimeoutMs < 100 || config.connectTimeoutMs > 120_000)
436
+ throw new DesktopBridgeError('bridge_connect_timeout_invalid');
437
+ if (!Number.isFinite(config.idleTimeoutMs) || config.idleTimeoutMs < 1_000 || config.idleTimeoutMs > 86_400_000)
438
+ throw new DesktopBridgeError('bridge_idle_timeout_invalid');
439
+ if (config.requestTimeoutMs !== undefined
440
+ && (!Number.isFinite(config.requestTimeoutMs) || config.requestTimeoutMs < 1_000 || config.requestTimeoutMs > 120_000)) {
441
+ throw new DesktopBridgeError('bridge_request_timeout_invalid');
442
+ }
443
+ if (config.maxConcurrentRequests !== undefined
444
+ && (!Number.isInteger(config.maxConcurrentRequests) || config.maxConcurrentRequests < 1 || config.maxConcurrentRequests > 1_024)) {
445
+ throw new DesktopBridgeError('bridge_concurrent_request_limit_invalid');
446
+ }
447
+ if (config.maxConnections !== undefined
448
+ && (!Number.isInteger(config.maxConnections) || config.maxConnections < 1 || config.maxConnections > 2_048)) {
449
+ throw new DesktopBridgeError('bridge_connection_limit_invalid');
450
+ }
451
+ for (const origin of config.allowedOrigins)
452
+ normalizeAllowedOrigin(origin);
453
+ if (!config.providerRegistry)
454
+ throw new DesktopBridgeError('bridge_provider_registry_missing');
455
+ if (!config.routePolicy)
456
+ throw new DesktopBridgeError('bridge_route_policy_invalid');
457
+ if (!Array.isArray(config.providerSessionPins))
458
+ throw new DesktopBridgeError('bridge_session_pin_invalid');
459
+ if (!/^[a-f0-9]{64}$/.test(config.clientCapabilitySha256))
460
+ throw new DesktopBridgeError('bridge_client_capability_invalid');
461
+ if (typeof config.resolveProviderCredential !== 'function')
462
+ throw new DesktopBridgeError('bridge_provider_credential_resolver_missing');
463
+ assertRegistryAndPolicy(config, config.providerRegistry);
464
+ if (!Object.values(config.providerRegistry.providers).some((provider) => provider.enabled)) {
465
+ throw new DesktopBridgeError('bridge_provider_registry_no_enabled_provider');
466
+ }
467
+ }
468
+ export async function prepareDesktopBridgeConfig(config, lookup = defaultLookup) {
469
+ validateDesktopBridgeConfig(config);
470
+ const entries = await Promise.all(Object.keys(config.providerRegistry.providers).map(async (id) => {
471
+ const provider = config.providerRegistry.providers[id];
472
+ if (!provider)
473
+ throw new DesktopBridgeError('bridge_provider_registry_invalid');
474
+ const prepared = await prepareProvider({ ...provider, remote: {} }, lookup);
475
+ return [id, prepared];
476
+ }));
477
+ const providers = Object.fromEntries(entries);
478
+ return { ...config, providers };
479
+ }
480
+ export function validatePreparedDesktopBridgeConfig(config) {
481
+ validateDesktopBridgeConfig(config);
482
+ for (const id of Object.keys(config.providers)) {
483
+ const provider = config.providers[id];
484
+ if (!provider || provider.provider_id !== id || provider.remote.origin !== new URL(provider.base_url).origin)
485
+ throw new DesktopBridgeError('bridge_prepared_config_invalid');
386
486
  }
387
487
  }
388
488
  export function resolveDesktopBridgeTarget(rawRequestUrl, remote) {
389
489
  const raw = String(rawRequestUrl || '/');
390
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw) || raw.startsWith('//') || /[\r\n\0]/.test(raw)) {
490
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw) || raw.startsWith('//') || /[\r\n\0]/.test(raw))
391
491
  throw new DesktopBridgeError('bridge_request_target_invalid');
392
- }
393
492
  let inbound;
394
493
  try {
395
494
  inbound = new URL(raw, 'http://bridge.invalid');
@@ -397,7 +496,22 @@ export function resolveDesktopBridgeTarget(rawRequestUrl, remote) {
397
496
  catch {
398
497
  throw new DesktopBridgeError('bridge_request_target_invalid');
399
498
  }
400
- return new URL(`${inbound.pathname}${inbound.search}`, remote.origin);
499
+ const base = new URL(remote.baseUrl);
500
+ const basePath = base.pathname.replace(/\/+$/, '');
501
+ const providerRelative = providerRelativePath(inbound.pathname);
502
+ const pathname = providerRelative === null
503
+ ? inbound.pathname
504
+ : `${basePath}${providerRelative}` || '/';
505
+ return new URL(`${pathname}${inbound.search}`, remote.origin);
506
+ }
507
+ function providerRelativePath(pathname) {
508
+ for (const prefix of ['/backend-api/codex', '/api/v1', '/v1']) {
509
+ if (pathname === prefix)
510
+ return '';
511
+ if (pathname.startsWith(`${prefix}/`))
512
+ return pathname.slice(prefix.length);
513
+ }
514
+ return null;
401
515
  }
402
516
  export function safeBridgeErrorCode(error) {
403
517
  if (error instanceof DesktopBridgeError)