Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
31 KiB
JSON
1 line
31 KiB
JSON
{"repo": "bzcasper/prompt-optimizer-2.0.0", "files": {"packages/mcp-server/src/config/environment.ts": {"d7fbd7b7daa5c7146292a53fd1c3dec47169d4f4": {"t": 1785745597.9248917, "n": 2989, "h": "5efc28446a0325a9", "c": "/**\n * 环境变量配置管理\n *\n * 注意:环境变量已通过 preload-env.js 在应用启动前加载\n * 这里的 config() 调用是备用加载机制\n */\n\nimport { config } from 'dotenv';\n\n// 备用环境变量加载(preload-env.js 已经处理了主要加载)\nconfig();\n\n// 导入共享常量\nconst CUSTOM_API_PATTERN = /^VITE_CUSTOM_API_(KEY|BASE_URL|MODEL)_(.+)$/;\nconst SUFFIX_PATTERN = /^[a-zA-Z0-9_-]+$/;\nconst MAX_SUFFIX_LENGTH = 50;\n\n/**\n * 扫描动态自定义模型环境变量\n * 查找 VITE_CUSTOM_API_*_suffix 模式的环境变量\n */\nfunction scanDynamicCustomEnvVars(): Record<string, string> {\n const dynamicMappings: Record<string, string> = {};\n\n // 使用共享的正则表达式模式\n const customApiPattern = CUSTOM_API_PATTERN;\n\n Object.keys(process.env).forEach(key => {\n const match = key.match(customApiPattern);\n if (match) {\n const [, configType, suffix] = match;\n\n // 验证后缀名(不能为空,不能包含特殊字符,不能超过长度限制)\n if (!suffix || suffix.length > MAX_SUFFIX_LENGTH || !SUFFIX_PATTERN.test(suffix)) {\n console.warn(`[MCP Environment] Invalid suffix in ${key}: ${suffix}`);\n return;\n }\n\n // 生成对应的MCP环境变量名(保持suffix原始大小写)\n const mcpKey = `CUSTOM_API_${configType}_${suffix}`;\n dynamicMappings[key] = mcpKey;\n }\n });\n\n console.log(`[MCP Environment] Found ${Object.keys(dynamicMappings).length} dynamic custom environment variables`);\n\n return dynamicMappings;\n}\n\n// 静态环境变量映射\nconst staticEnvMappings = {\n 'VITE_OPENAI_API_KEY': 'OPENAI_API_KEY',\n 'VITE_GEMINI_API_KEY': 'GEMINI_API_KEY',\n 'VITE_DEEPSEEK_API_KEY': 'DEEPSEEK_API_KEY',\n 'VITE_ZHIPU_API_KEY': 'ZHIPU_API_KEY',\n 'VITE_SILICONFLOW_API_KEY': 'SILICONFLOW_API_KEY',\n 'VITE_CUSTOM_API_KEY': 'CUSTOM_API_KEY',\n 'VITE_CUSTOM_API_BASE_URL': 'CUSTOM_API_BASE_URL',\n 'VITE_CUSTOM_API_MODEL': 'CUSTOM_API_MODEL'\n};\n\n// 动态环境变量映射\nconst dynamicEnvMappings = scanDynamicCustomEnvVars();\n\n// 合并所有环境变量映射\nconst allEnvMappings = {\n ...staticEnvMappings,\n ...dynamicEnvMappings\n};\n\n// 执行环境变量映射\nObject.entries(allEnvMappings).forEach(([viteKey, mcpKey]) => {\n if (process.env[viteKey] && !process.env[mcpKey]) {\n process.env[mcpKey] = process.env[viteKey];\n console.log(`[MCP Environment] Mapped ${viteKey} -> ${mcpKey}`);\n }\n});\n\nexport interface MCPServerConfig {\n httpPort: number;\n logLevel: 'debug' | 'info' | 'warn' | 'error';\n defaultLanguage: string;\n preferredModelProvider?: string;\n}\n\nexport function loadConfig(): MCPServerConfig {\n return {\n httpPort: parseInt(process.env.MCP_HTTP_PORT || '3000'),\n logLevel: (process.env.MCP_LOG_LEVEL as 'debug' | 'info' | 'warn' | 'error') || 'debug',\n defaultLanguage: process.env.MCP_DEFAULT_LANGUAGE || 'zh',\n preferredModelProvider: process.env.MCP_DEFAULT_MODEL_PROVIDER\n };\n}\n\nexport function validateConfig(config: MCPServerConfig): void {\n if (config.httpPort < 1 || config.httpPort > 65535) {\n throw new Error('HTTP port must be between 1 and 65535');\n }\n\n const validLogLevels = ['debug', 'info', 'warn', 'error'];\n if (!validLogLevels.includes(config.logLevel)) {\n throw new Error(`Log level must be one of: ${validLogLevels.join(', ')}`);\n }\n}\n"}, "088af1706cadd1c355e6114abac15b3ca7746083": {"t": 1785754637.6079466, "n": 2989, "h": "5efc28446a0325a9", "c": "/**\n * 环境变量配置管理\n *\n * 注意:环境变量已通过 preload-env.js 在应用启动前加载\n * 这里的 config() 调用是备用加载机制\n */\n\nimport { config } from 'dotenv';\n\n// 备用环境变量加载(preload-env.js 已经处理了主要加载)\nconfig();\n\n// 导入共享常量\nconst CUSTOM_API_PATTERN = /^VITE_CUSTOM_API_(KEY|BASE_URL|MODEL)_(.+)$/;\nconst SUFFIX_PATTERN = /^[a-zA-Z0-9_-]+$/;\nconst MAX_SUFFIX_LENGTH = 50;\n\n/**\n * 扫描动态自定义模型环境变量\n * 查找 VITE_CUSTOM_API_*_suffix 模式的环境变量\n */\nfunction scanDynamicCustomEnvVars(): Record<string, string> {\n const dynamicMappings: Record<string, string> = {};\n\n // 使用共享的正则表达式模式\n const customApiPattern = CUSTOM_API_PATTERN;\n\n Object.keys(process.env).forEach(key => {\n const match = key.match(customApiPattern);\n if (match) {\n const [, configType, suffix] = match;\n\n // 验证后缀名(不能为空,不能包含特殊字符,不能超过长度限制)\n if (!suffix || suffix.length > MAX_SUFFIX_LENGTH || !SUFFIX_PATTERN.test(suffix)) {\n console.warn(`[MCP Environment] Invalid suffix in ${key}: ${suffix}`);\n return;\n }\n\n // 生成对应的MCP环境变量名(保持suffix原始大小写)\n const mcpKey = `CUSTOM_API_${configType}_${suffix}`;\n dynamicMappings[key] = mcpKey;\n }\n });\n\n console.log(`[MCP Environment] Found ${Object.keys(dynamicMappings).length} dynamic custom environment variables`);\n\n return dynamicMappings;\n}\n\n// 静态环境变量映射\nconst staticEnvMappings = {\n 'VITE_OPENAI_API_KEY': 'OPENAI_API_KEY',\n 'VITE_GEMINI_API_KEY': 'GEMINI_API_KEY',\n 'VITE_DEEPSEEK_API_KEY': 'DEEPSEEK_API_KEY',\n 'VITE_ZHIPU_API_KEY': 'ZHIPU_API_KEY',\n 'VITE_SILICONFLOW_API_KEY': 'SILICONFLOW_API_KEY',\n 'VITE_CUSTOM_API_KEY': 'CUSTOM_API_KEY',\n 'VITE_CUSTOM_API_BASE_URL': 'CUSTOM_API_BASE_URL',\n 'VITE_CUSTOM_API_MODEL': 'CUSTOM_API_MODEL'\n};\n\n// 动态环境变量映射\nconst dynamicEnvMappings = scanDynamicCustomEnvVars();\n\n// 合并所有环境变量映射\nconst allEnvMappings = {\n ...staticEnvMappings,\n ...dynamicEnvMappings\n};\n\n// 执行环境变量映射\nObject.entries(allEnvMappings).forEach(([viteKey, mcpKey]) => {\n if (process.env[viteKey] && !process.env[mcpKey]) {\n process.env[mcpKey] = process.env[viteKey];\n console.log(`[MCP Environment] Mapped ${viteKey} -> ${mcpKey}`);\n }\n});\n\nexport interface MCPServerConfig {\n httpPort: number;\n logLevel: 'debug' | 'info' | 'warn' | 'error';\n defaultLanguage: string;\n preferredModelProvider?: string;\n}\n\nexport function loadConfig(): MCPServerConfig {\n return {\n httpPort: parseInt(process.env.MCP_HTTP_PORT || '3000'),\n logLevel: (process.env.MCP_LOG_LEVEL as 'debug' | 'info' | 'warn' | 'error') || 'debug',\n defaultLanguage: process.env.MCP_DEFAULT_LANGUAGE || 'zh',\n preferredModelProvider: process.env.MCP_DEFAULT_MODEL_PROVIDER\n };\n}\n\nexport function validateConfig(config: MCPServerConfig): void {\n if (config.httpPort < 1 || config.httpPort > 65535) {\n throw new Error('HTTP port must be between 1 and 65535');\n }\n\n const validLogLevels = ['debug', 'info', 'warn', 'error'];\n if (!validLogLevels.includes(config.logLevel)) {\n throw new Error(`Log level must be one of: ${validLogLevels.join(', ')}`);\n }\n}\n"}}, "packages/core/tests/unit/llm/llmParams.test.ts": {"d7fbd7b7daa5c7146292a53fd1c3dec47169d4f4": {"t": 1786068279.9662454, "n": 23207, "h": "c4e20dba21f12ca1", "c": "import { describe, it, expect, beforeAll, beforeEach } from 'vitest';\nimport { createLLMService, ModelManager, LocalStorageProvider } from '../../../src/index.js';\nimport { validateLLMParams } from '../../../src/services/model/validation';\nimport type { ModelConfig } from '../../../src/services/model/types';\nimport dotenv from 'dotenv';\nimport path from 'path';\n\n// Load environment variables\nbeforeAll(() => {\n dotenv.config({ path: path.resolve(process.cwd(), '.env.local') });\n});\n\nconst RUN_REAL_API = process.env.RUN_REAL_API === '1'\n\ndescribe.skipIf(!RUN_REAL_API)('LLM Parameters (llmParams) Functionality', () => {\n // Check for available API keys\n const openaiCompatibleKeys = [\n 'OPENAI_API_KEY', 'VITE_OPENAI_API_KEY',\n 'DEEPSEEK_API_KEY', 'VITE_DEEPSEEK_API_KEY', \n 'SILICONFLOW_API_KEY', 'VITE_SILICONFLOW_API_KEY',\n 'ZHIPU_API_KEY', 'VITE_ZHIPU_API_KEY',\n 'CUSTOM_API_KEY', 'VITE_CUSTOM_API_KEY'\n ];\n\n const geminiKeys = [\n 'GEMINI_API_KEY', 'VITE_GEMINI_API_KEY'\n ];\n\n const hasOpenAICompatibleKey = openaiCompatibleKeys.some(key => \n process.env[key] && process.env[key].trim()\n );\n\n const hasGeminiKey = geminiKeys.some(key => \n process.env[key] && process.env[key].trim()\n );\n\n // Configuration interface\n interface ProviderConfig {\n key: string;\n apiKey: string;\n baseURL: string;\n defaultModel: string;\n provider: string;\n }\n\n // Get all available OpenAI compatible configurations\n const getAvailableOpenAICompatibleConfigs = (): ProviderConfig[] => {\n const configs: ProviderConfig[] = [];\n \n if (process.env.OPENAI_API_KEY || process.env.VITE_OPENAI_API_KEY) {\n configs.push({\n key: 'openai',\n apiKey: (process.env.OPENAI_API_KEY || process.env.VITE_OPENAI_API_KEY)!,\n baseURL: 'https://api.openai.com/v1',\n defaultModel: 'gpt-3.5-turbo',\n provider: 'openai'\n });\n }\n \n if (process.env.DEEPSEEK_API_KEY || process.env.VITE_DEEPSEEK_API_KEY) {\n configs.push({\n key: 'deepseek',\n apiKey: (process.env.DEEPSEEK_API_KEY || process.env.VITE_DEEPSEEK_API_KEY)!,\n baseURL: 'https://api.deepseek.com/v1',\n defaultModel: 'deepseek-chat',\n provider: 'deepseek'\n });\n }\n \n if (process.env.SILICONFLOW_API_KEY || process.env.VITE_SILICONFLOW_API_KEY) {\n configs.push({\n key: 'siliconflow',\n apiKey: (process.env.SILICONFLOW_API_KEY || process.env.VITE_SILICONFLOW_API_KEY)!,\n baseURL: 'https://api.siliconflow.cn/v1',\n defaultModel: 'Pro/deepseek-ai/DeepSeek-V3',\n provider: 'siliconflow'\n });\n }\n \n if (process.env.ZHIPU_API_KEY || process.env.VITE_ZHIPU_API_KEY) {\n configs.push({\n key: 'zhipu',\n apiKey: (process.env.ZHIPU_API_KEY || process.env.VITE_ZHIPU_API_KEY)!,\n baseURL: 'https://open.bigmodel.cn/api/paas/v4',\n defaultModel: 'glm-4-flash',\n provider: 'zhipu'\n });\n }\n \n if (process.env.CUSTOM_API_KEY || process.env.VITE_CUSTOM_API_KEY) {\n configs.push({\n key: 'custom',\n apiKey: (process.env.CUSTOM_API_KEY || process.env.VITE_CUSTOM_API_KEY)!,\n baseURL: (process.env.CUSTOM_API_BASE_URL || process.env.VITE_CUSTOM_API_BASE_URL)!,\n defaultModel: (process.env.CUSTOM_API_MODEL || process.env.VITE_CUSTOM_API_MODEL)!,\n provider: 'custom'\n });\n }\n \n return configs;\n };\n\n // Get OpenAI compatible configuration (for backward compatibility)\n const getOpenAICompatibleConfig = () => {\n const configs = getAvailableOpenAICompatibleConfigs();\n return configs.length > 0 ? configs[0] : null;\n };\n\n // Get Gemini configuration\n const getGeminiConfig = () => {\n if (process.env.GEMINI_API_KEY || process.env.VITE_GEMINI_API_KEY) {\n return {\n key: 'gemini', // Use existing model key\n apiKey: process.env.GEMINI_API_KEY || process.env.VITE_GEMINI_API_KEY,\n baseURL: 'https://generativelanguage.googleapis.com/v1beta',\n defaultModel: 'gemini-2.0-flash',\n provider: 'gemini'\n };\n }\n return null;\n };\n\n describe('OpenAI Compatible Providers', () => {\n const openaiConfig = getOpenAICompatibleConfig();\n\n if (!hasOpenAICompatibleKey || !openaiConfig) {\n console.log('Skipping OpenAI Compatible tests: No API key available');\n it.skip('should handle llmParams for OpenAI compatible providers', () => {});\n return;\n }\n\n it('should use custom parameters from llmParams', async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n // Configure model with llmParams\n await modelManager.updateModel(openaiConfig.key, {\n name: 'Test OpenAI Compatible',\n apiKey: openaiConfig.apiKey,\n baseURL: openaiConfig.baseURL,\n defaultModel: openaiConfig.defaultModel,\n enabled: true,\n provider: openaiConfig.provider,\n models: [openaiConfig.defaultModel],\n llmParams: {\n temperature: 0.1, // Very low temperature for predictable output\n max_tokens: 50 // Short response\n }\n });\n\n const messages = [\n { role: 'user' as const, content: 'Say exactly: \"Hello World\"' }\n ];\n\n const response = await llmService.sendMessage(messages, openaiConfig.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n // With low temperature and specific instruction, response should be short and focused\n expect(response.length).toBeLessThan(200);\n }, 30000);\n\n it('should handle timeout parameter for OpenAI compatible providers', async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n // Configure model with custom timeout\n await modelManager.updateModel(openaiConfig.key, {\n name: 'Test OpenAI Compatible',\n apiKey: openaiConfig.apiKey,\n baseURL: openaiConfig.baseURL,\n defaultModel: openaiConfig.defaultModel,\n enabled: true,\n provider: openaiConfig.provider,\n models: [openaiConfig.defaultModel],\n llmParams: {\n timeout: 30000, // 30 seconds timeout\n temperature: 0.5\n }\n });\n\n const messages = [\n { role: 'user' as const, content: 'Hello' }\n ];\n\n const response = await llmService.sendMessage(messages, openaiConfig.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n }, 35000);\n });\n\n describe('Gemini Provider', () => {\n const geminiConfig = getGeminiConfig();\n\n if (!hasGeminiKey || !geminiConfig) {\n console.log('Skipping Gemini tests: No GEMINI_API_KEY available');\n it.skip('should handle llmParams for Gemini provider', () => {});\n return;\n }\n\n it('should use Gemini-specific parameters from llmParams', async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n // Configure Gemini model with llmParams\n await modelManager.updateModel(geminiConfig.key, {\n name: 'Test Gemini',\n apiKey: geminiConfig.apiKey,\n baseURL: geminiConfig.baseURL,\n defaultModel: geminiConfig.defaultModel,\n enabled: true,\n provider: geminiConfig.provider,\n models: [geminiConfig.defaultModel],\n llmParams: {\n temperature: 0.2,\n maxOutputTokens: 100,\n topP: 0.8,\n topK: 20\n }\n });\n\n const messages = [\n { role: 'user' as const, content: 'Tell me a very short fact about AI' }\n ];\n\n const response = await llmService.sendMessage(messages, geminiConfig.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n // With maxOutputTokens=100, response should be relatively short\n expect(response.length).toBeLessThan(500);\n }, 30000);\n });\n\n describe('Parameter Validation', () => {\n it('should validate OpenAI parameters correctly', () => {\n const validParams = {\n temperature: 0.7,\n max_tokens: 2048,\n timeout: 60000\n };\n\n const result = validateLLMParams(validParams, 'openai');\n\n // Debug: print validation results if test fails\n if (!result.isValid) {\n console.log('OpenAI validation failed:', JSON.stringify(result, null, 2));\n }\n\n expect(result.isValid).toBe(true);\n expect(result.errors).toHaveLength(0);\n });\n\n it('should detect invalid parameter types', () => {\n const invalidParams = {\n temperature: 'invalid', // should be number\n max_tokens: 2048.5 // should be integer\n };\n\n const result = validateLLMParams(invalidParams, 'openai');\n\n expect(result.isValid).toBe(false);\n expect(result.errors).toHaveLength(2);\n });\n\n it('should detect out-of-range parameter values', () => {\n const outOfRangeParams = {\n temperature: 3.0, // exceeds maximum 2.0\n presence_penalty: -3.0 // below minimum -2.0\n };\n\n const result = validateLLMParams(outOfRangeParams, 'openai');\n\n expect(result.isValid).toBe(false);\n expect(result.errors).toHaveLength(2);\n });\n\n it('should warn about unknown parameters', () => {\n const unknownParams = {\n temperature: 0.7,\n unknown_param: 'value'\n };\n\n const result = validateLLMParams(unknownParams, 'openai');\n\n expect(result.isValid).toBe(true);\n expect(result.warnings).toHaveLength(1);\n expect(result.warnings[0].parameterName).toBe('unknown_param');\n });\n\n it('should validate Gemini-specific parameters', () => {\n const geminiParams = {\n temperature: 0.8,\n maxOutputTokens: 2048,\n topK: 40,\n stopSequences: ['END', 'STOP']\n };\n\n const result = validateLLMParams(geminiParams, 'gemini');\n\n // Debug: print validation results if test fails\n if (!result.isValid) {\n console.log('Gemini validation failed:', JSON.stringify(result, null, 2));\n }\n\n expect(result.isValid).toBe(true);\n expect(result.errors).toHaveLength(0);\n });\n\n it('should validate stopSequences array correctly', () => {\n const invalidStopSequences = {\n stopSequences: 'should_be_array'\n };\n\n const result = validateLLMParams(invalidStopSequences, 'gemini');\n\n expect(result.isValid).toBe(false);\n expect(result.errors[0].parameterName).toBe('stopSequences');\n });\n\n it('should filter unsafe parameters in Gemini configuration', () => {\n // 这里我们测试的是参数验证,虽然buildGeminiGenerationConfig是私有方法\n // 但我们可以通过集成测试来验证它的行为\n const unsafeParams = {\n temperature: 0.8,\n maxOutputTokens: 2048,\n // 这些参数应该被警告或过滤\n dangerousParam: 'malicious_value',\n __proto__: 'attack',\n eval: 'dangerous_code'\n };\n\n const result = validateLLMParams(unsafeParams, 'gemini');\n\n // 验证不安全的参数被拒绝\n expect(result.warnings.length).toBeGreaterThan(0);\n expect(result.warnings.some(w => w.parameterName === 'dangerousParam')).toBe(true);\n });\n });\n\n describe('Individual Parameter Tests', () => {\n const openaiCompatibleConfigs = getAvailableOpenAICompatibleConfigs();\n const geminiConfig = getGeminiConfig();\n\n // Temperature parameter tests\n describe('Temperature Parameter', () => {\n // Test for all OpenAI compatible providers\n openaiCompatibleConfigs.forEach((config) => {\n it(`should accept valid temperature for ${config.provider} provider`, async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n await modelManager.updateModel(config.key, {\n name: `Test ${config.provider} Temperature`,\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n defaultModel: config.defaultModel,\n enabled: true,\n provider: config.provider,\n models: [config.defaultModel],\n llmParams: {\n temperature: 0.3\n }\n });\n\n const messages = [{ role: 'user' as const, content: 'Hello' }];\n\n const response = await llmService.sendMessage(messages, config.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n }, 30000);\n });\n });\n\n // Top P parameter tests\n describe('Top P Parameter', () => {\n // Test for all OpenAI compatible providers\n openaiCompatibleConfigs.forEach((config) => {\n it(`should accept valid top_p for ${config.provider} provider`, async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n await modelManager.updateModel(config.key, {\n name: `Test ${config.provider} Top P`,\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n defaultModel: config.defaultModel,\n enabled: true,\n provider: config.provider,\n models: [config.defaultModel],\n llmParams: {\n top_p: 0.9\n }\n });\n\n const messages = [{ role: 'user' as const, content: 'Hello' }];\n\n const response = await llmService.sendMessage(messages, config.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n }, 30000);\n });\n });\n\n // Max Tokens parameter tests (OpenAI compatible)\n describe('Max Tokens Parameter', () => {\n // Test for all OpenAI compatible providers\n openaiCompatibleConfigs.forEach((config) => {\n it(`should accept valid max_tokens for ${config.provider} provider`, async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n await modelManager.updateModel(config.key, {\n name: `Test ${config.provider} Max Tokens`,\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n defaultModel: config.defaultModel,\n enabled: true,\n provider: config.provider,\n models: [config.defaultModel],\n llmParams: {\n max_tokens: 100\n }\n });\n\n const messages = [{ role: 'user' as const, content: 'Tell me a short fact' }];\n\n const response = await llmService.sendMessage(messages, config.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n }, 30000);\n });\n });\n\n // Frequency Penalty parameter tests\n describe('Frequency Penalty Parameter', () => {\n // Test for all OpenAI compatible providers\n openaiCompatibleConfigs.forEach((config) => {\n it(`should accept valid frequency_penalty for ${config.provider} provider`, async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n await modelManager.updateModel(config.key, {\n name: `Test ${config.provider} Frequency Penalty`,\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n defaultModel: config.defaultModel,\n enabled: true,\n provider: config.provider,\n models: [config.defaultModel],\n llmParams: {\n frequency_penalty: 0.3\n }\n });\n\n const messages = [{ role: 'user' as const, content: 'Hello' }];\n\n const response = await llmService.sendMessage(messages, config.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n }, 60000);\n });\n });\n\n // Gemini specific parameters\n describe('Gemini Specific Parameters', () => {\n beforeEach(async () => {\n await new Promise(resolve => setTimeout(resolve, 10000)); // 等待 10 秒\n });\n\n if (hasGeminiKey && geminiConfig) {\n it('should accept valid maxOutputTokens for Gemini provider', async () => {\n // 添加间隔,避免频率限制,先等10秒\n await new Promise(resolve => setTimeout(resolve, 10000)); \n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n await modelManager.updateModel(geminiConfig.key, {\n name: 'Test Gemini Max Output Tokens',\n apiKey: geminiConfig.apiKey,\n baseURL: geminiConfig.baseURL,\n defaultModel: geminiConfig.defaultModel,\n enabled: true,\n provider: geminiConfig.provider,\n models: [geminiConfig.defaultModel],\n llmParams: {\n maxOutputTokens: 200\n }\n });\n\n const messages = [{ role: 'user' as const, content: 'Tell me about AI' }];\n\n const response = await llmService.sendMessage(messages, geminiConfig.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n }, 60000);\n it('should accept valid candidateCount for Gemini provider', async () => {\n // 添加间隔,避免频率限制,先等10秒\n await new Promise(resolve => setTimeout(resolve, 10000)); \n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n await modelManager.updateModel(geminiConfig.key, {\n name: 'Test Gemini Candidate Count',\n apiKey: geminiConfig.apiKey,\n baseURL: geminiConfig.baseURL,\n defaultModel: geminiConfig.defaultModel,\n enabled: true,\n provider: geminiConfig.provider,\n models: [geminiConfig.defaultModel],\n llmParams: {\n candidateCount: 1\n }\n });\n\n const messages = [{ role: 'user' as const, content: 'Hello' }];\n\n const response = await llmService.sendMessage(messages, geminiConfig.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n }, 60000);\n } else {\n it('should skip Gemini tests when API key is not available', () => {\n expect(true).toBe(true); // 占位测试,确保套件不为空\n });\n }\n });\n\n // Combined parameters tests\n describe('Combined Parameters', () => {\n // Test for all OpenAI compatible providers\n openaiCompatibleConfigs.forEach((config) => {\n it(`should handle multiple parameters for ${config.provider} provider`, async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n await modelManager.updateModel(config.key, {\n name: `Test ${config.provider} Combined`,\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n defaultModel: config.defaultModel,\n enabled: true,\n provider: config.provider,\n models: [config.defaultModel],\n llmParams: {\n temperature: 0.6,\n max_tokens: 50, // 减少token数量以加快响应\n top_p: 0.9,\n presence_penalty: 0.2,\n frequency_penalty: 0.1,\n timeout: 20000 // 减少超时时间\n }\n });\n\n const messages = [{ role: 'user' as const, content: 'Say hello' }]; // 简化请求\n\n const response = await llmService.sendMessage(messages, config.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n }, 45000); // 增加测试超时时间\n });\n });\n });\n\n describe('Edge Cases', () => {\n const openaiCompatibleConfigs = getAvailableOpenAICompatibleConfigs();\n \n it('should handle missing llmParams gracefully', () => {\n const result = validateLLMParams(undefined, 'openai');\n expect(result.isValid).toBe(true);\n expect(result.errors).toHaveLength(0);\n expect(result.warnings).toHaveLength(0);\n });\n\n it('should handle empty llmParams object', () => {\n const result = validateLLMParams({}, 'openai');\n expect(result.isValid).toBe(true);\n expect(result.errors).toHaveLength(0);\n expect(result.warnings).toHaveLength(0);\n });\n\n // Test that no default values are set when parameters are not provided\n describe('No Default Values', () => {\n // Test for all OpenAI compatible providers\n openaiCompatibleConfigs.forEach((config) => {\n it(`should not set default values when not provided for ${config.provider}`, async () => {\n const storage = new LocalStorageProvider();\n const modelManager = new ModelManager(storage);\n await modelManager.ensureInitialized();\n const llmService = createLLMService(modelManager);\n\n await modelManager.updateModel(config.key, {\n name: `Test ${config.provider} No Defaults`,\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n defaultModel: config.defaultModel,\n enabled: true,\n provider: config.provider,\n models: [config.defaultModel],\n // No llmParams provided - testing parameter transparency\n });\n\n const messages = [{ role: 'user' as const, content: 'Hello' }];\n\n const response = await llmService.sendMessage(messages, config.key);\n expect(response).toBeDefined();\n expect(typeof response).toBe('string');\n expect(response.length).toBeGreaterThan(0);\n // Should work fine without any default values being set\n }, 30000);\n });\n });\n });\n}); \n"}}}} |