import assert from 'node:assert/strict'; import { mkdtemp, readFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { runNativeZcode } from '../../services/zcode-agent/runner.mjs'; const stateDir = await mkdtemp('/var/tmp/yedir-native-fixture-'); const calls = []; const events = []; let requests = 0; function completion(body, message, finish) { const base = { id: 'fixture', created: 1, model: 'canvas-proof' }; const usage = { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 }; if (!body.stream) return Response.json({ ...base, object: 'chat.completion', usage, choices: [{ index: 0, message: { role: 'assistant', ...message }, finish_reason: finish }] }); let delta = message; if (message.tool_calls) delta = { tool_calls: message.tool_calls.map((call, index) => ({ index, ...call })) }; const chunks = [{ ...base, object: 'chat.completion.chunk', choices: [{ index: 0, delta: { role: 'assistant', ...delta }, finish_reason: null }] }, { ...base, object: 'chat.completion.chunk', usage, choices: [{ index: 0, delta: {}, finish_reason: finish }] }]; return new Response(chunks.map(value => `data: ${JSON.stringify(value)}\n\n`).join('') + 'data: [DONE]\n\n', { headers: { 'content-type': 'text/event-stream' } }); } try { const result = await runNativeZcode({ runId: 'native-mcp-proof', stateDir, runtimeDir: process.env.ZCODE_FIXTURE_RUNTIME || process.env.CANVAS_ZCODE_RUNTIME_DIR || resolve('.local/zcode-runtime'), nodePath: process.env.ZCODE_FIXTURE_NODE || join(process.env.ZCODE_FIXTURE_RUNTIME || process.env.CANVAS_ZCODE_RUNTIME_DIR || resolve('.local/zcode-runtime'), 'bin/node'), modelId: 'canvas-proof', instructions: 'Use the canvas MCP tools to read the canvas and then rename node a. Do not use Bash. Respond in Chinese.', prompt: '读取画布,然后把节点 a 改名为原生核心测试,实际执行后回复完成。', timeoutMs: 90_000, tools: [{ name: 'readCanvas', description: 'Read actual canvas', readOnly: true, inputSchema: { type: 'object', properties: {}, additionalProperties: false } }, { name: 'renameNode', description: 'Rename actual canvas node', readOnly: false, inputSchema: { type: 'object', properties: { nodeId: { type: 'string' }, title: { type: 'string' } }, required: ['nodeId', 'title'] } }], execute: async (name, input, id) => { calls.push({ name, input, id }); return name === 'readCanvas' ? { nodes: [{ id: 'a', title: 'before' }] } : { updatedNodeIds: ['a'], title: input.title }; }, onEvent: async event => { events.push(event.type); }, onSandbox: async sandbox => console.log('SANDBOX', JSON.stringify(sandbox)), model: async body => { requests++; if (requests > 12) return Response.json({ error: { message: 'fixture budget exceeded' } }, { status: 429 }); const names = (body.tools || []).map(tool => tool.function?.name || tool.name || ''); console.log('MODEL', requests, 'tools', names.filter(name => /readCanvas|renameNode/.test(name))); const hasRead = body.messages?.some(message => message.role === 'tool' && String(message.content).includes('before')); const hasRename = body.messages?.some(message => message.role === 'tool' && String(message.content).includes('updatedNodeIds')); if (!names.some(name => name.includes('readCanvas'))) return completion(body, { content: '模型配置辅助调用完成' }, 'stop'); if (!hasRead || !hasRename) { const name = names.find(name => name.includes(hasRead ? 'renameNode' : 'readCanvas')); return completion(body, { tool_calls: [{ id: hasRead ? 'native_rename' : 'native_read', type: 'function', function: { name, arguments: JSON.stringify(hasRead ? { nodeId: 'a', title: '原生核心测试' } : {}) } }] }, 'tool_calls'); } return completion(body, { content: '已完成节点改名。' }, 'stop'); }, }); assert.deepEqual(calls.map(call => call.name), ['readCanvas', 'renameNode']); assert.match(result.response, /完成/); assert.ok(result.sessionId); const beforeResume = calls.length; const resumed = await runNativeZcode({ runId: 'native-mcp-proof', stateDir, runtimeDir: process.env.ZCODE_FIXTURE_RUNTIME || process.env.CANVAS_ZCODE_RUNTIME_DIR || resolve('.local/zcode-runtime'), nodePath: process.env.ZCODE_FIXTURE_NODE || join(process.env.ZCODE_FIXTURE_RUNTIME || process.env.CANVAS_ZCODE_RUNTIME_DIR || resolve('.local/zcode-runtime'), 'bin/node'), modelId: 'canvas-proof', instructions: 'Reply to the user without changing the canvas.', prompt: '继续会话,确认刚才的修改。', sessionId: result.sessionId, timeoutMs: 60000, tools: [], execute: async () => { throw new Error('must not repeat'); }, model: async body => { assert.ok(JSON.stringify(body.messages).includes('原生核心测试'), 'native session must contain previous tool history'); return completion(body, { content: '已恢复原生会话。' }, 'stop'); } }); assert.equal(resumed.sessionId, result.sessionId); assert.equal(calls.length, beforeResume); console.log('NATIVE_RESUME_PASSED', resumed.sessionId); const controller = new AbortController(); let paidStarted = 0; let paidSettled = 0; await assert.rejects(runNativeZcode({ runId: 'native-cancel-proof', stateDir, runtimeDir: process.env.ZCODE_FIXTURE_RUNTIME || process.env.CANVAS_ZCODE_RUNTIME_DIR || resolve('.local/zcode-runtime'), nodePath: process.env.ZCODE_FIXTURE_NODE || join(process.env.ZCODE_FIXTURE_RUNTIME || process.env.CANVAS_ZCODE_RUNTIME_DIR || resolve('.local/zcode-runtime'), 'bin/node'), modelId: 'canvas-proof', instructions: 'Use only the paidProof MCP tool.', prompt: '执行工具。', timeoutMs: 60000, signal: controller.signal, tools: [{ name: 'paidProof', description: 'Synthetic accepted operation', readOnly: false, inputSchema: { type: 'object', properties: {} } }], execute: async () => { paidStarted++; controller.abort(); await new Promise(resolve => setTimeout(resolve, 300)); paidSettled++; return { createdNodeIds: ['synthetic-proof'] }; }, model: async body => { const name = body.tools?.map(tool => tool.function?.name).find(name => name?.includes('paidProof')); return completion(body, name ? { tool_calls: [{ id: 'accepted_paid_proof', type: 'function', function: { name, arguments: '{}' } }] } : { content: '辅助调用完成' }, name ? 'tool_calls' : 'stop'); } }), /停止/); assert.equal(paidStarted, 1); assert.equal(paidSettled, 1); console.log('NATIVE_CANCEL_DRAIN_PASSED', { paidStarted, paidSettled }); console.log('NATIVE_FIXTURE_PASSED' , JSON.stringify({ calls: calls.map(call => call.name), sessionId: result.sessionId, requests, eventTypes: [...new Set(events)], stateDir })); } catch (error) { console.error('NATIVE_FIXTURE_FAILED', error.message, stateDir); try { const exit = JSON.parse(await readFile(join(stateDir, 'native-mcp-proof/last-exit.json'), 'utf8')); console.error('EXIT', JSON.stringify(exit).replace(/Bearer\s+[\w.-]+|sk-[\w-]+/g, '[redacted]')); } catch {} process.exitCode = 1; }