Skip to content
Merged
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
39 changes: 39 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 @@ -320,6 +320,45 @@ 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 in Codex

For a routed provider and model verified to accept `service_tier: "flex"`, explicitly
declare Flex support alongside the existing Fast wire. Merge these fields into the
**existing** `providers.nanogpt` entry (do not replace its URL or credentials):

```json
{
"modelSupportsServiceTier": { "openai/gpt-6-luna": true },
"fastWire": {
"kind": "service-tier",
"canonicalToWire": { "priority": "priority", "flex": "flex" },
"foreignCallerTiers": "verbatim"
}
}
```

With an eligible `openai-responses` route, the Luna row advertises Standard,
Fast and Flex in Codex's speed selector. For an `openai-chat` route, also set
`chatServiceTier: true` **only if** the upstream Chat Completions endpoint supports
caller-provided service tiers. The `fastWire` mapping is **provider-wide**:
other service-tier-eligible routed models in the same `providers.nanogpt`
entry can also advertise Flex when the catalog publishes them as ordinary routed
rows, even when they are not listed in `modelSupportsServiceTier`. That map is not
an allowlist excluding other eligible models. Combo-namespace and native-alias
rows are **not** published with Flex. Verify Flex support for **every** eligible
ordinary routed model;
if only Luna supports Flex, configure it in a separate provider entry with
no other Fast-eligible models. Do not infer Flex support from Fast alone.

Keep OpenCodex's global Fast mode on **Auto** (`fastMode` unset), as forcing
Fast or Default overrides the caller's Flex choice. After `ocx sync`, refresh the
Codex model list. Select **Flex** from the speed selector, or use
`/model <routed-id> --speed=flex` in Codex CLI. This is a **selectable tier**,
not a per-model forced default: switching models does not force Flex automatically.
The tier is forwarded as `service_tier: "flex"` and can still be declined by
the upstream; availability, cost and latency depend on the provider. Neither
the menu nor a request alone proves which tier was billed.

### Selecting Ultra Fast in Codex Desktop

For a provider that supports `service_tier: "ultrafast"`, declare that wire mapping once.
Expand Down
40 changes: 40 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,46 @@ fast_mode = true
`service_tier`가 제거됩니다(레지스트리가 정식 OpenAI를 `true`, DeepSeek과 Volcengine Ark를 `false`로 분류). 미분류 커스텀 게이트웨이는 호출자가 준 값을 그대로 보존하고 주입도 받지 않습니다. 따라서
처리 불가능한 곳에 fast 옵션이 노출되지 않으며, 커스텀 게이트웨이는 `true`로 명시적으로 옵트인할 수 있습니다.

### Codex에서 Flex 선택

`service_tier: "flex"` 지원이 확인된 라우팅 프로바이더와 모델에 한해 Flex를
명시적으로 선언할 수 있습니다. 아래 필드는 **기존** `providers.nanogpt` 설정에
병합해야 하며 URL과 API 키를 덮어쓰면 안 됩니다.

```json
{
"modelSupportsServiceTier": { "openai/gpt-6-luna": true },
"fastWire": {
"kind": "service-tier",
"canonicalToWire": { "priority": "priority", "flex": "flex" },
"foreignCallerTiers": "verbatim"
}
}
```

적격한 `openai-responses` 라우트에서는 Luna의 Codex 속도 선택기에 Standard,
Fast, Flex가 나타납니다. `openai-chat` 라우트는 Chat Completions API가
호출자의 서비스 등급을 지원하는 것이 확인된 경우에만 `chatServiceTier: true`도
설정해야 합니다. `fastWire` 매핑은 **프로바이더 전체에 적용**되므로 같은
`providers.nanogpt` 항목에서 서비스 등급 사용이 이미 허용된 다른 모델에도
Flex 선택지가 표시될 수 있습니다. `modelSupportsServiceTier`에 Luna만 적어도
이미 적격인 다른 모델을 제외하는 허용 목록이 되는 것은 아닙니다. 다만 Flex 선택지는
카탈로그에 게시되는 **일반 라우팅 모델 행**에만 표시되며, Combo 네임스페이스와
Native Alias 행에는 표시되지 않습니다. 따라서 해당 항목에서 서비스 등급 사용이
허용된 **모든 일반 라우팅 모델**의 Flex 지원 여부를 확인하세요.
Luna만 Flex를 지원한다면 Fast 지원 모델이 다른 모델과 섞이지 않도록
**별도 프로바이더 항목**으로 분리해야 합니다. Fast 지원만으로 Flex 지원을
추정해서는 안 됩니다.

