@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
|
@@ -41,6 +41,7 @@ import type { FastPolicyAuthority } from "../../providers/fastwire";
|
|
|
41
41
|
import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry";
|
|
42
42
|
import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models";
|
|
43
43
|
import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
|
|
44
|
+
import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
|
|
44
45
|
import { routedSlug, slugEquals, slugEquivalenceKey, slugsEquivalent } from "../../providers/slug-codec";
|
|
45
46
|
import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
|
|
46
47
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
@@ -75,7 +76,7 @@ import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } fr
|
|
|
75
76
|
|
|
76
77
|
import { CODEX_CUSTOM_MODEL_CATALOG_KIND, JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
77
78
|
import type { CatalogModel } from "./parsing";
|
|
78
|
-
import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
79
|
+
import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
79
80
|
import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
|
|
80
81
|
import type { ComboCatalogOmission } from "./aggregation";
|
|
81
82
|
import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
|
|
@@ -571,6 +572,7 @@ function providerCatalogFingerprint(name: string, prov: OcxProviderConfig): Reco
|
|
|
571
572
|
ctx: prov.contextWindow ?? null,
|
|
572
573
|
ctxW: prov.modelContextWindows ?? null,
|
|
573
574
|
maxIn: prov.modelMaxInputTokens ?? null,
|
|
575
|
+
autoCompact: prov.modelAutoCompactTokenLimits ?? null,
|
|
574
576
|
inMod: prov.modelInputModalities ?? null,
|
|
575
577
|
re: prov.modelReasoningEfforts ?? null,
|
|
576
578
|
defRe: prov.modelDefaultReasoningEfforts ?? null,
|
|
@@ -625,6 +627,17 @@ export function configuredMaxInputTokens(prov: OcxProviderConfig, id: string): n
|
|
|
625
627
|
return typeof configured === "number" && configured > 0 ? configured : undefined;
|
|
626
628
|
}
|
|
627
629
|
|
|
630
|
+
export function configuredAutoCompactTokenLimit(
|
|
631
|
+
prov: OcxProviderConfig | undefined,
|
|
632
|
+
id: string,
|
|
633
|
+
): number | undefined {
|
|
634
|
+
if (!prov) return undefined;
|
|
635
|
+
const configured = modelRecordValue(prov.modelAutoCompactTokenLimits, id);
|
|
636
|
+
return typeof configured === "number" && Number.isSafeInteger(configured) && configured > 0
|
|
637
|
+
? configured
|
|
638
|
+
: undefined;
|
|
639
|
+
}
|
|
640
|
+
|
|
628
641
|
function configuredReasoningSummarySupport(prov: OcxProviderConfig | undefined, id: string): boolean | undefined {
|
|
629
642
|
if (!prov) return undefined;
|
|
630
643
|
const explicit = modelRecordValue(prov.modelSupportsReasoningSummaries, id);
|
|
@@ -636,6 +649,7 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
|
|
|
636
649
|
void name;
|
|
637
650
|
const configuredCap = configuredContextWindow(prov, model.id);
|
|
638
651
|
const configuredMaxInput = configuredMaxInputTokens(prov, model.id);
|
|
652
|
+
const configuredAutoCompact = configuredAutoCompactTokenLimit(prov, model.id);
|
|
639
653
|
let inputModalities = configuredInputModalities(prov, model.id);
|
|
640
654
|
// Vision-sidecar coverage: `noVisionModels` marks models whose images the PROXY describes
|
|
641
655
|
// (src/vision/index.ts). The catalog must still advertise image input for them — the Codex app
|
|
@@ -689,10 +703,31 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
|
|
|
689
703
|
...(prov.codexToolMode !== undefined ? { codexToolMode: prov.codexToolMode } : {}),
|
|
690
704
|
};
|
|
691
705
|
const capped = applyProviderContextCap(hinted.contextWindow, providerCap);
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
706
|
+
const withCap = providerCap !== undefined
|
|
707
|
+
? capped !== hinted.contextWindow
|
|
708
|
+
? { ...hinted, contextWindow: capped, contextCap: providerCap, contextCapped: true }
|
|
709
|
+
: { ...hinted, contextCap: providerCap, contextCapped: false }
|
|
710
|
+
: hinted;
|
|
711
|
+
const contextWindow = typeof withCap.contextWindow === "number" && withCap.contextWindow > 0
|
|
712
|
+
? withCap.contextWindow
|
|
713
|
+
: undefined;
|
|
714
|
+
const boundedMaxInput = typeof withCap.maxInputTokens === "number" && withCap.maxInputTokens > 0
|
|
715
|
+
? (contextWindow !== undefined ? Math.min(withCap.maxInputTokens, contextWindow) : withCap.maxInputTokens)
|
|
716
|
+
: undefined;
|
|
717
|
+
const withHardBounds = boundedMaxInput !== undefined && boundedMaxInput !== withCap.maxInputTokens
|
|
718
|
+
? { ...withCap, maxInputTokens: boundedMaxInput }
|
|
719
|
+
: withCap;
|
|
720
|
+
const softCandidates = [model.autoCompactTokenLimit, configuredAutoCompact]
|
|
721
|
+
.filter((value): value is number => typeof value === "number" && value > 0);
|
|
722
|
+
if (contextWindow === undefined || softCandidates.length === 0) return withHardBounds;
|
|
723
|
+
return {
|
|
724
|
+
...withHardBounds,
|
|
725
|
+
autoCompactTokenLimit: clampAutoCompactTokenLimit(
|
|
726
|
+
contextWindow,
|
|
727
|
+
boundedMaxInput,
|
|
728
|
+
Math.min(...softCandidates),
|
|
729
|
+
),
|
|
730
|
+
};
|
|
696
731
|
}
|
|
697
732
|
|
|
698
733
|
export function catalogHintsFromProviderConfig(name: string, prov: OcxProviderConfig, id: string, contextCap?: number): Partial<CatalogModel> {
|
|
@@ -719,6 +754,7 @@ interface ComboCatalogMemberFallback {
|
|
|
719
754
|
readonly contextWindow?: number;
|
|
720
755
|
/** Input ceiling when it is lower than the window (native GPT-5.6: 922k under 1.05M). */
|
|
721
756
|
readonly maxInputTokens?: number;
|
|
757
|
+
readonly autoCompactTokenLimit?: number;
|
|
722
758
|
readonly inputModalities?: readonly string[];
|
|
723
759
|
readonly reasoningEfforts?: readonly string[];
|
|
724
760
|
}
|
|
@@ -747,26 +783,33 @@ export function resolveComboCatalogMember(
|
|
|
747
783
|
if (prov?.disabled === true) return undefined;
|
|
748
784
|
|
|
749
785
|
const withFallbackMetadata = (member: CatalogModel): CatalogModel => {
|
|
750
|
-
if (!fallback) return member;
|
|
751
786
|
const contextWindow = typeof member.contextWindow === "number" && member.contextWindow > 0
|
|
752
787
|
? member.contextWindow
|
|
753
788
|
: undefined;
|
|
754
|
-
const addMaxInput = contextWindow !== undefined
|
|
789
|
+
const addMaxInput = fallback !== undefined && contextWindow !== undefined
|
|
755
790
|
&& !(typeof member.maxInputTokens === "number" && member.maxInputTokens > 0);
|
|
791
|
+
const effectiveMaxInput = addMaxInput
|
|
792
|
+
? Math.min(fallback?.maxInputTokens ?? contextWindow!, contextWindow!)
|
|
793
|
+
: member.maxInputTokens;
|
|
794
|
+
const softCandidates = [member.autoCompactTokenLimit, fallback?.autoCompactTokenLimit]
|
|
795
|
+
.filter((value): value is number => typeof value === "number" && value > 0);
|
|
796
|
+
const autoCompactTokenLimit = contextWindow !== undefined && softCandidates.length > 0
|
|
797
|
+
? clampAutoCompactTokenLimit(contextWindow, effectiveMaxInput, Math.min(...softCandidates))
|
|
798
|
+
: member.autoCompactTokenLimit;
|
|
799
|
+
const adjustAutoCompact = autoCompactTokenLimit !== member.autoCompactTokenLimit;
|
|
756
800
|
const addModalities = (!Array.isArray(member.inputModalities) || member.inputModalities.length === 0)
|
|
757
|
-
&& fallback
|
|
801
|
+
&& fallback?.inputModalities !== undefined;
|
|
758
802
|
const addReasoning = member.reasoningEfforts === undefined
|
|
759
|
-
&& fallback
|
|
760
|
-
if (!addMaxInput && !addModalities && !addReasoning) return member;
|
|
803
|
+
&& fallback?.reasoningEfforts !== undefined;
|
|
804
|
+
if (!addMaxInput && !adjustAutoCompact && !addModalities && !addReasoning) return member;
|
|
761
805
|
return {
|
|
762
806
|
...member,
|
|
763
807
|
// Never claim a larger input budget than the window, and prefer the model's own
|
|
764
808
|
// measured ceiling when the fallback carries one.
|
|
765
|
-
...(addMaxInput
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
...(
|
|
769
|
-
...(addReasoning ? { reasoningEfforts: [...fallback.reasoningEfforts!] } : {}),
|
|
809
|
+
...(addMaxInput ? { maxInputTokens: effectiveMaxInput } : {}),
|
|
810
|
+
...(adjustAutoCompact && autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
|
|
811
|
+
...(addModalities ? { inputModalities: [...fallback!.inputModalities!] } : {}),
|
|
812
|
+
...(addReasoning ? { reasoningEfforts: [...fallback!.reasoningEfforts!] } : {}),
|
|
770
813
|
};
|
|
771
814
|
};
|
|
772
815
|
|
|
@@ -847,6 +890,20 @@ export function resolveComboCatalogMember(
|
|
|
847
890
|
const maxInputTokens = effectiveMaxInput !== undefined
|
|
848
891
|
? Math.min(effectiveMaxInput, contextWindow)
|
|
849
892
|
: contextWindow;
|
|
893
|
+
const softCandidates = [
|
|
894
|
+
hinted.autoCompactTokenLimit,
|
|
895
|
+
base.autoCompactTokenLimit,
|
|
896
|
+
fallback?.autoCompactTokenLimit,
|
|
897
|
+
configuredAutoCompactTokenLimit(prov, target.model),
|
|
898
|
+
].filter((value): value is number => typeof value === "number" && value > 0);
|
|
899
|
+
// A generic 128k synthesis is a catalog compatibility fallback, not evidence
|
|
900
|
+
// that a configured soft policy has an authoritative window to clamp against.
|
|
901
|
+
const hasAuthoritativeAutoCompactBasis = hintedContext !== undefined
|
|
902
|
+
|| fallbackContext !== undefined
|
|
903
|
+
|| contextCap !== undefined;
|
|
904
|
+
const autoCompactTokenLimit = hasAuthoritativeAutoCompactBasis && softCandidates.length > 0
|
|
905
|
+
? clampAutoCompactTokenLimit(contextWindow, maxInputTokens, Math.min(...softCandidates))
|
|
906
|
+
: undefined;
|
|
850
907
|
|
|
851
908
|
return {
|
|
852
909
|
...hinted,
|
|
@@ -854,6 +911,7 @@ export function resolveComboCatalogMember(
|
|
|
854
911
|
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
|
|
855
912
|
contextWindow,
|
|
856
913
|
maxInputTokens,
|
|
914
|
+
...(autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
|
|
857
915
|
...(fallbackCapped ? { contextCap, contextCapped: true as const } : {}),
|
|
858
916
|
};
|
|
859
917
|
}
|
|
@@ -1774,6 +1832,7 @@ async function gatherRoutedModelsUncached(
|
|
|
1774
1832
|
// stay separate fields because routed/API rows of the same family run a wider window.
|
|
1775
1833
|
// Falls back to the window for slugs with no separate ceiling.
|
|
1776
1834
|
maxInputTokens: Math.min(nativeOpenAiMaxInputTokens(slug, openaiContextCap) ?? contextWindow, contextWindow),
|
|
1835
|
+
autoCompactTokenLimit: nativeOpenAiAutoCompactTokenLimit(slug, openaiContextCap),
|
|
1777
1836
|
inputModalities: nativeInputModalities(slug),
|
|
1778
1837
|
reasoningEfforts: nativeReasoningEfforts(slug),
|
|
1779
1838
|
...(nativeParallelToolCalls(slug) ? { parallelToolCalls: true } : {}),
|
|
@@ -1790,18 +1849,23 @@ async function gatherRoutedModelsUncached(
|
|
|
1790
1849
|
for (const id of listComboIds(config)) {
|
|
1791
1850
|
const combo = getCombo(config, id);
|
|
1792
1851
|
if (!combo) continue;
|
|
1852
|
+
const comboNativeLimits = nativeContextLimits(config);
|
|
1793
1853
|
const nativeContextWindow = combo.nativeAlias && combo.alias
|
|
1794
|
-
? nativeOpenAiContextWindow(combo.alias,
|
|
1854
|
+
? nativeOpenAiContextWindow(combo.alias, comboNativeLimits)
|
|
1795
1855
|
: undefined;
|
|
1796
1856
|
const nativeAliasMaxInput = combo.nativeAlias && combo.alias
|
|
1797
1857
|
? (combo.alias.startsWith("gpt-5.6-") || combo.alias.includes("daybreak")
|
|
1798
1858
|
? NATIVE_GPT56_MAX_INPUT_TOKENS
|
|
1799
1859
|
: nativeOpenAiMaxInputTokens(combo.alias) ?? nativeOpenAiContextWindow(combo.alias))
|
|
1800
1860
|
: undefined;
|
|
1861
|
+
const nativeAliasAutoCompact = combo.nativeAlias && combo.alias
|
|
1862
|
+
? nativeOpenAiAutoCompactTokenLimit(combo.alias, comboNativeLimits)
|
|
1863
|
+
: undefined;
|
|
1801
1864
|
const nativeAliasFallback = combo.nativeAlias && combo.alias && nativeContextWindow !== undefined
|
|
1802
1865
|
? {
|
|
1803
1866
|
contextWindow: nativeContextWindow,
|
|
1804
1867
|
...(nativeAliasMaxInput !== undefined ? { maxInputTokens: nativeAliasMaxInput } : {}),
|
|
1868
|
+
...(nativeAliasAutoCompact !== undefined ? { autoCompactTokenLimit: nativeAliasAutoCompact } : {}),
|
|
1805
1869
|
inputModalities: nativeInputModalities(combo.alias),
|
|
1806
1870
|
reasoningEfforts: nativeReasoningEfforts(combo.alias),
|
|
1807
1871
|
}
|
|
@@ -1864,9 +1928,23 @@ async function gatherRoutedModelsUncached(
|
|
|
1864
1928
|
const nativeAliasMaxInputTokens = codexForwardNativeCapabilityAlias
|
|
1865
1929
|
? nativeOpenAiMaxInputTokens(cm.modelId, customNativeLimits)
|
|
1866
1930
|
: undefined;
|
|
1867
|
-
const
|
|
1868
|
-
?
|
|
1869
|
-
:
|
|
1931
|
+
const configuredMaxInput = rawProvider
|
|
1932
|
+
? configuredMaxInputTokens(rawProvider, cm.modelId)
|
|
1933
|
+
: undefined;
|
|
1934
|
+
const hardMaxCandidates = [nativeAliasMaxInputTokens, configuredMaxInput]
|
|
1935
|
+
.filter((value): value is number => typeof value === "number" && value > 0);
|
|
1936
|
+
const customMaxInputTokens = hardMaxCandidates.length > 0
|
|
1937
|
+
? Math.min(
|
|
1938
|
+
...hardMaxCandidates,
|
|
1939
|
+
...(customContextWindow !== undefined ? [customContextWindow] : []),
|
|
1940
|
+
)
|
|
1941
|
+
: undefined;
|
|
1942
|
+
const configuredAutoCompact = configuredAutoCompactTokenLimit(rawProvider, cm.modelId);
|
|
1943
|
+
const customAutoCompactTokenLimit = codexForwardNativeCapabilityAlias
|
|
1944
|
+
? nativeOpenAiAutoCompactTokenLimit(cm.modelId, customNativeLimits)
|
|
1945
|
+
: customContextWindow !== undefined && configuredAutoCompact !== undefined
|
|
1946
|
+
? clampAutoCompactTokenLimit(customContextWindow, customMaxInputTokens, configuredAutoCompact)
|
|
1947
|
+
: undefined;
|
|
1870
1948
|
const nativeAliasDefaultEffort = codexForwardNativeCapabilityAlias
|
|
1871
1949
|
? nativeDefaultReasoningEffort(cm.modelId)
|
|
1872
1950
|
: undefined;
|
|
@@ -1887,6 +1965,7 @@ async function gatherRoutedModelsUncached(
|
|
|
1887
1965
|
: codexForwardNativeCapabilityAlias ? { displayName: "Daybreak Blue" } : {}),
|
|
1888
1966
|
...(customContextWindow !== undefined ? { contextWindow: customContextWindow } : {}),
|
|
1889
1967
|
...(customMaxInputTokens !== undefined ? { maxInputTokens: customMaxInputTokens } : {}),
|
|
1968
|
+
...(customAutoCompactTokenLimit !== undefined ? { autoCompactTokenLimit: customAutoCompactTokenLimit } : {}),
|
|
1890
1969
|
...(cm.inputModalities
|
|
1891
1970
|
? { inputModalities: cm.inputModalities }
|
|
1892
1971
|
: codexForwardNativeCapabilityAlias ? { inputModalities: nativeInputModalities(cm.modelId) } : {}),
|
|
@@ -1932,10 +2011,18 @@ async function gatherRoutedModelsUncached(
|
|
|
1932
2011
|
// along when it is actually a member — otherwise a provider default like "xhigh" would
|
|
1933
2012
|
// re-apply onto a narrower custom ladder and override the fallback in applyReasoningLevels.
|
|
1934
2013
|
const effectiveLadder = base.reasoningEfforts ?? replaced?.reasoningEfforts;
|
|
2014
|
+
const mergedMaxInputCandidates = [base.maxInputTokens, replaced?.maxInputTokens]
|
|
2015
|
+
.filter((value): value is number => typeof value === "number" && value > 0);
|
|
2016
|
+
const mergedMaxInput = mergedMaxInputCandidates.length > 0
|
|
2017
|
+
? Math.min(...mergedMaxInputCandidates)
|
|
2018
|
+
: undefined;
|
|
1935
2019
|
const merged: CatalogModel = replaced ? {
|
|
1936
2020
|
...base,
|
|
1937
2021
|
...(base.contextWindow === undefined && replaced.contextWindow !== undefined ? { contextWindow: replaced.contextWindow } : {}),
|
|
1938
|
-
...(
|
|
2022
|
+
...(mergedMaxInput !== undefined ? { maxInputTokens: mergedMaxInput } : {}),
|
|
2023
|
+
...(base.autoCompactTokenLimit === undefined && replaced.autoCompactTokenLimit !== undefined
|
|
2024
|
+
? { autoCompactTokenLimit: replaced.autoCompactTokenLimit }
|
|
2025
|
+
: {}),
|
|
1939
2026
|
...(base.inputModalities === undefined && replaced.inputModalities !== undefined ? { inputModalities: replaced.inputModalities } : {}),
|
|
1940
2027
|
...(base.reasoningEfforts === undefined && replaced.reasoningEfforts !== undefined ? { reasoningEfforts: replaced.reasoningEfforts } : {}),
|
|
1941
2028
|
...(base.defaultReasoningEffort === undefined && replaced.defaultReasoningEffort !== undefined
|
|
@@ -1952,14 +2039,36 @@ async function gatherRoutedModelsUncached(
|
|
|
1952
2039
|
// (#349/#344). Deliberately NOT the full applyProviderConfigHints pass — custom rows are a
|
|
1953
2040
|
// user override, so their explicit contextWindow / inputModalities / reasoning fields must be
|
|
1954
2041
|
// preserved verbatim (the hint pass would cap context and overwrite modalities from registry).
|
|
2042
|
+
const mergedContext = typeof merged.contextWindow === "number" && merged.contextWindow > 0
|
|
2043
|
+
? merged.contextWindow
|
|
2044
|
+
: undefined;
|
|
2045
|
+
const boundedMergedMaxInput = typeof merged.maxInputTokens === "number" && merged.maxInputTokens > 0
|
|
2046
|
+
? (mergedContext !== undefined ? Math.min(merged.maxInputTokens, mergedContext) : merged.maxInputTokens)
|
|
2047
|
+
: undefined;
|
|
2048
|
+
const mergedWithHardBounds = boundedMergedMaxInput !== undefined
|
|
2049
|
+
&& boundedMergedMaxInput !== merged.maxInputTokens
|
|
2050
|
+
? { ...merged, maxInputTokens: boundedMergedMaxInput }
|
|
2051
|
+
: merged;
|
|
2052
|
+
const mergedSoftCandidates = [mergedWithHardBounds.autoCompactTokenLimit, configuredAutoCompact]
|
|
2053
|
+
.filter((value): value is number => typeof value === "number" && value > 0);
|
|
2054
|
+
const mergedWithAutoCompact: CatalogModel = mergedContext !== undefined && mergedSoftCandidates.length > 0
|
|
2055
|
+
? {
|
|
2056
|
+
...mergedWithHardBounds,
|
|
2057
|
+
autoCompactTokenLimit: clampAutoCompactTokenLimit(
|
|
2058
|
+
mergedContext,
|
|
2059
|
+
boundedMergedMaxInput,
|
|
2060
|
+
Math.min(...mergedSoftCandidates),
|
|
2061
|
+
),
|
|
2062
|
+
}
|
|
2063
|
+
: mergedWithHardBounds;
|
|
1955
2064
|
const enrichedProvider = enrichedByName.get(cm.provider) ?? rawProvider;
|
|
1956
|
-
if (enrichedProvider && modelInList(enrichedProvider.noVisionModels,
|
|
1957
|
-
const current =
|
|
2065
|
+
if (enrichedProvider && modelInList(enrichedProvider.noVisionModels, mergedWithAutoCompact.id)) {
|
|
2066
|
+
const current = mergedWithAutoCompact.inputModalities ?? ["text"];
|
|
1958
2067
|
if (!current.includes("image")) {
|
|
1959
|
-
return { ...
|
|
2068
|
+
return { ...mergedWithAutoCompact, inputModalities: [...current, "image"] };
|
|
1960
2069
|
}
|
|
1961
2070
|
}
|
|
1962
|
-
return
|
|
2071
|
+
return mergedWithAutoCompact;
|
|
1963
2072
|
});
|
|
1964
2073
|
// Custom rows override discovered rows that encode to the same Codex-facing slug.
|
|
1965
2074
|
const customKeys = new Set(customModels.map(c => routedSlug(c.provider, c.id)));
|
|
@@ -2015,7 +2124,15 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
|
|
|
2015
2124
|
? Math.min(officialContext, userContext ?? officialContext, providerCap ?? officialContext)
|
|
2016
2125
|
: undefined;
|
|
2017
2126
|
const maxInputTokens = typeof officialMaxInput === "number"
|
|
2018
|
-
? Math.min(
|
|
2127
|
+
? Math.min(
|
|
2128
|
+
officialMaxInput,
|
|
2129
|
+
userMaxInput ?? officialMaxInput,
|
|
2130
|
+
contextWindow ?? officialMaxInput,
|
|
2131
|
+
)
|
|
2132
|
+
: undefined;
|
|
2133
|
+
const configuredAutoCompact = configuredAutoCompactTokenLimit(configured, id);
|
|
2134
|
+
const autoCompactTokenLimit = contextWindow !== undefined && configuredAutoCompact !== undefined
|
|
2135
|
+
? clampAutoCompactTokenLimit(contextWindow, maxInputTokens, configuredAutoCompact)
|
|
2019
2136
|
: undefined;
|
|
2020
2137
|
return {
|
|
2021
2138
|
provider: OPENAI_API_PROVIDER_ID,
|
|
@@ -2023,6 +2140,7 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
|
|
|
2023
2140
|
owned_by: OPENAI_API_PROVIDER_ID,
|
|
2024
2141
|
...(contextWindow ? { contextWindow } : {}),
|
|
2025
2142
|
...(maxInputTokens ? { maxInputTokens } : {}),
|
|
2143
|
+
...(autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
|
|
2026
2144
|
...(policy.modelInputModalities?.[id] ? { inputModalities: [...policy.modelInputModalities[id]!] } : {}),
|
|
2027
2145
|
...(policy.modelReasoningEfforts?.[id] ? { reasoningEfforts: [...policy.modelReasoningEfforts[id]!] } : {}),
|
|
2028
2146
|
};
|
|
@@ -1158,7 +1158,7 @@ export function mergeCatalogEntriesForSync(
|
|
|
1158
1158
|
isNativeAliasCatalogEntry(entry) && typeof entry.slug === "string" ? [entry.slug] : []
|
|
1159
1159
|
)),
|
|
1160
1160
|
),
|
|
1161
|
-
openaiContextCap?:
|
|
1161
|
+
openaiContextCap?: NativeContextLimitsInput,
|
|
1162
1162
|
keepNativeChatGptOnV1 = false,
|
|
1163
1163
|
): RawEntry[] {
|
|
1164
1164
|
// Retained for source compatibility with the original helper contract. Raw provider ids must
|
package/src/codex/convergence.ts
CHANGED
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
disabledNativeSlugs,
|
|
55
55
|
desktopAllowlistSuppressedNativeSlugs,
|
|
56
56
|
NATIVE_OPENAI_MODELS,
|
|
57
|
+
nativeContextLimits,
|
|
57
58
|
shouldIncludeAccountBoundNativeOpenAi,
|
|
58
59
|
shouldIncludeNativeOpenAi,
|
|
59
60
|
} from "./catalog/metadata";
|
|
@@ -286,6 +287,7 @@ function prepareCatalog(
|
|
|
286
287
|
// selector-qualified rows when a live selector is configured.
|
|
287
288
|
const observedNativeSlugs: string[] = [];
|
|
288
289
|
const disabledNative = disabledNativeSlugs(config);
|
|
290
|
+
const openaiContextCap = nativeContextLimits(config);
|
|
289
291
|
const nativeCatalogModels = mergeCatalogModelsWithNativeRecovery(
|
|
290
292
|
active?.models ?? catalog.models ?? [],
|
|
291
293
|
[catalog.models ?? [], ...nativeRecoverySources],
|
|
@@ -304,6 +306,7 @@ function prepareCatalog(
|
|
|
304
306
|
suppressedBareNativeSlugs,
|
|
305
307
|
disabledNativeAccountSlugs: new Set(),
|
|
306
308
|
multiAgentV2Enabled,
|
|
309
|
+
openaiContextCap,
|
|
307
310
|
});
|
|
308
311
|
const accountBoundEntries = accountSelectors.length === 0
|
|
309
312
|
? []
|
|
@@ -320,6 +323,7 @@ function prepareCatalog(
|
|
|
320
323
|
disabledNativeAccountSlugs: new Set([...disabledNative].filter(slug => suppressedBareNativeSlugs.has(slug))),
|
|
321
324
|
multiAgentV2Enabled,
|
|
322
325
|
keepNativeChatGptOnV1: config.keepNativeChatGptOnV1 === true,
|
|
326
|
+
openaiContextCap,
|
|
323
327
|
accountNativeSlugs,
|
|
324
328
|
accountNativeSlugsBySelector,
|
|
325
329
|
}).filter(entry => trustedAccountBoundNativeCatalogSlug(entry) !== undefined);
|
|
@@ -352,6 +356,7 @@ function prepareCatalog(
|
|
|
352
356
|
includeNativeOpenAi,
|
|
353
357
|
accountBoundEntries,
|
|
354
358
|
suppressedBareNativeSlugs,
|
|
359
|
+
openaiContextCap,
|
|
355
360
|
policy: {
|
|
356
361
|
...CANONICAL_NATIVE_CATALOG_CONTENT_POLICY,
|
|
357
362
|
nativeBackfillSlugs: [...availableBareNativeSlugs, ...observedNativeSlugs],
|
package/src/codex/shim.ts
CHANGED
|
@@ -603,6 +603,47 @@ function backupPathFor(path: string): string {
|
|
|
603
603
|
return ext ? `${path.slice(0, -ext.length)}.opencodex-real${ext}` : `${path}.opencodex-real`;
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
+
/**
|
|
607
|
+
* True when a Codex binary lives inside a version manager's install tree.
|
|
608
|
+
*
|
|
609
|
+
* These trees are rewritten in place on upgrade, which destroys both the shim
|
|
610
|
+
* and the sibling .opencodex-real backup it restores from (#2412). The tempting
|
|
611
|
+
* repair — adopt the newly installed binary as a fresh original — is wrong
|
|
612
|
+
* twice: it records a provenance that never happened, and the next upgrade wipes
|
|
613
|
+
* it again, so the repair silently un-repairs on the version manager's schedule.
|
|
614
|
+
*
|
|
615
|
+
* Scope is the three managers named in the report. nvm/fnm/npm-prefix are
|
|
616
|
+
* deliberately excluded: a false positive here refuses a restore that would
|
|
617
|
+
* otherwise be correct.
|
|
618
|
+
*/
|
|
619
|
+
export function isVersionManagerOwnedCodexPath(path: string): boolean {
|
|
620
|
+
const normalized = path.replace(/\\/g, "/").toLowerCase();
|
|
621
|
+
return normalized.includes("/mise/installs/")
|
|
622
|
+
|| normalized.includes("/mise/shims/")
|
|
623
|
+
|| normalized.includes("/.asdf/installs/")
|
|
624
|
+
|| normalized.includes("/.asdf/shims/")
|
|
625
|
+
|| normalized.includes("/.volta/");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Why auto-restore refused, in the operator's own terms. Auto-restore used to
|
|
630
|
+
* return a bare `{ status: "ineligible" }`, and the CLI warns only when a
|
|
631
|
+
* message is present, so `ocx start`, `ocx ensure`, and `ocx service repair` all
|
|
632
|
+
* reported success while routing quietly stayed native (#2412, the cause behind
|
|
633
|
+
* the misleading green status in #2411).
|
|
634
|
+
*/
|
|
635
|
+
function destroyedShimMessage(file: ShimFileState): string {
|
|
636
|
+
const wrapper = existsSync(file.wrapperPath)
|
|
637
|
+
? isShim(file.wrapperPath) ? "present but unusable" : "present but not an opencodex shim"
|
|
638
|
+
: "missing";
|
|
639
|
+
const backup = existsSync(file.backupPath) ? "present" : "missing";
|
|
640
|
+
const base = `Codex autostart shim not restored: wrapper ${wrapper} at ${file.wrapperPath}; original backup ${backup} at ${file.backupPath}.`;
|
|
641
|
+
if (!isVersionManagerOwnedCodexPath(file.wrapperPath)) {
|
|
642
|
+
return `${base} Re-run 'ocx codex-shim install' once the Codex binary is stable.`;
|
|
643
|
+
}
|
|
644
|
+
return `${base} This Codex binary is owned by a version manager (mise/asdf/volta), so opencodex will not wrap it as a new original — the next upgrade would overwrite the shim and its backup again. Route through Codex instead with 'ocx start', and use 'ocx service install' for autostart.`;
|
|
645
|
+
}
|
|
646
|
+
|
|
606
647
|
function shQuote(value: string): string {
|
|
607
648
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
608
649
|
}
|
|
@@ -2039,14 +2080,20 @@ export function autoRestoreCodexShim(options: {
|
|
|
2039
2080
|
if (seen.has(file.wrapperPath)) return { status: "ineligible" };
|
|
2040
2081
|
seen.add(file.wrapperPath);
|
|
2041
2082
|
if (file.preserveOnly) {
|
|
2042
|
-
if (!existsSync(file.backupPath) || existsSync(file.originalPath))
|
|
2083
|
+
if (!existsSync(file.backupPath) || existsSync(file.originalPath)) {
|
|
2084
|
+
return { status: "ineligible", message: destroyedShimMessage(file) };
|
|
2085
|
+
}
|
|
2043
2086
|
continue;
|
|
2044
2087
|
}
|
|
2045
|
-
if (!existsSync(file.wrapperPath) || !hasUsableBackingPath(file))
|
|
2088
|
+
if (!existsSync(file.wrapperPath) || !hasUsableBackingPath(file)) {
|
|
2089
|
+
return { status: "ineligible", message: destroyedShimMessage(file) };
|
|
2090
|
+
}
|
|
2046
2091
|
const probe = stableShimPathProbe(file.wrapperPath);
|
|
2047
2092
|
if (!probe) return { status: "deferred" };
|
|
2048
2093
|
if (probe.prefix.includes(SHIM_MARKER)) {
|
|
2049
|
-
if (!isHealthyShimProbe(probe, state.platform))
|
|
2094
|
+
if (!isHealthyShimProbe(probe, state.platform)) {
|
|
2095
|
+
return { status: "ineligible", message: destroyedShimMessage(file) };
|
|
2096
|
+
}
|
|
2050
2097
|
if (state.platform !== "win32" && !isCurrentUnixShimProbe(probe)) {
|
|
2051
2098
|
obsoleteShimProbes.set(file.wrapperPath, probe);
|
|
2052
2099
|
continue;
|
|
@@ -2054,6 +2101,12 @@ export function autoRestoreCodexShim(options: {
|
|
|
2054
2101
|
healthyCount += 1;
|
|
2055
2102
|
continue;
|
|
2056
2103
|
}
|
|
2104
|
+
// A surviving backup would otherwise let the replacement path below wrap the
|
|
2105
|
+
// version manager's NEW binary as a fresh original — the same adoption the
|
|
2106
|
+
// missing-backup case refuses, arriving through the back door.
|
|
2107
|
+
if (isVersionManagerOwnedCodexPath(file.wrapperPath)) {
|
|
2108
|
+
return { status: "ineligible", message: destroyedShimMessage(file) };
|
|
2109
|
+
}
|
|
2057
2110
|
replacementProbes.set(file.wrapperPath, probe);
|
|
2058
2111
|
}
|
|
2059
2112
|
|
package/src/config.ts
CHANGED
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
type ProviderCostOverlay,
|
|
74
74
|
} from "./types";
|
|
75
75
|
import { OPENAI_CODEX_PROVIDER_ID } from "./providers/openai-tiers";
|
|
76
|
+
import { modelAutoCompactTokenLimitsConfigError } from "./providers/auto-compact-budget";
|
|
76
77
|
import { fastWireDeclarationError, hasFastWireCapabilityConflict } from "./providers/fastwire";
|
|
77
78
|
import {
|
|
78
79
|
getProviderRegistryEntry,
|
|
@@ -866,6 +867,9 @@ const configSchema = z.object({
|
|
|
866
867
|
defaultProvider: z.string().min(1).default("openai"),
|
|
867
868
|
// A retry can be billable, so absence and malformed hand edits both stay off.
|
|
868
869
|
emptyCompletionRetry: z.boolean().optional().catch(false),
|
|
870
|
+
// A malformed hand edit must not silently stop opening the browser: fall back
|
|
871
|
+
// to undefined, which resolves to the historical auto-open behavior.
|
|
872
|
+
oauthOpenBrowser: z.boolean().optional().catch(undefined),
|
|
869
873
|
openaiProviderTierVersion: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
870
874
|
// Invalid hand edits must not discard an otherwise usable config.
|
|
871
875
|
googleAntigravityStaticCatalogVersion: z.union([z.literal(1), z.literal(2)]).optional().catch(undefined),
|
|
@@ -1101,6 +1105,17 @@ const configSchema = z.object({
|
|
|
1101
1105
|
message: maxInputError,
|
|
1102
1106
|
});
|
|
1103
1107
|
}
|
|
1108
|
+
const autoCompactError = modelAutoCompactTokenLimitsConfigError(
|
|
1109
|
+
(provider as { modelAutoCompactTokenLimits?: unknown }).modelAutoCompactTokenLimits,
|
|
1110
|
+
{ requireNativeIds: name === OPENAI_CODEX_PROVIDER_ID },
|
|
1111
|
+
);
|
|
1112
|
+
if (autoCompactError) {
|
|
1113
|
+
ctx.addIssue({
|
|
1114
|
+
code: "custom",
|
|
1115
|
+
path: ["providers", redactSecretString(name), "modelAutoCompactTokenLimits"],
|
|
1116
|
+
message: autoCompactError,
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1104
1119
|
const reasoningSummariesError = booleanRecordConfigError(
|
|
1105
1120
|
(provider as { modelSupportsReasoningSummaries?: unknown }).modelSupportsReasoningSummaries,
|
|
1106
1121
|
"modelSupportsReasoningSummaries",
|
|
@@ -2040,6 +2055,14 @@ function emptyCompletionRetryError(value: unknown): string | null {
|
|
|
2040
2055
|
return "schema_invalid: emptyCompletionRetry: must be a boolean or omitted";
|
|
2041
2056
|
}
|
|
2042
2057
|
|
|
2058
|
+
function oauthOpenBrowserError(value: unknown): string | null {
|
|
2059
|
+
const raw = rawConfigRecord(value);
|
|
2060
|
+
if (!raw || !Object.hasOwn(raw, "oauthOpenBrowser")) return null;
|
|
2061
|
+
const enabled = raw.oauthOpenBrowser;
|
|
2062
|
+
if (enabled === undefined || typeof enabled === "boolean") return null;
|
|
2063
|
+
return "schema_invalid: oauthOpenBrowser: must be a boolean or omitted";
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2043
2066
|
/** Validate an in-memory config candidate without touching disk. Used by headless CLI import/set. */
|
|
2044
2067
|
/**
|
|
2045
2068
|
* Reject a loopback-listener port that collides with the proxy port (#1102).
|
|
@@ -2089,6 +2112,7 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
|
|
|
2089
2112
|
?? codexAccountPrioritiesError(value)
|
|
2090
2113
|
?? codexAccountPickerEnabledError(value)
|
|
2091
2114
|
?? emptyCompletionRetryError(value)
|
|
2115
|
+
?? oauthOpenBrowserError(value)
|
|
2092
2116
|
?? loopbackListenerPortError(value);
|
|
2093
2117
|
if (boundaryError) return { ok: false, error: boundaryError };
|
|
2094
2118
|
const result = configSchema.safeParse(value);
|