Skip to main content
Glama
policy.ts10.9 kB
/** * Phase 3: Policy-Based Routing * Advanced routing with risk levels, budget controls, and simulation */ import type { ModelLayer } from '../../config/models.js'; import type { TaskType } from '../../mcp/types.js'; import { logger } from '../../logging/logger.js'; export interface RoutingPolicy { id: string; name: string; description: string; rules: PolicyRule[]; priority: number; enabled: boolean; } export interface PolicyRule { condition: RuleCondition; action: RuleAction; risk: RiskLevel; } export interface RuleCondition { taskType?: TaskType | TaskType[]; complexity?: 'low' | 'medium' | 'high'; filePattern?: string; // regex pattern costThreshold?: number; timeOfDay?: { start: number; end: number }; // hours 0-23 userRole?: string[]; } export interface RuleAction { type: 'allow' | 'deny' | 'escalate' | 'downgrade' | 'route-to'; targetLayer?: ModelLayer; maxCost?: number; requireApproval?: boolean; alertUsers?: string[]; } export type RiskLevel = 'low' | 'medium' | 'high' | 'critical'; /** * Predefined routing policies */ export const DEFAULT_POLICIES: RoutingPolicy[] = [ { id: 'cost-control', name: 'Cost Control Policy', description: 'Limit expensive model usage for non-critical tasks', priority: 100, enabled: true, rules: [ { condition: { complexity: 'low', costThreshold: 0.01, }, action: { type: 'route-to', targetLayer: 'L0', maxCost: 0.005, }, risk: 'low', }, { condition: { costThreshold: 1.0, }, action: { type: 'escalate', requireApproval: true, alertUsers: ['admin'], }, risk: 'high', }, ], }, { id: 'business-hours', name: 'Business Hours Policy', description: 'Route to cheaper models outside business hours', priority: 80, enabled: true, rules: [ { condition: { timeOfDay: { start: 18, end: 8 }, // 6 PM - 8 AM complexity: 'low', }, action: { type: 'downgrade', targetLayer: 'L0', }, risk: 'low', }, ], }, { id: 'security-sensitive', name: 'Security Sensitive Files', description: 'Require high-quality models for security-critical code', priority: 200, enabled: true, rules: [ { condition: { filePattern: '.*(auth|security|crypto|password).*', }, action: { type: 'route-to', targetLayer: 'L2', }, risk: 'critical', }, ], }, { id: 'test-files', name: 'Test File Policy', description: 'Use cheaper models for test file generation', priority: 50, enabled: true, rules: [ { condition: { filePattern: '.*\\.test\\.(ts|js)$', taskType: 'code', }, action: { type: 'route-to', targetLayer: 'L1', maxCost: 0.05, }, risk: 'low', }, ], }, ]; /** * Policy Matcher - Evaluate routing policies */ export class PolicyMatcher { private policies: RoutingPolicy[]; constructor(policies: RoutingPolicy[] = DEFAULT_POLICIES) { this.policies = policies.filter((p) => p.enabled).sort((a, b) => b.priority - a.priority); } /** * Match policies against request context */ match(context: { taskType?: TaskType; complexity?: 'low' | 'medium' | 'high'; filePath?: string; estimatedCost?: number; userRole?: string; }): { matchedPolicies: RoutingPolicy[]; action: RuleAction | null; risk: RiskLevel; } { const matchedPolicies: RoutingPolicy[] = []; let finalAction: RuleAction | null = null; let finalRisk: RiskLevel = 'low'; for (const policy of this.policies) { for (const rule of policy.rules) { if (this.matchesCondition(rule.condition, context)) { matchedPolicies.push(policy); finalAction = rule.action; finalRisk = this.maxRisk(finalRisk, rule.risk); logger.info('Policy matched', { policyId: policy.id, policyName: policy.name, action: rule.action.type, risk: rule.risk, }); // Stop at first match (highest priority) break; } } if (finalAction) break; } return { matchedPolicies, action: finalAction, risk: finalRisk, }; } /** * Check if rule condition matches context */ private matchesCondition( condition: RuleCondition, context: { taskType?: TaskType; complexity?: 'low' | 'medium' | 'high'; filePath?: string; estimatedCost?: number; userRole?: string; }, ): boolean { // Task type match if (condition.taskType) { const taskTypes = Array.isArray(condition.taskType) ? condition.taskType : [condition.taskType]; if (context.taskType && !taskTypes.includes(context.taskType)) { return false; } } // Complexity match if (condition.complexity && context.complexity !== condition.complexity) { return false; } // File pattern match if (condition.filePattern && context.filePath) { const regex = new RegExp(condition.filePattern); if (!regex.test(context.filePath)) { return false; } } // Cost threshold match if (condition.costThreshold !== undefined && context.estimatedCost !== undefined) { if (context.estimatedCost < condition.costThreshold) { return false; } } // Time of day match if (condition.timeOfDay) { const hour = new Date().getHours(); const { start, end } = condition.timeOfDay; // Handle overnight range (e.g., 18:00 - 08:00) const inRange = start > end ? hour >= start || hour < end : hour >= start && hour < end; if (!inRange) { return false; } } // User role match if (condition.userRole && context.userRole) { if (!condition.userRole.includes(context.userRole)) { return false; } } return true; } /** * Get maximum risk level */ private maxRisk(a: RiskLevel, b: RiskLevel): RiskLevel { const levels: Record<RiskLevel, number> = { low: 1, medium: 2, high: 3, critical: 4, }; return levels[a] > levels[b] ? a : b; } /** * Add custom policy */ addPolicy(policy: RoutingPolicy): void { this.policies.push(policy); this.policies.sort((a, b) => b.priority - a.priority); } /** * Remove policy by ID */ removePolicy(policyId: string): void { this.policies = this.policies.filter((p) => p.id !== policyId); } /** * Update policy */ updatePolicy(policyId: string, updates: Partial<RoutingPolicy>): void { const index = this.policies.findIndex((p) => p.id === policyId); if (index !== -1) { this.policies[index] = { ...this.policies[index], ...updates }; this.policies.sort((a, b) => b.priority - a.priority); } } } /** * Route Simulator - Test routing decisions without execution */ export class RouteSimulator { constructor(private policyMatcher: PolicyMatcher) { } /** * Simulate routing decision */ simulate(context: { taskType?: TaskType; complexity?: 'low' | 'medium' | 'high'; filePath?: string; estimatedCost?: number; userRole?: string; }): { selectedLayer: ModelLayer; matchedPolicies: string[]; risk: RiskLevel; estimatedCost: number; reasoning: string; } { const result = this.policyMatcher.match(context); let selectedLayer: ModelLayer = 'L0'; // Default let reasoning = 'Default routing to L0'; if (result.action) { if (result.action.type === 'route-to' && result.action.targetLayer) { selectedLayer = result.action.targetLayer; reasoning = `Policy-based routing to ${selectedLayer}`; } else if (result.action.type === 'escalate') { selectedLayer = 'L2'; reasoning = 'Escalated to L2 due to policy'; } else if (result.action.type === 'downgrade') { selectedLayer = result.action.targetLayer || 'L0'; reasoning = `Downgraded to ${selectedLayer} due to policy`; } } const estimatedCost = this.estimateCost(selectedLayer); return { selectedLayer, matchedPolicies: result.matchedPolicies.map((p) => p.name), risk: result.risk, estimatedCost, reasoning, }; } /** * Estimate cost for layer (simplified) */ private estimateCost(layer: ModelLayer): number { const costMap: Record<ModelLayer, number> = { L0: 0.0, L1: 0.01, L2: 0.05, L3: 0.20, }; return costMap[layer] || 0.0; } /** * Batch simulation for multiple scenarios */ batchSimulate( scenarios: Array<{ name: string; context: Parameters<RouteSimulator['simulate']>[0]; }>, ): Array<{ scenario: string; result: ReturnType<RouteSimulator['simulate']>; }> { return scenarios.map((scenario) => ({ scenario: scenario.name, result: this.simulate(scenario.context), })); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babasida246/ai-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server