@yansigit/opencodex 2.32.0 → 2.33.1
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 +2 -2
- package/gui/dist/assets/index-CIDo4y4k.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +8 -5
- package/src/adapters/anthropic.ts +20 -6
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +3 -1
- package/src/adapters/command-code.ts +131 -17
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-transport.ts +4 -2
- package/src/adapters/cursor/native-exec-fs.ts +13 -12
- package/src/adapters/cursor/native-exec-network.ts +3 -5
- package/src/adapters/cursor/native-exec-policy.ts +47 -0
- package/src/adapters/cursor/native-exec-shell.ts +13 -25
- package/src/adapters/cursor/native-exec.ts +18 -10
- package/src/adapters/cursor/protobuf-events.ts +186 -9
- package/src/adapters/cursor/protobuf-request.ts +53 -18
- package/src/adapters/cursor/request-builder.ts +11 -3
- package/src/adapters/cursor/tool-definitions.ts +49 -2
- package/src/adapters/cursor/tool-result-normalize.ts +21 -8
- package/src/adapters/cursor/types.ts +11 -3
- package/src/adapters/cursor.ts +123 -0
- package/src/adapters/google-aistudio-parser.ts +49 -0
- package/src/adapters/google-antigravity-replay.ts +2 -2
- package/src/adapters/google-antigravity-wire.ts +7 -0
- package/src/adapters/google-errors.ts +6 -2
- package/src/adapters/google-http.ts +30 -7
- package/src/adapters/google-truncation.ts +5 -0
- package/src/adapters/google-wire-compiler.ts +38 -6
- package/src/adapters/google.ts +242 -28
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +112 -9
- package/src/chat/inbound.ts +15 -0
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +10 -3
- package/src/cli/provider.ts +6 -0
- package/src/cli/status.ts +23 -0
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -12
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +39 -12
- package/src/codex/catalog/parsing.ts +38 -27
- package/src/codex/catalog/provider-fetch.ts +232 -133
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/convergence.ts +5 -0
- package/src/codex/shim.ts +56 -3
- package/src/config/provider-validation.ts +37 -0
- package/src/config.ts +78 -2
- package/src/generated/compatibility-version.json +187 -119
- package/src/generated/model-metadata.ts +3 -0
- package/src/images/loop.ts +37 -6
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/provider-outbound.ts +45 -33
- package/src/lib/provider-tls-profile.ts +309 -0
- package/src/lib/proxy-env.ts +49 -0
- package/src/lib/redact.ts +10 -1
- package/src/oauth/aistudio-native-daemon.ts +62 -0
- package/src/oauth/aistudio-session-sync.ts +95 -0
- package/src/oauth/antigravity-routing.ts +282 -236
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/command-code.ts +5 -16
- package/src/oauth/google-aistudio-auth.ts +98 -0
- package/src/oauth/google-antigravity.ts +42 -5
- package/src/oauth/index.ts +15 -3
- package/src/oauth/key-providers.ts +8 -0
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/login-cli.ts +66 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +6 -0
- package/src/providers/antigravity-quota.ts +3 -1
- package/src/providers/api-keys.ts +2 -1
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/derive.ts +5 -1
- package/src/providers/key-failover.ts +5 -1
- package/src/providers/openai-tiers.ts +5 -0
- package/src/providers/provider-id-rewrite.ts +1 -0
- package/src/providers/quota.ts +148 -50
- package/src/providers/registry.ts +27 -4
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +11 -0
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/schema.ts +37 -0
- package/src/responses/state.ts +94 -4
- package/src/router.ts +11 -2
- package/src/routing/account-pool/cooldown.ts +8 -0
- package/src/routing/account-pool/index.ts +1 -0
- package/src/server/aistudio-ws-hub.ts +295 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +2 -0
- package/src/server/images.ts +19 -35
- package/src/server/index.ts +94 -0
- package/src/server/management/agent-settings-routes.ts +205 -15
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +31 -5
- package/src/server/management/logs-usage-routes.ts +11 -5
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/oauth-account-routes.ts +38 -7
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/port-reclaim.ts +19 -1
- package/src/server/request-log-conversation.ts +12 -0
- package/src/server/request-log.ts +23 -1
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/compact.ts +30 -1
- package/src/server/responses/core.ts +363 -156
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +18 -5
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-undeclared-tool-guard.ts +90 -8
- package/src/server/ws-bridge.ts +2 -1
- package/src/service.ts +1 -1
- package/src/smoke/fingerprint-cache.ts +133 -0
- package/src/smoke/live-scenarios.ts +33 -0
- package/src/smoke/runner.ts +119 -0
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +18 -1
- package/src/types/request.ts +30 -0
- package/src/types/tools.ts +51 -0
- package/src/types.ts +6 -0
- package/src/usage/command-code-manifest.ts +116 -0
- package/src/usage/cost.ts +2 -2
- package/src/usage/expected-prices.ts +83 -0
- package/src/usage/log.ts +2 -2
- package/src/usage/summary.ts +34 -12
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/index.ts +16 -8
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-BG43zwVe.js +0 -102
- package/gui/dist/assets/index-CiSI-jrP.css +0 -1
|
@@ -31,12 +31,17 @@ import {
|
|
|
31
31
|
} from "../../oauth";
|
|
32
32
|
import { replaceProviderAccountSet } from "../../oauth/store";
|
|
33
33
|
import { providerDestinationResolvedError } from "../../lib/destination-policy";
|
|
34
|
+
import {
|
|
35
|
+
antigravityOAuthDestinationConfigError,
|
|
36
|
+
getProviderTlsProfileStatus,
|
|
37
|
+
isAntigravityOAuthProvider,
|
|
38
|
+
} from "../../lib/provider-tls-profile";
|
|
34
39
|
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
35
40
|
import { ProviderOutboundPolicyError, providerOutboundGet, providerOutboundPost, providerRedirectError } from "../../lib/provider-outbound";
|
|
36
41
|
import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
|
|
37
42
|
import { parseAntigravityAvailableModels } from "../../providers/antigravity-models";
|
|
38
43
|
import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
|
|
39
|
-
import { deriveProviderPresets } from "../../providers/derive";
|
|
44
|
+
import { deriveProviderPresets, providerConfigSeed } from "../../providers/derive";
|
|
40
45
|
import { effectiveGoogleMode, providerCodexAccountMode, providerMatchesRegistryTransport } from "../../providers/registry";
|
|
41
46
|
import {
|
|
42
47
|
extractModelEnvelopeRows,
|
|
@@ -54,6 +59,7 @@ import { clearThreadAccountMap } from "../../codex/routing";
|
|
|
54
59
|
import { primeCodexPoolQuotas } from "../../codex/auth-api";
|
|
55
60
|
import { clearModelCache, getProviderDiscoveryStatus } from "../../codex/model-cache";
|
|
56
61
|
import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
|
|
62
|
+
import { modelAutoCompactTokenLimitsConfigError } from "../../providers/auto-compact-budget";
|
|
57
63
|
import { resolveCodexHomeDir } from "../../codex/home";
|
|
58
64
|
import { readUsageEntries } from "../../usage/log";
|
|
59
65
|
import { getUsageDebugLogEntries } from "../../usage/debug";
|
|
@@ -157,6 +163,20 @@ function applyProviderPatchFields(
|
|
|
157
163
|
return { error: "authMode must be key, forward, oauth, or local" };
|
|
158
164
|
}
|
|
159
165
|
}
|
|
166
|
+
if (Object.hasOwn(rawBody, "azureCredential")) {
|
|
167
|
+
const value = rawBody.azureCredential;
|
|
168
|
+
if (value === null) {
|
|
169
|
+
delete next.azureCredential;
|
|
170
|
+
} else {
|
|
171
|
+
if (!isPlainRecord(value)) return { error: "azureCredential must be an object or null" };
|
|
172
|
+
const credential = structuredClone(value) as Record<string, unknown>;
|
|
173
|
+
if (typeof credential.managedIdentityClientId === "string") {
|
|
174
|
+
credential.managedIdentityClientId = credential.managedIdentityClientId.trim();
|
|
175
|
+
}
|
|
176
|
+
next.azureCredential = credential as OcxProviderConfig["azureCredential"];
|
|
177
|
+
}
|
|
178
|
+
touched = true;
|
|
179
|
+
}
|
|
160
180
|
if (Object.hasOwn(rawBody, "apiKeyTransport")) {
|
|
161
181
|
const transport = rawBody.apiKeyTransport;
|
|
162
182
|
if (transport === "x-api-key" || transport === "bearer") {
|
|
@@ -214,6 +234,17 @@ function applyProviderPatchFields(
|
|
|
214
234
|
}
|
|
215
235
|
touched = true;
|
|
216
236
|
}
|
|
237
|
+
if (Object.hasOwn(rawBody, "tlsProfile")) {
|
|
238
|
+
const value = rawBody.tlsProfile;
|
|
239
|
+
if (value === null || value === "") {
|
|
240
|
+
delete next.tlsProfile;
|
|
241
|
+
} else if (value === "antigravity-browser") {
|
|
242
|
+
next.tlsProfile = value;
|
|
243
|
+
} else {
|
|
244
|
+
return { error: "tlsProfile must be antigravity-browser or null" };
|
|
245
|
+
}
|
|
246
|
+
touched = true;
|
|
247
|
+
}
|
|
217
248
|
if (Object.hasOwn(rawBody, "upstreamHttpVersion")) {
|
|
218
249
|
const value = rawBody.upstreamHttpVersion;
|
|
219
250
|
if (value === null || value === "") {
|
|
@@ -266,6 +297,29 @@ function applyProviderPatchFields(
|
|
|
266
297
|
}
|
|
267
298
|
touched = true;
|
|
268
299
|
}
|
|
300
|
+
if (Object.hasOwn(rawBody, "modelAutoCompactTokenLimits")) {
|
|
301
|
+
const value = rawBody.modelAutoCompactTokenLimits;
|
|
302
|
+
const error = modelAutoCompactTokenLimitsConfigError(value, {
|
|
303
|
+
allowTombstones: true,
|
|
304
|
+
requireNativeIds: name === "openai",
|
|
305
|
+
});
|
|
306
|
+
if (error) return { error };
|
|
307
|
+
if (value === null) {
|
|
308
|
+
delete next.modelAutoCompactTokenLimits;
|
|
309
|
+
} else {
|
|
310
|
+
const budgets: Record<string, number> = Object.assign(
|
|
311
|
+
Object.create(null) as Record<string, number>,
|
|
312
|
+
next.modelAutoCompactTokenLimits ?? {},
|
|
313
|
+
);
|
|
314
|
+
for (const [model, budget] of Object.entries(value as Record<string, number | null>)) {
|
|
315
|
+
if (budget === null) delete budgets[model];
|
|
316
|
+
else budgets[model] = budget;
|
|
317
|
+
}
|
|
318
|
+
if (Object.keys(budgets).length > 0) next.modelAutoCompactTokenLimits = budgets;
|
|
319
|
+
else delete next.modelAutoCompactTokenLimits;
|
|
320
|
+
}
|
|
321
|
+
touched = true;
|
|
322
|
+
}
|
|
269
323
|
if (Object.hasOwn(rawBody, "modelSupportsServiceTier")) {
|
|
270
324
|
const value = rawBody.modelSupportsServiceTier;
|
|
271
325
|
if (value === null) {
|
|
@@ -369,6 +423,28 @@ function applyProviderPatchFields(
|
|
|
369
423
|
return { next, touched, editorTouched, enablingOpenAi, headersTouched };
|
|
370
424
|
}
|
|
371
425
|
|
|
426
|
+
/** Validate the canonical OpenAI soft-budget overlay against a fresh registry seed. */
|
|
427
|
+
function canonicalOpenAiBudgetPatchError(
|
|
428
|
+
provider: OcxProviderConfig,
|
|
429
|
+
rawBody: Record<string, unknown>,
|
|
430
|
+
keys: string[],
|
|
431
|
+
config: OcxConfig,
|
|
432
|
+
): string | null {
|
|
433
|
+
if (!isCanonicalOpenAiForwardProvider(provider)) {
|
|
434
|
+
return "provider openai must be the canonical built-in provider";
|
|
435
|
+
}
|
|
436
|
+
const entry = getProviderRegistryEntry("openai");
|
|
437
|
+
if (!entry) return "provider openai registry seed is unavailable";
|
|
438
|
+
const seed = providerConfigSeed(entry);
|
|
439
|
+
if (provider.codexAccountMode !== undefined) seed.codexAccountMode = provider.codexAccountMode;
|
|
440
|
+
if (provider.modelAutoCompactTokenLimits !== undefined) {
|
|
441
|
+
seed.modelAutoCompactTokenLimits = { ...provider.modelAutoCompactTokenLimits };
|
|
442
|
+
}
|
|
443
|
+
const applied = applyProviderPatchFields("openai", seed, rawBody, keys, config);
|
|
444
|
+
if ("error" in applied) return applied.error;
|
|
445
|
+
return providerManagementConfigError("openai", applied.next);
|
|
446
|
+
}
|
|
447
|
+
|
|
372
448
|
export async function handleProviderRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
373
449
|
const { req, url, config, deps, principal, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
|
|
374
450
|
|
|
@@ -405,11 +481,14 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
405
481
|
models: p.models ?? [],
|
|
406
482
|
contextWindow: p.contextWindow,
|
|
407
483
|
modelContextWindows: p.modelContextWindows,
|
|
484
|
+
modelAutoCompactTokenLimits: p.modelAutoCompactTokenLimits,
|
|
408
485
|
modelSupportsServiceTier: p.modelSupportsServiceTier,
|
|
409
486
|
noStructuredOutputModels: p.noStructuredOutputModels,
|
|
410
487
|
upstreamHttpVersion: p.upstreamHttpVersion,
|
|
411
488
|
authMode: p.authMode,
|
|
412
489
|
apiKeyTransport: p.apiKeyTransport,
|
|
490
|
+
tlsProfile: p.tlsProfile,
|
|
491
|
+
tlsProfileStatus: p.tlsProfile === undefined ? "disabled" : getProviderTlsProfileStatus(name, p),
|
|
413
492
|
disabled: p.disabled === true,
|
|
414
493
|
codexAccountMode: providerCodexAccountMode(name, p),
|
|
415
494
|
...(name === "xai" ? { xaiResponsesOptInState: xaiResponsesOptInState(p) } : {}),
|
|
@@ -536,13 +615,14 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
536
615
|
// call can never fire.
|
|
537
616
|
const submittedContextWindow = Object.hasOwn(prov, "contextWindow");
|
|
538
617
|
const submittedModelContextWindows = Object.hasOwn(prov, "modelContextWindows");
|
|
618
|
+
const submittedModelAutoCompactTokenLimits = Object.hasOwn(prov, "modelAutoCompactTokenLimits");
|
|
539
619
|
const submittedRequestPacing = Object.hasOwn(prov, "requestPacing");
|
|
540
620
|
enrichProviderFromCatalog(name, prov);
|
|
541
621
|
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
542
622
|
// Overwriting an existing provider must not drop its multi-key pool: carry it over, then
|
|
543
623
|
// let the (possibly new) apiKey join the pool as the active entry.
|
|
544
624
|
const existingPool = config.providers[name]?.apiKeyPool;
|
|
545
|
-
if (existingPool && !prov.apiKeyPool) prov.apiKeyPool = existingPool;
|
|
625
|
+
if (existingPool && !prov.apiKeyPool && !prov.azureCredential) prov.apiKeyPool = existingPool;
|
|
546
626
|
// The same rule applies to user-configured price overlays: the dashboard's
|
|
547
627
|
// add/edit form does not send modelCosts, so an overwrite must not silently
|
|
548
628
|
// erase hand-edited per-model prices from Logs/Usage estimates.
|
|
@@ -568,6 +648,11 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
568
648
|
? { ...existing.modelContextWindows, ...(prov.modelContextWindows ?? {}) }
|
|
569
649
|
: { ...existing.modelContextWindows };
|
|
570
650
|
}
|
|
651
|
+
if (existing?.modelAutoCompactTokenLimits) {
|
|
652
|
+
prov.modelAutoCompactTokenLimits = submittedModelAutoCompactTokenLimits
|
|
653
|
+
? { ...existing.modelAutoCompactTokenLimits, ...(prov.modelAutoCompactTokenLimits ?? {}) }
|
|
654
|
+
: { ...existing.modelAutoCompactTokenLimits };
|
|
655
|
+
}
|
|
571
656
|
config.providers[name] = stripRegistryOnlyStaticHeaders(name, prov);
|
|
572
657
|
if (body.setDefault === true) config.defaultProvider = name;
|
|
573
658
|
save(config);
|
|
@@ -591,6 +676,9 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
591
676
|
const keys = Object.keys(rawBody);
|
|
592
677
|
const hasMode = Object.hasOwn(rawBody, "codexAccountMode");
|
|
593
678
|
const hasSetDefault = Object.hasOwn(rawBody, "setDefault");
|
|
679
|
+
const canonicalBudgetOnly = name === "openai"
|
|
680
|
+
&& keys.length === 1
|
|
681
|
+
&& keys[0] === "modelAutoCompactTokenLimits";
|
|
594
682
|
|
|
595
683
|
// codexAccountMode keeps its dedicated side-effect path (quota cache clear, thread map
|
|
596
684
|
// clear, pool prime) and is mutually exclusive with every other patch field.
|
|
@@ -653,12 +741,16 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
653
741
|
|
|
654
742
|
const pacingOnly = keys.every(key => key === "requestPacing");
|
|
655
743
|
if (applied.editorTouched && !pacingOnly) {
|
|
656
|
-
const providerError =
|
|
744
|
+
const providerError = canonicalBudgetOnly
|
|
745
|
+
? canonicalOpenAiBudgetPatchError(next, rawBody, keys, config)
|
|
746
|
+
: providerManagementConfigError(name, next);
|
|
657
747
|
if (providerError) return jsonResponse({ error: providerError }, 400);
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
748
|
+
if (!canonicalBudgetOnly) {
|
|
749
|
+
const serviceTierError = providerServiceTierConfigError(name, next);
|
|
750
|
+
if (serviceTierError) return jsonResponse({ error: serviceTierError }, 400);
|
|
751
|
+
const resolvedError = await providerDestinationResolvedError(name, next);
|
|
752
|
+
if (resolvedError) return jsonResponse({ error: resolvedError }, 400);
|
|
753
|
+
}
|
|
662
754
|
} else if (applied.enablingOpenAi) {
|
|
663
755
|
// Same DNS gate as POST: Clash fake-IP only. Never honor a persisted
|
|
664
756
|
// allowPrivateNetwork on this path — it must not bypass the built-in guard.
|
|
@@ -682,15 +774,19 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
682
774
|
return;
|
|
683
775
|
}
|
|
684
776
|
if (replay.editorTouched && !pacingOnly) {
|
|
685
|
-
const syncError =
|
|
777
|
+
const syncError = canonicalBudgetOnly
|
|
778
|
+
? canonicalOpenAiBudgetPatchError(replay.next, rawBody, keys, config)
|
|
779
|
+
: providerManagementConfigError(name, replay.next);
|
|
686
780
|
if (syncError) {
|
|
687
781
|
replayError = syncError;
|
|
688
782
|
return;
|
|
689
783
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
784
|
+
if (!canonicalBudgetOnly) {
|
|
785
|
+
const serviceTierError = providerServiceTierConfigError(name, replay.next);
|
|
786
|
+
if (serviceTierError) {
|
|
787
|
+
replayError = serviceTierError;
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
694
790
|
}
|
|
695
791
|
} else if (replay.enablingOpenAi && !isCanonicalOpenAiForwardProvider(replay.next)) {
|
|
696
792
|
replayError = "provider openai must be the canonical built-in provider";
|
|
@@ -733,6 +829,8 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
733
829
|
if (prov.disabled) {
|
|
734
830
|
return jsonResponse({ ok: false, error: "Provider is disabled", latencyMs: 0 });
|
|
735
831
|
}
|
|
832
|
+
const antigravityError = antigravityOAuthDestinationConfigError(name, prov);
|
|
833
|
+
if (antigravityError) return jsonResponse({ ok: false, error: antigravityError, latencyMs: 0 }, 400);
|
|
736
834
|
if (prov.authMode === "forward") {
|
|
737
835
|
return jsonResponse({
|
|
738
836
|
ok: true,
|
|
@@ -747,12 +845,12 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
747
845
|
return jsonResponse({ applicable: false, reason: "static_catalog", latencyMs: 0 });
|
|
748
846
|
}
|
|
749
847
|
const { buildModelsRequest, getValidAccessTokenSnapshot, resolveModelsAuthToken } = await import("../../oauth");
|
|
750
|
-
const antigravity =
|
|
848
|
+
const antigravity = isAntigravityOAuthProvider(name, prov);
|
|
751
849
|
const snapshot = antigravity
|
|
752
850
|
? await getValidAccessTokenSnapshot(name).catch(() => undefined)
|
|
753
851
|
: undefined;
|
|
754
852
|
const apiKey = snapshot?.accessToken ?? await resolveModelsAuthToken(name, prov);
|
|
755
|
-
if (prov.authMode === "oauth" && !apiKey) {
|
|
853
|
+
if ((prov.authMode === "oauth" || antigravity) && !apiKey) {
|
|
756
854
|
return jsonResponse({ ok: false, latencyMs: 0, error: "static catalog only — upstream not verified (not logged in)" });
|
|
757
855
|
}
|
|
758
856
|
if (prov.adapter === "cursor") {
|
|
@@ -776,7 +874,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
776
874
|
message: `Connected. ${live.models.length} models.`,
|
|
777
875
|
});
|
|
778
876
|
}
|
|
779
|
-
const project =
|
|
877
|
+
const project = antigravity ? snapshot?.projectId : prov.project;
|
|
780
878
|
if (antigravity && !project) {
|
|
781
879
|
return jsonResponse({ ok: false, latencyMs: 0, error: "Antigravity project unavailable — re-run `ocx login google-antigravity`" });
|
|
782
880
|
}
|
|
@@ -188,6 +188,9 @@ function migrateProfileModelReferences(
|
|
|
188
188
|
if (config.injectionModel && config.injectionModel === oldPublicModel) {
|
|
189
189
|
config.injectionModel = newPublicModel;
|
|
190
190
|
}
|
|
191
|
+
if (config.v2NativeParentOverride?.model && config.v2NativeParentOverride.model === oldPublicModel) {
|
|
192
|
+
config.v2NativeParentOverride = { ...config.v2NativeParentOverride, model: newPublicModel };
|
|
193
|
+
}
|
|
191
194
|
if (config.shadowCallIntercept?.model && config.shadowCallIntercept.model === oldPublicModel) {
|
|
192
195
|
config.shadowCallIntercept = { ...config.shadowCallIntercept, model: newPublicModel };
|
|
193
196
|
}
|
|
@@ -144,6 +144,24 @@ export function scanListenPids(port: number): ListenPidScan {
|
|
|
144
144
|
.filter(pid => Number.isSafeInteger(pid) && pid > 0),
|
|
145
145
|
};
|
|
146
146
|
} catch (lsofErr) {
|
|
147
|
+
if (lsofErr && typeof lsofErr === "object" && "status" in lsofErr && lsofErr.status === 1) {
|
|
148
|
+
return { ok: true, pids: [] };
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const output = execFileSync("ss", ["-tlnp", `sport = :${port}`], {
|
|
152
|
+
encoding: "utf-8",
|
|
153
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
154
|
+
timeout: 3000,
|
|
155
|
+
});
|
|
156
|
+
const pids = new Set<number>();
|
|
157
|
+
for (const match of output.matchAll(/pid=(\d+)/g)) {
|
|
158
|
+
const pid = Number(match[1]);
|
|
159
|
+
if (Number.isSafeInteger(pid) && pid > 0) pids.add(pid);
|
|
160
|
+
}
|
|
161
|
+
return { ok: true, pids: [...pids] };
|
|
162
|
+
} catch {
|
|
163
|
+
/* ss unavailable or unsupported on this platform; try netstat */
|
|
164
|
+
}
|
|
147
165
|
try {
|
|
148
166
|
const output = execFileSync("netstat", ["-anlp"], {
|
|
149
167
|
encoding: "utf-8",
|
|
@@ -154,7 +172,7 @@ export function scanListenPids(port: number): ListenPidScan {
|
|
|
154
172
|
} catch (netstatErr) {
|
|
155
173
|
return {
|
|
156
174
|
ok: false,
|
|
157
|
-
error: `lsof/netstat unavailable: ${String(lsofErr)} / ${String(netstatErr)}`,
|
|
175
|
+
error: `lsof/ss/netstat unavailable: ${String(lsofErr)} / ${String(netstatErr)}`,
|
|
158
176
|
};
|
|
159
177
|
}
|
|
160
178
|
}
|
|
@@ -61,6 +61,18 @@ export function sessionIdHeaderFromRequest(headers: Headers): string | null {
|
|
|
61
61
|
return headers.get("session_id") ?? headers.get("session-id");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Codex thread identity from inbound Responses headers when the parent-thread header is absent.
|
|
66
|
+
* Priority: x-codex-parent-thread-id > thread-id > session_id / session-id.
|
|
67
|
+
*/
|
|
68
|
+
export function inboundClientThreadIdFromRequest(headers: Headers): string | undefined {
|
|
69
|
+
return firstSanitizedConversationId(
|
|
70
|
+
headers.get("x-codex-parent-thread-id"),
|
|
71
|
+
headers.get("thread-id"),
|
|
72
|
+
sessionIdHeaderFromRequest(headers),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
function firstSanitizedConversationId(
|
|
65
77
|
...values: Array<string | null | undefined>
|
|
66
78
|
): string | undefined {
|
|
@@ -549,6 +549,7 @@ export function requestLogErrorCode(
|
|
|
549
549
|
// Keep the high-confidence message fallback for runtimes/providers that stripped the
|
|
550
550
|
// structured code before emitting response.failed.
|
|
551
551
|
if (classifiedCode === CYBER_POLICY_ERROR_CODE) return CYBER_POLICY_ERROR_CODE;
|
|
552
|
+
if (classifiedCode === "insufficient_quota") return classifiedCode;
|
|
552
553
|
if (status === 400 || status === 409) return "invalid_request_error";
|
|
553
554
|
if (status === 401) return "invalid_api_key";
|
|
554
555
|
if (status === 403) {
|
|
@@ -1003,6 +1004,26 @@ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchPara
|
|
|
1003
1004
|
filtered = filtered.filter(entry => entry.provider === provider
|
|
1004
1005
|
|| entry.attempts?.some(attempt => attempt.provider === provider));
|
|
1005
1006
|
}
|
|
1007
|
+
const model = params.get("model")?.trim().toLowerCase();
|
|
1008
|
+
if (model) {
|
|
1009
|
+
filtered = filtered.filter(entry => entry.model.toLowerCase() === model
|
|
1010
|
+
|| entry.resolvedModel?.toLowerCase() === model
|
|
1011
|
+
|| entry.attempts?.some(attempt => attempt.model.toLowerCase() === model));
|
|
1012
|
+
}
|
|
1013
|
+
const sinceRaw = params.get("since")?.trim();
|
|
1014
|
+
if (sinceRaw) {
|
|
1015
|
+
const since = Number(sinceRaw);
|
|
1016
|
+
if (Number.isFinite(since)) {
|
|
1017
|
+
filtered = filtered.filter(entry => entry.timestamp >= since);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const untilRaw = params.get("until")?.trim();
|
|
1021
|
+
if (untilRaw) {
|
|
1022
|
+
const until = Number(untilRaw);
|
|
1023
|
+
if (Number.isFinite(until)) {
|
|
1024
|
+
filtered = filtered.filter(entry => entry.timestamp <= until);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1006
1027
|
const conversationId = params.get("conversationId")?.trim() || params.get("conversation")?.trim();
|
|
1007
1028
|
if (conversationId) {
|
|
1008
1029
|
filtered = filtered.filter(entry => matchesLogConversationId(entry.conversationId, conversationId));
|
|
@@ -1179,6 +1200,7 @@ export function finishRequestAttempt(
|
|
|
1179
1200
|
status: number,
|
|
1180
1201
|
durationMs: number,
|
|
1181
1202
|
usage?: OcxUsage,
|
|
1203
|
+
upstreamError?: string,
|
|
1182
1204
|
): PersistedUsageAttempt {
|
|
1183
1205
|
const finalized = finalizedUsage(
|
|
1184
1206
|
attempt.adapter,
|
|
@@ -1193,7 +1215,7 @@ export function finishRequestAttempt(
|
|
|
1193
1215
|
else delete attempt.usage;
|
|
1194
1216
|
if (finalized.totalTokens !== undefined) attempt.totalTokens = finalized.totalTokens;
|
|
1195
1217
|
else delete attempt.totalTokens;
|
|
1196
|
-
const errorCode = requestLogErrorCode(status);
|
|
1218
|
+
const errorCode = requestLogErrorCode(status, upstreamError);
|
|
1197
1219
|
if (errorCode) attempt.errorCode = errorCode;
|
|
1198
1220
|
else delete attempt.errorCode;
|
|
1199
1221
|
return attempt;
|
|
@@ -45,7 +45,7 @@ export function agentTaskRecoveryConfig(config: OcxConfig): AgentTaskRecoveryOpt
|
|
|
45
45
|
enabled: true,
|
|
46
46
|
model: typeof raw.model === "string" && raw.model.trim().length > 0
|
|
47
47
|
? raw.model.trim()
|
|
48
|
-
: "gpt-5.6-
|
|
48
|
+
: "gpt-5.6-luna",
|
|
49
49
|
timeoutMs: Number.isFinite(raw.timeoutMs) && (raw.timeoutMs ?? 0) >= 1_000
|
|
50
50
|
? Math.min(120_000, Math.floor(raw.timeoutMs!))
|
|
51
51
|
: 45_000,
|
|
@@ -127,6 +127,7 @@ import {
|
|
|
127
127
|
} from "./core";
|
|
128
128
|
import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel } from "./fetch-helpers";
|
|
129
129
|
import { mapCodexAuthContextErrorToResponse } from "./codex-auth-error";
|
|
130
|
+
import { decideV2NativeParentOverride } from "./v2-native-parent-override";
|
|
130
131
|
|
|
131
132
|
export const COMPACT_RESPONSE_MAX_BYTES = 32 * 1024 * 1024;
|
|
132
133
|
|
|
@@ -297,6 +298,31 @@ export async function handleResponsesCompact(
|
|
|
297
298
|
}
|
|
298
299
|
return formatErrorResponse(404, "invalid_request_error", err instanceof Error ? err.message : String(err));
|
|
299
300
|
}
|
|
301
|
+
const requestedModel = raw.model;
|
|
302
|
+
// Populate source-route identity before the opt-in decision can fail closed. This keeps
|
|
303
|
+
// malformed/unavailable targets observable as the caller's route without exposing any new data.
|
|
304
|
+
logCtx.requestedModel = requestedModel;
|
|
305
|
+
logCtx.model = route.modelId;
|
|
306
|
+
logCtx.routeDecision = route.routeDecision;
|
|
307
|
+
logCtx.provider = route.codexAccountNamespace
|
|
308
|
+
? `${route.providerName}-${route.codexAccountNamespace}`
|
|
309
|
+
: route.providerName;
|
|
310
|
+
logCtx.providerAdapter = route.provider.adapter;
|
|
311
|
+
const parentOverride = decideV2NativeParentOverride({
|
|
312
|
+
kind: "compact",
|
|
313
|
+
config,
|
|
314
|
+
headers: req.headers,
|
|
315
|
+
sourceRoute: route,
|
|
316
|
+
targetEvidence: evidenceFromBody(raw),
|
|
317
|
+
});
|
|
318
|
+
if (parentOverride.kind === "reject") {
|
|
319
|
+
if (parentOverride.trace) logCtx.routeDecision = parentOverride.trace as typeof logCtx.routeDecision;
|
|
320
|
+
return formatErrorResponse(404, "invalid_request_error", parentOverride.message);
|
|
321
|
+
}
|
|
322
|
+
if (parentOverride.kind === "override") {
|
|
323
|
+
route = parentOverride.route;
|
|
324
|
+
raw.model = route.modelId;
|
|
325
|
+
}
|
|
300
326
|
const selectedModelId = route.modelId;
|
|
301
327
|
// Derive from the RESOLVED route model, not the caller's raw string. An account-qualified
|
|
302
328
|
// selector like `side/gpt-daybreak-blue-latest` does not match the gated map — `slugsEquivalent`
|
|
@@ -304,7 +330,7 @@ export async function handleResponsesCompact(
|
|
|
304
330
|
// exactly the selector form back down the native compact endpoint this guard exists to avoid.
|
|
305
331
|
// `route.modelId` is the same value `applyCodexAccountGatedWireNormalization` uses in core.ts.
|
|
306
332
|
const accountGatedCompactWireModel = codexAccountGatedCanonicalWireModel(selectedModelId);
|
|
307
|
-
logCtx.requestedModel =
|
|
333
|
+
logCtx.requestedModel = requestedModel;
|
|
308
334
|
logCtx.model = selectedModelId;
|
|
309
335
|
logCtx.routeDecision = route.routeDecision;
|
|
310
336
|
logCtx.provider = route.codexAccountNamespace
|
|
@@ -677,6 +703,9 @@ export async function handleResponsesCompact(
|
|
|
677
703
|
body: JSON.stringify(internalBody),
|
|
678
704
|
});
|
|
679
705
|
const response = await handleResponses(internalReq, config, logCtx, { abortSignal: req.signal, turnAdmissionLease, ...(admission ? { admission } : {}) });
|
|
706
|
+
// The internal summarizer is a routed Responses turn, but compact logs retain the
|
|
707
|
+
// caller's selector just like the native compact branch above.
|
|
708
|
+
logCtx.requestedModel = requestedModel;
|
|
680
709
|
if (!response.ok) return response;
|
|
681
710
|
let json: { output?: unknown[]; status?: unknown; error?: unknown };
|
|
682
711
|
if (response.headers.get("content-type")?.includes("text/event-stream")) {
|