fix: Correct TTY check for AI progress indicator in CLI

Addresses `process.stdout.clearLine is not a function` error when running AI-dependent commands non-interactively (e.g., `update-subtask`).

Adds `process.stdout.isTTY` check before attempting to use terminal-specific output manipulations.

feat: Implement initial config manager for AI models

Adds `scripts/modules/config-manager.js` to handle reading/writing model selections from/to `.taskmasterconfig`.

Implements core functions: findProjectRoot, read/writeConfig, validateModel, get/setModel.

Defines valid model lists. Completes initial work for Subtask 61.1.
This commit is contained in:
Eyal Toledano
2025-04-14 18:53:41 -04:00
parent c7fefb0549
commit 329839aeb8
5 changed files with 331 additions and 23 deletions

230
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"license": "MIT WITH Commons-Clause",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"ai": "^4.3.6",
"boxen": "^8.0.1",
"chalk": "^4.1.2",
"cli-table3": "^0.6.5",
@@ -47,6 +48,76 @@
"node": ">=14.0.0"
}
},
"node_modules/@ai-sdk/provider": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz",
"integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==",
"license": "Apache-2.0",
"dependencies": {
"json-schema": "^0.4.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@ai-sdk/provider-utils": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.7.tgz",
"integrity": "sha512-kM0xS3GWg3aMChh9zfeM+80vEZfXzR3JEUBdycZLtbRZ2TRT8xOj3WodGHPb06sUK5yD7pAXC/P7ctsi2fvUGQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "1.1.3",
"nanoid": "^3.3.8",
"secure-json-parse": "^2.7.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.23.8"
}
},
"node_modules/@ai-sdk/react": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.9.tgz",
"integrity": "sha512-/VYm8xifyngaqFDLXACk/1czDRCefNCdALUyp+kIX6DUIYUWTM93ISoZ+qJ8+3E+FiJAKBQz61o8lIIl+vYtzg==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider-utils": "2.2.7",
"@ai-sdk/ui-utils": "1.2.8",
"swr": "^2.2.5",
"throttleit": "2.1.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^18 || ^19 || ^19.0.0-rc",
"zod": "^3.23.8"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"node_modules/@ai-sdk/ui-utils": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.8.tgz",
"integrity": "sha512-nls/IJCY+ks3Uj6G/agNhXqQeLVqhNfoJbuNgCny+nX2veY5ADB91EcZUqVeQ/ionul2SeUswPY6Q/DxteY29Q==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "1.1.3",
"@ai-sdk/provider-utils": "2.2.7",
"zod-to-json-schema": "^3.24.1"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.23.8"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -2179,6 +2250,15 @@
"node": ">= 8"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
@@ -2293,6 +2373,12 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/diff-match-patch": {
"version": "1.0.36",
"resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
"integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
"license": "MIT"
},
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -2427,6 +2513,32 @@
"node": ">= 8.0.0"
}
},
"node_modules/ai": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/ai/-/ai-4.3.6.tgz",
"integrity": "sha512-cRL/9zFfPRRfVUOk+ll5FHy08FVc692voFzXWJ2YPD9KS+mkjDPp72QT9Etr0ZD/mdlJZHYq4ZHIts7nRpdD6A==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "1.1.3",
"@ai-sdk/provider-utils": "2.2.7",
"@ai-sdk/react": "1.2.9",
"@ai-sdk/ui-utils": "1.2.8",
"@opentelemetry/api": "1.9.0",
"jsondiffpatch": "0.6.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^18 || ^19 || ^19.0.0-rc",
"zod": "^3.23.8"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
}
},
"node_modules/ansi-align": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
@@ -3459,6 +3571,15 @@
"node": ">= 0.8"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@@ -3500,6 +3621,12 @@
"wrappy": "1"
}
},
"node_modules/diff-match-patch": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
"license": "Apache-2.0"
},
"node_modules/diff-sequences": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
@@ -5732,6 +5859,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"license": "(AFL-2.1 OR BSD-3-Clause)"
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -5745,6 +5878,35 @@
"node": ">=6"
}
},
"node_modules/jsondiffpatch": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
"integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
"license": "MIT",
"dependencies": {
"@types/diff-match-patch": "^1.0.36",
"chalk": "^5.3.0",
"diff-match-patch": "^1.0.5"
},
"bin": {
"jsondiffpatch": "bin/jsondiffpatch.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
}
},
"node_modules/jsondiffpatch/node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@@ -6169,6 +6331,24 @@
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -6832,6 +7012,16 @@
"node": ">= 0.8"
}
},
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@@ -7055,6 +7245,12 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/secure-json-parse": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
"license": "BSD-3-Clause"
},
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -7526,6 +7722,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swr": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz",
"integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/term-size": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
@@ -7554,6 +7763,18 @@
"node": ">=8"
}
},
"node_modules/throttleit": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tinycolor2": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
@@ -7764,6 +7985,15 @@
"integrity": "sha512-EWkjYEN0L6KOfEoOH6Wj4ghQqU7eBZMJqRHQnxQAq+dSEzRPClkWjf8557HkWQXF6BrAUoLSAyy9i3RVTliaNg==",
"license": "http://geraintluff.github.io/tv4/LICENSE.txt"
},
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

