const http = require('http'); const { spawn } = require('child_process'); const PORT = 8000; const MODEL_ID = 'kimi-agent'; const TIMEOUT_MS = 15 * 60 * 1000; function lastUserMessage(messages) { for (let i = messages.length - 1; i >= 0; i--) { if (messages[i].role === 'user') { const c = messages[i].content; return Array.isArray(c) ? c.map(p => p.text || '').join('\n') : String(c); } } return ''; } function runKimi(prompt) { return new Promise((resolve, reject) => { const child = spawn( 'kimi', ['-p', prompt, '--output-format', 'text'], { cwd: '/workspace', timeout: TIMEOUT_MS } ); let stdout = ''; let stderr = ''; child.stdout.on('data', d => { stdout += d; }); child.stderr.on('data', d => { stderr += d; }); child.on('error', reject); child.on('close', code => { if (code !== 0 && !stdout.trim()) { reject(new Error(`kimi exited ${code}: ${stderr.slice(0, 2000)}`)); } else { resolve(stdout.trim()); } }); }); } function chatCompletion(text) { return { id: `chatcmpl-${Date.now()}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model: MODEL_ID, choices: [{ index: 0, message: { role: 'assistant', content: text }, finish_reason: 'stop', }], }; } const server = http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/v1/models') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ object: 'list', data: [{ id: MODEL_ID, object: 'model', owned_by: 'moonshot' }], })); return; } if (req.method === 'POST' && req.url === '/v1/chat/completions') { let body = ''; req.on('data', d => { body += d; }); req.on('end', async () => { let parsed; try { parsed = JSON.parse(body); } catch { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'invalid JSON body' })); return; } const prompt = lastUserMessage(parsed.messages || []); if (!prompt) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'no user message found' })); return; } try { const text = await runKimi(prompt); if (parsed.stream) { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }); const chunk = { id: `chatcmpl-${Date.now()}`, object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: MODEL_ID, choices: [{ index: 0, delta: { role: 'assistant', content: text }, finish_reason: null }], }; res.write(`data: ${JSON.stringify(chunk)}\n\n`); const doneChunk = { id: chunk.id, object: 'chat.completion.chunk', created: chunk.created, model: MODEL_ID, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }], }; res.write(`data: ${JSON.stringify(doneChunk)}\n\n`); res.write('data: [DONE]\n\n'); res.end(); } else { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(chatCompletion(text))); } } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: String(err.message || err) })); } }); return; } res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'not found' })); }); server.listen(PORT, () => console.log(`kimi-agent wrapper listening on :${PORT}`));