@iislee/opencodex 2.31.0 → 2.35.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/README.md +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
entitledCodexAccountIdsForModel,
|
|
30
30
|
isDirectCallerEntitledToCodexModel,
|
|
31
31
|
resolveCodexModelEntitlements,
|
|
32
|
-
type CodexModelEntitlementSnapshot,
|
|
33
32
|
} from "./model-entitlements";
|
|
34
33
|
import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "./catalog/native-models";
|
|
35
34
|
import type { CodexCooldownSource, CodexQuotaScope } from "./routing";
|
|
@@ -334,9 +333,7 @@ export interface ResolveCodexAuthContextOptions {
|
|
|
334
333
|
getMainAccountToken?: typeof getMainAccountToken;
|
|
335
334
|
primeCodexPoolQuotas?: (config: OcxConfig, reason: string) => Promise<void>;
|
|
336
335
|
/** Test seam for account-gated native model discovery. */
|
|
337
|
-
resolveCodexModelEntitlements?:
|
|
338
|
-
config: Pick<OcxConfig, "codexAccounts">,
|
|
339
|
-
) => Promise<CodexModelEntitlementSnapshot>;
|
|
336
|
+
resolveCodexModelEntitlements?: typeof resolveCodexModelEntitlements;
|
|
340
337
|
/** Direct requests admitted with a proxy bearer substitute the stored native-main credential. */
|
|
341
338
|
substituteMainCredentialForDirect?: boolean;
|
|
342
339
|
/** Test seam for a Direct request's own forwarded ChatGPT credential. */
|
|
@@ -381,28 +378,34 @@ export async function resolveCodexAuthContext(
|
|
|
381
378
|
return { kind: "main", accountId: null };
|
|
382
379
|
}
|
|
383
380
|
const affinityKey = fixedAccountId === undefined ? codexPoolAffinityKey(headers) : undefined;
|
|
384
|
-
const entitlementSnapshot = options.modelId && ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(options.modelId)
|
|
385
|
-
? await (options.resolveCodexModelEntitlements ?? resolveCodexModelEntitlements)(config)
|
|
386
|
-
: undefined;
|
|
387
|
-
const modelEligibleAccountIds = entitlementSnapshot
|
|
388
|
-
? entitledCodexAccountIdsForModel(entitlementSnapshot, options.modelId)
|
|
389
|
-
: undefined;
|
|
390
381
|
// Retained startup recovery makes the physical main identity ineligible. Routing
|
|
391
382
|
// can still preserve service by selecting a healthy configured pool account.
|
|
392
383
|
const nativeMainTrafficBlocked = isNativeMainTrafficBlocked();
|
|
393
384
|
const selectionAdmission = options.beginCodexAccountSelection?.();
|
|
394
385
|
const nativeMainReadsForbidden = nativeMainTrafficBlocked || selectionAdmission?.mainProfileDraining === true;
|
|
395
|
-
const selectionOptions = {
|
|
396
|
-
// Temporary switch drain keeps the candidate until the atomic claim rejects
|
|
397
|
-
// it. Retained recovery makes main wholly ineligible so pool routing continues.
|
|
398
|
-
nativeMainSelectionOnly: !nativeMainTrafficBlocked
|
|
399
|
-
&& selectionAdmission?.mainProfileDraining === true,
|
|
400
|
-
isMainAccountTokenLive: options.isMainAccountTokenLive,
|
|
401
|
-
modelEligibleAccountIds,
|
|
402
|
-
};
|
|
403
386
|
let accountId: string;
|
|
404
387
|
const quotaScope = codexQuotaScopeForModel(options.modelId);
|
|
405
388
|
try {
|
|
389
|
+
const excludeAccountIds = nativeMainReadsForbidden
|
|
390
|
+
? new Set([MAIN_CODEX_ACCOUNT_ID])
|
|
391
|
+
: undefined;
|
|
392
|
+
const entitlementSnapshot = options.modelId && ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(options.modelId)
|
|
393
|
+
? await (options.resolveCodexModelEntitlements ?? resolveCodexModelEntitlements)(config, { excludeAccountIds })
|
|
394
|
+
: undefined;
|
|
395
|
+
const entitledAccountIds = entitlementSnapshot
|
|
396
|
+
? entitledCodexAccountIdsForModel(entitlementSnapshot, options.modelId)
|
|
397
|
+
: undefined;
|
|
398
|
+
const modelEligibleAccountIds = entitledAccountIds
|
|
399
|
+
? new Set([...entitledAccountIds].filter(candidate => !excludeAccountIds?.has(candidate)))
|
|
400
|
+
: undefined;
|
|
401
|
+
const selectionOptions = {
|
|
402
|
+
// Temporary switch drain keeps the candidate until the atomic claim rejects
|
|
403
|
+
// it. Retained recovery makes main wholly ineligible so pool routing continues.
|
|
404
|
+
nativeMainSelectionOnly: !nativeMainTrafficBlocked
|
|
405
|
+
&& selectionAdmission?.mainProfileDraining === true,
|
|
406
|
+
isMainAccountTokenLive: options.isMainAccountTokenLive,
|
|
407
|
+
modelEligibleAccountIds,
|
|
408
|
+
};
|
|
406
409
|
// A pre-drain selector reserves the native identity while reconciliation and
|
|
407
410
|
// routing inspect it. Selectors arriving after the fence skip reconciliation
|
|
408
411
|
// and may still route to non-main pool accounts without touching switch state.
|
|
@@ -154,3 +154,19 @@ export function startupHealthSummary(health: StartupHealth): string {
|
|
|
154
154
|
if (health.serviceInstalled && !health.serviceViable) return `AT RISK after restart (installed service is disabled, stopped, or unhealthy; run '${command}')`;
|
|
155
155
|
return `AT RISK after restart (no viable background service; run '${command}')`;
|
|
156
156
|
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The routing/service/shim token `ocx doctor` prints under restart safety.
|
|
160
|
+
* Extracted so `ocx status` can show the same string rather than growing a
|
|
161
|
+
* second copy that drifts (#2411). Two management routes computing the same
|
|
162
|
+
* thing separately is exactly how #2457 happened.
|
|
163
|
+
*/
|
|
164
|
+
export function formatStartupRoutingDetail(health: StartupHealth): string {
|
|
165
|
+
const service = health.serviceViable
|
|
166
|
+
? "viable"
|
|
167
|
+
: health.serviceInstalled ? "installed-but-unhealthy" : "absent";
|
|
168
|
+
const shim = health.shimHealthy
|
|
169
|
+
? "healthy"
|
|
170
|
+
: health.shimInstalled ? "stale" : "absent";
|
|
171
|
+
return `routing=${health.routingKind}, service=${service}, shim=${shim}`;
|
|
172
|
+
}
|
|
@@ -13,6 +13,7 @@ import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, r
|
|
|
13
13
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
14
14
|
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
15
15
|
import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
|
|
16
|
+
import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
|
|
16
17
|
import { routedSlug, slugEquals, slugEquivalenceKey, slugsEquivalent } from "../../providers/slug-codec";
|
|
17
18
|
import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
|
|
18
19
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
@@ -154,8 +155,16 @@ export function deriveComboCatalogModel(
|
|
|
154
155
|
// combo would have the same window even without the cap.
|
|
155
156
|
const contextCapped = limitingMembers.every(member => member.contextCapped === true);
|
|
156
157
|
const maxInputTokens = Math.min(
|
|
158
|
+
contextWindow,
|
|
157
159
|
...members.map(member => member.maxInputTokens ?? member.contextWindow!),
|
|
158
160
|
);
|
|
161
|
+
const autoCompactTokenLimit = Math.min(
|
|
162
|
+
...members.map(member => clampAutoCompactTokenLimit(
|
|
163
|
+
member.contextWindow!,
|
|
164
|
+
member.maxInputTokens,
|
|
165
|
+
member.autoCompactTokenLimit,
|
|
166
|
+
)),
|
|
167
|
+
);
|
|
159
168
|
const defaultReasoningEffort = effectiveComboDefault(
|
|
160
169
|
combo.defaultEffort,
|
|
161
170
|
reasoningEfforts,
|
|
@@ -167,6 +176,7 @@ export function deriveComboCatalogModel(
|
|
|
167
176
|
owned_by: COMBO_NAMESPACE,
|
|
168
177
|
contextWindow,
|
|
169
178
|
maxInputTokens,
|
|
179
|
+
autoCompactTokenLimit,
|
|
170
180
|
...(hasLimitingContextCapMetadata ? { contextCapped } : {}),
|
|
171
181
|
inputModalities,
|
|
172
182
|
reasoningEfforts,
|
|
@@ -183,6 +193,12 @@ export function deriveComboCatalogModel(
|
|
|
183
193
|
? { supportsServiceTier: false }
|
|
184
194
|
: {}),
|
|
185
195
|
...(members.some(member => member.supportsReasoningSummaries === false) ? { supportsReasoningSummaries: false } : {}),
|
|
196
|
+
// A combo is only as capable as its least capable member. One member that cannot honour
|
|
197
|
+
// text.verbosity is enough to make the control a no-op for the whole combo, so the
|
|
198
|
+
// conservative false propagates — the same rule supportsReasoningSummaries uses above.
|
|
199
|
+
// Without this, routing a combo through an xAI or Kiro member re-advertised a control the
|
|
200
|
+
// upstream accepts and ignores.
|
|
201
|
+
...(members.some(member => member.supportsVerbosity === false) ? { supportsVerbosity: false } : {}),
|
|
186
202
|
...(members.every(member => member.codexToolMode === "shell")
|
|
187
203
|
? { codexToolMode: "shell" as const }
|
|
188
204
|
: {}),
|
|
@@ -210,6 +226,7 @@ export function comboCatalogWarningSignature(
|
|
|
210
226
|
key,
|
|
211
227
|
contextWindow: member?.contextWindow ?? null,
|
|
212
228
|
maxInputTokens: member?.maxInputTokens ?? null,
|
|
229
|
+
autoCompactTokenLimit: member?.autoCompactTokenLimit ?? null,
|
|
213
230
|
inputModalities: [...new Set(member?.inputModalities ?? [])].sort(),
|
|
214
231
|
reasoningEfforts: [...new Set(member?.reasoningEfforts ?? [])].sort(),
|
|
215
232
|
parallelToolCalls: member?.parallelToolCalls === true,
|
|
@@ -299,6 +316,7 @@ export function normalizedOpenAiApiSignature(model: CatalogModel): string {
|
|
|
299
316
|
id: model.id,
|
|
300
317
|
contextWindow: model.contextWindow ?? null,
|
|
301
318
|
maxInputTokens: model.maxInputTokens ?? null,
|
|
319
|
+
autoCompactTokenLimit: model.autoCompactTokenLimit ?? null,
|
|
302
320
|
inputModalities: [...new Set(model.inputModalities ?? [])].sort(),
|
|
303
321
|
reasoningEfforts: [...new Set(model.reasoningEfforts ?? [])].sort(),
|
|
304
322
|
ownedBy: model.owned_by ?? null,
|
|
@@ -13,6 +13,7 @@ import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, r
|
|
|
13
13
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
14
14
|
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
15
15
|
import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
|
|
16
|
+
import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
|
|
16
17
|
import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
|
|
17
18
|
import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
|
|
18
19
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
@@ -128,9 +129,23 @@ export function applyCatalogModelMetadata(entry: RawEntry, model?: CatalogModel)
|
|
|
128
129
|
if (typeof resolvedContext === "number" && resolvedContext > 0) {
|
|
129
130
|
entry.context_window = resolvedContext;
|
|
130
131
|
entry.max_context_window = resolvedContext;
|
|
131
|
-
entry.auto_compact_token_limit =
|
|
132
|
-
|
|
133
|
-
model.maxInputTokens
|
|
132
|
+
entry.auto_compact_token_limit = clampAutoCompactTokenLimit(
|
|
133
|
+
resolvedContext,
|
|
134
|
+
model.maxInputTokens,
|
|
135
|
+
model.autoCompactTokenLimit,
|
|
136
|
+
);
|
|
137
|
+
} else if (
|
|
138
|
+
typeof entry.context_window === "number"
|
|
139
|
+
&& entry.context_window > 0
|
|
140
|
+
&& typeof model.maxInputTokens === "number"
|
|
141
|
+
&& model.maxInputTokens > 0
|
|
142
|
+
) {
|
|
143
|
+
// A conservative routed fallback is not evidence for applying the optional soft policy,
|
|
144
|
+
// but a measured/configured input ceiling is still a hard bound. Compact before that
|
|
145
|
+
// ceiling even when the provider supplied no authoritative context window.
|
|
146
|
+
entry.auto_compact_token_limit = clampAutoCompactTokenLimit(
|
|
147
|
+
entry.context_window,
|
|
148
|
+
model.maxInputTokens,
|
|
134
149
|
);
|
|
135
150
|
}
|
|
136
151
|
if (Array.isArray(model.inputModalities) && model.inputModalities.length > 0) {
|
|
@@ -14,6 +14,7 @@ import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, r
|
|
|
14
14
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
15
15
|
import { getProviderRegistryEntry, providerCodexAccountMode } from "../../providers/registry";
|
|
16
16
|
import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
|
|
17
|
+
import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
|
|
17
18
|
import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
|
|
18
19
|
import { identifyRoutedModel } from "../../adapters/identity";
|
|
19
20
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
@@ -204,6 +205,8 @@ export interface NativeContextLimits {
|
|
|
204
205
|
readonly providerWindow?: number;
|
|
205
206
|
/** `providers.openai.modelContextWindows` — per-model, wins over `providerWindow`. */
|
|
206
207
|
readonly modelWindows?: Readonly<Record<string, number>>;
|
|
208
|
+
/** `providers.openai.modelAutoCompactTokenLimits` — soft, lowering-only budgets. */
|
|
209
|
+
readonly modelAutoCompactTokenLimits?: Readonly<Record<string, number>>;
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
export type NativeContextLimitsInput = NativeContextLimits | number | undefined;
|
|
@@ -227,12 +230,18 @@ export function nativeContextLimits(
|
|
|
227
230
|
const window = positiveInt(value);
|
|
228
231
|
if (window !== undefined) modelWindows[slug] = window;
|
|
229
232
|
}
|
|
233
|
+
const modelAutoCompactTokenLimits: Record<string, number> = {};
|
|
234
|
+
for (const [slug, value] of Object.entries(provider?.modelAutoCompactTokenLimits ?? {})) {
|
|
235
|
+
const budget = positiveInt(value);
|
|
236
|
+
if (budget !== undefined) modelAutoCompactTokenLimits[slug] = budget;
|
|
237
|
+
}
|
|
230
238
|
return {
|
|
231
239
|
...(positiveInt(providerContextCap(config, OPENAI_CODEX_PROVIDER_ID)) !== undefined
|
|
232
240
|
? { cap: providerContextCap(config, OPENAI_CODEX_PROVIDER_ID) }
|
|
233
241
|
: {}),
|
|
234
242
|
...(positiveInt(provider?.contextWindow) !== undefined ? { providerWindow: provider!.contextWindow } : {}),
|
|
235
243
|
...(Object.keys(modelWindows).length > 0 ? { modelWindows } : {}),
|
|
244
|
+
...(Object.keys(modelAutoCompactTokenLimits).length > 0 ? { modelAutoCompactTokenLimits } : {}),
|
|
236
245
|
};
|
|
237
246
|
}
|
|
238
247
|
|
|
@@ -277,6 +286,21 @@ export function nativeOpenAiMaxInputTokens(slug: string, limits?: NativeContextL
|
|
|
277
286
|
return window === undefined ? narrowed : Math.min(narrowed, window);
|
|
278
287
|
}
|
|
279
288
|
|
|
289
|
+
/** Effective native soft budget after every hard window/input limit is resolved. */
|
|
290
|
+
export function nativeOpenAiAutoCompactTokenLimit(
|
|
291
|
+
slug: string,
|
|
292
|
+
limits?: NativeContextLimitsInput,
|
|
293
|
+
): number | undefined {
|
|
294
|
+
const contextWindow = nativeOpenAiContextWindow(slug, limits);
|
|
295
|
+
if (contextWindow === undefined) return undefined;
|
|
296
|
+
const configured = positiveInt(asLimits(limits).modelAutoCompactTokenLimits?.[slug]);
|
|
297
|
+
return clampAutoCompactTokenLimit(
|
|
298
|
+
contextWindow,
|
|
299
|
+
nativeOpenAiMaxInputTokens(slug, limits),
|
|
300
|
+
configured,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
280
304
|
export function nativeInputModalities(slug: string): string[] {
|
|
281
305
|
const upstream = PINNED_NATIVE_CAPABILITY_ENTRIES.get(slug);
|
|
282
306
|
if (Array.isArray(upstream?.input_modalities) && upstream!.input_modalities!.length > 0) {
|
|
@@ -387,7 +411,7 @@ export function desktopVisibleNativeSlugs(
|
|
|
387
411
|
]);
|
|
388
412
|
}
|
|
389
413
|
|
|
390
|
-
export function nativeModelRows(config: Pick<OcxConfig, "disabledModels" | "combos" | "providerContextCaps" | "providers">): Array<{ slug: string; disabled: boolean; contextWindow?: number; maxInputTokens?: number }> {
|
|
414
|
+
export function nativeModelRows(config: Pick<OcxConfig, "disabledModels" | "combos" | "providerContextCaps" | "providers">): Array<{ slug: string; disabled: boolean; contextWindow?: number; maxInputTokens?: number; autoCompactTokenLimit?: number }> {
|
|
391
415
|
const disabled = disabledNativeSlugs(config);
|
|
392
416
|
const shadowed = configuredNativeAliasSlugs(config);
|
|
393
417
|
// Both user levers, not just the cap: a per-model window set from the dashboard has to show
|
|
@@ -403,11 +427,13 @@ export function nativeModelRows(config: Pick<OcxConfig, "disabledModels" | "comb
|
|
|
403
427
|
.filter(slug => !shadowed.has(slug)).map(slug => {
|
|
404
428
|
const contextWindow = nativeOpenAiContextWindow(slug, limits);
|
|
405
429
|
const maxInputTokens = nativeOpenAiMaxInputTokens(slug, limits);
|
|
430
|
+
const autoCompactTokenLimit = nativeOpenAiAutoCompactTokenLimit(slug, limits);
|
|
406
431
|
return {
|
|
407
432
|
slug,
|
|
408
433
|
disabled: disabled.has(slug),
|
|
409
434
|
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
410
435
|
...(maxInputTokens !== undefined ? { maxInputTokens } : {}),
|
|
436
|
+
...(autoCompactTokenLimit !== undefined ? { autoCompactTokenLimit } : {}),
|
|
411
437
|
};
|
|
412
438
|
});
|
|
413
439
|
}
|
|
@@ -3,6 +3,9 @@ export const NATIVE_DAYBREAK_BLUE_MODEL = "gpt-daybreak-blue-latest";
|
|
|
3
3
|
|
|
4
4
|
/** Native ChatGPT/Codex ids whose availability is proven per authenticated account. */
|
|
5
5
|
export const ACCOUNT_GATED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
|
|
6
|
+
"gpt-5.6-sol",
|
|
7
|
+
"gpt-5.6-terra",
|
|
8
|
+
"gpt-5.6-luna",
|
|
6
9
|
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
7
10
|
]);
|
|
8
11
|
|
|
@@ -58,8 +61,8 @@ export function nativeOpenAiCapabilitySourceSlug(slug: string): string {
|
|
|
58
61
|
* discover it on a clean install.
|
|
59
62
|
*
|
|
60
63
|
* Availability is not static: catalog sync and Pool routing require the account's authenticated
|
|
61
|
-
* `/models` roster to contain
|
|
62
|
-
* request. `disabledModels` remains the independent user visibility control.
|
|
64
|
+
* `/models` roster to contain account-gated slugs. An unconfirmed or unentitled account never
|
|
65
|
+
* receives the request. `disabledModels` remains the independent user visibility control.
|
|
63
66
|
*
|
|
64
67
|
* Devlog: 260816_codexrs_multiagent_v2_and_history_perf/011 §4-bis.
|
|
65
68
|
*/
|
|
@@ -31,7 +31,8 @@ import { redactSecretString } from "../../lib/redact";
|
|
|
31
31
|
import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
import { NATIVE_OPENAI_CONTEXT_OVERRIDES, SUPPORTED_NATIVE_OPENAI_SLUGS, UPSTREAM_NATIVE_ENTRIES, isNativeOpenAiCapabilityAliasModel, nativeMultiAgentVersion, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "./metadata";
|
|
34
|
+
import { NATIVE_OPENAI_CONTEXT_OVERRIDES, SUPPORTED_NATIVE_OPENAI_SLUGS, UPSTREAM_NATIVE_ENTRIES, isNativeOpenAiCapabilityAliasModel, nativeMultiAgentVersion, nativeOpenAiAutoCompactTokenLimit, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, type NativeContextLimitsInput } from "./metadata";
|
|
35
|
+
import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
|
|
35
36
|
import { trustedAccountBoundNativeCatalogSlug } from "./account-models";
|
|
36
37
|
import { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
|
|
37
38
|
|
|
@@ -111,6 +112,8 @@ export interface CatalogModel {
|
|
|
111
112
|
defaultReasoningEffort?: string;
|
|
112
113
|
contextWindow?: number;
|
|
113
114
|
maxInputTokens?: number;
|
|
115
|
+
/** Soft client compaction threshold; hard context/input limits remain authoritative. */
|
|
116
|
+
autoCompactTokenLimit?: number;
|
|
114
117
|
contextCap?: number;
|
|
115
118
|
contextCapped?: boolean;
|
|
116
119
|
inputModalities?: string[];
|
|
@@ -149,6 +152,9 @@ export const JAWCODE_CATALOG_AUGMENT_PROVIDERS = new Set(["opencode-go", "deepse
|
|
|
149
152
|
export const ROUTED_MODEL_COMPATIBILITY_EXCLUSIONS = new Set([
|
|
150
153
|
// Issue #82: Zen Go /models advertises HY3, but Console Go rejects it as outside the lite list.
|
|
151
154
|
"opencode-go/hy3-preview",
|
|
155
|
+
// Issue #2330: OpenCode Go models absent from current documentation or returning terminal HTTP 400 errors.
|
|
156
|
+
"opencode-go/mimo-v2-omni",
|
|
157
|
+
"opencode-go/mimo-v2-pro",
|
|
152
158
|
]);
|
|
153
159
|
|
|
154
160
|
export function isRoutedModelCompatibilityExcluded(slug: string): boolean {
|
|
@@ -191,6 +197,8 @@ export function shouldExposeRoutedModel(model: CatalogModel): boolean {
|
|
|
191
197
|
}
|
|
192
198
|
|
|
193
199
|
export function readCodexCatalogPath(): string {
|
|
200
|
+
const home = activeCodexHome();
|
|
201
|
+
if (home) return readCodexCatalogPathForHome(home);
|
|
194
202
|
try {
|
|
195
203
|
const configPath = activeCodexConfigPath();
|
|
196
204
|
if (existsSync(configPath)) {
|
|
@@ -202,6 +210,35 @@ export function readCodexCatalogPath(): string {
|
|
|
202
210
|
return activeDefaultCatalogPath();
|
|
203
211
|
}
|
|
204
212
|
|
|
213
|
+
/** Resolve the configured catalog without consulting ambient CODEX_HOME again. */
|
|
214
|
+
export function readCodexCatalogPathForHome(codexHome: string): string {
|
|
215
|
+
try {
|
|
216
|
+
const configPath = join(codexHome, "config.toml");
|
|
217
|
+
if (existsSync(configPath)) {
|
|
218
|
+
const toml = readFileSync(configPath, "utf-8");
|
|
219
|
+
const path = readRootTomlString(toml, "model_catalog_json");
|
|
220
|
+
if (path) return resolve(codexHome, path);
|
|
221
|
+
}
|
|
222
|
+
} catch { /* ignore */ }
|
|
223
|
+
return join(codexHome, "opencodex-catalog.json");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Read the configured auto-review model from the root of Codex's config.toml (issue #1225).
|
|
228
|
+
* Stamped onto catalog entries as `auto_review_model_override` during sync so the auto-review
|
|
229
|
+
* subagent uses the operator's chosen model across catalog regenerations.
|
|
230
|
+
*/
|
|
231
|
+
export function readConfiguredAutoReviewModel(): string | null {
|
|
232
|
+
try {
|
|
233
|
+
const configPath = activeCodexConfigPath();
|
|
234
|
+
if (existsSync(configPath)) {
|
|
235
|
+
const toml = readFileSync(configPath, "utf-8");
|
|
236
|
+
return readRootTomlString(toml, "auto_review_model");
|
|
237
|
+
}
|
|
238
|
+
} catch { /* ignore */ }
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
205
242
|
export function parseCatalogJson(raw: string): RawCatalog | null {
|
|
206
243
|
try {
|
|
207
244
|
const cat = JSON.parse(raw);
|
|
@@ -274,22 +311,6 @@ export function isNativeOpenAiEntry(entry: RawEntry): boolean {
|
|
|
274
311
|
return typeof entry.slug === "string" && !entry.slug.includes("/");
|
|
275
312
|
}
|
|
276
313
|
|
|
277
|
-
/**
|
|
278
|
-
* Auto-compaction threshold for a native row.
|
|
279
|
-
*
|
|
280
|
-
* The usual rule is 90% of the window, but a row whose input ceiling sits below that has to
|
|
281
|
-
* clamp to the ceiling instead — otherwise the client keeps filling until upstream answers
|
|
282
|
-
* `context_length_exceeded` and compaction never gets a chance to run. Native GPT-5.6 no
|
|
283
|
-
* longer trips this (922,000 window, 829,800 at 90%), but the routed and API-key rows carry
|
|
284
|
-
* the same family at a 1,050,000 window where 90% would be 945,000 — past the ceiling.
|
|
285
|
-
*/
|
|
286
|
-
function nativeAutoCompactLimit(contextWindow: number, maxInputTokens: number | undefined, contextCap?: number): number {
|
|
287
|
-
const ninety = Math.floor(contextWindow * 0.9);
|
|
288
|
-
if (typeof maxInputTokens !== "number" || maxInputTokens <= 0) return ninety;
|
|
289
|
-
const cappedMaxInput = applyProviderContextCap(maxInputTokens, contextCap) ?? maxInputTokens;
|
|
290
|
-
return Math.min(ninety, cappedMaxInput, contextWindow);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
314
|
/**
|
|
294
315
|
* Narrow any already-resolved native window by the user levers.
|
|
295
316
|
*
|
|
@@ -315,6 +336,9 @@ export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: Nativ
|
|
|
315
336
|
?? (isNativeOpenAiEntry(entry) ? entry.slug as string : undefined);
|
|
316
337
|
if (!nativeSlug) return;
|
|
317
338
|
const override = NATIVE_OPENAI_CONTEXT_OVERRIDES[nativeSlug];
|
|
339
|
+
// Captured before any override/cap rewrites the row: a retained compaction threshold only
|
|
340
|
+
// describes the window it arrived with.
|
|
341
|
+
const incomingContextWindow = typeof entry.context_window === "number" ? entry.context_window : undefined;
|
|
318
342
|
if (override) {
|
|
319
343
|
// Read the effective values through the accessors rather than re-deriving them from the
|
|
320
344
|
// static table: this function used to apply only the provider cap, so a per-model window
|
|
@@ -322,11 +346,6 @@ export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: Nativ
|
|
|
322
346
|
if (typeof override.contextWindow === "number") {
|
|
323
347
|
const contextWindow = nativeOpenAiContextWindow(nativeSlug, limits) ?? override.contextWindow;
|
|
324
348
|
entry.context_window = contextWindow;
|
|
325
|
-
entry.auto_compact_token_limit = nativeAutoCompactLimit(
|
|
326
|
-
contextWindow,
|
|
327
|
-
nativeOpenAiMaxInputTokens(nativeSlug, limits) ?? override.maxInputTokens,
|
|
328
|
-
undefined,
|
|
329
|
-
);
|
|
330
349
|
}
|
|
331
350
|
if (typeof override.maxContextWindow === "number") {
|
|
332
351
|
const maxContextWindow = narrowNativeMaxContextWindow(nativeSlug, override.maxContextWindow, limits);
|
|
@@ -341,17 +360,43 @@ export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: Nativ
|
|
|
341
360
|
const cappedContext = narrowNativeMaxContextWindow(nativeSlug, currentContext, limits);
|
|
342
361
|
if (cappedContext !== currentContext && typeof cappedContext === "number") {
|
|
343
362
|
entry.context_window = cappedContext;
|
|
344
|
-
entry.auto_compact_token_limit = nativeAutoCompactLimit(
|
|
345
|
-
cappedContext,
|
|
346
|
-
nativeOpenAiMaxInputTokens(nativeSlug, limits) ?? override?.maxInputTokens,
|
|
347
|
-
undefined,
|
|
348
|
-
);
|
|
349
363
|
}
|
|
350
364
|
const currentMax = typeof entry.max_context_window === "number" ? entry.max_context_window : undefined;
|
|
351
365
|
const cappedMax = narrowNativeMaxContextWindow(nativeSlug, currentMax, limits);
|
|
352
366
|
if (cappedMax !== currentMax) {
|
|
353
367
|
entry.max_context_window = cappedMax;
|
|
354
368
|
}
|
|
369
|
+
const effectiveContext = typeof entry.context_window === "number" && entry.context_window > 0
|
|
370
|
+
? entry.context_window
|
|
371
|
+
: undefined;
|
|
372
|
+
if (effectiveContext !== undefined) {
|
|
373
|
+
const derivedAutoCompactTokenLimit = nativeOpenAiAutoCompactTokenLimit(nativeSlug, limits);
|
|
374
|
+
// Only trust a retained threshold that still describes THIS window. When sync corrects the
|
|
375
|
+
// window, the old number is an artifact of the old one: a 115_200 limit retained from a
|
|
376
|
+
// 128k row would pin a corrected 272k model to 42% of its real window and compact every
|
|
377
|
+
// long turn early. Lower-is-policy still holds whenever the window is unchanged.
|
|
378
|
+
const retainedDescribesCurrentContext = incomingContextWindow === undefined
|
|
379
|
+
|| incomingContextWindow === effectiveContext;
|
|
380
|
+
const retainedAutoCompactTokenLimit = retainedDescribesCurrentContext
|
|
381
|
+
&& isNativeOpenAiEntry(entry)
|
|
382
|
+
&& typeof entry.auto_compact_token_limit === "number"
|
|
383
|
+
&& Number.isSafeInteger(entry.auto_compact_token_limit)
|
|
384
|
+
&& entry.auto_compact_token_limit > 0
|
|
385
|
+
? entry.auto_compact_token_limit
|
|
386
|
+
: undefined;
|
|
387
|
+
// A smaller threshold retained from Codex is policy evidence too. Configuration may
|
|
388
|
+
// lower it further, but catalog sync must never replace it with a larger default.
|
|
389
|
+
const loweringAutoCompactTokenLimit = retainedAutoCompactTokenLimit === undefined
|
|
390
|
+
? derivedAutoCompactTokenLimit
|
|
391
|
+
: derivedAutoCompactTokenLimit === undefined
|
|
392
|
+
? retainedAutoCompactTokenLimit
|
|
393
|
+
: Math.min(retainedAutoCompactTokenLimit, derivedAutoCompactTokenLimit);
|
|
394
|
+
entry.auto_compact_token_limit = clampAutoCompactTokenLimit(
|
|
395
|
+
effectiveContext,
|
|
396
|
+
nativeOpenAiMaxInputTokens(nativeSlug, limits) ?? override?.maxInputTokens,
|
|
397
|
+
loweringAutoCompactTokenLimit,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
355
400
|
}
|
|
356
401
|
|
|
357
402
|
export function ensureStrictCatalogFields(
|