@bitkyc08/opencodex 2.24.1 → 2.25.0-preview.20260818
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-C3FiAveG.js → index-TFd4xi1L.js} +8 -8
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +42 -0
- package/src/adapters/client-fingerprint.ts +9 -5
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +17 -0
- package/src/adapters/cursor/cursor-errors.ts +49 -0
- package/src/adapters/cursor/live-models.ts +36 -2
- package/src/adapters/cursor/live-transport.ts +55 -4
- package/src/adapters/cursor/native-exec.ts +9 -0
- package/src/adapters/cursor/protobuf-request.ts +160 -9
- package/src/adapters/cursor/request-builder.ts +9 -1
- package/src/adapters/cursor/tool-definitions.ts +7 -2
- package/src/adapters/google-antigravity-wire.ts +1 -1
- package/src/adapters/google.ts +30 -12
- package/src/adapters/openai-responses-url.ts +5 -3
- package/src/adapters/registry.ts +3 -1
- package/src/adapters/tool-catalog-nudge.ts +76 -9
- package/src/bridge.ts +53 -9
- 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/app-server-processes.ts +69 -35
- package/src/codex/catalog/metadata.ts +29 -10
- package/src/codex/catalog/provider-fetch.ts +21 -11
- 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/config.ts +1 -0
- package/src/generated/compatibility-version.json +61 -53
- package/src/grok/sync.ts +2 -4
- package/src/lab/projection/rebuild.ts +36 -18
- package/src/lib/windows-elevation.ts +18 -3
- package/src/lib/windows-secret-acl.ts +49 -19
- package/src/oauth/google-antigravity.ts +7 -2
- package/src/providers/antigravity-models.ts +126 -17
- package/src/providers/derive.ts +11 -1
- package/src/responses/parser.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +16 -1
- package/src/responses/thought-signature-replay.ts +17 -1
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +2 -10
- 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/provider-routes.ts +22 -0
- package/src/server/management/shared.ts +4 -4
- package/src/server/management-api.ts +2 -2
- package/src/server/request-log.ts +11 -3
- package/src/server/responses/core.ts +4 -1
- package/src/server/responses/input-admission.ts +13 -10
- package/src/server/system-env.ts +3 -3
- package/src/types.ts +13 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a `done` event's `stopReason` means the turn was cut short rather than finishing, and
|
|
3
|
+
* which Responses `incomplete_details.reason` it maps to.
|
|
4
|
+
*
|
|
5
|
+
* `stopReason` is an open-ended string and adapters do not agree on a vocabulary: openai-chat and
|
|
6
|
+
* google normalize to `max_tokens`/`content_filter`, Command Code forwards the raw provider or AI
|
|
7
|
+
* SDK value (`length`, `content-filter`, `error`), and Anthropic forwards `stop_reason` verbatim
|
|
8
|
+
* (`refusal`, `model_context_window_exceeded`, ...). A guard that matched only the two canonical
|
|
9
|
+
* strings let those turns read as completed — and, on a compaction turn, install a half-written
|
|
10
|
+
* summary as replacement history (#422).
|
|
11
|
+
*
|
|
12
|
+
* Classifying here keeps that decision independent of which adapter produced the event, and keeps
|
|
13
|
+
* suppression and terminal status in agreement: a turn whose compaction item is withheld must not
|
|
14
|
+
* also report success, or codex-rs receives a completed response with zero compaction items and
|
|
15
|
+
* fatals.
|
|
16
|
+
*
|
|
17
|
+
* Unknown reasons are deliberately NOT truncated. This must never turn a healthy turn into a
|
|
18
|
+
* failure, and an unrecognized value is far more likely an ordinary stop.
|
|
19
|
+
*/
|
|
20
|
+
type TruncationKind = "max_output_tokens" | "content_filter";
|
|
21
|
+
|
|
22
|
+
const TRUNCATED_STOP_REASONS = new Map<string, TruncationKind>([
|
|
23
|
+
// canonical (openai-chat, google)
|
|
24
|
+
["max_tokens", "max_output_tokens"],
|
|
25
|
+
["content_filter", "content_filter"],
|
|
26
|
+
// raw OpenAI / Command Code (AI SDK) finish reasons
|
|
27
|
+
["length", "max_output_tokens"],
|
|
28
|
+
["content-filter", "content_filter"],
|
|
29
|
+
// raw Anthropic stop reasons
|
|
30
|
+
["max_output_tokens", "max_output_tokens"],
|
|
31
|
+
["model_context_window_exceeded", "max_output_tokens"],
|
|
32
|
+
["refusal", "content_filter"],
|
|
33
|
+
// Anthropic documents `pause_turn` as a long-running turn that the client is expected to
|
|
34
|
+
// CONTINUE. Whatever was produced so far is by definition unfinished, so it must not be
|
|
35
|
+
// installed as replacement history.
|
|
36
|
+
["pause_turn", "max_output_tokens"],
|
|
37
|
+
// raw Gemini / Vertex finish reasons
|
|
38
|
+
["malformed_function_call", "content_filter"],
|
|
39
|
+
["malformed_response", "content_filter"],
|
|
40
|
+
["unexpected_tool_call", "content_filter"],
|
|
41
|
+
["safety", "content_filter"],
|
|
42
|
+
["recitation", "content_filter"],
|
|
43
|
+
["blocklist", "content_filter"],
|
|
44
|
+
["prohibited_content", "content_filter"],
|
|
45
|
+
["spii", "content_filter"],
|
|
46
|
+
["image_safety", "content_filter"],
|
|
47
|
+
["language", "content_filter"],
|
|
48
|
+
// Kiro
|
|
49
|
+
["model_context_window_exceeded_exception", "max_output_tokens"],
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/** The `incomplete_details.reason` a truncated stop maps to, or undefined for a normal stop. */
|
|
53
|
+
export function truncationReasonFor(stopReason: string | undefined): TruncationKind | undefined {
|
|
54
|
+
if (stopReason === undefined) return undefined;
|
|
55
|
+
return TRUNCATED_STOP_REASONS.get(stopReason.trim().toLowerCase());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isTruncatedStopReason(stopReason: string | undefined): boolean {
|
|
59
|
+
return truncationReasonFor(stopReason) !== undefined;
|
|
60
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { hasOwnProvider, resolveEnvValue } from "./config";
|
|
|
12
12
|
import { assertProviderDestinationAllowed } from "./lib/destination-policy";
|
|
13
13
|
import { redactSecretString, redactUrlForLog } from "./lib/redact";
|
|
14
14
|
import { PROVIDER_REGISTRY, providerCodexAccountMode } from "./providers/registry";
|
|
15
|
-
import { applyDirectReasoningEffortContracts } from "./providers/derive";
|
|
15
|
+
import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive";
|
|
16
16
|
import {
|
|
17
17
|
providerMatchesRegistryTransportWithStaticGuards,
|
|
18
18
|
providerSupportsLiveModelDiscovery,
|
|
@@ -286,14 +286,6 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
286
286
|
const modelReasoningEffortMap = mergeNestedRecord(registryEntry.modelReasoningEffortMap, provider.modelReasoningEffortMap);
|
|
287
287
|
const modelReasoningEfforts = mergeStringArrayRecord(registryEntry.modelReasoningEfforts, provider.modelReasoningEfforts);
|
|
288
288
|
const modelDefaultReasoningEfforts = mergeRecordFill(registryEntry.modelDefaultReasoningEfforts, provider.modelDefaultReasoningEfforts);
|
|
289
|
-
// Key-login used to persist this exact low-only ClinePass capability seed. Once the gateway's
|
|
290
|
-
// wider input ladder was live-verified, leaving that generated row untouched would keep old
|
|
291
|
-
// installs clamped forever. This branch is reached only after canonical transport matching, so
|
|
292
|
-
// same-named custom destinations and every other explicit ladder still retain user precedence.
|
|
293
|
-
const repairLegacyClinePassReasoningEfforts = providerName === "cline-pass"
|
|
294
|
-
&& provider.reasoningWireFormat === "gateway-object"
|
|
295
|
-
&& provider.reasoningEfforts?.length === 1
|
|
296
|
-
&& provider.reasoningEfforts[0] === "low";
|
|
297
289
|
const modelContextWindows = providerName === OPENAI_API_PROVIDER_ID
|
|
298
290
|
? mergePositiveNumberCaps(registryEntry.modelContextWindows, provider.modelContextWindows)
|
|
299
291
|
: mergeRecordFill(registryEntry.modelContextWindows, provider.modelContextWindows);
|
|
@@ -374,7 +366,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
374
366
|
...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}),
|
|
375
367
|
...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}),
|
|
376
368
|
...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
|
|
377
|
-
...((provider.reasoningEfforts === undefined ||
|
|
369
|
+
...((provider.reasoningEfforts === undefined || hasLegacyClinePassReasoningEfforts(providerName, provider))
|
|
378
370
|
&& registryEntry.reasoningEfforts !== undefined
|
|
379
371
|
? { reasoningEfforts: [...registryEntry.reasoningEfforts] }
|
|
380
372
|
: {}),
|
|
@@ -13,11 +13,10 @@
|
|
|
13
13
|
import type { OcxConfig } from "../types";
|
|
14
14
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
15
15
|
import { serviceTierSupportForModel } from "../providers/service-tier";
|
|
16
|
-
import { applyProviderContextCap, providerContextCap } from "../providers/context-cap";
|
|
17
16
|
import { PROVIDER_REGISTRY } from "../providers/registry";
|
|
18
17
|
import {
|
|
19
18
|
nativeInputModalities,
|
|
20
|
-
nativeOpenAiContextWindow,
|
|
19
|
+
nativeContextLimits, nativeOpenAiContextWindow,
|
|
21
20
|
nativeParallelToolCalls,
|
|
22
21
|
nativeReasoningEfforts,
|
|
23
22
|
} from "../codex/catalog/metadata";
|
|
@@ -164,11 +163,11 @@ export function candidateCapabilityEvidence(
|
|
|
164
163
|
?? provider?.contextWindow
|
|
165
164
|
?? registryEntry?.modelContextWindows?.[modelId]
|
|
166
165
|
?? catalogRow?.contextWindow
|
|
167
|
-
?? (isNative ? nativeOpenAiContextWindow(modelId) : undefined);
|
|
168
|
-
//
|
|
169
|
-
//
|
|
166
|
+
?? (isNative ? nativeOpenAiContextWindow(modelId, nativeContextLimits(config)) : undefined);
|
|
167
|
+
// Native rows go through the accessor (raise-to-ceiling + opt-in). Routed rows keep
|
|
168
|
+
// the raw value; a provider cap on openai must not invent a window they do not have.
|
|
170
169
|
const contextWindow = isNative
|
|
171
|
-
?
|
|
170
|
+
? (nativeOpenAiContextWindow(modelId, nativeContextLimits(config)) ?? rawContextWindow)
|
|
172
171
|
: rawContextWindow;
|
|
173
172
|
|
|
174
173
|
const modalities = provider?.modelInputModalities?.[modelId]
|
package/src/server/index.ts
CHANGED
|
@@ -50,7 +50,6 @@ import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup"
|
|
|
50
50
|
import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
|
|
51
51
|
import { runModelRenameStartupMigration } from "../providers/model-rename-startup";
|
|
52
52
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
53
|
-
import { providerContextCap } from "../providers/context-cap";
|
|
54
53
|
import { providerCodexAccountMode } from "../providers/registry";
|
|
55
54
|
import type { StorageCleanupPolicy } from "../types";
|
|
56
55
|
import { MAX_DECOMPRESSED_BODY_BYTES } from "./request-decompress";
|
|
@@ -906,7 +905,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
906
905
|
}
|
|
907
906
|
throw error;
|
|
908
907
|
}
|
|
909
|
-
const { accountBoundNativeOpenAiSlugsBySelector, applyNativeVisibility, buildCatalogEntries, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, NATIVE_OPENAI_MODELS, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, orderForSubagents, filterCatalogVisibleModels, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, uniqueCatalogModelsForRawPublicList, visibleCodexAccountSelectors, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
|
|
908
|
+
const { accountBoundNativeOpenAiSlugsBySelector, applyNativeVisibility, buildCatalogEntries, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, NATIVE_OPENAI_MODELS, nativeContextLimits, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, orderForSubagents, filterCatalogVisibleModels, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, uniqueCatalogModelsForRawPublicList, visibleCodexAccountSelectors, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
|
|
910
909
|
const includeNativeOpenAi = shouldIncludeNativeOpenAi(config);
|
|
911
910
|
const includeAccountBoundNativeOpenAi = shouldIncludeAccountBoundNativeOpenAi(config);
|
|
912
911
|
const nativeSlugs = includeNativeOpenAi
|
|
@@ -958,7 +957,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
958
957
|
: idsParam === "desktop"
|
|
959
958
|
? "desktop3p" as const
|
|
960
959
|
: (/^claude-code\//i.test(req.headers.get("user-agent") ?? "") ? "readable" as const : "desktop3p" as const);
|
|
961
|
-
const data = buildAnthropicModelInfos([...desktopVisibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias,
|
|
960
|
+
const data = buildAnthropicModelInfos([...desktopVisibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias, nativeContextLimits(config));
|
|
962
961
|
return jsonResponse({ data }, 200, req, policy);
|
|
963
962
|
}
|
|
964
963
|
if (url.searchParams.has("client_version")) {
|
|
@@ -985,7 +984,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
985
984
|
accountSelectors,
|
|
986
985
|
suppressedBareNativeSlugs,
|
|
987
986
|
new Set(),
|
|
988
|
-
|
|
987
|
+
nativeContextLimits(config),
|
|
989
988
|
accountNativeSlugs,
|
|
990
989
|
accountNativeSlugsBySelector,
|
|
991
990
|
config.keepNativeChatGptOnV1 === true,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import type { CatalogModel } from "../../codex/catalog";
|
|
4
|
-
import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
4
|
+
import { catalogModelSlug, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_SUBAGENT_MODELS,
|
|
7
7
|
codexAutoStartEnabled,
|
|
@@ -207,7 +207,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
207
207
|
current.apiKeys?.[0]?.key,
|
|
208
208
|
"static",
|
|
209
209
|
current.claudeCode.desktopProfile,
|
|
210
|
-
|
|
210
|
+
nativeContextLimits(current),
|
|
211
211
|
);
|
|
212
212
|
if (result.written && result.fingerprint) {
|
|
213
213
|
current.claudeCode = { ...current.claudeCode, desktopProfile: { ...current.claudeCode.desktopProfile, appliedFingerprint: result.fingerprint, appliedAt: new Date().toISOString() } };
|
|
@@ -886,7 +886,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
886
886
|
latest.apiKeys?.[0]?.key,
|
|
887
887
|
mode,
|
|
888
888
|
state.profile,
|
|
889
|
-
|
|
889
|
+
nativeContextLimits(latest),
|
|
890
890
|
);
|
|
891
891
|
if (!result.written) return jsonResponse({ error: result.reason ?? "Claude Desktop apply failed", saved: true, path: result.path }, 500);
|
|
892
892
|
// Persist applied fingerprint + timestamp so GUI can show saved-vs-applied state.
|
|
@@ -978,7 +978,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
978
978
|
if (isDisabled(m.provider, m.id)) continue;
|
|
979
979
|
aliases.push({ id: claudeCodeAlias(m.provider, m.id), display_name: `${m.id} (${m.provider})` });
|
|
980
980
|
}
|
|
981
|
-
const contextWindows = buildClaudeContextWindows([...visibleNativeSlugs(config)], models,
|
|
981
|
+
const contextWindows = buildClaudeContextWindows([...visibleNativeSlugs(config)], models, nativeContextLimits(config));
|
|
982
982
|
const webSearchOverride = config.claudeCode?.webSearchSidecar;
|
|
983
983
|
const visionOverride = config.claudeCode?.visionSidecar;
|
|
984
984
|
// Auto is a RESOLUTION, recomputed per request — never stored state. Detection is
|
|
@@ -1010,7 +1010,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1010
1010
|
classifierModel: config.claudeCode?.classifierModel ?? "",
|
|
1011
1011
|
classifierFallbacks: config.claudeCode?.classifierFallbacks ?? [],
|
|
1012
1012
|
systemEnv: config.claudeCode?.systemEnv === true,
|
|
1013
|
-
autoConnectSupported: process.platform === "darwin",
|
|
1013
|
+
autoConnectSupported: (ctx.deps.platform ?? process.platform) === "darwin",
|
|
1014
1014
|
maxContextTokens: config.claudeCode?.maxContextTokens ?? null,
|
|
1015
1015
|
alwaysEnableEffort: config.claudeCode?.alwaysEnableEffort === true,
|
|
1016
1016
|
autoContext: config.claudeCode?.autoContext !== false,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import type { CatalogModel } from "../../codex/catalog";
|
|
4
|
-
import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
4
|
+
import { catalogModelSlug, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_SUBAGENT_MODELS,
|
|
7
7
|
codexAutoStartEnabled,
|
|
@@ -173,7 +173,7 @@ async function syncEnabledClientIntegrations(
|
|
|
173
173
|
config.apiKeys?.[0]?.key,
|
|
174
174
|
"static",
|
|
175
175
|
config.claudeCode?.desktopProfile,
|
|
176
|
-
|
|
176
|
+
nativeContextLimits(config),
|
|
177
177
|
);
|
|
178
178
|
out.push(r.written
|
|
179
179
|
? { client: "claude-desktop", ok: true, changed: true }
|
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
} from "../../codex/app-server-restart-service";
|
|
17
17
|
|
|
18
18
|
export interface ManagementApiDeps {
|
|
19
|
+
/** Platform seam for capability projections; does not alter host-level startup behavior. */
|
|
20
|
+
platform?: NodeJS.Platform;
|
|
19
21
|
toggleCodexMultiAgentV2?: (enabled: boolean) => void;
|
|
20
22
|
toggleDefaultModeRequestUserInput?: (enabled: boolean) => void;
|
|
21
23
|
createManagementConvergeCodex?: (config: Readonly<OcxConfig>) => ConvergeCodex;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* 011 (Claude Code), 012 (Grok).
|
|
19
19
|
*/
|
|
20
20
|
import { loadConfig, readRuntimePort, saveConfigPreservingClaudeCode } from "../../config";
|
|
21
|
-
import { desktopVisibleNativeSlugs, filterCatalogVisibleModels, nativeOpenAiContextWindow, visibleNativeSlugs } from "../../codex/catalog";
|
|
21
|
+
import { desktopVisibleNativeSlugs, filterCatalogVisibleModels, nativeContextLimits, nativeOpenAiContextWindow, visibleNativeSlugs } from "../../codex/catalog";
|
|
22
22
|
import { providerContextCap } from "../../providers/context-cap";
|
|
23
23
|
import { OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
24
24
|
import { inspectDesktop3pConfigLibrary, removeDesktop3pStandardPivot, writeDesktop3pConfig } from "../../claude/desktop-3p";
|
|
@@ -508,7 +508,7 @@ async function handleGrokToggle(ctx: ManagementContext): Promise<Response> {
|
|
|
508
508
|
// Native slugs carry their context window: without it Grok falls back
|
|
509
509
|
// to its own 200k default and understates a 372k model.
|
|
510
510
|
...visibleNativeSlugs(config).map(id => {
|
|
511
|
-
const contextWindow = nativeOpenAiContextWindow(id,
|
|
511
|
+
const contextWindow = nativeOpenAiContextWindow(id, nativeContextLimits(config));
|
|
512
512
|
return { id, ...(contextWindow !== undefined ? { contextWindow } : {}) };
|
|
513
513
|
}),
|
|
514
514
|
...routed.map(m => ({
|
|
@@ -663,7 +663,7 @@ async function handleClaudeDesktopToggle(ctx: ManagementContext): Promise<Respon
|
|
|
663
663
|
latest.apiKeys?.[0]?.key,
|
|
664
664
|
"static",
|
|
665
665
|
latest.claudeCode?.desktopProfile,
|
|
666
|
-
|
|
666
|
+
nativeContextLimits(latest),
|
|
667
667
|
);
|
|
668
668
|
if (!result.written) return postCommitRefusal(500, "claude-desktop", "write_failed", "Claude Desktop apply failed.", { desiredEnabled: latestDesiredEnabled });
|
|
669
669
|
return jsonResponse({
|
|
@@ -33,6 +33,7 @@ import { replaceProviderAccountSet } from "../../oauth/store";
|
|
|
33
33
|
import { providerDestinationResolvedError } from "../../lib/destination-policy";
|
|
34
34
|
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
35
35
|
import { ProviderOutboundPolicyError, providerOutboundGet, providerOutboundPost, providerRedirectError } from "../../lib/provider-outbound";
|
|
36
|
+
import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
|
|
36
37
|
import { parseAntigravityAvailableModels } from "../../providers/antigravity-models";
|
|
37
38
|
import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
|
|
38
39
|
import { deriveProviderPresets } from "../../providers/derive";
|
|
@@ -732,6 +733,27 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
732
733
|
if (prov.authMode === "oauth" && !apiKey) {
|
|
733
734
|
return jsonResponse({ ok: false, latencyMs: 0, error: "static catalog only — upstream not verified (not logged in)" });
|
|
734
735
|
}
|
|
736
|
+
if (prov.adapter === "cursor") {
|
|
737
|
+
const started = Date.now();
|
|
738
|
+
const live = await fetchCursorUsableModels({
|
|
739
|
+
apiKey: apiKey ?? "",
|
|
740
|
+
baseUrl: prov.baseUrl,
|
|
741
|
+
});
|
|
742
|
+
const latencyMs = Date.now() - started;
|
|
743
|
+
if (!live.ok) {
|
|
744
|
+
return jsonResponse({
|
|
745
|
+
ok: false,
|
|
746
|
+
latencyMs,
|
|
747
|
+
error: `cursor discovery ${live.error}${live.detail ? `: ${live.detail}` : ""}`,
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
return jsonResponse({
|
|
751
|
+
ok: true,
|
|
752
|
+
latencyMs,
|
|
753
|
+
models: live.models.length,
|
|
754
|
+
message: `Connected. ${live.models.length} models.`,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
735
757
|
const project = prov.project ?? snapshot?.projectId;
|
|
736
758
|
if (antigravity && !project) {
|
|
737
759
|
return jsonResponse({ ok: false, latencyMs: 0, error: "Antigravity project unavailable — re-run `ocx login google-antigravity`" });
|
|
@@ -192,11 +192,11 @@ export interface GrokCandidateModel {
|
|
|
192
192
|
* from the same two sources as the sync so the two can never disagree.
|
|
193
193
|
*/
|
|
194
194
|
export async function fetchGrokCandidateModels(config: OcxConfig): Promise<GrokCandidateModel[]> {
|
|
195
|
-
const { filterCatalogVisibleModels, nativeOpenAiContextWindow, visibleNativeSlugs } = await import("../../codex/catalog");
|
|
195
|
+
const { filterCatalogVisibleModels, nativeContextLimits, nativeOpenAiContextWindow, visibleNativeSlugs } = await import("../../codex/catalog");
|
|
196
196
|
const routed = filterCatalogVisibleModels(await fetchAllModels(config), config);
|
|
197
197
|
return [
|
|
198
198
|
...visibleNativeSlugs(config).map(id => {
|
|
199
|
-
const contextWindow = nativeOpenAiContextWindow(id,
|
|
199
|
+
const contextWindow = nativeOpenAiContextWindow(id, nativeContextLimits(config));
|
|
200
200
|
return { id, native: true, ...(contextWindow !== undefined ? { contextWindow } : {}) };
|
|
201
201
|
}),
|
|
202
202
|
...routed.map(m => ({
|
|
@@ -221,7 +221,7 @@ export function stripRegistryOnlyStaticHeaders(name: string, provider: OcxProvid
|
|
|
221
221
|
|
|
222
222
|
/** Shared Desktop profile DTO builder for the management API and CLI. */
|
|
223
223
|
export async function buildClaudeDesktopState(config: OcxConfig, stored?: OcxClaudeDesktopProfile) {
|
|
224
|
-
const { filterCatalogVisibleModels, nativeOpenAiContextWindow, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
|
|
224
|
+
const { filterCatalogVisibleModels, nativeContextLimits, nativeOpenAiContextWindow, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
|
|
225
225
|
const { DESKTOP_SUPPORTS_1M_THRESHOLD } = await import("../../claude/desktop-3p");
|
|
226
226
|
const { reconcileDesktopProfile, renderDesktopProfile } = await import("../../claude/desktop-profile");
|
|
227
227
|
const routed = filterCatalogVisibleModels(await fetchAllModels(config), config);
|
|
@@ -229,7 +229,7 @@ export async function buildClaudeDesktopState(config: OcxConfig, stored?: OcxCla
|
|
|
229
229
|
// Native rows carry their real context window from the same accessor the Grok sync
|
|
230
230
|
// uses — otherwise Sol's 372k and gpt-5.5's 272k render as blank on Desktop.
|
|
231
231
|
...desktopVisibleNativeSlugs(config).map(id => {
|
|
232
|
-
const contextWindow = nativeOpenAiContextWindow(id,
|
|
232
|
+
const contextWindow = nativeOpenAiContextWindow(id, nativeContextLimits(config));
|
|
233
233
|
return { route: `native/${id}`, label: `${id} (native)`,
|
|
234
234
|
...(contextWindow !== undefined ? { contextWindow } : {}) };
|
|
235
235
|
}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import type { CatalogModel } from "../codex/catalog";
|
|
4
|
-
import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../codex/catalog";
|
|
4
|
+
import { catalogModelSlug, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../codex/catalog";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_SUBAGENT_MODELS,
|
|
7
7
|
codexAutoStartEnabled,
|
|
@@ -205,7 +205,7 @@ export async function handleManagementAPI(
|
|
|
205
205
|
import("../claude/context-windows"),
|
|
206
206
|
import("../codex/catalog"),
|
|
207
207
|
]);
|
|
208
|
-
injectClaudeAgentDefs(config, buildClaudeContextWindows([...visibleNativeSlugs(config)], models,
|
|
208
|
+
injectClaudeAgentDefs(config, buildClaudeContextWindows([...visibleNativeSlugs(config)], models, nativeContextLimits(config)));
|
|
209
209
|
} catch {
|
|
210
210
|
// Keep routes available through a provider-discovery blip. A later
|
|
211
211
|
// launch-time sync restores any context markers missing from this pass.
|
|
@@ -1004,10 +1004,14 @@ function finalizedUsage(
|
|
|
1004
1004
|
const usageFallback = !finalUsage && estimate !== undefined
|
|
1005
1005
|
? { inputTokens: estimate, outputTokens: 0, estimated: true }
|
|
1006
1006
|
: undefined;
|
|
1007
|
-
const
|
|
1007
|
+
const combinedInputTokens = finalUsage && estimate !== undefined
|
|
1008
|
+
? Math.max(finalUsage.inputTokens, estimate)
|
|
1009
|
+
: undefined;
|
|
1010
|
+
const loggedUsage = finalUsage && combinedInputTokens !== undefined
|
|
1008
1011
|
? {
|
|
1009
1012
|
...finalUsage,
|
|
1010
|
-
inputTokens:
|
|
1013
|
+
inputTokens: combinedInputTokens,
|
|
1014
|
+
totalTokens: combinedInputTokens + finalUsage.outputTokens,
|
|
1011
1015
|
estimated: true,
|
|
1012
1016
|
}
|
|
1013
1017
|
: finalUsage
|
|
@@ -1017,7 +1021,11 @@ function finalizedUsage(
|
|
|
1017
1021
|
// ESTIMATE via capEstimateAtContextWindow, and Math.max preserves a real
|
|
1018
1022
|
// provider-reported count, so it needs no further reduction.
|
|
1019
1023
|
? (finalUsage.estimated && contextWindow !== undefined && finalUsage.inputTokens > contextWindow
|
|
1020
|
-
? {
|
|
1024
|
+
? {
|
|
1025
|
+
...finalUsage,
|
|
1026
|
+
inputTokens: contextWindow,
|
|
1027
|
+
totalTokens: contextWindow + finalUsage.outputTokens,
|
|
1028
|
+
}
|
|
1021
1029
|
: finalUsage)
|
|
1022
1030
|
: usageFallback;
|
|
1023
1031
|
const totalTokens = usageTotalTokens(loggedUsage);
|
|
@@ -2,6 +2,7 @@ import type { Server } from "bun";
|
|
|
2
2
|
import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
|
|
3
3
|
import { formatPassthroughUpstreamError } from "./passthrough-error";
|
|
4
4
|
import { checkInputAdmission } from "./input-admission";
|
|
5
|
+
import { nativeContextLimits } from "../../codex/catalog";
|
|
5
6
|
import { describeUpstreamConnectFailure } from "./upstream-error";
|
|
6
7
|
import {
|
|
7
8
|
getConfigPath,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
bindReasoningReplayScope,
|
|
14
15
|
reasoningReplayCodexCredentialIdentity,
|
|
15
16
|
reasoningReplayDestinationIdentity,
|
|
17
|
+
durableReplayDestinationIdentity,
|
|
16
18
|
reasoningReplayKeyCredentialIdentity,
|
|
17
19
|
reasoningReplayOAuthCredentialIdentity,
|
|
18
20
|
} from "../../responses/reasoning-replay-cache";
|
|
@@ -335,6 +337,7 @@ function bindRouteReasoningReplayScope(args: {
|
|
|
335
337
|
? {
|
|
336
338
|
providerName,
|
|
337
339
|
providerDestinationIdentity,
|
|
340
|
+
providerDestinationDurableIdentity: durableReplayDestinationIdentity(provider.baseUrl),
|
|
338
341
|
adapterName,
|
|
339
342
|
modelId: parsed.modelId,
|
|
340
343
|
credentialIdentity,
|
|
@@ -1994,7 +1997,7 @@ async function handleResponsesInner(
|
|
|
1994
1997
|
// refusing the turn that shrinks the context would deadlock the client against the very
|
|
1995
1998
|
// limit this gate reports — it would be told to compact and then denied the compaction.
|
|
1996
1999
|
if (parsed._compactionRequest !== true) {
|
|
1997
|
-
const inputAdmission = checkInputAdmission(parsed, route.provider, route.providerName, parsed.modelId,
|
|
2000
|
+
const inputAdmission = checkInputAdmission(parsed, route.provider, route.providerName, parsed.modelId, nativeContextLimits(config));
|
|
1998
2001
|
if (!inputAdmission.admitted) {
|
|
1999
2002
|
// #1524: this is a LOCAL preflight refusal, not an upstream verdict. A policy or combo
|
|
2000
2003
|
// fallback must be able to skip this candidate and try one whose context window fits,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* catches the pathological case and stays out of the way otherwise. Every uncertainty
|
|
11
11
|
* resolves toward admitting.
|
|
12
12
|
*/
|
|
13
|
-
import { nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens } from "../../codex/catalog/metadata";
|
|
13
|
+
import { nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "../../codex/catalog/metadata";
|
|
14
14
|
import { estimateTokens } from "../../lib/token-estimate";
|
|
15
15
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
16
16
|
import type { OcxContentPart, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
@@ -131,7 +131,7 @@ export function resolveInputCeiling(
|
|
|
131
131
|
modelId: string,
|
|
132
132
|
// Operator cap for the canonical native provider. Passed in rather than read from a
|
|
133
133
|
// config here so this stays pure: no filesystem, no catalog, no registry scan.
|
|
134
|
-
nativeContextCap?:
|
|
134
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
135
135
|
): number | null {
|
|
136
136
|
const configured = positive(provider.modelContextWindows?.[modelId]) ?? positive(provider.contextWindow);
|
|
137
137
|
|
|
@@ -143,15 +143,18 @@ export function resolveInputCeiling(
|
|
|
143
143
|
const canonicalNativeBare = providerName === OPENAI_CODEX_PROVIDER_ID
|
|
144
144
|
&& isCanonicalOpenAiForwardProvider(provider)
|
|
145
145
|
&& !modelId.includes("/");
|
|
146
|
-
const
|
|
147
|
-
?
|
|
146
|
+
const nativeLimits = canonicalNativeBare && configured !== null
|
|
147
|
+
? {
|
|
148
|
+
...(typeof nativeContextCap === "number" ? { cap: nativeContextCap } : (nativeContextCap ?? {})),
|
|
149
|
+
modelWindows: { [modelId]: configured },
|
|
150
|
+
}
|
|
151
|
+
: nativeContextCap;
|
|
152
|
+
const native = canonicalNativeBare
|
|
153
|
+
? positive(nativeOpenAiContextWindow(modelId, nativeLimits))
|
|
148
154
|
: null;
|
|
149
|
-
|
|
150
|
-
// window: a 1,050,000 override must not raise the gate above the 922,000 the upstream
|
|
151
|
-
// actually accepts. Computed independently of `configured` for exactly that reason.
|
|
152
|
-
const nativeMaxInput = canonicalNativeBare ? positive(nativeOpenAiMaxInputTokens(modelId, nativeContextCap)) : null;
|
|
155
|
+
const nativeMaxInput = canonicalNativeBare ? positive(nativeOpenAiMaxInputTokens(modelId, nativeLimits)) : null;
|
|
153
156
|
|
|
154
|
-
const window =
|
|
157
|
+
const window = canonicalNativeBare ? native : configured;
|
|
155
158
|
// modelMaxInputTokens is an input-only cap, so it can only tighten the window.
|
|
156
159
|
const configuredMaxInput = positive(provider.modelMaxInputTokens?.[modelId]);
|
|
157
160
|
const limits = [window, configuredMaxInput, nativeMaxInput].filter((v): v is number => v !== null);
|
|
@@ -168,7 +171,7 @@ export function checkInputAdmission(
|
|
|
168
171
|
provider: OcxProviderConfig,
|
|
169
172
|
providerName: string,
|
|
170
173
|
modelId: string,
|
|
171
|
-
nativeContextCap?:
|
|
174
|
+
nativeContextCap?: NativeContextLimitsInput,
|
|
172
175
|
): InputAdmissionResult {
|
|
173
176
|
const ceiling = resolveInputCeiling(provider, providerName, modelId, nativeContextCap);
|
|
174
177
|
if (ceiling === null) return { admitted: true, estimatedTokens: 0, ceiling: null };
|
package/src/server/system-env.ts
CHANGED
|
@@ -237,12 +237,12 @@ function rollbackInjectedKeys(port: number, injectedKeys: string[]): void {
|
|
|
237
237
|
async function computeEffectiveModelEnv(config: OcxConfig, auto?: AutoContextMode): Promise<{ modelEnv: Record<string, string>; windows: Record<string, number> }> {
|
|
238
238
|
const { boundedContextWindows, buildClaudeContextWindows, effectiveModelEnv } = await import("../claude/context-windows");
|
|
239
239
|
const windows = await boundedContextWindows(async () => {
|
|
240
|
-
const { gatherRoutedModels, visibleNativeSlugs } = await import("../codex/catalog");
|
|
240
|
+
const { gatherRoutedModels, nativeContextLimits, visibleNativeSlugs } = await import("../codex/catalog");
|
|
241
241
|
try {
|
|
242
|
-
return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config),
|
|
242
|
+
return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config), nativeContextLimits(config));
|
|
243
243
|
} catch (error) {
|
|
244
244
|
if (error && typeof error === "object" && (error as { code?: unknown }).code === "catalog_busy") {
|
|
245
|
-
return buildClaudeContextWindows([...visibleNativeSlugs(config)], [],
|
|
245
|
+
return buildClaudeContextWindows([...visibleNativeSlugs(config)], [], nativeContextLimits(config));
|
|
246
246
|
}
|
|
247
247
|
throw error;
|
|
248
248
|
}
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,11 @@ export interface OcxReasoningReplayIdentity {
|
|
|
5
5
|
providerName: string;
|
|
6
6
|
/** Opaque process-local digest of the exact upstream destination. */
|
|
7
7
|
providerDestinationIdentity: string;
|
|
8
|
+
/**
|
|
9
|
+
* The same destination, digested WITHOUT the process-local random key, so it can key a
|
|
10
|
+
* durable store. Absent when no base URL was resolvable.
|
|
11
|
+
*/
|
|
12
|
+
providerDestinationDurableIdentity?: string;
|
|
8
13
|
adapterName: string;
|
|
9
14
|
modelId: string;
|
|
10
15
|
/** Opaque process-local credential identity; never a raw token or API key. */
|
|
@@ -1392,8 +1397,15 @@ export interface OcxProviderConfig {
|
|
|
1392
1397
|
* HTTP/2 streaming responses (issue #1668). "http1.1" / "h1" forces HTTP/1.1,
|
|
1393
1398
|
* "http2" / "h2" forces HTTP/2. Absent or "auto" keeps Bun's default negotiation
|
|
1394
1399
|
* (current behavior unchanged). Only meaningful for https: base URLs.
|
|
1395
|
-
|
|
1400
|
+
*/
|
|
1396
1401
|
upstreamHttpVersion?: UpstreamHttpVersion;
|
|
1402
|
+
/**
|
|
1403
|
+
* Google only. When `false`, the AI Studio (direct) path sends Gemini Flash ids
|
|
1404
|
+
* unchanged to the wire instead of applying the `-tiered` suffix (`gemini-3.7-flash`
|
|
1405
|
+
* -> `gemini-3.7-flash-tiered`). Set this to `false` when the configured upstream still
|
|
1406
|
+
* serves the bare ids. Absent (default) keeps the rename.
|
|
1407
|
+
*/
|
|
1408
|
+
directGeminiWireRenames?: boolean;
|
|
1397
1409
|
/** Keep provider settings on disk but exclude it from routing and model/catalog listings. */
|
|
1398
1410
|
disabled?: boolean;
|
|
1399
1411
|
/**
|