You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
3.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 使用 Cursor TypeScript SDK 一键调用大模型,按 Skill 约定生成 lab-eval-ai.json
* 依赖CURSOR_API_KEY可选 LAB_EVAL_MODEL默认 composer-2
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import { Agent, CursorAgentError } from '@cursor/sdk';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, '..');
const require = createRequire(import.meta.url);
const { analyzeChatLogsFile } = require(path.join(ROOT, 'lib/analyzeChatLogs.js'));
const { resolveChatLogPath } = require(path.join(ROOT, 'lib/resolveChatLogPath.cjs'));
function readText(p) {
return fs.readFileSync(p, 'utf8');
}
function extractJson(text) {
if (text == null) throw new Error('empty model output');
const s = typeof text === 'string' ? text : JSON.stringify(text);
const fence = s.match(/```(?:json)?\s*([\s\S]*?)```/i);
if (fence) return JSON.parse(fence[1].trim());
const start = s.indexOf('{');
const end = s.lastIndexOf('}');
if (start >= 0 && end > start) return JSON.parse(s.slice(start, end + 1));
throw new Error('Could not parse JSON from model output');
}
function slimReport(full) {
const max = 48000;
const str = JSON.stringify(full, null, 2);
if (str.length <= max) return str;
return `${str.slice(0, max)}\n…(truncated)…\n`;
}
async function main() {
const apiKey = process.env.CURSOR_API_KEY;
if (!apiKey) {
console.error('缺少环境变量 CURSOR_API_KEY。获取方式https://cursor.com/dashboard/cloud-agents');
process.exit(1);
}
const chatPath = resolveChatLogPath(ROOT);
if (!fs.existsSync(chatPath)) {
console.error(`缺少聊天日志文件: ${chatPath}(可设置环境变量 CHAT_LOG_PATH`);
process.exit(1);
}
const taskMd = readText(path.join(ROOT, 'config/lab-task-description.md'));
const dims = readText(path.join(ROOT, 'config/evaluation-dimensions.yaml'));
const skillPath = path.join(ROOT, '.cursor/skills/student-lab-ai-evaluation/SKILL.md');
const skill = fs.existsSync(skillPath) ? readText(skillPath) : '';
let heuristic;
try {
heuristic = analyzeChatLogsFile(chatPath);
} catch (e) {
console.error(e);
process.exit(1);
}
if (!heuristic.ok) {
console.error(heuristic.error || '解析 chat_logs 失败');
process.exit(1);
}
const prompt = `你是课程助教。请严格遵守下列 SKILL 中的输出约束(只输出一个 JSON 对象,不要其它文字)。\n\n--- SKILL 开始 ---\n${skill}\n--- SKILL 结束 ---\n\n--- 实训任务描述 ---\n${taskMd}\n\n--- 评价维度配置YAML---\n${dims}\n\n--- 启发式解析 JSON来自 chat_logs仅供参考---\n${slimReport(
heuristic
)}\n\n请根据以上材料生成评价 JSON。abilities 必须与 evaluation-dimensions.yaml 中的维度 id 对齐。`;
const modelId = process.env.LAB_EVAL_MODEL || 'composer-2';
let result;
try {
result = await Agent.prompt(prompt, {
apiKey,
model: { id: modelId },
local: { cwd: ROOT },
});
} catch (err) {
if (err instanceof CursorAgentError) {
console.error('Agent 启动失败:', err.message, 'retryable=', err.isRetryable);
process.exit(1);
}
throw err;
}
if (result.status === 'error') {
console.error('Agent 运行结束为 error:', result);
process.exit(2);
}
const raw = result.result;
let json;
try {
json = extractJson(raw);
} catch (e) {
console.error('解析 JSON 失败:', e.message);
console.error('模型原始输出(前 2500 字符):\n', String(raw).slice(0, 2500));
process.exit(3);
}
const outPath = path.join(ROOT, 'lab-eval-ai.json');
fs.writeFileSync(outPath, JSON.stringify(json, null, 2), 'utf8');
console.log('已写入', outPath);
console.log('刷新 http://127.0.0.1:53780/ 的作业评价区即可看到合并后的内容。');
}
main().catch((e) => {
console.error(e);
process.exit(1);
});