3924 lines
130 KiB
JavaScript
3924 lines
130 KiB
JavaScript
// open-sse/mcp-server/server.ts
|
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
|
|
// open-sse/mcp-server/schemas/tools.ts
|
|
import { z } from "zod";
|
|
var getHealthInput = z.object({}).describe("No parameters required");
|
|
var getHealthOutput = z.object({
|
|
uptime: z.string(),
|
|
version: z.string(),
|
|
memoryUsage: z.object({
|
|
heapUsed: z.number(),
|
|
heapTotal: z.number(),
|
|
}),
|
|
circuitBreakers: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
state: z.enum(["CLOSED", "OPEN", "HALF_OPEN"]),
|
|
failureCount: z.number(),
|
|
lastFailure: z.string().nullable(),
|
|
})
|
|
),
|
|
rateLimits: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
rpm: z.number(),
|
|
currentUsage: z.number(),
|
|
isLimited: z.boolean(),
|
|
})
|
|
),
|
|
cacheStats: z
|
|
.object({
|
|
hits: z.number(),
|
|
misses: z.number(),
|
|
hitRate: z.number(),
|
|
})
|
|
.optional(),
|
|
});
|
|
var getHealthTool = {
|
|
name: "omniroute_get_health",
|
|
description:
|
|
"Returns the current health status of OmniRoute including uptime, memory usage, circuit breaker states for all providers, rate limit status, and cache statistics.",
|
|
inputSchema: getHealthInput,
|
|
outputSchema: getHealthOutput,
|
|
scopes: ["read:health"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/monitoring/health", "/api/resilience", "/api/rate-limits"],
|
|
};
|
|
var listCombosInput = z.object({
|
|
includeMetrics: z
|
|
.boolean()
|
|
.optional()
|
|
.describe("Include request count, success rate, latency, and cost metrics per combo"),
|
|
});
|
|
var listCombosOutput = z.object({
|
|
combos: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
models: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
model: z.string(),
|
|
priority: z.number(),
|
|
})
|
|
),
|
|
strategy: z.enum([
|
|
"priority",
|
|
"weighted",
|
|
"round-robin",
|
|
"strict-random",
|
|
"random",
|
|
"least-used",
|
|
"cost-optimized",
|
|
"auto",
|
|
]),
|
|
enabled: z.boolean(),
|
|
metrics: z
|
|
.object({
|
|
requestCount: z.number(),
|
|
successRate: z.number(),
|
|
avgLatencyMs: z.number(),
|
|
totalCost: z.number(),
|
|
})
|
|
.optional(),
|
|
})
|
|
),
|
|
});
|
|
var listCombosTool = {
|
|
name: "omniroute_list_combos",
|
|
description:
|
|
"Lists all configured combos (model chains) with their strategies and optionally includes performance metrics. Combos define how requests are routed across multiple providers.",
|
|
inputSchema: listCombosInput,
|
|
outputSchema: listCombosOutput,
|
|
scopes: ["read:combos"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/combos", "/api/combos/metrics"],
|
|
};
|
|
var getComboMetricsInput = z.object({
|
|
comboId: z.string().describe("ID of the combo to get metrics for"),
|
|
});
|
|
var getComboMetricsOutput = z.object({
|
|
requests: z.number(),
|
|
successRate: z.number(),
|
|
avgLatency: z.number(),
|
|
costTotal: z.number(),
|
|
fallbackCount: z.number(),
|
|
byProvider: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
requests: z.number(),
|
|
successRate: z.number(),
|
|
avgLatency: z.number(),
|
|
})
|
|
),
|
|
});
|
|
var getComboMetricsTool = {
|
|
name: "omniroute_get_combo_metrics",
|
|
description:
|
|
"Returns detailed performance metrics for a specific combo including request count, success rate, average latency, total cost, and per-provider breakdowns.",
|
|
inputSchema: getComboMetricsInput,
|
|
outputSchema: getComboMetricsOutput,
|
|
scopes: ["read:combos"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/combos/metrics"],
|
|
};
|
|
var switchComboInput = z.object({
|
|
comboId: z.string().describe("ID of the combo to activate/deactivate"),
|
|
active: z.boolean().describe("Whether to enable or disable the combo"),
|
|
});
|
|
var switchComboOutput = z.object({
|
|
success: z.boolean(),
|
|
combo: z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
enabled: z.boolean(),
|
|
}),
|
|
});
|
|
var switchComboTool = {
|
|
name: "omniroute_switch_combo",
|
|
description:
|
|
"Activates or deactivates a combo. When deactivated, requests will not be routed through this combo. Use to toggle between different routing strategies.",
|
|
inputSchema: switchComboInput,
|
|
outputSchema: switchComboOutput,
|
|
scopes: ["write:combos"],
|
|
auditLevel: "full",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/combos"],
|
|
};
|
|
var checkQuotaInput = z.object({
|
|
provider: z
|
|
.string()
|
|
.optional()
|
|
.describe(
|
|
"Filter by provider name (e.g., 'claude', 'gemini'). If omitted, returns all providers."
|
|
),
|
|
connectionId: z.string().optional().describe("Filter by specific connection ID"),
|
|
});
|
|
var checkQuotaOutput = z.object({
|
|
providers: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
provider: z.string(),
|
|
connectionId: z.string(),
|
|
quotaUsed: z.number(),
|
|
quotaTotal: z.number().nullable(),
|
|
percentRemaining: z.number(),
|
|
resetAt: z.string().nullable(),
|
|
tokenStatus: z.enum(["valid", "expiring", "expired", "refreshing"]),
|
|
})
|
|
),
|
|
meta: z
|
|
.object({
|
|
generatedAt: z.string(),
|
|
filters: z.object({
|
|
provider: z.string().nullable(),
|
|
connectionId: z.string().nullable(),
|
|
}),
|
|
totalProviders: z.number(),
|
|
})
|
|
.optional(),
|
|
});
|
|
var checkQuotaTool = {
|
|
name: "omniroute_check_quota",
|
|
description:
|
|
"Checks the remaining API quota for one or all providers. Returns quota used/total, percentage remaining, reset time, and token health status.",
|
|
inputSchema: checkQuotaInput,
|
|
outputSchema: checkQuotaOutput,
|
|
scopes: ["read:quota"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/usage/quota", "/api/token-health", "/api/rate-limits"],
|
|
};
|
|
var routeRequestInput = z.object({
|
|
model: z.string().describe("Model identifier (e.g., 'claude-sonnet-4', 'gpt-4o')"),
|
|
messages: z
|
|
.array(
|
|
z.object({
|
|
role: z.string(),
|
|
content: z.string(),
|
|
})
|
|
)
|
|
.describe("Chat messages in OpenAI format"),
|
|
combo: z.string().optional().describe("Specific combo to route through"),
|
|
budget: z.number().optional().describe("Maximum cost in USD for this request"),
|
|
role: z
|
|
.enum(["coding", "review", "planning", "analysis"])
|
|
.optional()
|
|
.describe("Task role hint for intelligent routing"),
|
|
stream: z.boolean().optional().default(false).describe("Whether to stream the response"),
|
|
});
|
|
var routeRequestOutput = z.object({
|
|
response: z.object({
|
|
content: z.string(),
|
|
model: z.string(),
|
|
tokens: z.object({
|
|
prompt: z.number(),
|
|
completion: z.number(),
|
|
}),
|
|
}),
|
|
routing: z.object({
|
|
provider: z.string(),
|
|
combo: z.string().nullable(),
|
|
fallbacksTriggered: z.number(),
|
|
cost: z.number(),
|
|
latencyMs: z.number(),
|
|
routingExplanation: z.string(),
|
|
}),
|
|
});
|
|
var routeRequestTool = {
|
|
name: "omniroute_route_request",
|
|
description:
|
|
"Sends a chat completion request through OmniRoute's intelligent routing pipeline. Supports combo selection, budget limits, and task role hints for optimal provider matching.",
|
|
inputSchema: routeRequestInput,
|
|
outputSchema: routeRequestOutput,
|
|
scopes: ["execute:completions"],
|
|
auditLevel: "full",
|
|
phase: 1,
|
|
sourceEndpoints: ["/v1/chat/completions", "/v1/responses"],
|
|
};
|
|
var costReportInput = z.object({
|
|
period: z
|
|
.enum(["session", "day", "week", "month"])
|
|
.optional()
|
|
.default("session")
|
|
.describe("Time period for the cost report"),
|
|
});
|
|
var costReportOutput = z.object({
|
|
period: z.string(),
|
|
totalCost: z.number(),
|
|
requestCount: z.number(),
|
|
tokenCount: z.object({
|
|
prompt: z.number(),
|
|
completion: z.number(),
|
|
}),
|
|
byProvider: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
cost: z.number(),
|
|
requests: z.number(),
|
|
})
|
|
),
|
|
byModel: z.array(
|
|
z.object({
|
|
model: z.string(),
|
|
cost: z.number(),
|
|
requests: z.number(),
|
|
})
|
|
),
|
|
budget: z.object({
|
|
limit: z.number().nullable(),
|
|
remaining: z.number().nullable(),
|
|
}),
|
|
});
|
|
var costReportTool = {
|
|
name: "omniroute_cost_report",
|
|
description:
|
|
"Generates a cost report for the specified period showing total cost, request count, token usage, and breakdowns by provider and model. Also shows budget status if configured.",
|
|
inputSchema: costReportInput,
|
|
outputSchema: costReportOutput,
|
|
scopes: ["read:usage"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/usage/analytics", "/api/usage/budget"],
|
|
};
|
|
var listModelsCatalogInput = z.object({
|
|
provider: z.string().optional().describe("Filter by provider name"),
|
|
capability: z
|
|
.enum(["chat", "embedding", "image", "audio", "video", "rerank", "moderation"])
|
|
.optional()
|
|
.describe("Filter by model capability"),
|
|
});
|
|
var listModelsCatalogOutput = z.object({
|
|
models: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
provider: z.string(),
|
|
capabilities: z.array(z.string()),
|
|
status: z.enum(["available", "degraded", "unavailable"]),
|
|
pricing: z
|
|
.object({
|
|
inputPerMillion: z.number().nullable(),
|
|
outputPerMillion: z.number().nullable(),
|
|
})
|
|
.optional(),
|
|
})
|
|
),
|
|
});
|
|
var listModelsCatalogTool = {
|
|
name: "omniroute_list_models_catalog",
|
|
description:
|
|
"Lists all available AI models across all providers with their capabilities, current status, and pricing information.",
|
|
inputSchema: listModelsCatalogInput,
|
|
outputSchema: listModelsCatalogOutput,
|
|
scopes: ["read:models"],
|
|
auditLevel: "none",
|
|
phase: 1,
|
|
sourceEndpoints: ["/api/models/catalog", "/v1/models"],
|
|
};
|
|
var webSearchInput = z.object({
|
|
query: z
|
|
.string()
|
|
.min(1, "Query is required")
|
|
.max(1e3, "Query must be 1000 characters or fewer")
|
|
.describe("The search query string"),
|
|
max_results: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(20)
|
|
.default(5)
|
|
.describe("Maximum number of search results to return"),
|
|
search_type: z.enum(["web", "news"]).default("web").describe("Type of search to perform"),
|
|
provider: z
|
|
.string()
|
|
.optional()
|
|
.describe("Specific search provider to use (serper, brave, perplexity, exa, tavily)"),
|
|
});
|
|
var webSearchOutput = z.object({
|
|
id: z.string(),
|
|
provider: z.string(),
|
|
query: z.string(),
|
|
results: z.array(
|
|
z.object({
|
|
title: z.string(),
|
|
url: z.string(),
|
|
display_url: z.string().optional(),
|
|
snippet: z.string(),
|
|
position: z.number().int().positive(),
|
|
})
|
|
),
|
|
cached: z.boolean(),
|
|
usage: z.object({
|
|
queries_used: z.number().int().min(0),
|
|
search_cost_usd: z.number().min(0),
|
|
}),
|
|
});
|
|
var webSearchTool = {
|
|
name: "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,
|
|
outputSchema: webSearchOutput,
|
|
scopes: ["execute:search"],
|
|
auditLevel: "basic",
|
|
phase: 1,
|
|
sourceEndpoints: ["/v1/search"],
|
|
};
|
|
var simulateRouteInput = z.object({
|
|
model: z.string().describe("Target model for simulation"),
|
|
promptTokenEstimate: z.number().describe("Estimated prompt token count"),
|
|
combo: z.string().optional().describe("Specific combo to simulate (default: active combo)"),
|
|
});
|
|
var simulateRouteOutput = z.object({
|
|
simulatedPath: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
model: z.string(),
|
|
probability: z.number(),
|
|
estimatedCost: z.number(),
|
|
healthStatus: z.enum(["CLOSED", "OPEN", "HALF_OPEN"]),
|
|
quotaAvailable: z.number(),
|
|
})
|
|
),
|
|
fallbackTree: z.object({
|
|
primary: z.string(),
|
|
fallbacks: z.array(z.string()),
|
|
worstCaseCost: z.number(),
|
|
bestCaseCost: z.number(),
|
|
}),
|
|
});
|
|
var simulateRouteTool = {
|
|
name: "omniroute_simulate_route",
|
|
description:
|
|
"Simulates (dry-run) the routing path a request would take without actually executing it. Shows the fallback tree, provider probabilities, estimated costs, and health status.",
|
|
inputSchema: simulateRouteInput,
|
|
outputSchema: simulateRouteOutput,
|
|
scopes: ["read:health", "read:combos"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/combos", "/api/monitoring/health", "/api/resilience"],
|
|
};
|
|
var setBudgetGuardInput = z.object({
|
|
maxCost: z.number().describe("Maximum cost in USD for this session"),
|
|
action: z.enum(["degrade", "block", "alert"]).describe("Action when budget is exceeded"),
|
|
degradeToTier: z
|
|
.enum(["cheap", "free"])
|
|
.optional()
|
|
.describe("If action=degrade, which tier to fall back to"),
|
|
});
|
|
var setBudgetGuardOutput = z.object({
|
|
sessionId: z.string(),
|
|
budgetTotal: z.number(),
|
|
budgetSpent: z.number(),
|
|
budgetRemaining: z.number(),
|
|
action: z.string(),
|
|
status: z.enum(["active", "warning", "exceeded"]),
|
|
});
|
|
var setBudgetGuardTool = {
|
|
name: "omniroute_set_budget_guard",
|
|
description:
|
|
"Sets a budget guard that limits spending for the current session. When the budget is reached, it can degrade to cheaper models, block requests, or send alerts.",
|
|
inputSchema: setBudgetGuardInput,
|
|
outputSchema: setBudgetGuardOutput,
|
|
scopes: ["write:budget"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/usage/budget"],
|
|
};
|
|
var setRoutingStrategyInput = z.object({
|
|
comboId: z.string().describe("Combo ID or name to update"),
|
|
strategy: z
|
|
.enum([
|
|
"priority",
|
|
"weighted",
|
|
"round-robin",
|
|
"strict-random",
|
|
"random",
|
|
"least-used",
|
|
"cost-optimized",
|
|
"auto",
|
|
])
|
|
.describe("Routing strategy to apply"),
|
|
autoRoutingStrategy: z
|
|
.enum(["rules", "cost", "eco", "latency", "fast"])
|
|
.optional()
|
|
.describe("Optional strategy used by auto mode (only used when strategy='auto')"),
|
|
});
|
|
var setRoutingStrategyOutput = z.object({
|
|
success: z.boolean(),
|
|
combo: z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
strategy: z.string(),
|
|
autoRoutingStrategy: z.string().nullable(),
|
|
}),
|
|
});
|
|
var setRoutingStrategyTool = {
|
|
name: "omniroute_set_routing_strategy",
|
|
description:
|
|
"Updates a combo routing strategy (priority/weighted/auto/etc.) at runtime. Supports selecting the sub-strategy used by auto mode (rules/cost/latency).",
|
|
inputSchema: setRoutingStrategyInput,
|
|
outputSchema: setRoutingStrategyOutput,
|
|
scopes: ["write:combos"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/combos", "/api/combos/{id}"],
|
|
};
|
|
var setResilienceProfileInput = z.object({
|
|
profile: z
|
|
.enum(["aggressive", "balanced", "conservative"])
|
|
.describe("Resilience profile to apply"),
|
|
});
|
|
var setResilienceProfileOutput = z.object({
|
|
applied: z.boolean(),
|
|
settings: z.object({
|
|
circuitBreakerThreshold: z.number(),
|
|
retryCount: z.number(),
|
|
timeoutMs: z.number(),
|
|
fallbackDepth: z.number(),
|
|
}),
|
|
});
|
|
var setResilienceProfileTool = {
|
|
name: "omniroute_set_resilience_profile",
|
|
description:
|
|
"Applies a resilience profile that adjusts circuit breaker thresholds, retry counts, timeouts, and fallback depth. 'aggressive' = fast fail, 'conservative' = max retries.",
|
|
inputSchema: setResilienceProfileInput,
|
|
outputSchema: setResilienceProfileOutput,
|
|
scopes: ["write:resilience"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/resilience"],
|
|
};
|
|
var testComboInput = z.object({
|
|
comboId: z.string().describe("ID of the combo to test"),
|
|
testPrompt: z.string().max(500).describe("Short test prompt (max 500 chars)"),
|
|
});
|
|
var testComboOutput = z.object({
|
|
results: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
model: z.string(),
|
|
success: z.boolean(),
|
|
latencyMs: z.number(),
|
|
cost: z.number(),
|
|
tokenCount: z.number(),
|
|
error: z.string().optional(),
|
|
})
|
|
),
|
|
summary: z.object({
|
|
totalProviders: z.number(),
|
|
successful: z.number(),
|
|
fastestProvider: z.string(),
|
|
cheapestProvider: z.string(),
|
|
}),
|
|
});
|
|
var testComboTool = {
|
|
name: "omniroute_test_combo",
|
|
description:
|
|
"Tests a combo by sending a short test prompt to each provider in the combo and reporting individual results including latency, cost, and success status.",
|
|
inputSchema: testComboInput,
|
|
outputSchema: testComboOutput,
|
|
scopes: ["execute:completions", "read:combos"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/combos/test", "/v1/chat/completions"],
|
|
};
|
|
var getProviderMetricsInput = z.object({
|
|
provider: z.string().describe("Provider name (e.g., 'claude', 'gemini-cli', 'codex')"),
|
|
});
|
|
var getProviderMetricsOutput = z.object({
|
|
provider: z.string(),
|
|
successRate: z.number(),
|
|
requestCount: z.number(),
|
|
avgLatencyMs: z.number(),
|
|
p50LatencyMs: z.number(),
|
|
p95LatencyMs: z.number(),
|
|
p99LatencyMs: z.number(),
|
|
errorRate: z.number(),
|
|
lastError: z
|
|
.object({
|
|
message: z.string(),
|
|
timestamp: z.string(),
|
|
})
|
|
.nullable(),
|
|
circuitBreakerState: z.enum(["CLOSED", "OPEN", "HALF_OPEN"]),
|
|
quotaInfo: z.object({
|
|
used: z.number(),
|
|
total: z.number().nullable(),
|
|
resetAt: z.string().nullable(),
|
|
}),
|
|
});
|
|
var getProviderMetricsTool = {
|
|
name: "omniroute_get_provider_metrics",
|
|
description:
|
|
"Returns detailed performance metrics for a specific provider including success/error rates, latency percentiles (p50/p95/p99), circuit breaker state, and quota information.",
|
|
inputSchema: getProviderMetricsInput,
|
|
outputSchema: getProviderMetricsOutput,
|
|
scopes: ["read:health"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/provider-metrics", "/api/resilience"],
|
|
};
|
|
var bestComboForTaskInput = z.object({
|
|
taskType: z
|
|
.enum(["coding", "review", "planning", "analysis", "debugging", "documentation"])
|
|
.describe("Type of task to find the best combo for"),
|
|
budgetConstraint: z.number().optional().describe("Maximum cost in USD"),
|
|
latencyConstraint: z.number().optional().describe("Maximum acceptable latency in ms"),
|
|
});
|
|
var bestComboForTaskOutput = z.object({
|
|
recommendedCombo: z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
reason: z.string(),
|
|
}),
|
|
alternatives: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
tradeoff: z.string(),
|
|
})
|
|
),
|
|
freeAlternative: z
|
|
.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
})
|
|
.nullable(),
|
|
});
|
|
var bestComboForTaskTool = {
|
|
name: "omniroute_best_combo_for_task",
|
|
description:
|
|
"Recommends the best combo for a given task type (coding, review, planning, etc.) considering budget and latency constraints. Also suggests alternatives and free options.",
|
|
inputSchema: bestComboForTaskInput,
|
|
outputSchema: bestComboForTaskOutput,
|
|
scopes: ["read:combos", "read:health"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/combos", "/api/combos/metrics", "/api/monitoring/health"],
|
|
};
|
|
var explainRouteInput = z.object({
|
|
requestId: z.string().describe("Request ID from the X-Request-Id header"),
|
|
});
|
|
var explainRouteOutput = z.object({
|
|
requestId: z.string(),
|
|
decision: z.object({
|
|
comboUsed: z.string(),
|
|
providerSelected: z.string(),
|
|
modelUsed: z.string(),
|
|
score: z.number(),
|
|
factors: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
value: z.number(),
|
|
weight: z.number(),
|
|
contribution: z.number(),
|
|
})
|
|
),
|
|
fallbacksTriggered: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
reason: z.string(),
|
|
})
|
|
),
|
|
costActual: z.number(),
|
|
latencyActual: z.number(),
|
|
}),
|
|
});
|
|
var explainRouteTool = {
|
|
name: "omniroute_explain_route",
|
|
description:
|
|
"Explains why a specific request was routed to a particular provider. Shows the scoring factors, weights, fallbacks triggered, actual cost, and latency.",
|
|
inputSchema: explainRouteInput,
|
|
outputSchema: explainRouteOutput,
|
|
scopes: ["read:health", "read:usage"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: [],
|
|
};
|
|
var getSessionSnapshotInput = z.object({}).describe("No parameters required");
|
|
var getSessionSnapshotOutput = z.object({
|
|
sessionStart: z.string(),
|
|
duration: z.string(),
|
|
requestCount: z.number(),
|
|
costTotal: z.number(),
|
|
tokenCount: z.object({
|
|
prompt: z.number(),
|
|
completion: z.number(),
|
|
}),
|
|
topModels: z.array(
|
|
z.object({
|
|
model: z.string(),
|
|
count: z.number(),
|
|
})
|
|
),
|
|
topProviders: z.array(
|
|
z.object({
|
|
provider: z.string(),
|
|
count: z.number(),
|
|
})
|
|
),
|
|
errors: z.number(),
|
|
fallbacks: z.number(),
|
|
budgetGuard: z
|
|
.object({
|
|
active: z.boolean(),
|
|
remaining: z.number(),
|
|
})
|
|
.nullable(),
|
|
});
|
|
var getSessionSnapshotTool = {
|
|
name: "omniroute_get_session_snapshot",
|
|
description:
|
|
"Returns a snapshot of the current working session including duration, request count, total cost, top models/providers used, error count, and budget guard status.",
|
|
inputSchema: getSessionSnapshotInput,
|
|
outputSchema: getSessionSnapshotOutput,
|
|
scopes: ["read:usage"],
|
|
auditLevel: "none",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/usage/analytics", "/api/telemetry/summary"],
|
|
};
|
|
var syncPricingInput = z.object({
|
|
sources: z
|
|
.array(z.string())
|
|
.optional()
|
|
.describe("External pricing sources to sync from (default: ['litellm'])"),
|
|
dryRun: z
|
|
.boolean()
|
|
.optional()
|
|
.describe("If true, preview sync results without saving to database"),
|
|
});
|
|
var syncPricingOutput = z.object({
|
|
success: z.boolean(),
|
|
modelCount: z.number(),
|
|
providerCount: z.number(),
|
|
source: z.string(),
|
|
dryRun: z.boolean(),
|
|
error: z.string().optional(),
|
|
warnings: z.array(z.string()).optional(),
|
|
data: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
|
|
});
|
|
var syncPricingTool = {
|
|
name: "omniroute_sync_pricing",
|
|
description:
|
|
"Syncs pricing data from external sources (LiteLLM) into OmniRoute. Synced pricing fills gaps not covered by hardcoded defaults without overwriting user-set prices. Use dryRun=true to preview.",
|
|
inputSchema: syncPricingInput,
|
|
outputSchema: syncPricingOutput,
|
|
scopes: ["pricing:write"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/pricing/sync"],
|
|
};
|
|
var cacheStatsInput = z.object({}).describe("No parameters required");
|
|
var cacheStatsOutput = z.object({
|
|
semanticCache: z.object({
|
|
memoryEntries: z.number(),
|
|
dbEntries: z.number(),
|
|
hits: z.number(),
|
|
misses: z.number(),
|
|
hitRate: z.string(),
|
|
tokensSaved: z.number(),
|
|
}),
|
|
promptCache: z
|
|
.object({
|
|
totalRequests: z.number(),
|
|
requestsWithCacheControl: z.number(),
|
|
totalCachedTokens: z.number(),
|
|
totalCacheCreationTokens: z.number(),
|
|
estimatedCostSaved: z.number(),
|
|
})
|
|
.nullable(),
|
|
idempotency: z.object({
|
|
activeKeys: z.number(),
|
|
windowMs: z.number(),
|
|
}),
|
|
});
|
|
var cacheStatsTool = {
|
|
name: "omniroute_cache_stats",
|
|
description:
|
|
"Returns cache statistics including semantic cache hit rate, prompt cache metrics by provider, and idempotency layer stats.",
|
|
inputSchema: cacheStatsInput,
|
|
outputSchema: cacheStatsOutput,
|
|
scopes: ["read:cache"],
|
|
auditLevel: "basic",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/cache"],
|
|
};
|
|
var cacheFlushInput = z.object({
|
|
signature: z.string().optional().describe("Specific cache signature to invalidate"),
|
|
model: z.string().optional().describe("Invalidate all entries for a specific model"),
|
|
});
|
|
var cacheFlushOutput = z.object({
|
|
ok: z.boolean(),
|
|
invalidated: z.number().optional(),
|
|
scope: z.string().optional(),
|
|
});
|
|
var cacheFlushTool = {
|
|
name: "omniroute_cache_flush",
|
|
description:
|
|
"Flush cache entries. Provide signature to invalidate a single entry, model to invalidate all entries for a model, or omit both to clear all.",
|
|
inputSchema: cacheFlushInput,
|
|
outputSchema: cacheFlushOutput,
|
|
scopes: ["write:cache"],
|
|
auditLevel: "full",
|
|
phase: 2,
|
|
sourceEndpoints: ["/api/cache"],
|
|
};
|
|
var MCP_TOOLS = [
|
|
getHealthTool,
|
|
listCombosTool,
|
|
getComboMetricsTool,
|
|
switchComboTool,
|
|
checkQuotaTool,
|
|
routeRequestTool,
|
|
costReportTool,
|
|
listModelsCatalogTool,
|
|
webSearchTool,
|
|
simulateRouteTool,
|
|
setBudgetGuardTool,
|
|
setRoutingStrategyTool,
|
|
setResilienceProfileTool,
|
|
testComboTool,
|
|
getProviderMetricsTool,
|
|
bestComboForTaskTool,
|
|
explainRouteTool,
|
|
getSessionSnapshotTool,
|
|
syncPricingTool,
|
|
cacheStatsTool,
|
|
cacheFlushTool,
|
|
];
|
|
var MCP_ESSENTIAL_TOOLS = MCP_TOOLS.filter((t) => t.phase === 1);
|
|
var MCP_ADVANCED_TOOLS = MCP_TOOLS.filter((t) => t.phase === 2);
|
|
var MCP_TOOL_MAP = Object.fromEntries(MCP_TOOLS.map((t) => [t.name, t]));
|
|
|
|
// open-sse/mcp-server/runtimeHeartbeat.ts
|
|
import { promises as fs } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { join } from "node:path";
|
|
var HEARTBEAT_FILE = "mcp-heartbeat.json";
|
|
var RUNTIME_DIR = "runtime";
|
|
var DEFAULT_INTERVAL_MS = 5e3;
|
|
function resolveDataDir() {
|
|
const configured = process.env.DATA_DIR;
|
|
if (typeof configured === "string" && configured.trim().length > 0) {
|
|
return configured.trim();
|
|
}
|
|
return join(homedir(), ".omniroute");
|
|
}
|
|
function resolveMcpHeartbeatPath() {
|
|
return join(resolveDataDir(), RUNTIME_DIR, HEARTBEAT_FILE);
|
|
}
|
|
async function writeHeartbeat(snapshot) {
|
|
const heartbeatPath = resolveMcpHeartbeatPath();
|
|
const runtimeDir = join(resolveDataDir(), RUNTIME_DIR);
|
|
await fs.mkdir(runtimeDir, { recursive: true });
|
|
await fs.writeFile(heartbeatPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
}
|
|
function startMcpHeartbeat(config) {
|
|
const startedAt = /* @__PURE__ */ new Date().toISOString();
|
|
let timer = null;
|
|
let stopped = false;
|
|
const intervalMs =
|
|
typeof config.intervalMs === "number" && config.intervalMs > 0
|
|
? config.intervalMs
|
|
: DEFAULT_INTERVAL_MS;
|
|
const tick = async () => {
|
|
if (stopped) return;
|
|
const snapshot = {
|
|
pid: process.pid,
|
|
startedAt,
|
|
lastHeartbeatAt: /* @__PURE__ */ new Date().toISOString(),
|
|
version: config.version,
|
|
transport: "stdio",
|
|
scopesEnforced: config.scopesEnforced,
|
|
allowedScopes: [...config.allowedScopes],
|
|
toolCount: config.toolCount,
|
|
};
|
|
try {
|
|
await writeHeartbeat(snapshot);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
console.error("[MCP Heartbeat] Failed to write heartbeat:", message);
|
|
}
|
|
};
|
|
void tick();
|
|
timer = setInterval(() => {
|
|
void tick();
|
|
}, intervalMs);
|
|
return () => {
|
|
if (stopped) return;
|
|
stopped = true;
|
|
if (timer) {
|
|
clearInterval(timer);
|
|
timer = null;
|
|
}
|
|
void tick();
|
|
};
|
|
}
|
|
|
|
// open-sse/mcp-server/schemas/audit.ts
|
|
async function hashInput(input) {
|
|
const data = JSON.stringify(input);
|
|
const encoder = new TextEncoder();
|
|
const hashBuffer = await crypto.subtle.digest("SHA-256", encoder.encode(data));
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
}
|
|
function summarizeOutput(output, maxLength = 200) {
|
|
if (output === null || output === void 0) return "(null)";
|
|
const str = typeof output === "string" ? output : JSON.stringify(output);
|
|
if (str.length <= maxLength) return str;
|
|
return str.slice(0, maxLength) + "\u2026";
|
|
}
|
|
|
|
// open-sse/mcp-server/audit.ts
|
|
var db = null;
|
|
async function getDb() {
|
|
if (db) return db;
|
|
try {
|
|
const { homedir: homedir2 } = await import("node:os");
|
|
const { join: join2 } = await import("node:path");
|
|
const { existsSync } = await import("node:fs");
|
|
const dbPath = process.env.DATA_DIR
|
|
? join2(process.env.DATA_DIR, "storage.sqlite")
|
|
: join2(homedir2(), ".omniroute", "storage.sqlite");
|
|
if (!existsSync(dbPath)) {
|
|
console.error(`[MCP Audit] Database not found at ${dbPath} \u2014 audit logging disabled`);
|
|
return null;
|
|
}
|
|
const Database2 = (await import("better-sqlite3")).default;
|
|
db = new Database2(dbPath);
|
|
return db;
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
console.error("[MCP Audit] Failed to connect to database:", message);
|
|
return null;
|
|
}
|
|
}
|
|
async function logToolCall(toolName, input, output, durationMs, success, errorCode) {
|
|
try {
|
|
const database = await getDb();
|
|
if (!database) return;
|
|
const inputHash = await hashInput(input);
|
|
const outputSummary = summarizeOutput(output);
|
|
const apiKeyId = process.env.OMNIROUTE_API_KEY_ID || null;
|
|
database
|
|
.prepare(
|
|
`INSERT INTO mcp_tool_audit (tool_name, input_hash, output_summary, duration_ms, api_key_id, success, error_code)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
)
|
|
.run(
|
|
toolName,
|
|
inputHash,
|
|
outputSummary,
|
|
durationMs,
|
|
apiKeyId,
|
|
success ? 1 : 0,
|
|
errorCode || null
|
|
);
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
console.error("[MCP Audit] Failed to log:", message);
|
|
}
|
|
}
|
|
|
|
// open-sse/mcp-server/scopeEnforcement.ts
|
|
function normalizeScopeList(raw) {
|
|
if (!Array.isArray(raw)) return [];
|
|
const normalized = raw
|
|
.filter((value) => typeof value === "string")
|
|
.map((value) => value.trim())
|
|
.filter(Boolean);
|
|
return Array.from(new Set(normalized));
|
|
}
|
|
function extractMetaScopeList(meta) {
|
|
if (!meta || typeof meta !== "object") return [];
|
|
const metaRecord = meta;
|
|
const direct = normalizeScopeList(metaRecord.scopes);
|
|
if (direct.length > 0) return direct;
|
|
const auth = metaRecord.auth;
|
|
if (auth && typeof auth === "object") {
|
|
const authScopes = normalizeScopeList(auth.scopes);
|
|
if (authScopes.length > 0) return authScopes;
|
|
}
|
|
const omni = metaRecord.omniroute;
|
|
if (omni && typeof omni === "object") {
|
|
const omniScopes = normalizeScopeList(omni.scopes);
|
|
if (omniScopes.length > 0) return omniScopes;
|
|
}
|
|
return [];
|
|
}
|
|
function scopeMatches(grantedScope, requiredScope) {
|
|
if (grantedScope === "*" || grantedScope === requiredScope) {
|
|
return true;
|
|
}
|
|
if (grantedScope.endsWith("*")) {
|
|
const prefix = grantedScope.slice(0, -1);
|
|
return requiredScope.startsWith(prefix);
|
|
}
|
|
return false;
|
|
}
|
|
function resolveCallerScopeContext(extra, fallbackScopes = []) {
|
|
const callerId =
|
|
(typeof extra?.authInfo?.clientId === "string" && extra.authInfo.clientId.trim()) ||
|
|
(typeof extra?.sessionId === "string" && extra.sessionId.trim()) ||
|
|
"anonymous";
|
|
const authScopes = normalizeScopeList(extra?.authInfo?.scopes);
|
|
if (authScopes.length > 0) {
|
|
return { callerId, scopes: authScopes, source: "authInfo" };
|
|
}
|
|
const metaScopes = extractMetaScopeList(extra?._meta);
|
|
if (metaScopes.length > 0) {
|
|
return { callerId, scopes: metaScopes, source: "meta" };
|
|
}
|
|
const fallback = normalizeScopeList(fallbackScopes);
|
|
if (fallback.length > 0) {
|
|
return { callerId, scopes: fallback, source: "env" };
|
|
}
|
|
return { callerId, scopes: [], source: "none" };
|
|
}
|
|
function evaluateToolScopes(toolName, callerScopes, enforceScopes) {
|
|
const toolDef = MCP_TOOL_MAP[toolName];
|
|
if (!toolDef) {
|
|
return {
|
|
allowed: false,
|
|
required: [],
|
|
provided: Array.from(callerScopes),
|
|
missing: [],
|
|
reason: "tool_definition_missing",
|
|
};
|
|
}
|
|
const required = Array.isArray(toolDef.scopes) ? Array.from(toolDef.scopes) : [];
|
|
const provided = normalizeScopeList(callerScopes);
|
|
if (!enforceScopes || required.length === 0) {
|
|
return { allowed: true, required, provided, missing: [] };
|
|
}
|
|
const missing = required.filter(
|
|
(requiredScope) => !provided.some((grantedScope) => scopeMatches(grantedScope, requiredScope))
|
|
);
|
|
return {
|
|
allowed: missing.length === 0,
|
|
required,
|
|
provided,
|
|
missing,
|
|
reason: missing.length > 0 ? "missing_scopes" : void 0,
|
|
};
|
|
}
|
|
|
|
// src/shared/contracts/quota.ts
|
|
var TOKEN_STATUS_VALUES = ["valid", "expiring", "expired", "refreshing"];
|
|
function toNumber(value) {
|
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
if (typeof value === "string" && value.trim() !== "") {
|
|
const parsed = Number(value);
|
|
if (Number.isFinite(parsed)) return parsed;
|
|
}
|
|
return null;
|
|
}
|
|
function clamp(value, min, max) {
|
|
return Math.min(max, Math.max(min, value));
|
|
}
|
|
function normalizeTokenStatus(value) {
|
|
if (typeof value === "string" && TOKEN_STATUS_VALUES.includes(value)) {
|
|
return value;
|
|
}
|
|
return "valid";
|
|
}
|
|
function sanitizeQuotaProvider(input) {
|
|
const source = input && typeof input === "object" ? input : {};
|
|
const provider = typeof source.provider === "string" ? source.provider : "unknown";
|
|
const name = typeof source.name === "string" && source.name.trim() ? source.name : provider;
|
|
const connectionId =
|
|
typeof source.connectionId === "string" && source.connectionId.trim()
|
|
? source.connectionId
|
|
: "unknown";
|
|
const quotaTotalRaw = toNumber(source.quotaTotal);
|
|
const quotaTotal = quotaTotalRaw !== null && quotaTotalRaw >= 0 ? quotaTotalRaw : null;
|
|
const quotaUsedRaw = toNumber(source.quotaUsed) ?? 0;
|
|
const quotaUsed =
|
|
quotaTotal !== null ? clamp(quotaUsedRaw, 0, quotaTotal) : Math.max(0, quotaUsedRaw);
|
|
let percentRemainingRaw = toNumber(source.percentRemaining);
|
|
if (percentRemainingRaw === null) {
|
|
if (quotaTotal && quotaTotal > 0) {
|
|
percentRemainingRaw = ((quotaTotal - quotaUsed) / quotaTotal) * 100;
|
|
} else {
|
|
percentRemainingRaw = 100;
|
|
}
|
|
}
|
|
const percentRemaining = clamp(percentRemainingRaw, 0, 100);
|
|
const resetAt =
|
|
typeof source.resetAt === "string" && source.resetAt.trim() ? source.resetAt : null;
|
|
return {
|
|
name,
|
|
provider,
|
|
connectionId,
|
|
quotaUsed,
|
|
quotaTotal,
|
|
percentRemaining,
|
|
resetAt,
|
|
tokenStatus: normalizeTokenStatus(source.tokenStatus),
|
|
};
|
|
}
|
|
function normalizeQuotaResponse(raw, filters = {}) {
|
|
const source = raw && typeof raw === "object" ? raw : {};
|
|
const providersRaw = Array.isArray(source.providers)
|
|
? source.providers
|
|
: Array.isArray(raw)
|
|
? raw
|
|
: [];
|
|
const providers = providersRaw.map((entry) => sanitizeQuotaProvider(entry));
|
|
const sourceMeta = source.meta && typeof source.meta === "object" ? source.meta : {};
|
|
const sourceFilters =
|
|
sourceMeta.filters && typeof sourceMeta.filters === "object" ? sourceMeta.filters : {};
|
|
const providerFilter =
|
|
filters.provider ??
|
|
(typeof sourceFilters.provider === "string" && sourceFilters.provider.trim()
|
|
? sourceFilters.provider
|
|
: null);
|
|
const connectionFilter =
|
|
filters.connectionId ??
|
|
(typeof sourceFilters.connectionId === "string" && sourceFilters.connectionId.trim()
|
|
? sourceFilters.connectionId
|
|
: null);
|
|
const generatedAt =
|
|
typeof sourceMeta.generatedAt === "string" && sourceMeta.generatedAt.trim()
|
|
? sourceMeta.generatedAt
|
|
: /* @__PURE__ */ new Date().toISOString();
|
|
return {
|
|
providers,
|
|
meta: {
|
|
generatedAt,
|
|
filters: {
|
|
provider: providerFilter || null,
|
|
connectionId: connectionFilter || null,
|
|
},
|
|
totalProviders: providers.length,
|
|
},
|
|
};
|
|
}
|
|
|
|
// open-sse/mcp-server/tools/advancedTools.ts
|
|
var OMNIROUTE_BASE_URL = process.env.OMNIROUTE_BASE_URL || "http://localhost:20128";
|
|
var OMNIROUTE_API_KEY = process.env.OMNIROUTE_API_KEY || "";
|
|
async function apiFetch(path4, options = {}) {
|
|
const url = `${OMNIROUTE_BASE_URL}${path4}`;
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
...(OMNIROUTE_API_KEY ? { Authorization: `Bearer ${OMNIROUTE_API_KEY}` } : {}),
|
|
...(options.headers || {}),
|
|
};
|
|
const response = await fetch(url, { ...options, headers, signal: AbortSignal.timeout(3e4) });
|
|
if (!response.ok) {
|
|
const text = await response.text().catch(() => "Unknown error");
|
|
throw new Error(`API [${response.status}]: ${text}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
function isRecord(value) {
|
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
function toRecord(value) {
|
|
return isRecord(value) ? value : {};
|
|
}
|
|
function toArrayOfRecords(value) {
|
|
return Array.isArray(value) ? value.filter(isRecord) : [];
|
|
}
|
|
function toString(value, fallback = "") {
|
|
return typeof value === "string" ? value : fallback;
|
|
}
|
|
function toNumber2(value, fallback = 0) {
|
|
const parsed =
|
|
typeof value === "number"
|
|
? value
|
|
: typeof value === "string" && value.trim().length > 0
|
|
? Number(value)
|
|
: Number.NaN;
|
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
}
|
|
function getComboModels(combo) {
|
|
const directModels = toArrayOfRecords(combo.models);
|
|
const nestedModels = toArrayOfRecords(toRecord(combo.data).models);
|
|
const sourceModels = directModels.length > 0 ? directModels : nestedModels;
|
|
return sourceModels.map((model) => ({
|
|
provider: toString(model.provider, "unknown"),
|
|
model: toString(model.model, ""),
|
|
inputCostPer1M: toNumber2(model.inputCostPer1M, 3),
|
|
}));
|
|
}
|
|
function normalizeCombosResponse(raw) {
|
|
if (Array.isArray(raw)) return raw.filter(isRecord);
|
|
const source = toRecord(raw);
|
|
return Array.isArray(source.combos) ? source.combos.filter(isRecord) : [];
|
|
}
|
|
var activeBudgetGuard = null;
|
|
var RESILIENCE_PROFILES = {
|
|
aggressive: {
|
|
profiles: {
|
|
oauth: {
|
|
transientCooldown: 3e3,
|
|
rateLimitCooldown: 3e4,
|
|
maxBackoffLevel: 4,
|
|
circuitBreakerThreshold: 2,
|
|
circuitBreakerReset: 3e4,
|
|
},
|
|
apikey: {
|
|
transientCooldown: 2e3,
|
|
rateLimitCooldown: 0,
|
|
maxBackoffLevel: 3,
|
|
circuitBreakerThreshold: 3,
|
|
circuitBreakerReset: 15e3,
|
|
},
|
|
},
|
|
defaults: {
|
|
requestsPerMinute: 180,
|
|
minTimeBetweenRequests: 100,
|
|
concurrentRequests: 16,
|
|
},
|
|
},
|
|
balanced: {
|
|
profiles: {
|
|
oauth: {
|
|
transientCooldown: 5e3,
|
|
rateLimitCooldown: 6e4,
|
|
maxBackoffLevel: 8,
|
|
circuitBreakerThreshold: 3,
|
|
circuitBreakerReset: 6e4,
|
|
},
|
|
apikey: {
|
|
transientCooldown: 3e3,
|
|
rateLimitCooldown: 0,
|
|
maxBackoffLevel: 5,
|
|
circuitBreakerThreshold: 5,
|
|
circuitBreakerReset: 3e4,
|
|
},
|
|
},
|
|
defaults: {
|
|
requestsPerMinute: 100,
|
|
minTimeBetweenRequests: 200,
|
|
concurrentRequests: 10,
|
|
},
|
|
},
|
|
conservative: {
|
|
profiles: {
|
|
oauth: {
|
|
transientCooldown: 8e3,
|
|
rateLimitCooldown: 12e4,
|
|
maxBackoffLevel: 10,
|
|
circuitBreakerThreshold: 8,
|
|
circuitBreakerReset: 12e4,
|
|
},
|
|
apikey: {
|
|
transientCooldown: 5e3,
|
|
rateLimitCooldown: 3e4,
|
|
maxBackoffLevel: 8,
|
|
circuitBreakerThreshold: 8,
|
|
circuitBreakerReset: 6e4,
|
|
},
|
|
},
|
|
defaults: {
|
|
requestsPerMinute: 60,
|
|
minTimeBetweenRequests: 350,
|
|
concurrentRequests: 6,
|
|
},
|
|
},
|
|
};
|
|
var TASK_FITNESS = {
|
|
coding: { preferred: ["claude", "deepseek", "codex"], traits: ["fast", "code-optimized"] },
|
|
review: { preferred: ["claude", "gemini", "openai"], traits: ["analytical", "thorough"] },
|
|
planning: { preferred: ["gemini", "claude", "openai"], traits: ["reasoning", "structured"] },
|
|
analysis: { preferred: ["gemini", "claude"], traits: ["deep-reasoning", "large-context"] },
|
|
debugging: { preferred: ["claude", "deepseek", "codex"], traits: ["code-aware", "fast"] },
|
|
documentation: { preferred: ["gemini", "claude", "openai"], traits: ["clear", "structured"] },
|
|
};
|
|
async function handleSimulateRoute(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const [combosRaw, healthRaw, quotaRaw] = await Promise.allSettled([
|
|
apiFetch("/api/combos"),
|
|
apiFetch("/api/monitoring/health"),
|
|
apiFetch("/api/usage/quota"),
|
|
]);
|
|
const combos = combosRaw.status === "fulfilled" ? normalizeCombosResponse(combosRaw.value) : [];
|
|
const health = healthRaw.status === "fulfilled" ? toRecord(healthRaw.value) : {};
|
|
const quota =
|
|
quotaRaw.status === "fulfilled"
|
|
? normalizeQuotaResponse(quotaRaw.value)
|
|
: normalizeQuotaResponse({});
|
|
const targetCombo = args.combo
|
|
? combos.find(
|
|
(combo) => toString(combo.id) === args.combo || toString(combo.name) === args.combo
|
|
)
|
|
: combos.find((combo) => combo.enabled !== false);
|
|
if (!targetCombo) {
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify({ error: "No matching combo found" }) }],
|
|
isError: true,
|
|
};
|
|
}
|
|
const models = getComboModels(targetCombo);
|
|
const breakers = toArrayOfRecords(health.circuitBreakers);
|
|
const providers = quota.providers;
|
|
const simulatedPath = models.map((model, idx) => {
|
|
const cb = breakers.find((breaker) => toString(breaker.provider) === model.provider);
|
|
const q = providers.find((providerEntry) => providerEntry.provider === model.provider);
|
|
const estimatedCost = (args.promptTokenEstimate / 1e6) * model.inputCostPer1M;
|
|
return {
|
|
provider: model.provider,
|
|
model: model.model || args.model,
|
|
probability: idx === 0 ? 0.85 : 0.15 / Math.max(models.length - 1, 1),
|
|
estimatedCost: Math.round(estimatedCost * 1e4) / 1e4,
|
|
healthStatus: toString(cb?.state, "CLOSED"),
|
|
quotaAvailable: q?.percentRemaining ?? 100,
|
|
};
|
|
});
|
|
const costs = simulatedPath.map((pathEntry) => pathEntry.estimatedCost);
|
|
const result = {
|
|
simulatedPath,
|
|
fallbackTree: {
|
|
primary: simulatedPath[0]?.provider || "unknown",
|
|
fallbacks: simulatedPath.slice(1).map((pathEntry) => pathEntry.provider),
|
|
worstCaseCost: Math.max(...costs, 0),
|
|
bestCaseCost: Math.min(...costs, 0),
|
|
},
|
|
};
|
|
await logToolCall("omniroute_simulate_route", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_simulate_route", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleSetBudgetGuard(args) {
|
|
const start = Date.now();
|
|
try {
|
|
let spent = 0;
|
|
try {
|
|
const analytics = toRecord(await apiFetch("/api/usage/analytics?period=session"));
|
|
spent = toNumber2(analytics.totalCost, 0);
|
|
} catch {}
|
|
activeBudgetGuard = {
|
|
sessionId: `budget_${Date.now()}`,
|
|
maxCost: args.maxCost,
|
|
action: args.action,
|
|
degradeToTier: args.degradeToTier,
|
|
spent,
|
|
createdAt: /* @__PURE__ */ new Date().toISOString(),
|
|
};
|
|
const remaining = Math.max(0, args.maxCost - spent);
|
|
const result = {
|
|
sessionId: activeBudgetGuard.sessionId,
|
|
budgetTotal: args.maxCost,
|
|
budgetSpent: Math.round(spent * 1e4) / 1e4,
|
|
budgetRemaining: Math.round(remaining * 1e4) / 1e4,
|
|
action: args.action,
|
|
status: remaining <= 0 ? "exceeded" : remaining < args.maxCost * 0.2 ? "warning" : "active",
|
|
};
|
|
await logToolCall(
|
|
"omniroute_set_budget_guard",
|
|
{ maxCost: args.maxCost, action: args.action },
|
|
result,
|
|
Date.now() - start,
|
|
true
|
|
);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_set_budget_guard", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleSetRoutingStrategy(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const combos = normalizeCombosResponse(await apiFetch("/api/combos"));
|
|
const combo = combos.find(
|
|
(comboEntry) =>
|
|
toString(comboEntry.id) === args.comboId || toString(comboEntry.name) === args.comboId
|
|
);
|
|
if (!combo) {
|
|
const msg = `Combo '${args.comboId}' not found`;
|
|
await logToolCall(
|
|
"omniroute_set_routing_strategy",
|
|
args,
|
|
null,
|
|
Date.now() - start,
|
|
false,
|
|
msg
|
|
);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
const comboId = toString(combo.id);
|
|
if (!comboId) {
|
|
const msg = "Matched combo has no id";
|
|
await logToolCall(
|
|
"omniroute_set_routing_strategy",
|
|
args,
|
|
null,
|
|
Date.now() - start,
|
|
false,
|
|
msg
|
|
);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
const comboData = toRecord(combo.data);
|
|
const currentConfig = toRecord(
|
|
Object.keys(toRecord(combo.config)).length > 0 ? combo.config : comboData.config
|
|
);
|
|
let nextConfig = void 0;
|
|
if (args.strategy === "auto" && args.autoRoutingStrategy) {
|
|
const currentAutoConfig = toRecord(currentConfig.auto);
|
|
nextConfig = {
|
|
...currentConfig,
|
|
auto: {
|
|
...currentAutoConfig,
|
|
routingStrategy: args.autoRoutingStrategy,
|
|
},
|
|
};
|
|
}
|
|
const payload = { strategy: args.strategy };
|
|
if (nextConfig && Object.keys(nextConfig).length > 0) {
|
|
payload.config = nextConfig;
|
|
}
|
|
const updatedCombo = toRecord(
|
|
await apiFetch(`/api/combos/${encodeURIComponent(comboId)}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
})
|
|
);
|
|
const updatedConfig = toRecord(updatedCombo.config);
|
|
const resolvedAutoStrategy =
|
|
toString(toRecord(updatedConfig.auto).routingStrategy) ||
|
|
(args.strategy === "auto" ? (args.autoRoutingStrategy ?? "rules") : "");
|
|
const result = {
|
|
success: true,
|
|
combo: {
|
|
id: toString(updatedCombo.id, comboId),
|
|
name: toString(updatedCombo.name, toString(combo.name, comboId)),
|
|
strategy: toString(updatedCombo.strategy, args.strategy),
|
|
autoRoutingStrategy:
|
|
toString(updatedCombo.strategy, args.strategy) === "auto" ? resolvedAutoStrategy : null,
|
|
},
|
|
};
|
|
await logToolCall("omniroute_set_routing_strategy", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_set_routing_strategy", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleSetResilienceProfile(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const settings = RESILIENCE_PROFILES[args.profile];
|
|
if (!settings) {
|
|
return {
|
|
content: [{ type: "text", text: `Error: Invalid profile "${args.profile}"` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
await apiFetch("/api/resilience", {
|
|
method: "PATCH",
|
|
body: JSON.stringify({
|
|
profiles: settings.profiles,
|
|
defaults: settings.defaults,
|
|
}),
|
|
});
|
|
const result = { applied: true, profile: args.profile, settings };
|
|
await logToolCall("omniroute_set_resilience_profile", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall(
|
|
"omniroute_set_resilience_profile",
|
|
args,
|
|
null,
|
|
Date.now() - start,
|
|
false,
|
|
msg
|
|
);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleTestCombo(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const combos = normalizeCombosResponse(await apiFetch("/api/combos"));
|
|
const combo = combos.find(
|
|
(comboEntry) =>
|
|
toString(comboEntry.id) === args.comboId || toString(comboEntry.name) === args.comboId
|
|
);
|
|
if (!combo) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: JSON.stringify({ error: `Combo "${args.comboId}" not found` }),
|
|
},
|
|
],
|
|
isError: true,
|
|
};
|
|
}
|
|
const models = getComboModels(combo);
|
|
const prompt = (args.testPrompt || "Say hello").slice(0, 200);
|
|
const results = await Promise.allSettled(
|
|
models.map(async (model) => {
|
|
const providerStart = Date.now();
|
|
try {
|
|
const resp = toRecord(
|
|
await apiFetch("/v1/chat/completions", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
model: model.model || "auto",
|
|
messages: [{ role: "user", content: prompt }],
|
|
max_tokens: 50,
|
|
stream: false,
|
|
"x-provider": model.provider,
|
|
}),
|
|
})
|
|
);
|
|
const usage = toRecord(resp.usage);
|
|
return {
|
|
provider: model.provider,
|
|
model: model.model || toString(resp.model, "unknown"),
|
|
success: true,
|
|
latencyMs: Date.now() - providerStart,
|
|
cost: toNumber2(resp.cost, 0),
|
|
tokenCount: toNumber2(usage.prompt_tokens, 0) + toNumber2(usage.completion_tokens, 0),
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
provider: model.provider,
|
|
model: model.model || "unknown",
|
|
success: false,
|
|
latencyMs: Date.now() - providerStart,
|
|
cost: 0,
|
|
tokenCount: 0,
|
|
error: err instanceof Error ? err.message : String(err),
|
|
};
|
|
}
|
|
})
|
|
);
|
|
const providerResults = results.map((r) =>
|
|
r.status === "fulfilled"
|
|
? r.value
|
|
: {
|
|
provider: "unknown",
|
|
model: "unknown",
|
|
success: false,
|
|
latencyMs: 0,
|
|
cost: 0,
|
|
tokenCount: 0,
|
|
error: "Promise rejected",
|
|
}
|
|
);
|
|
const successful = providerResults.filter((r) => r.success);
|
|
const fastest = successful.sort((a, b) => a.latencyMs - b.latencyMs)[0];
|
|
const cheapest = successful.sort((a, b) => a.cost - b.cost)[0];
|
|
const result = {
|
|
results: providerResults,
|
|
summary: {
|
|
totalProviders: providerResults.length,
|
|
successful: successful.length,
|
|
fastestProvider: fastest?.provider || "none",
|
|
cheapestProvider: cheapest?.provider || "none",
|
|
},
|
|
};
|
|
await logToolCall(
|
|
"omniroute_test_combo",
|
|
{ comboId: args.comboId },
|
|
result.summary,
|
|
Date.now() - start,
|
|
true
|
|
);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_test_combo", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleGetProviderMetrics(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const [healthRaw, quotaRaw, analyticsRaw] = await Promise.allSettled([
|
|
apiFetch("/api/monitoring/health"),
|
|
apiFetch(`/api/usage/quota?provider=${encodeURIComponent(args.provider)}`),
|
|
apiFetch(`/api/usage/analytics?period=session&provider=${encodeURIComponent(args.provider)}`),
|
|
]);
|
|
const health = healthRaw.status === "fulfilled" ? toRecord(healthRaw.value) : {};
|
|
const quota =
|
|
quotaRaw.status === "fulfilled"
|
|
? normalizeQuotaResponse(quotaRaw.value, { provider: args.provider })
|
|
: normalizeQuotaResponse({});
|
|
const analytics = analyticsRaw.status === "fulfilled" ? toRecord(analyticsRaw.value) : {};
|
|
const cb = toArrayOfRecords(health.circuitBreakers).find(
|
|
(breaker) => toString(breaker.provider) === args.provider
|
|
);
|
|
const providerQuota = quota.providers.find((p) => p.provider === args.provider) || null;
|
|
const result = {
|
|
provider: args.provider,
|
|
successRate: toNumber2(analytics.successRate, 1),
|
|
requestCount: toNumber2(analytics.requestCount, 0),
|
|
avgLatencyMs: toNumber2(analytics.avgLatencyMs, 0),
|
|
p50LatencyMs: toNumber2(analytics.p50LatencyMs, 0),
|
|
p95LatencyMs: toNumber2(analytics.p95LatencyMs, 0),
|
|
p99LatencyMs: toNumber2(analytics.p99LatencyMs, 0),
|
|
errorRate: toNumber2(analytics.errorRate, 0),
|
|
lastError: toString(analytics.lastError) || null,
|
|
circuitBreakerState: toString(cb?.state, "CLOSED"),
|
|
quotaInfo: providerQuota
|
|
? {
|
|
used: providerQuota.quotaUsed,
|
|
total: providerQuota.quotaTotal,
|
|
resetAt: providerQuota.resetAt,
|
|
}
|
|
: { used: 0, total: null, resetAt: null },
|
|
};
|
|
await logToolCall("omniroute_get_provider_metrics", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_get_provider_metrics", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleBestComboForTask(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const fitness = TASK_FITNESS[args.taskType] || TASK_FITNESS.coding;
|
|
const combos = normalizeCombosResponse(await apiFetch("/api/combos"));
|
|
const enabledCombos = combos.filter((combo) => combo.enabled !== false);
|
|
if (enabledCombos.length === 0) {
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify({ error: "No enabled combos available" }) }],
|
|
isError: true,
|
|
};
|
|
}
|
|
const scored = enabledCombos.map((combo) => {
|
|
const models = getComboModels(combo);
|
|
let score = 0;
|
|
for (const model of models) {
|
|
const prefIdx = fitness.preferred.indexOf(model.provider);
|
|
if (prefIdx >= 0) score += (fitness.preferred.length - prefIdx) * 10;
|
|
}
|
|
const name = toString(combo.name).toLowerCase();
|
|
for (const trait of fitness.traits) {
|
|
if (name.includes(trait)) score += 5;
|
|
}
|
|
const isFree =
|
|
name.includes("free") ||
|
|
models.every((model) => model.provider.toLowerCase().includes("free"));
|
|
return { combo, score, isFree };
|
|
});
|
|
scored.sort((a, b) => b.score - a.score);
|
|
const best = scored[0];
|
|
const alternatives = scored.slice(1, 4).map((s) => ({
|
|
id: s.combo.id,
|
|
name: s.combo.name,
|
|
tradeoff: s.isFree
|
|
? "free but may have limits"
|
|
: s.score < best.score * 0.5
|
|
? "cheaper but slower"
|
|
: "similar quality, different providers",
|
|
}));
|
|
const freeAlt = scored.find((s) => s.isFree && s !== best);
|
|
const result = {
|
|
recommendedCombo: {
|
|
id: best.combo.id,
|
|
name: best.combo.name,
|
|
reason: `Best match for "${args.taskType}": preferred providers (${fitness.preferred.slice(0, 3).join(", ")})`,
|
|
},
|
|
alternatives,
|
|
freeAlternative: freeAlt ? { id: freeAlt.combo.id, name: freeAlt.combo.name } : null,
|
|
};
|
|
await logToolCall(
|
|
"omniroute_best_combo_for_task",
|
|
args,
|
|
result.recommendedCombo,
|
|
Date.now() - start,
|
|
true
|
|
);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_best_combo_for_task", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleExplainRoute(args) {
|
|
const start = Date.now();
|
|
try {
|
|
let decision = null;
|
|
try {
|
|
decision = toRecord(
|
|
await apiFetch(`/api/routing/decisions/${encodeURIComponent(args.requestId)}`)
|
|
);
|
|
} catch {}
|
|
const result = decision
|
|
? {
|
|
requestId: args.requestId,
|
|
decision: {
|
|
comboUsed: decision.comboUsed || "default",
|
|
providerSelected: decision.providerSelected || "unknown",
|
|
modelUsed: decision.modelUsed || "unknown",
|
|
score: decision.score || 0,
|
|
factors: decision.factors || [
|
|
{ name: "health", value: 1, weight: 0.3, contribution: 0.3 },
|
|
{ name: "quota", value: 1, weight: 0.25, contribution: 0.25 },
|
|
{ name: "cost", value: 0.8, weight: 0.2, contribution: 0.16 },
|
|
{ name: "latency", value: 0.9, weight: 0.15, contribution: 0.135 },
|
|
{ name: "task_fit", value: 0.7, weight: 0.1, contribution: 0.07 },
|
|
],
|
|
fallbacksTriggered: decision.fallbacksTriggered || [],
|
|
costActual: decision.costActual || 0,
|
|
latencyActual: decision.latencyActual || 0,
|
|
},
|
|
}
|
|
: {
|
|
requestId: args.requestId,
|
|
decision: {
|
|
comboUsed: "unknown",
|
|
providerSelected: "unknown",
|
|
modelUsed: "unknown",
|
|
score: 0,
|
|
factors: [],
|
|
fallbacksTriggered: [],
|
|
costActual: 0,
|
|
latencyActual: 0,
|
|
},
|
|
note: "Routing decision not found. The /api/routing/decisions endpoint may not be implemented yet, or the requestId is invalid.",
|
|
};
|
|
await logToolCall(
|
|
"omniroute_explain_route",
|
|
args,
|
|
{ requestId: args.requestId },
|
|
Date.now() - start,
|
|
true
|
|
);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_explain_route", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleSyncPricing(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const result = toRecord(
|
|
await apiFetch("/api/pricing/sync", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
sources: args.sources,
|
|
dryRun: args.dryRun ?? false,
|
|
}),
|
|
})
|
|
);
|
|
await logToolCall("omniroute_sync_pricing", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_sync_pricing", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleGetSessionSnapshot() {
|
|
const start = Date.now();
|
|
try {
|
|
const analytics = toRecord(
|
|
await apiFetch("/api/usage/analytics?period=session").catch(() => ({}))
|
|
);
|
|
const tokenCount = toRecord(analytics.tokenCount);
|
|
const byModel = toArrayOfRecords(analytics.byModel);
|
|
const byProvider = toArrayOfRecords(analytics.byProvider);
|
|
const result = {
|
|
sessionStart: toString(analytics.sessionStart, /* @__PURE__ */ new Date().toISOString()),
|
|
duration: toString(analytics.duration, "unknown"),
|
|
requestCount: toNumber2(analytics.requestCount, 0),
|
|
costTotal: toNumber2(analytics.totalCost, 0),
|
|
tokenCount: {
|
|
prompt: toNumber2(tokenCount.prompt, 0),
|
|
completion: toNumber2(tokenCount.completion, 0),
|
|
},
|
|
topModels: byModel.slice(0, 5).map((model) => ({
|
|
model: toString(model.model, "unknown"),
|
|
count: toNumber2(model.requests, 0),
|
|
})),
|
|
topProviders: byProvider.slice(0, 5).map((provider) => ({
|
|
provider: toString(provider.name, "unknown"),
|
|
count: toNumber2(provider.requests, 0),
|
|
})),
|
|
errors: toNumber2(analytics.errorCount, 0),
|
|
fallbacks: toNumber2(analytics.fallbackCount, 0),
|
|
budgetGuard: activeBudgetGuard
|
|
? {
|
|
active: true,
|
|
remaining: Math.max(0, activeBudgetGuard.maxCost - activeBudgetGuard.spent),
|
|
action: activeBudgetGuard.action,
|
|
}
|
|
: null,
|
|
};
|
|
await logToolCall(
|
|
"omniroute_get_session_snapshot",
|
|
{},
|
|
{ requestCount: result.requestCount },
|
|
Date.now() - start,
|
|
true
|
|
);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_get_session_snapshot", {}, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
|
|
// open-sse/mcp-server/tools/memoryTools.ts
|
|
import { z as z3 } from "zod";
|
|
|
|
// src/lib/db/core.ts
|
|
import Database from "better-sqlite3";
|
|
import path3 from "path";
|
|
import fs3 from "fs";
|
|
|
|
// src/lib/dataPaths.ts
|
|
import path from "path";
|
|
import os from "os";
|
|
var APP_NAME = "omniroute";
|
|
function safeHomeDir() {
|
|
try {
|
|
return os.homedir();
|
|
} catch {
|
|
return process.cwd();
|
|
}
|
|
}
|
|
function normalizeConfiguredPath(dir) {
|
|
if (typeof dir !== "string") return null;
|
|
const trimmed = dir.trim();
|
|
if (!trimmed) return null;
|
|
return path.resolve(trimmed);
|
|
}
|
|
function getLegacyDotDataDir() {
|
|
return path.join(safeHomeDir(), `.${APP_NAME}`);
|
|
}
|
|
function getDefaultDataDir() {
|
|
const homeDir = safeHomeDir();
|
|
if (process.platform === "win32") {
|
|
const appData = process.env.APPDATA || path.join(homeDir, "AppData", "Roaming");
|
|
return path.join(appData, APP_NAME);
|
|
}
|
|
const xdgConfigHome = normalizeConfiguredPath(process.env.XDG_CONFIG_HOME);
|
|
if (xdgConfigHome) {
|
|
return path.join(xdgConfigHome, APP_NAME);
|
|
}
|
|
return getLegacyDotDataDir();
|
|
}
|
|
function resolveDataDir2({ isCloud: isCloud2 = false } = {}) {
|
|
if (isCloud2) return "/tmp";
|
|
const configured = normalizeConfiguredPath(process.env.DATA_DIR);
|
|
if (configured) return configured;
|
|
return getDefaultDataDir();
|
|
}
|
|
|
|
// src/lib/db/migrationRunner.ts
|
|
import fs2 from "fs";
|
|
import path2 from "path";
|
|
import { fileURLToPath } from "url";
|
|
function resolveMigrationsDir() {
|
|
try {
|
|
const metaUrl = import.meta.url;
|
|
if (metaUrl && metaUrl.startsWith("file://")) {
|
|
const __filename = fileURLToPath(metaUrl);
|
|
return path2.join(path2.dirname(__filename), "migrations");
|
|
}
|
|
} catch {}
|
|
return path2.join(process.cwd(), "src", "lib", "db", "migrations");
|
|
}
|
|
var MIGRATIONS_DIR = resolveMigrationsDir();
|
|
function ensureMigrationsTable(db2) {
|
|
db2.exec(`
|
|
CREATE TABLE IF NOT EXISTS _omniroute_migrations (
|
|
version TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
`);
|
|
}
|
|
function getMigrationFiles() {
|
|
if (!fs2.existsSync(MIGRATIONS_DIR)) return [];
|
|
return fs2
|
|
.readdirSync(MIGRATIONS_DIR)
|
|
.filter((f) => f.endsWith(".sql"))
|
|
.sort()
|
|
.map((filename) => {
|
|
const match = filename.match(/^(\d+)_(.+)\.sql$/);
|
|
if (!match) return null;
|
|
return {
|
|
version: match[1],
|
|
name: match[2],
|
|
path: path2.join(MIGRATIONS_DIR, filename),
|
|
};
|
|
})
|
|
.filter(Boolean);
|
|
}
|
|
function getAppliedVersions(db2) {
|
|
const rows = db2.prepare("SELECT version FROM _omniroute_migrations").all();
|
|
return new Set(rows.map((r) => r.version));
|
|
}
|
|
function runMigrations(db2) {
|
|
ensureMigrationsTable(db2);
|
|
const files = getMigrationFiles();
|
|
const applied = getAppliedVersions(db2);
|
|
let count = 0;
|
|
for (const migration of files) {
|
|
if (applied.has(migration.version)) continue;
|
|
const sql = fs2.readFileSync(migration.path, "utf-8");
|
|
const applyMigration = db2.transaction(() => {
|
|
db2.exec(sql);
|
|
db2
|
|
.prepare("INSERT INTO _omniroute_migrations (version, name) VALUES (?, ?)")
|
|
.run(migration.version, migration.name);
|
|
});
|
|
try {
|
|
applyMigration();
|
|
count++;
|
|
console.log(`[Migration] Applied: ${migration.version}_${migration.name}`);
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
console.error(`[Migration] FAILED: ${migration.version}_${migration.name} \u2014 ${message}`);
|
|
throw err;
|
|
}
|
|
}
|
|
if (count > 0) {
|
|
console.log(`[Migration] ${count} migration(s) applied successfully.`);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// src/lib/db/core.ts
|
|
var isCloud = typeof globalThis.caches === "object" && globalThis.caches !== null;
|
|
var isBuildPhase = process.env.NEXT_PHASE === "phase-production-build";
|
|
var DATA_DIR = resolveDataDir2({ isCloud });
|
|
var LEGACY_DATA_DIR = isCloud ? null : getLegacyDotDataDir();
|
|
var SQLITE_FILE = isCloud ? null : path3.join(DATA_DIR, "storage.sqlite");
|
|
var JSON_DB_FILE = isCloud ? null : path3.join(DATA_DIR, "db.json");
|
|
var DB_BACKUPS_DIR = isCloud ? null : path3.join(DATA_DIR, "db_backups");
|
|
if (!isCloud && !fs3.existsSync(DATA_DIR)) {
|
|
try {
|
|
fs3.mkdirSync(DATA_DIR, { recursive: true });
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
console.warn(
|
|
`[DB] Cannot create data directory '${DATA_DIR}': ${msg}
|
|
[DB] Set the DATA_DIR environment variable to a writable path, e.g.:
|
|
[DB] DATA_DIR=/path/to/writable/dir omniroute`
|
|
);
|
|
}
|
|
}
|
|
var SCHEMA_SQL = `
|
|
CREATE TABLE IF NOT EXISTS provider_connections (
|
|
id TEXT PRIMARY KEY,
|
|
provider TEXT NOT NULL,
|
|
auth_type TEXT,
|
|
name TEXT,
|
|
email TEXT,
|
|
priority INTEGER DEFAULT 0,
|
|
is_active INTEGER DEFAULT 1,
|
|
access_token TEXT,
|
|
refresh_token TEXT,
|
|
expires_at TEXT,
|
|
token_expires_at TEXT,
|
|
scope TEXT,
|
|
project_id TEXT,
|
|
test_status TEXT,
|
|
error_code TEXT,
|
|
last_error TEXT,
|
|
last_error_at TEXT,
|
|
last_error_type TEXT,
|
|
last_error_source TEXT,
|
|
backoff_level INTEGER DEFAULT 0,
|
|
rate_limited_until TEXT,
|
|
health_check_interval INTEGER,
|
|
last_health_check_at TEXT,
|
|
last_tested TEXT,
|
|
api_key TEXT,
|
|
id_token TEXT,
|
|
provider_specific_data TEXT,
|
|
expires_in INTEGER,
|
|
display_name TEXT,
|
|
global_priority INTEGER,
|
|
default_model TEXT,
|
|
token_type TEXT,
|
|
consecutive_use_count INTEGER DEFAULT 0,
|
|
rate_limit_protection INTEGER DEFAULT 0,
|
|
last_used_at TEXT,
|
|
"group" TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_pc_provider ON provider_connections(provider);
|
|
CREATE INDEX IF NOT EXISTS idx_pc_active ON provider_connections(is_active);
|
|
CREATE INDEX IF NOT EXISTS idx_pc_priority ON provider_connections(provider, priority);
|
|
|
|
CREATE TABLE IF NOT EXISTS provider_nodes (
|
|
id TEXT PRIMARY KEY,
|
|
type TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
prefix TEXT,
|
|
api_type TEXT,
|
|
base_url TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS key_value (
|
|
namespace TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
value TEXT NOT NULL,
|
|
PRIMARY KEY (namespace, key)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS combos (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE,
|
|
data TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS api_keys (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
key TEXT NOT NULL UNIQUE,
|
|
machine_id TEXT,
|
|
allowed_models TEXT DEFAULT '[]',
|
|
no_log INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_ak_key ON api_keys(key);
|
|
|
|
CREATE TABLE IF NOT EXISTS db_meta (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS usage_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
provider TEXT,
|
|
model TEXT,
|
|
connection_id TEXT,
|
|
api_key_id TEXT,
|
|
api_key_name TEXT,
|
|
tokens_input INTEGER DEFAULT 0,
|
|
tokens_output INTEGER DEFAULT 0,
|
|
tokens_cache_read INTEGER DEFAULT 0,
|
|
tokens_cache_creation INTEGER DEFAULT 0,
|
|
tokens_reasoning INTEGER DEFAULT 0,
|
|
status TEXT,
|
|
success INTEGER DEFAULT 1,
|
|
latency_ms INTEGER DEFAULT 0,
|
|
ttft_ms INTEGER DEFAULT 0,
|
|
error_code TEXT,
|
|
timestamp TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uh_timestamp ON usage_history(timestamp);
|
|
CREATE INDEX IF NOT EXISTS idx_uh_provider ON usage_history(provider);
|
|
CREATE INDEX IF NOT EXISTS idx_uh_model ON usage_history(model);
|
|
|
|
CREATE TABLE IF NOT EXISTS call_logs (
|
|
id TEXT PRIMARY KEY,
|
|
timestamp TEXT NOT NULL,
|
|
method TEXT,
|
|
path TEXT,
|
|
status INTEGER,
|
|
model TEXT,
|
|
provider TEXT,
|
|
account TEXT,
|
|
connection_id TEXT,
|
|
duration INTEGER DEFAULT 0,
|
|
tokens_in INTEGER DEFAULT 0,
|
|
tokens_out INTEGER DEFAULT 0,
|
|
source_format TEXT,
|
|
target_format TEXT,
|
|
api_key_id TEXT,
|
|
api_key_name TEXT,
|
|
combo_name TEXT,
|
|
request_body TEXT,
|
|
response_body TEXT,
|
|
error TEXT,
|
|
artifact_relpath TEXT,
|
|
has_pipeline_details INTEGER DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_cl_timestamp ON call_logs(timestamp);
|
|
CREATE INDEX IF NOT EXISTS idx_cl_status ON call_logs(status);
|
|
|
|
CREATE TABLE IF NOT EXISTS proxy_logs (
|
|
id TEXT PRIMARY KEY,
|
|
timestamp TEXT NOT NULL,
|
|
status TEXT,
|
|
proxy_type TEXT,
|
|
proxy_host TEXT,
|
|
proxy_port INTEGER,
|
|
level TEXT,
|
|
level_id TEXT,
|
|
provider TEXT,
|
|
target_url TEXT,
|
|
public_ip TEXT,
|
|
latency_ms INTEGER DEFAULT 0,
|
|
error TEXT,
|
|
connection_id TEXT,
|
|
combo_id TEXT,
|
|
account TEXT,
|
|
tls_fingerprint INTEGER DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_pl_timestamp ON proxy_logs(timestamp);
|
|
CREATE INDEX IF NOT EXISTS idx_pl_status ON proxy_logs(status);
|
|
CREATE INDEX IF NOT EXISTS idx_pl_provider ON proxy_logs(provider);
|
|
|
|
-- Domain State Persistence (Phase 5)
|
|
CREATE TABLE IF NOT EXISTS domain_fallback_chains (
|
|
model TEXT PRIMARY KEY,
|
|
chain TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS domain_budgets (
|
|
api_key_id TEXT PRIMARY KEY,
|
|
daily_limit_usd REAL NOT NULL,
|
|
monthly_limit_usd REAL DEFAULT 0,
|
|
warning_threshold REAL DEFAULT 0.8
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS domain_cost_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
api_key_id TEXT NOT NULL,
|
|
cost REAL NOT NULL,
|
|
timestamp INTEGER NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_dch_key ON domain_cost_history(api_key_id);
|
|
CREATE INDEX IF NOT EXISTS idx_dch_ts ON domain_cost_history(timestamp);
|
|
|
|
CREATE TABLE IF NOT EXISTS domain_lockout_state (
|
|
identifier TEXT PRIMARY KEY,
|
|
attempts TEXT NOT NULL,
|
|
locked_until INTEGER
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS domain_circuit_breakers (
|
|
name TEXT PRIMARY KEY,
|
|
state TEXT NOT NULL DEFAULT 'CLOSED',
|
|
failure_count INTEGER DEFAULT 0,
|
|
last_failure_time INTEGER,
|
|
options TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS semantic_cache (
|
|
id TEXT PRIMARY KEY,
|
|
signature TEXT NOT NULL UNIQUE,
|
|
model TEXT NOT NULL,
|
|
prompt_hash TEXT NOT NULL,
|
|
response TEXT NOT NULL,
|
|
tokens_saved INTEGER DEFAULT 0,
|
|
hit_count INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_sc_sig ON semantic_cache(signature);
|
|
CREATE INDEX IF NOT EXISTS idx_sc_model ON semantic_cache(model);
|
|
`;
|
|
function getDb2() {
|
|
return globalThis.__omnirouteDb ?? null;
|
|
}
|
|
function setDb(db2) {
|
|
if (db2) {
|
|
globalThis.__omnirouteDb = db2;
|
|
} else {
|
|
delete globalThis.__omnirouteDb;
|
|
}
|
|
}
|
|
function ensureProviderConnectionsColumns(db2) {
|
|
try {
|
|
const columns = db2.prepare("PRAGMA table_info(provider_connections)").all();
|
|
const columnNames = new Set(columns.map((column) => String(column.name ?? "")));
|
|
if (!columnNames.has("rate_limit_protection")) {
|
|
db2.exec(
|
|
"ALTER TABLE provider_connections ADD COLUMN rate_limit_protection INTEGER DEFAULT 0"
|
|
);
|
|
console.log("[DB] Added provider_connections.rate_limit_protection column");
|
|
}
|
|
if (!columnNames.has("last_used_at")) {
|
|
db2.exec("ALTER TABLE provider_connections ADD COLUMN last_used_at TEXT");
|
|
console.log("[DB] Added provider_connections.last_used_at column");
|
|
}
|
|
if (!columnNames.has("group")) {
|
|
db2.exec('ALTER TABLE provider_connections ADD COLUMN "group" TEXT');
|
|
console.log('[DB] Added provider_connections."group" column');
|
|
}
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
console.warn("[DB] Failed to verify provider_connections schema:", message);
|
|
}
|
|
}
|
|
function ensureUsageHistoryColumns(db2) {
|
|
try {
|
|
const columns = db2.prepare("PRAGMA table_info(usage_history)").all();
|
|
const columnNames = new Set(columns.map((column) => String(column.name ?? "")));
|
|
if (!columnNames.has("success")) {
|
|
db2.exec("ALTER TABLE usage_history ADD COLUMN success INTEGER DEFAULT 1");
|
|
console.log("[DB] Added usage_history.success column");
|
|
}
|
|
if (!columnNames.has("latency_ms")) {
|
|
db2.exec("ALTER TABLE usage_history ADD COLUMN latency_ms INTEGER DEFAULT 0");
|
|
console.log("[DB] Added usage_history.latency_ms column");
|
|
}
|
|
if (!columnNames.has("ttft_ms")) {
|
|
db2.exec("ALTER TABLE usage_history ADD COLUMN ttft_ms INTEGER DEFAULT 0");
|
|
console.log("[DB] Added usage_history.ttft_ms column");
|
|
}
|
|
if (!columnNames.has("error_code")) {
|
|
db2.exec("ALTER TABLE usage_history ADD COLUMN error_code TEXT");
|
|
console.log("[DB] Added usage_history.error_code column");
|
|
}
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
console.warn("[DB] Failed to verify usage_history schema:", message);
|
|
}
|
|
}
|
|
function ensureCallLogsColumns(db2) {
|
|
try {
|
|
const columns = db2.prepare("PRAGMA table_info(call_logs)").all();
|
|
const columnNames = new Set(columns.map((column) => String(column.name ?? "")));
|
|
if (!columnNames.has("artifact_relpath")) {
|
|
db2.exec("ALTER TABLE call_logs ADD COLUMN artifact_relpath TEXT");
|
|
console.log("[DB] Added call_logs.artifact_relpath column");
|
|
}
|
|
if (!columnNames.has("has_pipeline_details")) {
|
|
db2.exec("ALTER TABLE call_logs ADD COLUMN has_pipeline_details INTEGER DEFAULT 0");
|
|
console.log("[DB] Added call_logs.has_pipeline_details column");
|
|
}
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
console.warn("[DB] Failed to verify call_logs schema:", message);
|
|
}
|
|
}
|
|
function getDbInstance() {
|
|
const existing = getDb2();
|
|
if (existing) return existing;
|
|
if (isCloud || isBuildPhase) {
|
|
if (isBuildPhase) {
|
|
console.log("[DB] Build phase detected \u2014 using in-memory SQLite (read-only)");
|
|
}
|
|
const memoryDb = new Database(":memory:");
|
|
memoryDb.pragma("journal_mode = WAL");
|
|
memoryDb.exec(SCHEMA_SQL);
|
|
ensureUsageHistoryColumns(memoryDb);
|
|
setDb(memoryDb);
|
|
return memoryDb;
|
|
}
|
|
const sqliteFile = SQLITE_FILE;
|
|
if (!sqliteFile) {
|
|
throw new Error("SQLITE_FILE is unavailable for local mode");
|
|
}
|
|
const jsonDbFile = JSON_DB_FILE;
|
|
if (fs3.existsSync(sqliteFile)) {
|
|
try {
|
|
const probe = new Database(sqliteFile, { readonly: true });
|
|
const hasOldSchema = probe
|
|
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'")
|
|
.get();
|
|
if (hasOldSchema) {
|
|
let hasData = false;
|
|
try {
|
|
const count = probe.prepare("SELECT COUNT(*) as c FROM provider_connections").get();
|
|
hasData = Boolean(count && count.c > 0);
|
|
} catch {}
|
|
probe.close();
|
|
if (hasData) {
|
|
console.log(
|
|
`[DB] Old schema_migrations table found but data exists \u2014 preserving data (#146)`
|
|
);
|
|
const fixDb = new Database(sqliteFile);
|
|
try {
|
|
fixDb.exec("DROP TABLE IF EXISTS schema_migrations");
|
|
fixDb.pragma("wal_checkpoint(TRUNCATE)");
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
console.warn("[DB] Could not clean up old schema table:", message);
|
|
} finally {
|
|
fixDb.close();
|
|
}
|
|
} else {
|
|
const oldPath = sqliteFile + ".old-schema";
|
|
console.log(
|
|
`[DB] Old incompatible schema detected (empty) \u2014 renaming to ${path3.basename(oldPath)}`
|
|
);
|
|
fs3.renameSync(sqliteFile, oldPath);
|
|
for (const ext of ["-wal", "-shm"]) {
|
|
try {
|
|
if (fs3.existsSync(sqliteFile + ext)) fs3.unlinkSync(sqliteFile + ext);
|
|
} catch {}
|
|
}
|
|
}
|
|
} else {
|
|
probe.close();
|
|
}
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
console.warn("[DB] Could not probe existing DB, will create fresh:", message);
|
|
try {
|
|
fs3.unlinkSync(sqliteFile);
|
|
} catch {}
|
|
}
|
|
}
|
|
const db2 = new Database(sqliteFile);
|
|
db2.pragma("journal_mode = WAL");
|
|
db2.pragma("busy_timeout = 5000");
|
|
db2.pragma("synchronous = NORMAL");
|
|
db2.exec(SCHEMA_SQL);
|
|
ensureProviderConnectionsColumns(db2);
|
|
ensureUsageHistoryColumns(db2);
|
|
ensureCallLogsColumns(db2);
|
|
db2.exec(`
|
|
CREATE TABLE IF NOT EXISTS _omniroute_migrations (
|
|
version TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
INSERT OR IGNORE INTO _omniroute_migrations (version, name)
|
|
VALUES ('001', 'initial_schema');
|
|
`);
|
|
runMigrations(db2);
|
|
if (jsonDbFile && fs3.existsSync(jsonDbFile)) {
|
|
migrateFromJson(db2, jsonDbFile);
|
|
}
|
|
const versionStmt = db2.prepare(
|
|
"INSERT OR REPLACE INTO db_meta (key, value) VALUES ('schema_version', '1')"
|
|
);
|
|
versionStmt.run();
|
|
setDb(db2);
|
|
console.log(`[DB] SQLite database ready: ${sqliteFile}`);
|
|
return db2;
|
|
}
|
|
function migrateFromJson(db2, jsonPath) {
|
|
try {
|
|
const raw = fs3.readFileSync(jsonPath, "utf-8");
|
|
const data = JSON.parse(raw);
|
|
const connCount = (data.providerConnections || []).length;
|
|
const nodeCount = (data.providerNodes || []).length;
|
|
const keyCount = (data.apiKeys || []).length;
|
|
if (connCount === 0 && nodeCount === 0 && keyCount === 0) {
|
|
console.log("[DB] db.json has no data to migrate, skipping");
|
|
fs3.renameSync(jsonPath, jsonPath + ".empty");
|
|
return;
|
|
}
|
|
console.log(
|
|
`[DB] Migrating db.json \u2192 SQLite (${connCount} connections, ${nodeCount} nodes, ${keyCount} keys)...`
|
|
);
|
|
const migrate = db2.transaction(() => {
|
|
const insertConn = db2.prepare(`
|
|
INSERT OR REPLACE INTO provider_connections (
|
|
id, provider, auth_type, name, email, priority, is_active,
|
|
access_token, refresh_token, expires_at, token_expires_at,
|
|
scope, project_id, test_status, error_code, last_error,
|
|
last_error_at, last_error_type, last_error_source, backoff_level,
|
|
rate_limited_until, health_check_interval, last_health_check_at,
|
|
last_tested, api_key, id_token, provider_specific_data,
|
|
expires_in, display_name, global_priority, default_model,
|
|
token_type, consecutive_use_count, rate_limit_protection, last_used_at, created_at, updated_at
|
|
) VALUES (
|
|
@id, @provider, @authType, @name, @email, @priority, @isActive,
|
|
@accessToken, @refreshToken, @expiresAt, @tokenExpiresAt,
|
|
@scope, @projectId, @testStatus, @errorCode, @lastError,
|
|
@lastErrorAt, @lastErrorType, @lastErrorSource, @backoffLevel,
|
|
@rateLimitedUntil, @healthCheckInterval, @lastHealthCheckAt,
|
|
@lastTested, @apiKey, @idToken, @providerSpecificData,
|
|
@expiresIn, @displayName, @globalPriority, @defaultModel,
|
|
@tokenType, @consecutiveUseCount, @rateLimitProtection, @lastUsedAt, @createdAt, @updatedAt
|
|
)
|
|
`);
|
|
for (const conn of data.providerConnections || []) {
|
|
insertConn.run({
|
|
id: conn.id,
|
|
provider: conn.provider,
|
|
authType: conn.authType || "oauth",
|
|
name: conn.name || null,
|
|
email: conn.email || null,
|
|
priority: conn.priority || 0,
|
|
isActive: conn.isActive === false ? 0 : 1,
|
|
accessToken: conn.accessToken || null,
|
|
refreshToken: conn.refreshToken || null,
|
|
expiresAt: conn.expiresAt || null,
|
|
tokenExpiresAt: conn.tokenExpiresAt || null,
|
|
scope: conn.scope || null,
|
|
projectId: conn.projectId || null,
|
|
testStatus: conn.testStatus || null,
|
|
errorCode: conn.errorCode || null,
|
|
lastError: conn.lastError || null,
|
|
lastErrorAt: conn.lastErrorAt || null,
|
|
lastErrorType: conn.lastErrorType || null,
|
|
lastErrorSource: conn.lastErrorSource || null,
|
|
backoffLevel: conn.backoffLevel || 0,
|
|
rateLimitedUntil: conn.rateLimitedUntil || null,
|
|
healthCheckInterval: conn.healthCheckInterval || null,
|
|
lastHealthCheckAt: conn.lastHealthCheckAt || null,
|
|
lastTested: conn.lastTested || null,
|
|
apiKey: conn.apiKey || null,
|
|
idToken: conn.idToken || null,
|
|
providerSpecificData: conn.providerSpecificData
|
|
? JSON.stringify(conn.providerSpecificData)
|
|
: null,
|
|
expiresIn: conn.expiresIn || null,
|
|
displayName: conn.displayName || null,
|
|
globalPriority: conn.globalPriority || null,
|
|
defaultModel: conn.defaultModel || null,
|
|
tokenType: conn.tokenType || null,
|
|
consecutiveUseCount: conn.consecutiveUseCount || 0,
|
|
lastUsedAt: conn.lastUsedAt || null,
|
|
rateLimitProtection:
|
|
conn.rateLimitProtection === true || conn.rateLimitProtection === 1 ? 1 : 0,
|
|
createdAt: conn.createdAt || /* @__PURE__ */ new Date().toISOString(),
|
|
updatedAt: conn.updatedAt || /* @__PURE__ */ new Date().toISOString(),
|
|
});
|
|
}
|
|
const insertNode = db2.prepare(`
|
|
INSERT OR REPLACE INTO provider_nodes (id, type, name, prefix, api_type, base_url, created_at, updated_at)
|
|
VALUES (@id, @type, @name, @prefix, @apiType, @baseUrl, @createdAt, @updatedAt)
|
|
`);
|
|
for (const node of data.providerNodes || []) {
|
|
insertNode.run({
|
|
id: node.id,
|
|
type: node.type,
|
|
name: node.name,
|
|
prefix: node.prefix || null,
|
|
apiType: node.apiType || null,
|
|
baseUrl: node.baseUrl || null,
|
|
createdAt: node.createdAt || /* @__PURE__ */ new Date().toISOString(),
|
|
updatedAt: node.updatedAt || /* @__PURE__ */ new Date().toISOString(),
|
|
});
|
|
}
|
|
const insertKv = db2.prepare(
|
|
"INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)"
|
|
);
|
|
for (const [alias, model] of Object.entries(data.modelAliases || {})) {
|
|
insertKv.run("modelAliases", alias, JSON.stringify(model));
|
|
}
|
|
for (const [toolName, mappings] of Object.entries(data.mitmAlias || {})) {
|
|
insertKv.run("mitmAlias", toolName, JSON.stringify(mappings));
|
|
}
|
|
for (const [key, value] of Object.entries(data.settings || {})) {
|
|
insertKv.run("settings", key, JSON.stringify(value));
|
|
}
|
|
for (const [provider, models] of Object.entries(data.pricing || {})) {
|
|
insertKv.run("pricing", provider, JSON.stringify(models));
|
|
}
|
|
for (const [providerId, models] of Object.entries(data.customModels || {})) {
|
|
insertKv.run("customModels", providerId, JSON.stringify(models));
|
|
}
|
|
if (data.proxyConfig) {
|
|
insertKv.run("proxyConfig", "global", JSON.stringify(data.proxyConfig.global || null));
|
|
insertKv.run("proxyConfig", "providers", JSON.stringify(data.proxyConfig.providers || {}));
|
|
insertKv.run("proxyConfig", "combos", JSON.stringify(data.proxyConfig.combos || {}));
|
|
insertKv.run("proxyConfig", "keys", JSON.stringify(data.proxyConfig.keys || {}));
|
|
}
|
|
const insertCombo = db2.prepare(`
|
|
INSERT OR REPLACE INTO combos (id, name, data, created_at, updated_at)
|
|
VALUES (@id, @name, @data, @createdAt, @updatedAt)
|
|
`);
|
|
for (const combo of data.combos || []) {
|
|
insertCombo.run({
|
|
id: combo.id,
|
|
name: combo.name,
|
|
data: JSON.stringify(combo),
|
|
createdAt: combo.createdAt || /* @__PURE__ */ new Date().toISOString(),
|
|
updatedAt: combo.updatedAt || /* @__PURE__ */ new Date().toISOString(),
|
|
});
|
|
}
|
|
const insertKey = db2.prepare(`
|
|
INSERT OR REPLACE INTO api_keys (id, name, key, machine_id, allowed_models, no_log, created_at)
|
|
VALUES (@id, @name, @key, @machineId, @allowedModels, @noLog, @createdAt)
|
|
`);
|
|
for (const apiKey of data.apiKeys || []) {
|
|
insertKey.run({
|
|
id: apiKey.id,
|
|
name: apiKey.name,
|
|
key: apiKey.key,
|
|
machineId: apiKey.machineId || null,
|
|
allowedModels: JSON.stringify(apiKey.allowedModels || []),
|
|
noLog: apiKey.noLog ? 1 : 0,
|
|
createdAt: apiKey.createdAt || /* @__PURE__ */ new Date().toISOString(),
|
|
});
|
|
}
|
|
});
|
|
migrate();
|
|
const migratedPath = jsonPath + ".migrated";
|
|
fs3.renameSync(jsonPath, migratedPath);
|
|
console.log(`[DB] \u2713 Migration complete. Original saved as ${migratedPath}`);
|
|
const legacyBackupDir = path3.join(DATA_DIR, "db_backups");
|
|
if (fs3.existsSync(legacyBackupDir)) {
|
|
const jsonBackups = fs3.readdirSync(legacyBackupDir).filter((f) => f.endsWith(".json"));
|
|
if (jsonBackups.length > 0) {
|
|
console.log(
|
|
`[DB] Note: ${jsonBackups.length} legacy .json backups remain in ${legacyBackupDir}`
|
|
);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error("[DB] Migration from db.json failed:", err.message);
|
|
}
|
|
}
|
|
|
|
// src/lib/memory/schemas.ts
|
|
import { z as z2 } from "zod";
|
|
|
|
// src/lib/memory/types.ts
|
|
var MemoryType = /* @__PURE__ */ ((MemoryType2) => {
|
|
MemoryType2["FACTUAL"] = "factual";
|
|
MemoryType2["EPISODIC"] = "episodic";
|
|
MemoryType2["PROCEDURAL"] = "procedural";
|
|
MemoryType2["SEMANTIC"] = "semantic";
|
|
return MemoryType2;
|
|
})(MemoryType || {});
|
|
|
|
// src/lib/memory/schemas.ts
|
|
var MemoryConfigSchema = z2.object({
|
|
enabled: z2.boolean(),
|
|
maxTokens: z2.number().int().positive(),
|
|
retrievalStrategy: z2.enum(["exact", "semantic", "hybrid"]).optional(),
|
|
autoSummarize: z2.boolean(),
|
|
persistAcrossModels: z2.boolean(),
|
|
retentionDays: z2.number().int().positive(),
|
|
scope: z2.enum(["session", "apiKey", "global"]).optional(),
|
|
});
|
|
var MemoryCreateInputSchema = z2
|
|
.object({
|
|
type: z2.nativeEnum(MemoryType),
|
|
key: z2.string().min(1),
|
|
content: z2.string().min(1),
|
|
metadata: z2.record(z2.string(), z2.unknown()).optional(),
|
|
})
|
|
.strict();
|
|
var MemoryUpdateInputSchema = z2
|
|
.object({
|
|
type: z2.nativeEnum(MemoryType).optional(),
|
|
key: z2.string().min(1).optional(),
|
|
content: z2.string().min(1).optional(),
|
|
metadata: z2.record(z2.string(), z2.unknown()).optional(),
|
|
})
|
|
.strict();
|
|
|
|
// src/lib/memory/retrieval.ts
|
|
function estimateTokens(text) {
|
|
if (!text || typeof text !== "string") return 0;
|
|
return Math.ceil(text.length / 4);
|
|
}
|
|
async function retrieveMemories(apiKeyId, config = {}) {
|
|
const normalizedConfig = MemoryConfigSchema.parse({
|
|
enabled: true,
|
|
maxTokens: 2e3,
|
|
retrievalStrategy: "recent",
|
|
autoSummarize: false,
|
|
persistAcrossModels: false,
|
|
retentionDays: 30,
|
|
scope: "apiKey",
|
|
...config,
|
|
});
|
|
const maxTokens = Math.min(Math.max(normalizedConfig.maxTokens, 100), 8e3);
|
|
const strategy = normalizedConfig.retrievalStrategy;
|
|
const db2 = getDbInstance();
|
|
const memories = [];
|
|
let totalTokens = 0;
|
|
let query = "SELECT * FROM memory WHERE apiKeyId = ?";
|
|
const params = [apiKeyId];
|
|
switch (strategy) {
|
|
case "semantic":
|
|
query += " ORDER BY createdAt DESC";
|
|
break;
|
|
case "hybrid":
|
|
query += " ORDER BY createdAt DESC";
|
|
break;
|
|
case "exact":
|
|
default:
|
|
query += " ORDER BY createdAt DESC";
|
|
}
|
|
query += " LIMIT 100";
|
|
const stmt = db2.prepare(query);
|
|
const rows = stmt.all(...params);
|
|
for (const row of rows) {
|
|
const memory = {
|
|
id: String(row.id),
|
|
apiKeyId: String(row.apiKeyId),
|
|
sessionId: String(row.sessionId),
|
|
type: row.type,
|
|
key: String(row.key),
|
|
content: String(row.content),
|
|
metadata: JSON.parse(String(row.metadata)),
|
|
createdAt: new Date(String(row.createdAt)),
|
|
updatedAt: new Date(String(row.updatedAt)),
|
|
expiresAt: row.expiresAt ? new Date(String(row.expiresAt)) : null,
|
|
};
|
|
const memoryTokens = estimateTokens(memory.content);
|
|
if (totalTokens + memoryTokens > maxTokens) {
|
|
if (memories.length === 0) {
|
|
memories.push(memory);
|
|
totalTokens += memoryTokens;
|
|
}
|
|
break;
|
|
}
|
|
memories.push(memory);
|
|
totalTokens += memoryTokens;
|
|
}
|
|
return memories;
|
|
}
|
|
|
|
// src/lib/memory/store.ts
|
|
var MEMORY_MAX_CACHE_SIZE = 1e4;
|
|
var _memoryCache = /* @__PURE__ */ new Map();
|
|
function parseJSON(value) {
|
|
if (!value || typeof value !== "string" || value.trim() === "") {
|
|
return {};
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(value);
|
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
function invalidateMemoryCache(key) {
|
|
_memoryCache.delete(key);
|
|
}
|
|
function evictIfNeeded(cache) {
|
|
if (cache.size > MEMORY_MAX_CACHE_SIZE) {
|
|
const keysArray = Array.from(cache.keys());
|
|
const entriesToRemove = Math.floor(cache.size * 0.2);
|
|
for (let i = 0; i < entriesToRemove; i++) {
|
|
cache.delete(keysArray[i]);
|
|
}
|
|
}
|
|
}
|
|
var MEMORY_VALIDATION_CACHE_TTL = 60 * 1e3;
|
|
async function createMemory(memory) {
|
|
const db2 = getDbInstance();
|
|
const id = crypto.randomUUID();
|
|
const now = /* @__PURE__ */ new Date().toISOString();
|
|
const stmt = db2.prepare(
|
|
"INSERT INTO memory (id, apiKeyId, sessionId, type, key, content, metadata, createdAt, updatedAt, expiresAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
);
|
|
stmt.run(
|
|
id,
|
|
memory.apiKeyId,
|
|
memory.sessionId,
|
|
memory.type,
|
|
memory.key,
|
|
memory.content,
|
|
JSON.stringify(memory.metadata),
|
|
now,
|
|
now,
|
|
memory.expiresAt?.toISOString() ?? null
|
|
);
|
|
const createdMemory = {
|
|
id,
|
|
apiKeyId: memory.apiKeyId,
|
|
sessionId: memory.sessionId,
|
|
type: memory.type,
|
|
key: memory.key,
|
|
content: memory.content,
|
|
metadata: memory.metadata,
|
|
createdAt: new Date(now),
|
|
updatedAt: new Date(now),
|
|
expiresAt: memory.expiresAt ?? null,
|
|
};
|
|
invalidateMemoryCache(id);
|
|
evictIfNeeded(_memoryCache);
|
|
_memoryCache.set(id, { value: createdMemory, timestamp: Date.now() });
|
|
return createdMemory;
|
|
}
|
|
async function deleteMemory(id) {
|
|
if (!id || typeof id !== "string") return false;
|
|
const db2 = getDbInstance();
|
|
const stmt = db2.prepare("DELETE FROM memory WHERE id = ?");
|
|
const result = stmt.run(id);
|
|
if (result.changes === 0) {
|
|
return false;
|
|
}
|
|
invalidateMemoryCache(id);
|
|
return true;
|
|
}
|
|
async function listMemories(filters) {
|
|
const db2 = getDbInstance();
|
|
let query = "SELECT * FROM memory";
|
|
const params = [];
|
|
const whereClauses = [];
|
|
if (filters.apiKeyId) {
|
|
whereClauses.push("apiKeyId = ?");
|
|
params.push(filters.apiKeyId);
|
|
}
|
|
if (filters.type) {
|
|
whereClauses.push("type = ?");
|
|
params.push(filters.type);
|
|
}
|
|
if (filters.sessionId) {
|
|
whereClauses.push("sessionId = ?");
|
|
params.push(filters.sessionId);
|
|
}
|
|
if (whereClauses.length > 0) {
|
|
query += " WHERE " + whereClauses.join(" AND ");
|
|
}
|
|
query += " ORDER BY createdAt DESC";
|
|
if (filters.limit !== void 0) {
|
|
query += " LIMIT ?";
|
|
params.push(filters.limit);
|
|
}
|
|
if (filters.offset !== void 0) {
|
|
query += " OFFSET ?";
|
|
params.push(filters.offset);
|
|
}
|
|
const stmt = db2.prepare(query);
|
|
const rows = stmt.all(...params);
|
|
return rows.map((row) => ({
|
|
id: String(row.id),
|
|
apiKeyId: String(row.apiKeyId),
|
|
sessionId: String(row.sessionId),
|
|
type: row.type,
|
|
key: String(row.key),
|
|
content: String(row.content),
|
|
metadata: parseJSON(row.metadata),
|
|
createdAt: new Date(String(row.createdAt)),
|
|
updatedAt: new Date(String(row.updatedAt)),
|
|
expiresAt: row.expiresAt ? new Date(String(row.expiresAt)) : null,
|
|
}));
|
|
}
|
|
|
|
// open-sse/mcp-server/tools/memoryTools.ts
|
|
var MemorySearchSchema = z3.object({
|
|
apiKeyId: z3.string(),
|
|
query: z3.string().optional(),
|
|
type: z3.enum(["factual", "episodic", "procedural", "semantic"]).optional(),
|
|
maxTokens: z3.number().int().positive().max(8e3).optional(),
|
|
limit: z3.number().int().positive().max(100).optional(),
|
|
});
|
|
var MemoryAddSchema = z3.object({
|
|
apiKeyId: z3.string(),
|
|
sessionId: z3.string().optional(),
|
|
type: z3.enum(["factual", "episodic", "procedural", "semantic"]),
|
|
key: z3.string().min(1),
|
|
content: z3.string().min(1),
|
|
metadata: z3.record(z3.string(), z3.unknown()).optional(),
|
|
});
|
|
var MemoryClearSchema = z3.object({
|
|
apiKeyId: z3.string(),
|
|
type: z3.enum(["factual", "episodic", "procedural", "semantic"]).optional(),
|
|
olderThan: z3.string().optional(),
|
|
});
|
|
var memoryTools = {
|
|
omniroute_memory_search: {
|
|
name: "omniroute_memory_search",
|
|
description: "Search memories by query, type, or API key with token budget enforcement",
|
|
inputSchema: MemorySearchSchema,
|
|
handler: async (args) => {
|
|
const config = {
|
|
enabled: true,
|
|
maxTokens: args.maxTokens || 2e3,
|
|
retrievalStrategy: "exact",
|
|
autoSummarize: false,
|
|
persistAcrossModels: false,
|
|
retentionDays: 30,
|
|
scope: "apiKey",
|
|
};
|
|
const memories = await retrieveMemories(args.apiKeyId, config);
|
|
const filtered = args.type ? memories.filter((m) => m.type === args.type) : memories;
|
|
const limited = args.limit ? filtered.slice(0, args.limit) : filtered;
|
|
return {
|
|
success: true,
|
|
data: {
|
|
memories: limited,
|
|
count: limited.length,
|
|
totalTokens: limited.reduce((sum, m) => sum + Math.ceil(m.content.length / 4), 0),
|
|
},
|
|
};
|
|
},
|
|
},
|
|
omniroute_memory_add: {
|
|
name: "omniroute_memory_add",
|
|
description: "Add a new memory entry",
|
|
inputSchema: MemoryAddSchema,
|
|
handler: async (args) => {
|
|
const memory = await createMemory({
|
|
apiKeyId: args.apiKeyId,
|
|
sessionId: args.sessionId || "",
|
|
type: args.type,
|
|
key: args.key,
|
|
content: args.content,
|
|
metadata: args.metadata || {},
|
|
expiresAt: null,
|
|
});
|
|
return {
|
|
success: true,
|
|
data: {
|
|
memory,
|
|
message: "Memory created successfully",
|
|
},
|
|
};
|
|
},
|
|
},
|
|
omniroute_memory_clear: {
|
|
name: "omniroute_memory_clear",
|
|
description: "Clear memories for an API key, optionally filtered by type or age",
|
|
inputSchema: MemoryClearSchema,
|
|
handler: async (args) => {
|
|
const memories = await listMemories({
|
|
apiKeyId: args.apiKeyId,
|
|
type: args.type,
|
|
});
|
|
let toDelete = memories;
|
|
if (args.olderThan) {
|
|
const cutoff = new Date(args.olderThan);
|
|
toDelete = memories.filter((m) => new Date(m.createdAt) < cutoff);
|
|
}
|
|
let deletedCount = 0;
|
|
for (const memory of toDelete) {
|
|
await deleteMemory(memory.id);
|
|
deletedCount++;
|
|
}
|
|
return {
|
|
success: true,
|
|
data: {
|
|
deletedCount,
|
|
message: `Cleared ${deletedCount} memories`,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
};
|
|
|
|
// open-sse/mcp-server/tools/skillTools.ts
|
|
import { z as z5 } from "zod";
|
|
|
|
// src/lib/skills/schemas.ts
|
|
import { z as z4 } from "zod";
|
|
|
|
// src/lib/skills/types.ts
|
|
var SkillMode = /* @__PURE__ */ ((SkillMode2) => {
|
|
SkillMode2["AUTO"] = "auto";
|
|
SkillMode2["MANUAL"] = "manual";
|
|
SkillMode2["HYBRID"] = "hybrid";
|
|
return SkillMode2;
|
|
})(SkillMode || {});
|
|
|
|
// src/lib/skills/schemas.ts
|
|
var SkillSchema = z4.object({
|
|
input: z4.record(z4.string(), z4.unknown()),
|
|
output: z4.record(z4.string(), z4.unknown()),
|
|
});
|
|
var SkillCreateInputSchema = z4
|
|
.object({
|
|
name: z4.string().min(1).max(100),
|
|
version: z4
|
|
.string()
|
|
.regex(/^\d+\.\d+\.\d+$/)
|
|
.default("1.0.0"),
|
|
description: z4.string().max(500).optional(),
|
|
schema: SkillSchema,
|
|
handler: z4.string().min(1),
|
|
enabled: z4.boolean().default(true),
|
|
})
|
|
.strict();
|
|
var SkillUpdateInputSchema = z4
|
|
.object({
|
|
name: z4.string().min(1).max(100).optional(),
|
|
version: z4
|
|
.string()
|
|
.regex(/^\d+\.\d+\.\d+$/)
|
|
.optional(),
|
|
description: z4.string().max(500).optional(),
|
|
schema: SkillSchema.optional(),
|
|
handler: z4.string().min(1).optional(),
|
|
enabled: z4.boolean().optional(),
|
|
})
|
|
.strict();
|
|
var SkillConfigSchema = z4.object({
|
|
enabled: z4.boolean(),
|
|
mode: z4.nativeEnum(SkillMode),
|
|
allowedSkills: z4.array(z4.string()),
|
|
timeout: z4.number().int().positive().default(3e4),
|
|
maxRetries: z4.number().int().min(0).default(3),
|
|
});
|
|
|
|
// src/lib/skills/registry.ts
|
|
import { randomUUID } from "crypto";
|
|
var SkillRegistry = class _SkillRegistry {
|
|
static instance;
|
|
registeredSkills = /* @__PURE__ */ new Map();
|
|
versionCache = /* @__PURE__ */ new Map();
|
|
constructor() {}
|
|
static getInstance() {
|
|
if (!_SkillRegistry.instance) {
|
|
_SkillRegistry.instance = new _SkillRegistry();
|
|
}
|
|
return _SkillRegistry.instance;
|
|
}
|
|
async register(skillData) {
|
|
const parsed = SkillCreateInputSchema.parse(skillData);
|
|
const db2 = getDbInstance();
|
|
const id = randomUUID();
|
|
const now = /* @__PURE__ */ new Date();
|
|
db2
|
|
.prepare(
|
|
`INSERT INTO skills (id, api_key_id, name, version, description, schema, handler, enabled, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
)
|
|
.run(
|
|
id,
|
|
skillData.apiKeyId,
|
|
parsed.name,
|
|
parsed.version,
|
|
parsed.description || null,
|
|
JSON.stringify(parsed.schema),
|
|
parsed.handler,
|
|
parsed.enabled ? 1 : 0,
|
|
now.toISOString(),
|
|
now.toISOString()
|
|
);
|
|
const skill = {
|
|
id,
|
|
apiKeyId: skillData.apiKeyId,
|
|
name: parsed.name,
|
|
version: parsed.version,
|
|
description: parsed.description || "",
|
|
schema: parsed.schema,
|
|
handler: parsed.handler,
|
|
enabled: parsed.enabled,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
this.registeredSkills.set(`${parsed.name}@${parsed.version}`, skill);
|
|
this.updateVersionCache(skill);
|
|
return skill;
|
|
}
|
|
async unregister(name, version, apiKeyId) {
|
|
const db2 = getDbInstance();
|
|
if (version) {
|
|
const key = `${name}@${version}`;
|
|
const skill = this.registeredSkills.get(key);
|
|
if (skill && (!apiKeyId || skill.apiKeyId === apiKeyId)) {
|
|
db2.prepare("DELETE FROM skills WHERE id = ?").run(skill.id);
|
|
this.registeredSkills.delete(key);
|
|
this.clearVersionCache(name);
|
|
return true;
|
|
}
|
|
} else {
|
|
const deleted = db2
|
|
.prepare("DELETE FROM skills WHERE name = ? AND (? IS NULL OR api_key_id = ?)")
|
|
.run(name, apiKeyId || null, apiKeyId || null);
|
|
if (deleted.changes > 0) {
|
|
const keysToDelete = Array.from(this.registeredSkills.keys()).filter((k) =>
|
|
k.startsWith(`${name}@`)
|
|
);
|
|
keysToDelete.forEach((k) => this.registeredSkills.delete(k));
|
|
this.clearVersionCache(name);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
list(apiKeyId) {
|
|
if (apiKeyId) {
|
|
return Array.from(this.registeredSkills.values()).filter((s) => s.apiKeyId === apiKeyId);
|
|
}
|
|
return Array.from(this.registeredSkills.values());
|
|
}
|
|
getSkill(name, apiKeyId) {
|
|
return this.registeredSkills.get(name);
|
|
}
|
|
getSkillVersions(name) {
|
|
const cached = this.versionCache.get(name);
|
|
if (!cached) return [];
|
|
return Array.from(cached.values()).sort((a, b) => this.compareVersions(b.version, a.version));
|
|
}
|
|
resolveVersion(name, constraint, apiKeyId) {
|
|
const versions = this.getSkillVersions(name);
|
|
if (versions.length === 0) return void 0;
|
|
const operator = constraint.charAt(0);
|
|
const version = constraint.slice(1);
|
|
switch (operator) {
|
|
case "^":
|
|
return versions.find((s) => this.satisfies(s.version, version, "^"));
|
|
case "~":
|
|
return versions.find((s) => this.satisfies(s.version, version, "~"));
|
|
case ">":
|
|
case ">=":
|
|
case "<":
|
|
case "<=":
|
|
case "==":
|
|
return versions.find((s) => this.satisfies(s.version, version, operator));
|
|
default:
|
|
return versions.find((s) => s.version === constraint);
|
|
}
|
|
}
|
|
satisfies(version, base, operator) {
|
|
const [baseMajor, baseMinor, basePatch] = base.split(".").map(Number);
|
|
const [verMajor, verMinor, verPatch] = version.split(".").map(Number);
|
|
switch (operator) {
|
|
case "^":
|
|
return (
|
|
verMajor === baseMajor &&
|
|
(verMinor > baseMinor || (verMinor === baseMinor && verPatch >= basePatch))
|
|
);
|
|
case "~":
|
|
return verMajor === baseMajor && verMinor === baseMinor && verPatch >= basePatch;
|
|
case ">":
|
|
return this.compareVersions(version, base) > 0;
|
|
case ">=":
|
|
return this.compareVersions(version, base) >= 0;
|
|
case "<":
|
|
return this.compareVersions(version, base) < 0;
|
|
case "<=":
|
|
return this.compareVersions(version, base) <= 0;
|
|
case "==":
|
|
return version === base;
|
|
default:
|
|
return version === base;
|
|
}
|
|
}
|
|
compareVersions(a, b) {
|
|
const [aMajor, aMinor, aPatch] = a.split(".").map(Number);
|
|
const [bMajor, bMinor, bPatch] = b.split(".").map(Number);
|
|
if (aMajor !== bMajor) return aMajor - bMajor;
|
|
if (aMinor !== bMinor) return aMinor - bMinor;
|
|
return aPatch - bPatch;
|
|
}
|
|
updateVersionCache(skill) {
|
|
if (!this.versionCache.has(skill.name)) {
|
|
this.versionCache.set(skill.name, /* @__PURE__ */ new Map());
|
|
}
|
|
this.versionCache.get(skill.name).set(skill.version, skill);
|
|
}
|
|
clearVersionCache(name) {
|
|
this.versionCache.delete(name);
|
|
}
|
|
async loadFromDatabase(apiKeyId) {
|
|
const db2 = getDbInstance();
|
|
const rows = apiKeyId
|
|
? db2.prepare("SELECT * FROM skills WHERE api_key_id = ?").all(apiKeyId)
|
|
: db2.prepare("SELECT * FROM skills").all();
|
|
for (const row of rows) {
|
|
const skill = {
|
|
id: row.id,
|
|
apiKeyId: row.api_key_id,
|
|
name: row.name,
|
|
version: row.version,
|
|
description: row.description || "",
|
|
schema: JSON.parse(row.schema),
|
|
handler: row.handler,
|
|
enabled: row.enabled === 1,
|
|
createdAt: new Date(row.created_at),
|
|
updatedAt: new Date(row.updated_at),
|
|
};
|
|
this.registeredSkills.set(`${skill.name}@${skill.version}`, skill);
|
|
this.updateVersionCache(skill);
|
|
}
|
|
}
|
|
};
|
|
var skillRegistry = SkillRegistry.getInstance();
|
|
|
|
// src/lib/skills/executor.ts
|
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
var SkillExecutor = class _SkillExecutor {
|
|
static instance;
|
|
handlers = /* @__PURE__ */ new Map();
|
|
timeout = 3e4;
|
|
maxRetries = 3;
|
|
constructor() {}
|
|
static getInstance() {
|
|
if (!_SkillExecutor.instance) {
|
|
_SkillExecutor.instance = new _SkillExecutor();
|
|
}
|
|
return _SkillExecutor.instance;
|
|
}
|
|
registerHandler(name, handler) {
|
|
this.handlers.set(name, handler);
|
|
}
|
|
setTimeout(ms) {
|
|
this.timeout = ms;
|
|
}
|
|
setMaxRetries(count) {
|
|
this.maxRetries = count;
|
|
}
|
|
async execute(skillName, input, context) {
|
|
const skill = skillRegistry.getSkill(skillName, context.apiKeyId);
|
|
if (!skill) {
|
|
throw new Error(`Skill not found: ${skillName}`);
|
|
}
|
|
if (!skill.enabled) {
|
|
throw new Error(`Skill is disabled: ${skillName}`);
|
|
}
|
|
const db2 = getDbInstance();
|
|
const executionId = randomUUID2();
|
|
const startTime = Date.now();
|
|
try {
|
|
db2
|
|
.prepare(
|
|
`INSERT INTO skill_executions (id, skill_id, api_key_id, session_id, input, status, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
)
|
|
.run(
|
|
executionId,
|
|
skill.id,
|
|
context.apiKeyId,
|
|
context.sessionId || null,
|
|
JSON.stringify(input),
|
|
"running" /* RUNNING */,
|
|
/* @__PURE__ */ new Date().toISOString()
|
|
);
|
|
const handler = this.handlers.get(skill.handler);
|
|
if (!handler) {
|
|
throw new Error(`Handler not found: ${skill.handler}`);
|
|
}
|
|
let output = null;
|
|
let errorMessage = null;
|
|
let status = "success"; /* SUCCESS */
|
|
try {
|
|
const result = await this.executeWithTimeout(
|
|
handler(input, { apiKeyId: context.apiKeyId, sessionId: context.sessionId || "" })
|
|
);
|
|
output = result;
|
|
} catch (err) {
|
|
errorMessage = err instanceof Error ? err.message : String(err);
|
|
status = "error" /* ERROR */;
|
|
}
|
|
const durationMs = Date.now() - startTime;
|
|
db2
|
|
.prepare(
|
|
`UPDATE skill_executions SET output = ?, status = ?, error_message = ?, duration_ms = ? WHERE id = ?`
|
|
)
|
|
.run(output ? JSON.stringify(output) : null, status, errorMessage, durationMs, executionId);
|
|
return {
|
|
id: executionId,
|
|
skillId: skill.id,
|
|
apiKeyId: context.apiKeyId,
|
|
sessionId: context.sessionId || "",
|
|
input,
|
|
output,
|
|
status,
|
|
errorMessage,
|
|
durationMs,
|
|
createdAt: /* @__PURE__ */ new Date(),
|
|
};
|
|
} catch (err) {
|
|
const durationMs = Date.now() - startTime;
|
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
db2
|
|
.prepare(
|
|
`UPDATE skill_executions SET status = ?, error_message = ?, duration_ms = ? WHERE id = ?`
|
|
)
|
|
.run("error" /* ERROR */, errorMessage, durationMs, executionId);
|
|
throw err;
|
|
}
|
|
}
|
|
async executeWithTimeout(promise) {
|
|
return Promise.race([
|
|
promise,
|
|
new Promise((_, reject) =>
|
|
setTimeout(() => reject(new Error("Skill execution timed out")), this.timeout)
|
|
),
|
|
]);
|
|
}
|
|
getExecution(executionId) {
|
|
const db2 = getDbInstance();
|
|
const row = db2.prepare("SELECT * FROM skill_executions WHERE id = ?").get(executionId);
|
|
if (!row) return void 0;
|
|
return {
|
|
id: row.id,
|
|
skillId: row.skill_id,
|
|
apiKeyId: row.api_key_id,
|
|
sessionId: row.session_id || "",
|
|
input: JSON.parse(row.input),
|
|
output: row.output ? JSON.parse(row.output) : null,
|
|
status: row.status,
|
|
errorMessage: row.error_message,
|
|
durationMs: row.duration_ms,
|
|
createdAt: new Date(row.created_at),
|
|
};
|
|
}
|
|
listExecutions(apiKeyId, limit = 50) {
|
|
const db2 = getDbInstance();
|
|
const rows = apiKeyId
|
|
? db2
|
|
.prepare(
|
|
"SELECT * FROM skill_executions WHERE api_key_id = ? ORDER BY created_at DESC LIMIT ?"
|
|
)
|
|
.all(apiKeyId, limit)
|
|
: db2.prepare("SELECT * FROM skill_executions ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
return rows.map((row) => ({
|
|
id: row.id,
|
|
skillId: row.skill_id,
|
|
apiKeyId: row.api_key_id,
|
|
sessionId: row.session_id || "",
|
|
input: JSON.parse(row.input),
|
|
output: row.output ? JSON.parse(row.output) : null,
|
|
status: row.status,
|
|
errorMessage: row.error_message,
|
|
durationMs: row.duration_ms,
|
|
createdAt: new Date(row.created_at),
|
|
}));
|
|
}
|
|
};
|
|
var skillExecutor = SkillExecutor.getInstance();
|
|
|
|
// open-sse/mcp-server/tools/skillTools.ts
|
|
var SkillListSchema = z5.object({
|
|
apiKeyId: z5.string().optional(),
|
|
name: z5.string().optional(),
|
|
enabled: z5.boolean().optional(),
|
|
});
|
|
var SkillEnableSchema = z5.object({
|
|
apiKeyId: z5.string(),
|
|
skillId: z5.string(),
|
|
enabled: z5.boolean(),
|
|
});
|
|
var SkillExecuteSchema = z5.object({
|
|
apiKeyId: z5.string(),
|
|
skillName: z5.string(),
|
|
input: z5.record(z5.string(), z5.unknown()),
|
|
sessionId: z5.string().optional(),
|
|
});
|
|
var skillTools = {
|
|
omniroute_skills_list: {
|
|
name: "omniroute_skills_list",
|
|
description: "List all registered skills with optional filtering by API key or name",
|
|
inputSchema: SkillListSchema,
|
|
handler: async (args) => {
|
|
await skillRegistry.loadFromDatabase(args.apiKeyId);
|
|
const skills = skillRegistry.list(args.apiKeyId);
|
|
let filtered = skills;
|
|
if (args.name) {
|
|
filtered = filtered.filter((s) => s.name.includes(args.name));
|
|
}
|
|
if (args.enabled !== void 0) {
|
|
filtered = filtered.filter((s) => s.enabled === args.enabled);
|
|
}
|
|
return {
|
|
skills: filtered.map((s) => ({
|
|
id: s.id,
|
|
name: s.name,
|
|
version: s.version,
|
|
description: s.description,
|
|
enabled: s.enabled,
|
|
createdAt: s.createdAt.toISOString(),
|
|
})),
|
|
count: filtered.length,
|
|
};
|
|
},
|
|
},
|
|
omniroute_skills_enable: {
|
|
name: "omniroute_skills_enable",
|
|
description: "Enable or disable a specific skill by ID",
|
|
inputSchema: SkillEnableSchema,
|
|
handler: async (args) => {
|
|
const skill = skillRegistry.getSkill(args.skillId, args.apiKeyId);
|
|
if (!skill) {
|
|
throw new Error(`Skill not found: ${args.skillId}`);
|
|
}
|
|
await skillRegistry.register({
|
|
...skill,
|
|
enabled: args.enabled,
|
|
apiKeyId: args.apiKeyId,
|
|
});
|
|
return { success: true, skillId: args.skillId, enabled: args.enabled };
|
|
},
|
|
},
|
|
omniroute_skills_execute: {
|
|
name: "omniroute_skills_execute",
|
|
description: "Execute a skill with provided input and return the result",
|
|
inputSchema: SkillExecuteSchema,
|
|
handler: async (args) => {
|
|
const execution = await skillExecutor.execute(args.skillName, args.input, {
|
|
apiKeyId: args.apiKeyId,
|
|
sessionId: args.sessionId,
|
|
});
|
|
return {
|
|
id: execution.id,
|
|
skillId: execution.skillId,
|
|
status: execution.status,
|
|
output: execution.output,
|
|
error: execution.errorMessage,
|
|
duration: execution.durationMs,
|
|
createdAt: execution.createdAt.toISOString(),
|
|
};
|
|
},
|
|
},
|
|
omniroute_skills_executions: {
|
|
name: "omniroute_skills_executions",
|
|
description: "List recent skill execution history",
|
|
inputSchema: z5.object({
|
|
apiKeyId: z5.string().optional(),
|
|
limit: z5.number().int().positive().max(100).optional(),
|
|
}),
|
|
handler: async (args) => {
|
|
const executions = skillExecutor.listExecutions(args.apiKeyId, args.limit || 50);
|
|
return {
|
|
executions: executions.map((e) => ({
|
|
id: e.id,
|
|
skillId: e.skillId,
|
|
status: e.status,
|
|
duration: e.durationMs,
|
|
error: e.errorMessage,
|
|
createdAt: e.createdAt.toISOString(),
|
|
})),
|
|
count: executions.length,
|
|
};
|
|
},
|
|
},
|
|
};
|
|
|
|
// open-sse/mcp-server/server.ts
|
|
var OMNIROUTE_BASE_URL2 = process.env.OMNIROUTE_BASE_URL || "http://localhost:20128";
|
|
var OMNIROUTE_API_KEY2 = process.env.OMNIROUTE_API_KEY || "";
|
|
var MCP_ENFORCE_SCOPES = process.env.OMNIROUTE_MCP_ENFORCE_SCOPES === "true";
|
|
var MCP_ALLOWED_SCOPES = new Set(
|
|
(process.env.OMNIROUTE_MCP_SCOPES || "")
|
|
.split(",")
|
|
.map((s) => s.trim())
|
|
.filter(Boolean)
|
|
);
|
|
function toRecord2(value) {
|
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
}
|
|
function toArray(value) {
|
|
return Array.isArray(value) ? value : [];
|
|
}
|
|
function toString2(value, fallback = "") {
|
|
return typeof value === "string" ? value : fallback;
|
|
}
|
|
function toNumber3(value, fallback = 0) {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
}
|
|
function toStringArray(value, fallback = []) {
|
|
const values = toArray(value).filter((entry) => typeof entry === "string");
|
|
return values.length > 0 ? values : fallback;
|
|
}
|
|
function normalizeComboModels(rawModels) {
|
|
return toArray(rawModels).map((rawModel, index) => {
|
|
const model = toRecord2(rawModel);
|
|
return {
|
|
provider: toString2(model.provider, "unknown"),
|
|
model: toString2(model.model, "unknown"),
|
|
priority: toNumber3(model.priority, index + 1),
|
|
};
|
|
});
|
|
}
|
|
async function omniRouteFetch(path4, options = {}) {
|
|
const url = `${OMNIROUTE_BASE_URL2}${path4}`;
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
...(OMNIROUTE_API_KEY2 ? { Authorization: `Bearer ${OMNIROUTE_API_KEY2}` } : {}),
|
|
...(options.headers || {}),
|
|
};
|
|
const response = await fetch(url, { ...options, headers, signal: AbortSignal.timeout(1e4) });
|
|
if (!response.ok) {
|
|
const errorText = await response.text().catch(() => "Unknown error");
|
|
throw new Error(`OmniRoute API error [${response.status}]: ${errorText}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
function withScopeEnforcement(toolName, handler) {
|
|
return async (args, extra) => {
|
|
const scopeContext = resolveCallerScopeContext(extra, Array.from(MCP_ALLOWED_SCOPES));
|
|
const scopeCheck = evaluateToolScopes(toolName, scopeContext.scopes, MCP_ENFORCE_SCOPES);
|
|
if (!scopeCheck.allowed) {
|
|
const missingScopes =
|
|
scopeCheck.missing.length > 0 ? scopeCheck.missing.join(", ") : "unavailable";
|
|
const reason = scopeCheck.reason || "scope_check_failed";
|
|
const msg = `Insufficient MCP scopes for ${toolName}. Missing: ${missingScopes}. Caller=${scopeContext.callerId}, source=${scopeContext.source}.`;
|
|
const safeArgs = args && typeof args === "object" ? toRecord2(args) : { rawArgs: args };
|
|
await logToolCall(
|
|
toolName,
|
|
{
|
|
...safeArgs,
|
|
_scopeCheck: {
|
|
callerId: scopeContext.callerId,
|
|
source: scopeContext.source,
|
|
required: scopeCheck.required,
|
|
provided: scopeCheck.provided,
|
|
missing: scopeCheck.missing,
|
|
},
|
|
},
|
|
null,
|
|
0,
|
|
false,
|
|
`scope_denied:${reason}`
|
|
);
|
|
return {
|
|
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
return handler(args, extra);
|
|
};
|
|
}
|
|
async function handleGetHealth() {
|
|
const start = Date.now();
|
|
try {
|
|
const [healthRaw, resilienceRaw, rateLimitsRaw] = await Promise.allSettled([
|
|
omniRouteFetch("/api/monitoring/health"),
|
|
omniRouteFetch("/api/resilience"),
|
|
omniRouteFetch("/api/rate-limits"),
|
|
]);
|
|
const health = healthRaw.status === "fulfilled" ? toRecord2(healthRaw.value) : {};
|
|
const resilience = resilienceRaw.status === "fulfilled" ? toRecord2(resilienceRaw.value) : {};
|
|
const rateLimits = rateLimitsRaw.status === "fulfilled" ? toRecord2(rateLimitsRaw.value) : {};
|
|
const memoryUsageRaw = toRecord2(health.memoryUsage);
|
|
const cacheStatsRaw = toRecord2(health.cacheStats);
|
|
const resilienceCircuitBreakers = toArray(resilience.circuitBreakers);
|
|
const rateLimitEntries = toArray(rateLimits.limits);
|
|
const result = {
|
|
uptime: toString2(health.uptime, "unknown"),
|
|
version: toString2(health.version, "unknown"),
|
|
memoryUsage: {
|
|
heapUsed: toNumber3(memoryUsageRaw.heapUsed, 0),
|
|
heapTotal: toNumber3(memoryUsageRaw.heapTotal, 0),
|
|
},
|
|
circuitBreakers: resilienceCircuitBreakers,
|
|
rateLimits: rateLimitEntries,
|
|
cacheStats:
|
|
Object.keys(cacheStatsRaw).length > 0
|
|
? {
|
|
hits: toNumber3(cacheStatsRaw.hits, 0),
|
|
misses: toNumber3(cacheStatsRaw.misses, 0),
|
|
hitRate: toNumber3(cacheStatsRaw.hitRate, 0),
|
|
}
|
|
: void 0,
|
|
};
|
|
await logToolCall("omniroute_get_health", {}, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_get_health", {}, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleListCombos(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const combosRaw = await omniRouteFetch("/api/combos");
|
|
const combosRecord = toRecord2(combosRaw);
|
|
const combos = Array.isArray(combosRecord.combos)
|
|
? combosRecord.combos
|
|
: Array.isArray(combosRaw)
|
|
? combosRaw
|
|
: [];
|
|
let metrics = {};
|
|
if (args.includeMetrics) {
|
|
metrics = toRecord2(await omniRouteFetch("/api/combos/metrics").catch(() => ({})));
|
|
}
|
|
const result = {
|
|
combos: toArray(combos).map((rawCombo) => {
|
|
const combo = toRecord2(rawCombo);
|
|
const comboData = toRecord2(combo.data);
|
|
const comboId = toString2(combo.id, "");
|
|
const modelsSource =
|
|
Array.isArray(combo.models) && combo.models.length > 0 ? combo.models : comboData.models;
|
|
return {
|
|
id: comboId,
|
|
name: toString2(combo.name, comboId || "unnamed"),
|
|
models: normalizeComboModels(modelsSource),
|
|
strategy: toString2(combo.strategy, toString2(comboData.strategy, "priority")),
|
|
enabled: combo.enabled !== false,
|
|
...(args.includeMetrics ? { metrics: metrics[comboId] ?? null } : {}),
|
|
};
|
|
}),
|
|
};
|
|
await logToolCall("omniroute_list_combos", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_list_combos", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleGetComboMetrics(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const result = await omniRouteFetch(
|
|
`/api/combos/metrics?comboId=${encodeURIComponent(args.comboId)}`
|
|
);
|
|
await logToolCall("omniroute_get_combo_metrics", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_get_combo_metrics", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleSwitchCombo(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const result = await omniRouteFetch(`/api/combos/${encodeURIComponent(args.comboId)}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ isActive: args.active }),
|
|
});
|
|
await logToolCall("omniroute_switch_combo", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_switch_combo", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleCheckQuota(args) {
|
|
const start = Date.now();
|
|
try {
|
|
let path4 = "/api/usage/quota";
|
|
if (args.connectionId) path4 += `?connectionId=${encodeURIComponent(args.connectionId)}`;
|
|
else if (args.provider) path4 += `?provider=${encodeURIComponent(args.provider)}`;
|
|
const result = normalizeQuotaResponse(await omniRouteFetch(path4), {
|
|
provider: args.provider || null,
|
|
connectionId: args.connectionId || null,
|
|
});
|
|
await logToolCall("omniroute_check_quota", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_check_quota", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleRouteRequest(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const body = {
|
|
model: args.model,
|
|
messages: args.messages,
|
|
stream: false,
|
|
// MCP tool always returns non-streaming
|
|
};
|
|
if (args.combo) {
|
|
body["x-combo"] = args.combo;
|
|
}
|
|
const raw = await omniRouteFetch("/v1/chat/completions", {
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
});
|
|
const choices = toArray(raw.choices);
|
|
const firstChoice = toRecord2(choices[0]);
|
|
const firstMessage = toRecord2(firstChoice.message);
|
|
const usage = toRecord2(raw.usage);
|
|
const result = {
|
|
response: {
|
|
content: toString2(firstMessage.content, ""),
|
|
model: toString2(raw.model, args.model),
|
|
tokens: {
|
|
prompt: toNumber3(usage.prompt_tokens, 0),
|
|
completion: toNumber3(usage.completion_tokens, 0),
|
|
},
|
|
},
|
|
routing: {
|
|
provider: toString2(raw.provider, "unknown"),
|
|
combo: raw.combo ?? null,
|
|
fallbacksTriggered: toNumber3(raw.fallbacksTriggered, 0),
|
|
cost: toNumber3(raw.cost, 0),
|
|
latencyMs: Date.now() - start,
|
|
routingExplanation: toString2(
|
|
raw.routingExplanation,
|
|
"Request routed through primary provider"
|
|
),
|
|
},
|
|
};
|
|
await logToolCall(
|
|
"omniroute_route_request",
|
|
{ model: args.model, messageCount: args.messages.length },
|
|
result.routing,
|
|
Date.now() - start,
|
|
true
|
|
);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall(
|
|
"omniroute_route_request",
|
|
{ model: args.model },
|
|
null,
|
|
Date.now() - start,
|
|
false,
|
|
msg
|
|
);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleCostReport(args) {
|
|
const start = Date.now();
|
|
try {
|
|
const period = args.period || "session";
|
|
const rangeMap = {
|
|
session: "1d",
|
|
day: "1d",
|
|
week: "7d",
|
|
month: "30d",
|
|
};
|
|
const range = rangeMap[period] || "30d";
|
|
const raw = toRecord2(
|
|
await omniRouteFetch(`/api/usage/analytics?range=${encodeURIComponent(range)}`)
|
|
);
|
|
const tokenCount = toRecord2(raw.tokenCount);
|
|
const budget = toRecord2(raw.budget);
|
|
const result = {
|
|
period,
|
|
totalCost: toNumber3(raw.totalCost, 0),
|
|
requestCount: toNumber3(raw.requestCount, 0),
|
|
tokenCount: {
|
|
prompt: toNumber3(tokenCount.prompt, 0),
|
|
completion: toNumber3(tokenCount.completion, 0),
|
|
},
|
|
byProvider: toArray(raw.byProvider),
|
|
byModel: toArray(raw.byModel),
|
|
budget: {
|
|
limit: budget.limit ?? null,
|
|
remaining: budget.remaining ?? null,
|
|
},
|
|
};
|
|
await logToolCall("omniroute_cost_report", args, result, Date.now() - start, true);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_cost_report", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
async function handleListModelsCatalog(args) {
|
|
const start = Date.now();
|
|
try {
|
|
let path4 = "/v1/models";
|
|
let isProviderSpecific = false;
|
|
let source = "local_catalog";
|
|
let warning;
|
|
if (args.provider && !args.capability) {
|
|
path4 = `/api/providers/${encodeURIComponent(args.provider)}/models?excludeHidden=true`;
|
|
isProviderSpecific = true;
|
|
} else {
|
|
const params = new URLSearchParams();
|
|
if (args.provider) params.set("provider", args.provider);
|
|
if (args.capability) params.set("capability", args.capability);
|
|
if (params.toString()) path4 += `?${params.toString()}`;
|
|
}
|
|
const raw = toRecord2(await omniRouteFetch(path4));
|
|
let rawModels = [];
|
|
if (isProviderSpecific) {
|
|
rawModels = Array.isArray(raw.models) ? raw.models : [];
|
|
source = typeof raw.source === "string" ? raw.source : "api";
|
|
if (raw.warning) warning = String(raw.warning);
|
|
} else {
|
|
rawModels = Array.isArray(raw.data) ? raw.data : [];
|
|
source = "local_catalog";
|
|
}
|
|
const result = {
|
|
models: rawModels.map((rawModel) => {
|
|
const model = toRecord2(rawModel);
|
|
return {
|
|
id: toString2(model.id, ""),
|
|
provider: toString2(
|
|
model.owned_by,
|
|
toString2(model.provider, args.provider || "unknown")
|
|
),
|
|
capabilities: toStringArray(model.capabilities, ["chat"]),
|
|
status: toString2(model.status, "available"),
|
|
pricing: model.pricing,
|
|
};
|
|
}),
|
|
source,
|
|
...(warning ? { warning } : {}),
|
|
};
|
|
await logToolCall(
|
|
"omniroute_list_models_catalog",
|
|
args,
|
|
{ modelCount: result.models.length },
|
|
Date.now() - start,
|
|
true
|
|
);
|
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
await logToolCall("omniroute_list_models_catalog", args, null, Date.now() - start, false, msg);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
}
|
|
function createMcpServer() {
|
|
const server = new McpServer({
|
|
name: "omniroute",
|
|
version: process.env.npm_package_version || "1.8.1",
|
|
});
|
|
server.registerTool(
|
|
"omniroute_get_health",
|
|
{
|
|
description:
|
|
"Returns OmniRoute health status including uptime, memory, circuit breakers, rate limits, and cache stats",
|
|
inputSchema: getHealthInput,
|
|
},
|
|
withScopeEnforcement("omniroute_get_health", async (args) => {
|
|
getHealthInput.parse(args ?? {});
|
|
return handleGetHealth();
|
|
})
|
|
);
|
|
server.registerTool(
|
|
"omniroute_list_combos",
|
|
{
|
|
description:
|
|
"Lists all configured combos (model chains) with strategies and optional metrics",
|
|
inputSchema: listCombosInput,
|
|
},
|
|
withScopeEnforcement("omniroute_list_combos", (args) =>
|
|
handleListCombos(listCombosInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_get_combo_metrics",
|
|
{
|
|
description: "Returns detailed performance metrics for a specific combo",
|
|
inputSchema: getComboMetricsInput,
|
|
},
|
|
withScopeEnforcement("omniroute_get_combo_metrics", (args) =>
|
|
handleGetComboMetrics(getComboMetricsInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_switch_combo",
|
|
{
|
|
description: "Activates or deactivates a combo for routing",
|
|
inputSchema: switchComboInput,
|
|
},
|
|
withScopeEnforcement("omniroute_switch_combo", (args) =>
|
|
handleSwitchCombo(switchComboInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_check_quota",
|
|
{
|
|
description: "Checks remaining API quota for one or all providers",
|
|
inputSchema: checkQuotaInput,
|
|
},
|
|
withScopeEnforcement("omniroute_check_quota", (args) =>
|
|
handleCheckQuota(checkQuotaInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_route_request",
|
|
{
|
|
description: "Sends a chat completion request through OmniRoute intelligent routing",
|
|
inputSchema: routeRequestInput,
|
|
},
|
|
withScopeEnforcement("omniroute_route_request", (args) =>
|
|
handleRouteRequest(routeRequestInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_cost_report",
|
|
{
|
|
description: "Generates a cost report for the specified period",
|
|
inputSchema: costReportInput,
|
|
},
|
|
withScopeEnforcement("omniroute_cost_report", (args) =>
|
|
handleCostReport(costReportInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_list_models_catalog",
|
|
{
|
|
description: "Lists all available AI models across providers with capabilities and pricing",
|
|
inputSchema: listModelsCatalogInput,
|
|
},
|
|
withScopeEnforcement("omniroute_list_models_catalog", (args) =>
|
|
handleListModelsCatalog(listModelsCatalogInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_simulate_route",
|
|
{
|
|
description: "Simulates the routing path a request would take without executing it (dry-run)",
|
|
inputSchema: simulateRouteInput,
|
|
},
|
|
withScopeEnforcement("omniroute_simulate_route", (args) =>
|
|
handleSimulateRoute(simulateRouteInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_set_budget_guard",
|
|
{
|
|
description:
|
|
"Sets a session budget limit with configurable action when exceeded (degrade/block/alert)",
|
|
inputSchema: setBudgetGuardInput,
|
|
},
|
|
withScopeEnforcement("omniroute_set_budget_guard", (args) =>
|
|
handleSetBudgetGuard(setBudgetGuardInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_set_routing_strategy",
|
|
{
|
|
description:
|
|
"Updates combo routing strategy at runtime (priority/weighted/round-robin/auto/etc.)",
|
|
inputSchema: setRoutingStrategyInput,
|
|
},
|
|
withScopeEnforcement("omniroute_set_routing_strategy", (args) =>
|
|
handleSetRoutingStrategy(setRoutingStrategyInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_set_resilience_profile",
|
|
{
|
|
description:
|
|
"Applies a resilience profile controlling circuit breakers, retries, timeouts, and fallback depth",
|
|
inputSchema: setResilienceProfileInput,
|
|
},
|
|
withScopeEnforcement("omniroute_set_resilience_profile", (args) =>
|
|
handleSetResilienceProfile(setResilienceProfileInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_test_combo",
|
|
{
|
|
description:
|
|
"Tests each provider in a combo with a real prompt, reporting latency, cost, and success per provider",
|
|
inputSchema: testComboInput,
|
|
},
|
|
withScopeEnforcement("omniroute_test_combo", (args) =>
|
|
handleTestCombo(testComboInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_get_provider_metrics",
|
|
{
|
|
description:
|
|
"Returns detailed metrics for a specific provider including latency percentiles and circuit breaker state",
|
|
inputSchema: getProviderMetricsInput,
|
|
},
|
|
withScopeEnforcement("omniroute_get_provider_metrics", (args) =>
|
|
handleGetProviderMetrics(getProviderMetricsInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_best_combo_for_task",
|
|
{
|
|
description:
|
|
"Recommends the best combo for a task type based on provider fitness and constraints",
|
|
inputSchema: bestComboForTaskInput,
|
|
},
|
|
withScopeEnforcement("omniroute_best_combo_for_task", (args) =>
|
|
handleBestComboForTask(bestComboForTaskInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_explain_route",
|
|
{
|
|
description:
|
|
"Explains why a request was routed to a specific provider, showing scoring factors and fallbacks",
|
|
inputSchema: explainRouteInput,
|
|
},
|
|
withScopeEnforcement("omniroute_explain_route", (args) =>
|
|
handleExplainRoute(explainRouteInput.parse(args))
|
|
)
|
|
);
|
|
server.registerTool(
|
|
"omniroute_get_session_snapshot",
|
|
{
|
|
description:
|
|
"Returns a full snapshot of the current working session: cost, tokens, top models, errors, budget status",
|
|
inputSchema: getSessionSnapshotInput,
|
|
},
|
|
withScopeEnforcement("omniroute_get_session_snapshot", async (args) => {
|
|
getSessionSnapshotInput.parse(args ?? {});
|
|
return handleGetSessionSnapshot();
|
|
})
|
|
);
|
|
server.registerTool(
|
|
"omniroute_sync_pricing",
|
|
{
|
|
description:
|
|
"Syncs pricing data from external sources (LiteLLM) into OmniRoute without overwriting user-set prices",
|
|
inputSchema: syncPricingInput,
|
|
},
|
|
withScopeEnforcement("omniroute_sync_pricing", (args) =>
|
|
handleSyncPricing(syncPricingInput.parse(args))
|
|
)
|
|
);
|
|
Object.values(memoryTools).forEach((toolDef) => {
|
|
server.registerTool(
|
|
toolDef.name,
|
|
{
|
|
description: toolDef.description,
|
|
// @ts-ignore: dynamic zod access
|
|
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", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
})
|
|
);
|
|
});
|
|
Object.values(skillTools).forEach((toolDef) => {
|
|
server.registerTool(
|
|
toolDef.name,
|
|
{
|
|
description: toolDef.description,
|
|
// @ts-ignore: dynamic zod access
|
|
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", text: JSON.stringify(result, null, 2) }] };
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
}
|
|
})
|
|
);
|
|
});
|
|
return server;
|
|
}
|
|
async function startMcpStdio() {
|
|
const server = createMcpServer();
|
|
const transport = new StdioServerTransport();
|
|
const version = process.env.npm_package_version || "1.8.1";
|
|
const stopHeartbeat = startMcpHeartbeat({
|
|
version,
|
|
scopesEnforced: MCP_ENFORCE_SCOPES,
|
|
allowedScopes: Array.from(MCP_ALLOWED_SCOPES),
|
|
toolCount: MCP_TOOLS.length,
|
|
});
|
|
const stopHeartbeatOnce = () => {
|
|
stopHeartbeat();
|
|
};
|
|
process.once("exit", stopHeartbeatOnce);
|
|
process.once("SIGINT", stopHeartbeatOnce);
|
|
process.once("SIGTERM", stopHeartbeatOnce);
|
|
console.error("[MCP] OmniRoute MCP Server starting (stdio transport)...");
|
|
try {
|
|
await server.connect(transport);
|
|
console.error("[MCP] OmniRoute MCP Server connected and ready.");
|
|
} finally {
|
|
stopHeartbeatOnce();
|
|
process.off("exit", stopHeartbeatOnce);
|
|
process.off("SIGINT", stopHeartbeatOnce);
|
|
process.off("SIGTERM", stopHeartbeatOnce);
|
|
}
|
|
}
|
|
if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"))) {
|
|
startMcpStdio().catch((err) => {
|
|
console.error("[MCP] Fatal error:", err);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
export { createMcpServer, startMcpStdio };
|