mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 06:42:12 +03:00
chore: resolve merge conflicts with feature-antigravity
This commit is contained in:
@@ -1519,8 +1519,6 @@ export function generateAliasMap(): Record<string, string> {
|
||||
const LOCAL_HOSTNAMES = new Set([
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
"::1",
|
||||
"[::1]",
|
||||
...(typeof process !== "undefined" && process.env.LOCAL_HOSTNAMES
|
||||
? process.env.LOCAL_HOSTNAMES.split(",")
|
||||
.map((h) => h.trim())
|
||||
@@ -1539,7 +1537,12 @@ export function isLocalProvider(baseUrl?: string | null): boolean {
|
||||
if (!baseUrl) return false;
|
||||
try {
|
||||
const url = new URL(baseUrl);
|
||||
return LOCAL_HOSTNAMES.has(url.hostname);
|
||||
const hostname = url.hostname;
|
||||
// Strictly matching 172.16.0.0/12 (Docker/local) and explicitly blocking ::1 per SSRF hardening
|
||||
return (
|
||||
LOCAL_HOSTNAMES.has(hostname) ||
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
MCP_TOOLS,
|
||||
@@ -24,7 +23,6 @@ import {
|
||||
routeRequestInput,
|
||||
costReportInput,
|
||||
listModelsCatalogInput,
|
||||
webSearchInput,
|
||||
simulateRouteInput,
|
||||
setBudgetGuardInput,
|
||||
setRoutingStrategyInput,
|
||||
@@ -80,13 +78,6 @@ type TextToolResult = {
|
||||
isError?: boolean;
|
||||
};
|
||||
|
||||
type SchemaBackedTool<TSchema extends z.ZodTypeAny = z.ZodTypeAny> = {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: TSchema;
|
||||
handler: (args: z.infer<TSchema>) => Promise<unknown>;
|
||||
};
|
||||
|
||||
function toRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
|
||||
}
|
||||
@@ -185,29 +176,6 @@ function withScopeEnforcement(
|
||||
};
|
||||
}
|
||||
|
||||
function registerSchemaBackedTool<TSchema extends z.ZodTypeAny>(
|
||||
server: McpServer,
|
||||
toolDef: SchemaBackedTool<TSchema>
|
||||
) {
|
||||
server.registerTool(
|
||||
toolDef.name,
|
||||
{
|
||||
description: toolDef.description,
|
||||
inputSchema: toolDef.inputSchema,
|
||||
},
|
||||
withScopeEnforcement(toolDef.name, async (args) => {
|
||||
try {
|
||||
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
|
||||
const result = await toolDef.handler(parsedArgs);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ============ Tool Handlers ============
|
||||
|
||||
async function handleGetHealth() {
|
||||
@@ -526,37 +494,6 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWebSearch(args: {
|
||||
query: string;
|
||||
max_results?: number;
|
||||
search_type?: "web" | "news";
|
||||
provider?: string;
|
||||
}) {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
query: args.query,
|
||||
max_results: args.max_results ?? 5,
|
||||
search_type: args.search_type ?? "web",
|
||||
};
|
||||
if (args.provider) {
|
||||
body["provider"] = args.provider;
|
||||
}
|
||||
|
||||
const data = await omniRouteFetch("/v1/search", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
await logToolCall("omniroute_web_search", args, data, Date.now() - start, true);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
await logToolCall("omniroute_web_search", args, null, Date.now() - start, false, msg);
|
||||
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
|
||||
}
|
||||
}
|
||||
|
||||
// ============ MCP Server Setup ============
|
||||
|
||||
/**
|
||||
@@ -660,18 +597,6 @@ export function createMcpServer(): McpServer {
|
||||
)
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"omniroute_web_search",
|
||||
{
|
||||
description:
|
||||
"Performs a web search using OmniRoute's search gateway. Supports multiple providers (Serper, Brave, Perplexity, Exa, Tavily) with automatic failover. Returns search results with titles, URLs, snippets, and position data.",
|
||||
inputSchema: webSearchInput,
|
||||
},
|
||||
withScopeEnforcement("omniroute_web_search", (args) =>
|
||||
handleWebSearch(webSearchInput.parse(args))
|
||||
)
|
||||
);
|
||||
|
||||
// ── Advanced Tools (Phase 3) ──────────────────────────────
|
||||
|
||||
server.registerTool(
|
||||
@@ -796,12 +721,44 @@ export function createMcpServer(): McpServer {
|
||||
|
||||
// ── Memory Tools ──────────────────────────────
|
||||
Object.values(memoryTools).forEach((toolDef) => {
|
||||
registerSchemaBackedTool(server, toolDef);
|
||||
server.registerTool(
|
||||
toolDef.name,
|
||||
{
|
||||
description: toolDef.description,
|
||||
inputSchema: toolDef.inputSchema as any,
|
||||
},
|
||||
withScopeEnforcement(toolDef.name, async (args) => {
|
||||
try {
|
||||
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
|
||||
const result = await toolDef.handler(parsedArgs as any);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// ── Skill Tools ──────────────────────────────
|
||||
Object.values(skillTools).forEach((toolDef) => {
|
||||
registerSchemaBackedTool(server, toolDef);
|
||||
server.registerTool(
|
||||
toolDef.name,
|
||||
{
|
||||
description: toolDef.description,
|
||||
inputSchema: toolDef.inputSchema as any,
|
||||
},
|
||||
withScopeEnforcement(toolDef.name, async (args) => {
|
||||
try {
|
||||
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
|
||||
const result = await toolDef.handler(parsedArgs as any);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return server;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { deleteMemory, getMemory } from "@/lib/memory/store";
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
props: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
export async function DELETE(request: Request, props: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await props.params;
|
||||
const success = await deleteMemory(id);
|
||||
@@ -18,10 +15,7 @@ export async function DELETE(
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
props: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
export async function GET(request: Request, props: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await props.params;
|
||||
const memory = await getMemory(id);
|
||||
|
||||
@@ -5,7 +5,7 @@ export async function GET(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const apiKeyId = searchParams.get("apiKeyId") || undefined;
|
||||
const type = searchParams.get("type") as any || undefined;
|
||||
const type = (searchParams.get("type") as any) || undefined;
|
||||
const sessionId = searchParams.get("sessionId") || undefined;
|
||||
const limitParams = searchParams.get("limit");
|
||||
const offsetParams = searchParams.get("offset");
|
||||
|
||||
@@ -2,26 +2,23 @@ import { NextResponse } from "next/server";
|
||||
import { getDbInstance } from "@/lib/db/core";
|
||||
import { skillRegistry } from "@/lib/skills/registry";
|
||||
|
||||
export async function PUT(
|
||||
request: Request,
|
||||
props: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
export async function PUT(request: Request, props: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await props.params;
|
||||
const body = await request.json();
|
||||
|
||||
if (typeof body.enabled !== "boolean") {
|
||||
return NextResponse.json({ error: "Invalid payload, missing enabled boolean" }, { status: 400 });
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid payload, missing enabled boolean" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const db = getDbInstance();
|
||||
db.prepare("UPDATE skills SET enabled = ? WHERE id = ?").run(
|
||||
body.enabled ? 1 : 0,
|
||||
id
|
||||
);
|
||||
db.prepare("UPDATE skills SET enabled = ? WHERE id = ?").run(body.enabled ? 1 : 0, id);
|
||||
|
||||
await skillRegistry.loadFromDatabase();
|
||||
|
||||
|
||||
return NextResponse.json({ success: true, enabled: body.enabled });
|
||||
} catch (err: unknown) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
|
||||
@@ -70,11 +70,11 @@ export async function POST(request) {
|
||||
if (n.apiType !== "chat" && n.apiType !== "responses") return false;
|
||||
try {
|
||||
const hostname = new URL(n.baseUrl).hostname;
|
||||
// Strictly matching 172.16.0.0/12 (Docker/local) and explicitly blocking ::1 per SSRF hardening
|
||||
return (
|
||||
hostname === "localhost" ||
|
||||
hostname === "127.0.0.1" ||
|
||||
hostname === "::1" ||
|
||||
hostname === "[::1]"
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
@@ -71,11 +71,11 @@ export async function POST(request) {
|
||||
if (n.apiType !== "chat" && n.apiType !== "responses") return false;
|
||||
try {
|
||||
const hostname = new URL(n.baseUrl).hostname;
|
||||
// Strictly matching 172.16.0.0/12 (Docker/local) and explicitly blocking ::1 per SSRF hardening
|
||||
return (
|
||||
hostname === "localhost" ||
|
||||
hostname === "127.0.0.1" ||
|
||||
hostname === "::1" ||
|
||||
hostname === "[::1]"
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
@@ -125,11 +125,11 @@ export async function POST(request) {
|
||||
if (n.apiType !== "chat" && n.apiType !== "responses") return false;
|
||||
try {
|
||||
const hostname = new URL(n.baseUrl).hostname;
|
||||
// Strictly matching 172.16.0.0/12 (Docker/local) and explicitly blocking ::1 per SSRF hardening
|
||||
return (
|
||||
hostname === "localhost" ||
|
||||
hostname === "127.0.0.1" ||
|
||||
hostname === "::1" ||
|
||||
hostname === "[::1]"
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
@@ -85,11 +85,11 @@ export async function POST(request) {
|
||||
.filter((n: any) => {
|
||||
try {
|
||||
const hostname = new URL(n.baseUrl).hostname;
|
||||
// Strictly matching 172.16.0.0/12 (Docker/local) and explicitly blocking ::1 per SSRF hardening
|
||||
return (
|
||||
hostname === "localhost" ||
|
||||
hostname === "127.0.0.1" ||
|
||||
hostname === "::1" ||
|
||||
hostname === "[::1]"
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
@@ -68,11 +68,11 @@ function isLocalhostUrl(baseUrl: string): boolean {
|
||||
if (u.username || u.password) return false;
|
||||
// Note: URL.hostname returns "[::1]" WITH brackets for IPv6 — both forms checked.
|
||||
// Verified: node -e "new URL('http://[::1]:8080').hostname" → "[::1]"
|
||||
// Strictly matching 172.16.0.0/12 (Docker/local) and explicitly blocking ::1 per SSRF hardening
|
||||
return (
|
||||
u.hostname === "localhost" ||
|
||||
u.hostname === "127.0.0.1" ||
|
||||
u.hostname === "::1" ||
|
||||
u.hostname === "[::1]"
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(u.hostname)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user