fix(providers): refresh GitHub Copilot catalog (#6154)

* fix(providers): refresh github copilot catalog

Limit GitHub Copilot discovery to the curated supported model set and keep the provider cooldown panel client-safe by moving countdown formatting out of localDb.

* chore(quality): rebaseline providerPageHelpers.ts file-size (+13, #6154 copilot catalog)

The GitHub Copilot catalog refresh grows the provider-page model-section helper
(1021->1034). Fast-path PR->release skips check:file-size, so the bump lands with
the PR. Justification recorded in file-size-baseline.json.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
This commit is contained in:
backryun
2026-07-05 14:33:32 +09:00
committed by GitHub
parent b1e27258c0
commit e755c5ac68
17 changed files with 405 additions and 203 deletions

View File

@@ -15,6 +15,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
buildCompatMap,
isModelHiddenFn,
getDisplayModelAlias,
effectiveNormalizeForProtocol,
effectivePreserveForProtocol,
anyNormalizeCompatBadge,
@@ -35,10 +36,7 @@ vi.mock("next/navigation", () => ({
vi.mock("next-intl", () => ({
useTranslations: () => (key: string, values?: Record<string, unknown>) => {
if (values) {
return Object.entries(values).reduce(
(acc, [k, v]) => acc.replace(`{${k}}`, String(v)),
key
);
return Object.entries(values).reduce((acc, [k, v]) => acc.replace(`{${k}}`, String(v)), key);
}
return key;
},
@@ -85,6 +83,22 @@ describe("providerPageHelpers — model-compat pure functions", () => {
expect(isModelHiddenFn("unknown-model", customMap, overrideMap)).toBe(false);
});
it("isModelHiddenFn ignores deleted tombstones when reading visibility", () => {
const customMap = buildCompatMap([]);
const overrideMap = buildCompatMap([
{ id: "gpt-4o-2024-11-20", isHidden: true, isDeleted: true },
{ id: "gpt-5-mini", isHidden: true },
]);
expect(isModelHiddenFn("gpt-4o-2024-11-20", customMap, overrideMap)).toBe(false);
expect(isModelHiddenFn("gpt-5-mini", customMap, overrideMap)).toBe(true);
});
it("getDisplayModelAlias ignores provider-scoped identity aliases", () => {
expect(getDisplayModelAlias("gpt-4o-2024-11-20", "gpt-4o-2024-11-20")).toBeNull();
expect(getDisplayModelAlias("gpt-5-mini", "fast-mini")).toBe("fast-mini");
});
it("effectiveNormalizeForProtocol returns correct flag", () => {
const customMap = buildCompatMap(customModels);
const overrideMap = buildCompatMap(overrideModels);
@@ -115,10 +129,10 @@ describe("providerPageHelpers — model-compat pure functions", () => {
});
it("formatProviderModelsErrorResponse extracts error.message", async () => {
const mockRes = new Response(
JSON.stringify({ error: { message: "Model not found" } }),
{ status: 422, statusText: "Unprocessable Entity" }
);
const mockRes = new Response(JSON.stringify({ error: { message: "Model not found" } }), {
status: 422,
statusText: "Unprocessable Entity",
});
const detail = await formatProviderModelsErrorResponse(mockRes);
expect(detail).toBe("Model not found");
});
@@ -185,7 +199,9 @@ describe("PassthroughModelRow — render smoke test", () => {
});
afterEach(() => {
act(() => { root.unmount(); });
act(() => {
root.unmount();
});
container.remove();
});
@@ -222,7 +238,9 @@ describe("ModelVisibilityToolbar — render smoke test", () => {
});
afterEach(() => {
act(() => { root.unmount(); });
act(() => {
root.unmount();
});
container.remove();
});
@@ -259,7 +277,9 @@ describe("useModelCompatState — hook unit test via component wrapper", () => {
});
afterEach(() => {
act(() => { root.unmount(); });
act(() => {
root.unmount();
});
container.remove();
});
@@ -267,7 +287,12 @@ describe("useModelCompatState — hook unit test via component wrapper", () => {
const { useModelCompatState } = await import("../hooks/useModelCompatState");
const customModels = [
{ id: "gpt-4o", normalizeToolCallId: true, preserveOpenAIDeveloperRole: false, isHidden: true },
{
id: "gpt-4o",
normalizeToolCallId: true,
preserveOpenAIDeveloperRole: false,
isHidden: true,
},
];
const modelCompatOverrides: any[] = [];
@@ -281,7 +306,9 @@ describe("useModelCompatState — hook unit test via component wrapper", () => {
compat.effectiveModelPreserveDeveloper("gpt-4o"),
compat.anyNormalizeCompatBadge("gpt-4o"),
compat.anyNoPreserveCompatBadge("gpt-4o"),
].map(String).join(",");
]
.map(String)
.join(",");
return <span data-testid="results">{results}</span>;
}
@@ -291,8 +318,9 @@ describe("useModelCompatState — hook unit test via component wrapper", () => {
const span = container.querySelector("[data-testid='results']");
expect(span).not.toBeNull();
const [hidden, notHidden, normalize, preserve, anyNorm, anyNoPreserve] =
(span!.textContent ?? "").split(",");
const [hidden, notHidden, normalize, preserve, anyNorm, anyNoPreserve] = (
span!.textContent ?? ""
).split(",");
expect(hidden).toBe("true");
expect(notHidden).toBe("false");

View File

@@ -17,6 +17,7 @@ import { resolveManagedModelAlias } from "@/shared/utils/providerModelAliases";
import { useNotificationStore } from "@/store/notificationStore";
import {
buildCompatMap,
getDisplayModelAlias,
providerText,
type CompatModelRow,
} from "../providerPageHelpers";
@@ -57,10 +58,7 @@ export interface CompatibleModelsSectionProps {
effectiveModelNormalize: (alias: string) => boolean;
effectiveModelPreserveDeveloper: (alias: string) => boolean;
getUpstreamHeadersRecord: (modelId: string, protocol: string) => Record<string, string>;
saveModelCompatFlags: (
modelId: string,
flags: CompatibleModelsSaveFlags
) => Promise<void>;
saveModelCompatFlags: (modelId: string, flags: CompatibleModelsSaveFlags) => Promise<void>;
compatSavingModelId?: string;
onModelsChanged?: () => void;
isModelHidden: (modelId: string) => boolean;
@@ -155,7 +153,8 @@ export default function CompatibleModelsSection({
for (const [alias, fullModel] of providerAliases) {
const fmStr = fullModel as string;
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
aliasByModelId.set(modelId, alias as string);
const displayAlias = getDisplayModelAlias(modelId, alias as string);
if (displayAlias) aliasByModelId.set(modelId, displayAlias);
}
const addModel = (model: CompatModelRow, source: string) => {
@@ -194,11 +193,13 @@ export default function CompatibleModelsSection({
const fmStr = fullModel as string;
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
if (!modelId || seenModelIds.has(modelId)) continue;
const displayAlias = getDisplayModelAlias(modelId, alias as string);
if (!displayAlias) continue;
const customModel = customModelMap.get(modelId);
rows.push({
modelId,
alias: alias as string,
displayName: alias as string,
alias: displayAlias,
displayName: displayAlias,
source: customModel ? customModel.source || "custom" : "alias",
isFree:
modelId.endsWith(":free") ||

View File

@@ -21,6 +21,7 @@ import {
import { useNotificationStore } from "@/store/notificationStore";
import {
buildCompatMap,
getDisplayModelAlias,
providerText,
testAllResultsText,
evaluateTestAllEntry,
@@ -228,7 +229,8 @@ export default function PassthroughModelsSection({
for (const [alias, fullModel] of providerAliases) {
const fmStr = fullModel as string;
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
aliasByModelId.set(modelId, alias as string);
const displayAlias = getDisplayModelAlias(modelId, alias as string);
if (displayAlias) aliasByModelId.set(modelId, displayAlias);
fullModelByModelId.set(modelId, fmStr);
}
@@ -266,12 +268,14 @@ export default function PassthroughModelsSection({
const fmStr = fullModel as string;
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
if (!modelId || seenModelIds.has(modelId)) continue;
const displayAlias = getDisplayModelAlias(modelId, alias as string);
if (!displayAlias) continue;
const customModel = customModelMap.get(modelId);
rows.push({
modelId,
fullModel: fmStr,
alias: alias as string,
displayName: alias as string,
alias: displayAlias,
displayName: displayAlias,
source: customModel ? customModel.source || "custom" : "alias",
isFree:
modelId.endsWith(":free") ||

View File

@@ -15,7 +15,11 @@ import { useState } from "react";
import { Button } from "@/shared/components";
import { matchesModelCatalogQuery } from "@/shared/utils/modelCatalogSearch";
import { isFreeModel, sortModelsFreeFirst } from "@/shared/utils/freeModels";
import { providerText, type ProviderMessageTranslator } from "../providerPageHelpers";
import {
getDisplayModelAlias,
providerText,
type ProviderMessageTranslator,
} from "../providerPageHelpers";
import ModelRow, { ModelVisibilityToolbar } from "./ModelRow";
import PassthroughModelsSection from "./PassthroughModelsSection";
import CompatibleModelsSection from "./CompatibleModelsSection";
@@ -86,11 +90,7 @@ export interface ProviderModelsSectionProps {
setAutoHideFailed: (v: boolean) => void;
setVisibilityFilter: (v: "all" | "visible" | "hidden") => void;
saveModelCompatFlags: (modelId: string, patch: ModelCompatSavePatch) => Promise<void>;
handleToggleModelHidden: (
providerKey: string,
modelId: string,
hidden: boolean
) => Promise<void>;
handleToggleModelHidden: (providerKey: string, modelId: string, hidden: boolean) => Promise<void>;
handleBulkToggleModelHidden: (
providerKey: string,
modelIds: string[],
@@ -187,8 +187,7 @@ export default function ProviderModelsSection({
</button>
);
const clearAllButton = (modelMeta.customModels.length > 0 ||
providerAliasEntries.length > 0) && (
const clearAllButton = (modelMeta.customModels.length > 0 || providerAliasEntries.length > 0) && (
<button
onClick={handleClearAllModels}
disabled={clearingModels}
@@ -251,9 +250,7 @@ export default function ProviderModelsSection({
onModelsChanged={fetchProviderModelMeta}
allowImport={compatibleSupportsModelImport}
isModelHidden={effectiveModelHidden}
onToggleHidden={(modelId, hidden) =>
handleToggleModelHidden(providerId, modelId, hidden)
}
onToggleHidden={(modelId, hidden) => handleToggleModelHidden(providerId, modelId, hidden)}
onBulkToggleHidden={(modelIds, hidden) =>
handleBulkToggleModelHidden(providerId, modelIds, hidden)
}
@@ -325,9 +322,7 @@ export default function ProviderModelsSection({
saveModelCompatFlags={saveModelCompatFlags}
compatSavingModelId={compatSavingModelId}
isModelHidden={effectiveModelHidden}
onToggleHidden={(modelId, hidden) =>
handleToggleModelHidden(providerId, modelId, hidden)
}
onToggleHidden={(modelId, hidden) => handleToggleModelHidden(providerId, modelId, hidden)}
onBulkToggleHidden={(modelIds, hidden) =>
handleBulkToggleModelHidden(providerId, modelIds, hidden)
}
@@ -377,7 +372,9 @@ export default function ProviderModelsSection({
(acc, [alias, fullModel]) => {
const prefix = `${providerDisplayAlias}/`;
if (fullModel.startsWith(prefix)) {
acc[fullModel.slice(prefix.length)] = alias;
const modelId = fullModel.slice(prefix.length);
const displayAlias = getDisplayModelAlias(modelId, alias);
if (displayAlias) acc[modelId] = displayAlias;
}
return acc;
},
@@ -467,9 +464,7 @@ export default function ProviderModelsSection({
onCopy={onCopy}
onSetAlias={(a) => onSetAlias(model.id, a, providerDisplayAlias)}
onDeleteAlias={
aliasByModelId[model.id]
? () => onDeleteAlias(aliasByModelId[model.id])
: undefined
aliasByModelId[model.id] ? () => onDeleteAlias(aliasByModelId[model.id]) : undefined
}
t={t}
showDeveloperToggle

View File

@@ -70,6 +70,7 @@ export type CompatModelRow = {
normalizeToolCallId?: boolean;
preserveOpenAIDeveloperRole?: boolean;
isHidden?: boolean;
isDeleted?: boolean;
upstreamHeaders?: Record<string, string>;
compatByProtocol?: CompatByProtocolMap;
/** #2905: per-model upstream wire-format override. */ targetFormat?: string;
@@ -557,19 +558,31 @@ export function buildCompatMap(rows: CompatModelRow[]): CompatModelMap {
return m;
}
export function getDisplayModelAlias(modelId: string, alias?: string | null): string | null {
const trimmed = typeof alias === "string" ? alias.trim() : "";
if (!trimmed || trimmed === modelId) return null;
return trimmed;
}
function readActiveHiddenFlag(row: CompatModelRow | undefined): boolean | undefined {
if (!row || row.isDeleted === true) return undefined;
if (Object.prototype.hasOwnProperty.call(row, "isHidden")) {
return Boolean(row.isHidden);
}
return undefined;
}
export function isModelHiddenFn(
modelId: string,
customMap: CompatModelMap,
overrideMap: CompatModelMap
): boolean {
const c = customMap.get(modelId);
if (c && Object.prototype.hasOwnProperty.call(c, "isHidden")) {
return Boolean(c.isHidden);
}
const o = overrideMap.get(modelId);
if (o && Object.prototype.hasOwnProperty.call(o, "isHidden")) {
return Boolean(o.isHidden);
}
const customHidden = readActiveHiddenFlag(customMap.get(modelId));
if (customHidden !== undefined) return customHidden;
const overrideHidden = readActiveHiddenFlag(overrideMap.get(modelId));
if (overrideHidden !== undefined) return overrideHidden;
return false;
}

View File

@@ -292,7 +292,7 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
supportsTools: true,
supportsVision: true,
adaptiveThinkingOnly: true,
aliases: BEDROCK_CLAUDE_ALIASES("claude-opus-4-8", "claude-opus-4.8"),
aliases: BEDROCK_CLAUDE_ALIASES("claude-opus-4-8", "claude-opus-4.8", "claude-opus-4.8-fast"),
},
// ── Claude Sonnet 4.5 ───────────────────────────────────────────