feat: Support custom response language (#510)
* feat: Support custom response language * fix: Add default values for response language in config-manager.js * chore: Update configuration file and add default response language settings * feat: Support MCP/CLI custom response language * chore: Update test comments to English for consistency * docs: Auto-update and format models.md * chore: fix format --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
This commit is contained in:
5
.changeset/tidy-meals-enter.md
Normal file
5
.changeset/tidy-meals-enter.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'task-master-ai': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Support custom response language
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
|
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
|
||||||
"userId": "1234567890",
|
"userId": "1234567890",
|
||||||
"azureBaseURL": "https://your-endpoint.azure.com/",
|
"azureBaseURL": "https://your-endpoint.azure.com/",
|
||||||
"defaultTag": "master"
|
"defaultTag": "master",
|
||||||
|
"responseLanguage": "English"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"main": {
|
"main": {
|
||||||
"provider": "anthropic",
|
"provider": "anthropic",
|
||||||
"modelId": "claude-3-7-sonnet-20250219",
|
"modelId": "claude-3-7-sonnet-20250219",
|
||||||
"maxTokens": 120000,
|
"maxTokens": 100000,
|
||||||
"temperature": 0.2
|
"temperature": 0.2
|
||||||
},
|
},
|
||||||
"research": {
|
"research": {
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
},
|
},
|
||||||
"fallback": {
|
"fallback": {
|
||||||
"provider": "anthropic",
|
"provider": "anthropic",
|
||||||
"modelId": "claude-3-5-sonnet-20240620",
|
"modelId": "claude-3-7-sonnet-20250219",
|
||||||
"maxTokens": 8192,
|
"maxTokens": 8192,
|
||||||
"temperature": 0.1
|
"temperature": 0.2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"global": {
|
"global": {
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"defaultTag": "master",
|
"defaultTag": "master",
|
||||||
"ollamaBaseURL": "http://localhost:11434/api",
|
"ollamaBaseURL": "http://localhost:11434/api",
|
||||||
"azureOpenaiBaseURL": "https://your-endpoint.openai.azure.com/",
|
"azureOpenaiBaseURL": "https://your-endpoint.openai.azure.com/",
|
||||||
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com"
|
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
|
||||||
|
"responseLanguage": "English"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ Taskmaster uses two primary methods for configuration:
|
|||||||
"ollamaBaseURL": "http://localhost:11434/api",
|
"ollamaBaseURL": "http://localhost:11434/api",
|
||||||
"azureBaseURL": "https://your-endpoint.azure.com/openai/deployments",
|
"azureBaseURL": "https://your-endpoint.azure.com/openai/deployments",
|
||||||
"vertexProjectId": "your-gcp-project-id",
|
"vertexProjectId": "your-gcp-project-id",
|
||||||
"vertexLocation": "us-central1"
|
"vertexLocation": "us-central1",
|
||||||
|
"responseLanguage": "English"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
40
mcp-server/src/core/direct-functions/response-language.js
Normal file
40
mcp-server/src/core/direct-functions/response-language.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* response-language.js
|
||||||
|
* Direct function for managing response language via MCP
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { setResponseLanguage } from '../../../../scripts/modules/task-manager.js';
|
||||||
|
import {
|
||||||
|
enableSilentMode,
|
||||||
|
disableSilentMode
|
||||||
|
} from '../../../../scripts/modules/utils.js';
|
||||||
|
import { createLogWrapper } from '../../tools/utils.js';
|
||||||
|
|
||||||
|
export async function responseLanguageDirect(args, log, context = {}) {
|
||||||
|
const { projectRoot, language } = args;
|
||||||
|
const mcpLog = createLogWrapper(log);
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
`Executing response-language_direct with args: ${JSON.stringify(args)}`
|
||||||
|
);
|
||||||
|
log.info(`Using project root: ${projectRoot}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
enableSilentMode();
|
||||||
|
return setResponseLanguage(language, {
|
||||||
|
mcpLog,
|
||||||
|
projectRoot
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: 'DIRECT_FUNCTION_ERROR',
|
||||||
|
message: error.message,
|
||||||
|
details: error.stack
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
disableSilentMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import { registerRemoveTaskTool } from './remove-task.js';
|
|||||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||||
import { registerModelsTool } from './models.js';
|
import { registerModelsTool } from './models.js';
|
||||||
import { registerMoveTaskTool } from './move-task.js';
|
import { registerMoveTaskTool } from './move-task.js';
|
||||||
|
import { registerResponseLanguageTool } from './response-language.js';
|
||||||
import { registerAddTagTool } from './add-tag.js';
|
import { registerAddTagTool } from './add-tag.js';
|
||||||
import { registerDeleteTagTool } from './delete-tag.js';
|
import { registerDeleteTagTool } from './delete-tag.js';
|
||||||
import { registerListTagsTool } from './list-tags.js';
|
import { registerListTagsTool } from './list-tags.js';
|
||||||
@@ -83,6 +84,7 @@ export function registerTaskMasterTools(server) {
|
|||||||
registerRemoveDependencyTool(server);
|
registerRemoveDependencyTool(server);
|
||||||
registerValidateDependenciesTool(server);
|
registerValidateDependenciesTool(server);
|
||||||
registerFixDependenciesTool(server);
|
registerFixDependenciesTool(server);
|
||||||
|
registerResponseLanguageTool(server);
|
||||||
|
|
||||||
// Group 7: Tag Management
|
// Group 7: Tag Management
|
||||||
registerListTagsTool(server);
|
registerListTagsTool(server);
|
||||||
|
|||||||
46
mcp-server/src/tools/response-language.js
Normal file
46
mcp-server/src/tools/response-language.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import {
|
||||||
|
createErrorResponse,
|
||||||
|
handleApiResult,
|
||||||
|
withNormalizedProjectRoot
|
||||||
|
} from './utils.js';
|
||||||
|
import { responseLanguageDirect } from '../core/direct-functions/response-language.js';
|
||||||
|
|
||||||
|
export function registerResponseLanguageTool(server) {
|
||||||
|
server.addTool({
|
||||||
|
name: 'response-language',
|
||||||
|
description: 'Get or set the response language for the project',
|
||||||
|
parameters: z.object({
|
||||||
|
projectRoot: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'The root directory for the project. ALWAYS SET THIS TO THE PROJECT ROOT DIRECTORY. IF NOT SET, THE TOOL WILL NOT WORK.'
|
||||||
|
),
|
||||||
|
language: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'The new response language to set. like "中文" "English" or "español".'
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||||
|
try {
|
||||||
|
log.info(
|
||||||
|
`Executing response-language tool with args: ${JSON.stringify(args)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await responseLanguageDirect(
|
||||||
|
{
|
||||||
|
...args,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
|
},
|
||||||
|
log,
|
||||||
|
{ session }
|
||||||
|
);
|
||||||
|
return handleApiResult(result, log, 'Error setting response language');
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Error in response-language tool: ${error.message}`);
|
||||||
|
return createErrorResponse(error.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -766,6 +766,44 @@ function createProjectStructure(
|
|||||||
}
|
}
|
||||||
// =====================================
|
// =====================================
|
||||||
|
|
||||||
|
// === Add Response Language Step ===
|
||||||
|
if (!isSilentMode() && !dryRun && !options?.yes) {
|
||||||
|
console.log(
|
||||||
|
boxen(chalk.cyan('Configuring Response Language...'), {
|
||||||
|
padding: 0.5,
|
||||||
|
margin: { top: 1, bottom: 0.5 },
|
||||||
|
borderStyle: 'round',
|
||||||
|
borderColor: 'blue'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
log(
|
||||||
|
'info',
|
||||||
|
'Running interactive response language setup. Please input your preferred language.'
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
execSync('npx task-master lang --setup', {
|
||||||
|
stdio: 'inherit',
|
||||||
|
cwd: targetDir
|
||||||
|
});
|
||||||
|
log('success', 'Response Language configured.');
|
||||||
|
} catch (error) {
|
||||||
|
log('error', 'Failed to configure response language:', error.message);
|
||||||
|
log('warn', 'You may need to run "task-master lang --setup" manually.');
|
||||||
|
}
|
||||||
|
} else if (isSilentMode() && !dryRun) {
|
||||||
|
log(
|
||||||
|
'info',
|
||||||
|
'Skipping interactive response language setup in silent (MCP) mode.'
|
||||||
|
);
|
||||||
|
log(
|
||||||
|
'warn',
|
||||||
|
'Please configure response language using "task-master models --set-response-language" or the "models" MCP tool.'
|
||||||
|
);
|
||||||
|
} else if (dryRun) {
|
||||||
|
log('info', 'DRY RUN: Skipping interactive response language setup.');
|
||||||
|
}
|
||||||
|
// =====================================
|
||||||
|
|
||||||
// === Add Model Configuration Step ===
|
// === Add Model Configuration Step ===
|
||||||
if (!isSilentMode() && !dryRun && !options?.yes) {
|
if (!isSilentMode() && !dryRun && !options?.yes) {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
getFallbackProvider,
|
getFallbackProvider,
|
||||||
getFallbackModelId,
|
getFallbackModelId,
|
||||||
getParametersForRole,
|
getParametersForRole,
|
||||||
|
getResponseLanguage,
|
||||||
getUserId,
|
getUserId,
|
||||||
MODEL_MAP,
|
MODEL_MAP,
|
||||||
getDebugFlag,
|
getDebugFlag,
|
||||||
@@ -551,9 +552,12 @@ async function _unifiedServiceRunner(serviceType, params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const messages = [];
|
const messages = [];
|
||||||
if (systemPrompt) {
|
const responseLanguage = getResponseLanguage(effectiveProjectRoot);
|
||||||
messages.push({ role: 'system', content: systemPrompt });
|
const systemPromptWithLanguage = `${systemPrompt} \n\n Always respond in ${responseLanguage}.`;
|
||||||
}
|
messages.push({
|
||||||
|
role: 'system',
|
||||||
|
content: systemPromptWithLanguage.trim()
|
||||||
|
});
|
||||||
|
|
||||||
// IN THE FUTURE WHEN DOING CONTEXT IMPROVEMENTS
|
// IN THE FUTURE WHEN DOING CONTEXT IMPROVEMENTS
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ import {
|
|||||||
findTaskById,
|
findTaskById,
|
||||||
taskExists,
|
taskExists,
|
||||||
moveTask,
|
moveTask,
|
||||||
migrateProject
|
migrateProject,
|
||||||
|
setResponseLanguage
|
||||||
} from './task-manager.js';
|
} from './task-manager.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -3661,6 +3662,63 @@ Examples:
|
|||||||
return; // Stop execution here
|
return; // Stop execution here
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// response-language command
|
||||||
|
programInstance
|
||||||
|
.command('lang')
|
||||||
|
.description('Manage response language settings')
|
||||||
|
.option('--response <response_language>', 'Set the response language')
|
||||||
|
.option('--setup', 'Run interactive setup to configure response language')
|
||||||
|
.action(async (options) => {
|
||||||
|
const projectRoot = findProjectRoot(); // Find project root for context
|
||||||
|
const { response, setup } = options;
|
||||||
|
console.log(
|
||||||
|
chalk.blue('Response language set to:', JSON.stringify(options))
|
||||||
|
);
|
||||||
|
let responseLanguage = response || 'English';
|
||||||
|
if (setup) {
|
||||||
|
console.log(
|
||||||
|
chalk.blue('Starting interactive response language setup...')
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const userResponse = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'responseLanguage',
|
||||||
|
message: 'Input your preferred response language',
|
||||||
|
default: 'English'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
chalk.blue(
|
||||||
|
'Response language set to:',
|
||||||
|
userResponse.responseLanguage
|
||||||
|
)
|
||||||
|
);
|
||||||
|
responseLanguage = userResponse.responseLanguage;
|
||||||
|
} catch (setupError) {
|
||||||
|
console.error(
|
||||||
|
chalk.red('\\nInteractive setup failed unexpectedly:'),
|
||||||
|
setupError.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = setResponseLanguage(responseLanguage, {
|
||||||
|
projectRoot
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log(chalk.green(`✅ ${result.data.message}`));
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
`❌ Error setting response language: ${result.error.message}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// move-task command
|
// move-task command
|
||||||
programInstance
|
programInstance
|
||||||
.command('move')
|
.command('move')
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ const DEFAULTS = {
|
|||||||
defaultPriority: 'medium',
|
defaultPriority: 'medium',
|
||||||
projectName: 'Task Master',
|
projectName: 'Task Master',
|
||||||
ollamaBaseURL: 'http://localhost:11434/api',
|
ollamaBaseURL: 'http://localhost:11434/api',
|
||||||
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com'
|
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com',
|
||||||
|
responseLanguage: 'English'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -425,6 +426,11 @@ function getVertexLocation(explicitRoot = null) {
|
|||||||
return getGlobalConfig(explicitRoot).vertexLocation || 'us-central1';
|
return getGlobalConfig(explicitRoot).vertexLocation || 'us-central1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getResponseLanguage(explicitRoot = null) {
|
||||||
|
// Directly return value from config
|
||||||
|
return getGlobalConfig(explicitRoot).responseLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets model parameters (maxTokens, temperature) for a specific role,
|
* Gets model parameters (maxTokens, temperature) for a specific role,
|
||||||
* considering model-specific overrides from supported-models.json.
|
* considering model-specific overrides from supported-models.json.
|
||||||
@@ -841,6 +847,7 @@ export {
|
|||||||
getOllamaBaseURL,
|
getOllamaBaseURL,
|
||||||
getAzureBaseURL,
|
getAzureBaseURL,
|
||||||
getBedrockBaseURL,
|
getBedrockBaseURL,
|
||||||
|
getResponseLanguage,
|
||||||
getParametersForRole,
|
getParametersForRole,
|
||||||
getUserId,
|
getUserId,
|
||||||
// API Key Checkers (still relevant)
|
// API Key Checkers (still relevant)
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ import updateSubtaskById from './task-manager/update-subtask-by-id.js';
|
|||||||
import removeTask from './task-manager/remove-task.js';
|
import removeTask from './task-manager/remove-task.js';
|
||||||
import taskExists from './task-manager/task-exists.js';
|
import taskExists from './task-manager/task-exists.js';
|
||||||
import isTaskDependentOn from './task-manager/is-task-dependent.js';
|
import isTaskDependentOn from './task-manager/is-task-dependent.js';
|
||||||
|
import setResponseLanguage from './task-manager/response-language.js';
|
||||||
import moveTask from './task-manager/move-task.js';
|
import moveTask from './task-manager/move-task.js';
|
||||||
import { migrateProject } from './task-manager/migrate.js';
|
import { migrateProject } from './task-manager/migrate.js';
|
||||||
import { performResearch } from './task-manager/research.js';
|
import { performResearch } from './task-manager/research.js';
|
||||||
import { readComplexityReport } from './utils.js';
|
import { readComplexityReport } from './utils.js';
|
||||||
|
|
||||||
// Export task manager functions
|
// Export task manager functions
|
||||||
export {
|
export {
|
||||||
parsePRD,
|
parsePRD,
|
||||||
@@ -49,6 +51,7 @@ export {
|
|||||||
findTaskById,
|
findTaskById,
|
||||||
taskExists,
|
taskExists,
|
||||||
isTaskDependentOn,
|
isTaskDependentOn,
|
||||||
|
setResponseLanguage,
|
||||||
moveTask,
|
moveTask,
|
||||||
readComplexityReport,
|
readComplexityReport,
|
||||||
migrateProject,
|
migrateProject,
|
||||||
|
|||||||
94
scripts/modules/task-manager/response-language.js
Normal file
94
scripts/modules/task-manager/response-language.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import {
|
||||||
|
getConfig,
|
||||||
|
isConfigFilePresent,
|
||||||
|
writeConfig
|
||||||
|
} from '../config-manager.js';
|
||||||
|
|
||||||
|
function setResponseLanguage(lang, options = {}) {
|
||||||
|
const { mcpLog, projectRoot } = options;
|
||||||
|
|
||||||
|
const report = (level, ...args) => {
|
||||||
|
if (mcpLog && typeof mcpLog[level] === 'function') {
|
||||||
|
mcpLog[level](...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let configPath;
|
||||||
|
let configExists = false;
|
||||||
|
|
||||||
|
if (projectRoot) {
|
||||||
|
configPath = path.join(projectRoot, '.taskmasterconfig');
|
||||||
|
configExists = fs.existsSync(configPath);
|
||||||
|
report(
|
||||||
|
'info',
|
||||||
|
`Checking for .taskmasterconfig at: ${configPath}, exists: ${configExists}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
configExists = isConfigFilePresent();
|
||||||
|
report(
|
||||||
|
'info',
|
||||||
|
`Checking for .taskmasterconfig using isConfigFilePresent(), exists: ${configExists}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configExists) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: 'CONFIG_MISSING',
|
||||||
|
message:
|
||||||
|
'The .taskmasterconfig file is missing. Run "task-master models --setup" to create it.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate response language
|
||||||
|
if (typeof lang !== 'string' || lang.trim() === '') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: 'INVALID_RESPONSE_LANGUAGE',
|
||||||
|
message: `Invalid response language: ${lang}. Must be a non-empty string.`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentConfig = getConfig(projectRoot);
|
||||||
|
currentConfig.global.responseLanguage = lang;
|
||||||
|
const writeResult = writeConfig(currentConfig, projectRoot);
|
||||||
|
|
||||||
|
if (!writeResult) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: 'WRITE_ERROR',
|
||||||
|
message: 'Error writing updated configuration to .taskmasterconfig'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const successMessage = `Successfully set response language to: ${lang}`;
|
||||||
|
report('info', successMessage);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
responseLanguage: lang,
|
||||||
|
message: successMessage
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
report('error', `Error setting response language: ${error.message}`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: 'SET_RESPONSE_LANGUAGE_ERROR',
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default setResponseLanguage;
|
||||||
@@ -8,6 +8,7 @@ const mockGetResearchModelId = jest.fn();
|
|||||||
const mockGetFallbackProvider = jest.fn();
|
const mockGetFallbackProvider = jest.fn();
|
||||||
const mockGetFallbackModelId = jest.fn();
|
const mockGetFallbackModelId = jest.fn();
|
||||||
const mockGetParametersForRole = jest.fn();
|
const mockGetParametersForRole = jest.fn();
|
||||||
|
const mockGetResponseLanguage = jest.fn();
|
||||||
const mockGetUserId = jest.fn();
|
const mockGetUserId = jest.fn();
|
||||||
const mockGetDebugFlag = jest.fn();
|
const mockGetDebugFlag = jest.fn();
|
||||||
const mockIsApiKeySet = jest.fn();
|
const mockIsApiKeySet = jest.fn();
|
||||||
@@ -98,6 +99,7 @@ jest.unstable_mockModule('../../scripts/modules/config-manager.js', () => ({
|
|||||||
getFallbackMaxTokens: mockGetFallbackMaxTokens,
|
getFallbackMaxTokens: mockGetFallbackMaxTokens,
|
||||||
getFallbackTemperature: mockGetFallbackTemperature,
|
getFallbackTemperature: mockGetFallbackTemperature,
|
||||||
getParametersForRole: mockGetParametersForRole,
|
getParametersForRole: mockGetParametersForRole,
|
||||||
|
getResponseLanguage: mockGetResponseLanguage,
|
||||||
getUserId: mockGetUserId,
|
getUserId: mockGetUserId,
|
||||||
getDebugFlag: mockGetDebugFlag,
|
getDebugFlag: mockGetDebugFlag,
|
||||||
getBaseUrlForRole: mockGetBaseUrlForRole,
|
getBaseUrlForRole: mockGetBaseUrlForRole,
|
||||||
@@ -277,6 +279,7 @@ describe('Unified AI Services', () => {
|
|||||||
if (role === 'fallback') return { maxTokens: 150, temperature: 0.6 };
|
if (role === 'fallback') return { maxTokens: 150, temperature: 0.6 };
|
||||||
return { maxTokens: 100, temperature: 0.5 }; // Default
|
return { maxTokens: 100, temperature: 0.5 }; // Default
|
||||||
});
|
});
|
||||||
|
mockGetResponseLanguage.mockReturnValue('English');
|
||||||
mockResolveEnvVariable.mockImplementation((key) => {
|
mockResolveEnvVariable.mockImplementation((key) => {
|
||||||
if (key === 'ANTHROPIC_API_KEY') return 'mock-anthropic-key';
|
if (key === 'ANTHROPIC_API_KEY') return 'mock-anthropic-key';
|
||||||
if (key === 'PERPLEXITY_API_KEY') return 'mock-perplexity-key';
|
if (key === 'PERPLEXITY_API_KEY') return 'mock-perplexity-key';
|
||||||
@@ -463,6 +466,68 @@ describe('Unified AI Services', () => {
|
|||||||
expect(mockAnthropicProvider.generateText).toHaveBeenCalledTimes(1);
|
expect(mockAnthropicProvider.generateText).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should use configured responseLanguage in system prompt', async () => {
|
||||||
|
mockGetResponseLanguage.mockReturnValue('中文');
|
||||||
|
mockAnthropicProvider.generateText.mockResolvedValue('中文回复');
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
role: 'main',
|
||||||
|
systemPrompt: 'You are an assistant',
|
||||||
|
prompt: 'Hello'
|
||||||
|
};
|
||||||
|
await generateTextService(params);
|
||||||
|
|
||||||
|
expect(mockAnthropicProvider.generateText).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: expect.stringContaining('Always respond in 中文')
|
||||||
|
},
|
||||||
|
{ role: 'user', content: 'Hello' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(mockGetResponseLanguage).toHaveBeenCalledWith(fakeProjectRoot);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should pass custom projectRoot to getResponseLanguage', async () => {
|
||||||
|
const customRoot = '/custom/project/root';
|
||||||
|
mockGetResponseLanguage.mockReturnValue('Español');
|
||||||
|
mockAnthropicProvider.generateText.mockResolvedValue(
|
||||||
|
'Respuesta en Español'
|
||||||
|
);
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
role: 'main',
|
||||||
|
systemPrompt: 'You are an assistant',
|
||||||
|
prompt: 'Hello',
|
||||||
|
projectRoot: customRoot
|
||||||
|
};
|
||||||
|
await generateTextService(params);
|
||||||
|
|
||||||
|
expect(mockGetResponseLanguage).toHaveBeenCalledWith(customRoot);
|
||||||
|
expect(mockAnthropicProvider.generateText).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: expect.stringContaining('Always respond in Español')
|
||||||
|
},
|
||||||
|
{ role: 'user', content: 'Hello' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add more tests for edge cases:
|
||||||
|
// - Missing API keys (should throw from _resolveApiKey)
|
||||||
|
// - Unsupported provider configured (should skip and log)
|
||||||
|
// - Missing provider/model config for a role (should skip and log)
|
||||||
|
// - Missing prompt
|
||||||
|
// - Different initial roles (research, fallback)
|
||||||
|
// - generateObjectService (mock schema, check object result)
|
||||||
|
// - streamTextService (more complex to test, might need stream helpers)
|
||||||
test('should skip provider with missing API key and try next in fallback sequence', async () => {
|
test('should skip provider with missing API key and try next in fallback sequence', async () => {
|
||||||
// Setup isApiKeySet to return false for anthropic but true for perplexity
|
// Setup isApiKeySet to return false for anthropic but true for perplexity
|
||||||
mockIsApiKeySet.mockImplementation((provider, session, root) => {
|
mockIsApiKeySet.mockImplementation((provider, session, root) => {
|
||||||
|
|||||||
@@ -141,7 +141,8 @@ const DEFAULT_CONFIG = {
|
|||||||
defaultPriority: 'medium',
|
defaultPriority: 'medium',
|
||||||
projectName: 'Task Master',
|
projectName: 'Task Master',
|
||||||
ollamaBaseURL: 'http://localhost:11434/api',
|
ollamaBaseURL: 'http://localhost:11434/api',
|
||||||
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com'
|
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com',
|
||||||
|
responseLanguage: 'English'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -685,6 +686,82 @@ describe('Getter Functions', () => {
|
|||||||
expect(logLevel).toBe(VALID_CUSTOM_CONFIG.global.logLevel);
|
expect(logLevel).toBe(VALID_CUSTOM_CONFIG.global.logLevel);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('getResponseLanguage should return responseLanguage from config', () => {
|
||||||
|
// Arrange
|
||||||
|
// Prepare a config object with responseLanguage property for this test
|
||||||
|
const configWithLanguage = JSON.stringify({
|
||||||
|
models: {
|
||||||
|
main: { provider: 'openai', modelId: 'gpt-4-turbo' }
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
projectName: 'Test Project',
|
||||||
|
responseLanguage: '中文'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up fs.readFileSync to return our test config
|
||||||
|
fsReadFileSyncSpy.mockImplementation((filePath) => {
|
||||||
|
if (filePath === MOCK_CONFIG_PATH) {
|
||||||
|
return configWithLanguage;
|
||||||
|
}
|
||||||
|
if (path.basename(filePath) === 'supported-models.json') {
|
||||||
|
return JSON.stringify({
|
||||||
|
openai: [{ id: 'gpt-4-turbo' }]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected fs.readFileSync call: ${filePath}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
fsExistsSyncSpy.mockReturnValue(true);
|
||||||
|
|
||||||
|
// Ensure getConfig returns new values instead of cached ones
|
||||||
|
configManager.getConfig(MOCK_PROJECT_ROOT, true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const responseLanguage =
|
||||||
|
configManager.getResponseLanguage(MOCK_PROJECT_ROOT);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(responseLanguage).toBe('中文');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getResponseLanguage should return undefined when responseLanguage is not in config', () => {
|
||||||
|
// Arrange
|
||||||
|
const configWithoutLanguage = JSON.stringify({
|
||||||
|
models: {
|
||||||
|
main: { provider: 'openai', modelId: 'gpt-4-turbo' }
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
projectName: 'Test Project'
|
||||||
|
// No responseLanguage property
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fsReadFileSyncSpy.mockImplementation((filePath) => {
|
||||||
|
if (filePath === MOCK_CONFIG_PATH) {
|
||||||
|
return configWithoutLanguage;
|
||||||
|
}
|
||||||
|
if (path.basename(filePath) === 'supported-models.json') {
|
||||||
|
return JSON.stringify({
|
||||||
|
openai: [{ id: 'gpt-4-turbo' }]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected fs.readFileSync call: ${filePath}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
fsExistsSyncSpy.mockReturnValue(true);
|
||||||
|
|
||||||
|
// Ensure getConfig returns new values instead of cached ones
|
||||||
|
configManager.getConfig(MOCK_PROJECT_ROOT, true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const responseLanguage =
|
||||||
|
configManager.getResponseLanguage(MOCK_PROJECT_ROOT);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(responseLanguage).toBe('English');
|
||||||
|
});
|
||||||
|
|
||||||
// Add more tests for other getters (getResearchProvider, getProjectName, etc.)
|
// Add more tests for other getters (getResearchProvider, getProjectName, etc.)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user