pqm-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +254 -0
  2. package/bin/pqm.js +6 -0
  3. package/package.json +31 -0
  4. package/src/ai/analyzer/collector.js +191 -0
  5. package/src/ai/analyzer/dependency.js +269 -0
  6. package/src/ai/analyzer/index.js +234 -0
  7. package/src/ai/analyzer/quality.js +241 -0
  8. package/src/ai/analyzer/security.js +302 -0
  9. package/src/ai/index.js +16 -0
  10. package/src/ai/providers/bailian.js +121 -0
  11. package/src/ai/providers/base.js +177 -0
  12. package/src/ai/providers/deepseek.js +100 -0
  13. package/src/ai/providers/index.js +100 -0
  14. package/src/ai/providers/openai.js +100 -0
  15. package/src/builders/base.js +35 -0
  16. package/src/builders/rollup.js +47 -0
  17. package/src/builders/vite.js +47 -0
  18. package/src/cli.js +41 -0
  19. package/src/commands/ai.js +317 -0
  20. package/src/commands/build.js +24 -0
  21. package/src/commands/commit.js +68 -0
  22. package/src/commands/config.js +113 -0
  23. package/src/commands/doctor.js +146 -0
  24. package/src/commands/init.js +61 -0
  25. package/src/commands/login.js +37 -0
  26. package/src/commands/publish.js +250 -0
  27. package/src/commands/release.js +107 -0
  28. package/src/commands/scan.js +239 -0
  29. package/src/commands/status.js +129 -0
  30. package/src/commands/watch.js +170 -0
  31. package/src/commands/webhook.js +240 -0
  32. package/src/config/detector.js +82 -0
  33. package/src/config/global.js +136 -0
  34. package/src/config/loader.js +49 -0
  35. package/src/core/builder.js +88 -0
  36. package/src/index.js +5 -0
  37. package/src/logs/build.js +47 -0
  38. package/src/logs/manager.js +60 -0
  39. package/src/report/formatter.js +282 -0
  40. package/src/utils/http.js +130 -0
  41. package/src/utils/logger.js +24 -0
  42. package/src/utils/prompt.js +132 -0
  43. package/src/utils/spinner.js +134 -0
