feat(voiceclaw-realtime): add xAI Realtime Voice Agent provider (closes #73019)#73032
feat(voiceclaw-realtime): add xAI Realtime Voice Agent provider (closes #73019)#73032matthewtran172 wants to merge 4 commits into
Conversation
Implements Issue openclaw#73019. Adds xAI's Voice Agent API (grok-voice-think-fast-1.0) as a /voiceclaw/realtime provider. xAI's Realtime API is documented as OpenAI-Realtime-protocol-compatible [1]; this adapter handles the two documented wire deltas: 1. response.text.delta (xAI) vs response.output_text.delta (OpenAI) 2. Single conversation.item.input_audio_transcription.completed event for user transcription (vs OpenAI's split delta/completed pair). The adapter synthesizes OpenClaw's transcript.delta + transcript.done pair from the single completed event. OpenAI Realtime support is anticipated by the existing types.ts provider union but is intentionally out of scope for this PR — see issue openclaw#71195. The adapter is structured so an OpenAI Realtime specialization could share most wire-protocol logic in a follow-up. Provider selection is via session.config.provider="xai". Default voice is "ara"; all five xAI voices (eve, ara, rex, sal, leo) accepted with case-insensitive resolution. Per-session voice override supported. XAI_API_KEY is read from the gateway process environment only; never logged, never reflected in client-facing errors. Sanitization defends against Bearer token / xai-* key reflection in upstream errors. Files added: - src/gateway/voiceclaw-realtime/xai-realtime.ts - src/gateway/voiceclaw-realtime/xai-realtime.test.ts Files modified: - src/gateway/voiceclaw-realtime/types.ts (add "xai" to provider union) - src/gateway/voiceclaw-realtime/session.ts (provider dispatch + per-provider voice/key defaults; back-compat default = "gemini") - src/gateway/voiceclaw-realtime/session.test.ts (provider selection) - docs/gateway/index.md (document xai provider option) - docs/providers/xai.md (note realtime support) Tests: mocked WebSocket only, no live xAI in CI, no API key required. Coverage: voice resolution, audio passthrough, transcript synthesis, function-call flow, barge-in, error sanitization, secret discipline, provider selection, default voice ara. Local gates run on this branch: - pnpm exec vitest run src/gateway/voiceclaw-realtime/ (120 passed) - pnpm exec oxlint src/gateway/voiceclaw-realtime/ (0 errors) - pnpm tsgo:core (clean) - pnpm tsgo:core:test (clean) [1] https://docs.x.ai/developers/model-capabilities/audio/voice-agent Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Self-audit follow-up for openclaw#73019 / PR openclaw#73032. Adds tests for previously uncovered adapter behaviors: Client-side methods: - commitAudio forwards input_audio_buffer.commit - createResponse forwards response.create - cancelResponse forwards response.cancel best-effort - injectContext forwards system-role conversation.item.create - sendFrame drops video frames silently (xAI Voice Agent is audio-only) - getTranscript returns a copy (caller mutation isolated) - disconnect clears state and is idempotent Close-handling: - code 1000 ignored without surfacing error - non-reconnectable code emits sanitized error - reconnectable code triggers reconnect path - in-flight tool calls are cancelled with explicit error if upstream closes mid-tool-call Conversation history replay: - last 12 entries serialized via conversation.item.create - user role serializes content type as input_text; assistant as text - empty / missing conversationHistory is a no-op No code changes in this commit; tests only. No live calls; no XAI_API_KEY required (mocks bypass openUpstream). Local gates: - pnpm exec vitest run src/gateway/voiceclaw-realtime/ -> 159 tests passed (was 120) - pnpm exec oxlint src/gateway/voiceclaw-realtime/ -> 0 errors - pnpm tsgo:core -> clean - pnpm tsgo:core:test -> clean Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
Thanks for the contribution. I reviewed the branch, and this PR is not a good landing base for OpenClaw. Close this PR as no longer a useful landing branch: the xAI realtime voice idea is useful, but current main has moved realtime voice onto the So I’m closing this PR rather than keeping an unmergeable branch open. A new narrow PR that carries only the useful part is welcome. Review detailsBest possible solution: Restart the useful xAI realtime voice work as a narrow Do we have a high-confidence way to reproduce the issue? Not applicable as a feature PR. Source checks show the branch no longer matches current main: the targeted gateway module is gone and current xAI docs still list realtime voice as unregistered. Is this the best way to solve the issue? No. The xAI realtime goal is useful, but this branch targets a removed Security review: Security review cleared: No concrete security or supply-chain regression was found in the diff, but the provider credential path still needs real runtime proof before any replacement PR lands. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 66880a5d736c. |
Lets the operator share audio files (voice memos, music clips) via the iOS Share sheet into OpenClaw alongside the existing image / URL / text paths. Mirrors the existing image-attachment plumbing: - extractedContent now handles UTType.audio providers (max 1 audio per share, 5 MB cap) - loadAudioAttachment + preferredAudioTypeIdentifier helpers - preferredFileName + ensureFileExtension helpers (suggestedName fallback to "shared-audio-N.m4a") - composeDraft drops a "Shared audio: <filename>" line when text/title/url all empty - log line now reads "attachments=N" (was image-only count) Audio attachment is base64-encoded into ShareAttachment.content, matching image flow.
Acted on the SAGE-side optimization audit (~/Documents/sage/self-improvement-audit/report.md, openclaw section). openclaw#1 — xai-realtime.ts:719 — replace per-overflow O(n) `.shift()` with batched 25% splice. Hot voice path; under sustained overflow the old shift-per-push was N*overflow_count. Batch trim drops 25% in one splice so amortized per-push cost is O(1) (audio overflow always meant data loss anyway; trimming a quarter at once is cleaner than dropping one frame at a time). openclaw#7 — server-maintenance.ts:93 — `[...map.entries()].toSorted(...)` allocates two distinct copies (spread + toSorted internal). Switched to single Array.from(...) + in-place .sort(). Drops one allocation per maintenance tick. openclaw#8 — xai-realtime.ts:698 + isValidXaiVoice — `.find()` / `.includes()` over the const XAI_VOICES tuple was O(N) per voice resolution. Pre-built `_XAI_VOICE_SET = new Set(XAI_VOICES)`; lookups are now O(1). Skipped openclaw#2 (audit suggested merging 4 server-maintenance Map iterations into one — they touch DIFFERENT collections; merging would be artificial code, not real perf). Multi-day refactors of attempt.ts / loader.ts / chat.ts deferred per audit recommendation.
|
This pull request has been automatically marked as stale due to inactivity. |
|
This pull request has been automatically marked as stale due to inactivity. |
|
ClawSweeper applied the proposed close for this PR.
|
Tracks #73019.
This is a draft PR opening the design proposed in #73019 for review. Adds support for xAI's Voice Agent API (
grok-voice-think-fast-1.0) as a/voiceclaw/realtimeprovider — the same surface that shipsgemini-live.tstoday. xAI's Realtime API is documented as OpenAI-Realtime-protocol-compatible (xAI docs); this PR ships the xAI specialization first.Surface scope
This PR targets
/voiceclaw/realtime(gateway endpoint,VoiceClawRealtimeAdapterinterface) — the surface for external WebSocket clients. It does not target the separatetalk-realtime-relay.ts/src/realtime-voice/provider-plugin surface used by OpenClaw's browser Talk UI, macOS Talk app, voice-call telephony, and Google Meet integration. Those use a different interface (RealtimeVoiceProviderPlugin) and are wired viaapi.registerRealtimeVoiceProvider(...)in extensionindex.tsfiles (e.g.,extensions/openai/realtime-voice-provider.ts).Related but not addressed by this PR:
/chatweb Talk via the provider-plugin surface (would benefit from a parallelextensions/xai/realtime-voice-provider.tsfollow-up; happy to scope that separately if maintainers want it).If maintainers prefer a unified shape across surfaces, I'm open to either (a) extracting a shared OpenAI-Realtime-protocol base used by both
xai-realtime.tshere AND a futureextensions/xai/realtime-voice-provider.ts, or (b) keeping the two surfaces independent as today.What this PR does
provider: "xai"as a/voiceclaw/realtimeoption in the existingVoiceClawSessionConfigEvent.providerunion.src/gateway/voiceclaw-realtime/xai-realtime.tsimplementingVoiceClawRealtimeAdapteragainst the OpenAI-Realtime wire protocol with xAI's documented deltas:response.text.delta(xAI) instead ofresponse.output_text.delta.conversation.item.input_audio_transcription.completeduser-transcription event; the adapter synthesizes OpenClaw'stranscript.delta+transcript.donepair.session.tswith provider-aware dispatch:resolveProvider(config.provider)— returns"xai"or"gemini"(back-compat default).defaultVoiceFor(provider)—"ara"for xAI,"Zephyr"for Gemini.requiredApiKeyEnvFor(provider)—XAI_API_KEYorGEMINI_API_KEY.createDefaultAdapterFactory()— instantiatesVoiceClawXaiRealtimeAdapterorVoiceClawGeminiLiveAdapterbased on the resolved provider.ara; all five xAI voices (eve,ara,rex,sal,leo) are supported with case-insensitive resolution and per-session override.docs/gateway/index.md(provider option) anddocs/providers/xai.md(realtime row in the feature-coverage table; cross-link from "Known limits").What this PR does not do
gemini-live.ts(Gemini behavior preserved byte-for-byte).extensions/voice-call/,extensions/openai/realtime-voice-provider.ts, orextensions/xai/batch/streaming-STT files (separate surfaces)./voiceclaw/realtime(the type union already accepts"openai"; the adapter is intentionally a separate, smaller PR).RealtimeVoiceProviderPluginfor the provider-plugin surface (surface B). Happy to follow up if maintainers want it.Security
XAI_API_KEYis read from the gateway process environment at upstream-open time; never logged, never reflected in client errors, never persisted on the adapter instance.Bearer ...token reflection andxai-...key reflection in upstream error messages.XAI_API_KEYvalue never appears in log lines or inJSON.stringify(adapter).Tests
XAI_API_KEYvalue required to run tests (a fakeTEST_KEY_NOT_REALis used in tests where adapter construction is exercised).ara, all five voices accepted case-insensitively, unknown voice falls back toara,isValidXaiVoicecorrectness.response.output_audio.deltaand OpenAI-styleresponse.audio.deltaboth forward as OpenClawaudio.delta.response.text.delta→ OpenClaw assistanttranscript.delta. Single user-transcriptioncompletedevent → synthesizedturn.started+transcript.delta+transcript.done.input_audio_buffer.speech_startedmid-assistant-response → finalizes pending assistant text with ellipsis + emitsturn.started(user role).response.function_call_arguments.done→tool.call. Tool result returned viaconversation.item.create(withfunction_call_output) +response.create.audio.append→input_audio_buffer.append.response.done→ flush transcripts +turn.ended.response.usagetranslated to OpenClawusage.metrics.Bearer xai-supersecretvalue123→ message containsBearer ***, never the raw value.session.updatepayload includes resolved voice (araby default), modalities["text", "audio"],pcm16audio formats,server_vadturn detection.session.updatewhen registered.XAI_API_KEYset (key is only required atopenUpstream)."xai"→VoiceClawXaiRealtimeAdapter;"gemini"andundefined→VoiceClawGeminiLiveAdapter.Local gates run on this branch
No live calls, no
openclaw onboard, no installs/upgrades beyondpnpm install --frozen-lockfile.SAGE is not changed
This PR is OpenClaw upstream only. The downstream SAGE project mentioned in #73019 will integrate against this provider once it ships; no SAGE code is included here.
Design notes for review
I'd appreciate maintainer input on the
Questions for maintainerssection in #73019, particularly:/voiceclaw/realtimein this PR if that's preferred, or keep PR small as proposed.gemini-live.tsprecedent suggests core; happy to move).extensions/xai/realtime-voice-provider.tsfor the surface-B plugin path (would close Feature: Live Voice Mode in /chat UI (Grok Voice Loop) #12911).The adapter avoids modifying
gemini-live.tsto keep the Gemini path byte-stable. If maintainers prefer a shared base in this PR, I can refactor.🤖 Generated with Claude Code