UNPKG

openai-compatible-task-master

Version:

使用MCP解析PRD文档并生成任务列表

183 lines 7.57 kB
import fetch from 'node-fetch'; import chalk from 'chalk'; import { getCurrentLogLevelNumber } from '../command.js'; // 导入获取日志级别的函数 import util from 'util'; // 导入 util 模块用于格式化 // 日志级别常量(可以从 command.ts 导入,但在这里复制更简单) const LOG_LEVEL_DEBUG = 3; // 自定义流式日志函数 function logStreamChunk(...args) { if (getCurrentLogLevelNumber() >= LOG_LEVEL_DEBUG) { // 模拟 console.log 的格式化行为 const message = util.format(...args); // 使用 process.stdout.write 实现无换行输出 process.stdout.write(message); } } // 解析JSON响应,移除代码块标记 export function cleanJsonContent(jsonContent) { let cleanedContent = jsonContent.trim(); // 检测并移除开头的代码块标记 const startMarkersRegex = /^```(?:json)?[\r\n]+/; if (startMarkersRegex.test(cleanedContent)) { cleanedContent = cleanedContent.replace(startMarkersRegex, ''); } // 检测并移除结尾的代码块标记 const endMarkersRegex = /[\r\n]+```$/; if (endMarkersRegex.test(cleanedContent)) { cleanedContent = cleanedContent.replace(endMarkersRegex, ''); } return cleanedContent; } // 处理任务依赖关系 export function processDependencies(dependencies) { let result = []; if (dependencies) { if (Array.isArray(dependencies)) { result = dependencies .map((dep) => String(dep)) // 转换为字符串 .filter((dep) => dep !== null && dep !== undefined && dep !== ''); } else if (typeof dependencies === 'string') { result = dependencies.split(',') .map((dep) => dep.trim()) .filter((dep) => dep !== null && dep !== undefined && dep !== ''); } } return result; } // 规范化任务数据 export function normalizeTaskData(task) { return { id: String(task.id), // 确保返回字符串类型的 ID title: task.title, description: task.description, status: (task.status || 'pending'), dependencies: processDependencies(task.dependencies), priority: task.priority, details: task.details, testStrategy: task.testStrategy }; } // 处理API错误响应 export async function handleApiError(response) { const errorText = await response.text(); try { const errorJson = JSON.parse(errorText); const detailedMessage = errorJson?.error?.message || errorText; throw new Error(`API调用失败:${response.status} ${response.statusText} - ${detailedMessage}`); } catch { // 如果错误响应不是JSON,则使用原始文本 throw new Error(`API调用失败:${response.status} ${response.statusText} - ${errorText}`); } } // 处理流式响应 export async function handleStreamResponse(responseBody) { if (!responseBody) { throw new Error('API响应体为空,无法处理流式数据'); } let fullContent = ''; let buffer = ''; let isFirstToken = true; let isThinkingStarted = false; let thinkingEnded = false; console.debug(chalk.blueBright('[流式处理开始]')); for await (const chunk of responseBody) { buffer += chunk.toString(); // console.debug(chalk.gray('[原始块]'), chunk.toString().trim()); // 可选:保留原始块调试 // 按SSE事件分割处理 const events = buffer.split('\n\n'); buffer = events.pop() || ''; for (const event of events) { if (event.startsWith('data: ')) { const dataLine = event.substring('data: '.length).trim(); if (dataLine === '[DONE]') { continue; // 流结束标志 } try { const chunkJson = JSON.parse(dataLine); const delta = chunkJson.choices?.[0]?.delta; if (!delta) continue; const contentChunk = delta.content; const reasoningChunk = delta.reasoning_content; if (isFirstToken) { if (reasoningChunk) { logStreamChunk(chalk.magenta('<thinking>')); logStreamChunk(chalk.magenta(reasoningChunk)); isThinkingStarted = true; isFirstToken = false; } else if (contentChunk) { logStreamChunk(chalk.cyan(contentChunk)); fullContent += contentChunk; // 累加到 fullContent isFirstToken = false; } // 如果第一个 delta 为空,则继续等待下一个 } else { // 非第一个 token if (reasoningChunk) { logStreamChunk(chalk.magenta(reasoningChunk)); } else if (contentChunk) { if (isThinkingStarted && !thinkingEnded) { logStreamChunk(chalk.magenta('</thinking>')); thinkingEnded = true; } logStreamChunk(chalk.cyan(contentChunk)); fullContent += contentChunk; // 累加到 fullContent } } } catch (e) { console.warn(chalk.yellow('无法解析流数据块:'), dataLine, e); } } } } if (isThinkingStarted && !thinkingEnded) { logStreamChunk(chalk.magenta('</thinking>')); } if (getCurrentLogLevelNumber() >= LOG_LEVEL_DEBUG) { process.stdout.write('\n'); } console.debug(chalk.blueBright('[流式处理结束]')); // 注意:fullContent 现在只包含 content 部分,不包含 reasoning_content return fullContent; } // OpenAI API调用的通用逻辑 export async function callOpenAIAPI(messages, openaiUrl, apiKey, model, streamMode) { if (!apiKey) { throw new Error('API密钥未提供,请使用 --api-key 参数提供有效的API密钥'); } const requestBody = { model: model, messages: messages, stream: streamMode, }; const response = await fetch(`${openaiUrl}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify(requestBody), }); if (!response.ok) { await handleApiError(response); } if (streamMode && response.body) { // 不再打印完整的 streamResult,因为内容已在 handleStreamResponse 中动态输出 const streamResult = await handleStreamResponse(response.body); // console.debug(chalk.green('[流式处理完成 - 累积内容]'), streamResult); // 可选:打印最终累积的 content return streamResult; } else { const data = await response.json(); const fullContent = data.choices[0]?.message?.content || ''; console.debug(chalk.green('[非流式响应内容]'), fullContent); // 非流式日志保持不变 return fullContent; } } //# sourceMappingURL=llm_utils.js.map