Spaces:
Sleeping
Sleeping
| /** | |
| * 端到端测试:向真实 Cursor2API 服务发送请求 | |
| * | |
| * 测试场景: | |
| * 1. 简单请求能正常返回 | |
| * 2. 带工具的多轮长对话触发压缩 | |
| * 3. 验证 stop_reason 正确 | |
| */ | |
| const API_URL = 'http://localhost:3010/v1/messages'; | |
| interface TestResult { | |
| name: string; | |
| passed: boolean; | |
| detail: string; | |
| } | |
| const results: TestResult[] = []; | |
| function assert(name: string, condition: boolean, detail = '') { | |
| results.push({ name, passed: condition, detail }); | |
| console.log(condition ? ` ✅ ${name}` : ` ❌ ${name}: ${detail}`); | |
| } | |
| // 构造一个模拟 Claude Code 的长对话请求(带很多轮工具交互历史) | |
| function buildLongToolRequest(turnCount: number) { | |
| const messages: any[] = []; | |
| // 模拟多轮工具交互历史 | |
| for (let i = 0; i < turnCount; i++) { | |
| if (i === 0) { | |
| // 第一轮:用户发起请求 | |
| messages.push({ | |
| role: 'user', | |
| content: 'Help me analyze the project structure. Read the main entry file first.' | |
| }); | |
| } else { | |
| // 工具结果 | |
| messages.push({ | |
| role: 'user', | |
| content: [ | |
| { | |
| type: 'tool_result', | |
| tool_use_id: `tool_${i}`, | |
| content: `File content of module${i}.ts:\n` + | |
| `import { something } from './utils';\n\n` + | |
| `export class Module${i} {\n` + | |
| Array.from({length: 30}, (_, j) => ` method${j}() { return ${j}; }`).join('\n') + | |
| `\n}\n` | |
| } | |
| ] | |
| }); | |
| } | |
| // 助手的工具调用 | |
| messages.push({ | |
| role: 'assistant', | |
| content: [ | |
| { type: 'text', text: `Let me check module${i + 1}.` }, | |
| { | |
| type: 'tool_use', | |
| id: `tool_${i + 1}`, | |
| name: 'Read', | |
| input: { file_path: `src/module${i + 1}.ts` } | |
| } | |
| ] | |
| }); | |
| } | |
| // 最后一轮工具结果 | |
| messages.push({ | |
| role: 'user', | |
| content: [ | |
| { | |
| type: 'tool_result', | |
| tool_use_id: `tool_${turnCount}`, | |
| content: 'File not found: src/module' + turnCount + '.ts' | |
| } | |
| ] | |
| }); | |
| return { | |
| model: 'claude-sonnet-4-20250514', | |
| max_tokens: 4096, | |
| stream: false, | |
| system: 'You are a helpful coding assistant.', | |
| tools: [ | |
| { | |
| name: 'Read', | |
| description: 'Read a file from disk', | |
| input_schema: { | |
| type: 'object', | |
| properties: { | |
| file_path: { type: 'string', description: 'Path to the file' } | |
| }, | |
| required: ['file_path'] | |
| } | |
| }, | |
| { | |
| name: 'Bash', | |
| description: 'Execute a shell command', | |
| input_schema: { | |
| type: 'object', | |
| properties: { | |
| command: { type: 'string', description: 'The command to execute' } | |
| }, | |
| required: ['command'] | |
| } | |
| } | |
| ], | |
| messages | |
| }; | |
| } | |
| async function runTests() { | |
| console.log('\n=== 测试 1:基本请求 ==='); | |
| try { | |
| const resp = await fetch(API_URL, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json', 'x-api-key': 'test' }, | |
| body: JSON.stringify({ | |
| model: 'claude-sonnet-4-20250514', | |
| max_tokens: 1024, | |
| stream: false, | |
| messages: [{ role: 'user', content: 'Say "hello" in one word.' }] | |
| }) | |
| }); | |
| assert('服务器响应', resp.ok, `status=${resp.status}`); | |
| const data = await resp.json(); | |
| assert('返回 message 类型', data.type === 'message', `type=${data.type}`); | |
| assert('stop_reason 是 end_turn', data.stop_reason === 'end_turn', `stop_reason=${data.stop_reason}`); | |
| assert('有 content', data.content?.length > 0, `content=${JSON.stringify(data.content)}`); | |
| console.log(` 📝 响应: ${data.content?.[0]?.text?.substring(0, 100)}`); | |
| } catch (e: any) { | |
| assert('基本请求', false, e.message); | |
| } | |
| console.log('\n=== 测试 2:长对话工具请求(触发压缩)==='); | |
| try { | |
| const longReq = buildLongToolRequest(18); // 18 轮 → 37 条消息 | |
| console.log(` 📊 发送 ${longReq.messages.length} 条消息...`); | |
| const resp = await fetch(API_URL, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json', 'x-api-key': 'test' }, | |
| body: JSON.stringify(longReq) | |
| }); | |
| assert('长对话服务器响应', resp.ok, `status=${resp.status}`); | |
| const data = await resp.json(); | |
| assert('长对话返回 message', data.type === 'message', `type=${data.type}`); | |
| assert('长对话有 content', data.content?.length > 0); | |
| // 检查 stop_reason | |
| const validStops = ['end_turn', 'tool_use', 'max_tokens']; | |
| assert('stop_reason 合法', validStops.includes(data.stop_reason), `stop_reason=${data.stop_reason}`); | |
| console.log(` 📝 stop_reason: ${data.stop_reason}`); | |
| console.log(` 📝 content blocks: ${data.content?.length}`); | |
| if (data.content?.[0]?.text) { | |
| console.log(` 📝 响应片段: ${data.content[0].text.substring(0, 150)}...`); | |
| } | |
| } catch (e: any) { | |
| assert('长对话请求', false, e.message); | |
| } | |
| console.log('\n=== 测试 3:流式请求 ==='); | |
| try { | |
| const resp = await fetch(API_URL, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json', 'x-api-key': 'test' }, | |
| body: JSON.stringify({ | |
| model: 'claude-sonnet-4-20250514', | |
| max_tokens: 1024, | |
| stream: true, | |
| messages: [{ role: 'user', content: 'Say "world" in one word.' }] | |
| }) | |
| }); | |
| assert('流式响应 200', resp.ok, `status=${resp.status}`); | |
| assert('Content-Type 是 SSE', resp.headers.get('content-type')?.includes('text/event-stream') ?? false); | |
| const body = await resp.text(); | |
| const events = body.split('\n').filter(l => l.startsWith('event:')); | |
| assert('有 SSE 事件', events.length > 0, `events=${events.length}`); | |
| assert('包含 message_start', body.includes('message_start')); | |
| assert('包含 message_stop', body.includes('message_stop')); | |
| // 检查 stop_reason | |
| const deltaMatch = body.match(/"stop_reason"\s*:\s*"([^"]+)"/); | |
| if (deltaMatch) { | |
| assert('流式 stop_reason 合法', ['end_turn', 'tool_use', 'max_tokens'].includes(deltaMatch[1]), `stop_reason=${deltaMatch[1]}`); | |
| } | |
| console.log(` 📝 SSE 事件数: ${events.length}`); | |
| } catch (e: any) { | |
| assert('流式请求', false, e.message); | |
| } | |
| // 总结 | |
| const passed = results.filter(r => r.passed).length; | |
| const failed = results.filter(r => !r.passed).length; | |
| console.log(`\n=== 端到端结果: ${passed} 通过, ${failed} 失败 ===\n`); | |
| } | |
| runTests().catch(console.error); | |