chore: resolve merge conflicts with feature-antigravity

This commit is contained in:
diegosouzapw
2026-04-01 08:47:05 -03:00
10 changed files with 60 additions and 109 deletions

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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");

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;