View File

@@ -39,6 +39,7 @@
"license": "MIT WITH Commons-Clause",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"ai": "^4.3.6",
"boxen": "^8.0.1",
"chalk": "^4.1.2",
"cli-table3": "^0.6.5",

View File

@@ -587,15 +587,18 @@ Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use
try {
// Update loading indicator to show streaming progress
let dotCount = 0;
const readline = await import('readline');
streamingInterval = setInterval(() => {
readline.cursorTo(process.stdout, 0);
process.stdout.write(
`Generating subtasks for task ${task.id}${'.'.repeat(dotCount)}`
);
dotCount = (dotCount + 1) % 4;
}, 500);
// Only create interval if not silent and stdout is a TTY
if (!isSilentMode() && process.stdout.isTTY) {
let dotCount = 0;
const readline = await import('readline');
streamingInterval = setInterval(() => {
readline.cursorTo(process.stdout, 0);
process.stdout.write(
`Generating subtasks for task ${task.id}${'.'.repeat(dotCount)}`
);
dotCount = (dotCount + 1) % 4;
}, 500);
}
// TODO: MOVE THIS TO THE STREAM REQUEST FUNCTION (DRY)
@@ -808,8 +811,8 @@ Note on dependencies: Subtasks can depend on other subtasks with lower IDs. Use
try {
// Update loading indicator to show streaming progress
// Only create if not in silent mode
if (!isSilent) {
// Only create interval if not silent and stdout is a TTY
if (!isSilentMode() && process.stdout.isTTY) {
let dotCount = 0;
const readline = await import('readline');
streamingInterval = setInterval(() => {
@@ -1389,15 +1392,18 @@ Return a JSON object with the following structure:
try {
// Update loading indicator to show streaming progress
let dotCount = 0;
const readline = await import('readline');
streamingInterval = setInterval(() => {
readline.cursorTo(process.stdout, 0);
process.stdout.write(
`Generating research-backed task description${'.'.repeat(dotCount)}`
);
dotCount = (dotCount + 1) % 4;
}, 500);
// Only create interval if not silent and stdout is a TTY
if (!isSilentMode() && process.stdout.isTTY) {
let dotCount = 0;
const readline = await import('readline');
streamingInterval = setInterval(() => {
readline.cursorTo(process.stdout, 0);
process.stdout.write(
`Generating research-backed task description${'.'.repeat(dotCount)}`
);
dotCount = (dotCount + 1) % 4;
}, 500);
}
// Use streaming API call
const stream = await anthropic.messages.create({

View File

@@ -142,7 +142,7 @@ export function getClient(model) {
- Test compatibility with serverless and edge deployments.
# Subtasks:
## 1. Create Configuration Management Module [pending]
## 1. Create Configuration Management Module [in-progress]
### Dependencies: None
### Description: Develop a centralized configuration module to manage AI model settings and preferences, leveraging the Strategy pattern for model selection.
### Details:
@@ -187,6 +187,77 @@ The configuration management module should:
```
</info added on 2025-04-14T21:54:28.887Z>
<info added on 2025-04-14T22:52:29.551Z>
```
The configuration management module should be updated to:
1. Separate model configuration into provider and modelId components:
```javascript
// Example config structure
{
"models": {
"main": {
"provider": "openai",
"modelId": "gpt-3.5-turbo"
},
"research": {
"provider": "openai",
"modelId": "gpt-4"
}
}
}
```
2. Define provider constants:
```javascript
const VALID_MAIN_PROVIDERS = ['openai', 'anthropic', 'local'];
const VALID_RESEARCH_PROVIDERS = ['openai', 'anthropic', 'cohere'];
const DEFAULT_MAIN_PROVIDER = 'openai';
const DEFAULT_RESEARCH_PROVIDER = 'openai';
```
3. Implement optional MODEL_MAP for validation:
```javascript
const MODEL_MAP = {
'openai': ['gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo'],
'anthropic': ['claude-2', 'claude-instant'],
'cohere': ['command', 'command-light'],
'local': ['llama2', 'mistral']
};
```
4. Update getter functions to handle provider/modelId separation:
```javascript
function getMainProvider() { /* return provider with fallbacks */ }
function getMainModelId() { /* return modelId with fallbacks */ }
function getResearchProvider() { /* return provider with fallbacks */ }
function getResearchModelId() { /* return modelId with fallbacks */ }
```
5. Update setter functions to validate both provider and modelId:
```javascript
function setMainModel(provider, modelId) {
// Validate provider is in VALID_MAIN_PROVIDERS
// Optionally validate modelId is valid for provider using MODEL_MAP
// Update config file with new values
}
```
6. Add utility functions for provider-specific validation:
```javascript
function isValidProviderModelCombination(provider, modelId) {
return MODEL_MAP[provider]?.includes(modelId) || false;
}
```
7. Extend unit tests to cover provider/modelId separation, including:
- Testing provider validation
- Testing provider-modelId combination validation
- Verifying getters return correct provider and modelId values
- Confirming setters properly validate and store both components
```
</info added on 2025-04-14T22:52:29.551Z>
## 2. Implement CLI Command Parser for Model Management [pending]
### Dependencies: 61.1
### Description: Extend the CLI command parser to handle the new 'models' command and associated flags for model management.

View File

@@ -2752,8 +2752,8 @@
"title": "Create Configuration Management Module",
"description": "Develop a centralized configuration module to manage AI model settings and preferences, leveraging the Strategy pattern for model selection.",
"dependencies": [],
"details": "1. Create a new `config-manager.js` module to handle model configuration\n2. Implement functions to read/write model preferences to a local config file\n3. Define model validation logic with clear error messages\n4. Create mapping of valid models for main and research operations\n5. Implement getters and setters for model configuration\n6. Add utility functions to validate model names against available options\n7. Include default fallback models\n8. Testing approach: Write unit tests to verify config reading/writing and model validation logic\n\n<info added on 2025-04-14T21:54:28.887Z>\nHere's the additional information to add:\n\n```\nThe configuration management module should:\n\n1. Use a `.taskmasterconfig` JSON file in the project root directory to store model settings\n2. Structure the config file with two main keys: `main` and `research` for respective model selections\n3. Implement functions to locate the project root directory (using package.json as reference)\n4. Define constants for valid models:\n ```javascript\n const VALID_MAIN_MODELS = ['gpt-4', 'gpt-3.5-turbo', 'gpt-4-turbo'];\n const VALID_RESEARCH_MODELS = ['gpt-4', 'gpt-4-turbo', 'claude-2'];\n const DEFAULT_MAIN_MODEL = 'gpt-3.5-turbo';\n const DEFAULT_RESEARCH_MODEL = 'gpt-4';\n ```\n5. Implement model getters with priority order:\n - First check `.taskmasterconfig` file\n - Fall back to environment variables if config file missing/invalid\n - Use defaults as last resort\n6. Implement model setters that validate input against valid model lists before updating config\n7. Keep API key management in `ai-services.js` using environment variables (don't store keys in config file)\n8. Add helper functions for config file operations:\n ```javascript\n function getConfigPath() { /* locate .taskmasterconfig */ }\n function readConfig() { /* read and parse config file */ }\n function writeConfig(config) { /* stringify and write config */ }\n ```\n9. Include error handling for file operations and invalid configurations\n```\n</info added on 2025-04-14T21:54:28.887Z>",
"status": "pending",
"details": "1. Create a new `config-manager.js` module to handle model configuration\n2. Implement functions to read/write model preferences to a local config file\n3. Define model validation logic with clear error messages\n4. Create mapping of valid models for main and research operations\n5. Implement getters and setters for model configuration\n6. Add utility functions to validate model names against available options\n7. Include default fallback models\n8. Testing approach: Write unit tests to verify config reading/writing and model validation logic\n\n<info added on 2025-04-14T21:54:28.887Z>\nHere's the additional information to add:\n\n```\nThe configuration management module should:\n\n1. Use a `.taskmasterconfig` JSON file in the project root directory to store model settings\n2. Structure the config file with two main keys: `main` and `research` for respective model selections\n3. Implement functions to locate the project root directory (using package.json as reference)\n4. Define constants for valid models:\n ```javascript\n const VALID_MAIN_MODELS = ['gpt-4', 'gpt-3.5-turbo', 'gpt-4-turbo'];\n const VALID_RESEARCH_MODELS = ['gpt-4', 'gpt-4-turbo', 'claude-2'];\n const DEFAULT_MAIN_MODEL = 'gpt-3.5-turbo';\n const DEFAULT_RESEARCH_MODEL = 'gpt-4';\n ```\n5. Implement model getters with priority order:\n - First check `.taskmasterconfig` file\n - Fall back to environment variables if config file missing/invalid\n - Use defaults as last resort\n6. Implement model setters that validate input against valid model lists before updating config\n7. Keep API key management in `ai-services.js` using environment variables (don't store keys in config file)\n8. Add helper functions for config file operations:\n ```javascript\n function getConfigPath() { /* locate .taskmasterconfig */ }\n function readConfig() { /* read and parse config file */ }\n function writeConfig(config) { /* stringify and write config */ }\n ```\n9. Include error handling for file operations and invalid configurations\n```\n</info added on 2025-04-14T21:54:28.887Z>\n\n<info added on 2025-04-14T22:52:29.551Z>\n```\nThe configuration management module should be updated to:\n\n1. Separate model configuration into provider and modelId components:\n ```javascript\n // Example config structure\n {\n \"models\": {\n \"main\": {\n \"provider\": \"openai\",\n \"modelId\": \"gpt-3.5-turbo\"\n },\n \"research\": {\n \"provider\": \"openai\",\n \"modelId\": \"gpt-4\"\n }\n }\n }\n ```\n\n2. Define provider constants:\n ```javascript\n const VALID_MAIN_PROVIDERS = ['openai', 'anthropic', 'local'];\n const VALID_RESEARCH_PROVIDERS = ['openai', 'anthropic', 'cohere'];\n const DEFAULT_MAIN_PROVIDER = 'openai';\n const DEFAULT_RESEARCH_PROVIDER = 'openai';\n ```\n\n3. Implement optional MODEL_MAP for validation:\n ```javascript\n const MODEL_MAP = {\n 'openai': ['gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo'],\n 'anthropic': ['claude-2', 'claude-instant'],\n 'cohere': ['command', 'command-light'],\n 'local': ['llama2', 'mistral']\n };\n ```\n\n4. Update getter functions to handle provider/modelId separation:\n ```javascript\n function getMainProvider() { /* return provider with fallbacks */ }\n function getMainModelId() { /* return modelId with fallbacks */ }\n function getResearchProvider() { /* return provider with fallbacks */ }\n function getResearchModelId() { /* return modelId with fallbacks */ }\n ```\n\n5. Update setter functions to validate both provider and modelId:\n ```javascript\n function setMainModel(provider, modelId) {\n // Validate provider is in VALID_MAIN_PROVIDERS\n // Optionally validate modelId is valid for provider using MODEL_MAP\n // Update config file with new values\n }\n ```\n\n6. Add utility functions for provider-specific validation:\n ```javascript\n function isValidProviderModelCombination(provider, modelId) {\n return MODEL_MAP[provider]?.includes(modelId) || false;\n }\n ```\n\n7. Extend unit tests to cover provider/modelId separation, including:\n - Testing provider validation\n - Testing provider-modelId combination validation\n - Verifying getters return correct provider and modelId values\n - Confirming setters properly validate and store both components\n```\n</info added on 2025-04-14T22:52:29.551Z>",
"status": "in-progress",
"parentTaskId": 61
},
{