@@ -0,0 +1,241 @@
1
+ import { Severity } from '../../report/formatter.js';
2
+
3
+ /**
4
+ * Default configuration for quality analysis
5
+ */
6
+ const CONFIG = {
7
+ maxLineLength: 120,
8
+ maxFunctionLength: 50,
9
+ maxComplexity: 15,
10
+ maxParams: 5,
11
+ maxNestingLevel: 4
12
+ };
13
+
14
+ /**
15
+ * Analyze code quality
16
+ * @param {string} code - Code to analyze
17
+ * @param {string} language - Programming language
18
+ * @returns {Array<Object>} Quality issues
19
+ */
20
+ export function analyzeQualityPatterns(code, language) {
21
+ const issues = [];
22
+ const lines = code.split('\n');
23
+
24
+ // Check line lengths
25
+ lines.forEach((line, index) => {
26
+ // Skip comments and strings for length check
27
+ const trimmed = line.trimEnd();
28
+ if (trimmed.length > CONFIG.maxLineLength) {
29
+ issues.push({
30
+ severity: Severity.LOW,
31
+ type: '行过长',
32
+ line: index + 1,
33
+ message: `行长度 ${trimmed.length} 超过建议值 ${CONFIG.maxLineLength}`,
34
+ suggestion: '将长行拆分为多行,提高可读性'
35
+ });
36
+ }
37
+ });
38
+
39
+ // Check for TODO/FIXME/HACK
40
+ const todoPatterns = [
41
+ { pattern: /\/\/\s*TODO:/gi, type: 'TODO' },
42
+ { pattern: /\/\/\s*FIXME:/gi, type: 'FIXME' },
43
+ { pattern: /\/\/\s*HACK:/gi, type: 'HACK' },
44
+ { pattern: /\/\/\s*XXX:/gi, type: 'XXX' },
45
+ { pattern: /#\s*TODO:/gi, type: 'TODO' },
46
+ { pattern: /#\s*FIXME:/gi, type: 'FIXME' },
47
+ { pattern: /\/\*\s*TODO:/gi, type: 'TODO' }
48
+ ];
49
+
50
+ lines.forEach((line, index) => {
51
+ for (const { pattern, type } of todoPatterns) {
52
+ if (pattern.test(line)) {
53
+ issues.push({
54
+ severity: Severity.INFO,
55
+ type: `${type} 标记`,
56
+ line: index + 1,
57
+ message: `检测到 ${type} 标记`,
58
+ suggestion: '处理待办事项或移除标记'
59
+ });
60
+ }
61
+ }
62
+ });
63
+
64
+ // Check for console.log/debug statements
65
+ const debugPatterns = [
66
+ /console\.log\s*\(/g,
67
+ /console\.debug\s*\(/g,
68
+ /console\.warn\s*\(/g,
69
+ /debugger\s*;?/g
70
+ ];
71
+
72
+ debugPatterns.forEach(pattern => {
73
+ lines.forEach((line, index) => {
74
+ if (pattern.test(line)) {
75
+ // Skip if it's in a comment
76
+ if (line.trim().startsWith('//') || line.trim().startsWith('*')) {
77
+ return;
78
+ }
79
+
80
+ issues.push({
81
+ severity: Severity.INFO,
82
+ type: '调试代码',
83
+ line: index + 1,
84
+ message: '检测到调试语句',
85
+ suggestion: '生产环境应移除或使用日志框架'
86
+ });
87
+ }
88
+ });
89
+ });
90
+
91
+ // Check for empty catch blocks
92
+ const emptyCatchPattern = /catch\s*\([^)]*\)\s*\{\s*\}/g;
93
+ let match;
94
+ while ((match = emptyCatchPattern.exec(code)) !== null) {
95
+ const lineNumber = code.substring(0, match.index).split('\n').length;
96
+ issues.push({
97
+ severity: Severity.MEDIUM,
98
+ type: '空 catch 块',
99
+ line: lineNumber,
100
+ message: '检测到空的 catch 块,可能隐藏错误',
101
+ suggestion: '添加错误处理逻辑或日志'
102
+ });
103
+ }
104
+
105
+ // Check for var keyword in JS
106
+ if (language === 'javascript' || language === 'typescript') {
107
+ const varPattern = /\bvar\s+\w+/g;
108
+ lines.forEach((line, index) => {
109
+ if (varPattern.test(line) && !line.includes('//') && !line.trim().startsWith('*')) {
110
+ issues.push({
111
+ severity: Severity.LOW,
112
+ type: 'var 关键字',
113
+ line: index + 1,
114
+ message: '使用 var 声明变量可能导致作用域问题',
115
+ suggestion: '使用 const 或 let 替代'
116
+ });
117
+ }
118
+ });
119
+ }
120
+
121
+ // Check for == instead of ===
122
+ if (language === 'javascript' || language === 'typescript') {
123
+ const looseEqPattern = /[^=!]==[^=]/g;
124
+ lines.forEach((line, index) => {
125
+ // Skip comments
126
+ const commentIndex = line.indexOf('//');
127
+ const codePart = commentIndex >= 0 ? line.substring(0, commentIndex) : line;
128
+
129
+ if (looseEqPattern.test(codePart)) {
130
+ issues.push({
131
+ severity: Severity.LOW,
132
+ type: '宽松相等',
133
+ line: index + 1,
134
+ message: '使用 == 可能导致类型转换问题',
135
+ suggestion: '使用 === 进行严格相等比较'
136
+ });
137
+ }
138
+ });
139
+ }
140
+
141
+ // Check function length (simplified)
142
+ const functionPattern = /function\s+\w+\s*\([^)]*\)\s*\{((?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*)\}/g;
143
+ while ((match = functionPattern.exec(code)) !== null) {
144
+ const functionBody = match[1];
145
+ const bodyLines = functionBody.split('\n').length;
146
+ const lineNumber = code.substring(0, match.index).split('\n').length;
147
+
148
+ if (bodyLines > CONFIG.maxFunctionLength) {
149
+ issues.push({
150
+ severity: Severity.LOW,
151
+ type: '函数过长',
152
+ line: lineNumber,
153
+ message: `函数体 ${bodyLines} 行,超过建议值 ${CONFIG.maxFunctionLength}`,
154
+ suggestion: '拆分函数,保持单一职责'
155
+ });
156
+ }
157
+ }
158
+
159
+ // Check for deeply nested code (simplified)
160
+ let maxIndent = 0;
161
+ lines.forEach((line, index) => {
162
+ if (line.trim().length === 0) return;
163
+
164
+ const indent = line.search(/\S|$/);
165
+ const nestingLevel = Math.floor(indent / 2);
166
+
167
+ if (nestingLevel > CONFIG.maxNestingLevel) {
168
+ issues.push({
169
+ severity: Severity.LOW,
170
+ type: '嵌套过深',
171
+ line: index + 1,
172
+ message: `嵌套深度 ${nestingLevel},超过建议值 ${CONFIG.maxNestingLevel}`,
173
+ suggestion: '重构代码,减少嵌套层级'
174
+ });
175
+ }
176
+ });
177
+
178
+ return issues;
179
+ }
180
+
181
+ /**
182
+ * Analyze code quality with AI (optional)
183
+ * @param {Object} provider - AI provider instance
184
+ * @param {string} code - Code to analyze
185
+ * @param {string} language - Programming language
186
+ * @returns {Promise<Array<Object>>} Quality issues
187
+ */
188
+ export async function analyzeQualityWithAI(provider, code, language) {
189
+ if (!provider) {
190
+ return [];
191
+ }
192
+
193
+ try {
194
+ const result = await provider.analyzeCode(code, language, { type: 'quality' });
195
+
196
+ if (result.raw) {
197
+ return [{
198
+ severity: Severity.INFO,
199
+ type: 'AI 分析',
200
+ line: 1,
201
+ message: result.analysis,
202
+ suggestion: ''
203
+ }];
204
+ }
205
+
206
+ return result.issues || [];
207
+ } catch (error) {
208
+ return [{
209
+ severity: Severity.LOW,
210
+ type: 'AI 分析错误',
211
+ line: 1,
212
+ message: `AI 分析失败: ${error.message}`,
213
+ suggestion: '请检查网络连接和 API 配置'
214
+ }];
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Run quality analysis
220
+ * @param {string} code - Code to analyze
221
+ * @param {string} language - Programming language
222
+ * @param {Object} options - Analysis options
223
+ * @returns {Array<Object>} Quality issues
224
+ */
225
+ export async function runQualityAnalysis(code, language, options = {}) {
226
+ const patternIssues = analyzeQualityPatterns(code, language);
227
+
228
+ let aiIssues = [];
229
+ if (options.useAI && options.provider) {
230
+ aiIssues = await analyzeQualityWithAI(options.provider, code, language);
231
+ }
232
+
233
+ return [...patternIssues, ...aiIssues];
234
+ }
235
+
236
+ export default {
237
+ analyzeQualityPatterns,
238
+ analyzeQualityWithAI,
239
+ runQualityAnalysis,
240
+ CONFIG
241
+ };
@@ -0,0 +1,302 @@
1
+ import { Severity } from '../../report/formatter.js';
2
+
3
+ /**
4
+ * Security vulnerability patterns
5
+ */
6
+ const PATTERNS = {
7
+ // XSS vulnerabilities
8
+ xss: [
9
+ {
10
+ pattern: /innerHTML\s*=\s*[^;]*\+/gi,
11
+ message: '使用 innerHTML 拼接内容可能导致 XSS 攻击',
12
+ suggestion: '使用 textContent 或创建 DOM 节点来设置内容'
13
+ },
14
+ {
15
+ pattern: /document\.write\s*\(/gi,
16
+ message: '使用 document.write 可能导致 XSS 攻击',
17
+ suggestion: '使用 DOM 操作方法替代 document.write'
18
+ },
19
+ {
20
+ pattern: /\.html\s*\(\s*[^)]*\+/gi,
21
+ message: 'jQuery .html() 拼接内容可能导致 XSS 攻击',
22
+ suggestion: '使用 .text() 或对内容进行转义处理'
23
+ },
24
+ {
25
+ pattern: /dangerouslySetInnerHTML/gi,
26
+ message: 'React dangerouslySetInnerHTML 可能导致 XSS 攻击',
27
+ suggestion: '避免使用 dangerouslySetInnerHTML,或确保内容已转义'
28
+ }
29
+ ],
30
+
31
+ // SQL injection
32
+ sqlInjection: [
33
+ {
34
+ pattern: /query\s*\(\s*[`'"]\s*SELECT.*\$\{.*\}/gis,
35
+ message: 'SQL 查询拼接变量可能导致 SQL 注入',
36
+ suggestion: '使用参数化查询或 ORM'
37
+ },
38
+ {
39
+ pattern: /\.query\s*\(\s*[`'"].*\+/gi,
40
+ message: 'SQL 查询拼接字符串可能导致 SQL 注入',
41
+ suggestion: '使用参数化查询,避免直接拼接用户输入'
42
+ },
43
+ {
44
+ pattern: /execute\s*\(\s*[`'"].*\+/gi,
45
+ message: 'SQL 执行拼接字符串可能导致 SQL 注入',
46
+ suggestion: '使用参数化查询'
47
+ }
48
+ ],
49
+
50
+ // Command injection
51
+ commandInjection: [
52
+ {
53
+ pattern: /exec\s*\(\s*[`'"].*\+/gi,
54
+ message: '命令执行拼接字符串可能导致命令注入',
55
+ suggestion: '使用 child_process.spawn 并传递参数数组'
56
+ },
57
+ {
58
+ pattern: /execSync\s*\(\s*[`'"].*\+/gi,
59
+ message: '同步命令执行拼接字符串可能导致命令注入',
60
+ suggestion: '使用 child_process.spawnSync 并传递参数数组'
61
+ },
62
+ {
63
+ pattern: /spawn\s*\([^)]*\+/gi,
64
+ message: 'spawn 拼接命令可能导致命令注入',
65
+ suggestion: '分离命令和参数,使用数组形式传递'
66
+ }
67
+ ],
68
+
69
+ // Dangerous eval
70
+ evalUsage: [
71
+ {
72
+ pattern: /\beval\s*\(/gi,
73
+ message: '使用 eval 可能执行恶意代码',
74
+ suggestion: '避免使用 eval,使用 JSON.parse 解析 JSON'
75
+ },
76
+ {
77
+ pattern: /new\s+Function\s*\(/gi,
78
+ message: '使用 new Function 可能执行恶意代码',
79
+ suggestion: '避免动态创建函数'
80
+ },
81
+ {
82
+ pattern: /setTimeout\s*\(\s*[`'"]/gi,
83
+ message: 'setTimeout 传递字符串参数可能执行恶意代码',
84
+ suggestion: '传递函数引用而非字符串'
85
+ },
86
+ {
87
+ pattern: /setInterval\s*\(\s*[`'"]/gi,
88
+ message: 'setInterval 传递字符串参数可能执行恶意代码',
89
+ suggestion: '传递函数引用而非字符串'
90
+ }
91
+ ],
92
+
93
+ // Hardcoded secrets
94
+ hardcodedSecrets: [
95
+ {
96
+ pattern: /password\s*[:=]\s*['"][^'"]+['"]/gi,
97
+ message: '检测到硬编码密码',
98
+ suggestion: '从环境变量或配置文件读取密码'
99
+ },
100
+ {
101
+ pattern: /api[_-]?key\s*[:=]\s*['"][^'"]+['"]/gi,
102
+ message: '检测到硬编码 API Key',
103
+ suggestion: '从环境变量读取 API Key'
104
+ },
105
+ {
106
+ pattern: /secret[_-]?key\s*[:=]\s*['"][^'"]+['"]/gi,
107
+ message: '检测到硬编码密钥',
108
+ suggestion: '从环境变量或密钥管理服务读取'
109
+ },
110
+ {
111
+ pattern: /token\s*[:=]\s*['"]\w{16,}['"]/gi,
112
+ message: '检测到硬编码 Token',
113
+ suggestion: '从环境变量读取 Token'
114
+ },
115
+ {
116
+ pattern: /private[_-]?key\s*[:=]\s*['"][^'"]+['"]/gi,
117
+ message: '检测到硬编码私钥',
118
+ suggestion: '从安全存储读取私钥'
119
+ }
120
+ ],
121
+
122
+ // Path traversal
123
+ pathTraversal: [
124
+ {
125
+ pattern: /path\.join\s*\([^)]*\+/gi,
126
+ message: '路径拼接用户输入可能导致路径遍历攻击',
127
+ suggestion: '验证和清理用户输入,使用 path.resolve'
128
+ },
129
+ {
130
+ pattern: /\.+\//gi,
131
+ message: '检测到可能的路径遍历模式',
132
+ suggestion: '验证路径,确保不包含 ../ 等危险模式'
133
+ }
134
+ ],
135
+
136
+ // Insecure deserialization
137
+ insecureDeserialization: [
138
+ {
139
+ pattern: /yaml\.load\s*\(/gi,
140
+ message: 'yaml.load 可能导致不安全的反序列化',
141
+ suggestion: '使用 yaml.safeLoad 并限制对象类型'
142
+ },
143
+ {
144
+ pattern: /pickle\.loads?\s*\(/gi,
145
+ message: 'pickle 可能导致不安全的反序列化',
146
+ suggestion: '避免 pickle,使用 JSON 等 safer 格式'
147
+ }
148
+ ]
149
+ };
150
+
151
+ /**
152
+ * Analyze code for security vulnerabilities
153
+ * @param {string} code - Code to analyze
154
+ * @param {string} language - Programming language
155
+ * @returns {Array<Object>} List of security issues
156
+ */
157
+ export function analyzeSecurityPatterns(code, language) {
158
+ const issues = [];
159
+ const lines = code.split('\n');
160
+
161
+ for (const [type, patterns] of Object.entries(PATTERNS)) {
162
+ for (const { pattern, message, suggestion } of patterns) {
163
+ let match;
164
+ const regex = new RegExp(pattern.source, pattern.flags);
165
+
166
+ while ((match = regex.exec(code)) !== null) {
167
+ // Line number
168
+ const lineNumber = code.substring(0, match.index).split('\n').length;
169
+
170
+ // Determine severity
171
+ const severity = getSeverity(type);
172
+
173
+ issues.push({
174
+ severity,
175
+ type: getTypeName(type),
176
+ line: lineNumber,
177
+ message,
178
+ suggestion,
179
+ match: match[0].substring(0, 50)
180
+ });
181
+ }
182
+ }
183
+ }
184
+
185
+ return issues;
186
+ }
187
+
188
+ /**
189
+ * Get severity based on vulnerability type
190
+ * @param {string} type - Vulnerability type
191
+ * @returns {string} Severity level
192
+ */
193
+ function getSeverity(type) {
194
+ const critical = ['evalUsage', 'commandInjection', 'sqlInjection'];
195
+ const high = ['xss', 'hardcodedSecrets'];
196
+ const medium = ['pathTraversal', 'insecureDeserialization'];
197
+
198
+ if (critical.includes(type)) return Severity.CRITICAL;
199
+ if (high.includes(type)) return Severity.HIGH;
200
+ if (medium.includes(type)) return Severity.MEDIUM;
201
+ return Severity.LOW;
202
+ }
203
+
204
+ /**
205
+ * Get human readable type name
206
+ * @param {string} type - Internal type name
207
+ * @returns {string} Human readable name
208
+ */
209
+ function getTypeName(type) {
210
+ const names = {
211
+ xss: 'XSS 跨站脚本',
212
+ sqlInjection: 'SQL 注入',
213
+ commandInjection: '命令注入',
214
+ evalUsage: '危险 eval',
215
+ hardcodedSecrets: '硬编码密钥',
216
+ pathTraversal: '路径遍历',
217
+ insecureDeserialization: '不安全反序列化'
218
+ };
219
+
220
+ return names[type] || type;
221
+ }
222
+
223
+ /**
224
+ * Analyze security with AI (optional)
225
+ * @param {Object} provider - AI provider instance
226
+ * @param {string} code - Code to analyze
227
+ * @param {string} language - Programming language
228
+ * @returns {Promise<Array<Object>>} Security issues
229
+ */
230
+ export async function analyzeWithAI(provider, code, language) {
231
+ if (!provider) {
232
+ return [];
233
+ }
234
+
235
+ try {
236
+ const result = await provider.analyzeCode(code, language, { type: 'security' });
237
+
238
+ if (result.raw) {
239
+ // If AI returned raw text, try to extract issues
240
+ return [{
241
+ severity: Severity.INFO,
242
+ type: 'AI 分析',
243
+ line: 1,
244
+ message: result.analysis,
245
+ suggestion: ''
246
+ }];
247
+ }
248
+
249
+ return result.issues || [];
250
+ } catch (error) {
251
+ return [{
252
+ severity: Severity.LOW,
253
+ type: 'AI 分析错误',
254
+ line: 1,
255
+ message: `AI 分析失败: ${error.message}`,
256
+ suggestion: '请检查网络连接和 API 配置'
257
+ }];
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Run security analysis
263
+ * @param {string} code - Code to analyze
264
+ * @param {string} language - Programming language
265
+ * @param {Object} options - Analysis options
266
+ * @returns {Array<Object>} Security issues
267
+ */
268
+ export async function runSecurityAnalysis(code, language, options = {}) {
269
+ // First run pattern-based analysis
270
+ const patternIssues = analyzeSecurityPatterns(code, language);
271
+
272
+ // Optionally run AI analysis
273
+ let aiIssues = [];
274
+ if (options.useAI && options.provider) {
275
+ aiIssues = await analyzeWithAI(options.provider, code, language);
276
+ }
277
+
278
+ // Combine and deduplicate
279
+ const allIssues = [...patternIssues, ...aiIssues];
280
+
281
+ // Sort by severity
282
+ const severityOrder = {
283
+ [Severity.CRITICAL]: 0,
284
+ [Severity.HIGH]: 1,
285
+ [Severity.MEDIUM]: 2,
286
+ [Severity.LOW]: 3,
287
+ [Severity.INFO]: 4
288
+ };
289
+
290
+ allIssues.sort((a, b) => {
291
+ return (severityOrder[a.severity] || 5) - (severityOrder[b.severity] || 5);
292
+ });
293
+
294
+ return allIssues;
295
+ }
296
+
297
+ export default {
298
+ analyzeSecurityPatterns,
299
+ analyzeWithAI,
300
+ runSecurityAnalysis,
301
+ PATTERNS
302
+ };
@@ -0,0 +1,16 @@
1
+ import { createProvider, getSupportedProviders } from './providers/index.js';
2
+ import { createAnalyzer, AnalysisType } from './analyzer/index.js';
3
+
4
+ export {
5
+ createProvider,
6
+ getSupportedProviders,
7
+ createAnalyzer,
8
+ AnalysisType
9
+ };
10
+
11
+ export default {
12
+ createProvider,
13
+ getSupportedProviders,
14
+ createAnalyzer,
15
+ AnalysisType
16
+ };
@@ -0,0 +1,121 @@
1
+ import { BaseAIProvider } from './base.js';
2
+ import { post } from '../../utils/http.js';
3
+
4
+ /**
5
+ * Alibaba Cloud Bailian (阿里云百炼) Provider
6
+ * API Documentation: https://help.aliyun.com/document_detail/2712195.html
7
+ */
8
+ export class BailianProvider extends BaseAIProvider {
9
+ constructor(config = {}) {
10
+ super(config);
11
+ this.endpoint = config.endpoint || 'https://dashscope.aliyuncs.com/api/v1';
12
+ this.model = config.model || 'qwen-turbo';
13
+ }
14
+
15
+ getDefaultModel() {
16
+ return 'qwen-turbo';
17
+ }
18
+
19
+ getDefaultEndpoint() {
20
+ return 'https://dashscope.aliyuncs.com/api/v1';
21
+ }
22
+
23
+ async chat(prompt, options = {}) {
24
+ const url = `${this.endpoint}/services/aigc/text-generation/generation`;
25
+
26
+ const body = {
27
+ model: this.model,
28
+ input: {
29
+ messages: [
30
+ {
31
+ role: 'user',
32
+ content: prompt
33
+ }
34
+ ]
35
+ },
36
+ parameters: {
37
+ max_tokens: options.maxTokens || 4096,
38
+ temperature: options.temperature || 0.7,
39
+ result_format: 'message'
40
+ }
41
+ };
42
+
43
+ const headers = {
44
+ 'Authorization': `Bearer ${this.apiKey}`,
45
+ 'Content-Type': 'application/json'
46
+ };
47
+
48
+ try {
49
+ const response = await post(url, body, headers, 60000);
50
+
51
+ // Handle different response formats
52
+ if (response.output?.text) {
53
+ return response.output.text;
54
+ }
55
+
56
+ if (response.output?.choices?.[0]?.message?.content) {
57
+ return response.output.choices[0].message.content;
58
+ }
59
+
60
+ if (response.choices?.[0]?.message?.content) {
61
+ return response.choices[0].message.content;
62
+ }
63
+
64
+ throw new Error('Unexpected response format from Bailian API');
65
+ } catch (error) {
66
+ throw new Error(`Bailian API error: ${error.message}`);
67
+ }
68
+ }
69
+
70
+ async validateConfig() {
71
+ if (!this.apiKey) {
72
+ return false;
73
+ }
74
+
75
+ // Basic API key format check for Bailian
76
+ if (!this.apiKey.startsWith('sk-')) {
77
+ return false;
78
+ }
79
+
80
+ try {
81
+ // Try a minimal request to validate
82
+ const url = `${this.endpoint}/services/aigc/text-generation/generation`;
83
+ const body = {
84
+ model: this.model,
85
+ input: {
86
+ messages: [{ role: 'user', content: 'Hi' }]
87
+ },
88
+ parameters: {
89
+ max_tokens: 10,
90
+ result_format: 'message'
91
+ }
92
+ };
93
+
94
+ const headers = {
95
+ 'Authorization': `Bearer ${this.apiKey}`,
96
+ 'Content-Type': 'application/json'
97
+ };
98
+
99
+ await post(url, body, headers, 10000);
100
+ return true;
101
+ } catch (error) {
102
+ // If we get a 400 or 401, the key format is wrong
103
+ // If we get other errors, the endpoint might be working but other issues
104
+ return !error.message.includes('HTTP 401') && !error.message.includes('HTTP 403');
105
+ }
106
+ }
107
+
108
+ getInfo() {
109
+ return {
110
+ name: 'bailian',
111
+ displayName: '阿里云百炼',
112
+ model: this.model,
113
+ models: ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-max-longcontext'],
114
+ endpoint: this.endpoint,
115
+ configured: !!this.apiKey,
116
+ free: true // 百炼有免费额度
117
+ };
118
+ }
119
+ }
120
+
121
+ export default BailianProvider;