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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ local/*

MCP directories
/.serena/
/.agents/skills/bmad*
89 changes: 89 additions & 0 deletions src/cli/commands/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { ConfigLoader } from '../../utils/config.js';
import { ProviderRegistry } from '../../providers/core/registry.js';
import { logger } from '../../utils/logger.js';
import type { ModelInfo } from '../../providers/core/types.js';

const UNSUPPORTED_PROVIDERS = new Set(['openai', 'bearer-auth', 'openai-compatible']);

function formatTable(models: ModelInfo[]): void {
const ID_WIDTH = 40;
const NAME_WIDTH = 35;
const DESC_WIDTH = 60;

const header =
chalk.bold(padEnd('ID', ID_WIDTH)) +
chalk.bold(padEnd('NAME', NAME_WIDTH)) +
chalk.bold('DESCRIPTION');

console.log(header);
console.log(chalk.dim('─'.repeat(ID_WIDTH + NAME_WIDTH + DESC_WIDTH)));

for (const model of models) {
const id = padEnd(model.id, ID_WIDTH);
const name = padEnd(model.name || model.id, NAME_WIDTH);
const desc = truncate(model.description ?? '', DESC_WIDTH);
console.log(chalk.cyan(id) + chalk.white(name) + chalk.dim(desc));
}
}

function padEnd(str: string, width: number): string {
return str.length >= width ? str.slice(0, width - 1) + ' ' : str.padEnd(width);
}

function truncate(str: string, maxLen: number): string {
return str.length > maxLen ? str.slice(0, maxLen - 3) + '...' : str;
}

export function createModelsCommand(): Command {
const command = new Command('models');
command.description('Manage and list AI models');

const listCommand = new Command('list');
listCommand
.description('List all models available for the current provider configuration')
.action(async () => {
try {
const config = await ConfigLoader.load(process.cwd());
const provider = config.provider;

if (!provider) {
console.error(chalk.red('No provider configured. Run ' + chalk.cyan('codemie setup') + ' to get started.'));
process.exit(1);
}

if (UNSUPPORTED_PROVIDERS.has(provider)) {
console.error(chalk.red(`Model listing is not supported for provider '${provider}'.`));
process.exit(1);
}

const proxy = ProviderRegistry.getModelProxy(provider);

if (!proxy) {
console.error(chalk.red(`Model listing is not supported for provider '${provider}'.`));
process.exit(1);
}

logger.debug(`Fetching models for provider: ${provider}`);

const models = await proxy.fetchModels(config);

if (models.length === 0) {
console.log(chalk.yellow(`No models found for provider '${provider}'.`));
return;
}

console.log(chalk.bold(`\nProvider: ${chalk.cyan(provider)}\n`));
formatTable(models);
console.log(chalk.dim(`\n${models.length} model(s) available.\n`));
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.error(chalk.red(`Failed to fetch models: ${message}`));
process.exit(1);
}
});

command.addCommand(listCommand);
return command;
}
2 changes: 2 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { createSkillCommand } from './commands/skill.js';
import { createPluginCommand } from './commands/plugin.js';
import { createOpencodeMetricsCommand } from './commands/opencode-metrics.js';
import { createTestMetricsCommand } from './commands/test-metrics.js';
import { createModelsCommand } from './commands/models.js';
import { createAssistantsCommand } from './commands/assistants/index.js';
import { FirstTimeExperience } from './first-time.js';
import { getDirname } from '../utils/paths.js';
Expand Down Expand Up @@ -72,6 +73,7 @@ program.addCommand(createSkillCommand());
program.addCommand(createPluginCommand());
program.addCommand(createOpencodeMetricsCommand());
program.addCommand(createTestMetricsCommand());
program.addCommand(createModelsCommand());

// Check for --task option before parsing commands
const taskIndex = process.argv.indexOf('--task');
Expand Down
22 changes: 13 additions & 9 deletions src/providers/plugins/bedrock/bedrock.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,32 @@ export class BedrockModelProxy implements ProviderModelFetcher {
/**
* Fetch available models from Bedrock
*/
async fetchModels(_config: CodeMieConfigOptions): Promise<ModelInfo[]> {
async fetchModels(config: CodeMieConfigOptions): Promise<ModelInfo[]> {
try {
const {
BedrockClient,
ListInferenceProfilesCommand
} = await import('@aws-sdk/client-bedrock');

const clientConfig: any = {
region: this.region
};
// Prefer runtime config values over constructor defaults
const region = config.awsRegion || this.region;
const awsProfile = config.awsProfile || this.profile;
const accessKeyId = config.apiKey || this.accessKeyId;
const secretAccessKey = config.awsSecretAccessKey || this.secretAccessKey;

if (this.profile) {
const clientConfig: any = { region };

if (awsProfile) {
// Use AWS profile - fromIni returns a credential provider function
// that the SDK will call when needed
clientConfig.credentials = fromIni({
profile: this.profile
profile: awsProfile
});
} else if (this.accessKeyId && this.secretAccessKey) {
} else if (accessKeyId && secretAccessKey) {
// Use direct credentials
clientConfig.credentials = {
accessKeyId: this.accessKeyId,
secretAccessKey: this.secretAccessKey
accessKeyId,
secretAccessKey
};
} else {
// Try to use default credentials chain (environment variables, default profile, etc.)
Expand Down
4 changes: 4 additions & 0 deletions src/providers/plugins/litellm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@

import { ProviderRegistry } from '../../core/registry.js';
import { LiteLLMSetupSteps } from './litellm.setup-steps.js';
import { LiteLLMModelProxy } from './litellm.models.js';

export { LiteLLMTemplate } from './litellm.template.js';
export { LiteLLMSetupSteps } from './litellm.setup-steps.js';

// Register setup steps
ProviderRegistry.registerSetupSteps('litellm', LiteLLMSetupSteps);

// Register model proxy (fetchModels uses runtime config, so empty defaults are fine)
ProviderRegistry.registerModelProxy('litellm', new LiteLLMModelProxy(''));
10 changes: 7 additions & 3 deletions src/providers/plugins/litellm/litellm.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ export class LiteLLMModelProxy extends BaseModelProxy {
}

/**
* Fetch models for setup wizard
* Fetch models using runtime config values (baseUrl and apiKey).
* Falls back to constructor values when config fields are absent.
*/
async fetchModels(_config: CodeMieConfigOptions): Promise<ModelInfo[]> {
return this.listModels();
async fetchModels(config: CodeMieConfigOptions): Promise<ModelInfo[]> {
const effectiveBaseUrl = config.baseUrl || this.baseUrl;
const effectiveApiKey = config.apiKey !== undefined ? config.apiKey : this.apiKey;
const proxy = new LiteLLMModelProxy(effectiveBaseUrl, effectiveApiKey);
return proxy.listModels();
}
}
Loading