Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 45 additions & 13 deletions src/commands/model-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
loadModelMap,
validateModelMap,
initModelMapFile,
addModelToMap,
getExampleModelMap,
getGlobalConfigDir,
type ModelMapConfig,
Expand Down Expand Up @@ -266,39 +267,70 @@ export const modelMapCommand: Command = {
name: 'modelmap',
aliases: ['mm', 'models-map'],
description: 'Show and manage model map configuration (codi-models.yaml)',
usage: '/modelmap [init [--global]|show|example]',
usage: '/modelmap [add|init|show|example] [--global]',
taskType: 'fast',
execute: async (args: string, context: CommandContext): Promise<string | null> => {
const trimmed = args.trim().toLowerCase();
const trimmed = args.trim();
const trimmedLower = trimmed.toLowerCase();

// Handle help flag locally without API call
if (trimmed === '-h' || trimmed === '--help') {
console.log('\nUsage: /modelmap [init [--global]|show|example]');
if (trimmedLower === '-h' || trimmedLower === '--help') {
console.log('\nUsage: /modelmap [action] [options]');
console.log('\nShow and manage model map configuration.');
console.log('\nConfig locations:');
console.log(` Global: ${getGlobalConfigDir()}/models.yaml`);
console.log(' Project: ./codi-models.yaml');
console.log('\nActions:');
console.log(' show Show current model map configuration (default)');
console.log(' init Create a new codi-models.yaml in current directory');
console.log(' init --global Create a new models.yaml in ~/.codi/');
console.log(' example Show example configuration');
console.log(' show Show current model map configuration (default)');
console.log(' add <name> <provider> <model> [description] Add a model alias');
console.log(' init Create a new codi-models.yaml in current directory');
console.log(' example Show example configuration');
console.log('\nFlags:');
console.log(' --global Apply to global config (~/.codi/models.yaml)');
console.log('\nExamples:');
console.log(' /modelmap Show current configuration');
console.log(' /modelmap init Create project codi-models.yaml');
console.log(' /modelmap init --global Create global ~/.codi/models.yaml');
console.log(' /modelmap example Show example YAML');
console.log(' /modelmap Show current configuration');
console.log(' /modelmap add coder ollama-cloud qwen3-coder:480b-cloud');
console.log(' /modelmap add --global fast anthropic claude-3-5-haiku-latest');
console.log(' /modelmap init Create project codi-models.yaml');
console.log(' /modelmap init --global Create global ~/.codi/models.yaml');
console.log('\nGlobal models can be used in any project with /switch <name>');
console.log();
return null;
}

// Parse action and flags
const parts = trimmed.split(/\s+/).filter(p => p);
const action = parts[0] || 'show';
const isGlobal = parts.includes('--global');
// Remove --global from parts for easier parsing
const filteredParts = parts.filter(p => p.toLowerCase() !== '--global');
const action = (filteredParts[0] || 'show').toLowerCase();

switch (action) {
case 'add': {
// Parse: add <name> <provider> <model> [description...]
const name = filteredParts[1];
const provider = filteredParts[2];
const model = filteredParts[3];
const description = filteredParts.slice(4).join(' ') || undefined;

if (!name || !provider || !model) {
return '__MODELMAP_ERROR__|Usage: /modelmap add <name> <provider> <model> [description]';
}

// Validate provider
const validProviders = ['anthropic', 'openai', 'ollama', 'ollama-cloud', 'runpod'];
if (!validProviders.includes(provider.toLowerCase())) {
return `__MODELMAP_ERROR__|Invalid provider "${provider}". Valid: ${validProviders.join(', ')}`;
}

const result = addModelToMap(name, provider.toLowerCase(), model, description, isGlobal);
if (result.success) {
const scope = isGlobal ? 'global' : 'project';
return `__MODELMAP_ADD__|${name}|${provider}|${model}|${result.path}|${scope}`;
}
return `__MODELMAP_ERROR__|${result.error}`;
}

case 'init': {
const result = initModelMapFile(process.cwd(), isGlobal);
if (result.success) {
Expand Down
11 changes: 11 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,17 @@ function handleModelMapOutput(output: string): void {
return;
}

if (firstLine.startsWith('__MODELMAP_ADD__|')) {
const parts = firstLine.slice('__MODELMAP_ADD__|'.length).split('|');
const [name, provider, model, filePath, scope] = parts;
console.log(chalk.green(`\nAdded model "${name}" to ${scope} config`));
console.log(chalk.dim(` Provider: ${provider}`));
console.log(chalk.dim(` Model: ${model}`));
console.log(chalk.dim(` File: ${filePath}`));
console.log(chalk.cyan(`\nUse with: /switch ${name}`));
return;
}

if (firstLine.startsWith('__MODELMAP_INIT__|')) {
const parts = firstLine.slice('__MODELMAP_INIT__|'.length).split('|');
const filePath = parts[0];
Expand Down
1 change: 1 addition & 0 deletions src/model-map/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
watchModelMap,
getExampleModelMap,
initModelMap as initModelMapFile,
addModelToMap,
getGlobalConfigDir,
ModelMapValidationError,
type ValidationResult,
Expand Down
88 changes: 88 additions & 0 deletions src/model-map/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,3 +723,91 @@ export function initModelMap(
export function getGlobalConfigDir(): string {
return GLOBAL_CONFIG_DIR;
}

/**
* Add a model to an existing model map file.
* Creates the file if it doesn't exist.
*
* @param name Model alias name
* @param provider Provider type (anthropic, openai, ollama, etc.)
* @param model Model ID
* @param description Optional description
* @param global If true, adds to global config (~/.codi/models.yaml)
* @param cwd Working directory for project config
*/
export function addModelToMap(
name: string,
provider: string,
model: string,
description?: string,
global: boolean = false,
cwd: string = process.cwd()
): { success: boolean; path: string; error?: string } {
const targetDir = global ? GLOBAL_CONFIG_DIR : cwd;
const fileName = global ? GLOBAL_MODEL_MAP_FILE : MODEL_MAP_FILE;
const configPath = path.join(targetDir, fileName);

// Ensure directory exists for global config
if (global && !fs.existsSync(targetDir)) {
try {
fs.mkdirSync(targetDir, { recursive: true });
} catch (error) {
return {
success: false,
path: configPath,
error: `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`,
};
}
}

// Load existing config or create new one
let config: ModelMapConfig;
if (fs.existsSync(configPath)) {
try {
const content = fs.readFileSync(configPath, 'utf-8');
config = yaml.load(content) as ModelMapConfig;
if (!config.models) config.models = {};
} catch (error) {
return {
success: false,
path: configPath,
error: `Failed to parse existing config: ${error instanceof Error ? error.message : String(error)}`,
};
}
} else {
// Create minimal config
config = {
version: '1',
models: {},
};
}

// Check if model already exists
if (config.models[name]) {
return {
success: false,
path: configPath,
error: `Model "${name}" already exists. Use a different name or edit the file directly.`,
};
}

// Add the model
config.models[name] = {
provider,
model,
...(description && { description }),
};

// Write back
try {
const yamlContent = yaml.dump(config, { lineWidth: 100, noRefs: true });
fs.writeFileSync(configPath, yamlContent);
return { success: true, path: configPath };
} catch (error) {
return {
success: false,
path: configPath,
error: `Failed to write config: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
Loading