openai-compatible-task-master
Version:
使用MCP解析PRD文档并生成任务列表
183 lines • 7.57 kB
JavaScript
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