@bitkyc08/opencodex 2.32.1 → 2.33.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.
- package/gui/dist/assets/{index-8HxacKlZ.js → index-23-Lf7jR.js} +14 -14
- package/gui/dist/assets/index-DxJMDyOr.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +18 -4
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/bridge.ts +12 -8
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +9 -2
- package/src/cli/status.ts +23 -0
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/parsing.ts +38 -27
- package/src/codex/catalog/provider-fetch.ts +143 -25
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/convergence.ts +5 -0
- package/src/codex/shim.ts +56 -3
- package/src/config.ts +24 -0
- package/src/generated/compatibility-version.json +43 -35
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/server/auth-cors.ts +14 -0
- package/src/server/management/agent-settings-routes.ts +13 -7
- package/src/server/management/config-routes.ts +31 -5
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-routes.ts +75 -11
- package/src/server/responses/core.ts +12 -1
- package/src/server/responses/empty-completion-guard.ts +28 -6
- package/src/server/responses-undeclared-tool-guard.ts +90 -8
- package/src/types/config.ts +13 -0
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +27 -0
- package/src/types.ts +1 -0
- package/gui/dist/assets/index-DcBbHIAz.css +0 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"path": "package.json",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "b2e14672cf7df2f0eadb7e4327c50c6ff2768e6fdc023927fe22da8fc336e249"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": "scripts/model-metadata.source.json",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"path": "src/adapters/anthropic.ts",
|
|
37
|
-
"sha256": "
|
|
37
|
+
"sha256": "8e3a0ab54d16da6a290d7574e5360b0c55e63d359d107a448c440eb80d935df0"
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"path": "src/adapters/azure.ts",
|
|
@@ -274,7 +274,7 @@
|
|
|
274
274
|
},
|
|
275
275
|
{
|
|
276
276
|
"path": "src/adapters/kiro-tools.ts",
|
|
277
|
-
"sha256": "
|
|
277
|
+
"sha256": "a176dac1e44959607f817c88c777df5394da3006f68fe9d5b759393600633da6"
|
|
278
278
|
},
|
|
279
279
|
{
|
|
280
280
|
"path": "src/adapters/kiro-truncation.ts",
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
},
|
|
339
339
|
{
|
|
340
340
|
"path": "src/bridge.ts",
|
|
341
|
-
"sha256": "
|
|
341
|
+
"sha256": "9a50601ff7b592204713845db8631bc01cf4bb0e9d8e8a3a7e226ce19148e03b"
|
|
342
342
|
},
|
|
343
343
|
{
|
|
344
344
|
"path": "src/chat/inbound.ts",
|
|
@@ -370,7 +370,7 @@
|
|
|
370
370
|
},
|
|
371
371
|
{
|
|
372
372
|
"path": "src/claude/context-windows.ts",
|
|
373
|
-
"sha256": "
|
|
373
|
+
"sha256": "c400f4b76319ec14855128af40fe52b60d31d217b89e291641522a51d2583d97"
|
|
374
374
|
},
|
|
375
375
|
{
|
|
376
376
|
"path": "src/claude/desktop-3p-guard.ts",
|
|
@@ -498,7 +498,7 @@
|
|
|
498
498
|
},
|
|
499
499
|
{
|
|
500
500
|
"path": "src/cli/doctor.ts",
|
|
501
|
-
"sha256": "
|
|
501
|
+
"sha256": "3809a5be6f5223ffcfc9225187dcca8e1f4be0b01d281126fe3722f89a754767"
|
|
502
502
|
},
|
|
503
503
|
{
|
|
504
504
|
"path": "src/cli/ensure-desired-integrations.ts",
|
|
@@ -514,7 +514,7 @@
|
|
|
514
514
|
},
|
|
515
515
|
{
|
|
516
516
|
"path": "src/cli/index.ts",
|
|
517
|
-
"sha256": "
|
|
517
|
+
"sha256": "64b43bdeed4ba242bfbf2531f49da3dc2c2292ed2a6a0cef0bb93cbc1222075e"
|
|
518
518
|
},
|
|
519
519
|
{
|
|
520
520
|
"path": "src/cli/init.ts",
|
|
@@ -594,7 +594,7 @@
|
|
|
594
594
|
},
|
|
595
595
|
{
|
|
596
596
|
"path": "src/cli/status.ts",
|
|
597
|
-
"sha256": "
|
|
597
|
+
"sha256": "809fe8cf65f422b1405c67a4a5739f79d988f8b421d8bdb8003c9dffd4c51d56"
|
|
598
598
|
},
|
|
599
599
|
{
|
|
600
600
|
"path": "src/cli/system-command.ts",
|
|
@@ -678,7 +678,7 @@
|
|
|
678
678
|
},
|
|
679
679
|
{
|
|
680
680
|
"path": "src/codex/auth-api.ts",
|
|
681
|
-
"sha256": "
|
|
681
|
+
"sha256": "b1b5dcece7e1f817a134361f6895c35550afc66d5db11602c6d25d0c40c2f0b8"
|
|
682
682
|
},
|
|
683
683
|
{
|
|
684
684
|
"path": "src/codex/auth-collision.ts",
|
|
@@ -690,7 +690,7 @@
|
|
|
690
690
|
},
|
|
691
691
|
{
|
|
692
692
|
"path": "src/codex/autostart-health.ts",
|
|
693
|
-
"sha256": "
|
|
693
|
+
"sha256": "e15bd998f1598bf6fe8a1bffdbb836225503baae36e6dc58357b4bb4bde425b6"
|
|
694
694
|
},
|
|
695
695
|
{
|
|
696
696
|
"path": "src/codex/catalog-admission.ts",
|
|
@@ -714,7 +714,7 @@
|
|
|
714
714
|
},
|
|
715
715
|
{
|
|
716
716
|
"path": "src/codex/catalog/aggregation.ts",
|
|
717
|
-
"sha256": "
|
|
717
|
+
"sha256": "aa80a049a216ffde1d39a221281c9f5666330f05cadd93a11399eac36c6f9404"
|
|
718
718
|
},
|
|
719
719
|
{
|
|
720
720
|
"path": "src/codex/catalog/bundled.ts",
|
|
@@ -722,7 +722,7 @@
|
|
|
722
722
|
},
|
|
723
723
|
{
|
|
724
724
|
"path": "src/codex/catalog/effort.ts",
|
|
725
|
-
"sha256": "
|
|
725
|
+
"sha256": "b837a78528f944ece136efc155aee4ff819f44f57d1affc65db1b36f47048ba6"
|
|
726
726
|
},
|
|
727
727
|
{
|
|
728
728
|
"path": "src/codex/catalog/filesystem-evidence.ts",
|
|
@@ -734,7 +734,7 @@
|
|
|
734
734
|
},
|
|
735
735
|
{
|
|
736
736
|
"path": "src/codex/catalog/metadata.ts",
|
|
737
|
-
"sha256": "
|
|
737
|
+
"sha256": "227871625347429067da8b8057412934f366900feba2e4be20749e6cb1dacaa3"
|
|
738
738
|
},
|
|
739
739
|
{
|
|
740
740
|
"path": "src/codex/catalog/native-models.ts",
|
|
@@ -742,15 +742,15 @@
|
|
|
742
742
|
},
|
|
743
743
|
{
|
|
744
744
|
"path": "src/codex/catalog/parsing.ts",
|
|
745
|
-
"sha256": "
|
|
745
|
+
"sha256": "ce7a1e2713ddcb2f2774d9641b014db88e92ca9e70e12cbde82b91ef5c0bd9db"
|
|
746
746
|
},
|
|
747
747
|
{
|
|
748
748
|
"path": "src/codex/catalog/provider-fetch.ts",
|
|
749
|
-
"sha256": "
|
|
749
|
+
"sha256": "f8f03db782a29e9c995d198780aa2c9279f6b4f81261f296d061788c3cc513e5"
|
|
750
750
|
},
|
|
751
751
|
{
|
|
752
752
|
"path": "src/codex/catalog/sync.ts",
|
|
753
|
-
"sha256": "
|
|
753
|
+
"sha256": "616a707b30b3e7c02334da5df2d35e490d758990916272e16fb0c7d5aeb2e38e"
|
|
754
754
|
},
|
|
755
755
|
{
|
|
756
756
|
"path": "src/codex/codex-write-lock.ts",
|
|
@@ -762,7 +762,7 @@
|
|
|
762
762
|
},
|
|
763
763
|
{
|
|
764
764
|
"path": "src/codex/convergence.ts",
|
|
765
|
-
"sha256": "
|
|
765
|
+
"sha256": "699abdffcecb97ea147e8514755d2d667fffea283d6146298af34bf6fda6502f"
|
|
766
766
|
},
|
|
767
767
|
{
|
|
768
768
|
"path": "src/codex/coordinator-doctor.ts",
|
|
@@ -1026,7 +1026,7 @@
|
|
|
1026
1026
|
},
|
|
1027
1027
|
{
|
|
1028
1028
|
"path": "src/codex/shim.ts",
|
|
1029
|
-
"sha256": "
|
|
1029
|
+
"sha256": "0ffddafd8f9c10344a213e67363318598894a113ae8e5d383440d935a9c3132f"
|
|
1030
1030
|
},
|
|
1031
1031
|
{
|
|
1032
1032
|
"path": "src/codex/subagent-defaults.ts",
|
|
@@ -1098,7 +1098,7 @@
|
|
|
1098
1098
|
},
|
|
1099
1099
|
{
|
|
1100
1100
|
"path": "src/config.ts",
|
|
1101
|
-
"sha256": "
|
|
1101
|
+
"sha256": "875dc438cd7bbbebe3645abac09b85efcaf5829548da154c09f116bc72329ee8"
|
|
1102
1102
|
},
|
|
1103
1103
|
{
|
|
1104
1104
|
"path": "src/config/atomic-write.ts",
|
|
@@ -2026,7 +2026,7 @@
|
|
|
2026
2026
|
},
|
|
2027
2027
|
{
|
|
2028
2028
|
"path": "src/oauth/callback-server.ts",
|
|
2029
|
-
"sha256": "
|
|
2029
|
+
"sha256": "3c481c4b26bcaaee246aff098be3fa402e1c0f813adf0f00a4122af92d8d5264"
|
|
2030
2030
|
},
|
|
2031
2031
|
{
|
|
2032
2032
|
"path": "src/oauth/chatgpt.ts",
|
|
@@ -2062,7 +2062,7 @@
|
|
|
2062
2062
|
},
|
|
2063
2063
|
{
|
|
2064
2064
|
"path": "src/oauth/kimi.ts",
|
|
2065
|
-
"sha256": "
|
|
2065
|
+
"sha256": "cbbd772bfcf74c1bf6357d494b1b87034c59fbc9ebeda0ccc93e31df431d4a30"
|
|
2066
2066
|
},
|
|
2067
2067
|
{
|
|
2068
2068
|
"path": "src/oauth/kiro-credentials.ts",
|
|
@@ -2088,6 +2088,10 @@
|
|
|
2088
2088
|
"path": "src/oauth/nous.ts",
|
|
2089
2089
|
"sha256": "49fb089b01a8a118e575069d2308912b7fd661ef882913193b0e9f4d8df28201"
|
|
2090
2090
|
},
|
|
2091
|
+
{
|
|
2092
|
+
"path": "src/oauth/open-browser-choice.ts",
|
|
2093
|
+
"sha256": "f4ef3a89b0df0db5d5ae853378029e4983cd3a239cc97a36535350d939066bbb"
|
|
2094
|
+
},
|
|
2091
2095
|
{
|
|
2092
2096
|
"path": "src/oauth/pkce.ts",
|
|
2093
2097
|
"sha256": "8e2798e44de278bc4db99ceaeded806e4d80a141f4d81bd81e4ab6fd22189509"
|
|
@@ -2128,6 +2132,10 @@
|
|
|
2128
2132
|
"path": "src/providers/api-keys.ts",
|
|
2129
2133
|
"sha256": "53be86c657fa5f4ad3605f0098312df7186072637f8fe4d16667a64ec853e19e"
|
|
2130
2134
|
},
|
|
2135
|
+
{
|
|
2136
|
+
"path": "src/providers/auto-compact-budget.ts",
|
|
2137
|
+
"sha256": "574d38c410f507f590a2285086b78b34318b01f233fabf322a6cef0b817b5eb9"
|
|
2138
|
+
},
|
|
2131
2139
|
{
|
|
2132
2140
|
"path": "src/providers/base-url-choices.ts",
|
|
2133
2141
|
"sha256": "f75c050dd851d38fd90335def782699aea43f89930dfff95060fa88d9724a9a6"
|
|
@@ -2254,7 +2262,7 @@
|
|
|
2254
2262
|
},
|
|
2255
2263
|
{
|
|
2256
2264
|
"path": "src/providers/xai-transport.ts",
|
|
2257
|
-
"sha256": "
|
|
2265
|
+
"sha256": "1ac6638465ad2b2a36603ee36aa53176474f9c6ca3253db0a43933ed7b20371d"
|
|
2258
2266
|
},
|
|
2259
2267
|
{
|
|
2260
2268
|
"path": "src/reasoning-effort.ts",
|
|
@@ -2434,7 +2442,7 @@
|
|
|
2434
2442
|
},
|
|
2435
2443
|
{
|
|
2436
2444
|
"path": "src/server/auth-cors.ts",
|
|
2437
|
-
"sha256": "
|
|
2445
|
+
"sha256": "8ae32bbf202ae3271139ebe5594f939fa81687316d006da088d064cb44521556"
|
|
2438
2446
|
},
|
|
2439
2447
|
{
|
|
2440
2448
|
"path": "src/server/background-lifecycle.ts",
|
|
@@ -2510,7 +2518,7 @@
|
|
|
2510
2518
|
},
|
|
2511
2519
|
{
|
|
2512
2520
|
"path": "src/server/management/agent-settings-routes.ts",
|
|
2513
|
-
"sha256": "
|
|
2521
|
+
"sha256": "d41b02b59c071c86f32829ec769110320b2b994efb724cfe2bcf64352645096a"
|
|
2514
2522
|
},
|
|
2515
2523
|
{
|
|
2516
2524
|
"path": "src/server/management/api-access.ts",
|
|
@@ -2530,7 +2538,7 @@
|
|
|
2530
2538
|
},
|
|
2531
2539
|
{
|
|
2532
2540
|
"path": "src/server/management/config-routes.ts",
|
|
2533
|
-
"sha256": "
|
|
2541
|
+
"sha256": "54b4936caf99e8df7cdf38afe9290615d9c9ae83acaeb6a42da192936bd1706d"
|
|
2534
2542
|
},
|
|
2535
2543
|
{
|
|
2536
2544
|
"path": "src/server/management/context.ts",
|
|
@@ -2558,7 +2566,7 @@
|
|
|
2558
2566
|
},
|
|
2559
2567
|
{
|
|
2560
2568
|
"path": "src/server/management/model-rows.ts",
|
|
2561
|
-
"sha256": "
|
|
2569
|
+
"sha256": "ef61d365e116a718e46f73925315c244e0dab66191f1f449e5b281476dc009c7"
|
|
2562
2570
|
},
|
|
2563
2571
|
{
|
|
2564
2572
|
"path": "src/server/management/native-integration-routes.ts",
|
|
@@ -2566,7 +2574,7 @@
|
|
|
2566
2574
|
},
|
|
2567
2575
|
{
|
|
2568
2576
|
"path": "src/server/management/oauth-account-routes.ts",
|
|
2569
|
-
"sha256": "
|
|
2577
|
+
"sha256": "a59494a55de7245e9bb37578437d085255ed67f8de9c3887042db5284b4691d2"
|
|
2570
2578
|
},
|
|
2571
2579
|
{
|
|
2572
2580
|
"path": "src/server/management/provider-capability-config.ts",
|
|
@@ -2574,7 +2582,7 @@
|
|
|
2574
2582
|
},
|
|
2575
2583
|
{
|
|
2576
2584
|
"path": "src/server/management/provider-routes.ts",
|
|
2577
|
-
"sha256": "
|
|
2585
|
+
"sha256": "aea775e35c8da60e4d5e487f0515da72a16df03defb9227c410fcee0b3e2a4b9"
|
|
2578
2586
|
},
|
|
2579
2587
|
{
|
|
2580
2588
|
"path": "src/server/management/request-history-routes.ts",
|
|
@@ -2706,7 +2714,7 @@
|
|
|
2706
2714
|
},
|
|
2707
2715
|
{
|
|
2708
2716
|
"path": "src/server/responses-undeclared-tool-guard.ts",
|
|
2709
|
-
"sha256": "
|
|
2717
|
+
"sha256": "6e023eec3dc7b2f81ff00b40c4dd8f2203c48daaff58fd0696a75ce2f4e3c1a6"
|
|
2710
2718
|
},
|
|
2711
2719
|
{
|
|
2712
2720
|
"path": "src/server/responses.ts",
|
|
@@ -2738,11 +2746,11 @@
|
|
|
2738
2746
|
},
|
|
2739
2747
|
{
|
|
2740
2748
|
"path": "src/server/responses/core.ts",
|
|
2741
|
-
"sha256": "
|
|
2749
|
+
"sha256": "d7c3664cbdfd358c4f7a6be202eae41a612a8c7c8f7bc5f921aaef743221d22b"
|
|
2742
2750
|
},
|
|
2743
2751
|
{
|
|
2744
2752
|
"path": "src/server/responses/empty-completion-guard.ts",
|
|
2745
|
-
"sha256": "
|
|
2753
|
+
"sha256": "7ba65c37a10ab1c2a6c57b2d373e4bc601e092c4600aefe33ff97ebe4ead4415"
|
|
2746
2754
|
},
|
|
2747
2755
|
{
|
|
2748
2756
|
"path": "src/server/responses/encrypted-payload.ts",
|
|
@@ -2910,7 +2918,7 @@
|
|
|
2910
2918
|
},
|
|
2911
2919
|
{
|
|
2912
2920
|
"path": "src/types.ts",
|
|
2913
|
-
"sha256": "
|
|
2921
|
+
"sha256": "71bf1c05ce762244905ad304d674754ac1a759bd0d62164c402fb3c090e21a5d"
|
|
2914
2922
|
},
|
|
2915
2923
|
{
|
|
2916
2924
|
"path": "src/types/accounts.ts",
|
|
@@ -2918,11 +2926,11 @@
|
|
|
2918
2926
|
},
|
|
2919
2927
|
{
|
|
2920
2928
|
"path": "src/types/config.ts",
|
|
2921
|
-
"sha256": "
|
|
2929
|
+
"sha256": "926fb0ee733520d9f756a1b54192193a255a078bd5fdf5c7511f47d1f033bbf3"
|
|
2922
2930
|
},
|
|
2923
2931
|
{
|
|
2924
2932
|
"path": "src/types/provider.ts",
|
|
2925
|
-
"sha256": "
|
|
2933
|
+
"sha256": "0edbdc5f84f07d6c1412da8cc89958156c96782f3fd9fed082a41d6239f63e18"
|
|
2926
2934
|
},
|
|
2927
2935
|
{
|
|
2928
2936
|
"path": "src/types/request.ts",
|
|
@@ -2930,7 +2938,7 @@
|
|
|
2930
2938
|
},
|
|
2931
2939
|
{
|
|
2932
2940
|
"path": "src/types/tools.ts",
|
|
2933
|
-
"sha256": "
|
|
2941
|
+
"sha256": "a5c36b0dc61a88d395783ca613e06caddce54bfb9fc44fe9f24c5fe9f59b7a64"
|
|
2934
2942
|
},
|
|
2935
2943
|
{
|
|
2936
2944
|
"path": "src/types/wire.ts",
|
|
@@ -276,10 +276,30 @@ export function parseCallbackInput(input: string): { kind: "url" | "query" | "ra
|
|
|
276
276
|
|
|
277
277
|
try {
|
|
278
278
|
const url = new URL(value);
|
|
279
|
+
// Also read the fragment. No provider configured here returns one — every
|
|
280
|
+
// OAuthCallbackFlow asks for response_type=code without response_mode, so
|
|
281
|
+
// the parameters land in the query — but a full URL whose parameters sit in
|
|
282
|
+
// the hash parses as a valid URL with no code, and reading only the query
|
|
283
|
+
// rejects it as "no authorization code found in input". This is the one
|
|
284
|
+
// place a fragment-returning provider would land, and the raw branch below
|
|
285
|
+
// already understands `code#state`.
|
|
286
|
+
//
|
|
287
|
+
// The query wins as a WHOLE when it carries the response, and the two
|
|
288
|
+
// fields are never mixed across collections. Reading `code` and `state`
|
|
289
|
+
// independently would accept `?state=<expected>#code=<other>` — one
|
|
290
|
+
// response assembled from two sources — which is exactly the confusion a
|
|
291
|
+
// state check exists to prevent. An authorization response arrives in one
|
|
292
|
+
// place; treat it that way.
|
|
293
|
+
//
|
|
294
|
+
// Only `code` and `state` are read — never a token. This repo does not
|
|
295
|
+
// implement the implicit grant and a paste field must not become the place
|
|
296
|
+
// it appears.
|
|
297
|
+
const fragment = new URLSearchParams(url.hash.replace(/^#/, ""));
|
|
298
|
+
const source = url.searchParams.has("code") ? url.searchParams : fragment;
|
|
279
299
|
return {
|
|
280
300
|
kind: "url",
|
|
281
|
-
code:
|
|
282
|
-
state:
|
|
301
|
+
code: source.get("code") ?? undefined,
|
|
302
|
+
state: source.get("state") ?? undefined,
|
|
283
303
|
};
|
|
284
304
|
} catch {
|
|
285
305
|
// Not a URL - check for query string format
|
package/src/oauth/kimi.ts
CHANGED
|
@@ -209,7 +209,15 @@ async function pollForToken(deviceCode: string, intervalMs: number, expiresInMs:
|
|
|
209
209
|
|
|
210
210
|
export async function loginKimi(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
211
211
|
const device = await requestDeviceAuthorization();
|
|
212
|
-
|
|
212
|
+
// `deviceCode` carries the human-typed user code, matching the other device
|
|
213
|
+
// providers. It is also what tells the management login route this is a device
|
|
214
|
+
// flow, so a provider-supplied verification URI is no longer handed to a local
|
|
215
|
+
// browser spawn — the URL stays visible and copyable in every login surface.
|
|
216
|
+
ctrl.onAuth?.({
|
|
217
|
+
url: device.verificationUriComplete,
|
|
218
|
+
instructions: `Enter code: ${device.userCode}`,
|
|
219
|
+
deviceCode: device.userCode,
|
|
220
|
+
});
|
|
213
221
|
return pollForToken(device.deviceCode, device.intervalMs, device.expiresInMs, ctrl.signal);
|
|
214
222
|
}
|
|
215
223
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { OcxConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a login should open a browser on the machine running the proxy.
|
|
5
|
+
*
|
|
6
|
+
* Both login routes share this so the two surfaces cannot drift: a per-request
|
|
7
|
+
* `openBrowser` beats the persisted setting, and the persisted setting beats
|
|
8
|
+
* the historical default. It lives in `src/oauth/` rather than beside either
|
|
9
|
+
* route because `src/codex/auth-api.ts` and
|
|
10
|
+
* `src/server/management/oauth-account-routes.ts` both already import from
|
|
11
|
+
* here, so no new edge is added to the import graph.
|
|
12
|
+
*
|
|
13
|
+
* The default is the important part. An operator who upgrades and configures
|
|
14
|
+
* nothing must see exactly what they saw before, so only an explicit `false`
|
|
15
|
+
* declines — `undefined`, `true`, and a malformed value all open.
|
|
16
|
+
*
|
|
17
|
+
* A malformed request field is ignored rather than rejected on purpose: this is
|
|
18
|
+
* a display preference, and no login should fail because of one.
|
|
19
|
+
*/
|
|
20
|
+
export function shouldOpenBrowserForLogin(
|
|
21
|
+
requested: unknown,
|
|
22
|
+
config: Pick<OcxConfig, "oauthOpenBrowser">,
|
|
23
|
+
): boolean {
|
|
24
|
+
if (typeof requested === "boolean") return requested;
|
|
25
|
+
return config.oauthOpenBrowser !== false;
|
|
26
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SUPPORTED_NATIVE_OPENAI_SLUGS } from "../codex/catalog/native-models";
|
|
2
|
+
import { redactSecretString } from "../lib/redact";
|
|
3
|
+
|
|
4
|
+
const RESERVED_OBJECT_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
5
|
+
|
|
6
|
+
function positiveSafeInteger(value: unknown): value is number {
|
|
7
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a client-facing soft compaction budget without changing any hard
|
|
12
|
+
* model limit. Configuration and measured input ceilings may only lower the
|
|
13
|
+
* default 90% envelope.
|
|
14
|
+
*/
|
|
15
|
+
export function clampAutoCompactTokenLimit(
|
|
16
|
+
contextWindow: number,
|
|
17
|
+
maxInputTokens?: number,
|
|
18
|
+
configuredLimit?: number,
|
|
19
|
+
): number {
|
|
20
|
+
const candidates = [Math.floor(contextWindow * 0.9), contextWindow];
|
|
21
|
+
if (positiveSafeInteger(maxInputTokens)) candidates.push(maxInputTokens);
|
|
22
|
+
if (positiveSafeInteger(configuredLimit)) candidates.push(configuredLimit);
|
|
23
|
+
return Math.min(...candidates);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AutoCompactBudgetValidationOptions = Readonly<{
|
|
27
|
+
/** PATCH accepts null for whole-map and per-key deletion. */
|
|
28
|
+
allowTombstones?: boolean;
|
|
29
|
+
/** The canonical ChatGPT provider accepts only exact supported native ids. */
|
|
30
|
+
requireNativeIds?: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
/** Shared config/load/management boundary for per-model soft budgets. */
|
|
34
|
+
export function modelAutoCompactTokenLimitsConfigError(
|
|
35
|
+
value: unknown,
|
|
36
|
+
options: AutoCompactBudgetValidationOptions = {},
|
|
37
|
+
): string | null {
|
|
38
|
+
const field = "modelAutoCompactTokenLimits";
|
|
39
|
+
if (value === undefined || (options.allowTombstones && value === null)) return null;
|
|
40
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
41
|
+
return `${field} must be a plain object${options.allowTombstones ? " or null" : ""}`;
|
|
42
|
+
}
|
|
43
|
+
const prototype = Object.getPrototypeOf(value);
|
|
44
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
45
|
+
return `${field} must be a plain object with own properties`;
|
|
46
|
+
}
|
|
47
|
+
for (const [modelId, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
48
|
+
const safeModelId = JSON.stringify(redactSecretString(modelId));
|
|
49
|
+
if (!modelId.trim()) return `${field} keys must be nonblank model ids`;
|
|
50
|
+
if (RESERVED_OBJECT_KEYS.has(modelId)) {
|
|
51
|
+
return `${field} key ${safeModelId} is reserved`;
|
|
52
|
+
}
|
|
53
|
+
if (options.requireNativeIds
|
|
54
|
+
&& (modelId.includes("/") || !SUPPORTED_NATIVE_OPENAI_SLUGS.has(modelId))) {
|
|
55
|
+
return `${field} key ${safeModelId} must be an exact supported native model id`;
|
|
56
|
+
}
|
|
57
|
+
if (options.allowTombstones && entry === null) continue;
|
|
58
|
+
if (!positiveSafeInteger(entry)) {
|
|
59
|
+
return `${field}[${safeModelId}] must be a positive safe integer${
|
|
60
|
+
options.allowTombstones ? " or null" : ""
|
|
61
|
+
}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -4,6 +4,27 @@ import { resolveGithubCopilotTransport } from "./github-copilot-transport";
|
|
|
4
4
|
|
|
5
5
|
export const XAI_GROK_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
6
6
|
|
|
7
|
+
/** The two hosts that serve xAI's Responses API: the public API and the Grok CLI proxy. */
|
|
8
|
+
const XAI_RESPONSES_HOSTS = new Set(["api.x.ai", "cli-chat-proxy.grok.com"]);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* True when this provider's Responses traffic terminates at xAI itself.
|
|
12
|
+
*
|
|
13
|
+
* Probed 2026-08-22 one field per request: the two hosts accept and refuse exactly the same
|
|
14
|
+
* web_search fields, so they are one dialect rather than two. Matching is exact-host over
|
|
15
|
+
* https, which keeps lookalikes (`api.x.ai.evil.test`) and nonstandard ports out.
|
|
16
|
+
*/
|
|
17
|
+
export function isXaiResponsesDestination(provider: Pick<OcxProviderConfig, "baseUrl">): boolean {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(provider.baseUrl);
|
|
20
|
+
return url.protocol === "https:"
|
|
21
|
+
&& XAI_RESPONSES_HOSTS.has(url.hostname.toLowerCase())
|
|
22
|
+
&& (url.port === "" || url.port === "443");
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
export const XAI_GROK_COMPATIBILITY = {
|
|
8
29
|
version: "0.2.93",
|
|
9
30
|
userAgent: "opencodex-grok/0.2.93",
|
package/src/server/auth-cors.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { effectiveGoogleMode, getProviderRegistryEntry, providerCodexAccountMode
|
|
|
26
26
|
import { providerConfigSeed } from "../providers/derive";
|
|
27
27
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
28
28
|
import { openRouterRoutingConfigError } from "../providers/openrouter-routing";
|
|
29
|
+
import { modelAutoCompactTokenLimitsConfigError } from "../providers/auto-compact-budget";
|
|
29
30
|
import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
|
|
30
31
|
import { xaiResponsesOptInState } from "../providers/xai-responses-opt-in";
|
|
31
32
|
|
|
@@ -565,6 +566,8 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
565
566
|
if (contextOverlayError) return contextOverlayError;
|
|
566
567
|
delete canonicalCandidate.contextWindow;
|
|
567
568
|
delete canonicalCandidate.modelContextWindows;
|
|
569
|
+
// User-owned soft compaction policy; it does not alter the canonical transport seed.
|
|
570
|
+
delete canonicalCandidate.modelAutoCompactTokenLimits;
|
|
568
571
|
const canonical = seed && sameCanonicalProviderSeed(canonicalCandidate, seed);
|
|
569
572
|
if (!canonical) {
|
|
570
573
|
return `provider ${name} must equal the canonical built-in provider seed`;
|
|
@@ -607,6 +610,13 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
607
610
|
if (apiKeyTransportError) return `provider ${name} ${apiKeyTransportError}`;
|
|
608
611
|
const maxInputError = positiveIntegerRecordConfigError(raw.modelMaxInputTokens, "modelMaxInputTokens");
|
|
609
612
|
if (maxInputError) return `provider ${name} ${maxInputError}`;
|
|
613
|
+
const autoCompactError = modelAutoCompactTokenLimitsConfigError(
|
|
614
|
+
raw.modelAutoCompactTokenLimits,
|
|
615
|
+
{ requireNativeIds: name === "openai" },
|
|
616
|
+
);
|
|
617
|
+
if (autoCompactError) {
|
|
618
|
+
return `provider ${JSON.stringify(redactSecretString(name))} ${autoCompactError}`;
|
|
619
|
+
}
|
|
610
620
|
const reasoningSummariesError = booleanRecordConfigError(raw.modelSupportsReasoningSummaries, "modelSupportsReasoningSummaries");
|
|
611
621
|
if (reasoningSummariesError) return `provider ${name} ${reasoningSummariesError}`;
|
|
612
622
|
const reasoningSummaryDeliveryError = reasoningSummaryDeliveryRecordConfigError(
|
|
@@ -707,6 +717,7 @@ export function safeConfigDTO(config: OcxConfig): unknown {
|
|
|
707
717
|
"models",
|
|
708
718
|
"contextWindow",
|
|
709
719
|
"modelContextWindows",
|
|
720
|
+
"modelAutoCompactTokenLimits",
|
|
710
721
|
"defaultMaxOutputTokens",
|
|
711
722
|
"modelMaxOutputTokens",
|
|
712
723
|
"openRouterRouting",
|
|
@@ -748,6 +759,9 @@ export function safeConfigDTO(config: OcxConfig): unknown {
|
|
|
748
759
|
defaultProvider: config.defaultProvider,
|
|
749
760
|
codexAutoStart: codexAutoStartEnabled(config),
|
|
750
761
|
websockets: config.websockets,
|
|
762
|
+
// The GUI's browser-open toggle reads and writes this; absent means the
|
|
763
|
+
// historical auto-open behavior.
|
|
764
|
+
oauthOpenBrowser: config.oauthOpenBrowser !== false,
|
|
751
765
|
providers,
|
|
752
766
|
};
|
|
753
767
|
}
|
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
webSearchCandidateRows,
|
|
61
61
|
webSearchModelIsRejected,
|
|
62
62
|
webSearchModelRejection,
|
|
63
|
+
type WebSearchBackend,
|
|
63
64
|
} from "./web-search-sidecar-options";
|
|
64
65
|
import { drainAndShutdown } from "../lifecycle";
|
|
65
66
|
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
|
|
@@ -1118,13 +1119,18 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1118
1119
|
if (field === "webSearchSidecar"
|
|
1119
1120
|
&& (section.model !== undefined || section.backend !== undefined)) {
|
|
1120
1121
|
const stored = config.claudeCode?.webSearchSidecar;
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1122
|
+
// Validate against the SUBMITTED backend across the whole union, not
|
|
1123
|
+
// just openai/anthropic (#2457). allowedBackends above already refused
|
|
1124
|
+
// unknown literals; Array.includes does not narrow, hence the cast.
|
|
1125
|
+
// null keeps its own meaning here — drop the override and inherit the
|
|
1126
|
+
// global backend — which is deliberately NOT the sidecar-settings rule.
|
|
1127
|
+
const submittedBackend = section.backend;
|
|
1128
|
+
const effectiveBackend = typeof submittedBackend === "string"
|
|
1129
|
+
&& allowedBackends.includes(submittedBackend)
|
|
1130
|
+
? submittedBackend as WebSearchBackend
|
|
1131
|
+
: submittedBackend === null
|
|
1132
|
+
? config.webSearchSidecar?.backend ?? "openai"
|
|
1133
|
+
: stored?.backend ?? config.webSearchSidecar?.backend ?? "openai";
|
|
1128
1134
|
const effectiveModel = section.model === ""
|
|
1129
1135
|
? config.webSearchSidecar?.model
|
|
1130
1136
|
: typeof section.model === "string"
|
|
@@ -298,6 +298,9 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
298
298
|
streamMode: config.streamMode ?? "auto",
|
|
299
299
|
appOwnedMemoryBudgetMb: config.appOwnedMemoryBudgetMb ?? 256,
|
|
300
300
|
codexAccountPickerEnabled: codexAccountPickerEnabled(config),
|
|
301
|
+
// Absent means the historical auto-open, so the GUI can render the toggle
|
|
302
|
+
// without having to know that `undefined` and `true` mean the same thing.
|
|
303
|
+
oauthOpenBrowser: config.oauthOpenBrowser !== false,
|
|
301
304
|
startupHealth: await readStartupHealth(config),
|
|
302
305
|
codexRuntime: {
|
|
303
306
|
path: displayCodexRuntimePath(resolved.runtime.command),
|
|
@@ -382,16 +385,21 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
382
385
|
streamMode?: unknown;
|
|
383
386
|
appOwnedMemoryBudgetMb?: unknown;
|
|
384
387
|
codexAccountPickerEnabled?: unknown;
|
|
388
|
+
oauthOpenBrowser?: unknown;
|
|
385
389
|
};
|
|
386
390
|
if (body.codexAutoStart === undefined
|
|
387
391
|
&& body.streamMode === undefined
|
|
388
392
|
&& body.appOwnedMemoryBudgetMb === undefined
|
|
389
|
-
&& body.codexAccountPickerEnabled === undefined
|
|
390
|
-
|
|
393
|
+
&& body.codexAccountPickerEnabled === undefined
|
|
394
|
+
&& body.oauthOpenBrowser === undefined) {
|
|
395
|
+
return jsonResponse({ error: "provide codexAutoStart, streamMode, appOwnedMemoryBudgetMb, codexAccountPickerEnabled, or oauthOpenBrowser" }, 400);
|
|
391
396
|
}
|
|
392
397
|
if (body.codexAutoStart !== undefined && typeof body.codexAutoStart !== "boolean") {
|
|
393
398
|
return jsonResponse({ error: "codexAutoStart boolean is required" }, 400);
|
|
394
399
|
}
|
|
400
|
+
if (body.oauthOpenBrowser !== undefined && typeof body.oauthOpenBrowser !== "boolean") {
|
|
401
|
+
return jsonResponse({ error: "oauthOpenBrowser boolean is required" }, 400);
|
|
402
|
+
}
|
|
395
403
|
if (body.streamMode !== undefined && !isStreamMode(body.streamMode)) {
|
|
396
404
|
return jsonResponse({ error: "streamMode must be auto, legacy-tee, or eager-relay" }, 400);
|
|
397
405
|
}
|
|
@@ -418,6 +426,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
418
426
|
hasCodexAccountNamespaces: Object.hasOwn(config, "codexAccountNamespaces"),
|
|
419
427
|
codexAccountPickerEnabled: config.codexAccountPickerEnabled,
|
|
420
428
|
hasCodexAccountPickerEnabled: Object.hasOwn(config, "codexAccountPickerEnabled"),
|
|
429
|
+
oauthOpenBrowser: config.oauthOpenBrowser,
|
|
430
|
+
hasOauthOpenBrowser: Object.hasOwn(config, "oauthOpenBrowser"),
|
|
421
431
|
};
|
|
422
432
|
const pickerWasEnabled = codexAccountPickerEnabled(config);
|
|
423
433
|
let pickerIsEnabled = pickerWasEnabled;
|
|
@@ -441,6 +451,9 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
441
451
|
} else if (body.codexAccountPickerEnabled === false) {
|
|
442
452
|
config.codexAccountPickerEnabled = false;
|
|
443
453
|
}
|
|
454
|
+
if (typeof body.oauthOpenBrowser === "boolean") {
|
|
455
|
+
config.oauthOpenBrowser = body.oauthOpenBrowser;
|
|
456
|
+
}
|
|
444
457
|
pickerIsEnabled = codexAccountPickerEnabled(config);
|
|
445
458
|
(deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode)(config);
|
|
446
459
|
} catch (error) {
|
|
@@ -457,6 +470,9 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
457
470
|
if (previousSettings.hasCodexAccountPickerEnabled) {
|
|
458
471
|
config.codexAccountPickerEnabled = previousSettings.codexAccountPickerEnabled;
|
|
459
472
|
} else delete config.codexAccountPickerEnabled;
|
|
473
|
+
if (previousSettings.hasOauthOpenBrowser) {
|
|
474
|
+
config.oauthOpenBrowser = previousSettings.oauthOpenBrowser;
|
|
475
|
+
} else delete config.oauthOpenBrowser;
|
|
460
476
|
throw error;
|
|
461
477
|
}
|
|
462
478
|
if (typeof body.appOwnedMemoryBudgetMb === "number") {
|
|
@@ -476,6 +492,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
476
492
|
streamMode: config.streamMode ?? "auto",
|
|
477
493
|
appOwnedMemoryBudgetMb: config.appOwnedMemoryBudgetMb ?? 256,
|
|
478
494
|
codexAccountPickerEnabled: pickerIsEnabled,
|
|
495
|
+
oauthOpenBrowser: config.oauthOpenBrowser !== false,
|
|
479
496
|
catalogRefreshPending,
|
|
480
497
|
startupHealth: await readStartupHealth(config),
|
|
481
498
|
});
|
|
@@ -665,9 +682,18 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
665
682
|
|
|
666
683
|
if (body.webSearch) {
|
|
667
684
|
const pairTouched = body.webSearch.model !== undefined || body.webSearch.backend !== undefined;
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
685
|
+
// Validate against the backend the caller SUBMITTED, across the whole
|
|
686
|
+
// union — not just openai/anthropic (#2457). The union check above has
|
|
687
|
+
// already refused unknown literals, so a surviving string is a member;
|
|
688
|
+
// Array.includes does not narrow, hence the cast. Falling back to the
|
|
689
|
+
// stored backend for xai/gemini/exa both rejected legal pairs and
|
|
690
|
+
// accepted illegal ones: a submitted gemini was checked against a stored
|
|
691
|
+
// openai. null means "unset the backend", and unset resolves to openai.
|
|
692
|
+
const submittedBackend = body.webSearch.backend;
|
|
693
|
+
const effectiveBackend = typeof submittedBackend === "string"
|
|
694
|
+
&& WEB_SEARCH_BACKENDS_UNION.includes(submittedBackend as never)
|
|
695
|
+
? submittedBackend as typeof WEB_SEARCH_BACKENDS_UNION[number]
|
|
696
|
+
: submittedBackend === null
|
|
671
697
|
? "openai"
|
|
672
698
|
: config.webSearchSidecar?.backend ?? "openai";
|
|
673
699
|
const effectiveModel = typeof body.webSearch.model === "string"
|