Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs-site/src/content/docs/guides/codex-app-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,22 @@ advertised where it cannot be honored. An `openai-chat` destination can authoriz
eligible model with `chatServiceTier: true`, or authorize only exact models with
`modelSupportsServiceTier`; Responses routes do not need that extra Chat wire authorization.

### Selecting Flex on eligible routed models

On NanoGPT, `openai/gpt-6-luna` now advertises **Flex** and **Fast** in Codex's model speed selector.
Other gateways can opt in per verified model by adding `modelSupportsServiceTier`
and `fastWire: { kind: "service-tier", canonicalToWire: { priority: "priority", flex: "flex" }, foreignCallerTiers: "verbatim" }`
to their provider entry. For `openai-chat` adapters, also set `chatServiceTier: true`
so the caller's Flex request reaches the upstream API. This declares support; it does not
change pricing or grant an account entitlement.

Flex is **not** enabled by Fast mode: it must be selected explicitly in Codex.
Leave opencodex's global `fastMode` on Auto; forced Fast or forced Default overrides
caller-selected Flex. Only an eligible exact routed model receives the Flex catalog tier,
and removing the capability withdraws the generated choice after synchronization.
Native/account rows and combos are unchanged. Flex may have higher latency or
temporary resource unavailability. A tier choice is not a promise of provider acceptance.

### Selecting Ultra Fast in Codex Desktop

For a provider that supports `service_tier: "ultrafast"`, declare that wire mapping once.
Expand Down
14 changes: 14 additions & 0 deletions docs-site/src/content/docs/ko/guides/codex-app-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,20 @@ fast_mode = true
`service_tier`가 제거됩니다(레지스트리가 정식 OpenAI를 `true`, DeepSeek과 Volcengine Ark를 `false`로 분류). 미분류 커스텀 게이트웨이는 호출자가 준 값을 그대로 보존하고 주입도 받지 않습니다. 따라서
처리 불가능한 곳에 fast 옵션이 노출되지 않으며, 커스텀 게이트웨이는 `true`로 명시적으로 옵트인할 수 있습니다.

### 지원되는 라우팅 모델에서 Flex 선택

NanoGPT의 `openai/gpt-6-luna`는 Codex 모델 속도 선택 메뉴에 **Flex**와 **Fast**를 표시합니다.
다른 게이트웨이는 검증된 모델에 대해 `modelSupportsServiceTier`를 지정하고,
프로바이더 설정에 `fastWire: { kind: "service-tier", canonicalToWire: { priority: "priority", flex: "flex" }, foreignCallerTiers: "verbatim" }`를 선언할 수 있습니다.
`openai-chat` 어댑터라면 `chatServiceTier: true`도 필요합니다.
이 설정은 지원 여부를 선언할 뿐 요금이나 실제 계정 권한을 보장하지 않습니다.

Flex는 Fast를 켜는 것만으로 활성화되지 않으며 Codex에서 직접 선택해야 합니다.
OpenCodex의 전역 `fastMode`는 Auto로 유지하세요. Fast/Default 강제 모드는 Flex 선택보다
우선합니다. 명시적으로 지원하는 라우팅 모델에만 Flex를 추가하고, 지원 선언이 없어지면
동기화 이후 자동 생성된 항목을 제거합니다. 네이티브·계정별 모델과 콤보는 변경하지 않습니다.
Flex 요청은 지연이 길거나 리소스 부족으로 실패할 수 있습니다.

### Codex 데스크톱에서 Ultra Fast 선택

