File size: 4,954 Bytes
097fb32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
 * test/unit-log-persist-compact.mjs
 *
 * 回归测试:compact 落盘模式应保留摘要信息,同时显著压缩 JSONL payload。
 * 运行方式:npm run build && node test/unit-log-persist-compact.mjs
 */

import fs from 'fs';
import path from 'path';

const LOG_DIR = '/tmp/cursor2api-log-compact';
process.env.LOG_FILE_ENABLED = '1';
process.env.LOG_DIR = LOG_DIR;
process.env.LOG_PERSIST_MODE = 'compact';

const { createRequestLogger, clearAllLogs, getRequestPayload } = await import('../dist/logger.js');

let passed = 0;
let failed = 0;

function assert(condition, msg) {
    if (!condition) throw new Error(msg || 'Assertion failed');
}

function assertEqual(a, b, msg) {
    const as = JSON.stringify(a);
    const bs = JSON.stringify(b);
    if (as !== bs) throw new Error(msg || `Expected ${bs}, got ${as}`);
}

function resetLogs() {
    clearAllLogs();
    fs.rmSync(LOG_DIR, { recursive: true, force: true });
}

function latestPersistedRecord() {
    const files = fs.readdirSync(LOG_DIR).filter(name => name.endsWith('.jsonl')).sort();
    assert(files.length > 0, '应生成 JSONL 文件');
    const lastFile = path.join(LOG_DIR, files[files.length - 1]);
    const lines = fs.readFileSync(lastFile, 'utf8').split('\n').filter(Boolean);
    assert(lines.length > 0, 'JSONL 文件不应为空');
    return JSON.parse(lines[lines.length - 1]);
}

async function runTest(name, fn) {
    try {
        resetLogs();
        await fn();
        console.log(`  ✅  ${name}`);
        passed++;
    } catch (e) {
        console.error(`  ❌  ${name}`);
        console.error(`      ${e.message}`);
        failed++;
    }
}

console.log('\n📦 [1] compact 落盘模式回归\n');

await runTest('磁盘 payload 应截断长文本并去掉重复 rawResponse', async () => {
    const hugePrompt = 'PROMPT-'.repeat(1200);
    const hugeResponse = 'RESPONSE-'.repeat(1600);
    const hugeCursor = 'CURSOR-'.repeat(900);
    const hugeToolDesc = 'DESC-'.repeat(500);

    const logger = createRequestLogger({
        method: 'POST',
        path: '/v1/chat/completions',
        model: 'gpt-4.1',
        stream: true,
        hasTools: true,
        toolCount: 1,
        messageCount: 1,
        apiFormat: 'openai',
    });

    logger.recordOriginalRequest({
        model: 'gpt-4.1',
        stream: true,
        temperature: 0.2,
        messages: [{ role: 'user', content: hugePrompt }],
        tools: [{
            type: 'function',
            function: {
                name: 'write_file',
                description: hugeToolDesc,
            },
        }],
    });
    logger.recordCursorRequest({
        model: 'anthropic/claude-sonnet-4.6',
        messages: [{
            role: 'user',
            parts: [{ type: 'text', text: hugeCursor }],
        }],
    });
    logger.recordToolCalls([{
        name: 'write_file',
        arguments: {
            path: '/tmp/demo.txt',
            content: 'X'.repeat(5000),
        },
    }]);
    logger.recordRawResponse(hugeResponse);
    logger.recordFinalResponse(hugeResponse);
    logger.complete(hugeResponse.length, 'stop');

    const persisted = latestPersistedRecord();
    const diskPayload = persisted.payload;
    const memoryPayload = getRequestPayload(persisted.summary.requestId);

    assert(memoryPayload, '内存 payload 应存在');
    assert(memoryPayload.rawResponse.length > diskPayload.finalResponse.length, '内存 payload 应保留完整文本');
    assertEqual(persisted.summary.status, 'success');

    assert(diskPayload.finalResponse.length < hugeResponse.length, '落盘 finalResponse 应被截断');
    assert(diskPayload.finalResponse.includes('...[截断 '), '落盘 finalResponse 应标记截断');
    assertEqual(diskPayload.rawResponse, undefined, 'rawResponse 与 finalResponse 相同,应省略落盘 rawResponse');

    assert(diskPayload.messages[0].contentPreview.length < hugePrompt.length, '落盘消息预览应被截断');
    assert(diskPayload.messages[0].contentPreview.includes('...[截断 '), '落盘消息预览应标记截断');

    assert(diskPayload.cursorMessages[0].contentPreview.length < hugeCursor.length, '落盘 Cursor 消息应被截断');
    assert(diskPayload.tools[0].description.length < hugeToolDesc.length, '落盘工具描述应被截断');
    assert(diskPayload.originalRequest.messageCount === 1, '落盘 originalRequest 应转为精简 meta');
    assertEqual(Array.isArray(diskPayload.originalRequest.messages), false, '落盘 originalRequest 不应保留完整 messages 数组');

    const compactToolCalls = JSON.stringify(diskPayload.toolCalls);
    assert(compactToolCalls.length < JSON.stringify(memoryPayload.toolCalls).length, '落盘 toolCalls 应被递归压缩');
  });

console.log('\n' + '═'.repeat(55));
console.log(`  结果: ${passed} 通过 / ${failed} 失败 / ${passed + failed} 总计`);
console.log('═'.repeat(55) + '\n');

if (failed > 0) process.exit(1);