mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
feat(proxy): move proxy configuration to dedicated System → Proxy page (#1907)
Integrated into release/v3.7.9
This commit is contained in:
@@ -538,9 +538,7 @@ export const setBudgetGuardTool: McpToolDefinition<
|
||||
// --- Tool 11: omniroute_set_routing_strategy ---
|
||||
export const setRoutingStrategyInput = z.object({
|
||||
comboId: z.string().describe("Combo ID or name to update"),
|
||||
strategy: z
|
||||
.enum(ROUTING_STRATEGY_VALUES)
|
||||
.describe("Routing strategy to apply"),
|
||||
strategy: z.enum(ROUTING_STRATEGY_VALUES).describe("Routing strategy to apply"),
|
||||
autoRoutingStrategy: z
|
||||
.enum(AUTO_ROUTING_STRATEGY_VALUES)
|
||||
.optional()
|
||||
|
||||
@@ -457,7 +457,9 @@ export async function handleSetRoutingStrategy(args: {
|
||||
name: toString(updatedCombo.name, toString(combo.name, comboId)),
|
||||
strategy: toString(updatedCombo.strategy, normalizedStrategy),
|
||||
autoRoutingStrategy:
|
||||
toString(updatedCombo.strategy, normalizedStrategy) === "auto" ? resolvedAutoStrategy : null,
|
||||
toString(updatedCombo.strategy, normalizedStrategy) === "auto"
|
||||
? resolvedAutoStrategy
|
||||
: null,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -15,8 +15,4 @@ export {
|
||||
export { getTaskFitness, getTaskTypes } from "./taskFitness";
|
||||
export { SelfHealingManager, getSelfHealingManager } from "./selfHealing";
|
||||
export { MODE_PACKS, getModePack, getModePackNames } from "./modePacks";
|
||||
export {
|
||||
selectProvider,
|
||||
type AutoComboConfig,
|
||||
type SelectionResult,
|
||||
} from "./engine";
|
||||
export { selectProvider, type AutoComboConfig, type SelectionResult } from "./engine";
|
||||
|
||||
@@ -665,14 +665,18 @@ function sortTargetsByContextSize(targets: ResolvedComboTarget[]) {
|
||||
.filter((target): target is ResolvedComboTarget => target !== null);
|
||||
}
|
||||
|
||||
function getP2CTargetScore(target: ResolvedComboTarget, metrics: ReturnType<typeof getComboMetrics>): number {
|
||||
function getP2CTargetScore(
|
||||
target: ResolvedComboTarget,
|
||||
metrics: ReturnType<typeof getComboMetrics>
|
||||
): number {
|
||||
const breakerState = getCircuitBreaker(target.provider)?.getStatus?.()?.state;
|
||||
if (breakerState === "OPEN") return -Infinity;
|
||||
const modelMetric = metrics?.byModel?.[target.modelStr] || null;
|
||||
const successRate = Number(modelMetric?.successRate);
|
||||
const avgLatency = Number(modelMetric?.avgLatencyMs);
|
||||
const successScore = Number.isFinite(successRate) ? successRate / 100 : 0.5;
|
||||
const latencyScore = Number.isFinite(avgLatency) && avgLatency > 0 ? 1 / Math.log10(avgLatency + 10) : 0.25;
|
||||
const latencyScore =
|
||||
Number.isFinite(avgLatency) && avgLatency > 0 ? 1 / Math.log10(avgLatency + 10) : 0.25;
|
||||
const breakerPenalty = breakerState === "HALF_OPEN" ? 0.25 : 0;
|
||||
return successScore + latencyScore - breakerPenalty;
|
||||
}
|
||||
@@ -1465,7 +1469,10 @@ export async function handleComboChat({
|
||||
orderedTargets = fisherYatesShuffle([...orderedTargets]);
|
||||
log.info("COMBO", `Random shuffle: ${orderedTargets.length} targets`);
|
||||
} else if (strategy === "fill-first") {
|
||||
log.info("COMBO", `Fill-first ordering: preserving priority order (${orderedTargets.length} targets)`);
|
||||
log.info(
|
||||
"COMBO",
|
||||
`Fill-first ordering: preserving priority order (${orderedTargets.length} targets)`
|
||||
);
|
||||
} else if (strategy === "p2c") {
|
||||
orderedTargets = orderTargetsByPowerOfTwoChoices(orderedTargets, combo.name);
|
||||
log.info("COMBO", `Power-of-two-choices ordering: selected ${orderedTargets[0]?.modelStr}`);
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const metadata = {
|
||||
title: "Compression",
|
||||
description: "Configure context compression settings to reduce token usage and costs.",
|
||||
};
|
||||
|
||||
export default function CompressionPage() {
|
||||
redirect("/dashboard/context/caveman");
|
||||
}
|
||||
|
||||
@@ -104,7 +104,8 @@ export default function ComboDefaultsTab() {
|
||||
setComboDefaults((prev) => ({
|
||||
...prev,
|
||||
...sanitizeComboRuntimeConfig(comboData.comboDefaults),
|
||||
strategy: comboData.comboDefaults?.strategy ?? settingsData.fallbackStrategy ?? prev.strategy,
|
||||
strategy:
|
||||
comboData.comboDefaults?.strategy ?? settingsData.fallbackStrategy ?? prev.strategy,
|
||||
stickyRoundRobinLimit:
|
||||
settingsData.stickyRoundRobinLimit ??
|
||||
comboData.comboDefaults?.stickyRoundRobinLimit ??
|
||||
|
||||
@@ -10,7 +10,6 @@ import SystemStorageTab from "./components/SystemStorageTab";
|
||||
import SecurityTab from "./components/SecurityTab";
|
||||
import RoutingTab from "./components/RoutingTab";
|
||||
import ComboDefaultsTab from "./components/ComboDefaultsTab";
|
||||
import ProxyTab from "./components/ProxyTab";
|
||||
import AppearanceTab from "./components/AppearanceTab";
|
||||
import ThinkingBudgetTab from "./components/ThinkingBudgetTab";
|
||||
import SystemPromptTab from "./components/SystemPromptTab";
|
||||
@@ -23,7 +22,6 @@ import ResilienceTab from "./components/ResilienceTab";
|
||||
import CliproxyapiSettingsTab from "./components/CliproxyapiSettingsTab";
|
||||
import PayloadRulesTab from "./components/PayloadRulesTab";
|
||||
import VisionBridgeSettingsTab from "./components/VisionBridgeSettingsTab";
|
||||
import MitmProxyTab from "./components/MitmProxyTab";
|
||||
import ModelRoutingSection from "@/shared/components/ModelRoutingSection";
|
||||
|
||||
const tabs = [
|
||||
@@ -33,7 +31,6 @@ const tabs = [
|
||||
{ id: "security", labelKey: "security", icon: "shield" },
|
||||
{ id: "routing", labelKey: "routing", icon: "route" },
|
||||
{ id: "resilience", labelKey: "resilience", icon: "electrical_services" },
|
||||
{ id: "mitm", labelKey: "mitmProxy", icon: "lan" },
|
||||
{ id: "advanced", labelKey: "advanced", icon: "tune" },
|
||||
];
|
||||
|
||||
@@ -138,12 +135,9 @@ export default function SettingsPage() {
|
||||
|
||||
{activeTab === "resilience" && <ResilienceTab />}
|
||||
|
||||
{activeTab === "mitm" && <MitmProxyTab />}
|
||||
|
||||
{activeTab === "advanced" && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<PayloadRulesTab />
|
||||
<ProxyTab />
|
||||
<CliproxyapiSettingsTab />
|
||||
</div>
|
||||
)}
|
||||
|
||||
79
src/app/(dashboard)/dashboard/system/proxy/page.tsx
Normal file
79
src/app/(dashboard)/dashboard/system/proxy/page.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { cn } from "@/shared/utils/cn";
|
||||
import ProxyTab from "@/app/(dashboard)/dashboard/settings/components/ProxyTab";
|
||||
import MitmProxyTab from "@/app/(dashboard)/dashboard/settings/components/MitmProxyTab";
|
||||
import OneproxyTab from "@/app/(dashboard)/dashboard/settings/components/OneproxyTab";
|
||||
|
||||
const subTabs = [
|
||||
{ id: "http", labelKey: "httpProxy", icon: "dns" },
|
||||
{ id: "mitm", labelKey: "mitmProxy", icon: "lan" },
|
||||
{ id: "oneproxy", labelKey: "1proxy", icon: "public" },
|
||||
];
|
||||
|
||||
export default function ProxyPage() {
|
||||
const t = useTranslations("settings");
|
||||
const [activeSubTab, setActiveSubTab] = useState("http");
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto min-w-0">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="w-full overflow-x-auto pb-1">
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label={t("proxySubTabsAria")}
|
||||
className="inline-flex items-center p-1 rounded-lg bg-black/5 dark:bg-white/5 min-w-max"
|
||||
>
|
||||
{subTabs.map((subTab) => (
|
||||
<button
|
||||
key={subTab.id}
|
||||
role="tab"
|
||||
aria-selected={activeSubTab === subTab.id}
|
||||
tabIndex={activeSubTab === subTab.id ? 0 : -1}
|
||||
onClick={() => setActiveSubTab(subTab.id)}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-4 py-2 rounded-md font-medium transition-all text-sm",
|
||||
activeSubTab === subTab.id
|
||||
? "bg-white dark:bg-white/10 text-text-main shadow-sm"
|
||||
: "text-text-muted hover:text-text-main"
|
||||
)}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[18px]" aria-hidden="true">
|
||||
{subTab.icon}
|
||||
</span>
|
||||
<span className="whitespace-nowrap">
|
||||
{subTab.id === "http" ? t("proxy") : t(subTab.labelKey)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
role="tabpanel"
|
||||
aria-label={t(subTabs.find((t) => t.id === activeSubTab)?.labelKey || "proxy")}
|
||||
>
|
||||
{activeSubTab === "http" && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<ProxyTab />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeSubTab === "mitm" && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<MitmProxyTab />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeSubTab === "oneproxy" && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<OneproxyTab />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -341,9 +341,6 @@ export {
|
||||
setReasoningCache,
|
||||
getReasoningCache,
|
||||
deleteReasoningCache,
|
||||
cleanupExpiredReasoning,
|
||||
getReasoningCacheStats,
|
||||
getReasoningCacheEntries,
|
||||
clearAllReasoningCache,
|
||||
} from "./db/reasoningCache";
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export const HIDEABLE_SIDEBAR_ITEM_IDS = [
|
||||
"audit",
|
||||
"webhooks",
|
||||
"health",
|
||||
"proxy",
|
||||
"settings",
|
||||
"docs",
|
||||
"issues",
|
||||
@@ -109,6 +110,7 @@ const SYSTEM_SIDEBAR_ITEMS: readonly SidebarItemDefinition[] = [
|
||||
{ id: "audit", href: "/dashboard/audit", i18nKey: "auditLog", icon: "policy" },
|
||||
{ id: "webhooks", href: "/dashboard/webhooks", i18nKey: "webhooks", icon: "webhook" },
|
||||
{ id: "health", href: "/dashboard/health", i18nKey: "health", icon: "health_and_safety" },
|
||||
{ id: "proxy", href: "/dashboard/system/proxy", i18nKey: "proxy", icon: "dns" },
|
||||
{ id: "settings", href: "/dashboard/settings", i18nKey: "settings", icon: "settings" },
|
||||
];
|
||||
|
||||
|
||||
@@ -39,9 +39,7 @@ export const comboSchema = z.object({
|
||||
name: z.string().min(1, "Combo name is required").max(100),
|
||||
model: z.string().min(1, "Model pattern is required"),
|
||||
endpoint: z.enum(["chat", "embeddings", "images"]).default("chat"),
|
||||
strategy: z
|
||||
.enum(ROUTING_STRATEGY_VALUES)
|
||||
.default("priority"),
|
||||
strategy: z.enum(ROUTING_STRATEGY_VALUES).default("priority"),
|
||||
nodes: z.array(comboNodeSchema).min(1, "At least one node is required"),
|
||||
isActive: z.boolean().default(true),
|
||||
maxRetries: z.number().int().min(0).max(10).default(2),
|
||||
|
||||
@@ -138,7 +138,10 @@ test("auto strategy handles null and empty prompt edge cases without throwing",
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
await selectedModelFor(combo, { model: combo.name, messages: [{ role: "user", content: null }] }),
|
||||
await selectedModelFor(combo, {
|
||||
model: combo.name,
|
||||
messages: [{ role: "user", content: null }],
|
||||
}),
|
||||
"openai/gpt-4"
|
||||
);
|
||||
assert.equal(await selectedModelFor(combo, { model: combo.name, messages: [] }), "openai/gpt-4");
|
||||
|
||||
@@ -527,12 +527,23 @@ test("CodexExecutor.transformRequest strips raw internal assistant commentary wi
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
result.input.some((item) => JSON.stringify(item).includes("Visible assistant history without phase")),
|
||||
result.input.some((item) =>
|
||||
JSON.stringify(item).includes("Visible assistant history without phase")
|
||||
),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
result.input.some((item) => item.type === "reasoning"),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
result.input.some((item) => item.type === "function_call"),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
result.input.some((item) => item.type === "function_call_output"),
|
||||
true
|
||||
);
|
||||
assert.equal(result.input.some((item) => item.type === "reasoning"), true);
|
||||
assert.equal(result.input.some((item) => item.type === "function_call"), true);
|
||||
assert.equal(result.input.some((item) => item.type === "function_call_output"), true);
|
||||
});
|
||||
|
||||
test("CodexExecutor.transformRequest strips internal assistant commentary before mapping messages to input", () => {
|
||||
|
||||
Reference in New Issue
Block a user