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
297 lines • 10.4 kB
JavaScript
/**
* Provider Capability Matrix Loader
*
* Loads and queries the authoritative provider capability matrix from
* agentic/code/providers/capability-matrix.yaml. Provides typed access
* for the scheduler, agent teams, steward, and runtime-info CLI.
*
* @issue #604
* @unblocks #597, #598, #599
*/
import { readFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { load as loadYaml } from 'js-yaml';
import { findPackageRoot } from '../cli/find-package-root.js';
// ---------------------------------------------------------------------------
// Loader
// ---------------------------------------------------------------------------
let _cached = null;
/**
* Resolve the path to capability-matrix.yaml relative to the package root.
* Works for both compiled (dist/src/providers/) and source (src/providers/)
* layouts by walking up to find the aiwg package.json.
*
* @issue #1261
*/
function resolveMatrixPath() {
if (process.env.AIWG_ROOT) {
return resolve(process.env.AIWG_ROOT, 'agentic', 'code', 'providers', 'capability-matrix.yaml');
}
const thisDir = typeof __dirname !== 'undefined'
? __dirname
: dirname(fileURLToPath(import.meta.url));
const packageRoot = findPackageRoot(thisDir);
if (!packageRoot) {
throw new Error(`Cannot locate aiwg package root from ${thisDir}. ` +
`Set AIWG_ROOT environment variable as a workaround.`);
}
return resolve(packageRoot, 'agentic', 'code', 'providers', 'capability-matrix.yaml');
}
/**
* Load the capability matrix from disk (cached after first load).
*/
export function loadCapabilityMatrix() {
if (_cached)
return _cached;
const matrixPath = resolveMatrixPath();
const raw = readFileSync(matrixPath, 'utf-8');
_cached = loadYaml(raw);
return _cached;
}
/**
* Force-reload the matrix (e.g. after edits during validate-metadata).
*/
export function reloadCapabilityMatrix() {
_cached = null;
return loadCapabilityMatrix();
}
// ---------------------------------------------------------------------------
// Query helpers
// ---------------------------------------------------------------------------
/**
* Get the full capabilities object for a provider by key or alias.
*/
export function getProviderCapabilities(providerKey) {
const matrix = loadCapabilityMatrix();
// Direct match
if (matrix.providers[providerKey]) {
return matrix.providers[providerKey];
}
// Search aliases
for (const [key, caps] of Object.entries(matrix.providers)) {
if (caps.aliases?.includes(providerKey)) {
return matrix.providers[key];
}
}
return undefined;
}
export function getAgentCapabilities(providerKey, agentType = 'default') {
const caps = getProviderCapabilities(providerKey);
if (!caps?.agent_capabilities)
return undefined;
const defaults = caps.agent_capabilities.default || {};
const override = caps.agent_capabilities[agentType] || {};
return {
...defaults,
...override,
recovery: {
...(defaults.recovery || {}),
...(override.recovery || {}),
},
};
}
/**
* Pre-flight a provider subagent dispatch before spawning work. The helper is
* intentionally deterministic: callers pass token/tool estimates and receive a
* structured routing signal instead of pattern-matching provider error strings.
*/
export function preflightSubagentDispatch(input) {
const agentType = input.agentType || 'default';
const provider = input.provider;
const caps = getProviderCapabilities(provider);
const agentCaps = getAgentCapabilities(provider, agentType);
const estimatedTokens = (input.promptTokens || 0) +
(input.contextTokens || 0) +
(input.outputTokens || 0);
const contextWindow = agentCaps?.context_window ?? null;
const maxOutputTokens = agentCaps?.max_output_tokens ?? null;
const maxToolCalls = agentCaps?.max_tool_calls ?? null;
const base = {
provider,
agentType,
estimatedTokens,
contextWindow,
maxOutputTokens,
maxToolCalls,
};
if (!caps) {
return {
...base,
status: 'unknown_provider',
recoveryHint: 'escalate_to_human',
message: `Unknown provider: ${provider}`,
};
}
if (input.requiresMillionContext === true &&
agentCaps?.million_context_requires_extra_usage === true &&
agentCaps?.quota_available !== true) {
return {
...base,
status: 'quota_unavailable',
recoveryHint: agentCaps?.recovery?.quota_exhausted || 'retry_with_standard_context',
message: `${provider}/${agentType} requires 1M-context extra usage, but quota is not marked available.`,
};
}
if (contextWindow !== null && estimatedTokens > contextWindow) {
return {
...base,
status: 'would_overflow',
recoveryHint: agentCaps?.recovery?.context_overflow || 'retry_with_prefiltered_context',
message: `Estimated dispatch size ${estimatedTokens} tokens exceeds ${provider}/${agentType} context window ${contextWindow}.`,
};
}
if (maxOutputTokens !== null && (input.outputTokens || 0) > maxOutputTokens) {
return {
...base,
status: 'would_overflow',
recoveryHint: 'reduce_expected_output',
message: `Requested output budget ${input.outputTokens || 0} exceeds ${provider}/${agentType} max output ${maxOutputTokens}.`,
};
}
if (maxToolCalls !== null && (input.toolCalls || 0) > maxToolCalls) {
return {
...base,
status: 'tool_budget_exceeded',
recoveryHint: 'split_task_or_prefilter_context',
message: `Estimated tool calls ${input.toolCalls || 0} exceeds ${provider}/${agentType} max tool calls ${maxToolCalls}.`,
};
}
return {
...base,
status: 'ok',
recoveryHint: null,
message: `${provider}/${agentType} dispatch fits declared provider budget.`,
};
}
/**
* Check whether a provider supports a feature natively (without emulation).
*/
export function supportsNative(caps, feature) {
return caps.native_features[feature] === true;
}
/**
* Get the emulation strategy for a feature on a provider.
* Returns 'native' if the provider supports it natively, or the
* emulation strategy string, or null if unsupported entirely.
*/
export function getEmulationStrategy(caps, feature) {
return caps.emulation[feature] ?? null;
}
/**
* List all provider keys.
*/
export function listProviders() {
return Object.keys(loadCapabilityMatrix().providers);
}
/**
* List all providers that natively support a given feature.
*/
export function providersWithNativeSupport(feature) {
const matrix = loadCapabilityMatrix();
return Object.entries(matrix.providers)
.filter(([, caps]) => caps.native_features[feature])
.map(([key]) => key);
}
/**
* Get the feature definition (description, examples, strategies).
*/
export function getFeatureDefinition(feature) {
return loadCapabilityMatrix().features[feature];
}
/**
* Get the daemon tier for a provider.
* Returns 'unsupported' if the provider is not found or has no daemon_tier.
*
* @issue #656
*/
export function getDaemonTier(providerKey) {
const caps = getProviderCapabilities(providerKey);
return caps?.daemon_tier ?? 'unsupported';
}
/**
* List all providers that support daemon mode (Tier 1: native).
*
* @issue #656
*/
export function daemonCapableProviders() {
const matrix = loadCapabilityMatrix();
return Object.entries(matrix.providers)
.filter(([, caps]) => caps.daemon_tier === 'native')
.map(([key]) => key);
}
// ---------------------------------------------------------------------------
// Display helpers (for CLI output)
// ---------------------------------------------------------------------------
/**
* Format the full capability matrix as a human-readable table string.
*/
export function formatCapabilityTable() {
const matrix = loadCapabilityMatrix();
const features = [
'cron',
'agent_teams',
'tasks',
'mcp',
'behaviors',
'mission_control',
'daemon',
];
const lines = [];
lines.push('Provider Capability Matrix (v' + matrix.version + ')');
lines.push('='.repeat(90));
lines.push('');
// Header
const hdr = [
padRight('Provider', 18),
...features.map((f) => padRight(f, 15)),
].join('| ');
lines.push(hdr);
lines.push('-'.repeat(hdr.length));
// Rows
for (const [, caps] of Object.entries(matrix.providers)) {
const cells = features.map((f) => {
if (caps.native_features[f])
return padRight('NATIVE', 15);
const emu = caps.emulation[f];
if (emu)
return padRight(emu, 15);
return padRight('--', 15);
});
lines.push([padRight(caps.display_name, 18), ...cells].join('| '));
}
lines.push('');
lines.push('Legend: NATIVE = built-in support, aiwg-* = AIWG emulation, -- = unsupported');
return lines.join('\n');
}
/**
* Format a single feature's support across all providers.
*/
export function formatFeatureSupport(feature) {
const matrix = loadCapabilityMatrix();
const def = matrix.features[feature];
const lines = [];
lines.push(`Feature: ${feature}`);
if (def) {
lines.push(`Description: ${def.description}`);
if (def.native_example) {
lines.push(`Native example: ${def.native_example}`);
}
}
lines.push('');
for (const [, caps] of Object.entries(matrix.providers)) {
const native = caps.native_features[feature];
const strategy = caps.emulation[feature];
const status = native
? 'NATIVE'
: strategy
? `emulated (${strategy})`
: 'unsupported';
lines.push(` ${padRight(caps.display_name, 18)} ${status}`);
}
return lines.join('\n');
}
function padRight(str, len) {
return str.length >= len ? str : str + ' '.repeat(len - str.length);
}
//# sourceMappingURL=capability-matrix.js.map