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
JavaScript
/**
* 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