@bitkyc08/opencodex 2.42.0 → 2.43.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/AGENTS_INSTALL.md +2 -2
- package/README.md +31 -0
- package/bin/ocx.mjs +10 -1
- package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
- package/gui/dist/assets/index-Djowl68T.js +112 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-codec.ts +304 -0
- package/src/adapters/anthropic-image-normalize.ts +8 -298
- package/src/adapters/anthropic.ts +30 -7
- package/src/adapters/command-code.ts +7 -5
- package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
- package/src/adapters/cursor/images.ts +36 -6
- package/src/adapters/cursor/live-transport.ts +7 -2
- package/src/adapters/cursor/native-exec-desktop.ts +2 -15
- package/src/adapters/cursor/protobuf-request.ts +13 -3
- package/src/adapters/cursor/tool-definitions.ts +5 -670
- package/src/adapters/cursor/tool-guidance.ts +236 -0
- package/src/adapters/cursor/tool-naming.ts +252 -0
- package/src/adapters/cursor/tool-schemas.ts +195 -0
- package/src/adapters/cursor/types.ts +6 -3
- package/src/adapters/exec-tool-result-normalize.ts +1 -1
- package/src/adapters/google-errors.ts +9 -1
- package/src/adapters/google.ts +1 -0
- package/src/adapters/kiro-calibration.ts +181 -0
- package/src/adapters/kiro.ts +135 -3
- package/src/adapters/openai-responses.ts +216 -26
- package/src/adapters/responses-code-mode.ts +59 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-schema-analysis.ts +86 -0
- package/src/adapters/xai-tool-schema.ts +2 -87
- package/src/adapters/xai-web-search.ts +1 -1
- package/src/bridge.ts +22 -10
- package/src/chat/inbound.ts +11 -3
- package/src/claude/inbound-content-options.ts +60 -0
- package/src/claude/inbound-model-options.ts +142 -0
- package/src/claude/inbound-records.ts +7 -0
- package/src/claude/inbound.ts +10 -202
- package/src/claude/model-info.ts +45 -0
- package/src/cli/account-auth.ts +21 -6
- package/src/cli/capabilities.ts +13 -2
- package/src/cli/claude.ts +232 -39
- package/src/cli/config-command.ts +9 -1
- package/src/cli/dispatch.ts +5 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/effort.ts +372 -0
- package/src/cli/export-command.ts +3 -9
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/init.ts +4 -0
- package/src/cli/model-selection-guidance.ts +30 -0
- package/src/cli/models-runtime.ts +3 -2
- package/src/cli/models.ts +8 -3
- package/src/cli/opencode.ts +4 -1
- package/src/cli/provider-runtime.ts +65 -0
- package/src/cli/provider.ts +8 -0
- package/src/cli/registry.ts +16 -2
- package/src/cli/runtime-api.ts +3 -1
- package/src/cli/star-prompt.ts +22 -6
- package/src/cli/status-probes.ts +168 -0
- package/src/cli/status.ts +5 -168
- package/src/clients/config-export/constants.ts +69 -0
- package/src/clients/config-export/contracts.ts +154 -0
- package/src/clients/config-export/dsh.ts +132 -0
- package/src/clients/config-export/fast-models.ts +29 -0
- package/src/clients/config-export/mcode.ts +83 -0
- package/src/clients/config-export/model-metadata.ts +108 -0
- package/src/clients/config-export/omp.ts +104 -0
- package/src/clients/config-export/zcode.ts +92 -0
- package/src/clients/config-export.ts +18 -710
- package/src/codex/account-lifecycle.ts +20 -3
- package/src/codex/account-usability.ts +2 -0
- package/src/codex/auth-api.ts +170 -23
- package/src/codex/auth-context.ts +328 -24
- package/src/codex/catalog/effort.ts +30 -4
- package/src/codex/catalog/metadata.ts +39 -9
- package/src/codex/catalog/native-models.ts +53 -12
- package/src/codex/catalog/parsing.ts +119 -5
- package/src/codex/catalog/provider-fetch.ts +150 -20
- package/src/codex/catalog/reserve.ts +52 -0
- package/src/codex/catalog/sync.ts +89 -16
- package/src/codex/convergence-types.ts +1 -0
- package/src/codex/convergence.ts +2 -0
- package/src/codex/data/upstream-models.json +1 -1
- package/src/codex/forward-transport-headers.ts +25 -0
- package/src/codex/inject.ts +3 -28
- package/src/codex/legacy-config-keys.ts +68 -0
- package/src/codex/log-guard/inspect-schema.ts +137 -0
- package/src/codex/log-guard/inspect.ts +2 -134
- package/src/codex/loopback-target.ts +54 -0
- package/src/codex/main-account-cache.ts +63 -1
- package/src/codex/main-account-hard-lock.ts +52 -0
- package/src/codex/main-account.ts +3 -1
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/model-entitlements.ts +54 -4
- package/src/codex/project-config-warnings.ts +92 -2
- package/src/codex/prompt-layers/encoding.ts +80 -0
- package/src/codex/prompt-layers/paths.ts +54 -0
- package/src/codex/prompt-layers/revision.ts +55 -0
- package/src/codex/prompt-layers/toml-edit.ts +163 -0
- package/src/codex/prompt-layers/toml-read.ts +181 -0
- package/src/codex/prompt-layers.ts +14 -520
- package/src/codex/quota-auto-refresh-state.ts +16 -0
- package/src/codex/quota-auto-refresh.ts +219 -0
- package/src/codex/quota-types.ts +51 -0
- package/src/codex/quota.ts +252 -93
- package/src/codex/reserve-availability.ts +177 -0
- package/src/codex/routing.ts +28 -9
- package/src/codex/shim.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +23 -3
- package/src/combos/failover.ts +125 -7
- package/src/combos/identifiers.ts +89 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/resolve.ts +80 -9
- package/src/combos/types.ts +20 -93
- package/src/config/subagent-models.ts +24 -0
- package/src/config.ts +156 -13
- package/src/generated/compatibility-version.json +454 -166
- package/src/generated/model-metadata.ts +1 -1
- package/src/integrations/journal.ts +65 -4
- package/src/integrations/store.ts +5 -0
- package/src/lab/events/limits.ts +4 -0
- package/src/lib/destination-policy.ts +31 -2
- package/src/lib/errors.ts +39 -0
- package/src/lib/provider-outbound.ts +69 -3
- package/src/lib/proxy-env.ts +22 -0
- package/src/lib/redact-folding.ts +176 -0
- package/src/lib/redact.ts +2 -175
- package/src/lib/state-store-sweeper.ts +20 -6
- package/src/lib/token-estimate.ts +94 -27
- package/src/lib/windows-user-principal.ts +53 -5
- package/src/oauth/anthropic-routing.ts +99 -3
- package/src/oauth/generic-account-failover.ts +36 -13
- package/src/oauth/index.ts +127 -26
- package/src/oauth/login-cli.ts +5 -0
- package/src/oauth/meta-muse.ts +117 -15
- package/src/oauth/pool-settings-capability.ts +15 -4
- package/src/providers/api-keys.ts +8 -10
- package/src/providers/default-aliases.ts +39 -0
- package/src/providers/derive.ts +10 -2
- package/src/providers/fastwire.ts +36 -7
- package/src/providers/initial-model-selection-runtime.ts +90 -0
- package/src/providers/initial-model-selection.ts +120 -0
- package/src/providers/key-failover.ts +134 -54
- package/src/providers/key-store.ts +11 -1
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery.ts +76 -0
- package/src/providers/model-rename-startup.ts +72 -8
- package/src/providers/openai-sidecar.ts +17 -5
- package/src/providers/openai-tiers-destination.ts +102 -0
- package/src/providers/openai-tiers.ts +2 -99
- package/src/providers/opencode-go-transport.ts +41 -0
- package/src/providers/quota-key-accounts.ts +141 -0
- package/src/providers/quota-types.ts +9 -0
- package/src/providers/quota.ts +535 -104
- package/src/providers/registry.ts +60 -17
- package/src/providers/xai-responses-opt-in.ts +31 -5
- package/src/quota/reset-activation.ts +81 -0
- package/src/quota/reset-detector.ts +305 -0
- package/src/quota/reset-notify-config.ts +162 -0
- package/src/quota/reset-observer.ts +125 -0
- package/src/quota/reset-poller.ts +160 -0
- package/src/quota/reset-seen-store.ts +385 -0
- package/src/quota/reset-sinks.ts +199 -0
- package/src/quota/window-mapping.ts +106 -0
- package/src/responses/apply-patch-envelope.ts +46 -0
- package/src/responses/code-mode-helper-compat.ts +39 -1
- package/src/responses/custom-tool-compat.ts +10 -4
- package/src/responses/hosted-tool-policy.ts +12 -4
- package/src/responses/parser-content.ts +133 -0
- package/src/responses/parser-text-format.ts +24 -0
- package/src/responses/parser-tools.ts +188 -0
- package/src/responses/parser.ts +3 -326
- package/src/responses/state.ts +124 -28
- package/src/router.ts +48 -13
- package/src/routing/analytics.ts +1 -0
- package/src/routing/capability.ts +17 -4
- package/src/server/auth-cors.ts +7 -1
- package/src/server/background-lifecycle.ts +23 -1
- package/src/server/chat-completions.ts +25 -3
- package/src/server/claude-messages.ts +62 -5
- package/src/server/effort-row.ts +1 -1
- package/src/server/fast-row.ts +295 -0
- package/src/server/gui-static.ts +30 -4
- package/src/server/index.ts +107 -21
- package/src/server/management/agent-settings-routes.ts +2 -2
- package/src/server/management/combo-routes.ts +37 -9
- package/src/server/management/config-routes.ts +93 -2
- package/src/server/management/integration-routes.ts +108 -0
- package/src/server/management/model-routes.ts +13 -3
- package/src/server/management/model-rows.ts +20 -1
- package/src/server/management/native-integration-routes.ts +4 -1
- package/src/server/management/oauth-account-routes.ts +38 -10
- package/src/server/management/provider-routes.ts +34 -3
- package/src/server/management/quota-reset-routes.ts +57 -0
- package/src/server/management/route-registry.ts +7 -4
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/system-routes.ts +3 -2
- package/src/server/management-api.ts +14 -2
- package/src/server/ports.ts +12 -2
- package/src/server/relay-eager.ts +38 -23
- package/src/server/relay.ts +4 -0
- package/src/server/request-log.ts +6 -0
- package/src/server/responses/codex-ws-correlation.ts +65 -0
- package/src/server/responses/codex-ws-exchange.ts +261 -0
- package/src/server/responses/codex-ws-metadata.ts +134 -0
- package/src/server/responses/codex-ws-pool.ts +162 -0
- package/src/server/responses/codex-ws-request.ts +87 -0
- package/src/server/responses/codex-ws-session.ts +93 -0
- package/src/server/responses/codex-ws-wire.ts +144 -0
- package/src/server/responses/collaboration.ts +41 -1
- package/src/server/responses/compact.ts +105 -12
- package/src/server/responses/core.ts +476 -57
- package/src/server/responses/fetch-helpers.ts +10 -3
- package/src/server/responses/input-admission.ts +16 -9
- package/src/server/responses/responses-field-backfill.ts +1 -1
- package/src/server/responses/ws-upstream.ts +34 -318
- package/src/server/responses-custom-tool-repair.ts +20 -4
- package/src/server/responses-undeclared-tool-guard.ts +100 -8
- package/src/server/safe-response-headers.ts +23 -0
- package/src/server/search.ts +9 -0
- package/src/server/subagent-models-startup.ts +27 -0
- package/src/server/system-env-shell.ts +238 -0
- package/src/server/system-env.ts +7 -234
- package/src/server/ws-bridge.ts +3 -25
- package/src/server/xai-responses-startup.ts +21 -0
- package/src/service-manager-probe.ts +1 -1
- package/src/service.ts +55 -16
- package/src/types/config.ts +108 -12
- package/src/types/provider.ts +36 -7
- package/src/types/tools.ts +26 -1
- package/src/types.ts +2 -0
- package/src/update/notify.ts +8 -2
- package/src/usage/cost.ts +38 -28
- package/src/usage/expected-prices.ts +34 -15
- package/src/usage/log.ts +2 -0
- package/src/usage/model-identity.ts +26 -0
- package/src/usage/summary.ts +15 -1
- package/src/vision/describe.ts +6 -0
- package/src/vision/image-rewrite.ts +108 -0
- package/src/vision/index.ts +19 -306
- package/src/vision/plan.ts +205 -0
- package/src/web-search/executor.ts +6 -0
- package/src/web-search/index.ts +8 -1
- package/gui/dist/assets/index-BU1tE0sr.js +0 -112
- package/gui/dist/assets/index-DL9-iS6J.css +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { effectiveProviderAlias, effectiveProviderAliasDecision } from "../../providers/default-aliases";
|
|
2
|
+
import { initialModelSelectionPending } from "../../providers/initial-model-selection";
|
|
1
3
|
import { execFileSync } from "node:child_process";
|
|
2
4
|
import { createHash, createHmac, randomBytes } from "node:crypto";
|
|
3
5
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
|
|
@@ -32,7 +34,7 @@ import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
|
32
34
|
import { modelInList } from "../../types";
|
|
33
35
|
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
34
36
|
import { isModelVisionSidecarConsumer } from "../../vision/eligibility";
|
|
35
|
-
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
|
|
37
|
+
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider, type ModelMetadata } from "../../generated/model-metadata";
|
|
36
38
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
37
39
|
import {
|
|
38
40
|
captureFastPolicyAuthority,
|
|
@@ -68,6 +70,7 @@ import {
|
|
|
68
70
|
import { redactSecretString } from "../../lib/redact";
|
|
69
71
|
import {
|
|
70
72
|
extractProviderModelItems,
|
|
73
|
+
isRegistryModelDiscoveryUrl,
|
|
71
74
|
readBoundedDiscoveryJson,
|
|
72
75
|
resolveProviderModelDiscovery,
|
|
73
76
|
type ModelDiscoveryResponseFailure,
|
|
@@ -164,6 +167,7 @@ interface CapturedProviderGather {
|
|
|
164
167
|
readonly request: CapturedModelsRequest;
|
|
165
168
|
readonly fastPolicyAuthority: FastPolicyAuthority;
|
|
166
169
|
readonly metadataModelIdCaseFold: boolean;
|
|
170
|
+
readonly effectiveAlias?: string | null;
|
|
167
171
|
readonly observedAuth?: ModelsAuthResolution;
|
|
168
172
|
/**
|
|
169
173
|
* Configured model ids this provider must keep even when live discovery omits
|
|
@@ -380,6 +384,7 @@ function captureTrustedOpenAiApiPolicy(
|
|
|
380
384
|
models: entry.models,
|
|
381
385
|
...(entry.modelContextWindows ? { modelContextWindows: entry.modelContextWindows } : {}),
|
|
382
386
|
...(entry.modelMaxInputTokens ? { modelMaxInputTokens: entry.modelMaxInputTokens } : {}),
|
|
387
|
+
...(entry.modelMaxOutputTokens ? { modelMaxOutputTokens: entry.modelMaxOutputTokens } : {}),
|
|
383
388
|
...(entry.virtualModels ? { virtualModels: entry.virtualModels } : {}),
|
|
384
389
|
...(entry.modelInputModalities ? { modelInputModalities: entry.modelInputModalities } : {}),
|
|
385
390
|
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: entry.modelReasoningEfforts } : {}),
|
|
@@ -413,6 +418,7 @@ function captureProviderGather(
|
|
|
413
418
|
configured: OcxProviderConfig,
|
|
414
419
|
authResolver: ModelsAuthResolver,
|
|
415
420
|
retainConfiguredModelIds?: ReadonlySet<string>,
|
|
421
|
+
config?: Pick<OcxConfig, "providers">,
|
|
416
422
|
): CapturedProviderGather {
|
|
417
423
|
const enriched = detachedClone(withCanonicalOpenAiForwardAuthDefault(name, configured));
|
|
418
424
|
enrichProviderFromRegistry(name, enriched);
|
|
@@ -454,6 +460,7 @@ function captureProviderGather(
|
|
|
454
460
|
maxModels: discovery.maxModels,
|
|
455
461
|
trustedOpenAiApi,
|
|
456
462
|
});
|
|
463
|
+
const effectiveAlias = effectiveProviderAliasDecision(name, configured, config);
|
|
457
464
|
return Object.freeze({
|
|
458
465
|
name,
|
|
459
466
|
provider,
|
|
@@ -462,6 +469,7 @@ function captureProviderGather(
|
|
|
462
469
|
request,
|
|
463
470
|
fastPolicyAuthority,
|
|
464
471
|
metadataModelIdCaseFold,
|
|
472
|
+
effectiveAlias,
|
|
465
473
|
...(observedAuth ? { observedAuth: Object.freeze({ ...observedAuth }) } : {}),
|
|
466
474
|
...(retainConfiguredModelIds && retainConfiguredModelIds.size > 0
|
|
467
475
|
? { retainConfiguredModelIds }
|
|
@@ -503,6 +511,7 @@ function captureGatherFlight(
|
|
|
503
511
|
provider,
|
|
504
512
|
authResolver,
|
|
505
513
|
comboTargetsByProvider.get(name),
|
|
514
|
+
config,
|
|
506
515
|
));
|
|
507
516
|
const discoveryPolicySnapshots = Object.freeze(providers.map(provider => provider.policy));
|
|
508
517
|
return Object.freeze({
|
|
@@ -625,8 +634,36 @@ export function clearGatherRoutedModelsInflight(): void {
|
|
|
625
634
|
gatherInflight.clear();
|
|
626
635
|
}
|
|
627
636
|
|
|
637
|
+
const NUMERIC_MODEL_ID_SEGMENT = /^\d+$/;
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Resolve an unknown Claude point release or date pin from the nearest configured
|
|
641
|
+
* family row. Only numeric tail segments are removed so unrelated model families
|
|
642
|
+
* cannot inherit one another's limits.
|
|
643
|
+
*/
|
|
644
|
+
function anthropicFamilyContextWindow(
|
|
645
|
+
record: Record<string, number> | undefined,
|
|
646
|
+
id: string,
|
|
647
|
+
): number | undefined {
|
|
648
|
+
if (!record || !id.toLowerCase().startsWith("claude-")) return undefined;
|
|
649
|
+
let candidate = id;
|
|
650
|
+
while (true) {
|
|
651
|
+
const cut = candidate.lastIndexOf("-");
|
|
652
|
+
if (cut <= 0 || !NUMERIC_MODEL_ID_SEGMENT.test(candidate.slice(cut + 1))) return undefined;
|
|
653
|
+
candidate = candidate.slice(0, cut);
|
|
654
|
+
const value = modelRecordValue(record, candidate);
|
|
655
|
+
if (typeof value === "number" && value > 0) return value;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Resolve the configured context window in exact-model, Anthropic numeric-family,
|
|
661
|
+
* then provider-wide order. Return undefined when the selected value is not positive.
|
|
662
|
+
*/
|
|
628
663
|
export function configuredContextWindow(prov: OcxProviderConfig, id: string): number | undefined {
|
|
629
|
-
const configured = modelRecordValue(prov.modelContextWindows, id)
|
|
664
|
+
const configured = modelRecordValue(prov.modelContextWindows, id)
|
|
665
|
+
?? (prov.adapter === "anthropic" ? anthropicFamilyContextWindow(prov.modelContextWindows, id) : undefined)
|
|
666
|
+
?? prov.contextWindow;
|
|
630
667
|
return typeof configured === "number" && configured > 0 ? configured : undefined;
|
|
631
668
|
}
|
|
632
669
|
|
|
@@ -717,7 +754,7 @@ function configuredVerbositySupport(name: string, prov: OcxProviderConfig | unde
|
|
|
717
754
|
// Read from the PROVIDER CONFIG, never from PROVIDER_REGISTRY. A gather flight captures its
|
|
718
755
|
// registry authority up front and forbids any later registry read, so consulting the registry
|
|
719
756
|
// here made a custom-destination flight fall back to "configured" instead of serving its own
|
|
720
|
-
// discovery result (tests/codex-gather-authority.test.ts). `applyVerbosityDefaults` in
|
|
757
|
+
// discovery result (tests/codex-integration/codex-gather-authority.test.ts). `applyVerbosityDefaults` in
|
|
721
758
|
// providers/derive.ts materializes the registry default into the config at seed/enrich time.
|
|
722
759
|
return prov.supportsVerbosity;
|
|
723
760
|
}
|
|
@@ -728,8 +765,17 @@ export function applyProviderConfigHints(
|
|
|
728
765
|
model: CatalogModel,
|
|
729
766
|
providerCap?: number,
|
|
730
767
|
metadataModelIdCaseFold?: boolean,
|
|
768
|
+
effectiveAlias?: string | null,
|
|
731
769
|
): CatalogModel {
|
|
732
770
|
const displayName = configuredModelDisplayName(prov, model.id);
|
|
771
|
+
// The alias decision is resolved once at flight admission (captureProviderGather) and threaded
|
|
772
|
+
// through as `effectiveAlias`. Re-deriving it here would read PROVIDER_REGISTRY after admission,
|
|
773
|
+
// which is exactly the authority leak tests/codex-integration/codex-gather-authority.test.ts
|
|
774
|
+
// forbids: a flight must not consult the live registry once its transport has been captured.
|
|
775
|
+
// When no decision was threaded in, carry whatever the row already resolved to instead.
|
|
776
|
+
const providerAlias = typeof effectiveAlias === "string" || effectiveAlias === null
|
|
777
|
+
? effectiveAlias
|
|
778
|
+
: model.providerAlias;
|
|
733
779
|
const configuredCap = configuredContextWindow(prov, model.id);
|
|
734
780
|
const configuredMaxInput = configuredMaxInputTokens(prov, model.id);
|
|
735
781
|
const maxOutputTokens = routedMaxOutputTokens(name, prov, model, model.id, metadataModelIdCaseFold);
|
|
@@ -755,6 +801,7 @@ export function applyProviderConfigHints(
|
|
|
755
801
|
const {
|
|
756
802
|
supportsServiceTier: _staleServiceTier,
|
|
757
803
|
fastTierDescription: _staleFastTierDescription,
|
|
804
|
+
providerAlias: _staleProviderAlias,
|
|
758
805
|
...modelWithoutServiceTier
|
|
759
806
|
} = model;
|
|
760
807
|
// 已发现窗口只允许被配置值压低;缺窗口时,已开的 Context cap 就是实际窗口。
|
|
@@ -767,6 +814,7 @@ export function applyProviderConfigHints(
|
|
|
767
814
|
const hinted = {
|
|
768
815
|
...modelWithoutServiceTier,
|
|
769
816
|
...(displayName !== undefined ? { displayName } : {}),
|
|
817
|
+
...(providerAlias !== undefined ? { providerAlias } : {}),
|
|
770
818
|
...(hintedWindow !== undefined ? { contextWindow: hintedWindow } : {}),
|
|
771
819
|
...(inputModalities ? { inputModalities } : {}),
|
|
772
820
|
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
|
|
@@ -826,8 +874,9 @@ export function catalogHintsFromProviderConfig(
|
|
|
826
874
|
id: string,
|
|
827
875
|
contextCap?: number,
|
|
828
876
|
metadataModelIdCaseFold?: boolean,
|
|
877
|
+
effectiveAlias?: string | null,
|
|
829
878
|
): Partial<CatalogModel> {
|
|
830
|
-
const hinted = applyProviderConfigHints(name, prov, { id, provider: name }, contextCap, metadataModelIdCaseFold);
|
|
879
|
+
const hinted = applyProviderConfigHints(name, prov, { id, provider: name }, contextCap, metadataModelIdCaseFold, effectiveAlias);
|
|
831
880
|
const { provider: _provider, id: _id, ...hints } = hinted;
|
|
832
881
|
return hints;
|
|
833
882
|
}
|
|
@@ -838,8 +887,9 @@ export function applyConfigHintsToCachedModels(
|
|
|
838
887
|
models: CatalogModel[],
|
|
839
888
|
contextCap?: number,
|
|
840
889
|
metadataModelIdCaseFold?: boolean,
|
|
890
|
+
effectiveAlias?: string | null,
|
|
841
891
|
): CatalogModel[] {
|
|
842
|
-
return models.map(model => applyProviderConfigHints(name, prov, model, contextCap, metadataModelIdCaseFold));
|
|
892
|
+
return models.map(model => applyProviderConfigHints(name, prov, model, contextCap, metadataModelIdCaseFold, effectiveAlias));
|
|
843
893
|
}
|
|
844
894
|
|
|
845
895
|
|
|
@@ -862,6 +912,62 @@ interface ComboCatalogMemberFallback {
|
|
|
862
912
|
readonly reasoningEfforts?: readonly string[];
|
|
863
913
|
}
|
|
864
914
|
|
|
915
|
+
/**
|
|
916
|
+
* Ladder advertised for a combo member whose vendor metadata says it reasons but
|
|
917
|
+
* carries no explicit ladder (Claude, Grok). Codex needs a non-empty ladder to show
|
|
918
|
+
* the effort control; the routed adapters clamp to the real upstream top rung.
|
|
919
|
+
*/
|
|
920
|
+
const ROUTED_COMBO_MEMBER_REASONING_EFFORTS: readonly string[] = ["low", "medium", "high", "xhigh", "max"];
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Vendor-table lookup tolerant of point releases and date pins. Configured combo
|
|
924
|
+
* targets often name a variant the table does not carry (`claude-fable-5-1`,
|
|
925
|
+
* `claude-opus-4-5-20251101`); the base family row still describes its modality
|
|
926
|
+
* and reasoning capability, so fall back to it before giving up.
|
|
927
|
+
*/
|
|
928
|
+
function comboMemberVendorMetadata(provider: string, modelId: string): ModelMetadata | undefined {
|
|
929
|
+
const exact = getModelMetadataCaseInsensitive(provider, modelId);
|
|
930
|
+
if (exact) return exact;
|
|
931
|
+
let candidate = modelId.replace(/\[[^\]]*\]$/, "");
|
|
932
|
+
while (true) {
|
|
933
|
+
const trimmed = candidate.replace(/-\d+$/, "");
|
|
934
|
+
if (trimmed === candidate || !trimmed.includes("-")) return undefined;
|
|
935
|
+
const hit = getModelMetadataCaseInsensitive(provider, trimmed);
|
|
936
|
+
if (hit) return hit;
|
|
937
|
+
candidate = trimmed;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Combo members are usually thin discovery rows (id + context window). Without a
|
|
943
|
+
* capability source the combo intersection collapses to text-only / no effort ladder,
|
|
944
|
+
* and the Codex app then refuses image attachments and hides the effort picker for
|
|
945
|
+
* every Claude combo. The generated vendor table knows both, so use it as the
|
|
946
|
+
* last-resort fallback when the caller supplied none.
|
|
947
|
+
*
|
|
948
|
+
* `ModelMetadata.maxTokens` is the OUTPUT ceiling, so it fills `maxOutputTokens`.
|
|
949
|
+
* Mapping it onto `maxInputTokens` would be read by the combo intersection
|
|
950
|
+
* (`aggregation.ts` `Math.min` over member input ceilings) as a 128k input limit and
|
|
951
|
+
* shrink a 1M Claude combo window to 128k, taking autoCompactTokenLimit down with it.
|
|
952
|
+
*/
|
|
953
|
+
function vendorMetadataComboFallback(target: { provider: string; model: string }): ComboCatalogMemberFallback | undefined {
|
|
954
|
+
const metadataProvider = resolveMetadataProvider(target.provider);
|
|
955
|
+
const metadata = metadataProvider ? comboMemberVendorMetadata(metadataProvider, target.model) : undefined;
|
|
956
|
+
if (!metadata) return undefined;
|
|
957
|
+
return {
|
|
958
|
+
...(typeof metadata.contextWindow === "number" && metadata.contextWindow > 0
|
|
959
|
+
? { contextWindow: metadata.contextWindow }
|
|
960
|
+
: {}),
|
|
961
|
+
...(typeof metadata.maxTokens === "number" && metadata.maxTokens > 0
|
|
962
|
+
? { maxOutputTokens: metadata.maxTokens }
|
|
963
|
+
: {}),
|
|
964
|
+
...(Array.isArray(metadata.input) && metadata.input.length > 0
|
|
965
|
+
? { inputModalities: [...metadata.input] }
|
|
966
|
+
: {}),
|
|
967
|
+
...(metadata.reasoning === true ? { reasoningEfforts: [...ROUTED_COMBO_MEMBER_REASONING_EFFORTS] } : {}),
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
|
|
865
971
|
/**
|
|
866
972
|
* Resolve a combo target to a catalog member for derivation.
|
|
867
973
|
* Prefer discovery metadata; when the target is missing from the gather map or
|
|
@@ -877,11 +983,12 @@ export function resolveComboCatalogMember(
|
|
|
877
983
|
memberByKey: ReadonlyMap<string, CatalogModel>,
|
|
878
984
|
providers: ReadonlyMap<string, OcxProviderConfig>,
|
|
879
985
|
contextCap?: number,
|
|
880
|
-
|
|
986
|
+
callerFallback?: ComboCatalogMemberFallback,
|
|
881
987
|
metadataModelIdCaseFold?: boolean,
|
|
882
988
|
): CatalogModel | undefined {
|
|
883
989
|
const existing = memberByKey.get(targetKey(target));
|
|
884
990
|
const prov = providers.get(target.provider);
|
|
991
|
+
const fallback = callerFallback ?? vendorMetadataComboFallback(target);
|
|
885
992
|
// Disabled providers never contribute members — even a complete discovery row
|
|
886
993
|
// is unusable for catalog derivation while the provider is off.
|
|
887
994
|
if (prov?.disabled === true) return undefined;
|
|
@@ -1410,7 +1517,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1410
1517
|
const configured: CatalogModel[] = configuredIds.map(id => ({
|
|
1411
1518
|
id,
|
|
1412
1519
|
provider: name,
|
|
1413
|
-
...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold),
|
|
1520
|
+
...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
|
|
1414
1521
|
}));
|
|
1415
1522
|
const withConfiguredRetention = (
|
|
1416
1523
|
models: CatalogModel[],
|
|
@@ -1464,7 +1571,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1464
1571
|
: [{
|
|
1465
1572
|
id: prov.defaultModel,
|
|
1466
1573
|
provider: name,
|
|
1467
|
-
...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold),
|
|
1574
|
+
...catalogHintsFromProviderConfig(name, prov, prov.defaultModel, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
|
|
1468
1575
|
}];
|
|
1469
1576
|
const vertexDefaultSeed = seedVertexDefault ? configured[0] : undefined;
|
|
1470
1577
|
const withVertexDefaultSeed = (models: CatalogModel[]): CatalogModel[] => (
|
|
@@ -1481,7 +1588,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1481
1588
|
const cachedCursor = getFreshCached(name, ttlMs);
|
|
1482
1589
|
if (cachedCursor) {
|
|
1483
1590
|
return observed(
|
|
1484
|
-
withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold)),
|
|
1591
|
+
withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias)),
|
|
1485
1592
|
"authoritative",
|
|
1486
1593
|
);
|
|
1487
1594
|
}
|
|
@@ -1489,7 +1596,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1489
1596
|
const cooling = getStaleCached(name);
|
|
1490
1597
|
return observed(
|
|
1491
1598
|
withConfiguredRetention(
|
|
1492
|
-
cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold) : configured,
|
|
1599
|
+
cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
|
|
1493
1600
|
),
|
|
1494
1601
|
"degraded",
|
|
1495
1602
|
);
|
|
@@ -1530,7 +1637,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1530
1637
|
const staleCursor = getStaleCached(name);
|
|
1531
1638
|
return observed(
|
|
1532
1639
|
withConfiguredRetention(
|
|
1533
|
-
staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold) : configured,
|
|
1640
|
+
staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
|
|
1534
1641
|
),
|
|
1535
1642
|
"degraded",
|
|
1536
1643
|
);
|
|
@@ -1548,7 +1655,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1548
1655
|
if (fresh) {
|
|
1549
1656
|
return observed(
|
|
1550
1657
|
withConfiguredRetention(
|
|
1551
|
-
withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold)),
|
|
1658
|
+
withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias)),
|
|
1552
1659
|
),
|
|
1553
1660
|
"authoritative",
|
|
1554
1661
|
); // dedups Codex's frequent /v1/models polling within the TTL
|
|
@@ -1560,7 +1667,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1560
1667
|
return observed(
|
|
1561
1668
|
withConfiguredRetention(
|
|
1562
1669
|
stale
|
|
1563
|
-
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold))
|
|
1670
|
+
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
|
|
1564
1671
|
: failedDiscoveryConfigured,
|
|
1565
1672
|
),
|
|
1566
1673
|
"degraded",
|
|
@@ -1600,7 +1707,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1600
1707
|
return {
|
|
1601
1708
|
models: withConfiguredRetention(
|
|
1602
1709
|
stale
|
|
1603
|
-
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold))
|
|
1710
|
+
? withVertexDefaultSeed(applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias))
|
|
1604
1711
|
: failedDiscoveryConfigured,
|
|
1605
1712
|
),
|
|
1606
1713
|
fallback: stale ? "stale" : "configured",
|
|
@@ -1608,16 +1715,24 @@ async function fetchProviderModelsWithAuth(
|
|
|
1608
1715
|
};
|
|
1609
1716
|
};
|
|
1610
1717
|
try {
|
|
1718
|
+
// Canonical-URL TUN transparency for Clash/Surge/Mihomo fake-IP DNS:
|
|
1719
|
+
// `isRegistryModelDiscoveryUrl` proves the FINAL request URL is the
|
|
1720
|
+
// registry's own fixed discovery URL, so a purely-benchmark DNS answer may
|
|
1721
|
+
// be pin-connected through the intercepting TUN without proxy env. The
|
|
1722
|
+
// proof is on the URL — not the provider name — because an OAuth/forward
|
|
1723
|
+
// name matches any baseUrl by design. Retargeted or renamed custom rows
|
|
1724
|
+
// fetch a different URL and keep the rejection.
|
|
1725
|
+
const outboundDependencies = { isCanonicalUrl: isRegistryModelDiscoveryUrl };
|
|
1611
1726
|
const res = request.method === "POST"
|
|
1612
1727
|
? await providerOutboundPost(name, prov, url, {
|
|
1613
1728
|
headers,
|
|
1614
1729
|
body: JSON.stringify({ project }),
|
|
1615
1730
|
signal: AbortSignal.timeout(8000),
|
|
1616
|
-
})
|
|
1731
|
+
}, outboundDependencies)
|
|
1617
1732
|
: await providerOutboundGet(name, prov, url, {
|
|
1618
1733
|
headers,
|
|
1619
1734
|
signal: AbortSignal.timeout(8000),
|
|
1620
|
-
});
|
|
1735
|
+
}, outboundDependencies);
|
|
1621
1736
|
const redirectError = await providerRedirectError(res, url);
|
|
1622
1737
|
if (redirectError) {
|
|
1623
1738
|
const { models, fallback, shouldLog } = failedDiscoveryFallback({ reason: "http", httpStatus: res.status });
|
|
@@ -1677,7 +1792,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1677
1792
|
reasoningEfforts: [],
|
|
1678
1793
|
...(model.contextWindow ? { contextWindow: model.contextWindow } : {}),
|
|
1679
1794
|
...(model.inputModalities ? { inputModalities: model.inputModalities } : {}),
|
|
1680
|
-
}, contextCap, metadataModelIdCaseFold));
|
|
1795
|
+
}, contextCap, metadataModelIdCaseFold, captured.effectiveAlias));
|
|
1681
1796
|
const forCache = withConfiguredRetention(live, { retainComboTargets: false });
|
|
1682
1797
|
if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
|
|
1683
1798
|
return observed(withConfiguredRetention(configured), "degraded");
|
|
@@ -1740,7 +1855,7 @@ async function fetchProviderModelsWithAuth(
|
|
|
1740
1855
|
provider: name,
|
|
1741
1856
|
...(ownedBy ? { owned_by: ownedBy } : {}),
|
|
1742
1857
|
...discoveredHints,
|
|
1743
|
-
}, contextCap, metadataModelIdCaseFold);
|
|
1858
|
+
}, contextCap, metadataModelIdCaseFold, captured.effectiveAlias);
|
|
1744
1859
|
})
|
|
1745
1860
|
.filter(m => shouldExposeProviderModel(name, m.id));
|
|
1746
1861
|
// Capture the count BEFORE the alias/configured augmentation below pushes extra rows into
|
|
@@ -1902,6 +2017,7 @@ export function filterCatalogVisibleModels(
|
|
|
1902
2017
|
}
|
|
1903
2018
|
}
|
|
1904
2019
|
return models.filter(m => {
|
|
2020
|
+
if (initialModelSelectionPending(config.providers[m.provider])) return false;
|
|
1905
2021
|
const nativeAlias = m.provider === COMBO_NAMESPACE && m.nativeAlias === true;
|
|
1906
2022
|
// disabledModels may be stored raw (canonical) or encoded (legacy UI writes).
|
|
1907
2023
|
for (const stored of disabled) {
|
|
@@ -2042,6 +2158,18 @@ async function gatherRoutedModelsUncached(
|
|
|
2042
2158
|
config,
|
|
2043
2159
|
capture.openAiApiPolicy,
|
|
2044
2160
|
);
|
|
2161
|
+
const apiProvider = activeProviders.find(provider => provider.name === OPENAI_API_PROVIDER_ID);
|
|
2162
|
+
// Trusted reconstruction replaces whole rows, including the earlier Fast hints.
|
|
2163
|
+
// Restore only that capability from the same captured authority used by discovery.
|
|
2164
|
+
if (apiProvider) {
|
|
2165
|
+
for (const model of apiAugmented) {
|
|
2166
|
+
if (model.provider !== OPENAI_API_PROVIDER_ID) continue;
|
|
2167
|
+
const policy = fastPolicyForModel(apiProvider.provider, model.id, apiProvider.name);
|
|
2168
|
+
const supported = serviceTierSupportFromPolicy(policy);
|
|
2169
|
+
if (supported !== undefined) model.supportsServiceTier = supported;
|
|
2170
|
+
if (supported === true && policy.fastTierDescription !== undefined) model.fastTierDescription = policy.fastTierDescription;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2045
2173
|
const metadataModelIdCaseFoldByProvider = new Map(
|
|
2046
2174
|
activeProviders.map(provider => [provider.name, provider.metadataModelIdCaseFold]),
|
|
2047
2175
|
);
|
|
@@ -2132,7 +2260,7 @@ async function gatherRoutedModelsUncached(
|
|
|
2132
2260
|
const nativeAliasMaxInput = combo.nativeAlias && combo.alias
|
|
2133
2261
|
? (combo.alias.startsWith("gpt-5.6-") || combo.alias.includes("daybreak")
|
|
2134
2262
|
? NATIVE_GPT56_MAX_INPUT_TOKENS
|
|
2135
|
-
: nativeOpenAiMaxInputTokens(combo.alias) ?? nativeOpenAiContextWindow(combo.alias))
|
|
2263
|
+
: nativeOpenAiMaxInputTokens(combo.alias, comboNativeLimits) ?? nativeOpenAiContextWindow(combo.alias, comboNativeLimits))
|
|
2136
2264
|
: undefined;
|
|
2137
2265
|
const nativeAliasAutoCompact = combo.nativeAlias && combo.alias
|
|
2138
2266
|
? nativeOpenAiAutoCompactTokenLimit(combo.alias, comboNativeLimits)
|
|
@@ -2455,7 +2583,9 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
|
|
|
2455
2583
|
const maxOutputTokens = routedMaxOutputTokens(
|
|
2456
2584
|
OPENAI_API_PROVIDER_ID,
|
|
2457
2585
|
configured,
|
|
2458
|
-
|
|
2586
|
+
policy.modelMaxOutputTokens?.[id] !== undefined
|
|
2587
|
+
? { provider: OPENAI_API_PROVIDER_ID, id, maxOutputTokens: policy.modelMaxOutputTokens[id] }
|
|
2588
|
+
: existingById.get(id) ?? { provider: OPENAI_API_PROVIDER_ID, id },
|
|
2459
2589
|
policy.virtualModels?.[id]?.wireModelId ?? id,
|
|
2460
2590
|
);
|
|
2461
2591
|
return {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import { isEffectiveCodexDesktopAuthless } from "../loopback-target";
|
|
3
|
+
import { CODEX_ACCOUNT_BOUND_CATALOG_KIND } from "./account-models";
|
|
4
|
+
import { NATIVE_RESERVE_MODEL } from "./native-models";
|
|
5
|
+
import type { RawEntry } from "./parsing";
|
|
6
|
+
|
|
7
|
+
export const RESERVE_METADATA_SOURCE_FIELD = "opencodex_reserve_metadata_source";
|
|
8
|
+
/** Validated genuine source metadata on the existing catalog, never an authorization. */
|
|
9
|
+
export const RESERVE_SOURCE_CATALOG_FIELD = "opencodex_reserve_source";
|
|
10
|
+
export const RESERVE_LUNA_METADATA_SOURCE = "gpt-5.6-luna";
|
|
11
|
+
|
|
12
|
+
/** Metadata only: no process-local availability or credential state belongs in a catalog. */
|
|
13
|
+
export interface ReserveCatalogProjection {
|
|
14
|
+
readonly source: RawEntry;
|
|
15
|
+
readonly mainSelectors: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The caller supplies a validated actual observation and an already context-capped Luna pin. */
|
|
19
|
+
export function createReserveCatalogProjection(
|
|
20
|
+
config: Pick<OcxConfig, "runtimeRole" | "hostname" | "unauthenticatedLoopbackListener" | "codexDesktopAuthless">,
|
|
21
|
+
mainSelectors: readonly string[],
|
|
22
|
+
observedSource: RawEntry | null,
|
|
23
|
+
lunaSource: RawEntry | null,
|
|
24
|
+
): ReserveCatalogProjection | undefined {
|
|
25
|
+
if (!isEffectiveCodexDesktopAuthless(config) || mainSelectors.length === 0) return undefined;
|
|
26
|
+
const original = observedSource ?? lunaSource;
|
|
27
|
+
if (!original) return undefined;
|
|
28
|
+
const source = structuredClone(original);
|
|
29
|
+
source.slug = NATIVE_RESERVE_MODEL;
|
|
30
|
+
source.display_name = observedSource?.display_name ?? "Luna Reserve";
|
|
31
|
+
source.description = "Manual main-account Reserve through OpenCodex; recent upstream permission is required for every request.";
|
|
32
|
+
// This qualified OCX endpoint accepts the selector, not an OpenAI API-key model grant.
|
|
33
|
+
source.supported_in_api = true;
|
|
34
|
+
source[RESERVE_METADATA_SOURCE_FIELD] = observedSource ? NATIVE_RESERVE_MODEL : RESERVE_LUNA_METADATA_SOURCE;
|
|
35
|
+
delete source.available_in_plans;
|
|
36
|
+
delete source.availability_nux;
|
|
37
|
+
delete source.upgrade;
|
|
38
|
+
delete source.opencodex_account_observed_native;
|
|
39
|
+
delete source.opencodex_account_observed_selectors;
|
|
40
|
+
return { source, mainSelectors: [...mainSelectors] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Exact OCX account projection, never another provider's similarly named model. */
|
|
44
|
+
export function isReserveCatalogProjection(entry: RawEntry): boolean {
|
|
45
|
+
return entry.opencodex_catalog_kind === CODEX_ACCOUNT_BOUND_CATALOG_KIND
|
|
46
|
+
&& typeof entry.slug === "string"
|
|
47
|
+
&& entry.slug.indexOf("/") > 0
|
|
48
|
+
&& entry.slug.indexOf("/") === entry.slug.lastIndexOf("/")
|
|
49
|
+
&& entry.slug.endsWith(`/${NATIVE_RESERVE_MODEL}`)
|
|
50
|
+
&& (entry[RESERVE_METADATA_SOURCE_FIELD] === NATIVE_RESERVE_MODEL
|
|
51
|
+
|| entry[RESERVE_METADATA_SOURCE_FIELD] === RESERVE_LUNA_METADATA_SOURCE);
|
|
52
|
+
}
|