Refactor: Improve MCP logging, update E2E & tests
Refactors MCP server logging and updates testing infrastructure.
- MCP Server:
- Replaced manual logger wrappers with centralized `createLogWrapper` utility.
- Updated direct function calls to use `{ session, mcpLog }` context.
- Removed deprecated `model` parameter from analyze, expand-all, expand-task tools.
- Adjusted MCP tool import paths and parameter descriptions.
- Documentation:
- Modified `docs/configuration.md`.
- Modified `docs/tutorial.md`.
- Testing:
- E2E Script (`run_e2e.sh`):
- Removed `set -e`.
- Added LLM analysis function (`analyze_log_with_llm`) & integration.
- Adjusted test run directory creation timing.
- Added debug echo statements.
- Deleted Unit Tests: Removed `ai-client-factory.test.js`, `ai-client-utils.test.js`, `ai-services.test.js`.
- Modified Fixtures: Updated `scripts/task-complexity-report.json`.
- Dev Scripts:
- Modified `scripts/dev.js`.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
'task-master-ai': patch
|
'task-master-ai': minor
|
||||||
---
|
---
|
||||||
|
|
||||||
feat(expand): Enhance `expand` and `expand-all` commands
|
feat(expand): Enhance `expand` and `expand-all` commands
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
'task-master-ai': patch
|
'task-master-ai': minor
|
||||||
---
|
---
|
||||||
|
|
||||||
- Adds support for the OpenRouter AI provider. Users can now configure models available through OpenRouter (requiring an `OPENROUTER_API_KEY`) via the `task-master models` command, granting access to a wide range of additional LLMs.
|
Adds support for the OpenRouter AI provider. Users can now configure models available through OpenRouter (requiring an `OPENROUTER_API_KEY`) via the `task-master models` command, granting access to a wide range of additional LLMs.
|
||||||
- IMPORTANT FYI ABOUT OPENROUTER: Taskmaster relies on AI SDK, which itself relies on tool use. It looks like **free** models sometimes do not include tool use. For example, Gemini 2.5 pro (free) failed via OpenRouter (no tool use) but worked fine on the paid version of the model. Custom model support for Open Router is considered experimental and likely will not be further improved for some time.
|
- IMPORTANT FYI ABOUT OPENROUTER: Taskmaster relies on AI SDK, which itself relies on tool use. It looks like **free** models sometimes do not include tool use. For example, Gemini 2.5 pro (free) failed via OpenRouter (no tool use) but worked fine on the paid version of the model. Custom model support for Open Router is considered experimental and likely will not be further improved for some time.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'task-master-ai': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Refactor AI service interaction to use unified layer and Vercel SDK
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
'task-master-ai': patch
|
'task-master-ai': minor
|
||||||
---
|
---
|
||||||
|
|
||||||
Adds model management and new configuration file .taskmasterconfig which houses the models used for main, research and fallback. Adds models command and setter flags. Adds a --setup flag with an interactive setup. We should be calling this during init. Shows a table of active and available models when models is called without flags. Includes SWE scores and token costs, which are manually entered into the supported_models.json, the new place where models are defined for support. Config-manager.js is the core module responsible for managing the new config."
|
Adds model management and new configuration file .taskmasterconfig which houses the models used for main, research and fallback. Adds models command and setter flags. Adds a --setup flag with an interactive setup. We should be calling this during init. Shows a table of active and available models when models is called without flags. Includes SWE scores and token costs, which are manually entered into the supported_models.json, the new place where models are defined for support. Config-manager.js is the core module responsible for managing the new config."
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'task-master-ai': patch
|
'task-master-ai': patch
|
||||||
---
|
---
|
||||||
|
|
||||||
- Improves next command to be subtask-aware
|
Improves next command to be subtask-aware
|
||||||
- The logic for determining the "next task" (findNextTask function, used by task-master next and the next_task MCP tool) has been significantly improved. Previously, it only considered top-level tasks, making its recommendation less useful when a parent task containing subtasks was already marked 'in-progress'.
|
- The logic for determining the "next task" (findNextTask function, used by task-master next and the next_task MCP tool) has been significantly improved. Previously, it only considered top-level tasks, making its recommendation less useful when a parent task containing subtasks was already marked 'in-progress'.
|
||||||
- The updated logic now prioritizes finding the next available subtask within any 'in-progress' parent task, considering subtask dependencies and priority.
|
- The updated logic now prioritizes finding the next available subtask within any 'in-progress' parent task, considering subtask dependencies and priority.
|
||||||
- If no suitable subtask is found within active parent tasks, it falls back to recommending the next eligible top-level task based on the original criteria (status, dependencies, priority).
|
- If no suitable subtask is found within active parent tasks, it falls back to recommending the next eligible top-level task based on the original criteria (status, dependencies, priority).
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
'task-master-ai': patch
|
'task-master-ai': minor
|
||||||
---
|
---
|
||||||
|
|
||||||
- feat: Add custom model ID support for Ollama and OpenRouter providers.
|
Adds custom model ID support for Ollama and OpenRouter providers.
|
||||||
- Adds the `--ollama` and `--openrouter` flags to `task-master models --set-<role>` command to set models for those providers outside of the support models list.
|
- Adds the `--ollama` and `--openrouter` flags to `task-master models --set-<role>` command to set models for those providers outside of the support models list.
|
||||||
- Updated `task-master models --setup` interactive mode with options to explicitly enter custom Ollama or OpenRouter model IDs.
|
- Updated `task-master models --setup` interactive mode with options to explicitly enter custom Ollama or OpenRouter model IDs.
|
||||||
- Implemented live validation against OpenRouter API (`/api/v1/models`) when setting a custom OpenRouter model ID (via flag or setup).
|
- Implemented live validation against OpenRouter API (`/api/v1/models`) when setting a custom OpenRouter model ID (via flag or setup).
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
'task-master-ai': minor
|
'task-master-ai': minor
|
||||||
---
|
---
|
||||||
|
|
||||||
Feat: Integrate OpenAI as a new AI provider.
|
Integrate OpenAI as a new AI provider.
|
||||||
Feat: Enhance `models` command/tool to display API key status.
|
- Enhance `models` command/tool to display API key status.
|
||||||
Feat: Implement model-specific `maxTokens` override based on `supported-models.json` to save you if you use an incorrect max token value.
|
- Implement model-specific `maxTokens` override based on `supported-models.json` to save you if you use an incorrect max token value.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
'task-master-ai': patch
|
'task-master-ai': minor
|
||||||
---
|
---
|
||||||
- Tweaks Perplexity AI calls for research mode to max out input tokens and get day-fresh information
|
Tweaks Perplexity AI calls for research mode to max out input tokens and get day-fresh information
|
||||||
- Forces temp at 0.1 for highly deterministic output, no variations
|
- Forces temp at 0.1 for highly deterministic output, no variations
|
||||||
- Adds a system prompt to further improve the output
|
- Adds a system prompt to further improve the output
|
||||||
- Correctly uses the maximum input tokens (8,719, used 8,700) for perplexity
|
- Correctly uses the maximum input tokens (8,719, used 8,700) for perplexity
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
'task-master-ai': patch
|
'task-master-ai': patch
|
||||||
---
|
---
|
||||||
|
|
||||||
- Adds a 'models' CLI and MCP command to get the current model configuration, available models, and gives the ability to set main/research/fallback models."
|
Adds a 'models' CLI and MCP command to get the current model configuration, available models, and gives the ability to set main/research/fallback models."
|
||||||
- In the CLI, `task-master models` shows the current models config. Using the `--setup` flag launches an interactive set up that allows you to easily select the models you want to use for each of the three roles. Use `q` during the interactive setup to cancel the setup.
|
- In the CLI, `task-master models` shows the current models config. Using the `--setup` flag launches an interactive set up that allows you to easily select the models you want to use for each of the three roles. Use `q` during the interactive setup to cancel the setup.
|
||||||
- In the MCP, responses are simplified in RESTful format (instead of the full CLI output). The agent can use the `models` tool with different arguments, including `listAvailableModels` to get available models. Run without arguments, it will return the current configuration. Arguments are available to set the model for each of the three roles. This allows you to manage Taskmaster AI providers and models directly from either the CLI or MCP or both.
|
- In the MCP, responses are simplified in RESTful format (instead of the full CLI output). The agent can use the `models` tool with different arguments, including `listAvailableModels` to get available models. Run without arguments, it will return the current configuration. Arguments are available to set the model for each of the three roles. This allows you to manage Taskmaster AI providers and models directly from either the CLI or MCP or both.
|
||||||
- Updated the CLI help menu when you run `task-master` to include missing commands and .taskmasterconfig information.
|
- Updated the CLI help menu when you run `task-master` to include missing commands and .taskmasterconfig information.
|
||||||
- Adds `--research` flag to `add-task` so you can hit up Perplexity right from the add-task flow, rather than having to add a task and then update it.
|
- Adds `--research` flag to `add-task` so you can hit up Perplexity right from the add-task flow, rather than having to add a task and then update it.
|
||||||
@@ -5,7 +5,7 @@ Taskmaster uses two primary methods for configuration:
|
|||||||
1. **`.taskmasterconfig` File (Project Root - Recommended for most settings)**
|
1. **`.taskmasterconfig` File (Project Root - Recommended for most settings)**
|
||||||
|
|
||||||
- This JSON file stores most configuration settings, including AI model selections, parameters, logging levels, and project defaults.
|
- This JSON file stores most configuration settings, including AI model selections, parameters, logging levels, and project defaults.
|
||||||
- **Location:** Create this file in the root directory of your project.
|
- **Location:** This file is created in the root directory of your project when you run the `task-master models --setup` interactive setup. You typically do this during the initialization sequence. Do not manually edit this file beyond adjusting Temperature and Max Tokens depending on your model.
|
||||||
- **Management:** Use the `task-master models --setup` command (or `models` MCP tool) to interactively create and manage this file. You can also set specific models directly using `task-master models --set-<role>=<model_id>`, adding `--ollama` or `--openrouter` flags for custom models. Manual editing is possible but not recommended unless you understand the structure.
|
- **Management:** Use the `task-master models --setup` command (or `models` MCP tool) to interactively create and manage this file. You can also set specific models directly using `task-master models --set-<role>=<model_id>`, adding `--ollama` or `--openrouter` flags for custom models. Manual editing is possible but not recommended unless you understand the structure.
|
||||||
- **Example Structure:**
|
- **Example Structure:**
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ MCP (Model Control Protocol) provides the easiest way to get started with Task M
|
|||||||
npm i -g task-master-ai
|
npm i -g task-master-ai
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
|
2. **Add the MCP config to your IDE/MCP Client** (Cursor is recommended, but it works with other clients):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -39,6 +39,13 @@ npm i -g task-master-ai
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**IMPORTANT:** An API key is _required_ for each AI provider you plan on using. Run the `task-master models` command to see your selected models and the status of your API keys across .env and mcp.json
|
||||||
|
|
||||||
|
**To use AI commands in CLI** you MUST have API keys in the .env file
|
||||||
|
**To use AI commands in MCP** you MUST have API keys in the .mcp.json file (or MCP config equivalent)
|
||||||
|
|
||||||
|
We recommend having keys in both places and adding mcp.json to your gitignore so your API keys aren't checked into git.
|
||||||
|
|
||||||
3. **Enable the MCP** in your editor settings
|
3. **Enable the MCP** in your editor settings
|
||||||
|
|
||||||
4. **Prompt the AI** to initialize Task Master:
|
4. **Prompt the AI** to initialize Task Master:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode
|
disableSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct function wrapper for adding a new task with error handling.
|
* Direct function wrapper for adding a new task with error handling.
|
||||||
@@ -31,19 +32,13 @@ export async function addTaskDirect(args, log, context = {}) {
|
|||||||
const { tasksJsonPath, prompt, dependencies, priority, research } = args;
|
const { tasksJsonPath, prompt, dependencies, priority, research } = args;
|
||||||
const { session } = context; // Destructure session from context
|
const { session } = context; // Destructure session from context
|
||||||
|
|
||||||
// Define the logger wrapper to ensure compatibility with core report function
|
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||||
const logWrapper = {
|
enableSilentMode();
|
||||||
info: (message, ...args) => log.info(message, ...args),
|
|
||||||
warn: (message, ...args) => log.warn(message, ...args),
|
// Create logger wrapper using the utility
|
||||||
error: (message, ...args) => log.error(message, ...args),
|
const mcpLog = createLogWrapper(log);
|
||||||
debug: (message, ...args) => log.debug && log.debug(message, ...args), // Handle optional debug
|
|
||||||
success: (message, ...args) => log.info(message, ...args) // Map success to info if needed
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
|
||||||
enableSilentMode();
|
|
||||||
|
|
||||||
// Check if tasksJsonPath was provided
|
// Check if tasksJsonPath was provided
|
||||||
if (!tasksJsonPath) {
|
if (!tasksJsonPath) {
|
||||||
log.error('addTaskDirect called without tasksJsonPath');
|
log.error('addTaskDirect called without tasksJsonPath');
|
||||||
@@ -112,8 +107,8 @@ export async function addTaskDirect(args, log, context = {}) {
|
|||||||
taskDependencies,
|
taskDependencies,
|
||||||
taskPriority,
|
taskPriority,
|
||||||
{
|
{
|
||||||
mcpLog: logWrapper,
|
session,
|
||||||
session
|
mcpLog
|
||||||
},
|
},
|
||||||
'json', // outputFormat
|
'json', // outputFormat
|
||||||
manualTaskData, // Pass the manual task data
|
manualTaskData, // Pass the manual task data
|
||||||
@@ -132,8 +127,8 @@ export async function addTaskDirect(args, log, context = {}) {
|
|||||||
taskDependencies,
|
taskDependencies,
|
||||||
taskPriority,
|
taskPriority,
|
||||||
{
|
{
|
||||||
mcpLog: logWrapper,
|
session,
|
||||||
session
|
mcpLog
|
||||||
},
|
},
|
||||||
'json', // outputFormat
|
'json', // outputFormat
|
||||||
null, // manualTaskData is null for AI creation
|
null, // manualTaskData is null for AI creation
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import {
|
|||||||
isSilentMode
|
isSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js'; // Import the new utility
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyze task complexity and generate recommendations
|
* Analyze task complexity and generate recommendations
|
||||||
* @param {Object} args - Function arguments
|
* @param {Object} args - Function arguments
|
||||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||||
* @param {string} args.outputPath - Explicit absolute path to save the report.
|
* @param {string} args.outputPath - Explicit absolute path to save the report.
|
||||||
* @param {string} [args.model] - Deprecated: LLM model to use for analysis (ignored)
|
|
||||||
* @param {string|number} [args.threshold] - Minimum complexity score to recommend expansion (1-10)
|
* @param {string|number} [args.threshold] - Minimum complexity score to recommend expansion (1-10)
|
||||||
* @param {boolean} [args.research] - Use Perplexity AI for research-backed complexity analysis
|
* @param {boolean} [args.research] - Use Perplexity AI for research-backed complexity analysis
|
||||||
* @param {Object} log - Logger object
|
* @param {Object} log - Logger object
|
||||||
@@ -76,14 +76,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
|||||||
enableSilentMode();
|
enableSilentMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
const logWrapper = {
|
// Create logger wrapper using the utility
|
||||||
info: (message, ...args) => log.info(message, ...args),
|
const mcpLog = createLogWrapper(log);
|
||||||
warn: (message, ...args) => log.warn(message, ...args),
|
|
||||||
error: (message, ...args) => log.error(message, ...args),
|
|
||||||
debug: (message, ...args) => log.debug && log.debug(message, ...args),
|
|
||||||
success: (message, ...args) => log.info(message, ...args) // Map success to info
|
|
||||||
};
|
|
||||||
// --- End Silent Mode and Logger Wrapper ---
|
|
||||||
|
|
||||||
let report; // To store the result from the core function
|
let report; // To store the result from the core function
|
||||||
|
|
||||||
@@ -92,7 +86,7 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
|||||||
// Call the core function, passing options and the context object { session, mcpLog }
|
// Call the core function, passing options and the context object { session, mcpLog }
|
||||||
report = await analyzeTaskComplexity(options, {
|
report = await analyzeTaskComplexity(options, {
|
||||||
session, // Pass the session object
|
session, // Pass the session object
|
||||||
mcpLog: logWrapper // Pass the logger wrapper
|
mcpLog // Pass the logger wrapper
|
||||||
});
|
});
|
||||||
// --- End Core Function Call ---
|
// --- End Core Function Call ---
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
import { expandAllTasks } from '../../../../scripts/modules/task-manager.js';
|
import { expandAllTasks } from '../../../../scripts/modules/task-manager.js';
|
||||||
import {
|
import {
|
||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode,
|
disableSilentMode
|
||||||
isSilentMode
|
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand all pending tasks with subtasks (Direct Function Wrapper)
|
* Expand all pending tasks with subtasks (Direct Function Wrapper)
|
||||||
@@ -26,14 +26,8 @@ export async function expandAllTasksDirect(args, log, context = {}) {
|
|||||||
// Destructure expected args
|
// Destructure expected args
|
||||||
const { tasksJsonPath, num, research, prompt, force } = args;
|
const { tasksJsonPath, num, research, prompt, force } = args;
|
||||||
|
|
||||||
// Create the standard logger wrapper
|
// Create logger wrapper using the utility
|
||||||
const logWrapper = {
|
const mcpLog = createLogWrapper(log);
|
||||||
info: (message, ...args) => log.info(message, ...args),
|
|
||||||
warn: (message, ...args) => log.warn(message, ...args),
|
|
||||||
error: (message, ...args) => log.error(message, ...args),
|
|
||||||
debug: (message, ...args) => log.debug && log.debug(message, ...args), // Handle optional debug
|
|
||||||
success: (message, ...args) => log.info(message, ...args) // Map success to info if needed
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!tasksJsonPath) {
|
if (!tasksJsonPath) {
|
||||||
log.error('expandAllTasksDirect called without tasksJsonPath');
|
log.error('expandAllTasksDirect called without tasksJsonPath');
|
||||||
@@ -58,15 +52,14 @@ export async function expandAllTasksDirect(args, log, context = {}) {
|
|||||||
const additionalContext = prompt || '';
|
const additionalContext = prompt || '';
|
||||||
const forceFlag = force === true;
|
const forceFlag = force === true;
|
||||||
|
|
||||||
// Call the core function, passing the logger wrapper and session
|
// Call the core function, passing options and the context object { session, mcpLog }
|
||||||
const result = await expandAllTasks(
|
const result = await expandAllTasks(
|
||||||
tasksJsonPath, // Use the provided path
|
tasksJsonPath,
|
||||||
numSubtasks,
|
numSubtasks,
|
||||||
useResearch,
|
useResearch,
|
||||||
additionalContext,
|
additionalContext,
|
||||||
forceFlag,
|
forceFlag,
|
||||||
{ mcpLog: logWrapper, session }, // Pass the wrapper and session
|
{ session, mcpLog }
|
||||||
'json' // Explicitly request JSON output format
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Core function now returns a summary object
|
// Core function now returns a summary object
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Direct function implementation for expanding a task into subtasks
|
* Direct function implementation for expanding a task into subtasks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import expandTask from '../../../../scripts/modules/task-manager/expand-task.js'; // Correct import path
|
import expandTask from '../../../../scripts/modules/task-manager/expand-task.js';
|
||||||
import {
|
import {
|
||||||
readJSON,
|
readJSON,
|
||||||
writeJSON,
|
writeJSON,
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct function wrapper for expanding a task into subtasks with error handling.
|
* Direct function wrapper for expanding a task into subtasks with error handling.
|
||||||
@@ -180,28 +181,23 @@ export async function expandTaskDirect(args, log, context = {}) {
|
|||||||
// Save tasks.json with potentially empty subtasks array
|
// Save tasks.json with potentially empty subtasks array
|
||||||
writeJSON(tasksPath, data);
|
writeJSON(tasksPath, data);
|
||||||
|
|
||||||
|
// Create logger wrapper using the utility
|
||||||
|
const mcpLog = createLogWrapper(log);
|
||||||
|
|
||||||
// Process the request
|
// Process the request
|
||||||
try {
|
try {
|
||||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||||
const wasSilent = isSilentMode();
|
const wasSilent = isSilentMode();
|
||||||
if (!wasSilent) enableSilentMode();
|
if (!wasSilent) enableSilentMode();
|
||||||
|
|
||||||
const logWrapper = {
|
// Call the core expandTask function with the wrapped logger
|
||||||
info: (message, ...args) => log.info(message, ...args),
|
|
||||||
warn: (message, ...args) => log.warn(message, ...args),
|
|
||||||
error: (message, ...args) => log.error(message, ...args),
|
|
||||||
debug: (message, ...args) => log.debug && log.debug(message, ...args),
|
|
||||||
success: (message, ...args) => log.info(message, ...args)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call expandTask with session context to ensure AI client is properly initialized
|
|
||||||
const result = await expandTask(
|
const result = await expandTask(
|
||||||
tasksPath,
|
tasksPath,
|
||||||
taskId,
|
taskId,
|
||||||
numSubtasks,
|
numSubtasks,
|
||||||
useResearch,
|
useResearch,
|
||||||
additionalContext,
|
additionalContext,
|
||||||
{ session: session, mcpLog: logWrapper }
|
{ mcpLog, session }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Restore normal logging
|
// Restore normal logging
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode
|
disableSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or update model configuration
|
* Get or update model configuration
|
||||||
@@ -25,14 +26,7 @@ export async function modelsDirect(args, log, context = {}) {
|
|||||||
const { projectRoot } = args; // Extract projectRoot from args
|
const { projectRoot } = args; // Extract projectRoot from args
|
||||||
|
|
||||||
// Create a logger wrapper that the core functions can use
|
// Create a logger wrapper that the core functions can use
|
||||||
const logWrapper = {
|
const mcpLog = createLogWrapper(log);
|
||||||
info: (message, ...args) => log.info(message, ...args),
|
|
||||||
warn: (message, ...args) => log.warn(message, ...args),
|
|
||||||
error: (message, ...args) => log.error(message, ...args),
|
|
||||||
debug: (message, ...args) =>
|
|
||||||
log.debug ? log.debug(message, ...args) : null,
|
|
||||||
success: (message, ...args) => log.info(message, ...args)
|
|
||||||
};
|
|
||||||
|
|
||||||
log.info(`Executing models_direct with args: ${JSON.stringify(args)}`);
|
log.info(`Executing models_direct with args: ${JSON.stringify(args)}`);
|
||||||
log.info(`Using project root: ${projectRoot}`);
|
log.info(`Using project root: ${projectRoot}`);
|
||||||
@@ -59,7 +53,7 @@ export async function modelsDirect(args, log, context = {}) {
|
|||||||
if (args.listAvailableModels === true) {
|
if (args.listAvailableModels === true) {
|
||||||
return await getAvailableModelsList({
|
return await getAvailableModelsList({
|
||||||
session,
|
session,
|
||||||
mcpLog: logWrapper,
|
mcpLog,
|
||||||
projectRoot // Pass projectRoot to function
|
projectRoot // Pass projectRoot to function
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -68,7 +62,7 @@ export async function modelsDirect(args, log, context = {}) {
|
|||||||
if (args.setMain) {
|
if (args.setMain) {
|
||||||
return await setModel('main', args.setMain, {
|
return await setModel('main', args.setMain, {
|
||||||
session,
|
session,
|
||||||
mcpLog: logWrapper,
|
mcpLog,
|
||||||
projectRoot, // Pass projectRoot to function
|
projectRoot, // Pass projectRoot to function
|
||||||
providerHint: args.openrouter
|
providerHint: args.openrouter
|
||||||
? 'openrouter'
|
? 'openrouter'
|
||||||
@@ -81,7 +75,7 @@ export async function modelsDirect(args, log, context = {}) {
|
|||||||
if (args.setResearch) {
|
if (args.setResearch) {
|
||||||
return await setModel('research', args.setResearch, {
|
return await setModel('research', args.setResearch, {
|
||||||
session,
|
session,
|
||||||
mcpLog: logWrapper,
|
mcpLog,
|
||||||
projectRoot, // Pass projectRoot to function
|
projectRoot, // Pass projectRoot to function
|
||||||
providerHint: args.openrouter
|
providerHint: args.openrouter
|
||||||
? 'openrouter'
|
? 'openrouter'
|
||||||
@@ -94,7 +88,7 @@ export async function modelsDirect(args, log, context = {}) {
|
|||||||
if (args.setFallback) {
|
if (args.setFallback) {
|
||||||
return await setModel('fallback', args.setFallback, {
|
return await setModel('fallback', args.setFallback, {
|
||||||
session,
|
session,
|
||||||
mcpLog: logWrapper,
|
mcpLog,
|
||||||
projectRoot, // Pass projectRoot to function
|
projectRoot, // Pass projectRoot to function
|
||||||
providerHint: args.openrouter
|
providerHint: args.openrouter
|
||||||
? 'openrouter'
|
? 'openrouter'
|
||||||
@@ -107,7 +101,7 @@ export async function modelsDirect(args, log, context = {}) {
|
|||||||
// Default action: get current configuration
|
// Default action: get current configuration
|
||||||
return await getModelConfiguration({
|
return await getModelConfiguration({
|
||||||
session,
|
session,
|
||||||
mcpLog: logWrapper,
|
mcpLog,
|
||||||
projectRoot // Pass projectRoot to function
|
projectRoot // Pass projectRoot to function
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode
|
disableSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct function wrapper for parsing PRD documents and generating tasks.
|
* Direct function wrapper for parsing PRD documents and generating tasks.
|
||||||
@@ -104,23 +105,20 @@ export async function parsePRDDirect(args, log, context = {}) {
|
|||||||
`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks`
|
`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the logger wrapper for proper logging in the core function
|
// --- Logger Wrapper ---
|
||||||
const logWrapper = {
|
const mcpLog = createLogWrapper(log);
|
||||||
info: (message, ...args) => log.info(message, ...args),
|
|
||||||
warn: (message, ...args) => log.warn(message, ...args),
|
// Prepare options for the core function
|
||||||
error: (message, ...args) => log.error(message, ...args),
|
const options = {
|
||||||
debug: (message, ...args) => log.debug && log.debug(message, ...args),
|
mcpLog,
|
||||||
success: (message, ...args) => log.info(message, ...args) // Map success to info
|
session
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||||
enableSilentMode();
|
enableSilentMode();
|
||||||
try {
|
try {
|
||||||
// Execute core parsePRD function - It now handles AI internally
|
// Execute core parsePRD function - It now handles AI internally
|
||||||
const tasksDataResult = await parsePRD(inputPath, outputPath, numTasks, {
|
const tasksDataResult = await parsePRD(inputPath, numTasks, options);
|
||||||
mcpLog: logWrapper,
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check the result from the core function (assuming it might return data or null/undefined)
|
// Check the result from the core function (assuming it might return data or null/undefined)
|
||||||
if (!tasksDataResult || !tasksDataResult.tasks) {
|
if (!tasksDataResult || !tasksDataResult.tasks) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode
|
disableSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct function wrapper for updateSubtaskById with error handling.
|
* Direct function wrapper for updateSubtaskById with error handling.
|
||||||
@@ -95,15 +96,8 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
|||||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||||
enableSilentMode();
|
enableSilentMode();
|
||||||
|
|
||||||
// Create a logger wrapper object to handle logging without breaking the mcpLog[level] calls
|
// Create the logger wrapper using the utility function
|
||||||
// This ensures outputFormat is set to 'json' while still supporting proper logging
|
const mcpLog = createLogWrapper(log);
|
||||||
const logWrapper = {
|
|
||||||
info: (message) => log.info(message),
|
|
||||||
warn: (message) => log.warn(message),
|
|
||||||
error: (message) => log.error(message),
|
|
||||||
debug: (message) => log.debug && log.debug(message),
|
|
||||||
success: (message) => log.info(message) // Map success to info if needed
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute core updateSubtaskById function
|
// Execute core updateSubtaskById function
|
||||||
// Pass both session and logWrapper as mcpLog to ensure outputFormat is 'json'
|
// Pass both session and logWrapper as mcpLog to ensure outputFormat is 'json'
|
||||||
@@ -114,7 +108,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
|||||||
useResearch,
|
useResearch,
|
||||||
{
|
{
|
||||||
session,
|
session,
|
||||||
mcpLog: logWrapper
|
mcpLog
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode
|
disableSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct function wrapper for updateTaskById with error handling.
|
* Direct function wrapper for updateTaskById with error handling.
|
||||||
@@ -96,14 +97,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
|||||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||||
enableSilentMode();
|
enableSilentMode();
|
||||||
|
|
||||||
// Create a logger wrapper that matches what updateTaskById expects
|
// Create the logger wrapper using the utility function
|
||||||
const logWrapper = {
|
const mcpLog = createLogWrapper(log);
|
||||||
info: (message) => log.info(message),
|
|
||||||
warn: (message) => log.warn(message),
|
|
||||||
error: (message) => log.error(message),
|
|
||||||
debug: (message) => log.debug && log.debug(message),
|
|
||||||
success: (message) => log.info(message) // Map success to info since many loggers don't have success
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute core updateTaskById function with proper parameters
|
// Execute core updateTaskById function with proper parameters
|
||||||
await updateTaskById(
|
await updateTaskById(
|
||||||
@@ -112,7 +107,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
|||||||
prompt,
|
prompt,
|
||||||
useResearch,
|
useResearch,
|
||||||
{
|
{
|
||||||
mcpLog: logWrapper, // Use our wrapper object that has the expected method structure
|
mcpLog, // Pass the wrapped logger
|
||||||
session
|
session
|
||||||
},
|
},
|
||||||
'json'
|
'json'
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
enableSilentMode,
|
enableSilentMode,
|
||||||
disableSilentMode
|
disableSilentMode
|
||||||
} from '../../../../scripts/modules/utils.js';
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct function wrapper for updating tasks based on new context/prompt.
|
* Direct function wrapper for updating tasks based on new context/prompt.
|
||||||
@@ -88,6 +89,9 @@ export async function updateTasksDirect(args, log, context = {}) {
|
|||||||
|
|
||||||
enableSilentMode(); // Enable silent mode
|
enableSilentMode(); // Enable silent mode
|
||||||
try {
|
try {
|
||||||
|
// Create logger wrapper using the utility
|
||||||
|
const mcpLog = createLogWrapper(log);
|
||||||
|
|
||||||
// Execute core updateTasks function, passing session context
|
// Execute core updateTasks function, passing session context
|
||||||
await updateTasks(
|
await updateTasks(
|
||||||
tasksJsonPath,
|
tasksJsonPath,
|
||||||
@@ -95,7 +99,7 @@ export async function updateTasksDirect(args, log, context = {}) {
|
|||||||
prompt,
|
prompt,
|
||||||
useResearch,
|
useResearch,
|
||||||
// Pass context with logger wrapper and session
|
// Pass context with logger wrapper and session
|
||||||
{ mcpLog: logWrapper, session },
|
{ mcpLog, session },
|
||||||
'json' // Explicitly request JSON format for MCP
|
'json' // Explicitly request JSON format for MCP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,6 @@ export function registerAnalyzeTool(server) {
|
|||||||
.describe(
|
.describe(
|
||||||
'Output file path relative to project root (default: scripts/task-complexity-report.json)'
|
'Output file path relative to project root (default: scripts/task-complexity-report.json)'
|
||||||
),
|
),
|
||||||
model: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
'Deprecated: LLM model override (model is determined by configured role)'
|
|
||||||
),
|
|
||||||
threshold: z.coerce
|
threshold: z.coerce
|
||||||
.number()
|
.number()
|
||||||
.min(1)
|
.min(1)
|
||||||
@@ -44,7 +38,7 @@ export function registerAnalyzeTool(server) {
|
|||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
'Path to the tasks file relative to project root (default: tasks/tasks.json)'
|
'Absolute path to the tasks file in the /tasks folder inside the project root (default: tasks/tasks.json)'
|
||||||
),
|
),
|
||||||
research: z
|
research: z
|
||||||
.boolean()
|
.boolean()
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function registerExpandAllTool(server) {
|
|||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
'Relative path to the tasks file from project root (default: tasks/tasks.json)'
|
'Absolute path to the tasks file in the /tasks folder inside the project root (default: tasks/tasks.json)'
|
||||||
),
|
),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
getProjectRootFromSession
|
getProjectRootFromSession
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { expandTaskDirect } from '../core/direct-functions/expand-task.js';
|
import { expandTaskDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksJsonPath } from '../core/utils/path-utils.js';
|
import { findTasksJsonPath } from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ function createContentResponse(content) {
|
|||||||
* @param {string} errorMessage - Error message to include in response
|
* @param {string} errorMessage - Error message to include in response
|
||||||
* @returns {Object} - Error content response object in FastMCP format
|
* @returns {Object} - Error content response object in FastMCP format
|
||||||
*/
|
*/
|
||||||
export function createErrorResponse(errorMessage) {
|
function createErrorResponse(errorMessage) {
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -455,6 +455,25 @@ export function createErrorResponse(errorMessage) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a logger wrapper object compatible with core function expectations.
|
||||||
|
* Adapts the MCP logger to the { info, warn, error, debug, success } structure.
|
||||||
|
* @param {Object} log - The MCP logger instance.
|
||||||
|
* @returns {Object} - The logger wrapper object.
|
||||||
|
*/
|
||||||
|
function createLogWrapper(log) {
|
||||||
|
return {
|
||||||
|
info: (message, ...args) => log.info(message, ...args),
|
||||||
|
warn: (message, ...args) => log.warn(message, ...args),
|
||||||
|
error: (message, ...args) => log.error(message, ...args),
|
||||||
|
// Handle optional debug method
|
||||||
|
debug: (message, ...args) =>
|
||||||
|
log.debug ? log.debug(message, ...args) : null,
|
||||||
|
// Map success to info as a common fallback
|
||||||
|
success: (message, ...args) => log.info(message, ...args)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure all functions are exported
|
// Ensure all functions are exported
|
||||||
export {
|
export {
|
||||||
getProjectRoot,
|
getProjectRoot,
|
||||||
@@ -463,5 +482,7 @@ export {
|
|||||||
executeTaskMasterCommand,
|
executeTaskMasterCommand,
|
||||||
getCachedOrExecute,
|
getCachedOrExecute,
|
||||||
processMCPResponseData,
|
processMCPResponseData,
|
||||||
createContentResponse
|
createContentResponse,
|
||||||
|
createErrorResponse,
|
||||||
|
createLogWrapper
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
* It imports functionality from the modules directory and provides a CLI.
|
* It imports functionality from the modules directory and provides a CLI.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import dotenv from 'dotenv'; // <-- ADD
|
import dotenv from 'dotenv';
|
||||||
dotenv.config(); // <-- ADD
|
dotenv.config();
|
||||||
|
|
||||||
// Add at the very beginning of the file
|
// Add at the very beginning of the file
|
||||||
if (process.env.DEBUG === '1') {
|
if (process.env.DEBUG === '1') {
|
||||||
|
|||||||
@@ -8,26 +8,22 @@
|
|||||||
|
|
||||||
// --- Core Dependencies ---
|
// --- Core Dependencies ---
|
||||||
import {
|
import {
|
||||||
// REMOVED: getProviderAndModelForRole, // This was incorrect
|
getMainProvider,
|
||||||
getMainProvider, // ADD individual getters
|
|
||||||
getMainModelId,
|
getMainModelId,
|
||||||
getResearchProvider,
|
getResearchProvider,
|
||||||
getResearchModelId,
|
getResearchModelId,
|
||||||
getFallbackProvider,
|
getFallbackProvider,
|
||||||
getFallbackModelId,
|
getFallbackModelId,
|
||||||
getParametersForRole
|
getParametersForRole
|
||||||
// ConfigurationError // Import if needed for specific handling
|
} from './config-manager.js';
|
||||||
} from './config-manager.js'; // Corrected: Removed getProviderAndModelForRole
|
|
||||||
import { log, resolveEnvVariable } from './utils.js';
|
import { log, resolveEnvVariable } from './utils.js';
|
||||||
|
|
||||||
// --- Provider Service Imports ---
|
|
||||||
// Corrected path from scripts/ai-providers/... to ../../src/ai-providers/...
|
|
||||||
import * as anthropic from '../../src/ai-providers/anthropic.js';
|
import * as anthropic from '../../src/ai-providers/anthropic.js';
|
||||||
import * as perplexity from '../../src/ai-providers/perplexity.js';
|
import * as perplexity from '../../src/ai-providers/perplexity.js';
|
||||||
import * as google from '../../src/ai-providers/google.js'; // Import Google provider
|
import * as google from '../../src/ai-providers/google.js';
|
||||||
import * as openai from '../../src/ai-providers/openai.js'; // ADD: Import OpenAI provider
|
import * as openai from '../../src/ai-providers/openai.js';
|
||||||
import * as xai from '../../src/ai-providers/xai.js'; // ADD: Import xAI provider
|
import * as xai from '../../src/ai-providers/xai.js';
|
||||||
import * as openrouter from '../../src/ai-providers/openrouter.js'; // ADD: Import OpenRouter provider
|
import * as openrouter from '../../src/ai-providers/openrouter.js';
|
||||||
// TODO: Import other provider modules when implemented (ollama, etc.)
|
// TODO: Import other provider modules when implemented (ollama, etc.)
|
||||||
|
|
||||||
// --- Provider Function Map ---
|
// --- Provider Function Map ---
|
||||||
@@ -37,13 +33,11 @@ const PROVIDER_FUNCTIONS = {
|
|||||||
generateText: anthropic.generateAnthropicText,
|
generateText: anthropic.generateAnthropicText,
|
||||||
streamText: anthropic.streamAnthropicText,
|
streamText: anthropic.streamAnthropicText,
|
||||||
generateObject: anthropic.generateAnthropicObject
|
generateObject: anthropic.generateAnthropicObject
|
||||||
// streamObject: anthropic.streamAnthropicObject, // Add when implemented
|
|
||||||
},
|
},
|
||||||
perplexity: {
|
perplexity: {
|
||||||
generateText: perplexity.generatePerplexityText,
|
generateText: perplexity.generatePerplexityText,
|
||||||
streamText: perplexity.streamPerplexityText,
|
streamText: perplexity.streamPerplexityText,
|
||||||
generateObject: perplexity.generatePerplexityObject
|
generateObject: perplexity.generatePerplexityObject
|
||||||
// streamObject: perplexity.streamPerplexityObject, // Add when implemented
|
|
||||||
},
|
},
|
||||||
google: {
|
google: {
|
||||||
// Add Google entry
|
// Add Google entry
|
||||||
@@ -73,22 +67,20 @@ const PROVIDER_FUNCTIONS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Configuration for Retries ---
|
// --- Configuration for Retries ---
|
||||||
const MAX_RETRIES = 2; // Total attempts = 1 + MAX_RETRIES
|
const MAX_RETRIES = 2;
|
||||||
const INITIAL_RETRY_DELAY_MS = 1000; // 1 second
|
const INITIAL_RETRY_DELAY_MS = 1000;
|
||||||
|
|
||||||
// Helper function to check if an error is retryable
|
// Helper function to check if an error is retryable
|
||||||
function isRetryableError(error) {
|
function isRetryableError(error) {
|
||||||
const errorMessage = error.message?.toLowerCase() || '';
|
const errorMessage = error.message?.toLowerCase() || '';
|
||||||
// Add common retryable error patterns
|
|
||||||
return (
|
return (
|
||||||
errorMessage.includes('rate limit') ||
|
errorMessage.includes('rate limit') ||
|
||||||
errorMessage.includes('overloaded') ||
|
errorMessage.includes('overloaded') ||
|
||||||
errorMessage.includes('service temporarily unavailable') ||
|
errorMessage.includes('service temporarily unavailable') ||
|
||||||
errorMessage.includes('timeout') ||
|
errorMessage.includes('timeout') ||
|
||||||
errorMessage.includes('network error') ||
|
errorMessage.includes('network error') ||
|
||||||
// Add specific status codes if available from the SDK errors
|
error.status === 429 ||
|
||||||
error.status === 429 || // Too Many Requests
|
error.status >= 500
|
||||||
error.status >= 500 // Server-side errors
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,11 +143,11 @@ function _resolveApiKey(providerName, session) {
|
|||||||
const keyMap = {
|
const keyMap = {
|
||||||
openai: 'OPENAI_API_KEY',
|
openai: 'OPENAI_API_KEY',
|
||||||
anthropic: 'ANTHROPIC_API_KEY',
|
anthropic: 'ANTHROPIC_API_KEY',
|
||||||
google: 'GOOGLE_API_KEY', // Add Google API Key
|
google: 'GOOGLE_API_KEY',
|
||||||
perplexity: 'PERPLEXITY_API_KEY',
|
perplexity: 'PERPLEXITY_API_KEY',
|
||||||
mistral: 'MISTRAL_API_KEY',
|
mistral: 'MISTRAL_API_KEY',
|
||||||
azure: 'AZURE_OPENAI_API_KEY',
|
azure: 'AZURE_OPENAI_API_KEY',
|
||||||
openrouter: 'OPENROUTER_API_KEY', // ADD OpenRouter key
|
openrouter: 'OPENROUTER_API_KEY',
|
||||||
xai: 'XAI_API_KEY'
|
xai: 'XAI_API_KEY'
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,7 +191,7 @@ async function _attemptProviderCallWithRetries(
|
|||||||
attemptRole
|
attemptRole
|
||||||
) {
|
) {
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
const fnName = providerApiFn.name; // Get function name for logging
|
const fnName = providerApiFn.name;
|
||||||
|
|
||||||
while (retries <= MAX_RETRIES) {
|
while (retries <= MAX_RETRIES) {
|
||||||
try {
|
try {
|
||||||
@@ -215,7 +207,7 @@ async function _attemptProviderCallWithRetries(
|
|||||||
'info',
|
'info',
|
||||||
`${fnName} succeeded for role ${attemptRole} (Provider: ${providerName}) on attempt ${retries + 1}`
|
`${fnName} succeeded for role ${attemptRole} (Provider: ${providerName}) on attempt ${retries + 1}`
|
||||||
);
|
);
|
||||||
return result; // Success!
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(
|
log(
|
||||||
'warn',
|
'warn',
|
||||||
@@ -235,7 +227,7 @@ async function _attemptProviderCallWithRetries(
|
|||||||
'error',
|
'error',
|
||||||
`Non-retryable error or max retries reached for role ${attemptRole} (${fnName} / ${providerName}).`
|
`Non-retryable error or max retries reached for role ${attemptRole} (${fnName} / ${providerName}).`
|
||||||
);
|
);
|
||||||
throw error; // Final failure for this attempt chain
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,18 +280,17 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
try {
|
try {
|
||||||
log('info', `New AI service call with role: ${currentRole}`);
|
log('info', `New AI service call with role: ${currentRole}`);
|
||||||
|
|
||||||
// --- Corrected Config Fetching ---
|
|
||||||
// 1. Get Config: Provider, Model, Parameters for the current role
|
// 1. Get Config: Provider, Model, Parameters for the current role
|
||||||
// Call individual getters based on the current role
|
// Call individual getters based on the current role
|
||||||
if (currentRole === 'main') {
|
if (currentRole === 'main') {
|
||||||
providerName = getMainProvider(); // Use individual getter
|
providerName = getMainProvider();
|
||||||
modelId = getMainModelId(); // Use individual getter
|
modelId = getMainModelId();
|
||||||
} else if (currentRole === 'research') {
|
} else if (currentRole === 'research') {
|
||||||
providerName = getResearchProvider(); // Use individual getter
|
providerName = getResearchProvider();
|
||||||
modelId = getResearchModelId(); // Use individual getter
|
modelId = getResearchModelId();
|
||||||
} else if (currentRole === 'fallback') {
|
} else if (currentRole === 'fallback') {
|
||||||
providerName = getFallbackProvider(); // Use individual getter
|
providerName = getFallbackProvider();
|
||||||
modelId = getFallbackModelId(); // Use individual getter
|
modelId = getFallbackModelId();
|
||||||
} else {
|
} else {
|
||||||
log(
|
log(
|
||||||
'error',
|
'error',
|
||||||
@@ -307,9 +298,8 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
);
|
);
|
||||||
lastError =
|
lastError =
|
||||||
lastError || new Error(`Unknown AI role specified: ${currentRole}`);
|
lastError || new Error(`Unknown AI role specified: ${currentRole}`);
|
||||||
continue; // Skip to the next role attempt
|
continue;
|
||||||
}
|
}
|
||||||
// --- End Corrected Config Fetching ---
|
|
||||||
|
|
||||||
if (!providerName || !modelId) {
|
if (!providerName || !modelId) {
|
||||||
log(
|
log(
|
||||||
@@ -321,10 +311,10 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
new Error(
|
new Error(
|
||||||
`Configuration missing for role '${currentRole}'. Provider: ${providerName}, Model: ${modelId}`
|
`Configuration missing for role '${currentRole}'. Provider: ${providerName}, Model: ${modelId}`
|
||||||
);
|
);
|
||||||
continue; // Skip to the next role
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
roleParams = getParametersForRole(currentRole); // Get { maxTokens, temperature }
|
roleParams = getParametersForRole(currentRole);
|
||||||
|
|
||||||
// 2. Get Provider Function Set
|
// 2. Get Provider Function Set
|
||||||
providerFnSet = PROVIDER_FUNCTIONS[providerName?.toLowerCase()];
|
providerFnSet = PROVIDER_FUNCTIONS[providerName?.toLowerCase()];
|
||||||
@@ -355,7 +345,7 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Resolve API Key (will throw if required and missing)
|
// 3. Resolve API Key (will throw if required and missing)
|
||||||
apiKey = _resolveApiKey(providerName?.toLowerCase(), session); // Throws on failure
|
apiKey = _resolveApiKey(providerName?.toLowerCase(), session);
|
||||||
|
|
||||||
// 4. Construct Messages Array
|
// 4. Construct Messages Array
|
||||||
const messages = [];
|
const messages = [];
|
||||||
@@ -395,10 +385,9 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
modelId,
|
modelId,
|
||||||
maxTokens: roleParams.maxTokens,
|
maxTokens: roleParams.maxTokens,
|
||||||
temperature: roleParams.temperature,
|
temperature: roleParams.temperature,
|
||||||
messages, // *** Pass the constructed messages array ***
|
messages,
|
||||||
// Add specific params for generateObject if needed
|
|
||||||
...(serviceType === 'generateObject' && { schema, objectName }),
|
...(serviceType === 'generateObject' && { schema, objectName }),
|
||||||
...restApiParams // Include other params like maxRetries
|
...restApiParams
|
||||||
};
|
};
|
||||||
|
|
||||||
// 6. Attempt the call with retries
|
// 6. Attempt the call with retries
|
||||||
@@ -412,20 +401,18 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
|
|
||||||
log('info', `${serviceType}Service succeeded using role: ${currentRole}`);
|
log('info', `${serviceType}Service succeeded using role: ${currentRole}`);
|
||||||
|
|
||||||
return result; // Return original result for other cases
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const cleanMessage = _extractErrorMessage(error); // Extract clean message
|
const cleanMessage = _extractErrorMessage(error);
|
||||||
log(
|
log(
|
||||||
'error', // Log as error since this role attempt failed
|
'error',
|
||||||
`Service call failed for role ${currentRole} (Provider: ${providerName || 'unknown'}, Model: ${modelId || 'unknown'}): ${cleanMessage}` // Log the clean message
|
`Service call failed for role ${currentRole} (Provider: ${providerName || 'unknown'}, Model: ${modelId || 'unknown'}): ${cleanMessage}`
|
||||||
);
|
);
|
||||||
lastError = error; // Store the original error for potential debugging
|
lastError = error;
|
||||||
lastCleanErrorMessage = cleanMessage; // Store the clean message for final throw
|
lastCleanErrorMessage = cleanMessage;
|
||||||
|
|
||||||
// --- ADDED: Specific check for tool use error in generateObject ---
|
|
||||||
if (serviceType === 'generateObject') {
|
if (serviceType === 'generateObject') {
|
||||||
const lowerCaseMessage = cleanMessage.toLowerCase();
|
const lowerCaseMessage = cleanMessage.toLowerCase();
|
||||||
// Check for specific error messages indicating lack of tool support
|
|
||||||
if (
|
if (
|
||||||
lowerCaseMessage.includes(
|
lowerCaseMessage.includes(
|
||||||
'no endpoints found that support tool use'
|
'no endpoints found that support tool use'
|
||||||
@@ -437,14 +424,9 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
) {
|
) {
|
||||||
const specificErrorMsg = `Model '${modelId || 'unknown'}' via provider '${providerName || 'unknown'}' does not support the 'tool use' required by generateObjectService. Please configure a model that supports tool/function calling for the '${currentRole}' role, or use generateTextService if structured output is not strictly required.`;
|
const specificErrorMsg = `Model '${modelId || 'unknown'}' via provider '${providerName || 'unknown'}' does not support the 'tool use' required by generateObjectService. Please configure a model that supports tool/function calling for the '${currentRole}' role, or use generateTextService if structured output is not strictly required.`;
|
||||||
log('error', `[Tool Support Error] ${specificErrorMsg}`);
|
log('error', `[Tool Support Error] ${specificErrorMsg}`);
|
||||||
// Throw a more specific error immediately, breaking the fallback loop for this specific issue.
|
|
||||||
// Using a generic Error for simplicity, could use a custom ConfigurationError.
|
|
||||||
throw new Error(specificErrorMsg);
|
throw new Error(specificErrorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- END ADDED ---
|
|
||||||
|
|
||||||
// Continue to the next role in the sequence if it wasn't a specific tool support error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +449,6 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
* @returns {Promise<string>} The generated text content.
|
* @returns {Promise<string>} The generated text content.
|
||||||
*/
|
*/
|
||||||
async function generateTextService(params) {
|
async function generateTextService(params) {
|
||||||
// Now directly returns the text string or throws error
|
|
||||||
return _unifiedServiceRunner('generateText', params);
|
return _unifiedServiceRunner('generateText', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +465,6 @@ async function generateTextService(params) {
|
|||||||
* @returns {Promise<ReadableStream<string>>} A readable stream of text deltas.
|
* @returns {Promise<ReadableStream<string>>} A readable stream of text deltas.
|
||||||
*/
|
*/
|
||||||
async function streamTextService(params) {
|
async function streamTextService(params) {
|
||||||
// Now directly returns the stream object or throws error
|
|
||||||
return _unifiedServiceRunner('streamText', params);
|
return _unifiedServiceRunner('streamText', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,7 +480,6 @@ async function streamTextService(params) {
|
|||||||
* @param {string} [params.systemPrompt] - Optional system prompt.
|
* @param {string} [params.systemPrompt] - Optional system prompt.
|
||||||
* @param {string} [params.objectName='generated_object'] - Name for object/tool.
|
* @param {string} [params.objectName='generated_object'] - Name for object/tool.
|
||||||
* @param {number} [params.maxRetries=3] - Max retries for object generation.
|
* @param {number} [params.maxRetries=3] - Max retries for object generation.
|
||||||
* // Other specific generateObject params can be included here.
|
|
||||||
* @returns {Promise<object>} The generated object matching the schema.
|
* @returns {Promise<object>} The generated object matching the schema.
|
||||||
*/
|
*/
|
||||||
async function generateObjectService(params) {
|
async function generateObjectService(params) {
|
||||||
@@ -509,7 +488,6 @@ async function generateObjectService(params) {
|
|||||||
maxRetries: 3
|
maxRetries: 3
|
||||||
};
|
};
|
||||||
const combinedParams = { ...defaults, ...params };
|
const combinedParams = { ...defaults, ...params };
|
||||||
// Now directly returns the generated object or throws error
|
|
||||||
return _unifiedServiceRunner('generateObject', combinedParams);
|
return _unifiedServiceRunner('generateObject', combinedParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
// Remove Anthropic import if client is no longer initialized globally
|
|
||||||
// import { Anthropic } from '@anthropic-ai/sdk';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
log,
|
log,
|
||||||
@@ -23,11 +21,6 @@ import { displayBanner } from './ui.js';
|
|||||||
|
|
||||||
import { generateTaskFiles } from './task-manager.js';
|
import { generateTaskFiles } from './task-manager.js';
|
||||||
|
|
||||||
// Remove global Anthropic client initialization
|
|
||||||
// const anthropic = new Anthropic({
|
|
||||||
// apiKey: process.env.ANTHROPIC_API_KEY
|
|
||||||
// });
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a dependency to a task
|
* Add a dependency to a task
|
||||||
* @param {string} tasksPath - Path to the tasks.json file
|
* @param {string} tasksPath - Path to the tasks.json file
|
||||||
|
|||||||
@@ -265,7 +265,7 @@
|
|||||||
"max_tokens": 1048576
|
"max_tokens": 1048576
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "google/gemini-2.5-pro-exp-03-25:free",
|
"id": "google/gemini-2.5-pro-exp-03-25",
|
||||||
"swe_score": 0,
|
"swe_score": 0,
|
||||||
"cost_per_1m_tokens": { "input": 0, "output": 0 },
|
"cost_per_1m_tokens": { "input": 0, "output": 0 },
|
||||||
"allowed_roles": ["main", "fallback"],
|
"allowed_roles": ["main", "fallback"],
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ Do not include any explanatory text, markdown formatting, or code block markers
|
|||||||
* @param {Object} options Command options
|
* @param {Object} options Command options
|
||||||
* @param {string} options.file - Path to tasks file
|
* @param {string} options.file - Path to tasks file
|
||||||
* @param {string} options.output - Path to report output file
|
* @param {string} options.output - Path to report output file
|
||||||
* @param {string} [options.model] - Deprecated: Model override (ignored)
|
|
||||||
* @param {string|number} [options.threshold] - Complexity threshold
|
* @param {string|number} [options.threshold] - Complexity threshold
|
||||||
* @param {boolean} [options.research] - Use research role
|
* @param {boolean} [options.research] - Use research role
|
||||||
* @param {Object} [options._filteredTasksData] - Pre-filtered task data (internal use)
|
* @param {Object} [options._filteredTasksData] - Pre-filtered task data (internal use)
|
||||||
|
|||||||
@@ -1,259 +1,259 @@
|
|||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"generatedAt": "2025-04-25T02:29:42.258Z",
|
"generatedAt": "2025-04-25T02:29:42.258Z",
|
||||||
"tasksAnalyzed": 31,
|
"tasksAnalyzed": 31,
|
||||||
"thresholdScore": 5,
|
"thresholdScore": 5,
|
||||||
"projectName": "Task Master",
|
"projectName": "Task Master",
|
||||||
"usedResearch": false
|
"usedResearch": false
|
||||||
},
|
},
|
||||||
"complexityAnalysis": [
|
"complexityAnalysis": [
|
||||||
{
|
{
|
||||||
"taskId": 24,
|
"taskId": 24,
|
||||||
"taskTitle": "Implement AI-Powered Test Generation Command",
|
"taskTitle": "Implement AI-Powered Test Generation Command",
|
||||||
"complexityScore": 9,
|
"complexityScore": 9,
|
||||||
"recommendedSubtasks": 10,
|
"recommendedSubtasks": 10,
|
||||||
"expansionPrompt": "Break down the implementation of an AI-powered test generation command into granular steps, covering CLI integration, task retrieval, AI prompt construction, API integration, test file formatting, error handling, documentation, and comprehensive testing (unit, integration, error cases, and manual verification).",
|
"expansionPrompt": "Break down the implementation of an AI-powered test generation command into granular steps, covering CLI integration, task retrieval, AI prompt construction, API integration, test file formatting, error handling, documentation, and comprehensive testing (unit, integration, error cases, and manual verification).",
|
||||||
"reasoning": "This task involves advanced CLI development, deep integration with external AI APIs, dynamic prompt engineering, file system operations, error handling, and extensive testing. It requires orchestrating multiple subsystems and ensuring robust, user-friendly output. The cognitive and technical demands are high, justifying a high complexity score and a need for further decomposition into at least 10 subtasks to manage risk and ensure quality.[1][3][4][5]"
|
"reasoning": "This task involves advanced CLI development, deep integration with external AI APIs, dynamic prompt engineering, file system operations, error handling, and extensive testing. It requires orchestrating multiple subsystems and ensuring robust, user-friendly output. The cognitive and technical demands are high, justifying a high complexity score and a need for further decomposition into at least 10 subtasks to manage risk and ensure quality.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 26,
|
"taskId": 26,
|
||||||
"taskTitle": "Implement Context Foundation for AI Operations",
|
"taskTitle": "Implement Context Foundation for AI Operations",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Expand the context foundation implementation into detailed subtasks for CLI flag integration, file reading utilities, error handling, context formatting, command handler updates, documentation, and comprehensive testing for both functionality and error scenarios.",
|
"expansionPrompt": "Expand the context foundation implementation into detailed subtasks for CLI flag integration, file reading utilities, error handling, context formatting, command handler updates, documentation, and comprehensive testing for both functionality and error scenarios.",
|
||||||
"reasoning": "This task introduces foundational context management across multiple commands, requiring careful CLI design, file I/O, error handling, and integration with AI prompt construction. While less complex than full AI-powered features, it still spans several modules and requires robust validation, suggesting a moderate-to-high complexity and a need for further breakdown.[1][3][4]"
|
"reasoning": "This task introduces foundational context management across multiple commands, requiring careful CLI design, file I/O, error handling, and integration with AI prompt construction. While less complex than full AI-powered features, it still spans several modules and requires robust validation, suggesting a moderate-to-high complexity and a need for further breakdown.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 27,
|
"taskId": 27,
|
||||||
"taskTitle": "Implement Context Enhancements for AI Operations",
|
"taskTitle": "Implement Context Enhancements for AI Operations",
|
||||||
"complexityScore": 8,
|
"complexityScore": 8,
|
||||||
"recommendedSubtasks": 10,
|
"recommendedSubtasks": 10,
|
||||||
"expansionPrompt": "Decompose the context enhancement task into subtasks for code context extraction, task history integration, PRD summarization, context formatting, token optimization, error handling, and comprehensive testing for each new context type.",
|
"expansionPrompt": "Decompose the context enhancement task into subtasks for code context extraction, task history integration, PRD summarization, context formatting, token optimization, error handling, and comprehensive testing for each new context type.",
|
||||||
"reasoning": "This phase builds on the foundation to add sophisticated context extraction (code, history, PRD), requiring advanced parsing, summarization, and prompt engineering. The need to optimize for token limits and maintain performance across large codebases increases both technical and cognitive complexity, warranting a high score and further subtask expansion.[1][3][4][5]"
|
"reasoning": "This phase builds on the foundation to add sophisticated context extraction (code, history, PRD), requiring advanced parsing, summarization, and prompt engineering. The need to optimize for token limits and maintain performance across large codebases increases both technical and cognitive complexity, warranting a high score and further subtask expansion.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 28,
|
"taskId": 28,
|
||||||
"taskTitle": "Implement Advanced ContextManager System",
|
"taskTitle": "Implement Advanced ContextManager System",
|
||||||
"complexityScore": 10,
|
"complexityScore": 10,
|
||||||
"recommendedSubtasks": 12,
|
"recommendedSubtasks": 12,
|
||||||
"expansionPrompt": "Expand the ContextManager implementation into subtasks for class design, context source integration, optimization algorithms, caching, token management, command interface updates, AI service integration, performance monitoring, logging, and comprehensive testing (unit, integration, performance, and user experience).",
|
"expansionPrompt": "Expand the ContextManager implementation into subtasks for class design, context source integration, optimization algorithms, caching, token management, command interface updates, AI service integration, performance monitoring, logging, and comprehensive testing (unit, integration, performance, and user experience).",
|
||||||
"reasoning": "This is a highly complex architectural task involving advanced class design, optimization algorithms, dynamic context prioritization, caching, and integration with multiple AI services. It requires deep system knowledge, careful performance considerations, and robust error handling, making it one of the most complex tasks in the set and justifying a large number of subtasks.[1][3][4][5]"
|
"reasoning": "This is a highly complex architectural task involving advanced class design, optimization algorithms, dynamic context prioritization, caching, and integration with multiple AI services. It requires deep system knowledge, careful performance considerations, and robust error handling, making it one of the most complex tasks in the set and justifying a large number of subtasks.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 32,
|
"taskId": 32,
|
||||||
"taskTitle": "Implement \"learn\" Command for Automatic Cursor Rule Generation",
|
"taskTitle": "Implement \"learn\" Command for Automatic Cursor Rule Generation",
|
||||||
"complexityScore": 9,
|
"complexityScore": 9,
|
||||||
"recommendedSubtasks": 15,
|
"recommendedSubtasks": 15,
|
||||||
"expansionPrompt": "Break down the 'learn' command implementation into subtasks for file structure setup, path utilities, chat history analysis, rule management, AI integration, error handling, performance optimization, CLI integration, logging, and comprehensive testing.",
|
"expansionPrompt": "Break down the 'learn' command implementation into subtasks for file structure setup, path utilities, chat history analysis, rule management, AI integration, error handling, performance optimization, CLI integration, logging, and comprehensive testing.",
|
||||||
"reasoning": "This task requires orchestrating file system operations, parsing complex chat and code histories, managing rule templates, integrating with AI for pattern extraction, and ensuring robust error handling and performance. The breadth and depth of required functionality, along with the need for both automatic and manual triggers, make this a highly complex task needing extensive decomposition.[1][3][4][5]"
|
"reasoning": "This task requires orchestrating file system operations, parsing complex chat and code histories, managing rule templates, integrating with AI for pattern extraction, and ensuring robust error handling and performance. The breadth and depth of required functionality, along with the need for both automatic and manual triggers, make this a highly complex task needing extensive decomposition.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 35,
|
"taskId": 35,
|
||||||
"taskTitle": "Integrate Grok3 API for Research Capabilities",
|
"taskTitle": "Integrate Grok3 API for Research Capabilities",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Expand the Grok3 API integration into subtasks for API client development, service layer updates, payload/response adaptation, error handling, configuration management, UI updates, backward compatibility, and documentation/testing.",
|
"expansionPrompt": "Expand the Grok3 API integration into subtasks for API client development, service layer updates, payload/response adaptation, error handling, configuration management, UI updates, backward compatibility, and documentation/testing.",
|
||||||
"reasoning": "This migration task involves replacing a core external API, adapting to new request/response formats, updating configuration and UI, and ensuring backward compatibility. While not as cognitively complex as some AI tasks, the risk and breadth of impact across the system justify a moderate-to-high complexity and further breakdown.[1][3][4]"
|
"reasoning": "This migration task involves replacing a core external API, adapting to new request/response formats, updating configuration and UI, and ensuring backward compatibility. While not as cognitively complex as some AI tasks, the risk and breadth of impact across the system justify a moderate-to-high complexity and further breakdown.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 36,
|
"taskId": 36,
|
||||||
"taskTitle": "Add Ollama Support for AI Services as Claude Alternative",
|
"taskTitle": "Add Ollama Support for AI Services as Claude Alternative",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Decompose the Ollama integration into subtasks for service class implementation, configuration, model selection, prompt formatting, error handling, fallback logic, documentation, and comprehensive testing.",
|
"expansionPrompt": "Decompose the Ollama integration into subtasks for service class implementation, configuration, model selection, prompt formatting, error handling, fallback logic, documentation, and comprehensive testing.",
|
||||||
"reasoning": "Adding a local AI provider requires interface compatibility, configuration management, error handling, and fallback logic, as well as user documentation. The technical complexity is moderate-to-high, especially in ensuring seamless switching and robust error handling, warranting further subtasking.[1][3][4]"
|
"reasoning": "Adding a local AI provider requires interface compatibility, configuration management, error handling, and fallback logic, as well as user documentation. The technical complexity is moderate-to-high, especially in ensuring seamless switching and robust error handling, warranting further subtasking.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 37,
|
"taskId": 37,
|
||||||
"taskTitle": "Add Gemini Support for Main AI Services as Claude Alternative",
|
"taskTitle": "Add Gemini Support for Main AI Services as Claude Alternative",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Expand Gemini integration into subtasks for service class creation, authentication, prompt/response mapping, configuration, error handling, streaming support, documentation, and comprehensive testing.",
|
"expansionPrompt": "Expand Gemini integration into subtasks for service class creation, authentication, prompt/response mapping, configuration, error handling, streaming support, documentation, and comprehensive testing.",
|
||||||
"reasoning": "Integrating a new cloud AI provider involves authentication, API adaptation, configuration, and ensuring feature parity. The complexity is similar to other provider integrations, requiring careful planning and multiple subtasks for robust implementation and testing.[1][3][4]"
|
"reasoning": "Integrating a new cloud AI provider involves authentication, API adaptation, configuration, and ensuring feature parity. The complexity is similar to other provider integrations, requiring careful planning and multiple subtasks for robust implementation and testing.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 40,
|
"taskId": 40,
|
||||||
"taskTitle": "Implement 'plan' Command for Task Implementation Planning",
|
"taskTitle": "Implement 'plan' Command for Task Implementation Planning",
|
||||||
"complexityScore": 6,
|
"complexityScore": 6,
|
||||||
"recommendedSubtasks": 6,
|
"recommendedSubtasks": 6,
|
||||||
"expansionPrompt": "Break down the 'plan' command implementation into subtasks for CLI integration, task/subtask retrieval, AI prompt construction, plan formatting, error handling, and testing.",
|
"expansionPrompt": "Break down the 'plan' command implementation into subtasks for CLI integration, task/subtask retrieval, AI prompt construction, plan formatting, error handling, and testing.",
|
||||||
"reasoning": "This task involves AI prompt engineering, CLI integration, and content formatting, but is more focused and less technically demanding than full AI service or context management features. It still requires careful error handling and testing, suggesting a moderate complexity and a handful of subtasks.[1][3][4]"
|
"reasoning": "This task involves AI prompt engineering, CLI integration, and content formatting, but is more focused and less technically demanding than full AI service or context management features. It still requires careful error handling and testing, suggesting a moderate complexity and a handful of subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 41,
|
"taskId": 41,
|
||||||
"taskTitle": "Implement Visual Task Dependency Graph in Terminal",
|
"taskTitle": "Implement Visual Task Dependency Graph in Terminal",
|
||||||
"complexityScore": 8,
|
"complexityScore": 8,
|
||||||
"recommendedSubtasks": 10,
|
"recommendedSubtasks": 10,
|
||||||
"expansionPrompt": "Expand the visual dependency graph implementation into subtasks for CLI command setup, graph layout algorithms, ASCII/Unicode rendering, color coding, circular dependency detection, filtering, accessibility, performance optimization, documentation, and testing.",
|
"expansionPrompt": "Expand the visual dependency graph implementation into subtasks for CLI command setup, graph layout algorithms, ASCII/Unicode rendering, color coding, circular dependency detection, filtering, accessibility, performance optimization, documentation, and testing.",
|
||||||
"reasoning": "Rendering complex dependency graphs in the terminal with color coding, layout optimization, and accessibility features is technically challenging and requires careful algorithm design and robust error handling. The need for performance optimization and user-friendly output increases the complexity, justifying a high score and further subtasking.[1][3][4][5]"
|
"reasoning": "Rendering complex dependency graphs in the terminal with color coding, layout optimization, and accessibility features is technically challenging and requires careful algorithm design and robust error handling. The need for performance optimization and user-friendly output increases the complexity, justifying a high score and further subtasking.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 42,
|
"taskId": 42,
|
||||||
"taskTitle": "Implement MCP-to-MCP Communication Protocol",
|
"taskTitle": "Implement MCP-to-MCP Communication Protocol",
|
||||||
"complexityScore": 10,
|
"complexityScore": 10,
|
||||||
"recommendedSubtasks": 12,
|
"recommendedSubtasks": 12,
|
||||||
"expansionPrompt": "Break down the MCP-to-MCP protocol implementation into subtasks for protocol definition, adapter pattern, client module, reference integration, mode support, core module updates, configuration, documentation, error handling, security, and comprehensive testing.",
|
"expansionPrompt": "Break down the MCP-to-MCP protocol implementation into subtasks for protocol definition, adapter pattern, client module, reference integration, mode support, core module updates, configuration, documentation, error handling, security, and comprehensive testing.",
|
||||||
"reasoning": "Designing and implementing a standardized communication protocol with dynamic mode switching, adapter patterns, and robust error handling is architecturally complex. It requires deep system understanding, security considerations, and extensive testing, making it one of the most complex tasks and requiring significant decomposition.[1][3][4][5]"
|
"reasoning": "Designing and implementing a standardized communication protocol with dynamic mode switching, adapter patterns, and robust error handling is architecturally complex. It requires deep system understanding, security considerations, and extensive testing, making it one of the most complex tasks and requiring significant decomposition.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 43,
|
"taskId": 43,
|
||||||
"taskTitle": "Add Research Flag to Add-Task Command",
|
"taskTitle": "Add Research Flag to Add-Task Command",
|
||||||
"complexityScore": 5,
|
"complexityScore": 5,
|
||||||
"recommendedSubtasks": 5,
|
"recommendedSubtasks": 5,
|
||||||
"expansionPrompt": "Expand the research flag implementation into subtasks for CLI parser updates, subtask generation logic, parent linking, help documentation, and testing.",
|
"expansionPrompt": "Expand the research flag implementation into subtasks for CLI parser updates, subtask generation logic, parent linking, help documentation, and testing.",
|
||||||
"reasoning": "This is a focused feature addition involving CLI parsing, subtask generation, and documentation. While it requires some integration with AI or templating logic, the scope is well-defined and less complex than architectural or multi-module tasks, suggesting a moderate complexity and a handful of subtasks.[1][3][4]"
|
"reasoning": "This is a focused feature addition involving CLI parsing, subtask generation, and documentation. While it requires some integration with AI or templating logic, the scope is well-defined and less complex than architectural or multi-module tasks, suggesting a moderate complexity and a handful of subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 44,
|
"taskId": 44,
|
||||||
"taskTitle": "Implement Task Automation with Webhooks and Event Triggers",
|
"taskTitle": "Implement Task Automation with Webhooks and Event Triggers",
|
||||||
"complexityScore": 9,
|
"complexityScore": 9,
|
||||||
"recommendedSubtasks": 10,
|
"recommendedSubtasks": 10,
|
||||||
"expansionPrompt": "Decompose the webhook and event trigger system into subtasks for event system design, webhook registration, trigger definition, incoming/outgoing webhook handling, authentication, rate limiting, CLI management, payload templating, logging, and comprehensive testing.",
|
"expansionPrompt": "Decompose the webhook and event trigger system into subtasks for event system design, webhook registration, trigger definition, incoming/outgoing webhook handling, authentication, rate limiting, CLI management, payload templating, logging, and comprehensive testing.",
|
||||||
"reasoning": "Building a robust automation system with webhooks and event triggers involves designing an event system, secure webhook handling, trigger logic, CLI management, and error handling. The breadth and integration requirements make this a highly complex task needing extensive breakdown.[1][3][4][5]"
|
"reasoning": "Building a robust automation system with webhooks and event triggers involves designing an event system, secure webhook handling, trigger logic, CLI management, and error handling. The breadth and integration requirements make this a highly complex task needing extensive breakdown.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 45,
|
"taskId": 45,
|
||||||
"taskTitle": "Implement GitHub Issue Import Feature",
|
"taskTitle": "Implement GitHub Issue Import Feature",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Expand the GitHub issue import feature into subtasks for CLI flag parsing, URL extraction, API integration, data mapping, authentication, error handling, override logic, documentation, and testing.",
|
"expansionPrompt": "Expand the GitHub issue import feature into subtasks for CLI flag parsing, URL extraction, API integration, data mapping, authentication, error handling, override logic, documentation, and testing.",
|
||||||
"reasoning": "This task involves external API integration, data mapping, authentication, error handling, and user override logic. While not as complex as architectural changes, it still requires careful planning and multiple subtasks for robust implementation and testing.[1][3][4]"
|
"reasoning": "This task involves external API integration, data mapping, authentication, error handling, and user override logic. While not as complex as architectural changes, it still requires careful planning and multiple subtasks for robust implementation and testing.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 46,
|
"taskId": 46,
|
||||||
"taskTitle": "Implement ICE Analysis Command for Task Prioritization",
|
"taskTitle": "Implement ICE Analysis Command for Task Prioritization",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Break down the ICE analysis command into subtasks for scoring algorithm development, LLM prompt engineering, report generation, CLI rendering, integration with complexity reports, sorting/filtering, error handling, and testing.",
|
"expansionPrompt": "Break down the ICE analysis command into subtasks for scoring algorithm development, LLM prompt engineering, report generation, CLI rendering, integration with complexity reports, sorting/filtering, error handling, and testing.",
|
||||||
"reasoning": "Implementing a prioritization command with LLM-based scoring, report generation, and CLI rendering involves moderate technical and cognitive complexity, especially in ensuring accurate and actionable outputs. It requires several subtasks for robust implementation and validation.[1][3][4]"
|
"reasoning": "Implementing a prioritization command with LLM-based scoring, report generation, and CLI rendering involves moderate technical and cognitive complexity, especially in ensuring accurate and actionable outputs. It requires several subtasks for robust implementation and validation.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 47,
|
"taskId": 47,
|
||||||
"taskTitle": "Enhance Task Suggestion Actions Card Workflow",
|
"taskTitle": "Enhance Task Suggestion Actions Card Workflow",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Expand the workflow enhancement into subtasks for UI redesign, phase management logic, interactive elements, progress tracking, context addition, task management integration, accessibility, and comprehensive testing.",
|
"expansionPrompt": "Expand the workflow enhancement into subtasks for UI redesign, phase management logic, interactive elements, progress tracking, context addition, task management integration, accessibility, and comprehensive testing.",
|
||||||
"reasoning": "Redesigning a multi-phase workflow with interactive UI elements, progress tracking, and context management involves both UI/UX and logic complexity. The need for seamless transitions and robust state management increases the complexity, warranting further breakdown.[1][3][4]"
|
"reasoning": "Redesigning a multi-phase workflow with interactive UI elements, progress tracking, and context management involves both UI/UX and logic complexity. The need for seamless transitions and robust state management increases the complexity, warranting further breakdown.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 48,
|
"taskId": 48,
|
||||||
"taskTitle": "Refactor Prompts into Centralized Structure",
|
"taskTitle": "Refactor Prompts into Centralized Structure",
|
||||||
"complexityScore": 6,
|
"complexityScore": 6,
|
||||||
"recommendedSubtasks": 6,
|
"recommendedSubtasks": 6,
|
||||||
"expansionPrompt": "Break down the prompt refactoring into subtasks for directory setup, prompt extraction, import updates, naming conventions, documentation, and regression testing.",
|
"expansionPrompt": "Break down the prompt refactoring into subtasks for directory setup, prompt extraction, import updates, naming conventions, documentation, and regression testing.",
|
||||||
"reasoning": "This is a codebase refactoring task focused on maintainability and organization. While it touches many files, the technical complexity is moderate, but careful planning and testing are needed to avoid regressions, suggesting a moderate complexity and several subtasks.[1][3][4]"
|
"reasoning": "This is a codebase refactoring task focused on maintainability and organization. While it touches many files, the technical complexity is moderate, but careful planning and testing are needed to avoid regressions, suggesting a moderate complexity and several subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 49,
|
"taskId": 49,
|
||||||
"taskTitle": "Implement Code Quality Analysis Command",
|
"taskTitle": "Implement Code Quality Analysis Command",
|
||||||
"complexityScore": 8,
|
"complexityScore": 8,
|
||||||
"recommendedSubtasks": 10,
|
"recommendedSubtasks": 10,
|
||||||
"expansionPrompt": "Expand the code quality analysis command into subtasks for pattern recognition, best practice verification, AI integration, recommendation generation, task integration, CLI development, configuration, error handling, documentation, and comprehensive testing.",
|
"expansionPrompt": "Expand the code quality analysis command into subtasks for pattern recognition, best practice verification, AI integration, recommendation generation, task integration, CLI development, configuration, error handling, documentation, and comprehensive testing.",
|
||||||
"reasoning": "This task involves static code analysis, AI integration for best practice checks, recommendation generation, and task creation workflows. The technical and cognitive demands are high, requiring robust validation and integration, justifying a high complexity and multiple subtasks.[1][3][4][5]"
|
"reasoning": "This task involves static code analysis, AI integration for best practice checks, recommendation generation, and task creation workflows. The technical and cognitive demands are high, requiring robust validation and integration, justifying a high complexity and multiple subtasks.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 50,
|
"taskId": 50,
|
||||||
"taskTitle": "Implement Test Coverage Tracking System by Task",
|
"taskTitle": "Implement Test Coverage Tracking System by Task",
|
||||||
"complexityScore": 9,
|
"complexityScore": 9,
|
||||||
"recommendedSubtasks": 12,
|
"recommendedSubtasks": 12,
|
||||||
"expansionPrompt": "Break down the test coverage tracking system into subtasks for data structure design, coverage parsing, mapping algorithms, CLI commands, LLM-powered test generation, MCP integration, visualization, workflow integration, error handling, documentation, and comprehensive testing.",
|
"expansionPrompt": "Break down the test coverage tracking system into subtasks for data structure design, coverage parsing, mapping algorithms, CLI commands, LLM-powered test generation, MCP integration, visualization, workflow integration, error handling, documentation, and comprehensive testing.",
|
||||||
"reasoning": "Mapping test coverage to tasks, integrating with coverage tools, generating targeted tests, and visualizing coverage requires advanced data modeling, parsing, AI integration, and workflow design. The breadth and depth of this system make it highly complex and in need of extensive decomposition.[1][3][4][5]"
|
"reasoning": "Mapping test coverage to tasks, integrating with coverage tools, generating targeted tests, and visualizing coverage requires advanced data modeling, parsing, AI integration, and workflow design. The breadth and depth of this system make it highly complex and in need of extensive decomposition.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 51,
|
"taskId": 51,
|
||||||
"taskTitle": "Implement Perplexity Research Command",
|
"taskTitle": "Implement Perplexity Research Command",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Expand the Perplexity research command into subtasks for API client development, context extraction, CLI interface, result formatting, caching, error handling, documentation, and comprehensive testing.",
|
"expansionPrompt": "Expand the Perplexity research command into subtasks for API client development, context extraction, CLI interface, result formatting, caching, error handling, documentation, and comprehensive testing.",
|
||||||
"reasoning": "This task involves external API integration, context extraction, CLI development, result formatting, caching, and error handling. The technical complexity is moderate-to-high, especially in ensuring robust and user-friendly output, suggesting multiple subtasks.[1][3][4]"
|
"reasoning": "This task involves external API integration, context extraction, CLI development, result formatting, caching, and error handling. The technical complexity is moderate-to-high, especially in ensuring robust and user-friendly output, suggesting multiple subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 52,
|
"taskId": 52,
|
||||||
"taskTitle": "Implement Task Suggestion Command for CLI",
|
"taskTitle": "Implement Task Suggestion Command for CLI",
|
||||||
"complexityScore": 6,
|
"complexityScore": 6,
|
||||||
"recommendedSubtasks": 6,
|
"recommendedSubtasks": 6,
|
||||||
"expansionPrompt": "Break down the task suggestion command into subtasks for task snapshot collection, context extraction, AI suggestion generation, interactive CLI interface, error handling, and testing.",
|
"expansionPrompt": "Break down the task suggestion command into subtasks for task snapshot collection, context extraction, AI suggestion generation, interactive CLI interface, error handling, and testing.",
|
||||||
"reasoning": "This is a focused feature involving AI suggestion generation and interactive CLI elements. While it requires careful context management and error handling, the scope is well-defined and less complex than architectural or multi-module tasks, suggesting a moderate complexity and several subtasks.[1][3][4]"
|
"reasoning": "This is a focused feature involving AI suggestion generation and interactive CLI elements. While it requires careful context management and error handling, the scope is well-defined and less complex than architectural or multi-module tasks, suggesting a moderate complexity and several subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 53,
|
"taskId": 53,
|
||||||
"taskTitle": "Implement Subtask Suggestion Feature for Parent Tasks",
|
"taskTitle": "Implement Subtask Suggestion Feature for Parent Tasks",
|
||||||
"complexityScore": 6,
|
"complexityScore": 6,
|
||||||
"recommendedSubtasks": 6,
|
"recommendedSubtasks": 6,
|
||||||
"expansionPrompt": "Expand the subtask suggestion feature into subtasks for parent task validation, context gathering, AI suggestion logic, interactive CLI interface, subtask linking, and testing.",
|
"expansionPrompt": "Expand the subtask suggestion feature into subtasks for parent task validation, context gathering, AI suggestion logic, interactive CLI interface, subtask linking, and testing.",
|
||||||
"reasoning": "Similar to the task suggestion command, this feature is focused but requires robust context management, AI integration, and interactive CLI handling. The complexity is moderate, warranting several subtasks for a robust implementation.[1][3][4]"
|
"reasoning": "Similar to the task suggestion command, this feature is focused but requires robust context management, AI integration, and interactive CLI handling. The complexity is moderate, warranting several subtasks for a robust implementation.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 54,
|
"taskId": 54,
|
||||||
"taskTitle": "Add Research Flag to Add-Task Command",
|
"taskTitle": "Add Research Flag to Add-Task Command",
|
||||||
"complexityScore": 5,
|
"complexityScore": 5,
|
||||||
"recommendedSubtasks": 5,
|
"recommendedSubtasks": 5,
|
||||||
"expansionPrompt": "Break down the research flag enhancement into subtasks for CLI parser updates, research invocation, user interaction, task creation flow integration, and testing.",
|
"expansionPrompt": "Break down the research flag enhancement into subtasks for CLI parser updates, research invocation, user interaction, task creation flow integration, and testing.",
|
||||||
"reasoning": "This is a focused enhancement involving CLI parsing, research invocation, and user interaction. The technical complexity is moderate, with a clear scope and integration points, suggesting a handful of subtasks.[1][3][4]"
|
"reasoning": "This is a focused enhancement involving CLI parsing, research invocation, and user interaction. The technical complexity is moderate, with a clear scope and integration points, suggesting a handful of subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 55,
|
"taskId": 55,
|
||||||
"taskTitle": "Implement Positional Arguments Support for CLI Commands",
|
"taskTitle": "Implement Positional Arguments Support for CLI Commands",
|
||||||
"complexityScore": 6,
|
"complexityScore": 6,
|
||||||
"recommendedSubtasks": 6,
|
"recommendedSubtasks": 6,
|
||||||
"expansionPrompt": "Expand positional argument support into subtasks for parser updates, argument mapping, help documentation, error handling, backward compatibility, and comprehensive testing.",
|
"expansionPrompt": "Expand positional argument support into subtasks for parser updates, argument mapping, help documentation, error handling, backward compatibility, and comprehensive testing.",
|
||||||
"reasoning": "Upgrading CLI parsing to support positional arguments requires careful mapping, error handling, documentation, and regression testing to maintain backward compatibility. The complexity is moderate, suggesting several subtasks.[1][3][4]"
|
"reasoning": "Upgrading CLI parsing to support positional arguments requires careful mapping, error handling, documentation, and regression testing to maintain backward compatibility. The complexity is moderate, suggesting several subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 56,
|
"taskId": 56,
|
||||||
"taskTitle": "Refactor Task-Master Files into Node Module Structure",
|
"taskTitle": "Refactor Task-Master Files into Node Module Structure",
|
||||||
"complexityScore": 8,
|
"complexityScore": 8,
|
||||||
"recommendedSubtasks": 10,
|
"recommendedSubtasks": 10,
|
||||||
"expansionPrompt": "Break down the refactoring into subtasks for directory setup, file migration, import path updates, build script adjustments, compatibility checks, documentation, regression testing, and rollback planning.",
|
"expansionPrompt": "Break down the refactoring into subtasks for directory setup, file migration, import path updates, build script adjustments, compatibility checks, documentation, regression testing, and rollback planning.",
|
||||||
"reasoning": "This is a high-risk, broad refactoring affecting many files and build processes. It requires careful planning, incremental changes, and extensive testing to avoid regressions, justifying a high complexity and multiple subtasks.[1][3][4][5]"
|
"reasoning": "This is a high-risk, broad refactoring affecting many files and build processes. It requires careful planning, incremental changes, and extensive testing to avoid regressions, justifying a high complexity and multiple subtasks.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 57,
|
"taskId": 57,
|
||||||
"taskTitle": "Enhance Task-Master CLI User Experience and Interface",
|
"taskTitle": "Enhance Task-Master CLI User Experience and Interface",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Expand the CLI UX enhancement into subtasks for log management, visual design, interactive elements, output formatting, help/documentation, accessibility, performance optimization, and comprehensive testing.",
|
"expansionPrompt": "Expand the CLI UX enhancement into subtasks for log management, visual design, interactive elements, output formatting, help/documentation, accessibility, performance optimization, and comprehensive testing.",
|
||||||
"reasoning": "Improving CLI UX involves log management, visual enhancements, interactive elements, and accessibility, requiring both technical and design skills. The breadth of improvements and need for robust testing increase the complexity, suggesting multiple subtasks.[1][3][4]"
|
"reasoning": "Improving CLI UX involves log management, visual enhancements, interactive elements, and accessibility, requiring both technical and design skills. The breadth of improvements and need for robust testing increase the complexity, suggesting multiple subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 58,
|
"taskId": 58,
|
||||||
"taskTitle": "Implement Elegant Package Update Mechanism for Task-Master",
|
"taskTitle": "Implement Elegant Package Update Mechanism for Task-Master",
|
||||||
"complexityScore": 7,
|
"complexityScore": 7,
|
||||||
"recommendedSubtasks": 8,
|
"recommendedSubtasks": 8,
|
||||||
"expansionPrompt": "Break down the update mechanism into subtasks for version detection, update command implementation, file management, configuration migration, notification system, rollback logic, documentation, and comprehensive testing.",
|
"expansionPrompt": "Break down the update mechanism into subtasks for version detection, update command implementation, file management, configuration migration, notification system, rollback logic, documentation, and comprehensive testing.",
|
||||||
"reasoning": "Implementing a robust update mechanism involves version management, file operations, configuration migration, rollback planning, and user communication. The technical and operational complexity is moderate-to-high, requiring multiple subtasks.[1][3][4]"
|
"reasoning": "Implementing a robust update mechanism involves version management, file operations, configuration migration, rollback planning, and user communication. The technical and operational complexity is moderate-to-high, requiring multiple subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 59,
|
"taskId": 59,
|
||||||
"taskTitle": "Remove Manual Package.json Modifications and Implement Automatic Dependency Management",
|
"taskTitle": "Remove Manual Package.json Modifications and Implement Automatic Dependency Management",
|
||||||
"complexityScore": 6,
|
"complexityScore": 6,
|
||||||
"recommendedSubtasks": 6,
|
"recommendedSubtasks": 6,
|
||||||
"expansionPrompt": "Expand the dependency management refactor into subtasks for code audit, removal of manual modifications, npm dependency updates, initialization command updates, documentation, and regression testing.",
|
"expansionPrompt": "Expand the dependency management refactor into subtasks for code audit, removal of manual modifications, npm dependency updates, initialization command updates, documentation, and regression testing.",
|
||||||
"reasoning": "This is a focused refactoring to align with npm best practices. While it touches installation and configuration logic, the technical complexity is moderate, with a clear scope and manageable risk, suggesting several subtasks.[1][3][4]"
|
"reasoning": "This is a focused refactoring to align with npm best practices. While it touches installation and configuration logic, the technical complexity is moderate, with a clear scope and manageable risk, suggesting several subtasks.[1][3][4]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 60,
|
"taskId": 60,
|
||||||
"taskTitle": "Implement Mentor System with Round-Table Discussion Feature",
|
"taskTitle": "Implement Mentor System with Round-Table Discussion Feature",
|
||||||
"complexityScore": 9,
|
"complexityScore": 9,
|
||||||
"recommendedSubtasks": 12,
|
"recommendedSubtasks": 12,
|
||||||
"expansionPrompt": "Break down the mentor system implementation into subtasks for mentor management, round-table simulation, CLI integration, AI personality simulation, task integration, output formatting, error handling, documentation, and comprehensive testing.",
|
"expansionPrompt": "Break down the mentor system implementation into subtasks for mentor management, round-table simulation, CLI integration, AI personality simulation, task integration, output formatting, error handling, documentation, and comprehensive testing.",
|
||||||
"reasoning": "This task involves designing a new system for mentor management, simulating multi-personality AI discussions, integrating with tasks, and ensuring robust CLI and output handling. The breadth and novelty of the feature, along with the need for robust simulation and integration, make it highly complex and in need of extensive decomposition.[1][3][4][5]"
|
"reasoning": "This task involves designing a new system for mentor management, simulating multi-personality AI discussions, integrating with tasks, and ensuring robust CLI and output handling. The breadth and novelty of the feature, along with the need for robust simulation and integration, make it highly complex and in need of extensive decomposition.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 61,
|
"taskId": 61,
|
||||||
"taskTitle": "Implement Flexible AI Model Management",
|
"taskTitle": "Implement Flexible AI Model Management",
|
||||||
"complexityScore": 10,
|
"complexityScore": 10,
|
||||||
"recommendedSubtasks": 15,
|
"recommendedSubtasks": 15,
|
||||||
"expansionPrompt": "Expand the AI model management implementation into subtasks for configuration management, CLI command parsing, provider module development, unified service abstraction, environment variable handling, documentation, integration testing, migration planning, and cleanup of legacy code.",
|
"expansionPrompt": "Expand the AI model management implementation into subtasks for configuration management, CLI command parsing, provider module development, unified service abstraction, environment variable handling, documentation, integration testing, migration planning, and cleanup of legacy code.",
|
||||||
"reasoning": "This is a major architectural overhaul involving configuration management, CLI design, multi-provider integration, abstraction layers, environment variable handling, documentation, and migration. The technical and organizational complexity is extremely high, requiring extensive decomposition and careful coordination.[1][3][4][5]"
|
"reasoning": "This is a major architectural overhaul involving configuration management, CLI design, multi-provider integration, abstraction layers, environment variable handling, documentation, and migration. The technical and organizational complexity is extremely high, requiring extensive decomposition and careful coordination.[1][3][4][5]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskId": 62,
|
"taskId": 62,
|
||||||
"taskTitle": "Add --simple Flag to Update Commands for Direct Text Input",
|
"taskTitle": "Add --simple Flag to Update Commands for Direct Text Input",
|
||||||
"complexityScore": 5,
|
"complexityScore": 5,
|
||||||
"recommendedSubtasks": 5,
|
"recommendedSubtasks": 5,
|
||||||
"expansionPrompt": "Break down the --simple flag implementation into subtasks for CLI parser updates, update logic modification, timestamp formatting, display logic, documentation, and testing.",
|
"expansionPrompt": "Break down the --simple flag implementation into subtasks for CLI parser updates, update logic modification, timestamp formatting, display logic, documentation, and testing.",
|
||||||
"reasoning": "This is a focused feature addition involving CLI parsing, conditional logic, timestamp formatting, and display updates. The technical complexity is moderate, with a clear scope and manageable risk, suggesting a handful of subtasks.[1][3][4]"
|
"reasoning": "This is a focused feature addition involving CLI parsing, conditional logic, timestamp formatting, and display updates. The technical complexity is moderate, with a clear scope and manageable risk, suggesting a handful of subtasks.[1][3][4]"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ export async function generateXaiText({
|
|||||||
model: client(modelId), // Correct model invocation
|
model: client(modelId), // Correct model invocation
|
||||||
messages: messages,
|
messages: messages,
|
||||||
maxTokens: maxTokens,
|
maxTokens: maxTokens,
|
||||||
temperature: temperature,
|
temperature: temperature
|
||||||
// Add reasoningEffort or other xAI specific options via providerOptions if needed
|
|
||||||
providerOptions: { xai: { reasoningEffort: 'high' } }
|
|
||||||
});
|
});
|
||||||
log(
|
log(
|
||||||
'debug',
|
'debug',
|
||||||
|
|||||||
19
tasks/task_074.txt
Normal file
19
tasks/task_074.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Task ID: 74
|
||||||
|
# Title: PR Review: better-model-management
|
||||||
|
# Status: done
|
||||||
|
# Dependencies: None
|
||||||
|
# Priority: medium
|
||||||
|
# Description: will add subtasks
|
||||||
|
# Details:
|
||||||
|
|
||||||
|
|
||||||
|
# Test Strategy:
|
||||||
|
|
||||||
|
|
||||||
|
# Subtasks:
|
||||||
|
## 1. pull out logWrapper into utils [done]
|
||||||
|
### Dependencies: None
|
||||||
|
### Description: its being used a lot across direct functions and repeated right now
|
||||||
|
### Details:
|
||||||
|
|
||||||
|
|
||||||
@@ -3916,6 +3916,27 @@
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"priority": "medium",
|
"priority": "medium",
|
||||||
"subtasks": []
|
"subtasks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 74,
|
||||||
|
"title": "PR Review: better-model-management",
|
||||||
|
"description": "will add subtasks",
|
||||||
|
"details": "",
|
||||||
|
"testStrategy": "",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [],
|
||||||
|
"priority": "medium",
|
||||||
|
"subtasks": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "pull out logWrapper into utils",
|
||||||
|
"description": "its being used a lot across direct functions and repeated right now",
|
||||||
|
"details": "",
|
||||||
|
"status": "done",
|
||||||
|
"dependencies": [],
|
||||||
|
"parentTaskId": 74
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Exit immediately if a command exits with a non-zero status.
|
|
||||||
set -e
|
|
||||||
# Treat unset variables as an error when substituting.
|
# Treat unset variables as an error when substituting.
|
||||||
set -u
|
set -u
|
||||||
# Prevent errors in pipelines from being masked.
|
# Prevent errors in pipelines from being masked.
|
||||||
@@ -33,6 +31,11 @@ mkdir -p "$LOG_DIR"
|
|||||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||||
LOG_FILE="$LOG_DIR/e2e_run_$TIMESTAMP.log"
|
LOG_FILE="$LOG_DIR/e2e_run_$TIMESTAMP.log"
|
||||||
|
|
||||||
|
# Define and create the test run directory *before* the main pipe
|
||||||
|
mkdir -p "$BASE_TEST_DIR" # Ensure base exists first
|
||||||
|
TEST_RUN_DIR="$BASE_TEST_DIR/run_$TIMESTAMP"
|
||||||
|
mkdir -p "$TEST_RUN_DIR"
|
||||||
|
|
||||||
# Echo starting message to the original terminal BEFORE the main piped block
|
# Echo starting message to the original terminal BEFORE the main piped block
|
||||||
echo "Starting E2E test. Output will be shown here and saved to: $LOG_FILE"
|
echo "Starting E2E test. Output will be shown here and saved to: $LOG_FILE"
|
||||||
echo "Running from directory: $(pwd)"
|
echo "Running from directory: $(pwd)"
|
||||||
@@ -82,6 +85,125 @@ overall_start_time=$(date +%s)
|
|||||||
echo " STEP ${test_step_count}: [$(_get_elapsed_time_for_log)] $(date +"%Y-%m-%d %H:%M:%S") $1"
|
echo " STEP ${test_step_count}: [$(_get_elapsed_time_for_log)] $(date +"%Y-%m-%d %H:%M:%S") $1"
|
||||||
echo "============================================="
|
echo "============================================="
|
||||||
}
|
}
|
||||||
|
|
||||||
|
analyze_log_with_llm() {
|
||||||
|
local log_file="$1"
|
||||||
|
local provider_summary_log="provider_add_task_summary.log" # File summarizing provider test outcomes
|
||||||
|
local api_key=""
|
||||||
|
local api_endpoint="https://api.anthropic.com/v1/messages"
|
||||||
|
local api_key_name="CLAUDE_API_KEY"
|
||||||
|
|
||||||
|
echo "" # Add a newline before analysis starts
|
||||||
|
log_info "Attempting LLM analysis of log: $log_file"
|
||||||
|
|
||||||
|
# Check for jq and curl
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
log_error "LLM Analysis requires 'jq'. Skipping analysis."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
log_error "LLM Analysis requires 'curl'. Skipping analysis."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for API Key in the TEST_RUN_DIR/.env (copied earlier)
|
||||||
|
if [ -f ".env" ]; then
|
||||||
|
# Using grep and sed for better handling of potential quotes/spaces
|
||||||
|
api_key=$(grep "^${api_key_name}=" .env | sed -e "s/^${api_key_name}=//" -e 's/^[[:space:]"]*//' -e 's/[[:space:]"]*$//')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$api_key" ]; then
|
||||||
|
log_error "${api_key_name} not found or empty in .env file in the test run directory ($(pwd)/.env). Skipping LLM analysis."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$log_file" ]; then
|
||||||
|
log_error "Log file not found: $log_file. Skipping LLM analysis."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Reading log file content..."
|
||||||
|
local log_content
|
||||||
|
# Read entire file, handle potential errors
|
||||||
|
log_content=$(cat "$log_file") || {
|
||||||
|
log_error "Failed to read log file: $log_file. Skipping LLM analysis."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare the prompt
|
||||||
|
# Using printf with %s for the log content is generally safer than direct variable expansion
|
||||||
|
local prompt_template='Analyze the following E2E test log for the task-master tool. The log contains output from various '\''task-master'\'' commands executed sequentially.\n\nYour goal is to:\n1. Verify if the key E2E steps completed successfully based on the log messages (e.g., init, parse PRD, list tasks, analyze complexity, expand task, set status, manage models, add/remove dependencies, add/update/remove tasks/subtasks, generate files).\n2. **Specifically analyze the Multi-Provider Add-Task Test Sequence:**\n a. Identify which providers were tested for `add-task`. Look for log steps like "Testing Add-Task with Provider: ..." and the summary log `'"$provider_summary_log"'`.\n b. For each tested provider, determine if `add-task` succeeded or failed. Note the created task ID if successful.\n c. Review the corresponding `add_task_show_output_<provider>_id_<id>.log` file (if created) for each successful `add-task` execution.\n d. **Compare the quality and completeness** of the task generated by each successful provider based on their `show` output. Assign a score (e.g., 1-10, 10 being best) based on relevance to the prompt, detail level, and correctness.\n e. Note any providers where `add-task` failed or where the task ID could not be extracted.\n3. Identify any general explicit "[ERROR]" messages or stack traces throughout the *entire* log.\n4. Identify any potential warnings or unusual output that might indicate a problem even if not marked as an explicit error.\n5. Provide an overall assessment of the test run'\''s health based *only* on the log content.\n\nReturn your analysis **strictly** in the following JSON format. Do not include any text outside of the JSON structure:\n\n{\n "overall_status": "Success|Failure|Warning",\n "verified_steps": [ "Initialization", "PRD Parsing", /* ...other general steps observed... */ ],\n "provider_add_task_comparison": {\n "prompt_used": "... (extract from log if possible or state 'standard auth prompt') ...",\n "provider_results": {\n "anthropic": { "status": "Success|Failure|ID_Extraction_Failed|Set_Model_Failed", "task_id": "...", "score": "X/10 | N/A", "notes": "..." },\n "openai": { "status": "Success|Failure|...", "task_id": "...", "score": "X/10 | N/A", "notes": "..." },\n /* ... include all tested providers ... */\n },\n "comparison_summary": "Brief overall comparison of generated tasks..."\n },\n "detected_issues": [ { "severity": "Error|Warning|Anomaly", "description": "...", "log_context": "[Optional, short snippet from log near the issue]" } ],\n "llm_summary_points": [ "Overall summary point 1", "Provider comparison highlight", "Any major issues noted" ]\n}\n\nHere is the main log content:\n\n%s'
|
||||||
|
|
||||||
|
local full_prompt
|
||||||
|
printf -v full_prompt "$prompt_template" "$log_content"
|
||||||
|
|
||||||
|
# Construct the JSON payload for Claude Messages API
|
||||||
|
# Using jq for robust JSON construction
|
||||||
|
local payload
|
||||||
|
payload=$(jq -n --arg prompt "$full_prompt" '{
|
||||||
|
"model": "claude-3-7-sonnet-20250219",
|
||||||
|
"max_tokens": 10000,
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": $prompt}
|
||||||
|
],
|
||||||
|
"temperature": 0.0
|
||||||
|
}') || {
|
||||||
|
log_error "Failed to create JSON payload using jq."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info "Sending request to LLM API endpoint: $api_endpoint ..."
|
||||||
|
local response_raw response_http_code response_body
|
||||||
|
# Capture body and HTTP status code separately
|
||||||
|
response_raw=$(curl -s -w "\nHTTP_STATUS_CODE:%{http_code}" -X POST "$api_endpoint" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "x-api-key: $api_key" \
|
||||||
|
-H "anthropic-version: 2023-06-01" \
|
||||||
|
--data "$payload")
|
||||||
|
|
||||||
|
# Extract status code and body
|
||||||
|
response_http_code=$(echo "$response_raw" | grep '^HTTP_STATUS_CODE:' | sed 's/HTTP_STATUS_CODE://')
|
||||||
|
response_body=$(echo "$response_raw" | sed '$d') # Remove last line (status code)
|
||||||
|
|
||||||
|
if [ "$response_http_code" != "200" ]; then
|
||||||
|
log_error "LLM API call failed with HTTP status $response_http_code."
|
||||||
|
log_error "Response Body: $response_body"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$response_body" ]; then
|
||||||
|
log_error "LLM API call returned empty response body."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Received LLM response (HTTP 200). Parsing analysis JSON..."
|
||||||
|
|
||||||
|
# Extract the analysis JSON string from the API response (adjust jq path if needed)
|
||||||
|
local analysis_json_string
|
||||||
|
analysis_json_string=$(echo "$response_body" | jq -r '.content[0].text' 2>/dev/null) # Assumes Messages API structure
|
||||||
|
|
||||||
|
if [ -z "$analysis_json_string" ]; then
|
||||||
|
log_error "Failed to extract 'content[0].text' from LLM response JSON."
|
||||||
|
log_error "Full API response body: $response_body"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate and pretty-print the extracted JSON
|
||||||
|
if ! echo "$analysis_json_string" | jq -e . > /dev/null 2>&1; then
|
||||||
|
log_error "Extracted content from LLM is not valid JSON."
|
||||||
|
log_error "Raw extracted content: $analysis_json_string"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "LLM analysis completed successfully."
|
||||||
|
echo ""
|
||||||
|
echo "--- LLM Analysis ---"
|
||||||
|
# Pretty print the JSON analysis
|
||||||
|
echo "$analysis_json_string" | jq '.'
|
||||||
|
echo "--------------------"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
# --- Test Setup (Output to tee) ---
|
# --- Test Setup (Output to tee) ---
|
||||||
@@ -95,12 +217,9 @@ overall_start_time=$(date +%s)
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$BASE_TEST_DIR"
|
|
||||||
log_info "Ensured base test directory exists: $BASE_TEST_DIR"
|
log_info "Ensured base test directory exists: $BASE_TEST_DIR"
|
||||||
|
|
||||||
TEST_RUN_DIR="$BASE_TEST_DIR/run_$TIMESTAMP"
|
log_info "Using test run directory (created earlier): $TEST_RUN_DIR"
|
||||||
mkdir -p "$TEST_RUN_DIR"
|
|
||||||
log_info "Created test run directory: $TEST_RUN_DIR"
|
|
||||||
|
|
||||||
# Check if source .env file exists
|
# Check if source .env file exists
|
||||||
if [ ! -f "$MAIN_ENV_FILE" ]; then
|
if [ ! -f "$MAIN_ENV_FILE" ]; then
|
||||||
@@ -209,8 +328,103 @@ overall_start_time=$(date +%s)
|
|||||||
log_step "Checking final model configuration"
|
log_step "Checking final model configuration"
|
||||||
task-master models > models_final_config.log
|
task-master models > models_final_config.log
|
||||||
log_success "Final model config saved to models_final_config.log"
|
log_success "Final model config saved to models_final_config.log"
|
||||||
|
|
||||||
|
log_step "Resetting main model to default (Claude Sonnet) before provider tests"
|
||||||
|
task-master models --set-main claude-3-7-sonnet-20250219
|
||||||
|
log_success "Main model reset to claude-3-7-sonnet-20250219."
|
||||||
|
|
||||||
# === End Model Commands Test ===
|
# === End Model Commands Test ===
|
||||||
|
|
||||||
|
# === Multi-Provider Add-Task Test ===
|
||||||
|
log_step "Starting Multi-Provider Add-Task Test Sequence"
|
||||||
|
|
||||||
|
# Define providers, models, and flags
|
||||||
|
# Array order matters: providers[i] corresponds to models[i] and flags[i]
|
||||||
|
declare -a providers=("anthropic" "openai" "google" "perplexity" "xai" "openrouter")
|
||||||
|
declare -a models=(
|
||||||
|
"claude-3-7-sonnet-20250219"
|
||||||
|
"gpt-4o"
|
||||||
|
"gemini-2.5-pro-exp-03-25"
|
||||||
|
"sonar-pro"
|
||||||
|
"grok-3"
|
||||||
|
"anthropic/claude-3.7-sonnet" # OpenRouter uses Claude 3.7
|
||||||
|
)
|
||||||
|
# Flags: Add provider-specific flags here, e.g., --openrouter. Use empty string if none.
|
||||||
|
declare -a flags=("" "" "" "" "" "--openrouter")
|
||||||
|
|
||||||
|
# Consistent prompt for all providers
|
||||||
|
add_task_prompt="Create a task to implement user authentication using OAuth 2.0 with Google as the provider. Include steps for registering the app, handling the callback, and storing user sessions."
|
||||||
|
log_info "Using consistent prompt for add-task tests: \"$add_task_prompt\""
|
||||||
|
|
||||||
|
for i in "${!providers[@]}"; do
|
||||||
|
provider="${providers[$i]}"
|
||||||
|
model="${models[$i]}"
|
||||||
|
flag="${flags[$i]}"
|
||||||
|
|
||||||
|
log_step "Testing Add-Task with Provider: $provider (Model: $model)"
|
||||||
|
|
||||||
|
# 1. Set the main model for this provider
|
||||||
|
log_info "Setting main model to $model for $provider ${flag:+using flag $flag}..."
|
||||||
|
set_model_cmd="task-master models --set-main \"$model\" $flag"
|
||||||
|
echo "Executing: $set_model_cmd"
|
||||||
|
if eval $set_model_cmd; then
|
||||||
|
log_success "Successfully set main model for $provider."
|
||||||
|
else
|
||||||
|
log_error "Failed to set main model for $provider. Skipping add-task for this provider."
|
||||||
|
# Optionally save failure info here if needed for LLM analysis
|
||||||
|
echo "Provider $provider set-main FAILED" >> provider_add_task_summary.log
|
||||||
|
continue # Skip to the next provider
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Run add-task
|
||||||
|
log_info "Running add-task with prompt..."
|
||||||
|
add_task_output_file="add_task_raw_output_${provider}.log"
|
||||||
|
# Run add-task and capture ALL output (stdout & stderr) to a file AND a variable
|
||||||
|
add_task_cmd_output=$(task-master add-task --prompt "$add_task_prompt" 2>&1 | tee "$add_task_output_file")
|
||||||
|
add_task_exit_code=${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
# 3. Check for success and extract task ID
|
||||||
|
new_task_id=""
|
||||||
|
if [ $add_task_exit_code -eq 0 ] && echo "$add_task_cmd_output" | grep -q "Successfully added task with ID:"; then
|
||||||
|
# Attempt to extract the ID (adjust grep/sed/awk as needed based on actual output format)
|
||||||
|
new_task_id=$(echo "$add_task_cmd_output" | grep "Successfully added task with ID:" | sed 's/.*Successfully added task with ID: \([0-9.]\+\).*/\1/')
|
||||||
|
if [ -n "$new_task_id" ]; then
|
||||||
|
log_success "Add-task succeeded for $provider. New task ID: $new_task_id"
|
||||||
|
echo "Provider $provider add-task SUCCESS (ID: $new_task_id)" >> provider_add_task_summary.log
|
||||||
|
else
|
||||||
|
# Succeeded but couldn't parse ID - treat as warning/anomaly
|
||||||
|
log_error "Add-task command succeeded for $provider, but failed to extract task ID from output."
|
||||||
|
echo "Provider $provider add-task SUCCESS (ID extraction FAILED)" >> provider_add_task_summary.log
|
||||||
|
new_task_id="UNKNOWN_ID_EXTRACTION_FAILED"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "Add-task command failed for $provider (Exit Code: $add_task_exit_code). See $add_task_output_file for details."
|
||||||
|
echo "Provider $provider add-task FAILED (Exit Code: $add_task_exit_code)" >> provider_add_task_summary.log
|
||||||
|
new_task_id="FAILED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Run task show if ID was obtained (even if extraction failed, use placeholder)
|
||||||
|
if [ "$new_task_id" != "FAILED" ] && [ "$new_task_id" != "UNKNOWN_ID_EXTRACTION_FAILED" ]; then
|
||||||
|
log_info "Running task show for new task ID: $new_task_id"
|
||||||
|
show_output_file="add_task_show_output_${provider}_id_${new_task_id}.log"
|
||||||
|
if task-master show "$new_task_id" > "$show_output_file"; then
|
||||||
|
log_success "Task show output saved to $show_output_file"
|
||||||
|
else
|
||||||
|
log_error "task show command failed for ID $new_task_id. Check log."
|
||||||
|
# Still keep the file, it might contain error output
|
||||||
|
fi
|
||||||
|
elif [ "$new_task_id" == "UNKNOWN_ID_EXTRACTION_FAILED" ]; then
|
||||||
|
log_info "Skipping task show for $provider due to ID extraction failure."
|
||||||
|
else
|
||||||
|
log_info "Skipping task show for $provider due to add-task failure."
|
||||||
|
fi
|
||||||
|
|
||||||
|
done # End of provider loop
|
||||||
|
|
||||||
|
log_step "Finished Multi-Provider Add-Task Test Sequence"
|
||||||
|
echo "Provider add-task summary log available at: provider_add_task_summary.log"
|
||||||
|
# === End Multi-Provider Add-Task Test ===
|
||||||
|
|
||||||
log_step "Listing tasks again (final)"
|
log_step "Listing tasks again (final)"
|
||||||
task-master list --with-subtasks > task_list_final.log
|
task-master list --with-subtasks > task_list_final.log
|
||||||
log_success "Final task list saved to task_list_final.log"
|
log_success "Final task list saved to task_list_final.log"
|
||||||
@@ -386,4 +600,26 @@ else
|
|||||||
fi
|
fi
|
||||||
echo "-------------------------"
|
echo "-------------------------"
|
||||||
|
|
||||||
|
# --- Attempt LLM Analysis ---
|
||||||
|
echo "DEBUG: Entering LLM Analysis section..."
|
||||||
|
# Run this *after* the main execution block and tee pipe finish writing the log file
|
||||||
|
# It will read the completed log file and append its output to the terminal (and the log via subsequent writes if tee is still active, though it shouldn't be)
|
||||||
|
# Change directory back into the test run dir where .env is located
|
||||||
|
if [ -d "$TEST_RUN_DIR" ]; then
|
||||||
|
echo "DEBUG: Found TEST_RUN_DIR: $TEST_RUN_DIR. Attempting cd..."
|
||||||
|
cd "$TEST_RUN_DIR"
|
||||||
|
echo "DEBUG: Changed directory to $(pwd). Calling analyze_log_with_llm..."
|
||||||
|
analyze_log_with_llm "$LOG_FILE"
|
||||||
|
echo "DEBUG: analyze_log_with_llm function call finished."
|
||||||
|
# Optional: cd back again if needed, though script is ending
|
||||||
|
# cd "$ORIGINAL_DIR"
|
||||||
|
else
|
||||||
|
# Use log_error format even outside the pipe for consistency
|
||||||
|
current_time_for_error=$(date +%s)
|
||||||
|
elapsed_seconds_for_error=$((current_time_for_error - overall_start_time)) # Use overall start time
|
||||||
|
formatted_duration_for_error=$(_format_duration "$elapsed_seconds_for_error")
|
||||||
|
echo "[ERROR] [$formatted_duration_for_error] $(date +"%Y-%m-%d %H:%M:%S") Test run directory $TEST_RUN_DIR not found. Cannot perform LLM analysis." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "DEBUG: Reached end of script before final exit."
|
||||||
exit $EXIT_CODE # Exit with the status of the main script block
|
exit $EXIT_CODE # Exit with the status of the main script block
|
||||||
@@ -1,550 +0,0 @@
|
|||||||
import { jest } from '@jest/globals';
|
|
||||||
import path from 'path'; // Needed for mocking fs
|
|
||||||
|
|
||||||
// --- Mock Vercel AI SDK Modules ---
|
|
||||||
// Mock implementations - they just need to be callable and return a basic object
|
|
||||||
const mockCreateOpenAI = jest.fn(() => ({ provider: 'openai', type: 'mock' }));
|
|
||||||
const mockCreateAnthropic = jest.fn(() => ({
|
|
||||||
provider: 'anthropic',
|
|
||||||
type: 'mock'
|
|
||||||
}));
|
|
||||||
const mockCreateGoogle = jest.fn(() => ({ provider: 'google', type: 'mock' }));
|
|
||||||
const mockCreatePerplexity = jest.fn(() => ({
|
|
||||||
provider: 'perplexity',
|
|
||||||
type: 'mock'
|
|
||||||
}));
|
|
||||||
const mockCreateOllama = jest.fn(() => ({ provider: 'ollama', type: 'mock' }));
|
|
||||||
const mockCreateMistral = jest.fn(() => ({
|
|
||||||
provider: 'mistral',
|
|
||||||
type: 'mock'
|
|
||||||
}));
|
|
||||||
const mockCreateAzure = jest.fn(() => ({ provider: 'azure', type: 'mock' }));
|
|
||||||
const mockCreateXai = jest.fn(() => ({ provider: 'xai', type: 'mock' }));
|
|
||||||
// jest.unstable_mockModule('@ai-sdk/grok', () => ({
|
|
||||||
// createGrok: mockCreateGrok
|
|
||||||
// }));
|
|
||||||
const mockCreateOpenRouter = jest.fn(() => ({
|
|
||||||
provider: 'openrouter',
|
|
||||||
type: 'mock'
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.unstable_mockModule('@ai-sdk/openai', () => ({
|
|
||||||
createOpenAI: mockCreateOpenAI
|
|
||||||
}));
|
|
||||||
jest.unstable_mockModule('@ai-sdk/anthropic', () => ({
|
|
||||||
createAnthropic: mockCreateAnthropic
|
|
||||||
}));
|
|
||||||
jest.unstable_mockModule('@ai-sdk/google', () => ({
|
|
||||||
createGoogle: mockCreateGoogle
|
|
||||||
}));
|
|
||||||
jest.unstable_mockModule('@ai-sdk/perplexity', () => ({
|
|
||||||
createPerplexity: mockCreatePerplexity
|
|
||||||
}));
|
|
||||||
jest.unstable_mockModule('ollama-ai-provider', () => ({
|
|
||||||
createOllama: mockCreateOllama
|
|
||||||
}));
|
|
||||||
jest.unstable_mockModule('@ai-sdk/mistral', () => ({
|
|
||||||
createMistral: mockCreateMistral
|
|
||||||
}));
|
|
||||||
jest.unstable_mockModule('@ai-sdk/azure', () => ({
|
|
||||||
createAzure: mockCreateAzure
|
|
||||||
}));
|
|
||||||
jest.unstable_mockModule('@ai-sdk/xai', () => ({
|
|
||||||
createXai: mockCreateXai
|
|
||||||
}));
|
|
||||||
// jest.unstable_mockModule('@ai-sdk/openrouter', () => ({
|
|
||||||
// createOpenRouter: mockCreateOpenRouter
|
|
||||||
// }));
|
|
||||||
jest.unstable_mockModule('@openrouter/ai-sdk-provider', () => ({
|
|
||||||
createOpenRouter: mockCreateOpenRouter
|
|
||||||
}));
|
|
||||||
// TODO: Mock other providers (OpenRouter, Grok) when added
|
|
||||||
|
|
||||||
// --- Mock Config Manager ---
|
|
||||||
const mockGetProviderAndModelForRole = jest.fn();
|
|
||||||
const mockFindProjectRoot = jest.fn();
|
|
||||||
jest.unstable_mockModule('../../scripts/modules/config-manager.js', () => ({
|
|
||||||
getProviderAndModelForRole: mockGetProviderAndModelForRole,
|
|
||||||
findProjectRoot: mockFindProjectRoot
|
|
||||||
}));
|
|
||||||
|
|
||||||
// --- Mock File System (for supported-models.json loading) ---
|
|
||||||
const mockFsExistsSync = jest.fn();
|
|
||||||
const mockFsReadFileSync = jest.fn();
|
|
||||||
jest.unstable_mockModule('fs', () => ({
|
|
||||||
__esModule: true, // Important for ES modules with default exports
|
|
||||||
default: {
|
|
||||||
// Provide the default export expected by `import fs from 'fs'`
|
|
||||||
existsSync: mockFsExistsSync,
|
|
||||||
readFileSync: mockFsReadFileSync
|
|
||||||
},
|
|
||||||
// Also provide named exports if they were directly imported elsewhere, though not needed here
|
|
||||||
existsSync: mockFsExistsSync,
|
|
||||||
readFileSync: mockFsReadFileSync
|
|
||||||
}));
|
|
||||||
|
|
||||||
// --- Mock path (specifically path.join used for supported-models.json) ---
|
|
||||||
const mockPathJoin = jest.fn((...args) => args.join(path.sep)); // Simple mock
|
|
||||||
const actualPath = jest.requireActual('path'); // Get the actual path module
|
|
||||||
jest.unstable_mockModule('path', () => ({
|
|
||||||
__esModule: true, // Indicate ES module mock
|
|
||||||
default: {
|
|
||||||
// Provide the default export
|
|
||||||
...actualPath, // Spread actual functions
|
|
||||||
join: mockPathJoin // Override join
|
|
||||||
},
|
|
||||||
// Also provide named exports for consistency
|
|
||||||
...actualPath,
|
|
||||||
join: mockPathJoin
|
|
||||||
}));
|
|
||||||
|
|
||||||
// --- Define Mock Data ---
|
|
||||||
const mockSupportedModels = {
|
|
||||||
openai: [
|
|
||||||
{ id: 'gpt-4o', allowed_roles: ['main', 'fallback'] },
|
|
||||||
{ id: 'gpt-3.5-turbo', allowed_roles: ['main', 'fallback'] }
|
|
||||||
],
|
|
||||||
anthropic: [
|
|
||||||
{ id: 'claude-3.5-sonnet-20240620', allowed_roles: ['main'] },
|
|
||||||
{ id: 'claude-3-haiku-20240307', allowed_roles: ['fallback'] }
|
|
||||||
],
|
|
||||||
perplexity: [{ id: 'sonar-pro', allowed_roles: ['research'] }],
|
|
||||||
ollama: [{ id: 'llama3', allowed_roles: ['main', 'fallback'] }],
|
|
||||||
google: [{ id: 'gemini-pro', allowed_roles: ['main'] }],
|
|
||||||
mistral: [{ id: 'mistral-large-latest', allowed_roles: ['main'] }],
|
|
||||||
azure: [{ id: 'azure-gpt4o', allowed_roles: ['main'] }],
|
|
||||||
xai: [{ id: 'grok-basic', allowed_roles: ['main'] }],
|
|
||||||
openrouter: [{ id: 'openrouter-model', allowed_roles: ['main'] }]
|
|
||||||
// Add other providers as needed for tests
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Import the module AFTER mocks ---
|
|
||||||
const { getClient, clearClientCache, _resetSupportedModelsCache } =
|
|
||||||
await import('../../scripts/modules/ai-client-factory.js');
|
|
||||||
|
|
||||||
describe('AI Client Factory (Role-Based)', () => {
|
|
||||||
const OLD_ENV = process.env;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// Reset state before each test
|
|
||||||
clearClientCache(); // Use the correct function name
|
|
||||||
_resetSupportedModelsCache(); // Reset the models cache
|
|
||||||
mockFsExistsSync.mockClear();
|
|
||||||
mockFsReadFileSync.mockClear();
|
|
||||||
mockGetProviderAndModelForRole.mockClear(); // Reset this mock too
|
|
||||||
|
|
||||||
// Reset environment to avoid test pollution
|
|
||||||
process.env = { ...OLD_ENV };
|
|
||||||
|
|
||||||
// Default mock implementations (can be overridden)
|
|
||||||
mockFindProjectRoot.mockReturnValue('/fake/project/root');
|
|
||||||
mockPathJoin.mockImplementation((...args) => args.join(actualPath.sep)); // Use actualPath.sep
|
|
||||||
|
|
||||||
// Default FS mocks for model/config loading
|
|
||||||
mockFsExistsSync.mockImplementation((filePath) => {
|
|
||||||
// Default to true for the files we expect to load
|
|
||||||
if (filePath.endsWith('supported-models.json')) return true;
|
|
||||||
// Add other expected files if necessary
|
|
||||||
return false; // Default to false for others
|
|
||||||
});
|
|
||||||
mockFsReadFileSync.mockImplementation((filePath) => {
|
|
||||||
if (filePath.endsWith('supported-models.json')) {
|
|
||||||
return JSON.stringify(mockSupportedModels);
|
|
||||||
}
|
|
||||||
// Throw if an unexpected file is read
|
|
||||||
throw new Error(`Unexpected readFileSync call in test: ${filePath}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Default config mock
|
|
||||||
mockGetProviderAndModelForRole.mockImplementation((role) => {
|
|
||||||
if (role === 'main') return { provider: 'openai', modelId: 'gpt-4o' };
|
|
||||||
if (role === 'research')
|
|
||||||
return { provider: 'perplexity', modelId: 'sonar-pro' };
|
|
||||||
if (role === 'fallback')
|
|
||||||
return { provider: 'anthropic', modelId: 'claude-3-haiku-20240307' };
|
|
||||||
return {}; // Default empty for unconfigured roles
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set default required env vars (can be overridden in tests)
|
|
||||||
process.env.OPENAI_API_KEY = 'test-openai-key';
|
|
||||||
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
|
|
||||||
process.env.PERPLEXITY_API_KEY = 'test-perplexity-key';
|
|
||||||
process.env.GOOGLE_API_KEY = 'test-google-key';
|
|
||||||
process.env.MISTRAL_API_KEY = 'test-mistral-key';
|
|
||||||
process.env.AZURE_OPENAI_API_KEY = 'test-azure-key';
|
|
||||||
process.env.AZURE_OPENAI_ENDPOINT = 'test-azure-endpoint';
|
|
||||||
process.env.XAI_API_KEY = 'test-xai-key';
|
|
||||||
process.env.OPENROUTER_API_KEY = 'test-openrouter-key';
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
process.env = OLD_ENV;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw error if role is missing', () => {
|
|
||||||
expect(() => getClient()).toThrow(
|
|
||||||
"Client role ('main', 'research', 'fallback') must be specified."
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw error if config manager fails to get role config', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockImplementation((role) => {
|
|
||||||
if (role === 'main') throw new Error('Config file not found');
|
|
||||||
});
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
"Failed to get configuration for role 'main': Config file not found"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw error if config manager returns undefined provider/model', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({}); // Empty object
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
"Could not determine provider or modelId for role 'main'"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw error if configured model is not supported for the role', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'anthropic',
|
|
||||||
modelId: 'claude-3.5-sonnet-20240620' // Only allowed for 'main' in mock data
|
|
||||||
});
|
|
||||||
expect(() => getClient('research')).toThrow(
|
|
||||||
/Model 'claude-3.5-sonnet-20240620' from provider 'anthropic' is either not supported or not allowed for the 'research' role/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw error if configured model is not found in supported list', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'openai',
|
|
||||||
modelId: 'gpt-unknown'
|
|
||||||
});
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Model 'gpt-unknown' from provider 'openai' is either not supported or not allowed for the 'main' role/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw error if configured provider is not found in supported list', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'unknown-provider',
|
|
||||||
modelId: 'some-model'
|
|
||||||
});
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Model 'some-model' from provider 'unknown-provider' is either not supported or not allowed for the 'main' role/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should skip model validation if supported-models.json is not found', () => {
|
|
||||||
mockFsExistsSync.mockReturnValue(false); // Simulate file not found
|
|
||||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); // Suppress warning
|
|
||||||
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'openai',
|
|
||||||
modelId: 'gpt-any' // Doesn't matter, validation skipped
|
|
||||||
});
|
|
||||||
process.env.OPENAI_API_KEY = 'test-key';
|
|
||||||
|
|
||||||
expect(() => getClient('main')).not.toThrow(); // Should not throw validation error
|
|
||||||
expect(mockCreateOpenAI).toHaveBeenCalled();
|
|
||||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
||||||
expect.stringContaining('Skipping model validation')
|
|
||||||
);
|
|
||||||
consoleWarnSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw environment validation error', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'openai',
|
|
||||||
modelId: 'gpt-4o'
|
|
||||||
});
|
|
||||||
delete process.env.OPENAI_API_KEY; // Trigger missing env var
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
// Expect the original error message from validateEnvironment
|
|
||||||
/Missing environment variables for provider 'openai': OPENAI_API_KEY\. Please check your \.env file or session configuration\./
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should successfully create client using config and process.env', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'openai',
|
|
||||||
modelId: 'gpt-4o'
|
|
||||||
});
|
|
||||||
process.env.OPENAI_API_KEY = 'env-key';
|
|
||||||
|
|
||||||
const client = getClient('main');
|
|
||||||
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockGetProviderAndModelForRole).toHaveBeenCalledWith('main');
|
|
||||||
expect(mockCreateOpenAI).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ apiKey: 'env-key', model: 'gpt-4o' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should successfully create client using config and session.env', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'anthropic',
|
|
||||||
modelId: 'claude-3.5-sonnet-20240620'
|
|
||||||
});
|
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
|
||||||
const session = { env: { ANTHROPIC_API_KEY: 'session-key' } };
|
|
||||||
|
|
||||||
const client = getClient('main', session);
|
|
||||||
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockGetProviderAndModelForRole).toHaveBeenCalledWith('main');
|
|
||||||
expect(mockCreateAnthropic).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
apiKey: 'session-key',
|
|
||||||
model: 'claude-3.5-sonnet-20240620'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should use overrideOptions when provided', () => {
|
|
||||||
process.env.PERPLEXITY_API_KEY = 'env-key';
|
|
||||||
const override = { provider: 'perplexity', modelId: 'sonar-pro' };
|
|
||||||
|
|
||||||
const client = getClient('research', null, override);
|
|
||||||
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockGetProviderAndModelForRole).not.toHaveBeenCalled(); // Config shouldn't be called
|
|
||||||
expect(mockCreatePerplexity).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ apiKey: 'env-key', model: 'sonar-pro' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw validation error even with override if role is disallowed', () => {
|
|
||||||
process.env.OPENAI_API_KEY = 'env-key';
|
|
||||||
// gpt-4o is not allowed for 'research' in mock data
|
|
||||||
const override = { provider: 'openai', modelId: 'gpt-4o' };
|
|
||||||
|
|
||||||
expect(() => getClient('research', null, override)).toThrow(
|
|
||||||
/Model 'gpt-4o' from provider 'openai' is either not supported or not allowed for the 'research' role/
|
|
||||||
);
|
|
||||||
expect(mockGetProviderAndModelForRole).not.toHaveBeenCalled();
|
|
||||||
expect(mockCreateOpenAI).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Caching Behavior (Role-Based)', () => {
|
|
||||||
test('should return cached client instance for the same provider/model derived from role', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'openai',
|
|
||||||
modelId: 'gpt-4o'
|
|
||||||
});
|
|
||||||
process.env.OPENAI_API_KEY = 'test-key';
|
|
||||||
|
|
||||||
const client1 = getClient('main');
|
|
||||||
const client2 = getClient('main'); // Same role, same config result
|
|
||||||
|
|
||||||
expect(client1).toBe(client2); // Should be the exact same instance
|
|
||||||
expect(mockGetProviderAndModelForRole).toHaveBeenCalledTimes(2); // Config lookup happens each time
|
|
||||||
expect(mockCreateOpenAI).toHaveBeenCalledTimes(1); // Instance created only once
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return different client instances for different roles if config differs', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockImplementation((role) => {
|
|
||||||
if (role === 'main') return { provider: 'openai', modelId: 'gpt-4o' };
|
|
||||||
if (role === 'research')
|
|
||||||
return { provider: 'perplexity', modelId: 'sonar-pro' };
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
process.env.OPENAI_API_KEY = 'test-key-1';
|
|
||||||
process.env.PERPLEXITY_API_KEY = 'test-key-2';
|
|
||||||
|
|
||||||
const client1 = getClient('main');
|
|
||||||
const client2 = getClient('research');
|
|
||||||
|
|
||||||
expect(client1).not.toBe(client2);
|
|
||||||
expect(mockCreateOpenAI).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockCreatePerplexity).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return same client instance if different roles resolve to same provider/model', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockImplementation((role) => {
|
|
||||||
// Both roles point to the same model
|
|
||||||
return { provider: 'openai', modelId: 'gpt-4o' };
|
|
||||||
});
|
|
||||||
process.env.OPENAI_API_KEY = 'test-key';
|
|
||||||
|
|
||||||
const client1 = getClient('main');
|
|
||||||
const client2 = getClient('fallback'); // Different role, same config result
|
|
||||||
|
|
||||||
expect(client1).toBe(client2); // Should be the exact same instance
|
|
||||||
expect(mockCreateOpenAI).toHaveBeenCalledTimes(1); // Instance created only once
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add tests for specific providers
|
|
||||||
describe('Specific Provider Instantiation', () => {
|
|
||||||
test('should successfully create Google client with GOOGLE_API_KEY', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'google',
|
|
||||||
modelId: 'gemini-pro'
|
|
||||||
}); // Assume gemini-pro is supported
|
|
||||||
process.env.GOOGLE_API_KEY = 'test-google-key';
|
|
||||||
const client = getClient('main');
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockCreateGoogle).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ apiKey: 'test-google-key' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw environment error if GOOGLE_API_KEY is missing', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'google',
|
|
||||||
modelId: 'gemini-pro'
|
|
||||||
});
|
|
||||||
delete process.env.GOOGLE_API_KEY;
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Missing environment variables for provider 'google': GOOGLE_API_KEY/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should successfully create Ollama client with OLLAMA_BASE_URL', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'ollama',
|
|
||||||
modelId: 'llama3'
|
|
||||||
}); // Use supported llama3
|
|
||||||
process.env.OLLAMA_BASE_URL = 'http://test-ollama:11434';
|
|
||||||
const client = getClient('main');
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockCreateOllama).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ baseURL: 'http://test-ollama:11434' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw environment error if OLLAMA_BASE_URL is missing', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'ollama',
|
|
||||||
modelId: 'llama3'
|
|
||||||
});
|
|
||||||
delete process.env.OLLAMA_BASE_URL;
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Missing environment variables for provider 'ollama': OLLAMA_BASE_URL/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should successfully create Mistral client with MISTRAL_API_KEY', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'mistral',
|
|
||||||
modelId: 'mistral-large-latest'
|
|
||||||
}); // Assume supported
|
|
||||||
process.env.MISTRAL_API_KEY = 'test-mistral-key';
|
|
||||||
const client = getClient('main');
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockCreateMistral).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ apiKey: 'test-mistral-key' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw environment error if MISTRAL_API_KEY is missing', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'mistral',
|
|
||||||
modelId: 'mistral-large-latest'
|
|
||||||
});
|
|
||||||
delete process.env.MISTRAL_API_KEY;
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Missing environment variables for provider 'mistral': MISTRAL_API_KEY/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should successfully create Azure client with AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'azure',
|
|
||||||
modelId: 'azure-gpt4o'
|
|
||||||
}); // Assume supported
|
|
||||||
process.env.AZURE_OPENAI_API_KEY = 'test-azure-key';
|
|
||||||
process.env.AZURE_OPENAI_ENDPOINT = 'https://test-azure.openai.azure.com';
|
|
||||||
const client = getClient('main');
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockCreateAzure).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
apiKey: 'test-azure-key',
|
|
||||||
endpoint: 'https://test-azure.openai.azure.com'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw environment error if AZURE_OPENAI_API_KEY or AZURE_OPENAI_ENDPOINT is missing', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'azure',
|
|
||||||
modelId: 'azure-gpt4o'
|
|
||||||
});
|
|
||||||
process.env.AZURE_OPENAI_API_KEY = 'test-azure-key';
|
|
||||||
delete process.env.AZURE_OPENAI_ENDPOINT;
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Missing environment variables for provider 'azure': AZURE_OPENAI_ENDPOINT/
|
|
||||||
);
|
|
||||||
|
|
||||||
process.env.AZURE_OPENAI_ENDPOINT = 'https://test-azure.openai.azure.com';
|
|
||||||
delete process.env.AZURE_OPENAI_API_KEY;
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Missing environment variables for provider 'azure': AZURE_OPENAI_API_KEY/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should successfully create xAI (Grok) client with XAI_API_KEY', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'xai',
|
|
||||||
modelId: 'grok-basic'
|
|
||||||
});
|
|
||||||
process.env.XAI_API_KEY = 'test-xai-key-specific';
|
|
||||||
const client = getClient('main');
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockCreateXai).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ apiKey: 'test-xai-key-specific' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw environment error if XAI_API_KEY is missing', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'xai',
|
|
||||||
modelId: 'grok-basic'
|
|
||||||
});
|
|
||||||
delete process.env.XAI_API_KEY;
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Missing environment variables for provider 'xai': XAI_API_KEY/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should successfully create OpenRouter client with OPENROUTER_API_KEY', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'openrouter',
|
|
||||||
modelId: 'openrouter-model'
|
|
||||||
});
|
|
||||||
process.env.OPENROUTER_API_KEY = 'test-openrouter-key-specific';
|
|
||||||
const client = getClient('main');
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockCreateOpenRouter).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ apiKey: 'test-openrouter-key-specific' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw environment error if OPENROUTER_API_KEY is missing', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'openrouter',
|
|
||||||
modelId: 'openrouter-model'
|
|
||||||
});
|
|
||||||
delete process.env.OPENROUTER_API_KEY;
|
|
||||||
expect(() => getClient('main')).toThrow(
|
|
||||||
/Missing environment variables for provider 'openrouter': OPENROUTER_API_KEY/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Environment Variable Precedence', () => {
|
|
||||||
test('should prioritize process.env over session.env for API keys', () => {
|
|
||||||
mockGetProviderAndModelForRole.mockReturnValue({
|
|
||||||
provider: 'openai',
|
|
||||||
modelId: 'gpt-4o'
|
|
||||||
});
|
|
||||||
process.env.OPENAI_API_KEY = 'process-env-key'; // This should be used
|
|
||||||
const session = { env: { OPENAI_API_KEY: 'session-env-key' } };
|
|
||||||
|
|
||||||
const client = getClient('main', session);
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockCreateOpenAI).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ apiKey: 'process-env-key', model: 'gpt-4o' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,350 +0,0 @@
|
|||||||
/**
|
|
||||||
* ai-client-utils.test.js
|
|
||||||
* Tests for AI client utility functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { jest } from '@jest/globals';
|
|
||||||
import {
|
|
||||||
getAnthropicClientForMCP,
|
|
||||||
getPerplexityClientForMCP,
|
|
||||||
getModelConfig,
|
|
||||||
getBestAvailableAIModel,
|
|
||||||
handleClaudeError
|
|
||||||
} from '../../mcp-server/src/core/utils/ai-client-utils.js';
|
|
||||||
|
|
||||||
// Mock the Anthropic constructor
|
|
||||||
jest.mock('@anthropic-ai/sdk', () => {
|
|
||||||
return {
|
|
||||||
Anthropic: jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
messages: {
|
|
||||||
create: jest.fn().mockResolvedValue({})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mock the OpenAI dynamic import
|
|
||||||
jest.mock('openai', () => {
|
|
||||||
return {
|
|
||||||
default: jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
chat: {
|
|
||||||
completions: {
|
|
||||||
create: jest.fn().mockResolvedValue({})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AI Client Utilities', () => {
|
|
||||||
const originalEnv = process.env;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// Reset process.env before each test
|
|
||||||
process.env = { ...originalEnv };
|
|
||||||
|
|
||||||
// Clear all mocks
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
// Restore process.env
|
|
||||||
process.env = originalEnv;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getAnthropicClientForMCP', () => {
|
|
||||||
it('should initialize client with API key from session', () => {
|
|
||||||
// Setup
|
|
||||||
const session = {
|
|
||||||
env: {
|
|
||||||
ANTHROPIC_API_KEY: 'test-key-from-session'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockLog = { error: jest.fn() };
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const client = getAnthropicClientForMCP(session, mockLog);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(client.messages.create).toBeDefined();
|
|
||||||
expect(mockLog.error).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fall back to process.env when session key is missing', () => {
|
|
||||||
// Setup
|
|
||||||
process.env.ANTHROPIC_API_KEY = 'test-key-from-env';
|
|
||||||
const session = { env: {} };
|
|
||||||
const mockLog = { error: jest.fn() };
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const client = getAnthropicClientForMCP(session, mockLog);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(mockLog.error).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw error when API key is missing', () => {
|
|
||||||
// Setup
|
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
|
||||||
const session = { env: {} };
|
|
||||||
const mockLog = { error: jest.fn() };
|
|
||||||
|
|
||||||
// Execute & Verify
|
|
||||||
expect(() => getAnthropicClientForMCP(session, mockLog)).toThrow();
|
|
||||||
expect(mockLog.error).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getPerplexityClientForMCP', () => {
|
|
||||||
it('should initialize client with API key from session', async () => {
|
|
||||||
// Setup
|
|
||||||
const session = {
|
|
||||||
env: {
|
|
||||||
PERPLEXITY_API_KEY: 'test-perplexity-key'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockLog = { error: jest.fn() };
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const client = await getPerplexityClientForMCP(session, mockLog);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(client).toBeDefined();
|
|
||||||
expect(client.chat.completions.create).toBeDefined();
|
|
||||||
expect(mockLog.error).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw error when API key is missing', async () => {
|
|
||||||
// Setup
|
|
||||||
delete process.env.PERPLEXITY_API_KEY;
|
|
||||||
const session = { env: {} };
|
|
||||||
const mockLog = { error: jest.fn() };
|
|
||||||
|
|
||||||
// Execute & Verify
|
|
||||||
await expect(
|
|
||||||
getPerplexityClientForMCP(session, mockLog)
|
|
||||||
).rejects.toThrow();
|
|
||||||
expect(mockLog.error).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getModelConfig', () => {
|
|
||||||
it('should get model config from session', () => {
|
|
||||||
// Setup
|
|
||||||
const session = {
|
|
||||||
env: {
|
|
||||||
MODEL: 'claude-3-opus',
|
|
||||||
MAX_TOKENS: '8000',
|
|
||||||
TEMPERATURE: '0.5'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const config = getModelConfig(session);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(config).toEqual({
|
|
||||||
model: 'claude-3-opus',
|
|
||||||
maxTokens: 8000,
|
|
||||||
temperature: 0.5
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use default values when session values are missing', () => {
|
|
||||||
// Setup
|
|
||||||
const session = {
|
|
||||||
env: {
|
|
||||||
// No values
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const config = getModelConfig(session);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(config).toEqual({
|
|
||||||
model: 'claude-3-7-sonnet-20250219',
|
|
||||||
maxTokens: 64000,
|
|
||||||
temperature: 0.2
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow custom defaults', () => {
|
|
||||||
// Setup
|
|
||||||
const session = { env: {} };
|
|
||||||
const customDefaults = {
|
|
||||||
model: 'custom-model',
|
|
||||||
maxTokens: 2000,
|
|
||||||
temperature: 0.3
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const config = getModelConfig(session, customDefaults);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(config).toEqual(customDefaults);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getBestAvailableAIModel', () => {
|
|
||||||
it('should return Perplexity for research when available', async () => {
|
|
||||||
// Setup
|
|
||||||
const session = {
|
|
||||||
env: {
|
|
||||||
PERPLEXITY_API_KEY: 'test-perplexity-key',
|
|
||||||
ANTHROPIC_API_KEY: 'test-anthropic-key'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockLog = { warn: jest.fn(), info: jest.fn(), error: jest.fn() };
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const result = await getBestAvailableAIModel(
|
|
||||||
session,
|
|
||||||
{ requiresResearch: true },
|
|
||||||
mockLog
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(result.type).toBe('perplexity');
|
|
||||||
expect(result.client).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return Claude when Perplexity is not available and Claude is not overloaded', async () => {
|
|
||||||
// Setup
|
|
||||||
const originalPerplexityKey = process.env.PERPLEXITY_API_KEY;
|
|
||||||
delete process.env.PERPLEXITY_API_KEY; // Make sure Perplexity is not available in process.env
|
|
||||||
|
|
||||||
const session = {
|
|
||||||
env: {
|
|
||||||
ANTHROPIC_API_KEY: 'test-anthropic-key'
|
|
||||||
// Purposely not including PERPLEXITY_API_KEY
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockLog = { warn: jest.fn(), info: jest.fn(), error: jest.fn() };
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Execute
|
|
||||||
const result = await getBestAvailableAIModel(
|
|
||||||
session,
|
|
||||||
{ requiresResearch: true },
|
|
||||||
mockLog
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
// In our implementation, we prioritize research capability through Perplexity
|
|
||||||
// so if we're testing research but Perplexity isn't available, Claude is used
|
|
||||||
expect(result.type).toBe('claude');
|
|
||||||
expect(result.client).toBeDefined();
|
|
||||||
expect(mockLog.warn).toHaveBeenCalled(); // Warning about using Claude instead of Perplexity
|
|
||||||
} finally {
|
|
||||||
// Restore original env variables
|
|
||||||
if (originalPerplexityKey) {
|
|
||||||
process.env.PERPLEXITY_API_KEY = originalPerplexityKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fall back to Claude as last resort when overloaded', async () => {
|
|
||||||
// Setup
|
|
||||||
const session = {
|
|
||||||
env: {
|
|
||||||
ANTHROPIC_API_KEY: 'test-anthropic-key'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockLog = { warn: jest.fn(), info: jest.fn(), error: jest.fn() };
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const result = await getBestAvailableAIModel(
|
|
||||||
session,
|
|
||||||
{ claudeOverloaded: true },
|
|
||||||
mockLog
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(result.type).toBe('claude');
|
|
||||||
expect(result.client).toBeDefined();
|
|
||||||
expect(mockLog.warn).toHaveBeenCalled(); // Warning about Claude overloaded
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw error when no models are available', async () => {
|
|
||||||
// Setup
|
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
|
||||||
delete process.env.PERPLEXITY_API_KEY;
|
|
||||||
const session = { env: {} };
|
|
||||||
const mockLog = { warn: jest.fn(), info: jest.fn(), error: jest.fn() };
|
|
||||||
|
|
||||||
// Execute & Verify
|
|
||||||
await expect(
|
|
||||||
getBestAvailableAIModel(session, {}, mockLog)
|
|
||||||
).rejects.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleClaudeError', () => {
|
|
||||||
it('should handle overloaded error', () => {
|
|
||||||
// Setup
|
|
||||||
const error = {
|
|
||||||
type: 'error',
|
|
||||||
error: {
|
|
||||||
type: 'overloaded_error',
|
|
||||||
message: 'Claude is overloaded'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const message = handleClaudeError(error);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(message).toContain('overloaded');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle rate limit error', () => {
|
|
||||||
// Setup
|
|
||||||
const error = {
|
|
||||||
type: 'error',
|
|
||||||
error: {
|
|
||||||
type: 'rate_limit_error',
|
|
||||||
message: 'Rate limit exceeded'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const message = handleClaudeError(error);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(message).toContain('rate limit');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle timeout error', () => {
|
|
||||||
// Setup
|
|
||||||
const error = {
|
|
||||||
message: 'Request timed out after 60 seconds'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const message = handleClaudeError(error);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(message).toContain('timed out');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle generic errors', () => {
|
|
||||||
// Setup
|
|
||||||
const error = {
|
|
||||||
message: 'Something went wrong'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
const message = handleClaudeError(error);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
expect(message).toContain('Error communicating with Claude');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
/**
|
|
||||||
* AI Services module tests
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { jest } from '@jest/globals';
|
|
||||||
import { parseSubtasksFromText } from '../../scripts/modules/ai-services.js';
|
|
||||||
|
|
||||||
// Create a mock log function we can check later
|
|
||||||
const mockLog = jest.fn();
|
|
||||||
|
|
||||||
// Mock dependencies
|
|
||||||
jest.mock('@anthropic-ai/sdk', () => {
|
|
||||||
const mockCreate = jest.fn().mockResolvedValue({
|
|
||||||
content: [{ text: 'AI response' }]
|
|
||||||
});
|
|
||||||
const mockAnthropicInstance = {
|
|
||||||
messages: {
|
|
||||||
create: mockCreate
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockAnthropicConstructor = jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => mockAnthropicInstance);
|
|
||||||
return {
|
|
||||||
Anthropic: mockAnthropicConstructor
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use jest.fn() directly for OpenAI mock
|
|
||||||
const mockOpenAIInstance = {
|
|
||||||
chat: {
|
|
||||||
completions: {
|
|
||||||
create: jest.fn().mockResolvedValue({
|
|
||||||
choices: [{ message: { content: 'Perplexity response' } }]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mockOpenAI = jest.fn().mockImplementation(() => mockOpenAIInstance);
|
|
||||||
|
|
||||||
jest.mock('openai', () => {
|
|
||||||
return { default: mockOpenAI };
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('dotenv', () => ({
|
|
||||||
config: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../scripts/modules/utils.js', () => ({
|
|
||||||
CONFIG: {
|
|
||||||
model: 'claude-3-sonnet-20240229',
|
|
||||||
temperature: 0.7,
|
|
||||||
maxTokens: 4000
|
|
||||||
},
|
|
||||||
log: mockLog,
|
|
||||||
sanitizePrompt: jest.fn((text) => text)
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../scripts/modules/ui.js', () => ({
|
|
||||||
startLoadingIndicator: jest.fn().mockReturnValue('mockLoader'),
|
|
||||||
stopLoadingIndicator: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock anthropic global object
|
|
||||||
global.anthropic = {
|
|
||||||
messages: {
|
|
||||||
create: jest.fn().mockResolvedValue({
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
text: '[{"id": 1, "title": "Test", "description": "Test", "dependencies": [], "details": "Test"}]'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock process.env
|
|
||||||
const originalEnv = process.env;
|
|
||||||
|
|
||||||
// Import Anthropic for testing constructor arguments
|
|
||||||
import { Anthropic } from '@anthropic-ai/sdk';
|
|
||||||
|
|
||||||
describe('AI Services Module', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
process.env = { ...originalEnv };
|
|
||||||
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
|
|
||||||
process.env.PERPLEXITY_API_KEY = 'test-perplexity-key';
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
process.env = originalEnv;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('parseSubtasksFromText function', () => {
|
|
||||||
test('should parse subtasks from JSON text', () => {
|
|
||||||
const text = `Here's your list of subtasks:
|
|
||||||
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"title": "Implement database schema",
|
|
||||||
"description": "Design and implement the database schema for user data",
|
|
||||||
"dependencies": [],
|
|
||||||
"details": "Create tables for users, preferences, and settings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"title": "Create API endpoints",
|
|
||||||
"description": "Develop RESTful API endpoints for user operations",
|
|
||||||
"dependencies": [],
|
|
||||||
"details": "Implement CRUD operations for user management"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
These subtasks will help you implement the parent task efficiently.`;
|
|
||||||
|
|
||||||
const result = parseSubtasksFromText(text, 1, 2, 5);
|
|
||||||
|
|
||||||
expect(result).toHaveLength(2);
|
|
||||||
expect(result[0]).toEqual({
|
|
||||||
id: 1,
|
|
||||||
title: 'Implement database schema',
|
|
||||||
description: 'Design and implement the database schema for user data',
|
|
||||||
status: 'pending',
|
|
||||||
dependencies: [],
|
|
||||||
details: 'Create tables for users, preferences, and settings',
|
|
||||||
parentTaskId: 5
|
|
||||||
});
|
|
||||||
expect(result[1]).toEqual({
|
|
||||||
id: 2,
|
|
||||||
title: 'Create API endpoints',
|
|
||||||
description: 'Develop RESTful API endpoints for user operations',
|
|
||||||
status: 'pending',
|
|
||||||
dependencies: [],
|
|
||||||
details: 'Implement CRUD operations for user management',
|
|
||||||
parentTaskId: 5
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle subtasks with dependencies', () => {
|
|
||||||
const text = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"title": "Setup React environment",
|
|
||||||
"description": "Initialize React app with necessary dependencies",
|
|
||||||
"dependencies": [],
|
|
||||||
"details": "Use Create React App or Vite to set up a new project"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"title": "Create component structure",
|
|
||||||
"description": "Design and implement component hierarchy",
|
|
||||||
"dependencies": [1],
|
|
||||||
"details": "Organize components by feature and reusability"
|
|
||||||
}
|
|
||||||
]`;
|
|
||||||
|
|
||||||
const result = parseSubtasksFromText(text, 1, 2, 5);
|
|
||||||
|
|
||||||
expect(result).toHaveLength(2);
|
|
||||||
expect(result[0].dependencies).toEqual([]);
|
|
||||||
expect(result[1].dependencies).toEqual([1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle complex dependency lists', () => {
|
|
||||||
const text = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"title": "Setup database",
|
|
||||||
"description": "Initialize database structure",
|
|
||||||
"dependencies": [],
|
|
||||||
"details": "Set up PostgreSQL database"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"title": "Create models",
|
|
||||||
"description": "Implement data models",
|
|
||||||
"dependencies": [1],
|
|
||||||
"details": "Define Prisma models"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"title": "Implement controllers",
|
|
||||||
"description": "Create API controllers",
|
|
||||||
"dependencies": [1, 2],
|
|
||||||
"details": "Build controllers for all endpoints"
|
|
||||||
}
|
|
||||||
]`;
|
|
||||||
|
|
||||||
const result = parseSubtasksFromText(text, 1, 3, 5);
|
|
||||||
|
|
||||||
expect(result).toHaveLength(3);
|
|
||||||
expect(result[2].dependencies).toEqual([1, 2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw an error for empty text', () => {
|
|
||||||
const emptyText = '';
|
|
||||||
|
|
||||||
expect(() => parseSubtasksFromText(emptyText, 1, 2, 5)).toThrow(
|
|
||||||
'Empty text provided, cannot parse subtasks'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should normalize subtask IDs', () => {
|
|
||||||
const text = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"title": "First task with incorrect ID",
|
|
||||||
"description": "First description",
|
|
||||||
"dependencies": [],
|
|
||||||
"details": "First details"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 20,
|
|
||||||
"title": "Second task with incorrect ID",
|
|
||||||
"description": "Second description",
|
|
||||||
"dependencies": [],
|
|
||||||
"details": "Second details"
|
|
||||||
}
|
|
||||||
]`;
|
|
||||||
|
|
||||||
const result = parseSubtasksFromText(text, 1, 2, 5);
|
|
||||||
|
|
||||||
expect(result).toHaveLength(2);
|
|
||||||
expect(result[0].id).toBe(1); // Should normalize to starting ID
|
|
||||||
expect(result[1].id).toBe(2); // Should normalize to starting ID + 1
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should convert string dependencies to numbers', () => {
|
|
||||||
const text = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"title": "First task",
|
|
||||||
"description": "First description",
|
|
||||||
"dependencies": [],
|
|
||||||
"details": "First details"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"title": "Second task",
|
|
||||||
"description": "Second description",
|
|
||||||
"dependencies": ["1"],
|
|
||||||
"details": "Second details"
|
|
||||||
}
|
|
||||||
]`;
|
|
||||||
|
|
||||||
const result = parseSubtasksFromText(text, 1, 2, 5);
|
|
||||||
|
|
||||||
expect(result[1].dependencies).toEqual([1]);
|
|
||||||
expect(typeof result[1].dependencies[0]).toBe('number');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw an error for invalid JSON', () => {
|
|
||||||
const text = `This is not valid JSON and cannot be parsed`;
|
|
||||||
|
|
||||||
expect(() => parseSubtasksFromText(text, 1, 2, 5)).toThrow(
|
|
||||||
'Could not locate valid JSON array in the response'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleClaudeError function', () => {
|
|
||||||
// Import the function directly for testing
|
|
||||||
let handleClaudeError;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
// Dynamic import to get the actual function
|
|
||||||
const module = await import('../../scripts/modules/ai-services.js');
|
|
||||||
handleClaudeError = module.handleClaudeError;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle overloaded_error type', () => {
|
|
||||||
const error = {
|
|
||||||
type: 'error',
|
|
||||||
error: {
|
|
||||||
type: 'overloaded_error',
|
|
||||||
message: 'Claude is experiencing high volume'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock process.env to include PERPLEXITY_API_KEY
|
|
||||||
const originalEnv = process.env;
|
|
||||||
process.env = { ...originalEnv, PERPLEXITY_API_KEY: 'test-key' };
|
|
||||||
|
|
||||||
const result = handleClaudeError(error);
|
|
||||||
|
|
||||||
// Restore original env
|
|
||||||
process.env = originalEnv;
|
|
||||||
|
|
||||||
expect(result).toContain('Claude is currently overloaded');
|
|
||||||
expect(result).toContain('fall back to Perplexity AI');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle rate_limit_error type', () => {
|
|
||||||
const error = {
|
|
||||||
type: 'error',
|
|
||||||
error: {
|
|
||||||
type: 'rate_limit_error',
|
|
||||||
message: 'Rate limit exceeded'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = handleClaudeError(error);
|
|
||||||
|
|
||||||
expect(result).toContain('exceeded the rate limit');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle invalid_request_error type', () => {
|
|
||||||
const error = {
|
|
||||||
type: 'error',
|
|
||||||
error: {
|
|
||||||
type: 'invalid_request_error',
|
|
||||||
message: 'Invalid request parameters'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = handleClaudeError(error);
|
|
||||||
|
|
||||||
expect(result).toContain('issue with the request format');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle timeout errors', () => {
|
|
||||||
const error = {
|
|
||||||
message: 'Request timed out after 60000ms'
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = handleClaudeError(error);
|
|
||||||
|
|
||||||
expect(result).toContain('timed out');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle network errors', () => {
|
|
||||||
const error = {
|
|
||||||
message: 'Network error occurred'
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = handleClaudeError(error);
|
|
||||||
|
|
||||||
expect(result).toContain('network error');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle generic errors', () => {
|
|
||||||
const error = {
|
|
||||||
message: 'Something unexpected happened'
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = handleClaudeError(error);
|
|
||||||
|
|
||||||
expect(result).toContain('Error communicating with Claude');
|
|
||||||
expect(result).toContain('Something unexpected happened');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Anthropic client configuration', () => {
|
|
||||||
test('should include output-128k beta header in client configuration', async () => {
|
|
||||||
// Read the file content to verify the change is present
|
|
||||||
const fs = await import('fs');
|
|
||||||
const path = await import('path');
|
|
||||||
const filePath = path.resolve('./scripts/modules/ai-services.js');
|
|
||||||
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
||||||
|
|
||||||
// Check if the beta header is in the file
|
|
||||||
expect(fileContent).toContain(
|
|
||||||
"'anthropic-beta': 'output-128k-2025-02-19'"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user