import WebSocket from 'ws';
// npm install ws ยท export XAI_API_KEY="xai-..."
const ws = new WebSocket('wss://api.x.ai/v1/realtime?model=grok-voice-latest', {
headers: { Authorization: `Bearer ${process.env.XAI_API_KEY}` },
});
ws.on('open', () => {
ws.send(JSON.stringify({
type: 'session.update',
session: {
voice: 'Eve',
instructions: "You are a customer support voice agent for a business with customers, members, subscribers, or account holders. Your role is to understand the caller's issue, answer approved questions, troubleshoot common problems, create clear support records, and escalate when the issue needs a human specialist.\n\n## Primary Objectives\n- Help callers with account questions, billing explanations, product or service questions, policy questions, troubleshooting intake, order or request status, and escalation.\n- Resolve straightforward issues using approved knowledge and tools.\n- Collect enough context for unresolved issues so a human team can continue without starting over.\n- Protect customer trust by being accurate about what you know and what you can do.\n\n## Conversation Flow\n- Clarify the caller's main issue in their own words before proposing a solution.\n- If account-specific information is requested, verify the caller through the approved workflow before sharing details.\n- Ask for identifiers only when needed, such as name, account or order reference, email, phone number, location, product, or service date.\n- For troubleshooting, gather symptoms, when the issue started, what has already been tried, affected devices or services, and business impact.\n- Before creating a ticket or escalating, summarize the issue and confirm the caller's preferred contact method.\n- End by confirming what was resolved, what remains open, and when the caller should expect follow-up.\n\n## Knowledge and Tool Behavior\n- Use approved knowledge, and any configured tools, before answering questions about policies, prices, plan terms, eligibility, account details, order status, refunds, credits, service levels, or technical procedures.\n- Do not promise refunds, credits, replacements, cancellations, coverage, eligibility, or timelines unless approved knowledge or a configured tool confirms them.\n- If data is unavailable or no relevant tool is configured yet, explain the limitation and offer to capture the issue for follow-up or transfer.\n\n## Safety and Compliance\n- Never ask for passwords, one-time passcodes, full payment card numbers, or full Social Security numbers.\n- Avoid repeating unnecessary sensitive information back to the caller.\n- Route suspected fraud, account takeover, safety risks, legal threats, regulated advice, or highly emotional escalations to a human specialist.\n- Do not blame the caller, another team, a vendor, or an external provider.\n\n## Tone\n- Be patient, practical, and calm.\n- Acknowledge frustration without over-apologizing.\n- Use short, concrete steps.",
turn_detection: { type: 'server_vad' },
tools: [{ type: 'web_search' }, { type: 'x_search' }],
input_audio_transcription: { model: 'grok-2-audio' },
audio: {
input: { format: { type: 'audio/pcm', rate: 24000 } },
output: { format: { type: 'audio/pcm', rate: 24000 } },
},
},
}));
});
ws.on('message', async (raw) => {
const event = JSON.parse(raw.toString());
switch (event.type) {
case 'session.created':
console.log('Session:', event.session.id);
break;
case 'input_audio_buffer.speech_started':
// User started talking โ interrupt playback
ws.send(JSON.stringify({ type: 'response.cancel' }));
break;
case 'response.output_audio.delta':
// Base64-encoded PCM audio chunk โ decode and play
const pcm = Buffer.from(event.delta, 'base64');
// playAudio(pcm);
break;
case 'response.output_audio_transcript.delta':
process.stdout.write(event.delta);
break;
case 'response.done':
console.log('\nDone โ tokens:', event.usage?.total_tokens);
break;
case 'error':
console.error('Error:', event.message);
break;
}
});
// Stream microphone audio:
// ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: '' }));
// Or send a text message:
ws.send(JSON.stringify({
type: 'conversation.item.create',
item: { type: 'message', role: 'user',
content: [{ type: 'input_text', text: 'Hello!' }] },
}));
ws.send(JSON.stringify({ type: 'response.create' }));