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
10 changes: 10 additions & 0 deletions docs-site/src/content/docs/reference/configuration/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ Routing turns the model id sent by a client into one concrete provider and upstr
| `combos?` | `Record<string, OcxComboConfig>` | `{}` | Virtual `combo/<id>` models built from ordered provider/model targets. |
| `routingProfiles?` | `Record<string, OcxRoutingProfileConfig>` | `{}` | Virtual `policy/<id>` models that select among an explicit candidate allowlist using hard capability requirements and deterministic scoring. |

### Model alias discovery

Set `preferModelAliasIds: true` to publish configured or enabled built-in model aliases
as the `id` in `GET /v1/models`. The default is `false`. Promoted rows retain their
previous qualified ID in `native_id` and keep `alias_of`; both IDs remain routable.
Models without aliases keep their existing IDs. If a short alias is ambiguous across
providers or is shadowed by another route, the row keeps its qualified ID so selecting
it still reaches the intended model. The qualified ID also stays when a built-in short
alias would produce an unparseable synthetic Fast selector. Codex's own model catalog is unchanged.

### Codex Pool low-quota protection

`codexPool.lowQuotaProtection` applies only to stored Codex Pool accounts. The Desktop/main
Expand Down
2 changes: 1 addition & 1 deletion scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": 1,
"root": "tests",
"explicitShards": ["layout-clients.json"],
"explicit": { "service-log.test.ts": "service", "messages-native-empty-inline.test.ts": "claude-integration",
"explicit": { "prefer-model-alias-ids.test.ts": "server", "service-log.test.ts": "service", "messages-native-empty-inline.test.ts": "claude-integration",
"owner-registry-isolation.test.ts": "config",
"codex-history-pending-wal.test.ts": "codex-integration", "responses-anthropic-billing-send-budget.test.ts": "responses", "anthropic-billing-ownership.test.ts": "adapters/anthropic", "server-anthropic-billing.test.ts": "server", "server-anthropic-billing-boundaries.test.ts": "server", "router-anthropic-billing-failover.test.ts": "routing", "anthropic-billing-refusal.test.ts": "adapters/anthropic", "messages-request-id-headers.test.ts": "claude-integration", "bounded-file-read.test.ts": "lib", "responses-undeclared-tool-guard-inbound.test.ts": "responses", "explicit-empty-tool-catalog.test.ts": "responses", "explicit-empty-tool-catalog-history.test.ts": "responses",
"messages-request-id-endpoint.test.ts": "claude-integration",
Expand Down
5 changes: 5 additions & 0 deletions src/cli/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,11 @@ async function fetchExposedModelIds(live: LiveProxy, fetchFn: ExposedModelsFetch
if (id.length === 0) continue;
if (id.length > EXPOSED_MODEL_ID_MAX_LENGTH) return null;
ids.add(id);
const nativeId = (row as { native_id?: unknown }).native_id;
if (nativeId !== undefined) {
if (typeof nativeId !== "string" || nativeId.length > EXPOSED_MODEL_ID_MAX_LENGTH) return null;
if (nativeId.length > 0) ids.add(nativeId);
}
}
return ids;
} catch {
Expand Down
1 change: 1 addition & 0 deletions src/config/schema/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export const configSchema = z.object({
.catch(undefined),
defaultProvider: z.string().min(1).default("openai"),
defaultModelAliases: z.boolean().optional(),
preferModelAliasIds: z.boolean().optional().catch(false),
// Malformed hand edits disable this opt-in projection without rejecting providers.
cursorEffortRows: z.boolean().optional().catch(false),
// Fast selectors default on; malformed hand edits disable them without rejecting providers.
Expand Down
36 changes: 36 additions & 0 deletions src/server/index/model-alias-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { fastRowBases } from "../fast-row";
import { previewRouteModel } from "../../router";
import type { OcxConfig } from "../../types";

/**
* Build the alias promoter for one model listing. The Fast-base inventory walks
* the configured catalog, so it is computed at most once per listing instead of
* once per aliased row.
*/
export function modelAliasIdPromoter(config: OcxConfig) {
let isFastBase: ((id: string) => boolean) | undefined;
const fastBases = () => (isFastBase ??= fastRowBases(config));
return (provider: string, model: string, alias: string, fastEligible: boolean) =>
preferredModelAliasId(config, provider, model, alias, fastEligible, fastBases);
}

/** Promote only a short alias whose ordinary route is this exact upstream model. */
export function preferredModelAliasId(
config: OcxConfig, provider: string, model: string, alias: string, fastEligible: boolean,
fastBases: () => (id: string) => boolean = () => fastRowBases(config),
): string | undefined {
if (config.preferModelAliasIds !== true) return undefined;
// Built-in bare aliases are not stable Fast bases. Preserve the qualified ID
// when promotion would publish a synthetic sibling that ingress cannot parse.
if (config.fastRows !== false && fastEligible && !fastBases()(alias)) return undefined;
try {
// Preview cannot consume a combo's round-robin selection state. A policy/combo,
// reserved native name or another provider's native id must keep its own meaning.
const route = previewRouteModel(config, alias);
return route.routeReason === "model-alias" && route.providerName === provider && route.modelId === model
? alias : undefined;
} catch {
// An ambiguous alias stays available through the existing qualified public id.
return undefined;
}
}
10 changes: 9 additions & 1 deletion src/server/index/serve-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Server, ServerWebSocket } from "bun";
import { modelAliasIdPromoter } from "./model-alias-id";
import type { StartServerDeps } from "./startup-warnings";
import { isLinkManagementPath } from "../../link/management-routes";
import {
Expand Down Expand Up @@ -1252,6 +1253,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
effortRowKnownIds,
));
};
const promoteAliasId = modelAliasIdPromoter(config);
const routedRows = await Promise.all(uniqueCatalogModelsForRawPublicList(goOrdered)
.filter(m => listAllows(m.provider, m.id))
.map(async m => {
Expand All @@ -1269,8 +1271,11 @@ export function createServeOptions(ctx: ServeOptionsContext) {
knownModelIdsForProvider(m.provider, provider, config),
).get(m.id)
: undefined;
const effectiveId = !isCombo && !fastModelId && effective
? promoteAliasId(m.provider, m.id, effective.alias, catalogRowFastEligible(m)) ?? publicId
: publicId;
const row = {
id: publicId,
id: effectiveId,
object: "model",
created: 0,
// This endpoint is an OpenAI-compatible inbound contract. Some clients use
Expand All @@ -1279,6 +1284,9 @@ export function createServeOptions(ctx: ServeOptionsContext) {
owned_by: isCombo ? "openai" : (m.owned_by ?? m.provider),
...(isCombo ? { is_combo: true } : {}),
...(effective ? { alias_of: `${provider?.alias || m.provider}/${effective.alias}` } : {}),
// When preferModelAliasIds promotes the alias to `id`, retain the original
// provider/nativeId so clients that need the upstream identity can read it.
...(effectiveId !== publicId ? { native_id: publicId } : {}),
...grokEffortFields(m.reasoningEfforts ?? [], m.defaultReasoningEffort),
...modelCapabilityFields({
reasoningEfforts: m.reasoningEfforts,
Expand Down
15 changes: 15 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,21 @@ export interface OcxConfig {
};
/** Enable the shipped model alias patterns for providers without an override. */
defaultModelAliases?: boolean;
/**
* When `true`, the raw OpenAI-compatible `/v1/models` list publishes a safe alias (e.g.
* `SEK-glm-4.7-flash`) as the row `id` instead of `provider/nativeId`
* (e.g. `Seek/glm-4.7-flash`). The original `provider/nativeId` moves to a
* new `native_id` field so clients that need it can still read it.
*
* **Default: `false`** — the historical `provider/nativeId` form remains the
* published `id`, and the alias is in the informational `alias_of` field.
*
* Codex (`client_version`) and Anthropic-specific discovery retain their own
* identities and are unchanged. Ambiguous or shadowed short aliases retain the
* qualified ID, as do aliases with unparseable synthetic Fast siblings.
* Existing requests using `provider/nativeId` continue to work unchanged.
*/
preferModelAliasIds?: boolean;
/**
* Opt-in Cursor Private Inference compatibility rows. When true, `/v1/models`
* adds `<base-id>--<effort>` selectors for reasoning-capable model ids absent
Expand Down
2 changes: 1 addition & 1 deletion structure/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ a label edit refresh Codex output.

Raw `/v1/models` rows advertise positive safe capacity values in both Cursor's nested
`capabilities` object and top-level discovery fields used by other clients. A model with a larger
opt-in context tier uses that effective long window in both shapes; invalid values are omitted. For `github-copilot`, `modelContextTiers` raises a selected long-context window only when an exact `modelContextWindows` value supplies per-model evidence; that value wins before the provider cap, while unknown models retain observed metadata. The gather fingerprint includes the map.
opt-in context tier uses that effective long window in both shapes; invalid values are omitted. For `github-copilot`, `modelContextTiers` raises a selected long-context window only when an exact `modelContextWindows` value supplies per-model evidence; that value wins before the provider cap, while unknown models retain observed metadata. The gather fingerprint includes the map. `src/server/index/model-alias-id.ts` applies opt-in `preferModelAliasIds` to `/v1/models` only: short aliases are promoted only when a read-only route preview identifies the same upstream by `model-alias`; ambiguous or shadowed aliases and aliases with unparseable synthetic Fast siblings retain qualified IDs; Cursor Fast remaps also keep their advertised identity. Promoted rows keep their original ID in `native_id` and retain `alias_of`. Codex catalog gathering is unchanged. `src/cli/doctor.ts` includes retained `native_id` values in model-exposure checks.

Supported bare native GPT rows also consume `providers.openai.modelDisplayNames`. Retained sync
and convergence pass the same map to the observed-state merge. After native normalization and
Expand Down
2 changes: 1 addition & 1 deletion structure/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ the [source-owned credential contract](codex-home.md#orca-source-owned-account-i

## Config surface

Custom Responses providers can explicitly enable `preserveResponsesInputItemIds` and `preserveResponsesMessageMetadata`; both default off and accept only booleans. The [replay sanitization contract](transports/responses-wire-shapes.md) keeps ordinary destinations unchanged and preserves xAI custom-call ID repair. `src/config/schema/compaction-recovery.ts` strictly validates opt-in `compactionRecovery`; invalid disk values disable it with a warning, while candidate writes reject them. `src/config/schema/blocked-model-redirects.ts` applies the same read-degrade/write-reject boundary to malformed `blockedModelRedirects` maps. The [failure-only contract](transports/responses-failover.md) leaves provider identity, accounts and client compaction unchanged. Optional `desktopCompatibility: { startOnProxyStart: boolean }` likewise rejects invalid writes and degrades invalid disk values to off; it saves neither certificate consent nor correction mode. Its field-scoped dashboard writer adopts committed state into the live comparison baseline; see [Codex Desktop startup](clients/codex-desktop.md#proxy-startup-preference). `src/cli/config-command.ts` accepts one leading UTF-8 BOM when parsing validate/import input from a file or stdin. JSON syntax and schema validation still run before persistence; BOM characters inside string values remain data.
`src/config/schema/config-schema.ts` validates the optional `preferModelAliasIds` flag as a boolean; malformed disk values disable this raw-list projection without rejecting providers. It defaults off. Custom Responses providers can explicitly enable `preserveResponsesInputItemIds` and `preserveResponsesMessageMetadata`; both default off and accept only booleans. The [replay sanitization contract](transports/responses-wire-shapes.md) keeps ordinary destinations unchanged and preserves xAI custom-call ID repair. `src/config/schema/compaction-recovery.ts` strictly validates opt-in `compactionRecovery`; invalid disk values disable it with a warning, while candidate writes reject them. `src/config/schema/blocked-model-redirects.ts` applies the same read-degrade/write-reject boundary to malformed `blockedModelRedirects` maps. The [failure-only contract](transports/responses-failover.md) leaves provider identity, accounts and client compaction unchanged. Optional `desktopCompatibility: { startOnProxyStart: boolean }` likewise rejects invalid writes and degrades invalid disk values to off; it saves neither certificate consent nor correction mode. Its field-scoped dashboard writer adopts committed state into the live comparison baseline; see [Codex Desktop startup](clients/codex-desktop.md#proxy-startup-preference). `src/cli/config-command.ts` accepts one leading UTF-8 BOM when parsing validate/import input from a file or stdin. JSON syntax and schema validation still run before persistence; BOM characters inside string values remain data.

`skills.catalog_refresh` in the proxy JSON configuration accepts `per_session` (the runtime default when absent) or `per_turn`. The former retains received skills instructions for a conversation; the latter passes through the current catalog. This is separate from Codex's `skills.include_instructions` TOML switch and does not change the live dashboard probe. See the [Responses snapshot contract](transports/responses.md#responses-httpsse).

Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/test-layout-expected.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"prefer-model-alias-ids.test.ts": "server",
"messages-native-empty-inline.test.ts": "claude-integration",
"explicit-empty-tool-catalog.test.ts": "responses",
"explicit-empty-tool-catalog-history.test.ts": "responses",
Expand Down
Loading
Loading