Skip to content

Instantly share code, notes, and snippets.

@dfinke
Last active May 11, 2026 15:47
Show Gist options
  • Select an option

  • Save dfinke/459a237030bb0b823784f5689b9bbe77 to your computer and use it in GitHub Desktop.

Select an option

Save dfinke/459a237030bb0b823784f5689b9bbe77 to your computer and use it in GitHub Desktop.
PowerShell example for chatting with the xAI Voice Agent API and hearing the reply as audio.

PowerShell xAI Voice Chat Demo

Tiny PowerShell example for chatting with the xAI Voice Agent API and hearing the reply as audio.

The script:

  • opens one realtime WebSocket
  • sends your typed messages as chat turns
  • receives the assistant transcript and audio chunks
  • writes the reply to a temp WAV file
  • plays the WAV, then returns to you:

Run

Requires PowerShell 7+ and an xAI API key.

$env:XAI_API_KEY = 'xai-your-key'
.\Start-StandaloneConsoleWithPlayback.ps1

Type /exit to quit.

Notes

This is meant to be a small, readable demo rather than a production client. It uses grok-voice-think-fast-1.0, the eve voice by default, and 24 kHz PCM audio.

On Windows, playback uses System.Media.SoundPlayer.

param(
[string]$ApiKey = $env:XAI_API_KEY,
[string]$Voice = 'eve'
)
# $env:XAI_API_KEY = 'xai-your-key'
$model = 'grok-voice-think-fast-1.0'
$rate = 24000
$wav = Join-Path ([IO.Path]::GetTempPath()) 'xai-voice-reply.wav'
if (!$ApiKey) { throw 'Set $env:XAI_API_KEY first.' }
function Send($event) {
$json = $event | ConvertTo-Json -Depth 20 -Compress
$bytes = [Text.Encoding]::UTF8.GetBytes($json)
$null = $ws.SendAsync(
[ArraySegment[byte]]::new($bytes),
[Net.WebSockets.WebSocketMessageType]::Text,
$true,
[Threading.CancellationToken]::None
).GetAwaiter().GetResult()
}
function Receive {
$stream = [IO.MemoryStream]::new()
do {
$buffer = [byte[]]::new(65536)
$result = $ws.ReceiveAsync(
[ArraySegment[byte]]::new($buffer),
[Threading.CancellationToken]::None
).GetAwaiter().GetResult()
if ($result.MessageType -eq 'Close') { throw 'WebSocket closed.' }
$stream.Write($buffer, 0, $result.Count)
} until ($result.EndOfMessage)
try { [Text.Encoding]::UTF8.GetString($stream.ToArray()) | ConvertFrom-Json }
finally { $stream.Dispose() }
}
function Save-Wav($chunks) {
$bytes = 0
foreach ($chunk in $chunks) { $bytes += $chunk.Length }
$w = [IO.BinaryWriter]::new([IO.File]::Create($wav))
try {
$w.Write([Text.Encoding]::ASCII.GetBytes('RIFF'))
$w.Write([uint32](36 + $bytes))
$w.Write([Text.Encoding]::ASCII.GetBytes('WAVEfmt '))
$w.Write([uint32]16)
$w.Write([uint16]1)
$w.Write([uint16]1)
$w.Write([uint32]$rate)
$w.Write([uint32]($rate * 2))
$w.Write([uint16]2)
$w.Write([uint16]16)
$w.Write([Text.Encoding]::ASCII.GetBytes('data'))
$w.Write([uint32]$bytes)
foreach ($chunk in $chunks) { $w.Write([byte[]]$chunk) }
}
finally { $w.Dispose() }
$wav
}
function Play-Wav($path) {
Add-Type -AssemblyName System.Windows.Extensions -ErrorAction SilentlyContinue
$player = [System.Media.SoundPlayer]::new($path)
try { $player.PlaySync() } finally { $player.Dispose() }
}
function Ask($text) {
Send @{
type = 'conversation.item.create'
item = @{
type = 'message'
role = 'user'
content = @(@{ type = 'input_text'; text = $text })
}
}
Send @{ type = 'response.create' }
$audio = [Collections.Generic.List[byte[]]]::new()
$printed = $false
while ($true) {
$event = Receive
switch -Regex ($event.type) {
'output_audio\.delta$|response\.audio\.delta$' {
$audio.Add([Convert]::FromBase64String($event.delta)) | Out-Null
}
'transcript\.delta$|text\.delta$' {
$printed = $true
Write-Host -NoNewline $event.delta
}
'transcript\.done$' {
if (!$printed) { Write-Host -NoNewline $event.transcript }
}
'^error$' {
throw ($event | ConvertTo-Json -Depth 20)
}
'^response\.done$' {
if ($audio.Count) { Play-Wav (Save-Wav $audio) }
return
}
}
}
}
$ws = [Net.WebSockets.ClientWebSocket]::new()
$ws.Options.SetRequestHeader('Authorization', "Bearer $ApiKey")
$null = $ws.ConnectAsync(
[uri]"wss://api.x.ai/v1/realtime?model=$model",
[Threading.CancellationToken]::None
).GetAwaiter().GetResult()
try {
Send @{
type = 'session.update'
session = @{
instructions = 'You are a concise, helpful voice assistant.'
voice = $Voice
audio = @{
input = @{ format = @{ type = 'audio/pcm'; rate = $rate } }
output = @{ format = @{ type = 'audio/pcm'; rate = $rate } }
}
}
}
while (($text = Read-Host 'you') -ne '/exit') {
Write-Host -NoNewline 'assistant: '
Ask $text
Write-Host ''
}
}
finally {
if ($ws.State -eq 'Open') {
$null = $ws.CloseAsync('NormalClosure', 'bye', [Threading.CancellationToken]::None).GetAwaiter().GetResult()
}
$ws.Dispose()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment