/** * AgentRouter resolves which agent config to use for a given channel+sender. * * Resolution order (first match wins): * 1. Exact sender match (channel:senderId) * 2. Glob pattern sender match * 3. Channel match * 4. default_agent fallback */ import type { RoutingConfig } from '../config/schema.js'; export type { RoutingConfig }; /** * Convert a simple glob pattern to a RegExp. * Supports `*` (match any sequence of characters). * All other regex-special characters are escaped. */ function patternToRegex(pattern: string): RegExp { const escaped = pattern .replace(/[.+^${}()|[\]\\]/g, '\\$&') .replace(/\*/g, '.*'); return new RegExp(`^${escaped}$`); } export class AgentRouter { private config: RoutingConfig; constructor(config: RoutingConfig) { this.config = config; } /** * Resolve the agent config name for a channel + sender pair. * Returns undefined if no match and no default is configured. */ resolve(channel: string, senderId: string): string | undefined { const senderKey = `${channel}:${senderId}`; // 1. Exact sender match if (this.config.senders[senderKey]) { return this.config.senders[senderKey]; } // 2. Glob pattern sender match for (const [pattern, agentName] of Object.entries(this.config.senders)) { if (pattern.includes('*') && patternToRegex(pattern).test(senderKey)) { return agentName; } } // 3. Channel match if (this.config.channels[channel]) { return this.config.channels[channel]; } // 4. Default fallback return this.config.default_agent; } }