`service_tier: "ultrafast"`를 지원하는 프로바이더에는 전송 매핑을 한 번 설정하면 됩니다.
Expand Down
8 changes: 7 additions & 1 deletion src/codex/catalog/build-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { NATIVE_RESERVE_MODEL } from "./native-models";
import { isReserveCatalogProjection, type ReserveCatalogProjection } from "./reserve";
import { deriveEntry, finishUpstreamNativeEntry, isExactComboCatalogEntry } from "./derive-entry";
import { PICKER_ORDER_PRIORITY_BASE, SPAWN_PRIORITY_FIELD } from "./subagent-roster";
import { applyConfiguredUltraFast, configuredUltraFastSlugs } from "./configured-tiers";
import { applyConfiguredFlex, applyConfiguredUltraFast, configuredFlexSlugs, configuredUltraFastSlugs } from "./configured-tiers";
import { applyNativeUltraFastTier } from "./native-ultrafast";

export interface ObservedCatalogEntryBuildInput {
Expand Down Expand Up @@ -114,11 +114,13 @@ export function buildCatalogEntries(
});
// Live discovery does not run the disk merge; use the same captured declarations and row fences.
const configured = configuredUltraFastSlugs(goModels);
const configuredFlex = configuredFlexSlugs(goModels);
for (const entry of entries) {
if (typeof entry.slug !== "string" || !entry.slug.includes("/")
|| isNativeAliasCatalogEntry(entry) || isExactComboCatalogEntry(entry, exactComboSlugs)
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined) continue;
applyConfiguredUltraFast(entry, undefined, configured.has(entry.slug), false);
applyConfiguredFlex(entry, configuredFlex.has(entry.slug));
}
applyFullModelPickerOrder(entries, modelPickerOrder);
return entries;
Expand Down Expand Up @@ -485,6 +487,8 @@ export interface ObservedCatalogMergeInput {
readonly ultraFastTier?: boolean;
/** Current captured wire declarations; disk rows and templates never populate this set. */
readonly configuredUltraFastSlugs?: ReadonlySet<string>;
/** Exact provider/model declarations for the optional Flex picker tier. */
readonly configuredFlexSlugs?: ReadonlySet<string>;
readonly catalogModels: readonly RawEntry[];
readonly baselineCatalogModels: readonly RawEntry[];
readonly routedEntries: readonly RawEntry[];
Expand Down Expand Up @@ -532,6 +536,7 @@ export interface ObservedCatalogMergeInput {
export function mergeCatalogEntriesFromObservedState({
ultraFastTier = false,
configuredUltraFastSlugs = new Set(),
configuredFlexSlugs = new Set(),
catalogModels,
baselineCatalogModels,
routedEntries,
Expand Down Expand Up @@ -888,6 +893,7 @@ export function mergeCatalogEntriesFromObservedState({
|| isNativeAliasCatalogEntry(entry) || isExactComboCatalogEntry(entry, exactComboSlugs)
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined) continue;
applyConfiguredUltraFast(entry, operatorRows.get(entry.slug), configuredUltraFastSlugs.has(entry.slug), ultraFastTier);
applyConfiguredFlex(entry, configuredFlexSlugs.has(entry.slug));
}
const finalRoutedEntrySet = new Set(finalRoutedEntries);
const degradedPreservedCount = preservedRoutedEntries.filter(entry => {
Expand Down
49 changes: 49 additions & 0 deletions src/codex/catalog/configured-tiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,55 @@ import { preserveOperatorUltraFastTiers } from "./operator-tiers";

const MANAGED_ULTRAFAST = "opencodex_managed_ultrafast";

/** Flex is a foreign caller tier: advertise it only when the final wire can forward it verbatim. */
export function supportsConfiguredFlex(policy: ResolvedFastPolicy | undefined): boolean {
return policy?.eligibility === "eligible"
&& policy.forwardCallerTier === true
&& policy.fastWire?.kind === "service-tier"
&& policy.fastWire.foreignCallerTiers === "verbatim"
&& policy.fastWire.canonicalToWire.flex === "flex";
}

/** An exact routed capability, never inferred from a model family or template. */
export function configuredFlexSlugs(models: readonly CatalogModel[]): ReadonlySet<string> {
const declarations = new Map<string, boolean>();
for (const model of models) {
const slug = catalogModelSlug(model);
const supported = model.provider !== COMBO_NAMESPACE && model.nativeAlias !== true
&& model.supportsServiceTier === true && model.supportsFlexTier === true;
declarations.set(slug, supported && declarations.get(slug) !== false);
}
return new Set([...declarations].filter(([, supported]) => supported).map(([slug]) => slug));
}

/**
* Publish the optional Flex selector. A generated tier is marked so a stale
* persisted row cannot keep it after the live capability has been withdrawn.
*/
export function applyConfiguredFlex(entry: RawEntry, configured: boolean): void {
const marker = "opencodex_managed_flex";
if (entry[marker] === entry.slug) {
if (Array.isArray(entry.service_tiers)) {
entry.service_tiers = entry.service_tiers.filter(tier => tier?.id !== "flex");
}
if (Array.isArray(entry.additional_speed_tiers)) {
entry.additional_speed_tiers = entry.additional_speed_tiers.filter(tier => tier !== "flex");
}
}
delete entry[marker];
if (!configured) return;
const tiers = Array.isArray(entry.service_tiers) ? entry.service_tiers : [];
if (!tiers.some(tier => tier?.id === "flex")) {
entry.service_tiers = [...tiers, {
id: "flex", name: "Flex",
description: "Lower cost, slower processing; provider access required",
}];
}
const speeds = Array.isArray(entry.additional_speed_tiers) ? entry.additional_speed_tiers : [];
if (!speeds.includes("flex")) entry.additional_speed_tiers = [...speeds, "flex"];
entry[marker] = entry.slug;
}

/** Advertise Ultra Fast only when the eligible final wire explicitly maps that exact tier. */
export function supportsConfiguredUltraFast(policy: ResolvedFastPolicy | undefined): boolean {
return policy?.eligibility === "eligible"
Expand Down
4 changes: 3 additions & 1 deletion src/codex/catalog/model-hints.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { canInferVendorContext, vendorContextInference } from "./vendor-context";
import { effectiveProviderAlias, effectiveProviderAliasDecision } from "../../providers/default-aliases";
import { initialModelSelectionPending } from "../../providers/initial-model-selection";
import { supportsConfiguredUltraFast } from "./configured-tiers";
import { supportsConfiguredFlex, supportsConfiguredUltraFast } from "./configured-tiers";
import { execFileSync } from "node:child_process";
import { createHash, createHmac, randomBytes } from "node:crypto";
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
Expand Down Expand Up @@ -319,6 +319,7 @@ export function applyProviderConfigHints(
contextCapped: previousContextCapped,
supportsServiceTier: _staleServiceTier,
supportsUltraFastTier: _staleUltraFastTier,
supportsFlexTier: _staleFlexTier,
fastTierDescription: _staleFastTierDescription,
providerAlias: _staleProviderAlias,
suppressSyntheticMax: _staleSuppressSyntheticMax,
Expand Down Expand Up @@ -360,6 +361,7 @@ export function applyProviderConfigHints(
...(typeof supportsVerbosity === "boolean" ? { supportsVerbosity } : {}),
...(typeof supportsServiceTier === "boolean" ? { supportsServiceTier } : {}),
supportsUltraFastTier: supportsConfiguredUltraFast(fastPolicy),
supportsFlexTier: supportsConfiguredFlex(fastPolicy),
...(supportsServiceTier === true && fastPolicy.fastTierDescription !== undefined
? { fastTierDescription: fastPolicy.fastTierDescription }
: {}),
Expand Down
2 changes: 2 additions & 0 deletions src/codex/catalog/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export interface CatalogModel {
supportsServiceTier?: boolean;
/** Captured eligible service-tier wire explicitly maps ultrafast to ultrafast. */
supportsUltraFastTier?: boolean;
/** Captured eligible service-tier wire explicitly supports caller-selected Flex. */
supportsFlexTier?: boolean;
/** Optional provider-specific copy for the advertised Fast tier. */
fastTierDescription?: string;
supportsReasoningSummaries?: boolean;
Expand Down
3 changes: 2 additions & 1 deletion src/codex/catalog/retained-sync.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { projectAntigravitySelectedModels } from "../../providers/antigravity-effort-families";
import { configuredUltraFastSlugs } from "./configured-tiers";
import { configuredFlexSlugs, configuredUltraFastSlugs } from "./configured-tiers";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { initializeConfigOwnership } from "../../lib/config-ownership";
Expand Down Expand Up @@ -511,6 +511,7 @@ function writeRetainedCatalogSync({
catalog.models = mergeCatalogEntriesFromObservedState({
ultraFastTier: ultraFastTierEnabled(config),
configuredUltraFastSlugs: configuredUltraFastSlugs(orderedGoModels),
configuredFlexSlugs: configuredFlexSlugs(orderedGoModels),
modelPickerOrder,
accountSelectors,
catalogModels: catalogModelsForMerge,
Expand Down
4 changes: 3 additions & 1 deletion src/codex/catalog/routed-gather.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { canInferVendorContext } from "./vendor-context";
import { effectiveProviderAlias, effectiveProviderAliasDecision } from "../../providers/default-aliases";
import { initialModelSelectionPending } from "../../providers/initial-model-selection";
import { supportsConfiguredUltraFast } from "./configured-tiers";
import { supportsConfiguredFlex, supportsConfiguredUltraFast } from "./configured-tiers";
import { execFileSync } from "node:child_process";
import { createHash, createHmac, randomBytes } from "node:crypto";
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
Expand Down Expand Up @@ -364,6 +364,7 @@ async function gatherRoutedModelsUncached(
const supported = serviceTierSupportFromPolicy(policy);
if (supported !== undefined) model.supportsServiceTier = supported;
model.supportsUltraFastTier = supportsConfiguredUltraFast(policy);
model.supportsFlexTier = supportsConfiguredFlex(policy);
if (supported === true && policy.fastTierDescription !== undefined) model.fastTierDescription = policy.fastTierDescription;
}
}
Expand Down Expand Up @@ -661,6 +662,7 @@ async function gatherRoutedModelsUncached(
...(cm.defaultReasoningEffort ? { defaultReasoningEffort: cm.defaultReasoningEffort } : {}),
...(typeof supportsServiceTier === "boolean" ? { supportsServiceTier } : {}),
supportsUltraFastTier: supportsConfiguredUltraFast(fastPolicy),
supportsFlexTier: supportsConfiguredFlex(fastPolicy),
...(supportsServiceTier === true && fastPolicy?.fastTierDescription !== undefined
? { fastTierDescription: fastPolicy.fastTierDescription }
: {}),
Expand Down
3 changes: 2 additions & 1 deletion src/codex/convergence.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { projectAntigravitySelectedModels } from "../providers/antigravity-effort-families";
import { configuredUltraFastSlugs } from "./catalog/configured-tiers";
import { configuredFlexSlugs, configuredUltraFastSlugs } from "./catalog/configured-tiers";
import { join } from "node:path";
import { sameCatalogHealPath } from "./catalog/heal-observation";

Expand Down Expand Up @@ -373,6 +373,7 @@ function prepareCatalog(
const mergedModels = mergeCatalogEntriesFromObservedState({
ultraFastTier: ultraFastTierEnabled(config),
configuredUltraFastSlugs: configuredUltraFastSlugs(ordered),
configuredFlexSlugs: configuredFlexSlugs(ordered),
modelPickerOrder,
accountSelectors,
catalogModels,
Expand Down
14 changes: 13 additions & 1 deletion src/providers/registry/entries-extended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,19 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
preserveResponsesReasoningContent: true,
note: "Domestic BigModel Coding Plan Responses endpoint; static model roster",
},
{ id: "nanogpt", label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://nano-gpt.com/api" },
{
id: "nanogpt", label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1",
adapter: "openai-chat", authKind: "key", dashboardUrl: "https://nano-gpt.com/api",
// NanoGPT's model catalog advertises both flex and priority for this exact Luna id.
// Do not extend this to other models without equivalent upstream capability evidence.
modelSupportsServiceTier: { "openai/gpt-6-luna": true },
chatServiceTier: true,
fastWire: {
kind: "service-tier",
canonicalToWire: { priority: "priority", flex: "flex" },
foreignCallerTiers: "verbatim",
},
},
{ id: "synthetic", label: "Synthetic", baseUrl: "https://api.synthetic.new/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://synthetic.new" },
// SiliconFlow publishes an OpenAI-compatible chat endpoint and a dynamic model catalog. Do not
// freeze reasoning controls here: enable_thinking/thinking_budget support and limits vary by
Expand Down
1 change: 1 addition & 0 deletions structure/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ is still not inferred from a GPT name. Stored configuration and native capabilit
unchanged. Request-time native effort clamps remain canonical-forward only.

The public builder in `src/codex/catalog/build-entries.ts` and shared merge use `src/codex/catalog/configured-tiers.ts` to project Ultra Fast only for an eligible captured service-tier wire explicitly mapping `ultrafast` to `ultrafast`. Live `/v1/models?client_version=...` discovery and both writers use current exact routed slugs; conflicting duplicates, templates, native/account rows and combos cannot grant capability. Live rows import no persisted operator tiers. Generated choices carry an exact-slug ownership marker so declaration withdrawal removes stale generated tiers even with operator retention enabled; publication leaves the default unchanged. Coverage: `tests/codex-integration/configured-ultrafast-picker.test.ts` and `tests/codex-integration/ultrafast-live-catalog.test.ts`.
Flex publication uses the same observed-state entry points but is independently gated: the final wire must be eligible, OpenAI service-tier compatible, and able to forward foreign caller tiers verbatim, with an explicit `flex: "flex"` mapping. Only exact routed models receive an opt-in `Flex` descriptor; no global or per-model default tier is changed. Duplicate declarations fail closed and withdrawn generated tiers lose their ownership marker. NanoGPT declares this capability only for `openai/gpt-6-luna`. Coverage extends the configured-tier integration tests.
Separately, `src/codex/catalog/operator-tiers.ts` preserves a hand-supplied Ultra Fast tier from the exact persisted routed row only when `ultraFastTier` is enabled. Baselines and native templates cannot supply it. Current configured capability takes precedence; previously generated tiers cannot become operator evidence. Both overlays run after admission, preserve Fast policy and never create rows. Disabling `ultraFastTier` removes only independently supplied operator tiers; disabling the wire declaration removes generated ones. Operator defaults are retained only alongside an independently supplied tier declaration. Coverage: `tests/codex-integration/ultrafast-tier-honesty.test.ts`.
Native rows are the one place the `ultraFastTier` flag itself adds a tier. `src/codex/catalog/native-ultrafast.ts` appends the documented `{ id: "ultrafast", name: "Ultrafast" }` descriptor to `service_tiers` and `"ultrafast"` to `additional_speed_tiers` on bare and trusted account-bound `gpt-6-astra` and `gpt-6.1-sol` rows, after the existing Fast tier and only when no Ultrafast entry is present. Codex reads the picker from `model_catalog_json` instead of ChatGPT's plan-aware live list, so without it Desktop never offers the choice. Each field it fills is recorded in the row's `opencodex_generated_service_tiers` marker, and disabling withdraws only marked fields (and only the unedited descriptor), so an operator-written native entry survives even when its text matches. Aliases, combos, reserve projections and other models are untouched, and the default tier never changes. Coverage: `tests/codex-integration/native-ultrafast-picker.test.ts`.

Expand Down
Loading
Loading