mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-06-27 00:29:58 +00:00
363 lines
11 KiB
JavaScript
363 lines
11 KiB
JavaScript
![]() |
import fs from 'fs';
|
||
|
import path from 'path';
|
||
|
import chalk from 'chalk';
|
||
|
|
||
|
const CONFIG_FILE_NAME = '.taskmasterconfig';
|
||
|
|
||
|
// Default configuration
|
||
|
const DEFAULT_MAIN_PROVIDER = 'anthropic';
|
||
|
const DEFAULT_MAIN_MODEL_ID = 'claude-3.7-sonnet-20250219';
|
||
|
const DEFAULT_RESEARCH_PROVIDER = 'perplexity';
|
||
|
const DEFAULT_RESEARCH_MODEL_ID = 'sonar-pro';
|
||
|
|
||
|
// Define ONE list of all supported providers
|
||
|
const VALID_PROVIDERS = [
|
||
|
'anthropic',
|
||
|
'openai',
|
||
|
'google',
|
||
|
'perplexity',
|
||
|
'ollama',
|
||
|
'openrouter',
|
||
|
'grok'
|
||
|
];
|
||
|
|
||
|
// Optional: Define known models per provider primarily for informational display or non-blocking warnings
|
||
|
const MODEL_MAP = {
|
||
|
anthropic: ['claude-3.5-sonnet-20240620', 'claude-3-7-sonnet-20250219'],
|
||
|
openai: ['gpt-4o', 'gpt-4-turbo'],
|
||
|
google: ['gemini-2.5-pro-latest', 'gemini-1.5-flash-latest'],
|
||
|
perplexity: ['sonar-pro', 'sonar-mini'],
|
||
|
ollama: [], // Users configure specific Ollama models locally
|
||
|
openrouter: [], // Users specify model string
|
||
|
grok: [] // Specify Grok model if known
|
||
|
};
|
||
|
|
||
|
let projectRoot = null;
|
||
|
|
||
|
function findProjectRoot() {
|
||
|
// Keep this function as is for CLI context
|
||
|
if (projectRoot) return projectRoot;
|
||
|
|
||
|
let currentDir = process.cwd();
|
||
|
while (currentDir !== path.parse(currentDir).root) {
|
||
|
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
|
||
|
projectRoot = currentDir;
|
||
|
return projectRoot;
|
||
|
}
|
||
|
currentDir = path.dirname(currentDir);
|
||
|
}
|
||
|
|
||
|
// Check root directory as a last resort
|
||
|
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
|
||
|
projectRoot = currentDir;
|
||
|
return projectRoot;
|
||
|
}
|
||
|
|
||
|
// If still not found, maybe look for other markers or return null
|
||
|
// For now, returning null if package.json isn't found up to the root
|
||
|
projectRoot = null;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function readConfig(explicitRoot = null) {
|
||
|
// Determine the root path to use
|
||
|
const rootToUse = explicitRoot || findProjectRoot();
|
||
|
|
||
|
const defaults = {
|
||
|
models: {
|
||
|
main: { provider: DEFAULT_MAIN_PROVIDER, modelId: DEFAULT_MAIN_MODEL_ID },
|
||
|
research: {
|
||
|
provider: DEFAULT_RESEARCH_PROVIDER,
|
||
|
modelId: DEFAULT_RESEARCH_MODEL_ID
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (!rootToUse) {
|
||
|
console.warn(
|
||
|
chalk.yellow(
|
||
|
'Warning: Could not determine project root. Using default configuration.'
|
||
|
)
|
||
|
);
|
||
|
return defaults;
|
||
|
}
|
||
|
const configPath = path.join(rootToUse, CONFIG_FILE_NAME);
|
||
|
|
||
|
if (fs.existsSync(configPath)) {
|
||
|
try {
|
||
|
const rawData = fs.readFileSync(configPath, 'utf-8');
|
||
|
const parsedConfig = JSON.parse(rawData);
|
||
|
|
||
|
// Deep merge defaults to ensure structure and handle partial configs
|
||
|
const config = {
|
||
|
models: {
|
||
|
main: {
|
||
|
provider:
|
||
|
parsedConfig?.models?.main?.provider ??
|
||
|
defaults.models.main.provider,
|
||
|
modelId:
|
||
|
parsedConfig?.models?.main?.modelId ??
|
||
|
defaults.models.main.modelId
|
||
|
},
|
||
|
research: {
|
||
|
provider:
|
||
|
parsedConfig?.models?.research?.provider ??
|
||
|
defaults.models.research.provider,
|
||
|
modelId:
|
||
|
parsedConfig?.models?.research?.modelId ??
|
||
|
defaults.models.research.modelId
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Validate loaded provider (no longer split by main/research)
|
||
|
if (!validateProvider(config.models.main.provider)) {
|
||
|
console.warn(
|
||
|
chalk.yellow(
|
||
|
`Warning: Invalid main provider "${config.models.main.provider}" in ${CONFIG_FILE_NAME}. Falling back to default.`
|
||
|
)
|
||
|
);
|
||
|
config.models.main = {
|
||
|
provider: defaults.models.main.provider,
|
||
|
modelId: defaults.models.main.modelId
|
||
|
};
|
||
|
}
|
||
|
// Optional: Add warning for model combination if desired, but don't block
|
||
|
// else if (!validateProviderModelCombination(config.models.main.provider, config.models.main.modelId)) { ... }
|
||
|
|
||
|
if (!validateProvider(config.models.research.provider)) {
|
||
|
console.warn(
|
||
|
chalk.yellow(
|
||
|
`Warning: Invalid research provider "${config.models.research.provider}" in ${CONFIG_FILE_NAME}. Falling back to default.`
|
||
|
)
|
||
|
);
|
||
|
config.models.research = {
|
||
|
provider: defaults.models.research.provider,
|
||
|
modelId: defaults.models.research.modelId
|
||
|
};
|
||
|
}
|
||
|
// Optional: Add warning for model combination if desired, but don't block
|
||
|
// else if (!validateProviderModelCombination(config.models.research.provider, config.models.research.modelId)) { ... }
|
||
|
|
||
|
return config;
|
||
|
} catch (error) {
|
||
|
console.error(
|
||
|
chalk.red(
|
||
|
`Error reading or parsing ${configPath}: ${error.message}. Using default configuration.`
|
||
|
)
|
||
|
);
|
||
|
return defaults;
|
||
|
}
|
||
|
} else {
|
||
|
return defaults;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validates if a provider name is in the list of supported providers.
|
||
|
* @param {string} providerName The name of the provider.
|
||
|
* @returns {boolean} True if the provider is valid, false otherwise.
|
||
|
*/
|
||
|
function validateProvider(providerName) {
|
||
|
return VALID_PROVIDERS.includes(providerName);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Optional: Validates if a modelId is known for a given provider based on MODEL_MAP.
|
||
|
* This is a non-strict validation; an unknown model might still be valid.
|
||
|
* @param {string} providerName The name of the provider.
|
||
|
* @param {string} modelId The model ID.
|
||
|
* @returns {boolean} True if the modelId is in the map for the provider, false otherwise.
|
||
|
*/
|
||
|
function validateProviderModelCombination(providerName, modelId) {
|
||
|
// If provider isn't even in our map, we can't validate the model
|
||
|
if (!MODEL_MAP[providerName]) {
|
||
|
return true; // Allow unknown providers or those without specific model lists
|
||
|
}
|
||
|
// If the provider is known, check if the model is in its list OR if the list is empty (meaning accept any)
|
||
|
return (
|
||
|
MODEL_MAP[providerName].length === 0 ||
|
||
|
MODEL_MAP[providerName].includes(modelId)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the currently configured main AI provider.
|
||
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
||
|
* @returns {string} The name of the main provider.
|
||
|
*/
|
||
|
function getMainProvider(explicitRoot = null) {
|
||
|
const config = readConfig(explicitRoot);
|
||
|
return config.models.main.provider;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the currently configured main AI model ID.
|
||
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
||
|
* @returns {string} The ID of the main model.
|
||
|
*/
|
||
|
function getMainModelId(explicitRoot = null) {
|
||
|
const config = readConfig(explicitRoot);
|
||
|
return config.models.main.modelId;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the currently configured research AI provider.
|
||
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
||
|
* @returns {string} The name of the research provider.
|
||
|
*/
|
||
|
function getResearchProvider(explicitRoot = null) {
|
||
|
const config = readConfig(explicitRoot);
|
||
|
return config.models.research.provider;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the currently configured research AI model ID.
|
||
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
||
|
* @returns {string} The ID of the research model.
|
||
|
*/
|
||
|
function getResearchModelId(explicitRoot = null) {
|
||
|
const config = readConfig(explicitRoot);
|
||
|
return config.models.research.modelId;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the main AI model (provider and modelId) in the configuration file.
|
||
|
* @param {string} providerName The name of the provider to set.
|
||
|
* @param {string} modelId The ID of the model to set.
|
||
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
||
|
* @returns {boolean} True if successful, false otherwise.
|
||
|
*/
|
||
|
function setMainModel(providerName, modelId, explicitRoot = null) {
|
||
|
if (!validateProvider(providerName)) {
|
||
|
console.error(
|
||
|
chalk.red(`Error: "${providerName}" is not a valid provider.`)
|
||
|
);
|
||
|
console.log(
|
||
|
chalk.yellow(`Available providers: ${VALID_PROVIDERS.join(', ')}`)
|
||
|
);
|
||
|
return false;
|
||
|
}
|
||
|
if (!validateProviderModelCombination(providerName, modelId)) {
|
||
|
console.warn(
|
||
|
chalk.yellow(
|
||
|
`Warning: Model "${modelId}" is not in the known list for provider "${providerName}". Ensure it is valid.`
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Pass explicitRoot down
|
||
|
const config = readConfig(explicitRoot);
|
||
|
config.models.main = { provider: providerName, modelId: modelId };
|
||
|
// Pass explicitRoot down
|
||
|
if (writeConfig(config, explicitRoot)) {
|
||
|
console.log(
|
||
|
chalk.green(`Main AI model set to: ${providerName} / ${modelId}`)
|
||
|
);
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the research AI model (provider and modelId) in the configuration file.
|
||
|
* @param {string} providerName The name of the provider to set.
|
||
|
* @param {string} modelId The ID of the model to set.
|
||
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
||
|
* @returns {boolean} True if successful, false otherwise.
|
||
|
*/
|
||
|
function setResearchModel(providerName, modelId, explicitRoot = null) {
|
||
|
if (!validateProvider(providerName)) {
|
||
|
console.error(
|
||
|
chalk.red(`Error: "${providerName}" is not a valid provider.`)
|
||
|
);
|
||
|
console.log(
|
||
|
chalk.yellow(`Available providers: ${VALID_PROVIDERS.join(', ')}`)
|
||
|
);
|
||
|
return false;
|
||
|
}
|
||
|
if (!validateProviderModelCombination(providerName, modelId)) {
|
||
|
console.warn(
|
||
|
chalk.yellow(
|
||
|
`Warning: Model "${modelId}" is not in the known list for provider "${providerName}". Ensure it is valid.`
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
if (
|
||
|
providerName === 'anthropic' ||
|
||
|
(providerName === 'openai' && modelId.includes('3.5'))
|
||
|
) {
|
||
|
console.warn(
|
||
|
chalk.yellow(
|
||
|
`Warning: Provider "${providerName}" with model "${modelId}" may not be ideal for research tasks. Perplexity or Grok recommended.`
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Pass explicitRoot down
|
||
|
const config = readConfig(explicitRoot);
|
||
|
config.models.research = { provider: providerName, modelId: modelId };
|
||
|
// Pass explicitRoot down
|
||
|
if (writeConfig(config, explicitRoot)) {
|
||
|
console.log(
|
||
|
chalk.green(`Research AI model set to: ${providerName} / ${modelId}`)
|
||
|
);
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function writeConfig(config, explicitRoot = null) {
|
||
|
// Determine the root path to use
|
||
|
const rootToUse = explicitRoot || findProjectRoot();
|
||
|
|
||
|
if (!rootToUse) {
|
||
|
console.error(
|
||
|
chalk.red(
|
||
|
'Error: Could not determine project root to write configuration.'
|
||
|
)
|
||
|
);
|
||
|
return false;
|
||
|
}
|
||
|
const configPath = path.join(rootToUse, CONFIG_FILE_NAME);
|
||
|
|
||
|
// Check if file exists, as expected by tests
|
||
|
if (!fs.existsSync(configPath)) {
|
||
|
console.error(
|
||
|
chalk.red(
|
||
|
`Error: ${CONFIG_FILE_NAME} does not exist. Create it first or initialize project.`
|
||
|
)
|
||
|
);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
// Added 'utf-8' encoding
|
||
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||
|
return true;
|
||
|
} catch (error) {
|
||
|
console.error(
|
||
|
chalk.red(`Error writing to ${configPath}: ${error.message}.`)
|
||
|
);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export {
|
||
|
// Not exporting findProjectRoot as it's internal for CLI context now
|
||
|
readConfig, // Keep exporting if direct access is needed elsewhere
|
||
|
writeConfig, // Keep exporting if direct access is needed elsewhere
|
||
|
validateProvider,
|
||
|
validateProviderModelCombination,
|
||
|
getMainProvider,
|
||
|
getMainModelId,
|
||
|
getResearchProvider,
|
||
|
getResearchModelId,
|
||
|
setMainModel,
|
||
|
setResearchModel,
|
||
|
VALID_PROVIDERS,
|
||
|
MODEL_MAP
|
||
|
};
|