diff --git a/.all-contributorsrc b/.all-contributorsrc index 01713672c1..fd91ee96ea 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1272,9 +1272,18 @@ "code", "test" ] + }, + { + "login": "evan-choi", + "name": "Evan", + "avatar_url": "https://avatars.githubusercontent.com/u/9690415?v=4", + "profile": "https://github.com/evan-choi", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, "linkToUsage": false, "commitType": "docs" -} \ No newline at end of file +} diff --git a/README.md b/README.md index 21f2305e00..7c057f0324 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/e Kevin Lin
Kevin Lin

💻 ⚠️ Borealin
Borealin

💻 ⚠️ BrenticusMaximus
BrenticusMaximus

💻 ⚠️ + Sakthimaran
Sakthimaran

💻 ⚠️ + Evan
Evan

💻 diff --git a/app/modules/api_keys/schemas.py b/app/modules/api_keys/schemas.py index 6947c3dfc0..10aa0dfe10 100644 --- a/app/modules/api_keys/schemas.py +++ b/app/modules/api_keys/schemas.py @@ -33,7 +33,7 @@ class ApiKeyCreateRequest(DashboardModel): default=None, pattern=r"(?i)^(none|minimal|low|medium|high|xhigh|max|ultra)$" ) allowed_reasoning_efforts: list[str] | None = None - enforced_service_tier: str | None = Field(default=None, pattern=r"(?i)^(auto|default|priority|flex|fast)$") + enforced_service_tier: str | None = Field(default=None, pattern=r"(?i)^(auto|default|priority|flex|(ultra)?fast)$") traffic_class: str | None = Field(default=None, pattern=r"(?i)^(foreground|opportunistic)$") transport_policy_override: str | None = None usage_sections: str | None = None @@ -53,7 +53,7 @@ class ApiKeyUpdateRequest(DashboardModel): default=None, pattern=r"(?i)^(none|minimal|low|medium|high|xhigh|max|ultra)$" ) allowed_reasoning_efforts: list[str] | None = None - enforced_service_tier: str | None = Field(default=None, pattern=r"(?i)^(auto|default|priority|flex|fast)$") + enforced_service_tier: str | None = Field(default=None, pattern=r"(?i)^(auto|default|priority|flex|(ultra)?fast)$") traffic_class: str | None = Field(default=None, pattern=r"(?i)^(foreground|opportunistic)$") transport_policy_override: str | None = None usage_sections: str | None = None diff --git a/app/modules/api_keys/service.py b/app/modules/api_keys/service.py index b6e3ca8f1a..a68bce8db0 100644 --- a/app/modules/api_keys/service.py +++ b/app/modules/api_keys/service.py @@ -1461,7 +1461,7 @@ def _normalize_model_slug(value: str | None) -> str | None: _REASONING_EFFORT_ORDER = ("minimal", "low", "medium", "high", "xhigh", "max", "ultra") _SUPPORTED_REASONING_EFFORTS = frozenset({"none", *_REASONING_EFFORT_ORDER}) _SUPPORTED_SELECTABLE_REASONING_EFFORTS = frozenset(_REASONING_EFFORT_ORDER) -_SUPPORTED_SERVICE_TIERS = frozenset({"auto", "default", "priority", "flex"}) +_SUPPORTED_SERVICE_TIERS = frozenset({"auto", "default", "priority", "flex", "ultrafast"}) def _normalize_expires_at(value: datetime | None) -> datetime | None: diff --git a/docs/screenshots/api-key-ultrafast-after.jpg b/docs/screenshots/api-key-ultrafast-after.jpg new file mode 100644 index 0000000000..e9c89139dc Binary files /dev/null and b/docs/screenshots/api-key-ultrafast-after.jpg differ diff --git a/docs/screenshots/api-key-ultrafast-before.jpg b/docs/screenshots/api-key-ultrafast-before.jpg new file mode 100644 index 0000000000..54ecb306f2 Binary files /dev/null and b/docs/screenshots/api-key-ultrafast-before.jpg differ diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 80121d75ae..bec37fc76d 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -11,9 +11,9 @@ codex-lb refreshes usage on its own schedule and treats upstream samples conserv **Codex CLI falls back to POST instead of WebSockets.** Run the [WebSocket verification steps](client-setup.md#verify-websocket-transport). If codex-lb sits behind a reverse proxy, make sure it forwards WebSocket upgrades — see [Remote Access](deployment/remote.md). -## Fast Mode and service tiers +## Fast Mode, Ultrafast, and service tiers -Fast Mode and service-tier behavior is documented in the +Fast Mode, Ultrafast, and service-tier behavior is documented in the [Responses API compatibility context](https://github.com/Soju06/codex-lb/blob/main/openspec/specs/responses-api-compat/context.md#fast-mode-and-service-tiers). ## Old Codex sessions missing after migrating diff --git a/frontend/src/features/api-keys/components/api-key-create-dialog.test.tsx b/frontend/src/features/api-keys/components/api-key-create-dialog.test.tsx index af78496569..8a056a2d21 100644 --- a/frontend/src/features/api-keys/components/api-key-create-dialog.test.tsx +++ b/frontend/src/features/api-keys/components/api-key-create-dialog.test.tsx @@ -81,6 +81,31 @@ describe("ApiKeyCreateDialog", () => { expect(onSubmit.mock.calls[0][0].trafficClass).toBe("opportunistic"); }); + it("submits Ultrafast service tier", async () => { + const user = userEvent.setup(); + const onSubmit = vi.fn().mockResolvedValue(undefined); + + renderWithProviders( + , + ); + + await user.type(screen.getByLabelText("Name"), "Ultrafast key"); + await user.click(screen.getByRole("combobox", { name: /enforced service tier/i })); + await user.click(await screen.findByRole("option", { name: "Ultrafast" })); + await user.click(screen.getByRole("button", { name: "Create" })); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledTimes(1); + }); + + expect(onSubmit.mock.calls[0][0].enforcedServiceTier).toBe("ultrafast"); + }); + it("renders and submits a transport policy override", async () => { const user = userEvent.setup(); const onSubmit = vi.fn().mockResolvedValue(undefined); diff --git a/frontend/src/features/api-keys/components/api-key-create-dialog.tsx b/frontend/src/features/api-keys/components/api-key-create-dialog.tsx index e6589988dc..c4376d00b7 100644 --- a/frontend/src/features/api-keys/components/api-key-create-dialog.tsx +++ b/frontend/src/features/api-keys/components/api-key-create-dialog.tsx @@ -260,6 +260,7 @@ function ApiKeyCreateForm({ busy, onClose, onSubmit }: ApiKeyCreateFormProps) { {t("common.serviceTier.default")} {t("common.serviceTier.priority")} {t("common.serviceTier.flex")} + {t("common.serviceTier.ultrafast")} diff --git a/frontend/src/features/api-keys/components/api-key-edit-dialog.test.tsx b/frontend/src/features/api-keys/components/api-key-edit-dialog.test.tsx index bc8ddd7929..df842ed191 100644 --- a/frontend/src/features/api-keys/components/api-key-edit-dialog.test.tsx +++ b/frontend/src/features/api-keys/components/api-key-edit-dialog.test.tsx @@ -527,6 +527,20 @@ describe("ApiKeyEditDialog", () => { const trafficClassSelect = screen.getByRole("combobox", { name: /traffic class/i }); expect(trafficClassSelect).toHaveTextContent("Opportunistic"); }); + + it("shows the stored Ultrafast service tier", () => { + renderWithProviders( + , + ); + + expect(screen.getByRole("combobox", { name: /enforced service tier/i })).toHaveTextContent("Ultrafast"); + }); }); describe("hasLimitRuleChanges", () => { diff --git a/frontend/src/features/api-keys/components/api-key-edit-dialog.tsx b/frontend/src/features/api-keys/components/api-key-edit-dialog.tsx index 3885d2d15c..f405931770 100644 --- a/frontend/src/features/api-keys/components/api-key-edit-dialog.tsx +++ b/frontend/src/features/api-keys/components/api-key-edit-dialog.tsx @@ -320,9 +320,11 @@ function ApiKeyEditForm({ apiKey, busy, onSubmit, onClose }: ApiKeyEditFormProps
-
{t("apiKeys.form.enforcedServiceTier")}
+
diff --git a/frontend/src/features/api-keys/schemas.test.ts b/frontend/src/features/api-keys/schemas.test.ts index b87465d201..10b77f3870 100644 --- a/frontend/src/features/api-keys/schemas.test.ts +++ b/frontend/src/features/api-keys/schemas.test.ts @@ -189,6 +189,15 @@ describe("ApiKeyCreateRequestSchema", () => { expect(parsed.enforcedReasoningEffort).toBe("ultra"); }); + it("accepts Ultrafast service tier in create payload", () => { + const parsed = ApiKeyCreateRequestSchema.parse({ + name: "Ultrafast key", + enforcedServiceTier: "ultrafast", + }); + + expect(parsed.enforcedServiceTier).toBe("ultrafast"); + }); + it("accepts a non-empty allowed reasoning effort list", () => { const parsed = ApiKeyCreateRequestSchema.parse({ name: "Selectable reasoning key", @@ -277,6 +286,14 @@ describe("ApiKeyUpdateRequestSchema", () => { expect(parsed.trafficClass).toBe("opportunistic"); }); + + it("accepts Ultrafast service tier in update payload", () => { + const parsed = ApiKeyUpdateRequestSchema.parse({ + enforcedServiceTier: "ultrafast", + }); + + expect(parsed.enforcedServiceTier).toBe("ultrafast"); + }); }); describe("LimitRuleCreateSchema", () => { diff --git a/frontend/src/features/api-keys/schemas.ts b/frontend/src/features/api-keys/schemas.ts index a192e37864..2bbd8b91b0 100644 --- a/frontend/src/features/api-keys/schemas.ts +++ b/frontend/src/features/api-keys/schemas.ts @@ -30,7 +30,7 @@ const ApiKeyUsageSummarySchema = z.object({ totalCostUsd: z.number().nonnegative().default(0), }); -const SERVICE_TIERS = ["auto", "default", "priority", "flex"] as const; +const SERVICE_TIERS = ["auto", "default", "priority", "flex", "ultrafast"] as const; export type ServiceTierType = (typeof SERVICE_TIERS)[number]; export const TRAFFIC_CLASSES = ["foreground", "opportunistic"] as const; diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 518306ccf3..41a81f801d 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -559,6 +559,7 @@ "common.serviceTier.default": "Default", "common.serviceTier.flex": "Flex", "common.serviceTier.priority": "Priority", + "common.serviceTier.ultrafast": "Ultrafast", "common.states.active": "Active", "common.states.disabled": "Disabled", "common.states.enabled": "Enabled", diff --git a/frontend/src/i18n/locales/ko.json b/frontend/src/i18n/locales/ko.json index adac30f799..b5d482b8c9 100644 --- a/frontend/src/i18n/locales/ko.json +++ b/frontend/src/i18n/locales/ko.json @@ -559,6 +559,7 @@ "common.serviceTier.default": "Default", "common.serviceTier.flex": "Flex", "common.serviceTier.priority": "Priority", + "common.serviceTier.ultrafast": "Ultrafast", "common.states.active": "활성", "common.states.disabled": "꺼짐", "common.states.enabled": "켜짐", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index e76c857631..60d781635a 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -559,6 +559,7 @@ "common.serviceTier.default": "默认", "common.serviceTier.flex": "Flex", "common.serviceTier.priority": "优先", + "common.serviceTier.ultrafast": "Ultrafast", "common.states.active": "活跃", "common.states.disabled": "已禁用", "common.states.enabled": "已启用", diff --git a/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/.openspec.yaml b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/.openspec.yaml new file mode 100644 index 0000000000..4af864176c --- /dev/null +++ b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-14 diff --git a/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/design.md b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/design.md new file mode 100644 index 0000000000..8edd2e5838 --- /dev/null +++ b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/design.md @@ -0,0 +1,35 @@ +## Context + +The Responses request models, upstream transports, request logs, and model registry already carry service tiers as normalized strings. They therefore preserve `ultrafast` without a transport change and can route it using live per-account catalog metadata. The remaining hard-coded allowlists are the API-key CRUD contract and dashboard controls. + +OpenAI documents `ultrafast` as an access-controlled processing tier currently available for `gpt-5.6-sol`. Entitlement must therefore come from each account's live upstream catalog instead of a static plan or bootstrap assumption. + +## Goals / Non-Goals + +**Goals:** + +- Make `ultrafast` a supported canonical API-key service tier. +- Expose the tier through the existing dashboard API-key controls. +- Preserve existing entitlement-aware account routing and response-tier logging. +- Add focused regression coverage and user-facing compatibility notes. + +**Non-Goals:** + +- Invent an `ultrafast` model-name alias. +- Advertise Ultrafast from bootstrap metadata or grant it to a plan statically. +- Add a setting, dependency, or database migration. +- Guess a distinct Ultrafast token price that OpenAI has not published. + +## Decisions + +1. Add `ultrafast` only to the existing backend and frontend API-key tier allowlists. The request models and transports already pass it through, so adding another normalization layer would duplicate working behavior. +2. Keep `ultrafast` canonical. Unlike the legacy `fast` alias, it is an upstream wire value and must not normalize to `priority`. +3. Reuse live model-catalog routing. An explicit or enforced Ultrafast request can select only accounts whose catalog advertises that tier; the existing enforced-tier fallback still removes it for models that do not advertise it. +4. Do not add Ultrafast to the bundled model catalog. Static metadata cannot prove access to an access-controlled preview and would expose a tier that an imported account may not hold. +5. Keep pricing unchanged. No distinct public Ultrafast token price is available in the official OpenAI documentation, so this change does not introduce a speculative multiplier. + +## Risks / Trade-offs + +- [An entitled account's catalog does not advertise `ultrafast`] → The existing explicit-tier routing error remains visible instead of silently selecting an ineligible account. +- [OpenAI later publishes distinct Ultrafast pricing] → Add the published rates in a focused pricing change before claiming separate cost accuracy. +- [Dashboard-visible option requires review evidence] → Include before and after screenshots in the PR body as required by the simplicity gates. diff --git a/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/proposal.md b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/proposal.md new file mode 100644 index 0000000000..cc3ec42afe --- /dev/null +++ b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/proposal.md @@ -0,0 +1,27 @@ +## Why + +OpenAI introduced an access-controlled Ultrafast processing tier for `gpt-5.6-sol`. codex-lb already preserves unknown request tier strings, but its API-key policy and dashboard reject `ultrafast`, leaving the feature incomplete and untested. + +## What Changes + +- Accept and persist `ultrafast` as an API-key-enforced service tier. +- Expose Ultrafast in the API key create and edit controls. +- Preserve and forward the canonical `ultrafast` value through Responses-compatible routes. +- Use live upstream model-catalog entitlement data to route Ultrafast requests only to advertising accounts. +- Document the upstream availability constraint and add focused regression coverage. + +## Capabilities + +### New Capabilities + +None. + +### Modified Capabilities + +- `api-keys`: allow dashboard API keys to enforce the canonical `ultrafast` tier. +- `responses-api-compat`: define pass-through behavior for explicit and enforced Ultrafast requests. +- `model-catalog-compat`: define entitlement-aware account routing for the access-controlled tier. + +## Impact + +The change affects API-key request validation and normalization, dashboard API-key forms and translations, Responses compatibility documentation, model-catalog routing tests, and focused backend/frontend tests. It adds no dependency, setting, database migration, or bootstrap entitlement metadata. diff --git a/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/api-keys/spec.md b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/api-keys/spec.md new file mode 100644 index 0000000000..591b8408d6 --- /dev/null +++ b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/api-keys/spec.md @@ -0,0 +1,17 @@ +## ADDED Requirements + +### Requirement: API keys can enforce the Ultrafast service tier + +The dashboard API key CRUD surface MUST accept and persist `ultrafast` as a canonical enforced service tier. The service MUST return the same canonical value and MUST NOT normalize it to `priority`. + +#### Scenario: Create an API key with Ultrafast enforcement + +- **WHEN** a dashboard client creates an API key with `enforcedServiceTier: "ultrafast"` +- **THEN** the request is accepted +- **AND** the persisted and returned enforced service tier is `ultrafast` + +#### Scenario: Enforce Ultrafast on an advertising model + +- **GIVEN** an account model advertises the `ultrafast` service tier +- **WHEN** a request uses an API key whose enforced service tier is `ultrafast` +- **THEN** the upstream request carries `service_tier: "ultrafast"` diff --git a/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/model-catalog-compat/spec.md b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/model-catalog-compat/spec.md new file mode 100644 index 0000000000..b69a2ced33 --- /dev/null +++ b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/model-catalog-compat/spec.md @@ -0,0 +1,17 @@ +## ADDED Requirements + +### Requirement: Ultrafast routing follows live account entitlement + +The system MUST treat `ultrafast` as an access-controlled service tier and MUST derive account eligibility from live or retained per-account upstream catalog metadata. The bundled bootstrap catalog MUST NOT invent Ultrafast entitlement. + +#### Scenario: Only an advertising account is eligible + +- **GIVEN** two accounts advertise `gpt-5.6-sol` +- **AND** only one account advertises the `ultrafast` service tier +- **WHEN** a request explicitly asks for `service_tier: "ultrafast"` +- **THEN** account selection considers only the advertising account + +#### Scenario: Bootstrap metadata does not grant preview access + +- **WHEN** no live or retained account catalog advertises `ultrafast` +- **THEN** bootstrap model metadata does not expose or grant that tier diff --git a/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/responses-api-compat/spec.md b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/responses-api-compat/spec.md new file mode 100644 index 0000000000..c2bace9663 --- /dev/null +++ b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/specs/responses-api-compat/spec.md @@ -0,0 +1,15 @@ +## ADDED Requirements + +### Requirement: Responses routes preserve the Ultrafast service tier + +Responses-compatible routes MUST accept the canonical `ultrafast` service tier and MUST forward it unchanged. When upstream reports the actual response tier, request logging MUST preserve `ultrafast` using the existing requested, actual, and billable tier contract. + +#### Scenario: Explicit Ultrafast request is forwarded + +- **WHEN** a client sends a Responses request with `service_tier: "ultrafast"` +- **THEN** the forwarded upstream payload contains `service_tier: "ultrafast"` + +#### Scenario: Upstream confirms Ultrafast processing + +- **WHEN** upstream completes a request with `response.service_tier: "ultrafast"` +- **THEN** the actual and billable request-log tiers are `ultrafast` diff --git a/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/tasks.md b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/tasks.md new file mode 100644 index 0000000000..bf283c6da4 --- /dev/null +++ b/openspec/changes/archive/2026-08-14-support-ultrafast-service-tier/tasks.md @@ -0,0 +1,15 @@ +## 1. Backend support + +- [x] 1.1 Accept and persist canonical `ultrafast` API-key enforcement values +- [x] 1.2 Add focused request, API-key, catalog-routing, and logging regression coverage + +## 2. Dashboard and documentation + +- [x] 2.1 Add Ultrafast to dashboard schemas, create/edit controls, and translations +- [x] 2.2 Add frontend schema and interaction coverage for the new option +- [x] 2.3 Document official availability, entitlement behavior, and a concrete request example + +## 3. Verification + +- [x] 3.1 Validate OpenSpec artifacts and run focused backend/frontend checks +- [x] 3.2 Run the repository local CI gate and capture dashboard before/after evidence diff --git a/openspec/specs/api-keys/spec.md b/openspec/specs/api-keys/spec.md index 2127be5b61..274490f613 100644 --- a/openspec/specs/api-keys/spec.md +++ b/openspec/specs/api-keys/spec.md @@ -1403,3 +1403,19 @@ reclamation does. - **WHEN** stale usage-reservation reclamation runs - **THEN** the reservation stays `reserved` +### Requirement: API keys can enforce the Ultrafast service tier + +The dashboard API key CRUD surface MUST accept and persist `ultrafast` as a canonical enforced service tier. The service MUST return the same canonical value and MUST NOT normalize it to `priority`. + +#### Scenario: Create an API key with Ultrafast enforcement + +- **WHEN** a dashboard client creates an API key with `enforcedServiceTier: "ultrafast"` +- **THEN** the request is accepted +- **AND** the persisted and returned enforced service tier is `ultrafast` + +#### Scenario: Enforce Ultrafast on an advertising model + +- **GIVEN** an account model advertises the `ultrafast` service tier +- **WHEN** a request uses an API key whose enforced service tier is `ultrafast` +- **THEN** the upstream request carries `service_tier: "ultrafast"` + diff --git a/openspec/specs/model-catalog-compat/spec.md b/openspec/specs/model-catalog-compat/spec.md index 8cbc2cc3ba..4c5141e9c7 100644 --- a/openspec/specs/model-catalog-compat/spec.md +++ b/openspec/specs/model-catalog-compat/spec.md @@ -1057,3 +1057,18 @@ The model catalog builders for `GET /v1/models` and `GET /backend-api/codex/mode acquisition - **THEN** the reservation is released before cancellation propagates +### Requirement: Ultrafast routing follows live account entitlement + +The system MUST treat `ultrafast` as an access-controlled service tier and MUST derive account eligibility from live or retained per-account upstream catalog metadata. The bundled bootstrap catalog MUST NOT invent Ultrafast entitlement. + +#### Scenario: Only an advertising account is eligible + +- **GIVEN** two accounts advertise `gpt-5.6-sol` +- **AND** only one account advertises the `ultrafast` service tier +- **WHEN** a request explicitly asks for `service_tier: "ultrafast"` +- **THEN** account selection considers only the advertising account + +#### Scenario: Bootstrap metadata does not grant preview access + +- **WHEN** no live or retained account catalog advertises `ultrafast` +- **THEN** bootstrap model metadata does not expose or grant that tier diff --git a/openspec/specs/responses-api-compat/context.md b/openspec/specs/responses-api-compat/context.md index 6f96c7ccc7..4a82968a8e 100644 --- a/openspec/specs/responses-api-compat/context.md +++ b/openspec/specs/responses-api-compat/context.md @@ -75,6 +75,33 @@ Responses request with: Clients that expose Fast Mode as `fast` may keep using that spelling; codex-lb normalizes it to `priority` before forwarding. +### Ultrafast Processing + +The [OpenAI Responses API reference](https://developers.openai.com/api/reference/resources/responses/methods/create) +documents `ultrafast` as an access-controlled processing tier currently +available for `gpt-5.6-sol`. codex-lb forwards this canonical value unchanged; +it does not grant Ultrafast access by itself. + +Account eligibility comes from live or retained per-account upstream catalog +metadata. The bundled bootstrap catalog deliberately does not advertise +Ultrafast. If no account advertises the tier, an explicit Ultrafast request +cannot select an eligible account; API-key enforcement follows the existing +model-capability fallback when the model itself does not advertise the tier. + +Send a Responses request with: + +```json +{ + "model": "gpt-5.6-sol", + "input": "Summarize the change.", + "service_tier": "ultrafast" +} +``` + +After completion, verify that the response reports +`service_tier: "ultrafast"`. Request logs retain `ultrafast` in the requested, +actual, and effective billable tier fields when upstream confirms it. + ### Operator Fast Mode prohibition Operators can enable the Routing setting `prohibitFastMode` when qualified diff --git a/openspec/specs/responses-api-compat/spec.md b/openspec/specs/responses-api-compat/spec.md index a580853043..a56410d4de 100644 --- a/openspec/specs/responses-api-compat/spec.md +++ b/openspec/specs/responses-api-compat/spec.md @@ -5299,3 +5299,16 @@ only an inactive `unknown` operation may enter a fresh recovery attempt. - **WHEN** a duplicate request finds a submitted operation still referenced by another pending request - **THEN** the proxy refuses a second dispatch and preserves the existing spool +### Requirement: Responses routes preserve the Ultrafast service tier + +Responses-compatible routes MUST accept the canonical `ultrafast` service tier and MUST forward it unchanged. When upstream reports the actual response tier, request logging MUST preserve `ultrafast` using the existing requested, actual, and billable tier contract. + +#### Scenario: Explicit Ultrafast request is forwarded + +- **WHEN** a client sends a Responses request with `service_tier: "ultrafast"` +- **THEN** the forwarded upstream payload contains `service_tier: "ultrafast"` + +#### Scenario: Upstream confirms Ultrafast processing + +- **WHEN** upstream completes a request with `response.service_tier: "ultrafast"` +- **THEN** the actual and billable request-log tiers are `ultrafast` diff --git a/tests/integration/test_api_keys_api.py b/tests/integration/test_api_keys_api.py index 2b976c34e3..4508b5dbe0 100644 --- a/tests/integration/test_api_keys_api.py +++ b/tests/integration/test_api_keys_api.py @@ -4,6 +4,7 @@ import base64 import contextlib import json +from dataclasses import replace from datetime import timedelta from types import SimpleNamespace from typing import cast @@ -778,10 +779,14 @@ async def fake_stream(payload, _headers, _access_token, _account_id, base_url=No @pytest.mark.asyncio -async def test_api_key_enforces_service_tier_for_responses(async_client, monkeypatch): - await _populate_test_registry() - model_ids = sorted(_TEST_MODELS) - forced_model = model_ids[0] +@pytest.mark.parametrize( + ("enforced_service_tier", "expected_service_tier"), + [("fast", "priority"), ("ULTRAFAST", "ultrafast")], +) +async def test_api_key_enforces_service_tier_for_responses( + async_client, monkeypatch, enforced_service_tier, expected_service_tier +): + forced_model = "gpt-5.6-sol" enable = await async_client.put( "/api/settings", @@ -794,27 +799,47 @@ async def test_api_key_enforces_service_tier_for_responses(async_client, monkeyp ) assert enable.status_code == 200 + account_id = await _import_account( + async_client, + f"acc_enforced_{expected_service_tier}_service_tier", + f"enforced-{expected_service_tier}-service-tier@example.com", + ) + advertising_model = replace( + _make_upstream_model(forced_model), + raw={"service_tiers": [{"slug": expected_service_tier}]}, + ) + await get_model_registry().update( + {"pro": [advertising_model]}, + per_account_results={account_id: ("pro", [advertising_model])}, + active_account_plans={account_id: "pro"}, + ) + created = await async_client.post( "/api/api-keys/", json={ "name": "enforced-service-tier", "allowedModels": [forced_model], "enforcedModel": forced_model, - "enforcedServiceTier": "fast", + "enforcedServiceTier": enforced_service_tier, }, ) assert created.status_code == 200 key = created.json()["key"] - assert created.json()["enforcedServiceTier"] == "priority" - - await _import_account(async_client, "acc_enforced_service_tier", "enforced-service-tier@example.com") + assert created.json()["enforcedServiceTier"] == expected_service_tier seen: dict[str, str | None] = {} async def fake_stream(payload, _headers, _access_token, _account_id, base_url=None, raise_for_status=False): seen["service_tier"] = payload.service_tier usage = {"input_tokens": 3, "output_tokens": 2} - event = {"type": "response.completed", "response": {"id": "resp_enforced_service_tier", "usage": usage}} + event = { + "type": "response.completed", + "response": { + "id": "resp_enforced_service_tier", + "service_tier": expected_service_tier, + "usage": usage, + }, + } yield f"data: {json.dumps(event)}\n\n" monkeypatch.setattr(proxy_module, "core_stream_responses", fake_stream) @@ -834,7 +859,15 @@ async def fake_stream(payload, _headers, _access_token, _account_id, base_url=No assert response.status_code == 200 _ = [line async for line in response.aiter_lines() if line] - assert seen["service_tier"] == "priority" + assert seen["service_tier"] == expected_service_tier + + async with SessionLocal() as session: + result = await session.execute(select(RequestLog).order_by(RequestLog.requested_at.desc())) + latest_log = result.scalars().first() + assert latest_log is not None + assert latest_log.requested_service_tier == expected_service_tier + assert latest_log.actual_service_tier == expected_service_tier + assert latest_log.service_tier == expected_service_tier @pytest.mark.asyncio diff --git a/tests/unit/test_api_keys_service.py b/tests/unit/test_api_keys_service.py index 63fc9d9e1d..9a33fd47f1 100644 --- a/tests/unit/test_api_keys_service.py +++ b/tests/unit/test_api_keys_service.py @@ -848,6 +848,23 @@ async def test_create_key_normalizes_fast_service_tier_alias() -> None: assert created.enforced_service_tier == "priority" +@pytest.mark.asyncio +async def test_create_key_preserves_ultrafast_service_tier() -> None: + repo = _FakeApiKeysRepository() + service = ApiKeysService(repo) + + created = await service.create_key( + ApiKeyCreateData( + name="ultrafast-service-tier-policy", + allowed_models=None, + enforced_service_tier=" ULTRAFAST ", + expires_at=None, + ) + ) + + assert created.enforced_service_tier == "ultrafast" + + @pytest.mark.asyncio async def test_update_key_normalizes_service_tier_alias() -> None: repo = _FakeApiKeysRepository() diff --git a/tests/unit/test_model_registry.py b/tests/unit/test_model_registry.py index 2cc88b077e..fa3ed54477 100644 --- a/tests/unit/test_model_registry.py +++ b/tests/unit/test_model_registry.py @@ -253,6 +253,7 @@ def test_bootstrap_models_include_representative_upstream_metadata(): "ultra", ] assert sol.raw["additional_speed_tiers"] == ["fast"] + assert "ultrafast" not in str(sol.raw["service_tiers"]) terra = models["gpt-5.6-terra"] assert terra.display_name == "GPT-5.6-Terra" diff --git a/tests/unit/test_openai_requests.py b/tests/unit/test_openai_requests.py index 36d4e91af9..04e8cd6e60 100644 --- a/tests/unit/test_openai_requests.py +++ b/tests/unit/test_openai_requests.py @@ -175,17 +175,18 @@ def test_strip_unsupported_fields_namespace_flag_controls_replayed_calls(namespa assert stripped["input"] == [{"type": "function_call"}] -def test_responses_preserves_service_tier(): +@pytest.mark.parametrize("service_tier", ["priority", "ultrafast"]) +def test_responses_preserves_service_tier(service_tier: str): payload = { "model": "gpt-5.1", "instructions": "hi", "input": [], - "service_tier": "priority", + "service_tier": service_tier, } request = ResponsesRequest.model_validate(payload) dumped = request.to_payload() - assert dumped["service_tier"] == "priority" + assert dumped["service_tier"] == service_tier def test_responses_normalizes_fast_service_tier_to_priority_for_upstream(): @@ -579,16 +580,17 @@ def test_openai_compatible_top_level_verbosity_is_normalized(): assert "verbosity" not in dumped -def test_v1_responses_preserves_service_tier(): +@pytest.mark.parametrize("service_tier", ["priority", "ultrafast"]) +def test_v1_responses_preserves_service_tier(service_tier: str): payload = { "model": "gpt-5.1", "input": "hello", - "service_tier": "priority", + "service_tier": service_tier, } request = V1ResponsesRequest.model_validate(payload).to_responses_request() dumped = request.to_payload() - assert dumped["service_tier"] == "priority" + assert dumped["service_tier"] == service_tier def test_v1_responses_normalizes_fast_service_tier_to_priority_for_upstream(): diff --git a/tests/unit/test_proxy_load_balancer_refresh.py b/tests/unit/test_proxy_load_balancer_refresh.py index f82d9b5fa8..6166c2e824 100644 --- a/tests/unit/test_proxy_load_balancer_refresh.py +++ b/tests/unit/test_proxy_load_balancer_refresh.py @@ -1237,26 +1237,26 @@ async def test_select_account_filters_requested_service_tier_plans(monkeypatch) @pytest.mark.asyncio async def test_select_account_filters_requested_service_tier_accounts(monkeypatch) -> None: - no_fast = _make_account("acc-tier-pro-default", "tier-pro-default@example.com") - no_fast.plan_type = "pro" - fast = _make_account("acc-tier-pro-fast", "tier-pro-fast@example.com") - fast.plan_type = "pro" + no_ultrafast = _make_account("acc-tier-pro-default", "tier-pro-default@example.com") + no_ultrafast.plan_type = "pro" + ultrafast = _make_account("acc-tier-pro-ultrafast", "tier-pro-ultrafast@example.com") + ultrafast.plan_type = "pro" now = utcnow() now_epoch = int(now.replace(tzinfo=timezone.utc).timestamp()) usage_repo = StubUsageRepository( primary={ - no_fast.id: UsageHistory( + no_ultrafast.id: UsageHistory( id=63, - account_id=no_fast.id, + account_id=no_ultrafast.id, recorded_at=now, window="primary", used_percent=1.0, reset_at=now_epoch + 300, window_minutes=5, ), - fast.id: UsageHistory( + ultrafast.id: UsageHistory( id=64, - account_id=fast.id, + account_id=ultrafast.id, recorded_at=now, window="primary", used_percent=2.0, @@ -1272,7 +1272,7 @@ async def test_select_account_filters_requested_service_tier_accounts(monkeypatc lambda: SimpleNamespace( plan_types_for_model=lambda _model: frozenset({"pro"}), account_ids_for_model_service_tier=lambda _model, tier: ( - frozenset({fast.id}) if tier == "priority" else None + frozenset({ultrafast.id}) if tier == "ultrafast" else None ), plan_types_for_model_service_tier=lambda _model, _tier: frozenset({"pro"}), ), @@ -1280,15 +1280,15 @@ async def test_select_account_filters_requested_service_tier_accounts(monkeypatc balancer = LoadBalancer( lambda: _repo_factory( - StubAccountsRepository([no_fast, fast]), + StubAccountsRepository([no_ultrafast, ultrafast]), usage_repo, StubStickySessionsRepository(), ) ) - selection = await balancer.select_account(model="gpt-5.5", service_tier="priority") + selection = await balancer.select_account(model="gpt-5.6-sol", service_tier="ultrafast") assert selection.account is not None - assert selection.account.id == fast.id + assert selection.account.id == ultrafast.id @pytest.mark.asyncio