@bitkyc08/opencodex 2.8.0 → 2.9.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 +24 -0
- package/bin/ocx.mjs +32 -4
- package/gui/dist/assets/index-CHwf3tTD.css +1 -0
- package/gui/dist/assets/index-u5eFOv2y.js +67 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-normalize.ts +114 -20
- package/src/adapters/anthropic.ts +126 -10
- package/src/adapters/azure.ts +3 -3
- package/src/adapters/base.ts +7 -3
- package/src/adapters/cursor/discovery.ts +14 -4
- package/src/adapters/cursor/effort-map.ts +18 -6
- package/src/adapters/cursor/framing.ts +102 -27
- package/src/adapters/cursor/kv-store.ts +30 -3
- package/src/adapters/cursor/live-models.ts +22 -2
- package/src/adapters/cursor/live-transport.ts +245 -49
- package/src/adapters/cursor/mcp-manager.ts +105 -8
- package/src/adapters/cursor/native-exec-mcp.ts +5 -3
- package/src/adapters/cursor/native-exec-shell.ts +296 -14
- package/src/adapters/cursor/native-exec.ts +381 -33
- package/src/adapters/cursor/protobuf-events.ts +28 -1
- package/src/adapters/cursor/protobuf-request.ts +71 -39
- package/src/adapters/cursor/request-builder.ts +2 -2
- package/src/adapters/cursor/transport.ts +2 -0
- package/src/adapters/cursor.ts +13 -2
- package/src/adapters/google-antigravity-replay.ts +184 -17
- package/src/adapters/google.ts +58 -8
- package/src/adapters/kiro-thinking.ts +23 -9
- package/src/adapters/kiro-tools.ts +49 -18
- package/src/adapters/kiro.ts +377 -133
- package/src/adapters/mimo-free.ts +36 -4
- package/src/adapters/openai-chat.ts +143 -17
- package/src/adapters/openai-responses.ts +130 -14
- package/src/adapters/run-turn-queue.ts +7 -1
- package/src/bridge.ts +466 -69
- package/src/chat/outbound.ts +144 -38
- package/src/claude/inbound-debug.ts +53 -8
- package/src/claude/outbound.ts +224 -38
- package/src/cli/agent-driven.ts +34 -1
- package/src/cli/catalog-prewarm.ts +5 -2
- package/src/cli/claude-desktop.ts +2 -2
- package/src/cli/doctor.ts +12 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +11 -0
- package/src/cli/index.ts +13 -3
- package/src/cli/init.ts +129 -102
- package/src/cli/opencode.ts +36 -151
- package/src/cli/star-prompt.ts +13 -4
- package/src/cli/status-oauth.ts +12 -2
- package/src/clients/config-export.ts +377 -0
- package/src/codex/account-runtime-state.ts +19 -1
- package/src/codex/account-store.ts +162 -82
- package/src/codex/auth-api.ts +467 -159
- package/src/codex/auth-context.ts +15 -2
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/effort.ts +16 -6
- package/src/codex/catalog/metadata.ts +6 -0
- package/src/codex/catalog/parsing.ts +3 -1
- package/src/codex/catalog/provider-fetch.ts +29 -0
- package/src/codex/catalog/sync.ts +64 -7
- package/src/codex/catalog.ts +2 -2
- package/src/codex/inject.ts +5 -5
- package/src/codex/main-account-cache.ts +8 -1
- package/src/codex/model-cache.ts +81 -2
- package/src/codex/pool-rotation.ts +39 -0
- package/src/codex/project-config-warnings.ts +12 -1
- package/src/codex/quota.ts +35 -3
- package/src/codex/routing.ts +46 -1
- package/src/codex/shim.ts +10 -4
- package/src/codex/subagent-model-fallback.ts +12 -0
- package/src/codex/websocket-registry.ts +27 -0
- package/src/combos/failover.ts +31 -1
- package/src/combos/request.ts +9 -0
- package/src/combos/resolve.ts +60 -4
- package/src/combos/types.ts +12 -0
- package/src/config.ts +510 -55
- package/src/github/star-state.ts +13 -1
- package/src/images/fulfill.ts +39 -1
- package/src/images/loop.ts +52 -12
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bun-stream-caps.ts +31 -7
- package/src/lib/config-ownership.ts +33 -0
- package/src/lib/crash-guard.ts +65 -5
- package/src/lib/debug-log-buffer.ts +47 -6
- package/src/lib/destination-policy.ts +12 -1
- package/src/lib/errors.ts +3 -0
- package/src/lib/gcp-adc.ts +40 -2
- package/src/lib/injection-debug-log.ts +26 -2
- package/src/lib/provider-outbound.ts +3 -0
- package/src/lib/sidecar-tracker.ts +5 -2
- package/src/lib/sse-decoder.ts +257 -37
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/windows-secret-acl.ts +33 -12
- package/src/lib/winsw.ts +14 -1
- package/src/oauth/anthropic-routing.ts +31 -7
- package/src/oauth/google-antigravity.ts +2 -1
- package/src/oauth/health.ts +30 -12
- package/src/oauth/index.ts +127 -23
- package/src/oauth/kiro-credentials.ts +72 -1
- package/src/oauth/kiro.ts +23 -4
- package/src/oauth/store.ts +165 -18
- package/src/oauth/token-guardian.ts +43 -4
- package/src/oauth/types.ts +2 -1
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/derive.ts +12 -0
- package/src/providers/free-directory.ts +4 -1
- package/src/providers/key-failover.ts +12 -0
- package/src/providers/openai-sidecar.ts +4 -1
- package/src/providers/quota.ts +68 -7
- package/src/providers/registry.ts +279 -3
- package/src/responses/parser.ts +5 -1
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +520 -102
- package/src/router.ts +18 -1
- package/src/server/adapter-resolve.ts +20 -3
- package/src/server/auth-cors.ts +121 -28
- package/src/server/chat-completions.ts +57 -12
- package/src/server/claude-messages.ts +85 -13
- package/src/server/index.ts +242 -100
- package/src/server/lifecycle.ts +155 -25
- package/src/server/management/agent-settings-routes.ts +79 -36
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +5 -1
- package/src/server/management/config-routes.ts +42 -12
- package/src/server/management/logs-usage-routes.ts +41 -21
- package/src/server/management/model-routes.ts +188 -54
- package/src/server/management/oauth-account-routes.ts +115 -26
- package/src/server/management/provider-routes.ts +56 -6
- package/src/server/management/shared.ts +16 -3
- package/src/server/management/sidebar-routes.ts +50 -1
- package/src/server/management/system-restart.ts +13 -6
- package/src/server/management/system-routes.ts +15 -3
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +39 -5
- package/src/server/management-auth.ts +65 -14
- package/src/server/port-reclaim.ts +58 -12
- package/src/server/ports.ts +2 -0
- package/src/server/proxy-liveness.ts +60 -14
- package/src/server/relay-eager.ts +20 -4
- package/src/server/relay.ts +548 -154
- package/src/server/request-decompress.ts +51 -4
- package/src/server/request-log.ts +134 -15
- package/src/server/responses/collaboration.ts +15 -4
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +241 -66
- package/src/server/responses-image-gen-repair.ts +19 -5
- package/src/server/responses-item-id-repair.ts +23 -5
- package/src/server/sse-payload-rewrite.ts +71 -12
- package/src/server/startup-health-cache.ts +14 -1
- package/src/server/system-env.ts +8 -1
- package/src/server/windows-tcp-drop.ts +15 -5
- package/src/server/ws-bridge.ts +25 -0
- package/src/service.ts +179 -13
- package/src/storage/policy-job.ts +93 -23
- package/src/storage/policy-worker.ts +6 -0
- package/src/storage/restore-job.ts +62 -16
- package/src/storage/restore-worker.ts +6 -0
- package/src/storage/storage-mutation-coordinator.ts +36 -6
- package/src/storage/worker-lifecycle.ts +181 -47
- package/src/tray/windows.ts +97 -25
- package/src/types.ts +39 -6
- package/src/update/index.ts +24 -5
- package/src/update/job.ts +598 -73
- package/src/usage/log.ts +115 -17
- package/src/usage/summary.ts +67 -2
- package/src/vision/index.ts +112 -22
- package/src/web-search/loop.ts +38 -6
- package/src/web-search/progress-stream.ts +14 -3
- package/gui/dist/assets/index-BDjpkcRN.js +0 -67
- package/gui/dist/assets/index-BHsKRFh9.css +0 -1
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CodexCredentialGenerationConflictError,
|
|
3
3
|
CodexCredentialRefreshLockTimeoutError,
|
|
4
|
+
CodexCredentialRefreshBusyError,
|
|
5
|
+
CodexCredentialRefreshStaleError,
|
|
4
6
|
getValidCodexToken,
|
|
5
7
|
isCodexAccountGenerationLive,
|
|
6
8
|
} from "./account-store";
|
|
9
|
+
import { ConfigMutationLockError } from "../config";
|
|
7
10
|
import { markAccountNeedsReauth } from "./account-runtime-state";
|
|
8
11
|
import { isCodexAccountUsable } from "./account-usability";
|
|
9
12
|
import { reconcileMainCodexAccountRuntimeState } from "./account-lifecycle";
|
|
@@ -24,12 +27,14 @@ import { formatErrorResponse } from "../bridge";
|
|
|
24
27
|
import { getAccountQuota } from "./quota";
|
|
25
28
|
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
|
|
26
29
|
import { FORWARD_HEADERS } from "../adapters/openai-responses";
|
|
30
|
+
import { captureConfigGeneration } from "../lib/state-store-sweeper";
|
|
27
31
|
|
|
28
32
|
export type CodexAuthContext =
|
|
29
33
|
| { kind: "main"; accountId: null }
|
|
30
34
|
| {
|
|
31
35
|
kind: "pool";
|
|
32
36
|
accountId: string;
|
|
37
|
+
writerGeneration: number;
|
|
33
38
|
generation: number;
|
|
34
39
|
accessToken: string;
|
|
35
40
|
chatgptAccountId: string;
|
|
@@ -49,6 +54,7 @@ export type CodexAuthContext =
|
|
|
49
54
|
// (Option A). Distinct from "main" (passthrough fallback that forwards the client token).
|
|
50
55
|
kind: "main-pool";
|
|
51
56
|
accountId: string;
|
|
57
|
+
writerGeneration: number;
|
|
52
58
|
accessToken: string;
|
|
53
59
|
chatgptAccountId: string;
|
|
54
60
|
/** See `pool.probeLeaseId`. */
|
|
@@ -180,7 +186,11 @@ export class CodexThreadAffinityExpiredError extends Error {
|
|
|
180
186
|
}
|
|
181
187
|
|
|
182
188
|
export function shouldMarkAccountNeedsReauthForCodexAuthFailure(cause: unknown): boolean {
|
|
183
|
-
return !(cause instanceof CodexCredentialGenerationConflictError)
|
|
189
|
+
return !(cause instanceof CodexCredentialGenerationConflictError)
|
|
190
|
+
&& !(cause instanceof CodexCredentialRefreshLockTimeoutError)
|
|
191
|
+
&& !(cause instanceof CodexCredentialRefreshBusyError)
|
|
192
|
+
&& !(cause instanceof CodexCredentialRefreshStaleError)
|
|
193
|
+
&& !(cause instanceof ConfigMutationLockError);
|
|
184
194
|
}
|
|
185
195
|
|
|
186
196
|
export interface ResolveCodexAuthContextOptions {
|
|
@@ -195,6 +205,7 @@ export async function resolveCodexAuthContext(
|
|
|
195
205
|
mode: CodexAccountMode,
|
|
196
206
|
options: ResolveCodexAuthContextOptions = {},
|
|
197
207
|
): Promise<CodexAuthContext> {
|
|
208
|
+
const writerGeneration = captureConfigGeneration();
|
|
198
209
|
if (mode === "direct") {
|
|
199
210
|
if (!hasCallerCodexBearer(headers)) throw new CodexDirectAuthenticationError();
|
|
200
211
|
return { kind: "main", accountId: null };
|
|
@@ -254,6 +265,7 @@ export async function resolveCodexAuthContext(
|
|
|
254
265
|
return {
|
|
255
266
|
kind: "main-pool",
|
|
256
267
|
accountId,
|
|
268
|
+
writerGeneration,
|
|
257
269
|
accessToken: token.accessToken,
|
|
258
270
|
chatgptAccountId: token.chatgptAccountId,
|
|
259
271
|
...(quotaScope ? { quotaScope } : {}),
|
|
@@ -267,6 +279,7 @@ export async function resolveCodexAuthContext(
|
|
|
267
279
|
return {
|
|
268
280
|
kind: "pool",
|
|
269
281
|
accountId,
|
|
282
|
+
writerGeneration,
|
|
270
283
|
generation: token.generation,
|
|
271
284
|
accessToken: token.accessToken,
|
|
272
285
|
chatgptAccountId: token.chatgptAccountId,
|
|
@@ -278,7 +291,7 @@ export async function resolveCodexAuthContext(
|
|
|
278
291
|
if (probeLeaseId && probeQuotaScope) releaseCodexQuotaScopeProbeLease(accountId, probeQuotaScope, probeLeaseId);
|
|
279
292
|
else if (probeLeaseId) releaseCodexQuotaProbeLease(accountId, probeLeaseId);
|
|
280
293
|
if (shouldMarkAccountNeedsReauthForCodexAuthFailure(cause)) {
|
|
281
|
-
markAccountNeedsReauth(accountId);
|
|
294
|
+
markAccountNeedsReauth(accountId, writerGeneration);
|
|
282
295
|
}
|
|
283
296
|
throw new CodexAuthContextError(accountId, cause);
|
|
284
297
|
}
|
|
@@ -281,6 +281,21 @@ export function resetOpenAiApiCatalogWarningStateForTests(): void {
|
|
|
281
281
|
export const slugAliasCollisionWarnings = new Set<string>();
|
|
282
282
|
|
|
283
283
|
export const comboMasqueradeCollisionWarnings = new Set<string>();
|
|
284
|
+
let lastWarningReconciledGeneration = 0;
|
|
285
|
+
|
|
286
|
+
export function reconcileCatalogWarningMemos(generation: number): number {
|
|
287
|
+
if (generation <= lastWarningReconciledGeneration) return 0;
|
|
288
|
+
const removed = openAiApiCollisionWarnings.size
|
|
289
|
+
+ comboCatalogWarningSignatures.size
|
|
290
|
+
+ slugAliasCollisionWarnings.size
|
|
291
|
+
+ comboMasqueradeCollisionWarnings.size;
|
|
292
|
+
openAiApiCollisionWarnings.clear();
|
|
293
|
+
comboCatalogWarningSignatures.clear();
|
|
294
|
+
slugAliasCollisionWarnings.clear();
|
|
295
|
+
comboMasqueradeCollisionWarnings.clear();
|
|
296
|
+
lastWarningReconciledGeneration = generation;
|
|
297
|
+
return removed;
|
|
298
|
+
}
|
|
284
299
|
|
|
285
300
|
export function warnComboMasqueradeCollisionOnce(slug: string): void {
|
|
286
301
|
if (comboMasqueradeCollisionWarnings.has(slug)) return;
|
|
@@ -109,19 +109,29 @@ export function catalogEntryEfforts(entry: RawEntry): string[] {
|
|
|
109
109
|
|
|
110
110
|
export const ROUTED_REASONING_LEVELS = [...CODEX_REASONING_LEVELS];
|
|
111
111
|
|
|
112
|
-
export function applyCatalogModelMetadata(
|
|
112
|
+
export function applyCatalogModelMetadata(
|
|
113
|
+
entry: RawEntry,
|
|
114
|
+
model?: CatalogModel,
|
|
115
|
+
fallbackDisplayName?: string,
|
|
116
|
+
): void {
|
|
113
117
|
if (!model) return;
|
|
114
118
|
// This marker survives strict catalog normalization and lets sync distinguish a stale
|
|
115
119
|
// bare combo alias from a genuine native model row.
|
|
116
120
|
if (model.provider === COMBO_NAMESPACE) entry.owned_by = model.owned_by ?? COMBO_NAMESPACE;
|
|
117
121
|
// displayName is DISPLAY-ONLY: it relabels the picker row but never touches the routing
|
|
118
|
-
// slug, alias, or provider.
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
122
|
+
// slug, alias, or provider. A configured displayName wins. Otherwise physical routed rows
|
|
123
|
+
// use the final segment of their native id, keeping provider/vendor namespaces out of the
|
|
124
|
+
// narrow picker label. buildCatalogEntries supplies a longer fallback only when that basename
|
|
125
|
+
// would collide with another visible row. Aliased combos keep their public alias. Natives never
|
|
126
|
+
// reach here (no CatalogModel), so genuine upstream marketing names are preserved untouched.
|
|
123
127
|
const displayName = typeof model.displayName === "string" ? model.displayName.trim() : "";
|
|
124
128
|
if (displayName) entry.display_name = displayName;
|
|
129
|
+
else if (!model.alias) {
|
|
130
|
+
const nativeId = model.id.trim();
|
|
131
|
+
const finalSegment = nativeId.slice(nativeId.lastIndexOf("/") + 1).trim();
|
|
132
|
+
const fallback = fallbackDisplayName?.trim() || finalSegment;
|
|
133
|
+
if (fallback) entry.display_name = fallback;
|
|
134
|
+
}
|
|
125
135
|
if (typeof model.contextWindow === "number" && model.contextWindow > 0) {
|
|
126
136
|
entry.context_window = model.contextWindow;
|
|
127
137
|
entry.max_context_window = model.contextWindow;
|
|
@@ -123,6 +123,12 @@ export function visibleNativeSlugs(config: Pick<OcxConfig, "disabledModels">): s
|
|
|
123
123
|
return nativeOpenAiSlugs().filter(slug => !disabled.has(slug));
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/** Native slugs exposed to Claude Desktop show/export/apply (opt-out via claudeCode.desktopNativeModels). */
|
|
127
|
+
export function desktopVisibleNativeSlugs(config: Pick<OcxConfig, "claudeCode" | "disabledModels">): string[] {
|
|
128
|
+
if (config.claudeCode?.desktopNativeModels === false) return [];
|
|
129
|
+
return visibleNativeSlugs(config);
|
|
130
|
+
}
|
|
131
|
+
|
|
126
132
|
export function nativeModelRows(config: Pick<OcxConfig, "disabledModels">): Array<{ slug: string; disabled: boolean; contextWindow?: number }> {
|
|
127
133
|
const disabled = disabledNativeSlugs(config);
|
|
128
134
|
return NATIVE_OPENAI_MODELS.map(slug => {
|
|
@@ -92,7 +92,9 @@ export interface CatalogModel {
|
|
|
92
92
|
/**
|
|
93
93
|
* Display-only Codex catalog `display_name` override. Relabels the picker row ONLY — it never
|
|
94
94
|
* affects the routing slug, alias-collision order, native marketing-name precedence, or provider
|
|
95
|
-
* behavior. When unset, the
|
|
95
|
+
* behavior. When unset, routed entries use the final segment of the native model id so the
|
|
96
|
+
* provider namespace does not crowd the picker; basename collisions retain enough route context
|
|
97
|
+
* to stay distinguishable, and aliased combo rows keep their public alias.
|
|
96
98
|
* Native upstream entries (e.g. gpt-5.6-sol → "GPT-5.6-Sol") come from the pinned snapshot path
|
|
97
99
|
* which carries no CatalogModel, so a configured displayName can never override a native name.
|
|
98
100
|
*/
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
type ProviderModelsApiItem,
|
|
54
54
|
} from "../../providers/model-discovery";
|
|
55
55
|
import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
56
|
+
import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } from "../../lib/admission";
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
import { JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
@@ -69,6 +70,21 @@ interface GatherFlightResult {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
const gatherInflight = new Map<string, Promise<GatherFlightResult>>();
|
|
73
|
+
const MAX_CONCURRENT_CATALOG_GATHERS = 8;
|
|
74
|
+
const gatherGate = createAdmissionGate("catalog_gathers", MAX_CONCURRENT_CATALOG_GATHERS);
|
|
75
|
+
|
|
76
|
+
export class CatalogGatherBusyError extends ResourceAdmissionError {
|
|
77
|
+
override readonly code = "catalog_busy";
|
|
78
|
+
readonly retryAfterSeconds = 1;
|
|
79
|
+
constructor() {
|
|
80
|
+
super("catalog_gathers", MAX_CONCURRENT_CATALOG_GATHERS);
|
|
81
|
+
this.name = "CatalogGatherBusyError";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function catalogGatherAdmissionMetrics(): AdmissionMetrics {
|
|
86
|
+
return gatherGate.metrics();
|
|
87
|
+
}
|
|
72
88
|
|
|
73
89
|
function stableJson(value: unknown): string {
|
|
74
90
|
return JSON.stringify(value, (_key, nested) => {
|
|
@@ -217,6 +233,15 @@ export function isDatedVariantId(liveId: string, configuredId: string): boolean
|
|
|
217
233
|
}
|
|
218
234
|
|
|
219
235
|
export const lastDropWarnSignature = new Map<string, string>();
|
|
236
|
+
let lastWarningReconciledGeneration = 0;
|
|
237
|
+
|
|
238
|
+
export function reconcileProviderFetchWarnings(generation: number): number {
|
|
239
|
+
if (generation <= lastWarningReconciledGeneration) return 0;
|
|
240
|
+
const removed = lastDropWarnSignature.size;
|
|
241
|
+
lastDropWarnSignature.clear();
|
|
242
|
+
lastWarningReconciledGeneration = generation;
|
|
243
|
+
return removed;
|
|
244
|
+
}
|
|
220
245
|
|
|
221
246
|
export const QUIET_AUTHORITATIVE_CATALOG_PROVIDERS = new Set(["kimi", "xai"]);
|
|
222
247
|
|
|
@@ -345,6 +370,7 @@ export function catalogHintsFromModelsApiItem(providerName: string, item: Provid
|
|
|
345
370
|
const contextWindow =
|
|
346
371
|
positiveSafeInteger(
|
|
347
372
|
limits?.max_context_length,
|
|
373
|
+
metadata?.context_length,
|
|
348
374
|
item.context_length,
|
|
349
375
|
item.context_size,
|
|
350
376
|
item.max_model_len,
|
|
@@ -648,10 +674,13 @@ export async function gatherRoutedModels(
|
|
|
648
674
|
const key = gatherFlightKey(config);
|
|
649
675
|
let promise = gatherInflight.get(key);
|
|
650
676
|
if (!promise) {
|
|
677
|
+
const lease = gatherGate.tryAcquire();
|
|
678
|
+
if (!lease) throw new CatalogGatherBusyError();
|
|
651
679
|
// Claim the slot synchronously before any await so same-key callers join this flight.
|
|
652
680
|
// Distinct keys keep their own entries — a second config must not evict the first.
|
|
653
681
|
const flight = gatherRoutedModelsUncached(config).finally(() => {
|
|
654
682
|
if (gatherInflight.get(key) === flight) gatherInflight.delete(key);
|
|
683
|
+
lease.release();
|
|
655
684
|
});
|
|
656
685
|
gatherInflight.set(key, flight);
|
|
657
686
|
promise = flight;
|
|
@@ -166,6 +166,7 @@ export function deriveEntry(
|
|
|
166
166
|
priority: number,
|
|
167
167
|
model?: CatalogModel,
|
|
168
168
|
exactComboSlugs: ReadonlySet<string> = new Set(),
|
|
169
|
+
fallbackDisplayName?: string,
|
|
169
170
|
): RawEntry {
|
|
170
171
|
const preserveExact = isExactComboCatalogModel(model, exactComboSlugs);
|
|
171
172
|
const isRouted = model !== undefined;
|
|
@@ -202,7 +203,7 @@ export function deriveEntry(
|
|
|
202
203
|
applyReasoningLevels(e, model?.reasoningEfforts, model?.defaultReasoningEffort, preserveExact);
|
|
203
204
|
normalizeRoutedCatalogEntry(e, model?.parallelToolCalls === true);
|
|
204
205
|
if (model) applyJawcodeCatalogMetadata(e, model.provider, model.id, model.contextCap);
|
|
205
|
-
applyCatalogModelMetadata(e, model);
|
|
206
|
+
applyCatalogModelMetadata(e, model, fallbackDisplayName);
|
|
206
207
|
} else {
|
|
207
208
|
applyNativeOpenAiContextOverride(e);
|
|
208
209
|
if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(e);
|
|
@@ -239,7 +240,7 @@ export function deriveEntry(
|
|
|
239
240
|
if (isGpt56NativeSlug(slug)) ensureGpt56ReasoningLevels(entry);
|
|
240
241
|
}
|
|
241
242
|
if (model && isRouted) applyJawcodeCatalogMetadata(entry, model.provider, model.id, model.contextCap);
|
|
242
|
-
applyCatalogModelMetadata(entry, model);
|
|
243
|
+
applyCatalogModelMetadata(entry, model, fallbackDisplayName);
|
|
243
244
|
if (!isRouted) applyNativeOpenAiContextOverride(entry);
|
|
244
245
|
return ensureStrictCatalogFields(normalizeServiceTiers(entry), {
|
|
245
246
|
preserveExactInputModalities: preserveExact,
|
|
@@ -247,6 +248,50 @@ export function deriveEntry(
|
|
|
247
248
|
});
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
function configuredDisplayName(model: CatalogModel): string {
|
|
252
|
+
return typeof model.displayName === "string" ? model.displayName.trim() : "";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function defaultPickerLabel(model: CatalogModel): string {
|
|
256
|
+
const nativeId = model.id.trim();
|
|
257
|
+
return nativeId.slice(nativeId.lastIndexOf("/") + 1).trim();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Keep the common picker path compact, but retain only as much route context as needed when two
|
|
262
|
+
* emitted rows would otherwise have the same label. Explicit display names and combo aliases are
|
|
263
|
+
* author-owned and never rewritten. If distinct native ids share a basename, the native id is
|
|
264
|
+
* enough; if the same native id is exposed by multiple providers, append the provider as well.
|
|
265
|
+
*/
|
|
266
|
+
function routedFallbackDisplayNames(models: readonly CatalogModel[]): Map<CatalogModel, string> {
|
|
267
|
+
const intendedLabel = (model: CatalogModel): string =>
|
|
268
|
+
configuredDisplayName(model) || model.alias?.trim() || defaultPickerLabel(model);
|
|
269
|
+
const labelCounts = new Map<string, number>();
|
|
270
|
+
const nativeIdCounts = new Map<string, number>();
|
|
271
|
+
for (const model of models) {
|
|
272
|
+
const label = intendedLabel(model);
|
|
273
|
+
if (!label) continue;
|
|
274
|
+
labelCounts.set(label, (labelCounts.get(label) ?? 0) + 1);
|
|
275
|
+
const nativeKey = `${label}\u0000${model.id.trim()}`;
|
|
276
|
+
nativeIdCounts.set(nativeKey, (nativeIdCounts.get(nativeKey) ?? 0) + 1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const out = new Map<CatalogModel, string>();
|
|
280
|
+
for (const model of models) {
|
|
281
|
+
if (configuredDisplayName(model) || model.alias) continue;
|
|
282
|
+
const label = defaultPickerLabel(model);
|
|
283
|
+
if (!label) continue;
|
|
284
|
+
if ((labelCounts.get(label) ?? 0) <= 1) {
|
|
285
|
+
out.set(model, label);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const nativeId = model.id.trim();
|
|
289
|
+
const sameNativeIdCount = nativeIdCounts.get(`${label}\u0000${nativeId}`) ?? 0;
|
|
290
|
+
out.set(model, sameNativeIdCount > 1 ? `${nativeId} (${model.provider})` : nativeId);
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
|
|
250
295
|
export function buildCatalogEntries(
|
|
251
296
|
template: RawEntry | null,
|
|
252
297
|
gptSlugs: string[],
|
|
@@ -271,22 +316,28 @@ export function buildCatalogEntries(
|
|
|
271
316
|
if (rank.has(slug)) e.priority = rank.get(slug)!;
|
|
272
317
|
out.push(e);
|
|
273
318
|
}
|
|
274
|
-
|
|
275
|
-
if (collisionSkipped.has(m))
|
|
319
|
+
const routedModels = goModels.filter(m => {
|
|
320
|
+
if (collisionSkipped.has(m)) return false;
|
|
276
321
|
const slug = catalogModelSlug(m);
|
|
277
322
|
if (m.provider !== COMBO_NAMESPACE && comboPublicSlugs.has(slug)) {
|
|
278
323
|
warnComboMasqueradeCollisionOnce(slug);
|
|
279
|
-
|
|
324
|
+
return false;
|
|
280
325
|
}
|
|
326
|
+
return true;
|
|
327
|
+
});
|
|
328
|
+
const fallbackDisplayNames = routedFallbackDisplayNames(routedModels);
|
|
329
|
+
for (const m of routedModels) {
|
|
330
|
+
const slug = catalogModelSlug(m);
|
|
281
331
|
// Provider rows use the one-slash slug codec; combo aliases intentionally override that
|
|
282
332
|
// public slug and may be bare.
|
|
283
333
|
const e = deriveEntry(
|
|
284
334
|
template,
|
|
285
335
|
slug,
|
|
286
|
-
`Routed via opencodex → ${
|
|
336
|
+
`Routed via opencodex → ${slug} (${m.owned_by ?? m.provider}).`,
|
|
287
337
|
5,
|
|
288
338
|
m,
|
|
289
339
|
exactComboSlugs,
|
|
340
|
+
fallbackDisplayNames.get(m),
|
|
290
341
|
);
|
|
291
342
|
// Featured picks may be stored raw (legacy) or encoded — honor both.
|
|
292
343
|
const rankHit = rank.get(slug) ?? rank.get(`${m.provider}/${m.id}`);
|
|
@@ -492,6 +543,12 @@ export async function syncCatalogModels(config: OcxConfig): Promise<{
|
|
|
492
543
|
const catalog = loadCatalogForSync(catalogPath);
|
|
493
544
|
if (!catalog) return { added: 0, path: catalogPath, catalogWritten: false, comboOmissions: [] };
|
|
494
545
|
|
|
546
|
+
// The bundled catalog is a reliable native template on the default path, but it is not the
|
|
547
|
+
// merge source. Preservation must inspect the file that this sync is about to overwrite;
|
|
548
|
+
// otherwise an empty/partial provider gather cannot see routed or user-native rows on disk.
|
|
549
|
+
const onDiskCatalog = readCatalog(catalogPath);
|
|
550
|
+
const catalogModelsForMerge = onDiskCatalog?.models ?? catalog.models ?? [];
|
|
551
|
+
|
|
495
552
|
const template = findNativeTemplate(catalog);
|
|
496
553
|
|
|
497
554
|
const comboOmissions: ComboCatalogOmission[] = [];
|
|
@@ -534,7 +591,7 @@ export async function syncCatalogModels(config: OcxConfig): Promise<{
|
|
|
534
591
|
// bare gpt-* rows that hard-404 via NoEnabledOpenAiProviderError. Keep natives when no
|
|
535
592
|
// providers are configured yet (fresh install / catalog bootstrap tests).
|
|
536
593
|
const includeNativeOpenAi = enabledProviders.length === 0 || hasCanonicalOpenai;
|
|
537
|
-
catalog.models = mergeCatalogEntriesForSync(
|
|
594
|
+
catalog.models = mergeCatalogEntriesForSync(catalogModelsForMerge, goEntries, baseline, featured, wsEnabled, goIds, template, disabledNativeSlugs(config), gatheredProviderNames, multiAgentMode, exactComboSlugs, hasPhysicalComboProvider, includeNativeOpenAi);
|
|
538
595
|
clampCatalogModelsToCodexSupport(catalog.models);
|
|
539
596
|
|
|
540
597
|
atomicWriteFile(catalogPath, JSON.stringify(catalog, null, 2) + "\n");
|
package/src/codex/catalog.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// AUTO-SPLIT facade: original catalog.ts body moved into ./catalog/* modules.
|
|
2
2
|
// Public surface preserved exactly; importers keep using "src/codex/catalog".
|
|
3
|
-
export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
|
|
3
|
+
export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, readCatalog, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
|
|
4
4
|
export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
|
|
5
|
-
export { NATIVE_OPENAI_MODELS, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, nativeModelRows, applyNativeVisibility, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs } from "./catalog/metadata";
|
|
5
|
+
export { NATIVE_OPENAI_MODELS, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs } 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, augmentRoutedModelsWithJawcodeMetadata } from "./catalog/provider-fetch";
|
package/src/codex/inject.ts
CHANGED
|
@@ -296,10 +296,10 @@ function stripExistingModelProvider(content: string): string {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
|
-
* Drop ROOT-level `model_context_window`
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
299
|
+
* Drop ROOT-level `model_context_window` overrides (keys before the first table header). Codex
|
|
300
|
+
* treats this root key as a global override that wins over the per-model catalog values, so a stale
|
|
301
|
+
* `model_context_window = 1000000` makes every model (e.g. gpt-5.5) report a 1M window. User-owned
|
|
302
|
+
* compaction limits do not alter the advertised context window and must survive reinjection.
|
|
303
303
|
*/
|
|
304
304
|
export function stripRootContextWindowOverrides(content: string): string {
|
|
305
305
|
const lines = content.split("\n");
|
|
@@ -307,7 +307,7 @@ export function stripRootContextWindowOverrides(content: string): string {
|
|
|
307
307
|
return lines
|
|
308
308
|
.filter((line, i) => {
|
|
309
309
|
const isRoot = firstTable === -1 || i < firstTable;
|
|
310
|
-
return !isRoot || !/^\s*
|
|
310
|
+
return !isRoot || !/^\s*model_context_window\s*=/.test(line);
|
|
311
311
|
})
|
|
312
312
|
.join("\n");
|
|
313
313
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { StoredAccountQuota } from "./quota";
|
|
2
|
+
import { truncateRetainedUtf8 } from "../lib/admission";
|
|
3
|
+
|
|
4
|
+
const MAX_DIAGNOSTIC_VALUE_BYTES = 8 * 1024;
|
|
2
5
|
|
|
3
6
|
export interface MainAccountInfo {
|
|
4
7
|
email: string | null;
|
|
@@ -17,7 +20,11 @@ export function getMainAccountInfoCache(): CachedMainAccountInfo | null {
|
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export function setMainAccountInfoCache(value: CachedMainAccountInfo): void {
|
|
20
|
-
cachedMainAccountInfo =
|
|
23
|
+
cachedMainAccountInfo = {
|
|
24
|
+
...value,
|
|
25
|
+
email: value.email === null ? null : truncateRetainedUtf8(value.email, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
26
|
+
plan: value.plan === null ? null : truncateRetainedUtf8(value.plan, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
27
|
+
};
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
export function clearMainAccountInfoCache(): void {
|
package/src/codex/model-cache.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* restarts, so an in-memory cache is sufficient here (no SQLite layer needed).
|
|
9
9
|
*/
|
|
10
10
|
import type { CatalogModel } from "./catalog";
|
|
11
|
+
import type { GenerationContext } from "../lib/state-store-sweeper";
|
|
12
|
+
import { enforceAppOwnedMemoryBudget, type RetainedStoreSnapshot } from "../lib/app-owned-memory";
|
|
11
13
|
|
|
12
14
|
/** Default freshness window. Matches Codex's own 5-min models cache so the two stay in step. */
|
|
13
15
|
export const DEFAULT_MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
@@ -15,6 +17,7 @@ export const DEFAULT_MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
15
17
|
interface CacheEntry {
|
|
16
18
|
models: CatalogModel[];
|
|
17
19
|
fetchedAt: number;
|
|
20
|
+
sizeBytes: number;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export type ProviderModelDiscoveryFailureReason =
|
|
@@ -40,6 +43,29 @@ export type ProviderModelDiscoveryFailure = ProviderModelDiscoveryStatus extends
|
|
|
40
43
|
: never;
|
|
41
44
|
|
|
42
45
|
const cache = new Map<string, CacheEntry>();
|
|
46
|
+
let cacheBytes = 0;
|
|
47
|
+
let oldestCachedProvider: string | undefined;
|
|
48
|
+
let oldestCachedAt: number | null = null;
|
|
49
|
+
const modelCacheEncoder = new TextEncoder();
|
|
50
|
+
|
|
51
|
+
function recomputeOldestCachedProvider(): void {
|
|
52
|
+
oldestCachedProvider = undefined;
|
|
53
|
+
oldestCachedAt = null;
|
|
54
|
+
for (const [provider, entry] of cache) {
|
|
55
|
+
if (oldestCachedAt !== null && entry.fetchedAt >= oldestCachedAt) continue;
|
|
56
|
+
oldestCachedProvider = provider;
|
|
57
|
+
oldestCachedAt = entry.fetchedAt;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function deleteCachedProvider(provider: string): number {
|
|
62
|
+
const entry = cache.get(provider);
|
|
63
|
+
if (!entry) return 0;
|
|
64
|
+
cache.delete(provider);
|
|
65
|
+
cacheBytes = Math.max(0, cacheBytes - entry.sizeBytes);
|
|
66
|
+
if (oldestCachedProvider === provider) recomputeOldestCachedProvider();
|
|
67
|
+
return entry.sizeBytes;
|
|
68
|
+
}
|
|
43
69
|
|
|
44
70
|
/** Cooldown after a failed live `/models` fetch, so a dead/unreachable provider doesn't re-pay
|
|
45
71
|
* the full fetch timeout on every catalog poll (issue #54: UI stalls behind corporate proxies). */
|
|
@@ -54,6 +80,7 @@ const discoveryStatus = new Map<string, ProviderModelDiscoveryStatus>();
|
|
|
54
80
|
* custom-only, and the provider's configured fallback would wrongly stay authoritative.
|
|
55
81
|
*/
|
|
56
82
|
const liveModelCounts = new Map<string, number>();
|
|
83
|
+
let lastReconciledGeneration = 0;
|
|
57
84
|
|
|
58
85
|
export function markModelsFetchFailure(provider: string, now = Date.now()): void {
|
|
59
86
|
failureAt.set(provider, now);
|
|
@@ -129,20 +156,72 @@ export function getStaleCached(provider: string): CatalogModel[] | null {
|
|
|
129
156
|
}
|
|
130
157
|
|
|
131
158
|
export function setCached(provider: string, models: CatalogModel[], now = Date.now()): void {
|
|
132
|
-
|
|
159
|
+
deleteCachedProvider(provider);
|
|
160
|
+
const sizeBytes = modelCacheEncoder.encode(provider).byteLength
|
|
161
|
+
+ modelCacheEncoder.encode(JSON.stringify(models)).byteLength;
|
|
162
|
+
cache.set(provider, { models, fetchedAt: now, sizeBytes });
|
|
163
|
+
cacheBytes += sizeBytes;
|
|
164
|
+
if (oldestCachedAt === null || now < oldestCachedAt) {
|
|
165
|
+
oldestCachedProvider = provider;
|
|
166
|
+
oldestCachedAt = now;
|
|
167
|
+
}
|
|
168
|
+
enforceAppOwnedMemoryBudget();
|
|
133
169
|
}
|
|
134
170
|
|
|
135
171
|
/** Drop one provider's cache (or all) so the next resolve forces a live re-fetch. */
|
|
136
172
|
export function clearModelCache(provider?: string): void {
|
|
137
173
|
if (provider) {
|
|
138
|
-
|
|
174
|
+
deleteCachedProvider(provider);
|
|
139
175
|
failureAt.delete(provider);
|
|
140
176
|
discoveryStatus.delete(provider);
|
|
141
177
|
liveModelCounts.delete(provider);
|
|
142
178
|
} else {
|
|
143
179
|
cache.clear();
|
|
180
|
+
cacheBytes = 0;
|
|
181
|
+
oldestCachedProvider = undefined;
|
|
182
|
+
oldestCachedAt = null;
|
|
144
183
|
failureAt.clear();
|
|
145
184
|
discoveryStatus.clear();
|
|
146
185
|
liveModelCounts.clear();
|
|
147
186
|
}
|
|
148
187
|
}
|
|
188
|
+
|
|
189
|
+
export function reconcileModelCacheProviders(
|
|
190
|
+
validProviders: ReadonlySet<string>,
|
|
191
|
+
generation = lastReconciledGeneration + 1,
|
|
192
|
+
): number {
|
|
193
|
+
if (generation <= lastReconciledGeneration) return 0;
|
|
194
|
+
const removedProviders = new Set<string>();
|
|
195
|
+
for (const store of [failureAt, discoveryStatus, liveModelCounts]) {
|
|
196
|
+
for (const provider of store.keys()) {
|
|
197
|
+
if (validProviders.has(provider)) continue;
|
|
198
|
+
store.delete(provider);
|
|
199
|
+
removedProviders.add(provider);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
for (const provider of cache.keys()) {
|
|
203
|
+
if (validProviders.has(provider)) continue;
|
|
204
|
+
deleteCachedProvider(provider);
|
|
205
|
+
removedProviders.add(provider);
|
|
206
|
+
}
|
|
207
|
+
lastReconciledGeneration = generation;
|
|
208
|
+
return removedProviders.size;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function reconcileModelCacheGeneration(context: GenerationContext): number {
|
|
212
|
+
return reconcileModelCacheProviders(context.providerNames, context.generation);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function modelCacheRetainedStoreSnapshot(): RetainedStoreSnapshot {
|
|
216
|
+
return {
|
|
217
|
+
count: cache.size,
|
|
218
|
+
bytes: cacheBytes,
|
|
219
|
+
evictableBytes: cacheBytes,
|
|
220
|
+
pinnedBytes: 0,
|
|
221
|
+
oldestAt: oldestCachedAt,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function evictOldestModelCacheForBudget(): number {
|
|
226
|
+
return oldestCachedProvider === undefined ? 0 : deleteCachedProvider(oldestCachedProvider);
|
|
227
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OcxAccountPoolRotationStrategy } from "../types";
|
|
2
|
+
import type { GenerationContext } from "../lib/state-store-sweeper";
|
|
2
3
|
|
|
3
4
|
export const POOL_KEY_CODEX = "codex";
|
|
4
5
|
export const POOL_KEY_ANTHROPIC = "anthropic";
|
|
@@ -10,6 +11,7 @@ interface SelectionState {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const selectionState = new Map<string, SelectionState>();
|
|
14
|
+
let lastReconciledGeneration = 0;
|
|
13
15
|
|
|
14
16
|
const DEFAULT_STICKY_LIMIT = 1;
|
|
15
17
|
const MIN_STICKY_LIMIT = 1;
|
|
@@ -184,3 +186,40 @@ export function clearPoolRotationState(poolKey?: string): void {
|
|
|
184
186
|
}
|
|
185
187
|
selectionState.delete(poolKey);
|
|
186
188
|
}
|
|
189
|
+
|
|
190
|
+
export function reconcilePoolRotationState(context: GenerationContext): number {
|
|
191
|
+
if (context.generation <= lastReconciledGeneration) return 0;
|
|
192
|
+
const anthropicIds = new Set<string>();
|
|
193
|
+
for (const key of context.oauthAccountKeys) {
|
|
194
|
+
const separator = key.indexOf("\0");
|
|
195
|
+
if (separator > 0 && key.slice(0, separator) === "anthropic") {
|
|
196
|
+
anthropicIds.add(key.slice(separator + 1));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
let removed = 0;
|
|
200
|
+
for (const [poolKey, state] of selectionState) {
|
|
201
|
+
const valid = poolKey === POOL_KEY_ANTHROPIC
|
|
202
|
+
? anthropicIds
|
|
203
|
+
: poolKey === POOL_KEY_CODEX || poolKey.startsWith(`${POOL_KEY_CODEX}:`)
|
|
204
|
+
? context.codexAccountIds
|
|
205
|
+
: null;
|
|
206
|
+
if (!valid) continue;
|
|
207
|
+
if (valid.size === 0) {
|
|
208
|
+
selectionState.delete(poolKey);
|
|
209
|
+
removed += 1;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (state.activeKey && !valid.has(state.activeKey)) {
|
|
213
|
+
delete state.activeKey;
|
|
214
|
+
state.successes = 0;
|
|
215
|
+
removed += 1;
|
|
216
|
+
}
|
|
217
|
+
for (const accountId of state.currentWeights.keys()) {
|
|
218
|
+
if (valid.has(accountId)) continue;
|
|
219
|
+
state.currentWeights.delete(accountId);
|
|
220
|
+
removed += 1;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
lastReconciledGeneration = context.generation;
|
|
224
|
+
return removed;
|
|
225
|
+
}
|
|
@@ -3,9 +3,11 @@ import path, { dirname, join, resolve } from "node:path";
|
|
|
3
3
|
import { expandUserPath } from "../config";
|
|
4
4
|
import { defaultCodexHome } from "./home";
|
|
5
5
|
import { readRootTomlString } from "./paths";
|
|
6
|
+
import { truncateRetainedUtf8 } from "../lib/admission";
|
|
6
7
|
|
|
7
8
|
const OCX_SECTION_MARKER = "# Auto-injected by opencodex";
|
|
8
9
|
const DIAGNOSTICS_CACHE_TTL_MS = 30_000;
|
|
10
|
+
const MAX_DIAGNOSTIC_VALUE_BYTES = 8 * 1024;
|
|
9
11
|
|
|
10
12
|
function resolveCodexConfigPath(): string {
|
|
11
13
|
const raw = process.env.CODEX_HOME?.trim();
|
|
@@ -309,7 +311,16 @@ export function getCachedProjectConfigDiagnostics(): {
|
|
|
309
311
|
} {
|
|
310
312
|
const now = Date.now();
|
|
311
313
|
if (!diagnosticsCache || now - diagnosticsCache.at > DIAGNOSTICS_CACHE_TTL_MS) {
|
|
312
|
-
diagnosticsCache = {
|
|
314
|
+
diagnosticsCache = {
|
|
315
|
+
at: now,
|
|
316
|
+
warnings: collectProjectCodexConfigWarnings().map(warning => ({
|
|
317
|
+
...warning,
|
|
318
|
+
path: truncateRetainedUtf8(warning.path, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
319
|
+
detail: truncateRetainedUtf8(warning.detail, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
320
|
+
...(warning.profileName === undefined ? {} : { profileName: truncateRetainedUtf8(warning.profileName, MAX_DIAGNOSTIC_VALUE_BYTES) }),
|
|
321
|
+
message: truncateRetainedUtf8(warning.message, MAX_DIAGNOSTIC_VALUE_BYTES),
|
|
322
|
+
})),
|
|
323
|
+
};
|
|
313
324
|
}
|
|
314
325
|
const warnings = diagnosticsCache.warnings;
|
|
315
326
|
return { warnings, grouped: groupProjectCodexConfigWarningsByPath(warnings) };
|