mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 15:22:12 +03:00
fix(providers): suppress retired Copilot Gemini models (#9103)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates: typecheck/complexity/cognitive/changelog/vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-222248-suite.log
This commit is contained in:
@@ -4,10 +4,17 @@
|
||||
* models/; this file re-exports their public APIs for backward compatibility.
|
||||
*/
|
||||
|
||||
import { isRetiredGitHubCopilotModelId } from "@omniroute/open-sse/config/providers/registry/github/retiredModels.ts";
|
||||
|
||||
import { getDbInstance } from "./core";
|
||||
import { backupDbFile } from "./backup";
|
||||
import { getProviderConnectionsCount } from "./providers";
|
||||
import { type JsonRecord, asRecord, toNonEmptyString, getKeyValue } from "./models/shared";
|
||||
import { type JsonRecord, getKeyValue } from "./models/shared";
|
||||
import {
|
||||
normalizeSyncedAvailableModels,
|
||||
type SyncedAvailableModel,
|
||||
type SyncedAvailableModelInput,
|
||||
} from "./models/synced";
|
||||
import {
|
||||
readCompatList,
|
||||
writeCompatList,
|
||||
@@ -40,6 +47,7 @@ export {
|
||||
deleteModelAliasesForProvider,
|
||||
} from "./models/aliases";
|
||||
export { getMitmAlias, setMitmAliasAll } from "./models/mitmAlias";
|
||||
export type { SyncedAvailableModel } from "./models/synced";
|
||||
|
||||
// ──────────────── Custom Models ────────────────
|
||||
|
||||
@@ -318,104 +326,6 @@ export async function removeCustomModel(providerId: string, modelId: string) {
|
||||
// Each connection stores its own model list. Reads union across all connections
|
||||
// for a provider. Deleting a connection removes only its models.
|
||||
|
||||
export interface SyncedAvailableModel {
|
||||
id: string;
|
||||
name: string;
|
||||
source: "imported";
|
||||
apiFormat?: string;
|
||||
targetFormat?: string;
|
||||
upstreamProtocol?: string;
|
||||
supportedEndpoints?: string[];
|
||||
supportedThinkingEfforts?: string[];
|
||||
defaultThinkingEffort?: string;
|
||||
inputTokenLimit?: number;
|
||||
outputTokenLimit?: number;
|
||||
description?: string;
|
||||
supportsThinking?: boolean;
|
||||
alwaysThinking?: boolean;
|
||||
supportsTools?: boolean;
|
||||
supportsVideo?: boolean;
|
||||
// #4264: image-input capability captured at sync time (e.g. OpenRouter
|
||||
// `architecture.input_modalities`/`modality`) so the catalog can surface vision.
|
||||
supportsVision?: boolean;
|
||||
}
|
||||
|
||||
type SyncedAvailableModelInput = Omit<SyncedAvailableModel, "source"> & {
|
||||
source?: string;
|
||||
};
|
||||
|
||||
function normalizeSyncedAvailableModel(model: unknown): SyncedAvailableModel | null {
|
||||
const record = asRecord(model);
|
||||
const id =
|
||||
toNonEmptyString(record.id) || toNonEmptyString(record.name) || toNonEmptyString(record.model);
|
||||
if (!id) return null;
|
||||
|
||||
const name =
|
||||
toNonEmptyString(record.name) ||
|
||||
toNonEmptyString(record.displayName) ||
|
||||
toNonEmptyString(record.model) ||
|
||||
id;
|
||||
const supportedEndpoints = Array.isArray(record.supportedEndpoints)
|
||||
? Array.from(
|
||||
new Set(
|
||||
record.supportedEndpoints
|
||||
.map((endpoint) => toNonEmptyString(endpoint))
|
||||
.filter((endpoint): endpoint is string => Boolean(endpoint))
|
||||
)
|
||||
).sort()
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
source: "imported",
|
||||
...(toNonEmptyString(record.apiFormat)
|
||||
? { apiFormat: toNonEmptyString(record.apiFormat)! }
|
||||
: {}),
|
||||
...(toNonEmptyString(record.targetFormat)
|
||||
? { targetFormat: toNonEmptyString(record.targetFormat)! }
|
||||
: {}),
|
||||
...(toNonEmptyString(record.upstreamProtocol)
|
||||
? { upstreamProtocol: toNonEmptyString(record.upstreamProtocol)! }
|
||||
: {}),
|
||||
...(supportedEndpoints && supportedEndpoints.length > 0 ? { supportedEndpoints } : {}),
|
||||
...(Array.isArray(record.supportedThinkingEfforts)
|
||||
? {
|
||||
supportedThinkingEfforts: record.supportedThinkingEfforts.filter(
|
||||
(effort): effort is string => typeof effort === "string" && effort.length > 0
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
...(toNonEmptyString(record.defaultThinkingEffort)
|
||||
? { defaultThinkingEffort: toNonEmptyString(record.defaultThinkingEffort)! }
|
||||
: {}),
|
||||
...(typeof record.inputTokenLimit === "number"
|
||||
? { inputTokenLimit: record.inputTokenLimit }
|
||||
: {}),
|
||||
...(typeof record.outputTokenLimit === "number"
|
||||
? { outputTokenLimit: record.outputTokenLimit }
|
||||
: {}),
|
||||
...(typeof record.description === "string" ? { description: record.description } : {}),
|
||||
...(typeof record.supportsThinking === "boolean"
|
||||
? { supportsThinking: record.supportsThinking }
|
||||
: {}),
|
||||
...(record.alwaysThinking === true ? { alwaysThinking: true } : {}),
|
||||
...(typeof record.supportsTools === "boolean" ? { supportsTools: record.supportsTools } : {}),
|
||||
...(typeof record.supportsVideo === "boolean" ? { supportsVideo: record.supportsVideo } : {}),
|
||||
...(record.supportsVision === true ? { supportsVision: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSyncedAvailableModels(models: unknown): SyncedAvailableModel[] {
|
||||
if (!Array.isArray(models)) return [];
|
||||
const deduped = new Map<string, SyncedAvailableModel>();
|
||||
for (const model of models) {
|
||||
const normalized = normalizeSyncedAvailableModel(model);
|
||||
if (normalized) deduped.set(normalized.id, normalized);
|
||||
}
|
||||
return Array.from(deduped.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get synced available models for a specific provider connection.
|
||||
*/
|
||||
@@ -432,7 +342,7 @@ export async function getSyncedAvailableModelsForConnection(
|
||||
if (!value) return [];
|
||||
try {
|
||||
const models = JSON.parse(value);
|
||||
return normalizeSyncedAvailableModels(models);
|
||||
return normalizeSyncedAvailableModels(models, providerId);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
@@ -454,7 +364,7 @@ export async function getSyncedAvailableModels(
|
||||
for (const row of rows) {
|
||||
const { key, value } = getKeyValue(row);
|
||||
if (!key || value === null) continue;
|
||||
const models = normalizeSyncedAvailableModels(JSON.parse(value));
|
||||
const models = normalizeSyncedAvailableModels(JSON.parse(value), providerId);
|
||||
for (const m of models) {
|
||||
if (m.id) map.set(m.id, m);
|
||||
}
|
||||
@@ -481,7 +391,7 @@ export async function getSyncedAvailableModelsByConnection(
|
||||
if (!key || value === null || !key.startsWith(prefix)) continue;
|
||||
try {
|
||||
const connectionId = key.slice(prefix.length);
|
||||
result[connectionId] = normalizeSyncedAvailableModels(JSON.parse(value));
|
||||
result[connectionId] = normalizeSyncedAvailableModels(JSON.parse(value), providerId);
|
||||
} catch {
|
||||
// Ignore malformed legacy entries.
|
||||
}
|
||||
@@ -506,7 +416,7 @@ export async function getAllSyncedAvailableModels(): Promise<
|
||||
if (!key || value === null) continue;
|
||||
const providerId = key.split(":")[0];
|
||||
if (!byProvider.has(providerId)) byProvider.set(providerId, new Map());
|
||||
const models = normalizeSyncedAvailableModels(JSON.parse(value));
|
||||
const models = normalizeSyncedAvailableModels(JSON.parse(value), providerId);
|
||||
const map = byProvider.get(providerId)!;
|
||||
for (const m of models) {
|
||||
if (m.id) map.set(m.id, m);
|
||||
@@ -549,7 +459,8 @@ export async function getActiveProvidersWithSyncedModel(modelId: string): Promis
|
||||
|
||||
return rows
|
||||
.map((row) => row.provider)
|
||||
.filter((provider): provider is string => typeof provider === "string" && provider.length > 0);
|
||||
.filter((provider): provider is string => typeof provider === "string" && provider.length > 0)
|
||||
.filter((provider) => !isRetiredGitHubCopilotModelId(provider, modelId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -570,7 +481,7 @@ export async function replaceSyncedAvailableModelsForConnection(
|
||||
// the synced store so they remain listed-but-hidden across re-syncs instead of
|
||||
// churning back on through the managed-alias path ("Auto Sync Enabling all
|
||||
// Models"). See getModelIsDeleted for the legacy-row caveat.
|
||||
const normalizedModels = normalizeSyncedAvailableModels(models).filter(
|
||||
const normalizedModels = normalizeSyncedAvailableModels(models, providerId).filter(
|
||||
(m) => !getModelIsDeleted(providerId, m.id)
|
||||
);
|
||||
if (normalizedModels.length === 0) {
|
||||
@@ -617,7 +528,7 @@ export async function removeSyncedAvailableModel(
|
||||
continue;
|
||||
}
|
||||
|
||||
const models = normalizeSyncedAvailableModels(parsedModels);
|
||||
const models = normalizeSyncedAvailableModels(parsedModels, providerId);
|
||||
const filtered = models.filter((m) => m.id !== modelId);
|
||||
if (filtered.length !== models.length) {
|
||||
removedAny = true;
|
||||
|
||||
106
src/lib/db/models/synced.ts
Normal file
106
src/lib/db/models/synced.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { isRetiredGitHubCopilotModelId } from "@omniroute/open-sse/config/providers/registry/github/retiredModels.ts";
|
||||
|
||||
import { asRecord, toNonEmptyString } from "./shared";
|
||||
|
||||
export interface SyncedAvailableModel {
|
||||
id: string;
|
||||
name: string;
|
||||
source: "imported";
|
||||
apiFormat?: string;
|
||||
targetFormat?: string;
|
||||
upstreamProtocol?: string;
|
||||
supportedEndpoints?: string[];
|
||||
supportedThinkingEfforts?: string[];
|
||||
defaultThinkingEffort?: string;
|
||||
inputTokenLimit?: number;
|
||||
outputTokenLimit?: number;
|
||||
description?: string;
|
||||
supportsThinking?: boolean;
|
||||
alwaysThinking?: boolean;
|
||||
supportsTools?: boolean;
|
||||
supportsVideo?: boolean;
|
||||
// #4264: image-input capability captured at sync time (e.g. OpenRouter
|
||||
// `architecture.input_modalities`/`modality`) so the catalog can surface vision.
|
||||
supportsVision?: boolean;
|
||||
}
|
||||
|
||||
export type SyncedAvailableModelInput = Omit<SyncedAvailableModel, "source"> & {
|
||||
source?: string;
|
||||
};
|
||||
|
||||
function normalizeSyncedAvailableModel(model: unknown): SyncedAvailableModel | null {
|
||||
const record = asRecord(model);
|
||||
const id =
|
||||
toNonEmptyString(record.id) || toNonEmptyString(record.name) || toNonEmptyString(record.model);
|
||||
if (!id) return null;
|
||||
|
||||
const name =
|
||||
toNonEmptyString(record.name) ||
|
||||
toNonEmptyString(record.displayName) ||
|
||||
toNonEmptyString(record.model) ||
|
||||
id;
|
||||
const supportedEndpoints = Array.isArray(record.supportedEndpoints)
|
||||
? Array.from(
|
||||
new Set(
|
||||
record.supportedEndpoints
|
||||
.map((endpoint) => toNonEmptyString(endpoint))
|
||||
.filter((endpoint): endpoint is string => Boolean(endpoint))
|
||||
)
|
||||
).sort()
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
source: "imported",
|
||||
...(toNonEmptyString(record.apiFormat)
|
||||
? { apiFormat: toNonEmptyString(record.apiFormat)! }
|
||||
: {}),
|
||||
...(toNonEmptyString(record.targetFormat)
|
||||
? { targetFormat: toNonEmptyString(record.targetFormat)! }
|
||||
: {}),
|
||||
...(toNonEmptyString(record.upstreamProtocol)
|
||||
? { upstreamProtocol: toNonEmptyString(record.upstreamProtocol)! }
|
||||
: {}),
|
||||
...(supportedEndpoints && supportedEndpoints.length > 0 ? { supportedEndpoints } : {}),
|
||||
...(Array.isArray(record.supportedThinkingEfforts)
|
||||
? {
|
||||
supportedThinkingEfforts: record.supportedThinkingEfforts.filter(
|
||||
(effort): effort is string => typeof effort === "string" && effort.length > 0
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
...(toNonEmptyString(record.defaultThinkingEffort)
|
||||
? { defaultThinkingEffort: toNonEmptyString(record.defaultThinkingEffort)! }
|
||||
: {}),
|
||||
...(typeof record.inputTokenLimit === "number"
|
||||
? { inputTokenLimit: record.inputTokenLimit }
|
||||
: {}),
|
||||
...(typeof record.outputTokenLimit === "number"
|
||||
? { outputTokenLimit: record.outputTokenLimit }
|
||||
: {}),
|
||||
...(typeof record.description === "string" ? { description: record.description } : {}),
|
||||
...(typeof record.supportsThinking === "boolean"
|
||||
? { supportsThinking: record.supportsThinking }
|
||||
: {}),
|
||||
...(record.alwaysThinking === true ? { alwaysThinking: true } : {}),
|
||||
...(typeof record.supportsTools === "boolean" ? { supportsTools: record.supportsTools } : {}),
|
||||
...(typeof record.supportsVideo === "boolean" ? { supportsVideo: record.supportsVideo } : {}),
|
||||
...(record.supportsVision === true ? { supportsVision: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeSyncedAvailableModels(
|
||||
models: unknown,
|
||||
providerId?: string
|
||||
): SyncedAvailableModel[] {
|
||||
if (!Array.isArray(models)) return [];
|
||||
const deduped = new Map<string, SyncedAvailableModel>();
|
||||
for (const model of models) {
|
||||
const normalized = normalizeSyncedAvailableModel(model);
|
||||
if (normalized && !isRetiredGitHubCopilotModelId(providerId, normalized.id)) {
|
||||
deduped.set(normalized.id, normalized);
|
||||
}
|
||||
}
|
||||
return Array.from(deduped.values());
|
||||
}
|
||||
Reference in New Issue
Block a user