UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

245 lines 8.82 kB
/** * Model Resolver * Resolves agent names and roles to concrete model identifiers using tier system * * @implements @.aiwg/architecture/ADR-015-enhanced-model-selection.md * @architecture @.aiwg/architecture/enhanced-model-selection-design.md */ import { ConfigLoader } from './config-loader.js'; /** * Resolves models for agents using tier system */ export class ModelResolver { config = null; loader; options; constructor(options = {}, projectPath) { this.options = { respectMinimums: true, ...options, }; this.loader = new ConfigLoader(projectPath); } /** * Resolve model for an agent * * @param agentName - Name of the agent * @param agentRole - Role hint from agent frontmatter (legacy 'model' field) * @param agentTier - Tier from agent frontmatter (new 'model-tier' field) * @returns Resolved model information */ async resolve(agentName, agentRole, agentTier) { const config = await this.getConfig(); // Determine effective tier and role const { tier, role, source } = this.determineEffectiveTierAndRole(agentName, agentRole, agentTier, config); // Check for direct model override (bypasses tier system) const override = config.agentOverrides[agentName]; if (override?.['model-override']) { return { modelId: override['model-override'], provider: this.options.provider || config.defaults.provider, tier, role, source: 'agent', }; } // Resolve tier + role to concrete model const modelId = this.resolveModelId(tier, role, config); return { modelId, provider: this.options.provider || config.defaults.provider, tier, role, source, }; } /** * Determine effective tier and role for an agent */ determineEffectiveTierAndRole(agentName, agentRole, agentTier, config) { // 1. CLI tier takes precedence if (this.options.tier) { const role = this.determineRole(agentName, agentRole, config); return { tier: this.applyMinimumTier(agentName, this.options.tier, config), role, source: 'cli', }; } // 2. Agent frontmatter tier const agentOverride = config.agentOverrides[agentName]; if (agentTier || agentOverride?.['model-tier']) { const tier = agentTier || agentOverride['model-tier']; const role = this.determineRole(agentName, agentRole, config); return { tier: this.applyMinimumTier(agentName, tier, config), role, source: 'agent', }; } // 3. Default tier from config const defaultTier = config.defaults.tier; const role = this.determineRole(agentName, agentRole, config); return { tier: this.applyMinimumTier(agentName, defaultTier, config), role, source: 'default', }; } /** * Determine role for agent */ determineRole(agentName, agentRole, config) { // If explicit role provided, use it if (agentRole) { return agentRole; } // Check agent role assignments if (config?.agentRoleAssignments) { for (const category of Object.values(config.agentRoleAssignments)) { if (category.agents.includes(agentName)) { return category.defaultRole; } } } // Default to 'coding' as reasonable middle ground return 'coding'; } /** * Apply minimum tier constraint if respectMinimums is true */ applyMinimumTier(agentName, requestedTier, config) { if (!this.options.respectMinimums) { return requestedTier; } // Find agent's minimum tier for (const category of Object.values(config.agentRoleAssignments || {})) { if (category.agents.includes(agentName)) { const minimumTier = category.minimumTier; return this.maxTier(requestedTier, minimumTier); } } return requestedTier; } /** * Return the higher of two tiers */ maxTier(tier1, tier2) { const tierOrder = ['economy', 'standard', 'premium', 'max-quality']; const index1 = tierOrder.indexOf(tier1); const index2 = tierOrder.indexOf(tier2); return tierOrder[Math.max(index1, index2)]; } /** * Resolve tier + role to concrete model ID */ resolveModelId(tier, role, config) { const provider = this.options.provider || config.defaults.provider; let providerDef = config.providers[provider]; if (!providerDef) { throw new Error(`Provider ${provider} not found in configuration`); } // Resolve inheritance providerDef = this.loader.resolveProviderInheritance(providerDef, config.providers); // Check for tier-specific override first if (providerDef.tierOverrides?.[tier]?.[role]) { return providerDef.tierOverrides[tier][role]; } // Apply tier's role mapping const tierDef = config.tiers[tier]; if (!tierDef) { throw new Error(`Tier ${tier} not found in configuration`); } const mappedRole = tierDef.roleMapping[role]; // Get model for mapped role const modelDef = providerDef.models?.[mappedRole]; if (!modelDef) { throw new Error(`Model not found for provider ${provider}, role ${mappedRole}`); } return modelDef.default; } /** * Get model info by ID or alias */ async getModelInfo(modelIdOrAlias) { const config = await this.getConfig(); const provider = this.options.provider || config.defaults.provider; let providerDef = config.providers[provider]; if (!providerDef) { return null; } // Resolve inheritance providerDef = this.loader.resolveProviderInheritance(providerDef, config.providers); // Check shorthand aliases const resolvedId = config.shorthand[modelIdOrAlias] || modelIdOrAlias; // Search through all roles for (const [roleName, modelDef] of Object.entries(providerDef.models || {})) { if (modelDef.default === resolvedId || modelDef.aliases?.includes(modelIdOrAlias)) { return { id: modelDef.default, provider, role: roleName, aliases: modelDef.aliases || [], capabilities: modelDef.capabilities || [], contextWindow: modelDef.contextWindow || 0, costPer1kTokens: modelDef.costPer1kTokens, }; } } return null; } /** * List all available models for provider */ async listModels(provider) { const config = await this.getConfig(); const targetProvider = provider || this.options.provider || config.defaults.provider; let providerDef = config.providers[targetProvider]; if (!providerDef) { return []; } // Resolve inheritance providerDef = this.loader.resolveProviderInheritance(providerDef, config.providers); const models = []; for (const [roleName, modelDef] of Object.entries(providerDef.models || {})) { models.push({ id: modelDef.default, provider: targetProvider, role: roleName, aliases: modelDef.aliases || [], capabilities: modelDef.capabilities || [], contextWindow: modelDef.contextWindow || 0, costPer1kTokens: modelDef.costPer1kTokens, }); } return models; } /** * List available tiers */ async listTiers() { const config = await this.getConfig(); return Object.entries(config.tiers).map(([tierName, tierDef]) => ({ tier: tierName, description: tierDef.description, costMultiplier: tierDef.costMultiplier, })); } /** * Get configuration (cached) */ async getConfig() { if (!this.config) { this.config = await this.loader.load(); } return this.config; } /** * Clear cache (useful for testing) */ clearCache() { this.config = null; this.loader.clearCache(); } } //# sourceMappingURL=resolver.js.map