mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 22:02:08 +03:00
chore(ts): wave 4c — type 8 files (components, SSE handlers, services) 313→252
This commit is contained in:
@@ -29,9 +29,9 @@ export default function ModelSelectModal({
|
||||
modelAliases = {},
|
||||
}) {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [combos, setCombos] = useState([]);
|
||||
const [providerNodes, setProviderNodes] = useState([]);
|
||||
const [customModels, setCustomModels] = useState({});
|
||||
const [combos, setCombos] = useState<any[]>([]);
|
||||
const [providerNodes, setProviderNodes] = useState<any[]>([]);
|
||||
const [customModels, setCustomModels] = useState<Record<string, any>>({});
|
||||
|
||||
const fetchCombos = async () => {
|
||||
try {
|
||||
@@ -88,7 +88,7 @@ export default function ModelSelectModal({
|
||||
|
||||
// Group models by provider with priority order
|
||||
const groupedModels = useMemo(() => {
|
||||
const groups = {};
|
||||
const groups: Record<string, any> = {};
|
||||
|
||||
// Get all active provider IDs from connections
|
||||
const activeConnectionIds = activeProviders.map((p) => p.provider);
|
||||
@@ -115,9 +115,9 @@ export default function ModelSelectModal({
|
||||
const providerCustomModels = customModels[providerId] || [];
|
||||
|
||||
if (providerInfo.passthroughModels) {
|
||||
const aliasModels = Object.entries(modelAliases)
|
||||
.filter(([, fullModel]) => fullModel.startsWith(`${alias}/`))
|
||||
.map(([aliasName, fullModel]) => ({
|
||||
const aliasModels = Object.entries(modelAliases as Record<string, string>)
|
||||
.filter(([, fullModel]: [string, string]) => fullModel.startsWith(`${alias}/`))
|
||||
.map(([aliasName, fullModel]: [string, string]) => ({
|
||||
id: fullModel.replace(`${alias}/`, ""),
|
||||
name: aliasName,
|
||||
value: fullModel,
|
||||
@@ -150,9 +150,9 @@ export default function ModelSelectModal({
|
||||
const matchedNode = providerNodes.find((node) => node.id === providerId);
|
||||
const displayName = matchedNode?.name || providerInfo.name;
|
||||
|
||||
const nodeModels = Object.entries(modelAliases)
|
||||
.filter(([, fullModel]) => fullModel.startsWith(`${providerId}/`))
|
||||
.map(([aliasName, fullModel]) => ({
|
||||
const nodeModels = Object.entries(modelAliases as Record<string, string>)
|
||||
.filter(([, fullModel]: [string, string]) => fullModel.startsWith(`${providerId}/`))
|
||||
.map(([aliasName, fullModel]: [string, string]) => ({
|
||||
id: fullModel.replace(`${providerId}/`, ""),
|
||||
name: aliasName,
|
||||
value: fullModel,
|
||||
@@ -227,9 +227,9 @@ export default function ModelSelectModal({
|
||||
if (!searchQuery.trim()) return groupedModels;
|
||||
|
||||
const query = searchQuery.toLowerCase();
|
||||
const filtered = {};
|
||||
const filtered: Record<string, any> = {};
|
||||
|
||||
Object.entries(groupedModels).forEach(([providerId, group]) => {
|
||||
Object.entries(groupedModels).forEach(([providerId, group]: [string, any]) => {
|
||||
const matchedModels = group.models.filter(
|
||||
(m) => m.name.toLowerCase().includes(query) || m.id.toLowerCase().includes(query)
|
||||
);
|
||||
@@ -247,7 +247,7 @@ export default function ModelSelectModal({
|
||||
return filtered;
|
||||
}, [groupedModels, searchQuery]);
|
||||
|
||||
const handleSelect = (model) => {
|
||||
const handleSelect = (model: any) => {
|
||||
onSelect(model);
|
||||
onClose();
|
||||
setSearchQuery("");
|
||||
@@ -317,7 +317,7 @@ export default function ModelSelectModal({
|
||||
)}
|
||||
|
||||
{/* Provider models */}
|
||||
{Object.entries(filteredGroups).map(([providerId, group]) => (
|
||||
{Object.entries(filteredGroups).map(([providerId, group]: [string, any]) => (
|
||||
<div key={providerId}>
|
||||
{/* Provider header */}
|
||||
<div className="flex items-center gap-1.5 mb-1.5 sticky top-0 bg-surface py-0.5">
|
||||
|
||||
@@ -23,9 +23,9 @@ import {
|
||||
|
||||
export default function UsageAnalytics() {
|
||||
const [range, setRange] = useState("30d");
|
||||
const [analytics, setAnalytics] = useState(null);
|
||||
const [analytics, setAnalytics] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchAnalytics = useCallback(async () => {
|
||||
try {
|
||||
@@ -36,7 +36,7 @@ export default function UsageAnalytics() {
|
||||
setAnalytics(data);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setError((err as any).message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import Badge from "./Badge";
|
||||
import { CardSkeleton } from "./Loading";
|
||||
import { fmtFull, fmtCost } from "@/shared/utils/formatting";
|
||||
|
||||
function SortIcon({ field, currentSort, currentOrder }) {
|
||||
function SortIcon({ field, currentSort, currentOrder }: { field: string; currentSort: string; currentOrder: string }) {
|
||||
if (currentSort !== field) return <span className="ml-1 opacity-20">↕</span>;
|
||||
return <span className="ml-1">{currentOrder === "asc" ? "↑" : "↓"}</span>;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ SortIcon.propTypes = {
|
||||
currentOrder: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
function MiniBarGraph({ data, colorClass = "bg-primary" }) {
|
||||
function MiniBarGraph({ data, colorClass = "bg-primary" }: { data: number[]; colorClass?: string }) {
|
||||
const max = Math.max(...data, 1);
|
||||
return (
|
||||
<div className="flex items-end gap-1 h-8 w-24">
|
||||
@@ -47,14 +47,14 @@ export default function UsageStats() {
|
||||
const sortBy = searchParams.get("sortBy") || "rawModel";
|
||||
const sortOrder = searchParams.get("sortOrder") || "asc";
|
||||
|
||||
const [stats, setStats] = useState(null);
|
||||
const [stats, setStats] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [autoRefresh, setAutoRefresh] = useState(true);
|
||||
const [viewMode, setViewMode] = useState("tokens"); // 'tokens' or 'costs'
|
||||
const [refreshInterval, setRefreshInterval] = useState(5000); // Start with 5s
|
||||
const prevTotalRequestsRef = useRef(0);
|
||||
|
||||
const toggleSort = (field) => {
|
||||
const toggleSort = (field: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
if (sortBy === field) {
|
||||
params.set("sortOrder", sortOrder === "asc" ? "desc" : "asc");
|
||||
@@ -66,9 +66,9 @@ export default function UsageStats() {
|
||||
};
|
||||
|
||||
const sortData = useCallback(
|
||||
(dataMap, pendingMap = {}) => {
|
||||
(dataMap: Record<string, any>, pendingMap: Record<string, any> = {}) => {
|
||||
return Object.entries(dataMap || {})
|
||||
.map(([key, data]) => {
|
||||
.map(([key, data]: [string, any]) => {
|
||||
const totalTokens = (data.promptTokens || 0) + (data.completionTokens || 0);
|
||||
const totalCost = data.cost || 0;
|
||||
|
||||
@@ -111,9 +111,9 @@ export default function UsageStats() {
|
||||
const sortedAccounts = useMemo(() => {
|
||||
// For accounts, pendingMap is by connectionId, but dataMap is by accountKey
|
||||
// We need to map connectionId pending counts to accountKeys
|
||||
const accountPendingMap = {};
|
||||
const accountPendingMap: Record<string, any> = {};
|
||||
if (stats?.pending?.byAccount) {
|
||||
Object.entries(stats.byAccount || {}).forEach(([accountKey, data]) => {
|
||||
Object.entries(stats.byAccount || {}).forEach(([accountKey, data]: [string, any]) => {
|
||||
const connPending = stats.pending.byAccount[data.connectionId];
|
||||
if (connPending) {
|
||||
// Get modelKey (rawModel (provider))
|
||||
@@ -125,7 +125,7 @@ export default function UsageStats() {
|
||||
return sortData(stats?.byAccount, accountPendingMap);
|
||||
}, [stats?.byAccount, stats?.pending?.byAccount, sortData]);
|
||||
|
||||
const fetchStats = useCallback(async (showLoading = true) => {
|
||||
const fetchStats = useCallback(async (showLoading = true): Promise<void> => {
|
||||
if (showLoading) setLoading(true);
|
||||
try {
|
||||
const res = await fetch("/api/usage/history");
|
||||
@@ -156,7 +156,7 @@ export default function UsageStats() {
|
||||
}, [fetchStats]);
|
||||
|
||||
useEffect(() => {
|
||||
let intervalId;
|
||||
let intervalId: ReturnType<typeof setInterval> | undefined;
|
||||
let isPageVisible = true;
|
||||
|
||||
// Page Visibility API - pause when tab is hidden
|
||||
@@ -191,16 +191,16 @@ export default function UsageStats() {
|
||||
if (!stats) return <div className="text-text-muted">Failed to load usage statistics.</div>;
|
||||
|
||||
// Format number with commas — delegated to shared module
|
||||
const fmt = (n) => fmtFull(n);
|
||||
const fmt = (n: number) => fmtFull(n);
|
||||
|
||||
// Format cost with dollar sign and 2 decimals — delegated to shared module
|
||||
|
||||
// Time format for "Last Used"
|
||||
const fmtTime = (iso) => {
|
||||
const fmtTime = (iso: string) => {
|
||||
if (!iso) return "Never";
|
||||
const date = new Date(iso);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
|
||||
if (diffMins < 1) return "Just now";
|
||||
@@ -459,7 +459,7 @@ export default function UsageStats() {
|
||||
{data.rawModel}
|
||||
</td>
|
||||
<td className="px-6 py-3">
|
||||
<Badge variant={data.pending > 0 ? "primary" : "neutral"} size="sm">
|
||||
<Badge variant={data.pending > 0 ? "primary" : "default"} size="sm">
|
||||
{data.provider}
|
||||
</Badge>
|
||||
</td>
|
||||
@@ -625,7 +625,7 @@ export default function UsageStats() {
|
||||
{data.rawModel}
|
||||
</td>
|
||||
<td className="px-6 py-3">
|
||||
<Badge variant={data.pending > 0 ? "primary" : "neutral"} size="sm">
|
||||
<Badge variant={data.pending > 0 ? "primary" : "default"} size="sm">
|
||||
{data.provider}
|
||||
</Badge>
|
||||
</td>
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
|
||||
// ── Custom Tooltip for dark theme ──────────────────────────────────────────
|
||||
|
||||
function DarkTooltip({ active, payload, label, formatter }) {
|
||||
function DarkTooltip({ active, payload, label, formatter }: { active?: boolean; payload?: any[]; label?: any; formatter?: Function }) {
|
||||
if (!active || !payload?.length) return null;
|
||||
return (
|
||||
<div className="rounded-lg border border-white/10 bg-surface px-3 py-2 text-xs shadow-lg">
|
||||
@@ -48,7 +48,7 @@ function DarkTooltip({ active, payload, label, formatter }) {
|
||||
|
||||
// ── Sort Indicator (shared by tables) ──────────────────────────────────────
|
||||
|
||||
export function SortIndicator({ active, sortOrder }) {
|
||||
export function SortIndicator({ active, sortOrder }: { active: boolean; sortOrder: string }) {
|
||||
if (!active) {
|
||||
return (
|
||||
<span className="material-symbols-outlined text-[12px] opacity-0 group-hover:opacity-30">
|
||||
@@ -65,7 +65,7 @@ export function SortIndicator({ active, sortOrder }) {
|
||||
|
||||
// ── StatCard ───────────────────────────────────────────────────────────────
|
||||
|
||||
export function StatCard({ icon, label, value, subValue, color = "text-text-main" }) {
|
||||
export function StatCard({ icon, label, value, subValue, color = "text-text-main" }: { icon: any; label: any; value: any; subValue?: any; color?: string }) {
|
||||
return (
|
||||
<Card className="px-4 py-3 flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2 text-text-muted text-xs uppercase font-semibold tracking-wider">
|
||||
@@ -161,7 +161,7 @@ export function ActivityHeatmap({ activityMap }) {
|
||||
<h3 className="text-sm font-semibold text-text-muted uppercase tracking-wider">Activity</h3>
|
||||
<span className="text-xs text-text-muted">
|
||||
{Object.keys(activityMap || {}).length} active days ·{" "}
|
||||
{fmt(Object.values(activityMap || {}).reduce((a, b) => a + b, 0))} tokens · 365 days
|
||||
{fmt(Object.values(activityMap || {}).reduce((a: number, b: number) => a + b, 0))} tokens · 365 days
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -320,7 +320,7 @@ export function DailyTrendChart({ dailyTrend }) {
|
||||
|
||||
// ── Cost-aware Tooltip ─────────────────────────────────────────────────────
|
||||
|
||||
function CostTooltip({ active, payload, label }) {
|
||||
function CostTooltip({ active, payload, label }: { active?: boolean; payload?: any[]; label?: any }) {
|
||||
if (!active || !payload?.length) return null;
|
||||
return (
|
||||
<div className="rounded-lg border border-white/10 bg-surface px-3 py-2 text-xs shadow-lg">
|
||||
|
||||
@@ -8,14 +8,14 @@ const MAX_BACKUPS_PER_TOOL = 5;
|
||||
/**
|
||||
* Get backup directory for a specific tool
|
||||
*/
|
||||
function getToolBackupDir(toolId) {
|
||||
function getToolBackupDir(toolId: string) {
|
||||
return path.join(BACKUP_DIR, toolId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure backup directory exists for a tool
|
||||
*/
|
||||
async function ensureBackupDir(toolId) {
|
||||
async function ensureBackupDir(toolId: string) {
|
||||
const dir = getToolBackupDir(toolId);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
return dir;
|
||||
@@ -24,7 +24,7 @@ async function ensureBackupDir(toolId) {
|
||||
/**
|
||||
* Generate a backup filename with timestamp
|
||||
*/
|
||||
function makeBackupName(originalPath) {
|
||||
function makeBackupName(originalPath: string) {
|
||||
const ext = path.extname(originalPath);
|
||||
const base = path.basename(originalPath, ext);
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
@@ -35,7 +35,7 @@ function makeBackupName(originalPath) {
|
||||
* Create a backup of a file before modifying it.
|
||||
* Returns the backup path, or null if the source doesn't exist.
|
||||
*/
|
||||
export async function createBackup(toolId, filePath) {
|
||||
export async function createBackup(toolId: string, filePath: string) {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
} catch {
|
||||
@@ -71,8 +71,8 @@ export async function createBackup(toolId, filePath) {
|
||||
* Create backups for multiple files in one operation (e.g. Codex config.toml + auth.json).
|
||||
* Returns an array of backup paths.
|
||||
*/
|
||||
export async function createMultiBackup(toolId, filePaths) {
|
||||
const results = [];
|
||||
export async function createMultiBackup(toolId: string, filePaths: string[]) {
|
||||
const results: (string | null)[] = [];
|
||||
for (const filePath of filePaths) {
|
||||
const result = await createBackup(toolId, filePath);
|
||||
results.push(result);
|
||||
@@ -83,7 +83,7 @@ export async function createMultiBackup(toolId, filePaths) {
|
||||
/**
|
||||
* List all backups for a tool (sorted newest first).
|
||||
*/
|
||||
export async function listBackups(toolId) {
|
||||
export async function listBackups(toolId: string) {
|
||||
const dir = getToolBackupDir(toolId);
|
||||
|
||||
let entries;
|
||||
@@ -94,7 +94,7 @@ export async function listBackups(toolId) {
|
||||
}
|
||||
|
||||
const metaFiles = entries.filter((e) => e.endsWith(".meta.json"));
|
||||
const backups = [];
|
||||
const backups: any[] = [];
|
||||
|
||||
for (const metaFile of metaFiles) {
|
||||
try {
|
||||
@@ -127,14 +127,14 @@ export async function listBackups(toolId) {
|
||||
}
|
||||
|
||||
// Sort newest first
|
||||
backups.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
backups.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
return backups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a backup by its id (filename).
|
||||
*/
|
||||
export async function restoreBackup(toolId, backupId) {
|
||||
export async function restoreBackup(toolId: string, backupId: string) {
|
||||
const dir = getToolBackupDir(toolId);
|
||||
const backupPath = path.join(dir, backupId);
|
||||
const metaPath = backupPath + ".meta.json";
|
||||
@@ -173,7 +173,7 @@ export async function restoreBackup(toolId, backupId) {
|
||||
/**
|
||||
* Delete a specific backup by its id.
|
||||
*/
|
||||
export async function deleteBackup(toolId, backupId) {
|
||||
export async function deleteBackup(toolId: string, backupId: string) {
|
||||
const dir = getToolBackupDir(toolId);
|
||||
const backupPath = path.join(dir, backupId);
|
||||
const metaPath = backupPath + ".meta.json";
|
||||
@@ -196,18 +196,18 @@ export async function deleteBackup(toolId, backupId) {
|
||||
* Enforce max backups per tool — removes oldest when limit exceeded.
|
||||
* Groups by original file basename so each config file gets its own rotation.
|
||||
*/
|
||||
async function rotateBackups(toolId) {
|
||||
async function rotateBackups(toolId: string) {
|
||||
const all = await listBackups(toolId);
|
||||
|
||||
// Group by original file basename
|
||||
const groups = {};
|
||||
const groups: Record<string, any[]> = {};
|
||||
for (const b of all) {
|
||||
const key = path.basename(b.originalPath);
|
||||
if (!groups[key]) groups[key] = [];
|
||||
groups[key].push(b);
|
||||
}
|
||||
|
||||
for (const [, group] of Object.entries(groups)) {
|
||||
for (const [, group] of Object.entries(groups) as [string, any[]][]) {
|
||||
// Already sorted newest first
|
||||
if (group.length > MAX_BACKUPS_PER_TOOL) {
|
||||
const toDelete = group.slice(MAX_BACKUPS_PER_TOOL);
|
||||
|
||||
@@ -42,7 +42,7 @@ import { logAuditEvent } from "../../lib/compliance/index";
|
||||
* Supports: OpenAI, Claude, Gemini, OpenAI Responses API formats
|
||||
* Format detection and translation handled by translator
|
||||
*/
|
||||
export async function handleChat(request, clientRawRequest = null) {
|
||||
export async function handleChat(request: any, clientRawRequest: any = null) {
|
||||
// Pipeline: Start request telemetry
|
||||
const reqId = generateRequestId();
|
||||
const telemetry = new RequestTelemetry(reqId);
|
||||
@@ -155,7 +155,7 @@ export async function handleChat(request, clientRawRequest = null) {
|
||||
|
||||
// Pre-check function: skip models where all accounts are in cooldown
|
||||
// Uses modelAvailability module for TTL-based cooldowns
|
||||
const checkModelAvailable = async (modelString) => {
|
||||
const checkModelAvailable = async (modelString: string) => {
|
||||
const parsed = parseModel(modelString);
|
||||
const provider = parsed.provider;
|
||||
if (!provider) return true; // can't determine provider, let it try
|
||||
@@ -178,10 +178,10 @@ export async function handleChat(request, clientRawRequest = null) {
|
||||
]);
|
||||
telemetry.endPhase();
|
||||
|
||||
const response = await handleComboChat({
|
||||
const response = await (handleComboChat as any)({
|
||||
body,
|
||||
combo,
|
||||
handleSingleModel: (b, m) =>
|
||||
handleSingleModel: (b: any, m: string) =>
|
||||
handleSingleModelChat(b, m, clientRawRequest, request, combo.name, apiKeyInfo, telemetry),
|
||||
isModelAvailable: checkModelAvailable,
|
||||
log,
|
||||
@@ -217,24 +217,24 @@ export async function handleChat(request, clientRawRequest = null) {
|
||||
* credential retry loop.
|
||||
*/
|
||||
async function handleSingleModelChat(
|
||||
body,
|
||||
modelStr,
|
||||
clientRawRequest = null,
|
||||
request = null,
|
||||
comboName = null,
|
||||
apiKeyInfo = null,
|
||||
telemetry = null
|
||||
body: any,
|
||||
modelStr: string,
|
||||
clientRawRequest: any = null,
|
||||
request: any = null,
|
||||
comboName: string | null = null,
|
||||
apiKeyInfo: any = null,
|
||||
telemetry: any = null
|
||||
) {
|
||||
// 1. Resolve model → provider/model (or return error)
|
||||
const modelInfo = await getModelInfo(modelStr);
|
||||
if (!modelInfo.provider) {
|
||||
if (modelInfo.errorType === "ambiguous_model") {
|
||||
if ((modelInfo as any).errorType === "ambiguous_model") {
|
||||
const message =
|
||||
modelInfo.errorMessage ||
|
||||
(modelInfo as any).errorMessage ||
|
||||
`Ambiguous model '${modelStr}'. Use provider/model prefix (ex: gh/${modelStr} or cc/${modelStr}).`;
|
||||
log.warn("CHAT", message, {
|
||||
model: modelStr,
|
||||
candidates: modelInfo.candidateAliases || modelInfo.candidateProviders || [],
|
||||
candidates: (modelInfo as any).candidateAliases || (modelInfo as any).candidateProviders || [],
|
||||
});
|
||||
return errorResponse(HTTP_STATUS.BAD_REQUEST, message);
|
||||
}
|
||||
@@ -256,7 +256,7 @@ async function handleSingleModelChat(
|
||||
// Pipeline: Check model availability (TTL cooldown)
|
||||
if (!isModelAvailable(provider, model)) {
|
||||
log.warn("AVAILABILITY", `${provider}/${model} is in cooldown, rejecting request`);
|
||||
return unavailableResponse(
|
||||
return (unavailableResponse as any)(
|
||||
HTTP_STATUS.SERVICE_UNAVAILABLE,
|
||||
`Model ${provider}/${model} is temporarily unavailable (cooldown)`,
|
||||
30
|
||||
@@ -267,11 +267,11 @@ async function handleSingleModelChat(
|
||||
const breaker = getCircuitBreaker(provider, {
|
||||
failureThreshold: 5,
|
||||
resetTimeout: 30000,
|
||||
onStateChange: (name, from, to) => log.info("CIRCUIT", `${name}: ${from} → ${to}`),
|
||||
onStateChange: (name: string, from: string, to: string) => log.info("CIRCUIT", `${name}: ${from} → ${to}`),
|
||||
});
|
||||
if (!breaker.canExecute()) {
|
||||
log.warn("CIRCUIT", `Circuit breaker OPEN for ${provider}, rejecting request`);
|
||||
return unavailableResponse(
|
||||
return (unavailableResponse as any)(
|
||||
HTTP_STATUS.SERVICE_UNAVAILABLE,
|
||||
`Provider ${provider} circuit breaker is open`,
|
||||
30
|
||||
@@ -314,7 +314,7 @@ async function handleSingleModelChat(
|
||||
try {
|
||||
const chatFn = () =>
|
||||
runWithProxyContext(proxyInfo?.proxy || null, () =>
|
||||
handleChatCore({
|
||||
(handleChatCore as any)({
|
||||
body: { ...body, model: `${provider}/${model}` },
|
||||
modelInfo: { provider, model },
|
||||
credentials: refreshedCredentials,
|
||||
@@ -324,7 +324,7 @@ async function handleSingleModelChat(
|
||||
apiKeyInfo,
|
||||
userAgent,
|
||||
comboName,
|
||||
onCredentialsRefreshed: async (newCreds) => {
|
||||
onCredentialsRefreshed: async (newCreds: any) => {
|
||||
await updateProviderCredentials(credentials.connectionId, {
|
||||
accessToken: newCreds.accessToken,
|
||||
refreshToken: newCreds.refreshToken,
|
||||
@@ -351,7 +351,7 @@ async function handleSingleModelChat(
|
||||
} catch (cbErr) {
|
||||
if (cbErr instanceof CircuitBreakerOpenError) {
|
||||
log.warn("CIRCUIT", `${provider} circuit open during retry: ${cbErr.message}`);
|
||||
return unavailableResponse(
|
||||
return (unavailableResponse as any)(
|
||||
HTTP_STATUS.SERVICE_UNAVAILABLE,
|
||||
`Provider ${provider} circuit breaker is open`,
|
||||
Math.ceil(cbErr.retryAfterMs / 1000)
|
||||
@@ -425,12 +425,12 @@ async function handleSingleModelChat(
|
||||
// ──── Extracted helpers (T-28) ────
|
||||
|
||||
function handleNoCredentials(
|
||||
credentials,
|
||||
excludeConnectionId,
|
||||
provider,
|
||||
model,
|
||||
lastError,
|
||||
lastStatus
|
||||
credentials: any,
|
||||
excludeConnectionId: string | null,
|
||||
provider: string,
|
||||
model: string,
|
||||
lastError: string | null,
|
||||
lastStatus: number | null
|
||||
) {
|
||||
if (credentials?.allRateLimited) {
|
||||
const errorMsg = lastError || credentials.lastError || "Unavailable";
|
||||
@@ -455,10 +455,10 @@ function handleNoCredentials(
|
||||
);
|
||||
}
|
||||
|
||||
async function safeResolveProxy(connectionId) {
|
||||
async function safeResolveProxy(connectionId: string) {
|
||||
try {
|
||||
return await resolveProxyForConnection(connectionId);
|
||||
} catch (proxyErr) {
|
||||
} catch (proxyErr: any) {
|
||||
log.debug("PROXY", `Failed to resolve proxy: ${proxyErr.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@omniroute/open-sse/config/providerModels.js";
|
||||
import { logProxyEvent } from "../../lib/proxyLogger";
|
||||
import { logTranslationEvent } from "../../lib/translatorEvents";
|
||||
import { updateProviderCredentials } from "../services/auth";
|
||||
// updateProviderCredentials is dynamically imported from ../services/auth when needed
|
||||
|
||||
const HTTP_STATUS = {
|
||||
BAD_REQUEST: 400,
|
||||
@@ -34,17 +34,17 @@ const HTTP_STATUS = {
|
||||
* @param {Function} errorResponse - Error response factory
|
||||
* @returns {Promise<{ error?: Response, provider: string, model: string, sourceFormat: string, targetFormat: string }>}
|
||||
*/
|
||||
export async function resolveModelOrError(modelStr, body, log, errorResponse) {
|
||||
export async function resolveModelOrError(modelStr: string, body: any, log: any, errorResponse: Function) {
|
||||
const modelInfo = await getModelInfo(modelStr);
|
||||
|
||||
if (!modelInfo.provider) {
|
||||
if (modelInfo.errorType === "ambiguous_model") {
|
||||
if ((modelInfo as any).errorType === "ambiguous_model") {
|
||||
const message =
|
||||
modelInfo.errorMessage ||
|
||||
(modelInfo as any).errorMessage ||
|
||||
`Ambiguous model '${modelStr}'. Use provider/model prefix (ex: gh/${modelStr} or cc/${modelStr}).`;
|
||||
log.warn("CHAT", message, {
|
||||
model: modelStr,
|
||||
candidates: modelInfo.candidateAliases || modelInfo.candidateProviders || [],
|
||||
candidates: (modelInfo as any).candidateAliases || (modelInfo as any).candidateProviders || [],
|
||||
});
|
||||
return { error: errorResponse(HTTP_STATUS.BAD_REQUEST, message) };
|
||||
}
|
||||
@@ -155,7 +155,8 @@ export function buildChatCoreParams({
|
||||
apiKeyInfo,
|
||||
userAgent,
|
||||
comboName,
|
||||
onCredentialsRefreshed: async (newCreds) => {
|
||||
onCredentialsRefreshed: async (newCreds: any) => {
|
||||
const { updateProviderCredentials } = await import("../services/tokenRefresh");
|
||||
await updateProviderCredentials(credentials.connectionId, {
|
||||
accessToken: newCreds.accessToken,
|
||||
refreshToken: newCreds.refreshToken,
|
||||
|
||||
@@ -19,42 +19,41 @@ import {
|
||||
|
||||
export const TOKEN_EXPIRY_BUFFER_MS = BUFFER_MS;
|
||||
|
||||
// Wrap functions with local logger
|
||||
export const refreshAccessToken = (provider, refreshToken, credentials) =>
|
||||
export const refreshAccessToken = (provider: string, refreshToken: string, credentials: any) =>
|
||||
_refreshAccessToken(provider, refreshToken, credentials, log);
|
||||
|
||||
export const refreshClaudeOAuthToken = (refreshToken) =>
|
||||
export const refreshClaudeOAuthToken = (refreshToken: string) =>
|
||||
_refreshClaudeOAuthToken(refreshToken, log);
|
||||
|
||||
export const refreshGoogleToken = (refreshToken, clientId, clientSecret) =>
|
||||
export const refreshGoogleToken = (refreshToken: string, clientId: string, clientSecret: string) =>
|
||||
_refreshGoogleToken(refreshToken, clientId, clientSecret, log);
|
||||
|
||||
export const refreshQwenToken = (refreshToken) => _refreshQwenToken(refreshToken, log);
|
||||
export const refreshQwenToken = (refreshToken: string) => _refreshQwenToken(refreshToken, log);
|
||||
|
||||
export const refreshCodexToken = (refreshToken) => _refreshCodexToken(refreshToken, log);
|
||||
export const refreshCodexToken = (refreshToken: string) => _refreshCodexToken(refreshToken, log);
|
||||
|
||||
export const refreshIflowToken = (refreshToken) => _refreshIflowToken(refreshToken, log);
|
||||
export const refreshIflowToken = (refreshToken: string) => _refreshIflowToken(refreshToken, log);
|
||||
|
||||
export const refreshGitHubToken = (refreshToken) => _refreshGitHubToken(refreshToken, log);
|
||||
export const refreshGitHubToken = (refreshToken: string) => _refreshGitHubToken(refreshToken, log);
|
||||
|
||||
export const refreshCopilotToken = (githubAccessToken) =>
|
||||
export const refreshCopilotToken = (githubAccessToken: string) =>
|
||||
_refreshCopilotToken(githubAccessToken, log);
|
||||
|
||||
export const getAccessToken = (provider, credentials) =>
|
||||
export const getAccessToken = (provider: string, credentials: any) =>
|
||||
_getAccessToken(provider, credentials, log);
|
||||
|
||||
export const refreshTokenByProvider = (provider, credentials) =>
|
||||
export const refreshTokenByProvider = (provider: string, credentials: any) =>
|
||||
_refreshTokenByProvider(provider, credentials, log);
|
||||
|
||||
export const formatProviderCredentials = (provider, credentials) =>
|
||||
export const formatProviderCredentials = (provider: string, credentials: any) =>
|
||||
_formatProviderCredentials(provider, credentials, log);
|
||||
|
||||
export const getAllAccessTokens = (userInfo) => _getAllAccessTokens(userInfo, log);
|
||||
export const getAllAccessTokens = (userInfo: any) => _getAllAccessTokens(userInfo, log);
|
||||
|
||||
// Local-specific: Update credentials in localDb
|
||||
export async function updateProviderCredentials(connectionId, newCredentials) {
|
||||
export async function updateProviderCredentials(connectionId: string, newCredentials: any) {
|
||||
try {
|
||||
const updates = {};
|
||||
const updates: Record<string, any> = {};
|
||||
|
||||
if (newCredentials.accessToken) {
|
||||
updates.accessToken = newCredentials.accessToken;
|
||||
@@ -79,14 +78,14 @@ export async function updateProviderCredentials(connectionId, newCredentials) {
|
||||
} catch (error) {
|
||||
log.error("TOKEN_REFRESH", "Error updating credentials in localDb", {
|
||||
connectionId,
|
||||
error: error.message,
|
||||
error: (error as any).message,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Local-specific: Check and refresh token proactively
|
||||
export async function checkAndRefreshToken(provider, credentials) {
|
||||
export async function checkAndRefreshToken(provider: string, credentials: any) {
|
||||
let updatedCredentials = { ...credentials };
|
||||
|
||||
// Check regular token expiry
|
||||
@@ -150,7 +149,7 @@ export async function checkAndRefreshToken(provider, credentials) {
|
||||
}
|
||||
|
||||
// Local-specific: Refresh GitHub and Copilot tokens together
|
||||
export async function refreshGitHubAndCopilotTokens(credentials) {
|
||||
export async function refreshGitHubAndCopilotTokens(credentials: any) {
|
||||
const newGitHubCredentials = await refreshGitHubToken(credentials.refreshToken);
|
||||
if (newGitHubCredentials?.accessToken) {
|
||||
const copilotToken = await refreshCopilotToken(newGitHubCredentials.accessToken);
|
||||
|
||||
Reference in New Issue
Block a user