diff --git a/CHANGELOG.md b/CHANGELOG.md index f33614c55a..702a5e6f56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - **fix(translator/claude-to-openai):** stop including `cache_creation_input_tokens` in `prompt_tokens`. Anthropic pads short prompts up to a 1024-token minimum on cache creation, so a 2-token `"hi"` could be reported as ~2008 `prompt_tokens` and inflate downstream billing (Sub2API/NewAPI/OneAPI) ~250x. `prompt_tokens` now matches the dashboard "Total In" (`input + cache_read`); `cache_creation_tokens` is exposed separately in `prompt_tokens_details.cache_creation_tokens` for auditing. (#2215) - **fix(ui/claude-extra-usage):** clarify the toggle-success notification text to spell out the toggle→effect relationship ("Claude extra-usage blocking enabled/disabled" instead of the ambiguous "blocked/allowed"). (#2157) - **fix(providers/qoder):** disambiguate the "Local CLI runtime is not installed" error when a user pastes a Personal Access Token but the connection is in OAuth/CLI-flavored mode. The test route now surfaces a single actionable message ("switch this connection to API Key auth") instead of cascading CLI + 401 errors. (#2247) +- **fix(dashboard/api-manager):** route custom OpenAI-/Anthropic-compatible provider IDs through `getProviderDisplayName` so the model grouping label shows `Compatible (openai)` instead of leaking the raw synthetic `openai-compatible-chat-` value. (#2021) ### Changed diff --git a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx index 2e28626508..24723a461e 100644 --- a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx +++ b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useMemo, useCallback, memo } from "react"; import { Card, Button, Input, Modal, CardSkeleton } from "@/shared/components"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; import { useTranslations } from "next-intl"; +import { getProviderDisplayName } from "@/lib/display/names"; // Constants for validation const MAX_KEY_NAME_LENGTH = 200; @@ -434,16 +435,19 @@ export default function ApiManagerPageClient() { // Debounced search for performance const debouncedSearchModel = useDebouncedValue(searchModel, 150); - // Group models by provider + // Group models by provider (issue #2021 — use centralized display helper so + // custom OpenAI-/Anthropic-compatible providers don't leak raw synthetic + // ids like "openai-compatible-chat-" into the grouping label) const modelsByProvider = useMemo((): ProviderGroup[] => { const grouped: Record = {}; for (const model of allModels) { - const provider = model.owned_by || t("unknownProvider"); + const provider = + getProviderDisplayName(model.owned_by) || model.owned_by || t("unknownProvider"); if (!grouped[provider]) grouped[provider] = []; grouped[provider].push(model); } return Object.entries(grouped).sort((a, b) => a[0].localeCompare(b[0])); - }, [allModels]); + }, [allModels, t]); // Filter models based on debounced search const filteredModelsByProvider = useMemo((): ProviderGroup[] => {