@bitkyc08/opencodex 2.24.0 → 2.24.2
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-CFqJKF2L.js → index-DW-DYWmz.js} +17 -17
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/claude/context-windows.ts +2 -2
- package/src/claude/desktop-3p.ts +6 -6
- package/src/claude/model-info.ts +2 -2
- package/src/cli/claude-desktop.ts +2 -3
- package/src/codex/catalog/metadata.ts +29 -10
- package/src/codex/catalog/provider-fetch.ts +16 -10
- package/src/codex/catalog-admission.ts +5 -3
- package/src/codex/catalog.ts +1 -1
- package/src/codex/injected-marker.ts +9 -3
- package/src/codex/user-identity.ts +88 -6
- package/src/generated/compatibility-version.json +26 -26
- package/src/grok/sync.ts +2 -4
- package/src/lab/projection/rebuild.ts +36 -18
- package/src/lib/windows-elevation.ts +10 -1
- package/src/lib/windows-secret-acl.ts +49 -19
- package/src/routing/capability.ts +5 -6
- package/src/server/index.ts +3 -4
- package/src/server/management/agent-settings-routes.ts +5 -5
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +2 -0
- package/src/server/management/native-integration-routes.ts +3 -3
- package/src/server/management/shared.ts +4 -4
- package/src/server/management-api.ts +2 -2
- package/src/server/responses/core.ts +2 -1
- package/src/server/responses/input-admission.ts +13 -10
- package/src/server/system-env.ts +3 -3
package/gui/dist/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-DW-DYWmz.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-DQsMZzI5.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { aliasForNative, aliasForRoute } from "./alias";
|
|
12
12
|
import { desktop3pAlias } from "./desktop-3p";
|
|
13
|
-
import { nativeOpenAiContextWindow, type CatalogModel } from "../codex/catalog";
|
|
13
|
+
import { nativeOpenAiContextWindow, type CatalogModel, type NativeContextLimitsInput } from "../codex/catalog";
|
|
14
14
|
|
|
15
15
|
const ONE_MILLION = 1_000_000;
|
|
16
16
|
|
|
@@ -104,7 +104,7 @@ export function buildClaudeContextWindows(
|
|
|
104
104
|
// A configured providerContextCaps.openai has to reach the native rows here too. Without
|
|
105
105
|
// it the Claude surface keeps advertising the uncapped authoritative window while the
|
|
106
106
|
// Codex catalog advertises the capped one, and the two disagree about the same model.
|
|
107
|
-
nativeContextCap?:
|
|
107
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
108
108
|
): Record<string, number> {
|
|
109
109
|
const out: Record<string, number> = {};
|
|
110
110
|
const put = (key: string | null, value: number) => {
|
package/src/claude/desktop-3p.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
renderDesktopProfile,
|
|
11
11
|
type DesktopProfileModel,
|
|
12
12
|
} from "./desktop-profile";
|
|
13
|
-
import { nativeOpenAiContextWindow } from "../codex/catalog";
|
|
13
|
+
import { nativeOpenAiContextWindow, type NativeContextLimitsInput } from "../codex/catalog";
|
|
14
14
|
import { assertDesktop3pModelsValid } from "./desktop-3p-guard";
|
|
15
15
|
|
|
16
16
|
export interface Desktop3pModelEntry {
|
|
@@ -191,7 +191,7 @@ function collectDesktop3pModels(
|
|
|
191
191
|
nativeSlugs: string[],
|
|
192
192
|
routedModels: Array<Desktop3pRoutedModel>,
|
|
193
193
|
profile?: OcxClaudeDesktopProfile,
|
|
194
|
-
nativeContextCap?:
|
|
194
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
195
195
|
): { models: Desktop3pModelEntry[]; registry: Map<string, string> } {
|
|
196
196
|
const registry = new Map<string, string>();
|
|
197
197
|
const models: Desktop3pModelEntry[] = [];
|
|
@@ -293,7 +293,7 @@ export function buildDesktop3pRegistry(
|
|
|
293
293
|
nativeSlugs: string[],
|
|
294
294
|
routedModels: Array<Desktop3pRoutedModel>,
|
|
295
295
|
profile?: OcxClaudeDesktopProfile,
|
|
296
|
-
nativeContextCap?:
|
|
296
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
297
297
|
): Map<string, string> {
|
|
298
298
|
const { registry } = collectDesktop3pModels(nativeSlugs, routedModels, profile, nativeContextCap);
|
|
299
299
|
desktop3pRegistry = registry;
|
|
@@ -305,7 +305,7 @@ export function generateDesktop3pModels(
|
|
|
305
305
|
nativeSlugs: string[],
|
|
306
306
|
routedModels: Array<Desktop3pRoutedModel>,
|
|
307
307
|
profile?: OcxClaudeDesktopProfile,
|
|
308
|
-
nativeContextCap?:
|
|
308
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
309
309
|
): Desktop3pModelEntry[] {
|
|
310
310
|
const { models, registry } = collectDesktop3pModels(nativeSlugs, routedModels, profile, nativeContextCap);
|
|
311
311
|
desktop3pRegistry = registry;
|
|
@@ -337,7 +337,7 @@ export function generateDesktop3pConfig(
|
|
|
337
337
|
apiKey = "ocx",
|
|
338
338
|
mode: Desktop3pConfigMode = "static",
|
|
339
339
|
profile?: OcxClaudeDesktopProfile,
|
|
340
|
-
nativeContextCap?:
|
|
340
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
341
341
|
): object {
|
|
342
342
|
const base = {
|
|
343
343
|
inferenceProvider: "gateway",
|
|
@@ -558,7 +558,7 @@ export function writeDesktop3pConfig(
|
|
|
558
558
|
apiKey?: string,
|
|
559
559
|
mode: Desktop3pConfigMode = "static",
|
|
560
560
|
profile?: OcxClaudeDesktopProfile,
|
|
561
|
-
nativeContextCap?:
|
|
561
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
562
562
|
): { written: boolean; path: string; reason?: string; fingerprint?: string } {
|
|
563
563
|
const libraryPath = resolveDesktop3pConfigLibraryPath();
|
|
564
564
|
const metadataPath = join(libraryPath, "_meta.json");
|
package/src/claude/model-info.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - created_at is a fixed constant; max_input_tokens is authoritative-or-null;
|
|
16
16
|
* max_tokens is always null (no authoritative output limit exists proxy-side).
|
|
17
17
|
*/
|
|
18
|
-
import { catalogModelEfforts, nativeEffortClamp, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type CatalogModel } from "../codex/catalog";
|
|
18
|
+
import { catalogModelEfforts, nativeEffortClamp, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type CatalogModel, type NativeContextLimitsInput } from "../codex/catalog";
|
|
19
19
|
import { claudeCodeAlias, claudeCodeNativeAlias } from "./alias";
|
|
20
20
|
import { desktop3pAlias } from "./desktop-3p";
|
|
21
21
|
import { AUTO_CONTEXT_OFF, type AutoContextMode } from "./context-windows";
|
|
@@ -108,7 +108,7 @@ export function buildAnthropicModelInfos(
|
|
|
108
108
|
auto: AutoContextMode = AUTO_CONTEXT_OFF,
|
|
109
109
|
idStyle: AnthropicIdStyle = "desktop3p",
|
|
110
110
|
aliasForRoute: (provider: string, modelId: string) => string = desktop3pAlias,
|
|
111
|
-
nativeContextCap?:
|
|
111
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
112
112
|
): AnthropicModelInfo[] {
|
|
113
113
|
const out: AnthropicModelInfo[] = [];
|
|
114
114
|
const seen = new Set<string>();
|
|
@@ -11,11 +11,10 @@ import {
|
|
|
11
11
|
type DesktopProfile,
|
|
12
12
|
} from "../claude/desktop-profile";
|
|
13
13
|
import { writeDesktop3pConfig, type Desktop3pConfigMode, parseDesktop3pModeArgs } from "../claude/desktop-3p";
|
|
14
|
-
import { filterCatalogVisibleModels, desktopVisibleNativeSlugs } from "../codex/catalog";
|
|
14
|
+
import { filterCatalogVisibleModels, desktopVisibleNativeSlugs, nativeContextLimits } from "../codex/catalog";
|
|
15
15
|
import { buildClaudeDesktopState, fetchAllModels } from "../server/management-api";
|
|
16
16
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
17
17
|
import { runtimeRequest } from "./runtime-api";
|
|
18
|
-
import { providerContextCap } from "../providers/context-cap";
|
|
19
18
|
import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
20
19
|
|
|
21
20
|
function isFamily(value: string | undefined): value is DesktopFamily {
|
|
@@ -100,7 +99,7 @@ export async function applyProfile(
|
|
|
100
99
|
config.apiKeys?.[0]?.key,
|
|
101
100
|
mode,
|
|
102
101
|
state.profile,
|
|
103
|
-
|
|
102
|
+
nativeContextLimits(config),
|
|
104
103
|
);
|
|
105
104
|
return { ok: result.written, path: result.path, reason: result.reason };
|
|
106
105
|
}
|
|
@@ -123,7 +123,7 @@ export function isUnsupportedOpenAiNativeSlug(slug: string): boolean {
|
|
|
123
123
|
* Evidence: devlog/_plan/260817_native_gpt56_1m_context/001_measurement_evidence.md
|
|
124
124
|
* and 014_final_922k_with_margin.md.
|
|
125
125
|
*/
|
|
126
|
-
export const NATIVE_GPT56_CONTEXT_WINDOW =
|
|
126
|
+
export const NATIVE_GPT56_CONTEXT_WINDOW = 272_000;
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
129
|
* Hard ceiling: the largest input the native GPT-5.6 family actually accepts (measured).
|
|
@@ -134,19 +134,29 @@ export const NATIVE_GPT56_CONTEXT_WINDOW = 922_000;
|
|
|
134
134
|
*/
|
|
135
135
|
export const NATIVE_GPT56_MAX_INPUT_TOKENS = 922_000;
|
|
136
136
|
|
|
137
|
+
/** User-facing 1M opt-in: the largest window the native 5.6 family may advertise. */
|
|
138
|
+
export const NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW = NATIVE_GPT56_MAX_INPUT_TOKENS;
|
|
139
|
+
|
|
140
|
+
const NATIVE_GPT56_FAMILY = new Set<string>([
|
|
141
|
+
"gpt-5.6-sol",
|
|
142
|
+
"gpt-5.6-terra",
|
|
143
|
+
"gpt-5.6-luna",
|
|
144
|
+
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
145
|
+
]);
|
|
146
|
+
|
|
137
147
|
export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: number; maxContextWindow?: number; maxInputTokens?: number }> = {
|
|
138
148
|
"gpt-5.5": { contextWindow: 272_000, maxContextWindow: 272_000 },
|
|
139
149
|
"gpt-5.4": { contextWindow: 1_000_000, maxContextWindow: 1_000_000 },
|
|
140
150
|
"gpt-5.3-codex-spark": { contextWindow: 100_000, maxContextWindow: 100_000 },
|
|
141
|
-
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow:
|
|
142
|
-
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow:
|
|
143
|
-
"gpt-5.6-luna": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow:
|
|
151
|
+
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
152
|
+
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
153
|
+
"gpt-5.6-luna": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
144
154
|
// Daybreak Blue borrows Sol's capability metadata and rides the same family contract.
|
|
145
155
|
// Unlike sol/terra/luna its window was NOT measured here: this account cannot reach it
|
|
146
156
|
// (`400 "The 'gpt-daybreak-blue-latest' model is not supported when using Codex with a
|
|
147
157
|
// ChatGPT account."`), so the promotion rests on a report from an account that has
|
|
148
158
|
// access rather than on a probe. Treat it as the weaker evidence of the four.
|
|
149
|
-
[NATIVE_DAYBREAK_BLUE_MODEL]: { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow:
|
|
159
|
+
[NATIVE_DAYBREAK_BLUE_MODEL]: { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
150
160
|
};
|
|
151
161
|
|
|
152
162
|
const PINNED_UPSTREAM_MODELS: Map<string, RawEntry> = new Map(
|
|
@@ -171,10 +181,10 @@ const PINNED_NATIVE_CAPABILITY_ENTRIES: Map<string, RawEntry> = new Map(
|
|
|
171
181
|
);
|
|
172
182
|
|
|
173
183
|
/**
|
|
174
|
-
* The user-owned levers that
|
|
184
|
+
* The user-owned levers that set a native window, carried together.
|
|
175
185
|
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
186
|
+
* For the GPT-5.6 family these may raise the Codex 272k default up to the measured
|
|
187
|
+
* 922k ceiling. Other native slugs still only ever lower.
|
|
178
188
|
*
|
|
179
189
|
* This travels as an ARGUMENT rather than module state on purpose. `grok/sync.ts` runs in
|
|
180
190
|
* the `ocx ensure` parent process, outside the server, so an injected global would never
|
|
@@ -223,13 +233,22 @@ export function nativeContextLimits(
|
|
|
223
233
|
};
|
|
224
234
|
}
|
|
225
235
|
|
|
226
|
-
/** Apply the user levers to an authoritative value.
|
|
236
|
+
/** Apply the user levers to an authoritative value. */
|
|
227
237
|
function narrowToLimits(raw: number | undefined, slug: string, input: NativeContextLimitsInput): number | undefined {
|
|
228
238
|
if (raw === undefined) return undefined;
|
|
229
239
|
const limits = asLimits(input);
|
|
230
240
|
const overlay = positiveInt(limits.modelWindows?.[slug]) ?? positiveInt(limits.providerWindow);
|
|
241
|
+
const cap = positiveInt(limits.cap);
|
|
242
|
+
if (NATIVE_GPT56_FAMILY.has(slug)) {
|
|
243
|
+
const ceiling = NATIVE_GPT56_MAX_INPUT_TOKENS;
|
|
244
|
+
const chosen = overlay ?? cap ?? raw;
|
|
245
|
+
const window = Math.min(chosen, ceiling);
|
|
246
|
+
return overlay !== undefined && cap !== undefined ? Math.min(window, cap) : window;
|
|
247
|
+
}
|
|
231
248
|
const narrowed = overlay === undefined ? raw : Math.min(raw, overlay);
|
|
232
|
-
|
|
249
|
+
// 922k is the GPT-5.6 1M opt-in, not a request to shrink gpt-5.4's 1M window.
|
|
250
|
+
if (cap === NATIVE_GPT56_MAX_INPUT_TOKENS) return narrowed;
|
|
251
|
+
return applyProviderContextCap(narrowed, cap) ?? narrowed;
|
|
233
252
|
}
|
|
234
253
|
|
|
235
254
|
export function nativeOpenAiContextWindow(slug: string, limits?: NativeContextLimitsInput): number | undefined {
|
|
@@ -74,7 +74,7 @@ import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } fr
|
|
|
74
74
|
|
|
75
75
|
import { CODEX_CUSTOM_MODEL_CATALOG_KIND, JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
76
76
|
import type { CatalogModel } from "./parsing";
|
|
77
|
-
import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
77
|
+
import { disabledNativeSlugs, hasComboTargets, isNativeOpenAiCapabilityAliasModel, NATIVE_GPT56_MAX_INPUT_TOKENS, nativeContextLimits, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
78
78
|
import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
|
|
79
79
|
import type { ComboCatalogOmission } from "./aggregation";
|
|
80
80
|
import type { CatalogGatherProviderAuthEvidence } from "./filesystem-evidence";
|
|
@@ -1707,7 +1707,7 @@ async function gatherRoutedModelsUncached(
|
|
|
1707
1707
|
// configs that will never need it.
|
|
1708
1708
|
} else {
|
|
1709
1709
|
const disabled = disabledNativeSlugs(config);
|
|
1710
|
-
const openaiContextCap =
|
|
1710
|
+
const openaiContextCap = nativeContextLimits(config);
|
|
1711
1711
|
const requiredNativeComboTargets = new Set(listComboIds(config).flatMap(id => {
|
|
1712
1712
|
const combo = getCombo(config, id);
|
|
1713
1713
|
return combo?.targets.flatMap(target => (
|
|
@@ -1747,15 +1747,17 @@ async function gatherRoutedModelsUncached(
|
|
|
1747
1747
|
const combo = getCombo(config, id);
|
|
1748
1748
|
if (!combo) continue;
|
|
1749
1749
|
const nativeContextWindow = combo.nativeAlias && combo.alias
|
|
1750
|
-
? nativeOpenAiContextWindow(combo.alias,
|
|
1750
|
+
? nativeOpenAiContextWindow(combo.alias, nativeContextLimits(config))
|
|
1751
1751
|
: undefined;
|
|
1752
1752
|
const nativeAliasMaxInput = combo.nativeAlias && combo.alias
|
|
1753
|
-
?
|
|
1753
|
+
? (combo.alias.startsWith("gpt-5.6-") || combo.alias.includes("daybreak")
|
|
1754
|
+
? NATIVE_GPT56_MAX_INPUT_TOKENS
|
|
1755
|
+
: nativeOpenAiMaxInputTokens(combo.alias) ?? nativeOpenAiContextWindow(combo.alias))
|
|
1754
1756
|
: undefined;
|
|
1755
1757
|
const nativeAliasFallback = combo.nativeAlias && combo.alias && nativeContextWindow !== undefined
|
|
1756
1758
|
? {
|
|
1757
1759
|
contextWindow: nativeContextWindow,
|
|
1758
|
-
...(nativeAliasMaxInput !== undefined ? { maxInputTokens:
|
|
1760
|
+
...(nativeAliasMaxInput !== undefined ? { maxInputTokens: nativeAliasMaxInput } : {}),
|
|
1759
1761
|
inputModalities: nativeInputModalities(combo.alias),
|
|
1760
1762
|
reasoningEfforts: nativeReasoningEfforts(combo.alias),
|
|
1761
1763
|
}
|
|
@@ -1801,18 +1803,22 @@ async function gatherRoutedModelsUncached(
|
|
|
1801
1803
|
&& providerForCanonicalCheck !== undefined
|
|
1802
1804
|
&& isCanonicalOpenAiForwardProvider(providerForCanonicalCheck)
|
|
1803
1805
|
&& isNativeOpenAiCapabilityAliasModel(cm.modelId);
|
|
1806
|
+
const customNativeLimits = {
|
|
1807
|
+
...nativeContextLimits(config),
|
|
1808
|
+
...(typeof cm.contextWindow === "number" && cm.contextWindow > 0
|
|
1809
|
+
? { modelWindows: { ...(nativeContextLimits(config).modelWindows ?? {}), [cm.modelId]: cm.contextWindow } }
|
|
1810
|
+
: {}),
|
|
1811
|
+
};
|
|
1804
1812
|
const nativeAliasContextWindow = codexForwardNativeCapabilityAlias
|
|
1805
|
-
? nativeOpenAiContextWindow(cm.modelId,
|
|
1813
|
+
? nativeOpenAiContextWindow(cm.modelId, customNativeLimits)
|
|
1806
1814
|
: undefined;
|
|
1807
1815
|
const customContextWindow = cm.contextWindow
|
|
1808
1816
|
? nativeAliasContextWindow !== undefined
|
|
1809
|
-
?
|
|
1817
|
+
? nativeAliasContextWindow
|
|
1810
1818
|
: cm.contextWindow
|
|
1811
1819
|
: nativeAliasContextWindow;
|
|
1812
|
-
// Input ceiling for a native capability alias, clamped to whatever window we settled on
|
|
1813
|
-
// above. A custom row that lowered the window must not keep the full native input budget.
|
|
1814
1820
|
const nativeAliasMaxInputTokens = codexForwardNativeCapabilityAlias
|
|
1815
|
-
? nativeOpenAiMaxInputTokens(cm.modelId,
|
|
1821
|
+
? nativeOpenAiMaxInputTokens(cm.modelId, customNativeLimits)
|
|
1816
1822
|
: undefined;
|
|
1817
1823
|
const customMaxInputTokens = nativeAliasMaxInputTokens !== undefined && customContextWindow !== undefined
|
|
1818
1824
|
? Math.min(nativeAliasMaxInputTokens, customContextWindow)
|
|
@@ -79,9 +79,11 @@ function canonicalConfigEncoding(value: unknown, ancestors = new Set<object>()):
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
if (ancestors.has(value)) throw new TypeError("Catalog config identity cannot encode a cyclic graph.");
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
// Symbol keys are process-local metadata (e.g. the user-cost-overlay
|
|
83
|
+
// preservation-owner tag) that JSON.stringify omits and no persist path
|
|
84
|
+
// writes. They must not change the durable config identity, so encoding
|
|
85
|
+
// proceeds over the enumerable string-keyed properties below (Object.keys
|
|
86
|
+
// already ignores symbols). Symbol VALUES are still refused above.
|
|
85
87
|
ancestors.add(value);
|
|
86
88
|
try {
|
|
87
89
|
if (Array.isArray(value)) {
|
package/src/codex/catalog.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Public surface preserved exactly; importers keep using "src/codex/catalog".
|
|
3
3
|
export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, readCatalog, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
|
|
4
4
|
export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
|
|
5
|
-
export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeContextLimits, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, type NativeContextLimits, type NativeContextLimitsInput } from "./catalog/metadata";
|
|
5
|
+
export { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, CODEX_NATIVE_ALIAS_CATALOG_KIND, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_GPT56_OPT_IN_CONTEXT_WINDOW, NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, NATIVE_OPENAI_MODELS, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, isNativeAliasCatalogEntry, isNativeOpenAiCapabilityAliasModel, nativeContextLimits, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, observedAccountBoundNativeEntries, observedAccountBoundNativeOpenAiSlugs, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeInputModalities, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, type NativeContextLimits, type NativeContextLimitsInput } from "./catalog/metadata";
|
|
6
6
|
export { isSpawnableCodexCandidate, codexExecInvocation, loadBundledCodexCatalog, materializeBundledCodexCatalog, loadCatalogTemplate } from "./catalog/bundled";
|
|
7
7
|
export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
|
|
8
8
|
export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember, configuredComboTargetModelsByProvider } from "./catalog/provider-fetch";
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* them here breaks that cycle. `inject.ts` imports them back and re-exports the
|
|
8
8
|
* two public predicates, so external callers see no change.
|
|
9
9
|
*/
|
|
10
|
+
import { parseTomlString } from "./paths";
|
|
11
|
+
|
|
10
12
|
export const OCX_SECTION_MARKER = "# Auto-injected by opencodex";
|
|
11
13
|
|
|
12
14
|
export function isRootOpenaiBaseUrlLine(line: string): boolean {
|
|
@@ -16,7 +18,11 @@ export function isRootOpenaiBaseUrlLine(line: string): boolean {
|
|
|
16
18
|
export function tomlStringPattern(key: string): RegExp {
|
|
17
19
|
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18
20
|
const keyToken = `(?:${escaped}|"${escaped}"|'${escaped}')`;
|
|
19
|
-
|
|
21
|
+
// The quoted value is captured WITH its quotes so callers can decode it as TOML.
|
|
22
|
+
// A basic string escapes backslashes, so a Windows path is stored doubled; reading
|
|
23
|
+
// the raw bytes back returned a path that matched nothing on disk and made the
|
|
24
|
+
// journal's recorded catalog path un-restorable (#1798).
|
|
25
|
+
return new RegExp(`^\\s*${keyToken}\\s*=\\s*("(?:\\\\.|[^"])*"|'[^']*')\\s*(?:#.*)?$`);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export function rootTomlString(content: string, key: string): string | null {
|
|
@@ -26,7 +32,7 @@ export function rootTomlString(content: string, key: string): string | null {
|
|
|
26
32
|
const pattern = tomlStringPattern(key);
|
|
27
33
|
for (const line of rootLines) {
|
|
28
34
|
const match = pattern.exec(line);
|
|
29
|
-
if (match?.[1]) return match[1].trim();
|
|
35
|
+
if (match?.[1]) return parseTomlString(match[1]).trim();
|
|
30
36
|
}
|
|
31
37
|
return null;
|
|
32
38
|
}
|
|
@@ -45,7 +51,7 @@ export function providerTableString(content: string, provider: string, key: stri
|
|
|
45
51
|
const pattern = tomlStringPattern(key);
|
|
46
52
|
for (let index = start + 1; index < lines.length && !/^\s*\[/.test(lines[index]); index += 1) {
|
|
47
53
|
const match = pattern.exec(lines[index]);
|
|
48
|
-
if (match?.[1]) return match[1].trim();
|
|
54
|
+
if (match?.[1]) return parseTomlString(match[1]).trim();
|
|
49
55
|
}
|
|
50
56
|
return null;
|
|
51
57
|
}
|
|
@@ -39,6 +39,31 @@ const SID_PATTERN = /^S-1-(?:\d+-)+\d+$/i;
|
|
|
39
39
|
*/
|
|
40
40
|
const WINDOWS_POWERSHELL_LOOKUP_TIMEOUT_MS = 8_000;
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* FOLDERID_LocalAppData, and the flag that makes the lookup ignore the caller's
|
|
44
|
+
* environment.
|
|
45
|
+
*
|
|
46
|
+
* The obvious spelling, .NET's
|
|
47
|
+
* `GetFolderPath(SpecialFolder.LocalApplicationData)`, is unusable here: on
|
|
48
|
+
* Windows it resolves through `USERPROFILE`, and when the profile named there has
|
|
49
|
+
* no local AppData directory on disk it returns an EMPTY STRING rather than an
|
|
50
|
+
* error. Any
|
|
51
|
+
* caller that redirected `USERPROFILE` (the test sandbox does, and so does a
|
|
52
|
+
* service account whose profile has not been materialized) therefore refused every
|
|
53
|
+
* coordinator lookup with "returned an empty value", which is the exact
|
|
54
|
+
* environment dependence this module exists to eliminate.
|
|
55
|
+
*
|
|
56
|
+
* `SHGetKnownFolderPath` with a null token and `KF_FLAG_DEFAULT_PATH` reads the
|
|
57
|
+
* known-folder registration for the effective token instead: it returns the real
|
|
58
|
+
* per-user path whether or not the directory exists, and it is unaffected by
|
|
59
|
+
* `USERPROFILE`, `LOCALAPPDATA`, `HOMEDRIVE`, or `HOMEPATH`. A non-null token argument
|
|
60
|
+
* is NOT equivalent: passing (HANDLE)-1 resolves the DEFAULT USER profile
|
|
61
|
+
* (the built-in "Default" profile), which would key coordination to a namespace
|
|
62
|
+
* no real account writes to.
|
|
63
|
+
*/
|
|
64
|
+
const WINDOWS_LOCAL_APPDATA_FOLDER_ID = "F1B32785-6FBA-4FCF-9D55-7B8E7F157091";
|
|
65
|
+
const WINDOWS_KF_FLAG_DEFAULT_PATH = "0x00000400";
|
|
66
|
+
|
|
42
67
|
export class CodexUserIdentityRefusal extends Error {
|
|
43
68
|
readonly code = "CODEX_USER_IDENTITY_REFUSED";
|
|
44
69
|
|
|
@@ -93,6 +118,42 @@ export function windowsIdentityPowerShellCommandForTests(expression: string): st
|
|
|
93
118
|
return windowsIdentityPowerShellCommand(expression);
|
|
94
119
|
}
|
|
95
120
|
|
|
121
|
+
/**
|
|
122
|
+
* PowerShell expression yielding the effective account's local AppData path.
|
|
123
|
+
*
|
|
124
|
+
* P/Invoke rather than a .NET convenience wrapper, for the reason recorded on
|
|
125
|
+
* WINDOWS_LOCAL_APPDATA_FOLDER_ID: the wrapper follows `USERPROFILE` and answers
|
|
126
|
+
* an empty string for a profile whose directory is absent, which is precisely
|
|
127
|
+
* the environment dependence this module refuses to inherit. The type is added
|
|
128
|
+
* under a unique name per process because `Add-Type` cannot redefine one.
|
|
129
|
+
*
|
|
130
|
+
* The whole sequence is wrapped in one `$(...)` subexpression because the caller
|
|
131
|
+
* substitutes this text into `[string](<expression>)`; several statements
|
|
132
|
+
* spliced in bare would close that cast's parenthesis early and fail to parse.
|
|
133
|
+
*/
|
|
134
|
+
function windowsLocalAppDataExpression(): string {
|
|
135
|
+
const signature =
|
|
136
|
+
'[DllImport("shell32.dll", CharSet = CharSet.Unicode)] public static extern int '
|
|
137
|
+
+ 'SHGetKnownFolderPath(ref System.Guid id, uint flags, System.IntPtr token, out System.IntPtr path);';
|
|
138
|
+
const statements = [
|
|
139
|
+
`$ocxShell = Add-Type -MemberDefinition '${signature}'`
|
|
140
|
+
+ " -Name OcxKnownFolder -Namespace OcxIdentity -PassThru",
|
|
141
|
+
`$ocxFolderId = [System.Guid]'${WINDOWS_LOCAL_APPDATA_FOLDER_ID}'`,
|
|
142
|
+
"$ocxPathPtr = [System.IntPtr]::Zero",
|
|
143
|
+
"$ocxHr = $ocxShell::SHGetKnownFolderPath([ref]$ocxFolderId, "
|
|
144
|
+
+ `${WINDOWS_KF_FLAG_DEFAULT_PATH}, [System.IntPtr]::Zero, [ref]$ocxPathPtr)`,
|
|
145
|
+
"if ($ocxHr -ne 0) { throw 'SHGetKnownFolderPath failed' }",
|
|
146
|
+
"try { [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ocxPathPtr) }"
|
|
147
|
+
+ " finally { [System.Runtime.InteropServices.Marshal]::FreeCoTaskMem($ocxPathPtr) }",
|
|
148
|
+
];
|
|
149
|
+
return `$(${statements.join("; ")})`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Test-only readback of the environment-independent known-folder expression. */
|
|
153
|
+
export function windowsLocalAppDataExpressionForTests(): string {
|
|
154
|
+
return windowsLocalAppDataExpression();
|
|
155
|
+
}
|
|
156
|
+
|
|
96
157
|
/** Test-only readback of the spawn options shared by the identity lookups (#1278). */
|
|
97
158
|
export function windowsIdentityPowerShellSpawnOptionsForTests(): ReturnType<
|
|
98
159
|
typeof windowsIdentityPowerShellSpawnOptions
|
|
@@ -122,7 +183,31 @@ export function decodeWindowsIdentityPowerShellOutputForTests(output: Uint8Array
|
|
|
122
183
|
return decodeWindowsIdentityPowerShellOutput(output);
|
|
123
184
|
}
|
|
124
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Per-process memo for the Windows lookups.
|
|
188
|
+
*
|
|
189
|
+
* Both values -- the effective token's SID and its known-folder local AppData --
|
|
190
|
+
* are fixed for the lifetime of a process: neither can change without a new logon
|
|
191
|
+
* token, and the lookups deliberately ignore the environment, so nothing a caller
|
|
192
|
+
* does between two calls can alter the answer. Each call otherwise spawns a fresh
|
|
193
|
+
* PowerShell, roughly 150ms for the SID and 310ms for the folder, and the
|
|
194
|
+
* coordinator asks for both on every config write and lock acquisition. That cost
|
|
195
|
+
* pushed real multi-process injection tests past their budget while proving
|
|
196
|
+
* nothing: the second spawn re-derives what the first already established.
|
|
197
|
+
*
|
|
198
|
+
* Only successful lookups are memoized, so a transient failure cannot pin the
|
|
199
|
+
* process into a permanently refusing state.
|
|
200
|
+
*/
|
|
201
|
+
const windowsIdentityValueCache = new Map<string, string>();
|
|
202
|
+
|
|
203
|
+
/** Test-only reset so a suite can force a fresh lookup. */
|
|
204
|
+
export function resetWindowsIdentityValueCacheForTests(): void {
|
|
205
|
+
windowsIdentityValueCache.clear();
|
|
206
|
+
}
|
|
207
|
+
|
|
125
208
|
function powershellValue(expression: string): string {
|
|
209
|
+
const memoized = windowsIdentityValueCache.get(expression);
|
|
210
|
+
if (memoized !== undefined) return memoized;
|
|
126
211
|
let command: string[];
|
|
127
212
|
try {
|
|
128
213
|
command = windowsIdentityPowerShellCommand(expression);
|
|
@@ -145,6 +230,7 @@ function powershellValue(expression: string): string {
|
|
|
145
230
|
if (result.exitCode !== 0) refuse("Windows effective-account lookup failed.");
|
|
146
231
|
const value = decodeWindowsIdentityPowerShellOutput(result.stdout ?? Buffer.alloc(0));
|
|
147
232
|
if (!value) refuse("Windows effective-account lookup returned an empty value.");
|
|
233
|
+
windowsIdentityValueCache.set(expression, value);
|
|
148
234
|
return value;
|
|
149
235
|
}
|
|
150
236
|
|
|
@@ -265,9 +351,7 @@ export function probeCodexCoordinatorNamespace(identity: UserIdentity): Coordina
|
|
|
265
351
|
}
|
|
266
352
|
|
|
267
353
|
if (!SID_PATTERN.test(identity.sid)) refuse("The coordinator identity contains an invalid SID.");
|
|
268
|
-
const localAppData = powershellValue(
|
|
269
|
-
"[Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData)",
|
|
270
|
-
);
|
|
354
|
+
const localAppData = powershellValue(windowsLocalAppDataExpression());
|
|
271
355
|
if (!isAbsolute(localAppData)) refuse("Windows LocalAppData resolution returned a relative path.");
|
|
272
356
|
const root = resolve(localAppData, "OpenCodex", "Runtime", "v1", identity.sid.toUpperCase());
|
|
273
357
|
let entry;
|
|
@@ -297,9 +381,7 @@ export function probeCodexCoordinatorNamespace(identity: UserIdentity): Coordina
|
|
|
297
381
|
|
|
298
382
|
function resolveWindowsRuntimeRoot(identity: Extract<UserIdentity, { platform: "win32" }>): string {
|
|
299
383
|
if (!SID_PATTERN.test(identity.sid)) refuse("The coordinator identity contains an invalid SID.");
|
|
300
|
-
const localAppData = powershellValue(
|
|
301
|
-
"[Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData)",
|
|
302
|
-
);
|
|
384
|
+
const localAppData = powershellValue(windowsLocalAppDataExpression());
|
|
303
385
|
if (!isAbsolute(localAppData)) refuse("Windows LocalAppData resolution returned a relative path.");
|
|
304
386
|
|
|
305
387
|
// The SID and known-folder values come from the effective token/.NET OS APIs,
|