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:
@@ -8,6 +8,7 @@ const mockGetResearchModelId = jest.fn();
|
||||
const mockGetFallbackProvider = jest.fn();
|
||||
const mockGetFallbackModelId = jest.fn();
|
||||
const mockGetParametersForRole = jest.fn();
|
||||
const mockGetResponseLanguage = jest.fn();
|
||||
const mockGetUserId = jest.fn();
|
||||
const mockGetDebugFlag = jest.fn();
|
||||
const mockIsApiKeySet = jest.fn();
|
||||
@@ -98,6 +99,7 @@ jest.unstable_mockModule('../../scripts/modules/config-manager.js', () => ({
|
||||
getFallbackMaxTokens: mockGetFallbackMaxTokens,
|
||||
getFallbackTemperature: mockGetFallbackTemperature,
|
||||
getParametersForRole: mockGetParametersForRole,
|
||||
getResponseLanguage: mockGetResponseLanguage,
|
||||
getUserId: mockGetUserId,
|
||||
getDebugFlag: mockGetDebugFlag,
|
||||
getBaseUrlForRole: mockGetBaseUrlForRole,
|
||||
@@ -277,6 +279,7 @@ describe('Unified AI Services', () => {
|
||||
if (role === 'fallback') return { maxTokens: 150, temperature: 0.6 };
|
||||
return { maxTokens: 100, temperature: 0.5 }; // Default
|
||||
});
|
||||
mockGetResponseLanguage.mockReturnValue('English');
|
||||
mockResolveEnvVariable.mockImplementation((key) => {
|
||||
if (key === 'ANTHROPIC_API_KEY') return 'mock-anthropic-key';
|
||||
if (key === 'PERPLEXITY_API_KEY') return 'mock-perplexity-key';
|
||||
@@ -463,6 +466,68 @@ describe('Unified AI Services', () => {
|
||||
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 () => {
|
||||
// Setup isApiKeySet to return false for anthropic but true for perplexity
|
||||
mockIsApiKeySet.mockImplementation((provider, session, root) => {
|
||||
|
||||
@@ -141,7 +141,8 @@ const DEFAULT_CONFIG = {
|
||||
defaultPriority: 'medium',
|
||||
projectName: 'Task Master',
|
||||
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);
|
||||
});
|
||||
|
||||
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.)
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user