import { TextDecoder } from 'util'; import { renderChunk } from './output.js'; const API_URL = 'https://api.deepseek.com/chat/completions'; export async function streamChat({ model, apiKey, prompt, userInput }) { const body = { model, messages: [ { role: 'system', content: prompt }, { role: 'user', content: userInput }, ], stream: true, }; const res = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify(body), }); if (!res.ok) { const t = await res.text().catch(() => ''); throw new Error(`请求失败 ${res.status}: ${t}`); } process.stdout.write(`发送中 (模型: ${model})...\n`); const decoder = new TextDecoder(); const reader = res.body.getReader(); let buffer = ''; let sawReason = false; let sawContent = false; const enableReason = model === 'deepseek-reasoner'; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split(/\r?\n/); buffer = lines.pop(); // keep incomplete line for (const line of lines) { if (!line.startsWith('data: ')) continue; const data = line.slice(6); if (data === '[DONE]') { process.stdout.write('\n'); return; } let delta; try { delta = JSON.parse(data)?.choices?.[0]?.delta || {}; } catch { continue; } const rc = delta.reasoning_content || ''; const ct = delta.content || ''; if (enableReason && rc) { if (!sawReason) { process.stdout.write('思考:\n'); sawReason = true; } process.stdout.write(renderChunk(rc)); } if (ct) { if (!sawContent) { if (sawReason) process.stdout.write('\n\n'); process.stdout.write('回复:\n'); sawContent = true; } process.stdout.write(renderChunk(ct)); } } } // flush possible remaining buffered text (unlikely but safe) if (buffer.trim()) { try { const delta = JSON.parse(buffer)?.choices?.[0]?.delta || {}; const ct = delta.content || ''; if (ct) process.stdout.write(ct); } catch { // ignore malformed tail } } process.stdout.write('\n'); }