OpenCodex의 전역 Fast 설정은 **Auto**(`fastMode` 생략)로 유지하세요.
Fast 또는 Default 강제 모드는 Flex 선택을 덮어씁니다. `ocx sync` 후 Codex의
모델 목록을 갱신하고 속도 선택기에서 **Flex**를 선택하거나, Codex CLI에서
`/model <routed-id> --speed=flex`를 사용하면 됩니다. 이는 **선택 가능한 등급**이지
모델별 강제 기본값은 아닙니다. 다른 모델로 바꾸더라도 Flex 사용이 자동 강제되지는
않습니다. 상위 API로 `service_tier: "flex"`를 전달하지만, 실제 처리 가능 여부,
요금과 지연 시간은 공급자에 따라 다릅니다. 메뉴나 요청 로그만으로 Flex 과금이
확정되지는 않습니다.

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

`service_tier: "ultrafast"`를 지원하는 프로바이더에는 전송 매핑을 한 번 설정하면 됩니다.
Expand Down
22 changes: 22 additions & 0 deletions src/codex/catalog/build-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,8 @@ export interface ObservedCatalogMergeInput {
readonly ultraFastTier?: boolean;
/** Current captured wire declarations; disk rows and templates never populate this set. */
readonly configuredUltraFastSlugs?: ReadonlySet<string>;
/** Current config-only verdict for persisted rows (not authority from the old disk tier). */
readonly retainedFlexPolicyBySlug?: ReadonlyMap<string, boolean>;
readonly catalogModels: readonly RawEntry[];
readonly baselineCatalogModels: readonly RawEntry[];
readonly routedEntries: readonly RawEntry[];
Expand Down Expand Up @@ -532,6 +534,7 @@ export interface ObservedCatalogMergeInput {
export function mergeCatalogEntriesFromObservedState({
ultraFastTier = false,
configuredUltraFastSlugs = new Set(),
retainedFlexPolicyBySlug = new Map(),
catalogModels,
baselineCatalogModels,
routedEntries,
Expand Down Expand Up @@ -889,6 +892,25 @@ export function mergeCatalogEntriesFromObservedState({
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined) continue;
applyConfiguredUltraFast(entry, operatorRows.get(entry.slug), configuredUltraFastSlugs.has(entry.slug), ultraFastTier);
}
// Degraded discovery preserves disk rows, so they bypass fresh tier generation.
// Revoke only an explicitly disallowed Flex tier according to today's provider policy;
// a transient outage must not erase Flex while the configured wire still permits it.
const persistedRoutedSet = new Set(preservedRoutedEntries);
for (const entry of finalRoutedEntries) {
if (!persistedRoutedSet.has(entry)) continue;
const slug = typeof entry.slug === "string" ? entry.slug : "";
const excluded = isNativeAliasCatalogEntry(entry) || isExactComboCatalogEntry(entry, exactComboSlugs);
if (!excluded && retainedFlexPolicyBySlug.get(slug) !== false) continue;
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");
}
for (const field of ["service_tier", "default_service_tier"] as const) {
if (entry[field] === "flex") delete entry[field];
}
}
const finalRoutedEntrySet = new Set(finalRoutedEntries);
const degradedPreservedCount = preservedRoutedEntries.filter(entry => {
if (!finalRoutedEntrySet.has(entry)) return false;
Expand Down
70 changes: 70 additions & 0 deletions src/codex/catalog/configured-tiers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { COMBO_NAMESPACE } from "../../combos";
import type { OcxProviderConfig } from "../../types";
import { decodeRoutedModelId } from "../../providers/slug-codec";
import { fastPolicyForModel } from "../../providers/service-tier";
import type { ResolvedFastPolicy } from "../../providers/fastwire";
import { catalogModelSlug, type CatalogModel, type RawEntry } from "./parsing";
import { preserveOperatorUltraFastTiers } from "./operator-tiers";

const MANAGED_ULTRAFAST = "opencodex_managed_ultrafast";

/** Advertise Flex only when the final route can forward the caller's exact flex tier. */
export function supportsConfiguredFlex(policy: ResolvedFastPolicy | undefined): boolean {
return policy?.eligibility === "eligible"
&& policy.fastWire?.kind === "service-tier"
&& policy.fastWire.canonicalToWire.flex === "flex"
&& policy.forwardCallerTier;
}

/** 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 Expand Up @@ -48,3 +59,62 @@ export function applyConfiguredUltraFast(
// Advertising a selectable capability does not opt the user into its increased usage.
entry[MANAGED_ULTRAFAST] = entry.slug;
}

/**
* Re-evaluate persisted routed Flex choices using current provider configuration. Degraded
* discovery keeps disk rows but cannot let a withdrawn wire declaration keep old picker tiers.
* Only rows tied to a configured provider are judged; unmanaged foreign rows are untouched.
*/
export function retainedFlexPolicyBySlug(
providers: Readonly<Record<string, OcxProviderConfig>>,
rows: readonly RawEntry[],
observedModels: readonly CatalogModel[],
): ReadonlyMap<string, boolean> {
const result = new Map<string, boolean>();
const currentModels = new Map<string, CatalogModel[]>();
for (const model of observedModels) {
const slug = catalogModelSlug(model);
const candidates = currentModels.get(slug) ?? [];
candidates.push(model);
currentModels.set(slug, candidates);
}
for (const row of rows) {
const slug = typeof row.slug === "string" ? row.slug : "";
const slash = slug.indexOf("/");
if (slash <= 0) continue;
const namespace = slug.slice(0, slash);
const owner = Object.entries(providers).find(([name, provider]) =>
name === namespace || provider.alias === namespace);
if (!owner) continue;
const [name, provider] = owner;
if (provider.disabled === true || namespace === COMBO_NAMESPACE || name === COMBO_NAMESPACE) {
result.set(slug, false);
continue;
}
const observed = currentModels.get(slug);
if (observed?.length) {
// The current gather re-applies captured policy hints; conflicting rows fail closed.
result.set(slug, observed.every(model =>
model.provider !== COMBO_NAMESPACE && model.nativeAlias !== true
&& model.supportsServiceTier === true && model.supportsFlexTier === true));
continue;
}
const requested = slug.slice(slash + 1);
const aliased = Object.entries(provider.modelAliases ?? {})
.filter(([, alias]) => alias === requested).map(([id]) => id);
// A non-unique short alias is not authority for a capability declaration.
if (aliased.length > 1) {
result.set(slug, false);
continue;
}
const knownIds = [
...(provider.models ?? []),
...Object.keys(provider.modelSupportsServiceTier ?? {}),
...Object.keys(provider.modelAliases ?? {}),
...Object.keys(provider.modelAdapters ?? {}),
];
const modelId = aliased[0] ?? decodeRoutedModelId(requested, knownIds);
result.set(slug, supportsConfiguredFlex(fastPolicyForModel(provider, modelId, name)));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
return result;
}
10 changes: 8 additions & 2 deletions src/codex/catalog/effort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ export function applyCatalogModelMetadata(entry: RawEntry, model?: CatalogModel)
entry.supports_reasoning_summaries = model.supportsReasoningSummaries;
}
if (model.supportsServiceTier === true) {
const eligibleRoutedFlex = model.supportsFlexTier === true
&& model.provider !== COMBO_NAMESPACE && model.nativeAlias !== true;
const nativeFast = model.codexForwardNativeCapabilityAlias && Array.isArray(entry.service_tiers)
? entry.service_tiers.find(tier => tier?.id === "priority")
: undefined;
Expand All @@ -192,8 +194,12 @@ export function applyCatalogModelMetadata(entry: RawEntry, model?: CatalogModel)
id: "priority",
name: "Fast",
description: model.fastTierDescription ?? nativeFast?.description ?? "1.5x speed, increased usage",
}];
entry.additional_speed_tiers = ["fast"];
}, ...(eligibleRoutedFlex ? [{
id: "flex",
name: "Flex",
description: "Capacity-dependent processing; availability and latency may vary",
}] : [])];
entry.additional_speed_tiers = eligibleRoutedFlex ? ["fast", "flex"] : ["fast"];
}
stampCapabilityProvenance(entry, model);
}
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;
/** Explicitly verified exact Flex wire and foreign caller-tier forwarding on this route. */
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 { configuredUltraFastSlugs, retainedFlexPolicyBySlug } 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),
retainedFlexPolicyBySlug: retainedFlexPolicyBySlug(config.providers ?? {}, catalogModelsForMerge, 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 { configuredUltraFastSlugs, retainedFlexPolicyBySlug } 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),
retainedFlexPolicyBySlug: retainedFlexPolicyBySlug(config.providers ?? {}, catalogModels, ordered),
modelPickerOrder,
accountSelectors,
catalogModels,
Expand Down
2 changes: 1 addition & 1 deletion tests/codex-integration/codex-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2043,7 +2043,7 @@ describe("provider discovered model display names", () => {
maxOutputTokens: 500_000,
// Classified by the 2026-09-13 OAuth-lane probe: grok-4.6 is Fast-eligible on
// either xAI transport, so the hint fills the capability and its description.
supportsServiceTier: true, supportsUltraFastTier: false,
supportsServiceTier: true, supportsUltraFastTier: false, supportsFlexTier: false,
fastTierDescription: "Priority processing; tier pricing applies on key auth only",
});
expect(catalogModelSlug(output)).toBe("xai/grok-4.6");
Expand Down
Loading
Loading