@bitkyc08/opencodex 2.21.0 → 2.22.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/AGENTS_INSTALL.md +32 -0
- package/README.md +1 -1
- package/gui/dist/assets/{index-BOFeam5a.js → index-ClEcVlFO.js} +43 -17
- package/gui/dist/assets/index-DQsMZzI5.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/claude/agents-inject.ts +2 -2
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/dispatch.ts +7 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/models.ts +16 -6
- package/src/cli/observe.ts +38 -2
- package/src/cli/registry.ts +2 -1
- package/src/cli/v2.ts +34 -1
- package/src/codex/app-server-processes.ts +46 -26
- package/src/codex/catalog/effort.ts +49 -1
- package/src/codex/catalog/parsing.ts +64 -4
- package/src/codex/catalog/provider-fetch.ts +12 -0
- package/src/codex/catalog/sync.ts +14 -1
- package/src/codex/convergence.ts +2 -0
- package/src/codex/inject.ts +3 -3
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +39 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/paths.ts +5 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/project-config-warnings.ts +2 -2
- package/src/generated/compatibility-version.json +72 -32
- package/src/providers/antigravity-models.ts +11 -1
- package/src/providers/model-discovery.ts +94 -6
- package/src/providers/quota.ts +159 -0
- package/src/providers/registry.ts +13 -0
- package/src/providers/slug-codec.ts +29 -0
- package/src/router.ts +12 -4
- package/src/routing/capability.ts +32 -17
- package/src/server/index.ts +1 -0
- package/src/server/management/agent-settings-routes.ts +20 -2
- package/src/server/management/context.ts +15 -0
- package/src/server/management/model-routes.ts +12 -3
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management-api.ts +3 -1
- package/src/server/responses/core.ts +13 -0
- package/src/types.ts +6 -1
- package/gui/dist/assets/index-Xq49CY8F.css +0 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"path": "package.json",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "23d58fd93c12138baca76f044899d82aea45768aa38f5f78c86e957e080be06b"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": "scripts/model-metadata.source.json",
|
|
@@ -318,7 +318,7 @@
|
|
|
318
318
|
},
|
|
319
319
|
{
|
|
320
320
|
"path": "src/claude/agents-inject.ts",
|
|
321
|
-
"sha256": "
|
|
321
|
+
"sha256": "46ebe634b087860bd3c107efe17d4c54289c19845c50c729eeaf53d18b4e8247"
|
|
322
322
|
},
|
|
323
323
|
{
|
|
324
324
|
"path": "src/claude/alias.ts",
|
|
@@ -432,6 +432,10 @@
|
|
|
432
432
|
"path": "src/cli/claude.ts",
|
|
433
433
|
"sha256": "7b9b5ea1fbeab889f137a3f889e89350d7205ab77102f6b46357dd825be0e08d"
|
|
434
434
|
},
|
|
435
|
+
{
|
|
436
|
+
"path": "src/cli/codex-log-guard-doctor.ts",
|
|
437
|
+
"sha256": "e6a62a5c9cbf65e900ec3523598a225f5ccd1238abb8da9cae232d60da6fdabf"
|
|
438
|
+
},
|
|
435
439
|
{
|
|
436
440
|
"path": "src/cli/codex-shim-autorestore.ts",
|
|
437
441
|
"sha256": "9c6787a2a41f696eca1fbd95905a3f7b90718cf044dd21cb5e47f789fea48b44"
|
|
@@ -454,7 +458,7 @@
|
|
|
454
458
|
},
|
|
455
459
|
{
|
|
456
460
|
"path": "src/cli/dispatch.ts",
|
|
457
|
-
"sha256": "
|
|
461
|
+
"sha256": "3edcfa5957813d525da64ed091f605dc70adda272cf16414d62f42a99ed5ed08"
|
|
458
462
|
},
|
|
459
463
|
{
|
|
460
464
|
"path": "src/cli/doctor.ts",
|
|
@@ -466,7 +470,7 @@
|
|
|
466
470
|
},
|
|
467
471
|
{
|
|
468
472
|
"path": "src/cli/help.ts",
|
|
469
|
-
"sha256": "
|
|
473
|
+
"sha256": "829453ef2aac53bc15825bc6a47320a3a0a5a9989f616fbb62e76fdfa01891ee"
|
|
470
474
|
},
|
|
471
475
|
{
|
|
472
476
|
"path": "src/cli/index.ts",
|
|
@@ -502,11 +506,11 @@
|
|
|
502
506
|
},
|
|
503
507
|
{
|
|
504
508
|
"path": "src/cli/models.ts",
|
|
505
|
-
"sha256": "
|
|
509
|
+
"sha256": "a7ddece988af02fc1ebbf4f97bbed882ab70713048c65b094b62bba42b99036f"
|
|
506
510
|
},
|
|
507
511
|
{
|
|
508
512
|
"path": "src/cli/observe.ts",
|
|
509
|
-
"sha256": "
|
|
513
|
+
"sha256": "c403189847bbeb114e87f133e6e3612c7e3f483e8c64e579152d54f4fc83f462"
|
|
510
514
|
},
|
|
511
515
|
{
|
|
512
516
|
"path": "src/cli/opencode.ts",
|
|
@@ -526,7 +530,7 @@
|
|
|
526
530
|
},
|
|
527
531
|
{
|
|
528
532
|
"path": "src/cli/registry.ts",
|
|
529
|
-
"sha256": "
|
|
533
|
+
"sha256": "160e10cb3a5797cb7918261d804fc09d7eb264a3fbddbc9e8735eabf4bad87e7"
|
|
530
534
|
},
|
|
531
535
|
{
|
|
532
536
|
"path": "src/cli/root.ts",
|
|
@@ -566,7 +570,7 @@
|
|
|
566
570
|
},
|
|
567
571
|
{
|
|
568
572
|
"path": "src/cli/v2.ts",
|
|
569
|
-
"sha256": "
|
|
573
|
+
"sha256": "c82727ac194d3e21494b73f3ca4b8c32c86bacea1f39f489477f9cc0ddf8491f"
|
|
570
574
|
},
|
|
571
575
|
{
|
|
572
576
|
"path": "src/clients/config-export.ts",
|
|
@@ -618,7 +622,7 @@
|
|
|
618
622
|
},
|
|
619
623
|
{
|
|
620
624
|
"path": "src/codex/app-server-processes.ts",
|
|
621
|
-
"sha256": "
|
|
625
|
+
"sha256": "28c7fb91d8fd5c2332738e512b9d87c53ea09843191c145b0801d912e124fe9e"
|
|
622
626
|
},
|
|
623
627
|
{
|
|
624
628
|
"path": "src/codex/app-server-restart-service.ts",
|
|
@@ -670,7 +674,7 @@
|
|
|
670
674
|
},
|
|
671
675
|
{
|
|
672
676
|
"path": "src/codex/catalog/effort.ts",
|
|
673
|
-
"sha256": "
|
|
677
|
+
"sha256": "ac208a47306bd0081e7be7a73ff483296d28ca0e23427218ce999dc53a6071c9"
|
|
674
678
|
},
|
|
675
679
|
{
|
|
676
680
|
"path": "src/codex/catalog/filesystem-evidence.ts",
|
|
@@ -690,15 +694,15 @@
|
|
|
690
694
|
},
|
|
691
695
|
{
|
|
692
696
|
"path": "src/codex/catalog/parsing.ts",
|
|
693
|
-
"sha256": "
|
|
697
|
+
"sha256": "0998690562c0f00554630f2070205f6864d8f573ffcaa809d0def6c67f21be6c"
|
|
694
698
|
},
|
|
695
699
|
{
|
|
696
700
|
"path": "src/codex/catalog/provider-fetch.ts",
|
|
697
|
-
"sha256": "
|
|
701
|
+
"sha256": "472501e50bc4c82cfc0df13fd534b9ca9b391f3207b8bee74543d6cc999c3d30"
|
|
698
702
|
},
|
|
699
703
|
{
|
|
700
704
|
"path": "src/codex/catalog/sync.ts",
|
|
701
|
-
"sha256": "
|
|
705
|
+
"sha256": "d586a2d468cf4120da088f0bdc3088751c0e7015a9fc5545dd5584804d1e3525"
|
|
702
706
|
},
|
|
703
707
|
{
|
|
704
708
|
"path": "src/codex/codex-write-lock.ts",
|
|
@@ -710,7 +714,7 @@
|
|
|
710
714
|
},
|
|
711
715
|
{
|
|
712
716
|
"path": "src/codex/convergence.ts",
|
|
713
|
-
"sha256": "
|
|
717
|
+
"sha256": "ffe1a891c51b33ace33d99bb11ca36015544189f8c4bb1966c2bb08469a163fb"
|
|
714
718
|
},
|
|
715
719
|
{
|
|
716
720
|
"path": "src/codex/custom-model-catalog-migration.ts",
|
|
@@ -770,7 +774,7 @@
|
|
|
770
774
|
},
|
|
771
775
|
{
|
|
772
776
|
"path": "src/codex/inject.ts",
|
|
773
|
-
"sha256": "
|
|
777
|
+
"sha256": "fd020e956a6b7b50d256c9bdd7d585172cc7662993e4eb21fe2c55b0e2387e37"
|
|
774
778
|
},
|
|
775
779
|
{
|
|
776
780
|
"path": "src/codex/injected-marker.ts",
|
|
@@ -792,6 +796,38 @@
|
|
|
792
796
|
"path": "src/codex/journal.ts",
|
|
793
797
|
"sha256": "f90fcb039ff2ca8ddbdfe8e9101b431e4c8d864cc18923bbbc98c671b1b7adb0"
|
|
794
798
|
},
|
|
799
|
+
{
|
|
800
|
+
"path": "src/codex/log-guard/inspect.ts",
|
|
801
|
+
"sha256": "b2b4153ff668d3f3f89322e21bee5af375fa7f92d8f4039e8db88f1f040c8357"
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
"path": "src/codex/log-guard/lock.ts",
|
|
805
|
+
"sha256": "b4ce32198272163541652b9864f892eeff8f681e7807de67c321c6c8901b4384"
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
"path": "src/codex/log-guard/maintenance.ts",
|
|
809
|
+
"sha256": "f11d9c7d774719715a8b3b33e7573ab09eca33fa4f57e853479a4fb5d8617786"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
"path": "src/codex/log-guard/path-safety.ts",
|
|
813
|
+
"sha256": "8df1c28dafbe16417dcc474fe06c63ec0d6b8367d47971569b4a80a17a614e16"
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
"path": "src/codex/log-guard/policy.ts",
|
|
817
|
+
"sha256": "ba282dba9271822d1778256f28dd4e8087f83dd282ce63dc06539e46db0da58b"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
"path": "src/codex/log-guard/processes.ts",
|
|
821
|
+
"sha256": "a1388d9adfedc5918d0fdce83ae1f0577ede9a23f07789ba46ced5351a05edb1"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
"path": "src/codex/log-guard/protection.ts",
|
|
825
|
+
"sha256": "92c1e86ec698bba7953f1e0d389383e76d1c7f05206d6beaa5f1eccc451e9b9b"
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"path": "src/codex/log-guard/sqlite-errors.ts",
|
|
829
|
+
"sha256": "aa4335d71cf642e9b98b066af9204e6d66fff49a7f95b14c50b4c82abd1cce6f"
|
|
830
|
+
},
|
|
795
831
|
{
|
|
796
832
|
"path": "src/codex/main-account-cache.ts",
|
|
797
833
|
"sha256": "1fe336e582bce085b1e37d8a1925b60e91ec667706f80d939974d41062673f6e"
|
|
@@ -866,7 +902,7 @@
|
|
|
866
902
|
},
|
|
867
903
|
{
|
|
868
904
|
"path": "src/codex/paths.ts",
|
|
869
|
-
"sha256": "
|
|
905
|
+
"sha256": "f14512fec482d38044172cee72532372bb32757f964b4c2c90f1349c4d07c32c"
|
|
870
906
|
},
|
|
871
907
|
{
|
|
872
908
|
"path": "src/codex/plan.ts",
|
|
@@ -874,7 +910,7 @@
|
|
|
874
910
|
},
|
|
875
911
|
{
|
|
876
912
|
"path": "src/codex/plugins-doctor.ts",
|
|
877
|
-
"sha256": "
|
|
913
|
+
"sha256": "51344b58e99a143d2c7d397481e5b2a26c86ab0a1c61db5b153068793e4d3cab"
|
|
878
914
|
},
|
|
879
915
|
{
|
|
880
916
|
"path": "src/codex/pool-rotation.ts",
|
|
@@ -882,7 +918,7 @@
|
|
|
882
918
|
},
|
|
883
919
|
{
|
|
884
920
|
"path": "src/codex/project-config-warnings.ts",
|
|
885
|
-
"sha256": "
|
|
921
|
+
"sha256": "b7543a8d5bd7ff439398480360d9f8b5840ade77e1db19155994d298136ef868"
|
|
886
922
|
},
|
|
887
923
|
{
|
|
888
924
|
"path": "src/codex/prompt-journal.ts",
|
|
@@ -1962,7 +1998,7 @@
|
|
|
1962
1998
|
},
|
|
1963
1999
|
{
|
|
1964
2000
|
"path": "src/providers/antigravity-models.ts",
|
|
1965
|
-
"sha256": "
|
|
2001
|
+
"sha256": "1382f985bf4fdf525fb4425d5040e7244460f441f492b22ddc493226e5b778d1"
|
|
1966
2002
|
},
|
|
1967
2003
|
{
|
|
1968
2004
|
"path": "src/providers/api-keys.ts",
|
|
@@ -2018,7 +2054,7 @@
|
|
|
2018
2054
|
},
|
|
2019
2055
|
{
|
|
2020
2056
|
"path": "src/providers/model-discovery.ts",
|
|
2021
|
-
"sha256": "
|
|
2057
|
+
"sha256": "bcf2a1b01dc8cdad91bd2b2e3ee935107e203905649985ee57691d33e3fed9c9"
|
|
2022
2058
|
},
|
|
2023
2059
|
{
|
|
2024
2060
|
"path": "src/providers/model-rename-migration.ts",
|
|
@@ -2058,11 +2094,11 @@
|
|
|
2058
2094
|
},
|
|
2059
2095
|
{
|
|
2060
2096
|
"path": "src/providers/quota.ts",
|
|
2061
|
-
"sha256": "
|
|
2097
|
+
"sha256": "828c4304b6b67d2a71d85141f8da0d2a59453f782c3de46a1905e68903750375"
|
|
2062
2098
|
},
|
|
2063
2099
|
{
|
|
2064
2100
|
"path": "src/providers/registry.ts",
|
|
2065
|
-
"sha256": "
|
|
2101
|
+
"sha256": "363d273b5dbf67ed5b3491578f18c5d408d10b546a21a4a7cad3697606a47047"
|
|
2066
2102
|
},
|
|
2067
2103
|
{
|
|
2068
2104
|
"path": "src/providers/request-pacing.ts",
|
|
@@ -2074,7 +2110,7 @@
|
|
|
2074
2110
|
},
|
|
2075
2111
|
{
|
|
2076
2112
|
"path": "src/providers/slug-codec.ts",
|
|
2077
|
-
"sha256": "
|
|
2113
|
+
"sha256": "733bfa8cb48e8e9baa678f5aefbf579f7eaa5eb05b16433779bd26e39b41ffe8"
|
|
2078
2114
|
},
|
|
2079
2115
|
{
|
|
2080
2116
|
"path": "src/providers/static-model-discovery.ts",
|
|
@@ -2134,7 +2170,7 @@
|
|
|
2134
2170
|
},
|
|
2135
2171
|
{
|
|
2136
2172
|
"path": "src/router.ts",
|
|
2137
|
-
"sha256": "
|
|
2173
|
+
"sha256": "574c51a5c95350217d72b6fa82b06806251e6706265866e909fec729882f82fb"
|
|
2138
2174
|
},
|
|
2139
2175
|
{
|
|
2140
2176
|
"path": "src/routing/analytics.ts",
|
|
@@ -2142,7 +2178,7 @@
|
|
|
2142
2178
|
},
|
|
2143
2179
|
{
|
|
2144
2180
|
"path": "src/routing/capability.ts",
|
|
2145
|
-
"sha256": "
|
|
2181
|
+
"sha256": "00606e95e636abd8612bb6825c6cb8c2a62ca1eb396ebebba64501f34cd0e060"
|
|
2146
2182
|
},
|
|
2147
2183
|
{
|
|
2148
2184
|
"path": "src/routing/compatibility/assemble.ts",
|
|
@@ -2286,7 +2322,7 @@
|
|
|
2286
2322
|
},
|
|
2287
2323
|
{
|
|
2288
2324
|
"path": "src/server/index.ts",
|
|
2289
|
-
"sha256": "
|
|
2325
|
+
"sha256": "2745fca63c657742f90c22f2a251a3bf1702aca0c5c5b24cfab9d01dad39cd93"
|
|
2290
2326
|
},
|
|
2291
2327
|
{
|
|
2292
2328
|
"path": "src/server/lifecycle.ts",
|
|
@@ -2306,7 +2342,7 @@
|
|
|
2306
2342
|
},
|
|
2307
2343
|
{
|
|
2308
2344
|
"path": "src/server/management-api.ts",
|
|
2309
|
-
"sha256": "
|
|
2345
|
+
"sha256": "4736d3b1030eff60d06b22e3339bb25f66062d9d3bf0c2becc701f5b10bcc032"
|
|
2310
2346
|
},
|
|
2311
2347
|
{
|
|
2312
2348
|
"path": "src/server/management-auth.ts",
|
|
@@ -2314,7 +2350,7 @@
|
|
|
2314
2350
|
},
|
|
2315
2351
|
{
|
|
2316
2352
|
"path": "src/server/management/agent-settings-routes.ts",
|
|
2317
|
-
"sha256": "
|
|
2353
|
+
"sha256": "d22e53a8f698e29c78905944a8c696522a0fe468bda72da4fb5f6491aa0b0cc6"
|
|
2318
2354
|
},
|
|
2319
2355
|
{
|
|
2320
2356
|
"path": "src/server/management/api-access.ts",
|
|
@@ -2338,7 +2374,7 @@
|
|
|
2338
2374
|
},
|
|
2339
2375
|
{
|
|
2340
2376
|
"path": "src/server/management/context.ts",
|
|
2341
|
-
"sha256": "
|
|
2377
|
+
"sha256": "85564a590d77fb49f7aab7a0cf4729ca6d704bcef4b3a68b5a437d87809aa212"
|
|
2342
2378
|
},
|
|
2343
2379
|
{
|
|
2344
2380
|
"path": "src/server/management/integration-routes.ts",
|
|
@@ -2358,7 +2394,7 @@
|
|
|
2358
2394
|
},
|
|
2359
2395
|
{
|
|
2360
2396
|
"path": "src/server/management/model-routes.ts",
|
|
2361
|
-
"sha256": "
|
|
2397
|
+
"sha256": "de001901177f757c8a611f808e63ae55f64c6d81a9a5db6f29ac6bbb28f5c6f9"
|
|
2362
2398
|
},
|
|
2363
2399
|
{
|
|
2364
2400
|
"path": "src/server/management/model-rows.ts",
|
|
@@ -2400,6 +2436,10 @@
|
|
|
2400
2436
|
"path": "src/server/management/sidebar-routes.ts",
|
|
2401
2437
|
"sha256": "0926c61288d6344456516c4208ea788192739d5d4c47c5566baf861313b75301"
|
|
2402
2438
|
},
|
|
2439
|
+
{
|
|
2440
|
+
"path": "src/server/management/storage-log-guard-routes.ts",
|
|
2441
|
+
"sha256": "76491da8b595410593405d8ae7fbfa0b870653277f801b75c01086fa69ca3445"
|
|
2442
|
+
},
|
|
2403
2443
|
{
|
|
2404
2444
|
"path": "src/server/management/sync-response.ts",
|
|
2405
2445
|
"sha256": "580cd5b67c1b1c7ac2a25262da00dc4dd31c7959e506622fc13a2f240d800786"
|
|
@@ -2514,7 +2554,7 @@
|
|
|
2514
2554
|
},
|
|
2515
2555
|
{
|
|
2516
2556
|
"path": "src/server/responses/core.ts",
|
|
2517
|
-
"sha256": "
|
|
2557
|
+
"sha256": "8a4539f6e9fd8a7016bc7562316525f7ce0f236481ec51c63d14a6ca253d1786"
|
|
2518
2558
|
},
|
|
2519
2559
|
{
|
|
2520
2560
|
"path": "src/server/responses/empty-completion-guard.ts",
|
|
@@ -2674,7 +2714,7 @@
|
|
|
2674
2714
|
},
|
|
2675
2715
|
{
|
|
2676
2716
|
"path": "src/types.ts",
|
|
2677
|
-
"sha256": "
|
|
2717
|
+
"sha256": "a1aa12232d1424a93598402de73b7455bf5d33892c67ef0772b8c60a44f64560"
|
|
2678
2718
|
},
|
|
2679
2719
|
{
|
|
2680
2720
|
"path": "src/update/badge.ts",
|
|
@@ -331,7 +331,17 @@ export function parseAntigravityAvailableModels(
|
|
|
331
331
|
out.push({
|
|
332
332
|
id,
|
|
333
333
|
...(antigravityPositiveInteger(info.maxTokens) ? { contextWindow: antigravityPositiveInteger(info.maxTokens) } : {}),
|
|
334
|
-
|
|
334
|
+
// Tri-state, deliberately not a ternary: `true` asserts image support,
|
|
335
|
+
// `false` asserts against it, and ABSENT is unknown. Collapsing absent into
|
|
336
|
+
// `["text"]` let routing read it as a confident `image: false` (#1796). The
|
|
337
|
+
// strict catalog still receives its `["text"]` compatibility default
|
|
338
|
+
// downstream via ensureStrictCatalogFields; only the routing-evidence
|
|
339
|
+
// channel stays honest about what was never asserted.
|
|
340
|
+
...(info.supportsImages === true
|
|
341
|
+
? { inputModalities: ["text", "image"] as string[] }
|
|
342
|
+
: info.supportsImages === false
|
|
343
|
+
? { inputModalities: ["text"] as string[] }
|
|
344
|
+
: {}),
|
|
335
345
|
});
|
|
336
346
|
}
|
|
337
347
|
return out;
|
|
@@ -99,7 +99,12 @@ export function providerModelDiscoverySpecError(spec: ProviderModelDiscoverySpec
|
|
|
99
99
|
return "discovery path must be a query-free relative/origin path";
|
|
100
100
|
}
|
|
101
101
|
if (path.includes("\\")) return "discovery path must use forward slashes";
|
|
102
|
-
|
|
102
|
+
const segments = path.split("/");
|
|
103
|
+
if (segments.some((segment, index) => {
|
|
104
|
+
const decoded = segment.replace(/%2e/gi, ".");
|
|
105
|
+
if (decoded !== "..") return false;
|
|
106
|
+
return index !== 0 || segments.filter(s => s.replace(/%2e/gi, ".") === "..").length !== 1;
|
|
107
|
+
})) {
|
|
103
108
|
return "discovery path must not contain parent-directory segments";
|
|
104
109
|
}
|
|
105
110
|
}
|
|
@@ -326,12 +331,83 @@ export function extractModelEnvelopeRows(
|
|
|
326
331
|
}
|
|
327
332
|
|
|
328
333
|
/** Validate, bound, deduplicate, and declaratively filter OpenAI `{data:[...]}` or top-level arrays (Together `#617`). */
|
|
334
|
+
/**
|
|
335
|
+
* Metadata a sibling `models[]` array may contribute to an ALREADY-ADMITTED
|
|
336
|
+
* `data[]` row (#1797).
|
|
337
|
+
*
|
|
338
|
+
* llama.cpp serves a dual-envelope body: an Ollama-style `models[]` array
|
|
339
|
+
* carrying `capabilities` alongside the OpenAI-style `data[]` array carrying
|
|
340
|
+
* `meta`, so the two halves of one model's metadata never meet and a server
|
|
341
|
+
* that truthfully advertises "multimodal" produced an image-blind row.
|
|
342
|
+
*
|
|
343
|
+
* Two boundaries make this safe, and both were added after review found the
|
|
344
|
+
* first attempt unsound:
|
|
345
|
+
*
|
|
346
|
+
* 1. It runs AFTER admission filtering. Enriching first let a sibling supply
|
|
347
|
+
* the exact field a provider filter requires — reproduced against the real
|
|
348
|
+
* Chutes policy, where a row lacking `supported_features: ["tools"]` was
|
|
349
|
+
* admitted once a same-id sibling provided it. Enrichment may change what is
|
|
350
|
+
* KNOWN about a model, never WHICH models are published.
|
|
351
|
+
* 2. Only the capability keys #1797 needs are copied. A blanket "fill every
|
|
352
|
+
* absent key" made the untrusted `models[]` array a way into any field the
|
|
353
|
+
* pipeline consumes.
|
|
354
|
+
*
|
|
355
|
+
* The list deliberately EXCLUDES `supported_features` and `features`, even
|
|
356
|
+
* though both are capability-shaped: they are the two keys real provider
|
|
357
|
+
* filters test (`registry.ts:1591` requires `supported_features` to contain
|
|
358
|
+
* "tools"; `registry.ts:1883` tests `features.tool_use`). Ordering already
|
|
359
|
+
* prevents a sibling from flipping an admission verdict, but a key that is
|
|
360
|
+
* both enrichable and filter-relevant is one refactor away from becoming a
|
|
361
|
+
* bypass again. #1797 does not need them.
|
|
362
|
+
*/
|
|
363
|
+
const SIBLING_ENRICHABLE_KEYS = new Set(["capabilities", "modalities", "input_modalities"]);
|
|
364
|
+
|
|
365
|
+
type SiblingIndex = Map<string, Record<string, unknown> | null>;
|
|
366
|
+
|
|
367
|
+
function buildSiblingIndex(value: unknown, limit: number): SiblingIndex | null {
|
|
368
|
+
const record = plainObject(value);
|
|
369
|
+
const sibling = record?.models;
|
|
370
|
+
if (!Array.isArray(sibling) || sibling.length === 0 || sibling.length > limit) return null;
|
|
371
|
+
|
|
372
|
+
const byId: SiblingIndex = new Map();
|
|
373
|
+
for (const raw of sibling) {
|
|
374
|
+
const entry = plainObject(raw);
|
|
375
|
+
if (!entry) continue;
|
|
376
|
+
for (const key of ["id", "model", "name"]) {
|
|
377
|
+
const id = entry[key];
|
|
378
|
+
if (typeof id !== "string" || id.length === 0) continue;
|
|
379
|
+
// `null` marks an ambiguous id: two sibling entries claim it, so neither
|
|
380
|
+
// can be attributed with confidence and both are ignored.
|
|
381
|
+
byId.set(id, byId.has(id) && byId.get(id) !== entry ? null : entry);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return byId.size > 0 ? byId : null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function enrichAdmittedModel(item: ProviderModelsApiItem, siblings: SiblingIndex): ProviderModelsApiItem {
|
|
388
|
+
const extra = siblings.get(item.id);
|
|
389
|
+
if (!extra) return item;
|
|
390
|
+
let merged: Record<string, unknown> | null = null;
|
|
391
|
+
for (const key of SIBLING_ENRICHABLE_KEYS) {
|
|
392
|
+
if (!(key in extra) || key in item) continue;
|
|
393
|
+
merged ??= { ...(item as Record<string, unknown>) };
|
|
394
|
+
merged[key] = extra[key];
|
|
395
|
+
}
|
|
396
|
+
return (merged ?? item) as ProviderModelsApiItem;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function plainObject(value: unknown): Record<string, unknown> | null {
|
|
400
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
401
|
+
? value as Record<string, unknown>
|
|
402
|
+
: null;
|
|
403
|
+
}
|
|
329
404
|
export function extractProviderModelItems(
|
|
330
405
|
value: unknown,
|
|
331
406
|
discovery: ResolvedProviderModelDiscovery,
|
|
332
407
|
): ProviderModelItemsResult {
|
|
333
408
|
const limit = positiveIntegerAtMost(discovery.maxModels, MODEL_DISCOVERY_MAX_MODELS);
|
|
334
409
|
let data: unknown[];
|
|
410
|
+
let siblings: SiblingIndex | null = null;
|
|
335
411
|
if (Array.isArray(value)) {
|
|
336
412
|
// Together-style top-level /models arrays. Catalog discovery must not treat a stray
|
|
337
413
|
// `models` key on openai-chat responses as valid — only `data` envelopes or top-level arrays.
|
|
@@ -341,6 +417,7 @@ export function extractProviderModelItems(
|
|
|
341
417
|
const envelope = extractModelEnvelopeRows(value, discovery.maxModels, ["data"]);
|
|
342
418
|
if (!envelope.ok) return envelope;
|
|
343
419
|
data = envelope.rows;
|
|
420
|
+
siblings = buildSiblingIndex(value, limit);
|
|
344
421
|
}
|
|
345
422
|
|
|
346
423
|
const items: ProviderModelsApiItem[] = [];
|
|
@@ -351,11 +428,22 @@ export function extractProviderModelItems(
|
|
|
351
428
|
}
|
|
352
429
|
const id = (raw as { id?: unknown }).id;
|
|
353
430
|
if (!isValidModelDiscoveryModelId(id)) return { ok: false, reason: "invalid_shape" };
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
|
|
431
|
+
const prefix = discovery.spec?.stripIdPrefix;
|
|
432
|
+
let finalId = id;
|
|
433
|
+
if (prefix && finalId.startsWith(prefix)) {
|
|
434
|
+
finalId = finalId.slice(prefix.length);
|
|
435
|
+
if (!isValidModelDiscoveryModelId(finalId)) continue;
|
|
436
|
+
}
|
|
437
|
+
const item = finalId === id ? raw as ProviderModelsApiItem : { ...(raw as ProviderModelsApiItem), id: finalId };
|
|
438
|
+
// Admission is decided on the ORIGINAL `data[]` row, before any sibling
|
|
439
|
+
// enrichment. Merging first let a `models[]` entry supply the very field a
|
|
440
|
+
// provider filter requires — reproduced against the real Chutes policy,
|
|
441
|
+
// where a row lacking `supported_features: ["tools"]` was admitted once a
|
|
442
|
+
// same-id sibling provided it. Enrichment must never change WHICH models
|
|
443
|
+
// are published, only what is known about an already-admitted one.
|
|
444
|
+
if (!providerModelMatchesDiscoveryFilter(item, discovery.spec?.filter) || seen.has(finalId)) continue;
|
|
445
|
+
seen.add(finalId);
|
|
446
|
+
items.push(siblings ? enrichAdmittedModel(item, siblings) : item);
|
|
359
447
|
}
|
|
360
448
|
return { ok: true, items, rawCount: data.length };
|
|
361
449
|
}
|
package/src/providers/quota.ts
CHANGED
|
@@ -38,6 +38,11 @@ const REQUEST_TIMEOUT_MS = 8_000;
|
|
|
38
38
|
export const QUOTA_RESPONSE_MAX_BYTES = 512 * 1024;
|
|
39
39
|
const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
40
40
|
const KIMI_CODE_USAGE_URL = `${KIMI_CODE_BASE_URL}/usages`;
|
|
41
|
+
const COMMAND_CODE_BASE_URL = "https://api.commandcode.ai";
|
|
42
|
+
const COMMAND_CODE_WHOAMI_URL = `${COMMAND_CODE_BASE_URL}/alpha/whoami`;
|
|
43
|
+
const COMMAND_CODE_CREDITS_URL = `${COMMAND_CODE_BASE_URL}/alpha/billing/credits`;
|
|
44
|
+
const COMMAND_CODE_SUBSCRIPTIONS_URL = `${COMMAND_CODE_BASE_URL}/alpha/billing/subscriptions`;
|
|
45
|
+
const COMMAND_CODE_USAGE_URL = `${COMMAND_CODE_BASE_URL}/alpha/usage/summary`;
|
|
41
46
|
const A6API_BASE_URL = "https://api.a6api.com";
|
|
42
47
|
const OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1";
|
|
43
48
|
const OPENCODE_GO_USAGE_URL = `${OPENCODE_GO_BASE_URL}/usage`;
|
|
@@ -1510,6 +1515,12 @@ function isCanonicalKimiCodeBaseUrl(baseUrl: string): boolean {
|
|
|
1510
1515
|
return normalizedBaseUrl(baseUrl) === KIMI_CODE_BASE_URL;
|
|
1511
1516
|
}
|
|
1512
1517
|
|
|
1518
|
+
function isCanonicalCommandCodeBaseUrl(baseUrl: string): boolean {
|
|
1519
|
+
const normalized = normalizedBaseUrl(baseUrl);
|
|
1520
|
+
// OAuth preset points at the API root; the Provider-API preset at /provider/v1.
|
|
1521
|
+
return normalized === COMMAND_CODE_BASE_URL || normalized === `${COMMAND_CODE_BASE_URL}/provider/v1`;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1513
1524
|
/** Prefer the nested `data` shell when the outer object is only an envelope. */
|
|
1514
1525
|
function unwrapKimiQuotaPayload(value: unknown): Record<string, unknown> | null {
|
|
1515
1526
|
const body = asRecord(value);
|
|
@@ -1631,6 +1642,145 @@ async function fetchKimiQuota(provider: string, config: OcxProviderConfig): Prom
|
|
|
1631
1642
|
return quota ? report(provider, "kimi:usages", quota) : null;
|
|
1632
1643
|
}
|
|
1633
1644
|
|
|
1645
|
+
/**
|
|
1646
|
+
* Command Code rolling window: `{ cap, used, resetAt }` off /alpha/billing/credits,
|
|
1647
|
+
* normalized to a percent with an optional reset timestamp.
|
|
1648
|
+
*/
|
|
1649
|
+
function parseCommandCodeWindow(value: unknown): { percent: number; resetAt?: number } | null {
|
|
1650
|
+
const row = asRecord(value);
|
|
1651
|
+
if (!row) return null;
|
|
1652
|
+
const cap = toFiniteNumber(row.cap);
|
|
1653
|
+
const used = toFiniteNumber(row.used);
|
|
1654
|
+
if (cap === undefined || used === undefined || cap <= 0 || used < 0) return null;
|
|
1655
|
+
const percent = normalizePercent((used / cap) * 100);
|
|
1656
|
+
if (percent === undefined) return null;
|
|
1657
|
+
const resetAt = quotaResetAt(row);
|
|
1658
|
+
return { percent, ...(resetAt !== undefined ? { resetAt } : {}) };
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/** Soft-fail GET returning a parsed record, or null when unavailable. */
|
|
1662
|
+
async function fetchCommandCodeJson(url: string, bearer: string): Promise<Record<string, unknown> | null> {
|
|
1663
|
+
try {
|
|
1664
|
+
const response = await fetch(url, {
|
|
1665
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
|
1666
|
+
redirect: "error",
|
|
1667
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
1668
|
+
});
|
|
1669
|
+
if (!response.ok) return null;
|
|
1670
|
+
return asRecord(await readQuotaJson(response));
|
|
1671
|
+
} catch {
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/**
|
|
1677
|
+
* Soft-fail period spend (used) against the remaining credit pools → creditsUsd.
|
|
1678
|
+
* Period scoping: `since=<currentPeriodStart>` keeps spend aligned with the
|
|
1679
|
+
* pools' billing cycle, and `currentPeriodEnd` becomes expiresAt.
|
|
1680
|
+
*/
|
|
1681
|
+
async function fetchCommandCodeSpend(
|
|
1682
|
+
bearer: string,
|
|
1683
|
+
credits: Record<string, unknown> | null,
|
|
1684
|
+
orgQuery: string,
|
|
1685
|
+
): Promise<ProviderQuotaCreditsUsd | undefined> {
|
|
1686
|
+
if (!credits) return undefined;
|
|
1687
|
+
const subscriptionBody = await fetchCommandCodeJson(`${COMMAND_CODE_SUBSCRIPTIONS_URL}${orgQuery}`, bearer);
|
|
1688
|
+
const subscription = asRecord(subscriptionBody?.data) ?? subscriptionBody;
|
|
1689
|
+
const periodStart = typeof subscription?.currentPeriodStart === "string" ? subscription.currentPeriodStart.trim() : "";
|
|
1690
|
+
// Unscoped /usage/summary is lifetime spend; mixing it with current-cycle
|
|
1691
|
+
// remaining pools produces a wrong percent. Omit creditsUsd until a period exists.
|
|
1692
|
+
if (!periodStart) return undefined;
|
|
1693
|
+
const sinceQuery = `${orgQuery ? "&" : "?"}since=${encodeURIComponent(periodStart)}`;
|
|
1694
|
+
const expiresAt = normalizeResetAt(subscription?.currentPeriodEnd);
|
|
1695
|
+
const summaryBody = await fetchCommandCodeJson(`${COMMAND_CODE_USAGE_URL}${orgQuery}${sinceQuery}`, bearer);
|
|
1696
|
+
const summary = asRecord(summaryBody?.data) ?? summaryBody;
|
|
1697
|
+
const used = toFiniteNumber(summary?.totalCost) ?? toFiniteNumber(summary?.totalMonthlyCredits);
|
|
1698
|
+
if (used === undefined || used < 0) return undefined;
|
|
1699
|
+
const pools = [credits.monthlyCredits, credits.purchasedCredits, credits.freeCredits]
|
|
1700
|
+
.map(value => toFiniteNumber(value))
|
|
1701
|
+
.filter((value): value is number => value !== undefined);
|
|
1702
|
+
// Field presence is what separates a real balance from absent data: an exhausted
|
|
1703
|
+
// all-zero account still reports remaining=0, while no remaining-credit field at
|
|
1704
|
+
// all means there is nothing to meter.
|
|
1705
|
+
if (pools.length === 0) return undefined;
|
|
1706
|
+
const remaining = pools.reduce((sum, value) => sum + Math.max(0, value ?? 0), 0);
|
|
1707
|
+
const limit = used + remaining;
|
|
1708
|
+
const percent = normalizePercent(limit > 0 ? (used / limit) * 100 : 0);
|
|
1709
|
+
// Purchased credits roll over past the subscription period end, so an expiry is
|
|
1710
|
+
// only truthful when the aggregate contains no non-expiring purchased pool.
|
|
1711
|
+
const purchased = toFiniteNumber(credits.purchasedCredits) ?? 0;
|
|
1712
|
+
return percent === undefined
|
|
1713
|
+
? undefined
|
|
1714
|
+
: {
|
|
1715
|
+
used,
|
|
1716
|
+
limit,
|
|
1717
|
+
remaining,
|
|
1718
|
+
percent,
|
|
1719
|
+
...(expiresAt !== undefined && purchased <= 0 ? { expiresAt } : {}),
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/** OAuth access token or ACTIVE Provider-API key for the Command Code quota probe. */
|
|
1724
|
+
async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig): Promise<string | null> {
|
|
1725
|
+
if (config.authMode === "oauth") {
|
|
1726
|
+
try {
|
|
1727
|
+
return await getValidAccessToken("command-code");
|
|
1728
|
+
} catch {
|
|
1729
|
+
return null;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
// ACTIVE key only: a quota bar for a different account than the one routing
|
|
1733
|
+
// requests is a wrong meter, not a helpful one.
|
|
1734
|
+
return resolveEnvValue(config.apiKey)?.trim() || null;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* Command Code `GET /alpha/billing/credits` — the same Bearer surface the CLI's
|
|
1739
|
+
* usage view uses (windowLimits.fiveHour / windowLimits.weekly), plus soft
|
|
1740
|
+
* whoami (team orgId scoping) and subscription-scoped spend for creditsUsd.
|
|
1741
|
+
*/
|
|
1742
|
+
async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
1743
|
+
// Never release credentials to a user-edited or lookalike provider host.
|
|
1744
|
+
if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
|
|
1745
|
+
const bearer = await resolveCommandCodeQuotaBearer(config);
|
|
1746
|
+
if (!bearer) return null;
|
|
1747
|
+
const whoamiBody = await fetchCommandCodeJson(COMMAND_CODE_WHOAMI_URL, bearer);
|
|
1748
|
+
const whoami = asRecord(whoamiBody?.data) ?? whoamiBody;
|
|
1749
|
+
const org = asRecord(whoami?.org);
|
|
1750
|
+
const orgId = typeof org?.id === "string" && org.id.trim() ? org.id.trim() : null;
|
|
1751
|
+
const orgQuery = orgId ? `?orgId=${encodeURIComponent(orgId)}` : "";
|
|
1752
|
+
const response = await fetch(`${COMMAND_CODE_CREDITS_URL}${orgQuery}`, {
|
|
1753
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
|
1754
|
+
redirect: "error",
|
|
1755
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
1756
|
+
});
|
|
1757
|
+
if (!response.ok) {
|
|
1758
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
1759
|
+
? TERMINAL_QUOTA_FAILURE
|
|
1760
|
+
: null;
|
|
1761
|
+
}
|
|
1762
|
+
const raw = asRecord(await readQuotaJson(response));
|
|
1763
|
+
const body = asRecord(raw?.data) ?? raw;
|
|
1764
|
+
const credits = asRecord(body?.credits);
|
|
1765
|
+
const limits = asRecord(body?.windowLimits);
|
|
1766
|
+
if (!credits && !limits) return null;
|
|
1767
|
+
const fiveHour = parseCommandCodeWindow(limits?.fiveHour);
|
|
1768
|
+
const weekly = parseCommandCodeWindow(limits?.weekly);
|
|
1769
|
+
const creditsUsd = await fetchCommandCodeSpend(bearer, credits, orgQuery);
|
|
1770
|
+
return report(provider, "command-code:credits", {
|
|
1771
|
+
...(fiveHour ? {
|
|
1772
|
+
fiveHourPercent: fiveHour.percent,
|
|
1773
|
+
...(fiveHour.resetAt !== undefined ? { fiveHourResetAt: fiveHour.resetAt } : {}),
|
|
1774
|
+
} : {}),
|
|
1775
|
+
...(weekly ? {
|
|
1776
|
+
weeklyPercent: weekly.percent,
|
|
1777
|
+
...(weekly.resetAt !== undefined ? { weeklyResetAt: weekly.resetAt } : {}),
|
|
1778
|
+
} : {}),
|
|
1779
|
+
...(creditsUsd ? { creditsUsd } : {}),
|
|
1780
|
+
updatedAt: Date.now(),
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1634
1784
|
/** Cursor included usage via api2.cursor.sh (Bearer from OAuth) — unofficial, may change. */
|
|
1635
1785
|
async function fetchCursorQuota(provider: string): Promise<ProviderQuotaReport | null> {
|
|
1636
1786
|
let accessToken: string;
|
|
@@ -1919,6 +2069,15 @@ async function maybeFetchProviderQuota(
|
|
|
1919
2069
|
if (provider.authMode === "key" && isCanonicalKimiCodeBaseUrl(provider.baseUrl)) {
|
|
1920
2070
|
return fetchKimiQuota(name, provider);
|
|
1921
2071
|
}
|
|
2072
|
+
// OAuth account login or Provider-API key only; forward/local modes carry no
|
|
2073
|
+
// credential of ours on the canonical host.
|
|
2074
|
+
if (provider.authMode === "oauth" && name === "command-code") {
|
|
2075
|
+
return fetchCommandCodeQuota(name, provider);
|
|
2076
|
+
}
|
|
2077
|
+
if ((provider.authMode ?? "key") === "key" && name === "commandcode"
|
|
2078
|
+
&& isCanonicalCommandCodeBaseUrl(provider.baseUrl)) {
|
|
2079
|
+
return fetchCommandCodeQuota(name, provider);
|
|
2080
|
+
}
|
|
1922
2081
|
if ((provider.authMode ?? "key") === "key" && name === "opencode-go") {
|
|
1923
2082
|
return fetchOpenCodeGoQuota(name, provider);
|
|
1924
2083
|
}
|