feat(xai): add realtime voice provider#106267
Conversation
Dependency GuardThis PR changes dependency-related files. Maintainers should confirm these changes are intentional. Changed files:
Maintainer follow-up:
|
Dependency graph changes notedThis PR includes dependency graph changes. The dependency guard is informational because the PR author is a repository admin or a member of
Security review is still recommended before merge when the dependency graph change is intentional. |
|
/allow-dependencies-change |
|
Land-ready feature proof for clean replacement head
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c343bd26d2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| this.markQueue.push(markName); | ||
| this.config.onMark?.(markName); |
There was a problem hiding this comment.
Handle missing mark acknowledgers
When this bridge is used without an onMark callback (allowed by RealtimeVoiceBridgeCallbacks, and also what the runtime's markStrategy: "ignore" does), sendMark() still appends every audio delta to markQueue but no one can call acknowledgeMark(). Because requestResponseCreate() refuses to create the next response while markQueue.length > 0, any later text turn or tool result stays stuck in responseCreatePending, so non-mark transports hang after the first audio response.
Useful? React with 👍 / 👎.
|
Codex review: stale review; fresh review needed. Summary Next step Review history (3 earlier review cycles)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c330bea56
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const audioEndMs = shouldInterruptProvider | ||
| ? Math.max( | ||
| 0, | ||
| responseStartTimestamp === null | ||
| ? this.latestMediaTimestamp | ||
| : this.latestMediaTimestamp - responseStartTimestamp, | ||
| ) |
There was a problem hiding this comment.
Derive truncation from playback, not input timestamps
When native clients suppress microphone frames during assistant output (Android does this in TalkModeManager.kt:1064, and iOS does it on the built-in-speaker path in RealtimeTalkRelaySession.swift:1052-1059), latestMediaTimestamp stops advancing right when the response starts. A later cancelOutput/barge-in can therefore send conversation.item.truncate with audio_end_ms near 0 even after the user heard several seconds of audio, causing xAI to remove already-played assistant content from the provider history and making follow-up turns lose context. The truncation point needs to come from the transport playback boundary/mark acknowledgements, or from an explicit playback offset passed with the cancel request, rather than from the last appended input frame timestamp.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dd6b5f498b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| this.scheduleDelayedToolResult({ callId, result, ...(options ? { options } : {}) }); | ||
| return; |
There was a problem hiding this comment.
Return delayed tool-result submission promises
When a final consult result arrives while assistant audio is still queued, this branch schedules the actual talk.session.submitToolResult for later but returns a resolved promise immediately. submitRealtimeTalkConsult treats that as provider submission complete, switches the UI back to listening, and drops the call's abort tracking even though the provider is still waiting for the function output; if the delayed submission later fails or is held behind cancellation, the UI exposes a new listening turn during an unresolved tool call. The delayed path should resolve/reject only when sendToolResultNow actually runs.
Useful? React with 👍 / 👎.
|
Merged via squash.
|
* feat(xai): add realtime voice provider Co-authored-by: Vitor Cepeda Lopes <[email protected]> * fix(xai): satisfy realtime voice validation * fix(xai): harden realtime relay boundaries * chore: drop release-owned changelog edit * chore(i18n): refresh native voice catalogs * chore(i18n): refresh Control UI raw-copy baseline * chore(plugin-sdk): refresh API baseline hashes * chore(i18n): use canonical main catalogs * chore(i18n): refresh realtime voice source inventory * chore: refresh xai voice landing head [skip ci] * chore: refresh integrated protocol artifacts [skip ci] --------- Co-authored-by: Vitor Cepeda Lopes <[email protected]>
* feat(xai): add realtime voice provider Co-authored-by: Vitor Cepeda Lopes <[email protected]> * fix(xai): satisfy realtime voice validation * fix(xai): harden realtime relay boundaries * chore: drop release-owned changelog edit * chore(i18n): refresh native voice catalogs * chore(i18n): refresh Control UI raw-copy baseline * chore(plugin-sdk): refresh API baseline hashes * chore(i18n): use canonical main catalogs * chore(i18n): refresh realtime voice source inventory * chore: refresh xai voice landing head [skip ci] * chore: refresh integrated protocol artifacts [skip ci] --------- Co-authored-by: Vitor Cepeda Lopes <[email protected]>
Closes #99087.
Supersedes #99088, the stale duplicate #91308, and the ancestry-noisy replacement #106150. This maintainer-owned replacement preserves contributor credit for @TheAngryPit after the original fork branch could not accept the verified prepared tree.
What Problem This Solves
OpenClaw Talk has a realtime provider contract and gateway-relay path, but the bundled xAI plugin does not expose Grok Voice Agent as a native realtime voice provider. Users therefore cannot use xAI voice through Talk without falling back to a slower STT-model-TTS loop.
Why This Change Was Made
This adds one canonical bundled xAI realtime provider through the existing Talk
gateway-relaycontract. xAI-specific WebSocket auth, session configuration, transcripts, tools, server-VAD interruption, and optional resumption stay inside the xAI plugin; core, gateway, UI, iOS, and Android carry only generic typed playback marks and interruption state.The maintainer repair also splits the original provider monolith into bridge, config, event, and protocol owners; rejects unsupported generic response/interruption modes instead of silently diverging from xAI semantics; and truncates provider history only to the relay's acknowledged playback boundary. The implementation was checked against the current xAI Voice Agent guide and Voice API reference.
Final hardening constrains xAI reasoning effort to the provider's supported
high/nonevalues, regenerates the Android method catalog for playback acknowledgements, and holds final tool results behind every outstanding playback-cancellation request. Cancellation failure now closes the relay instead of releasing a result against unconfirmed provider state.wsis intentionally added to the xAI plugin runtime dependency set because the provider imports it at runtime. The exact-head dependency guard therefore requires maintainer authorization.User Impact
Users can select xAI realtime Talk with
provider: "xai"andtransport: "gateway-relay", stream microphone audio, receive native Grok voice audio, invoke tools, interrupt queued playback without corrupting conversation history, and opt into provider session resumption.Evidence
Final branch head:
72e064280b8005e79198e90965c452632f64ac4d(branch history preserves co-authorship by Vitor Cepeda Lopes). This is the clean 43-file comparison after merging currentmain; the earlier #106150 append-only sync retained stale ancestry and therefore exposed an unrelated 560-file comparison. The feature patch remains the reviewed implementation, with the release-ownedCHANGELOG.mdentry removed as required by the current prepare gate. Generated native catalogs were aligned with the integrated source tree; final main syncs include the current Control UI route, export-baseline, and event-envelope gate repairs.Exact code-head Blacksmith Testbox protocol, focused test, lint/format, and production build proof: run 29239566954. Feature tree
a4fd07a91dffd456c5e61185ccc549cf08b12fe2then passed native/UI/protocol generators, 242 focused assertions, fullpnpm check:changed, and fullpnpm buildin Testbox. Final integrated tree0d2284dc5b2250ec3f8e0c6530a1cceac35fa5e9has native inventory and JSON/Swift/Kotlin protocol generators idempotent; exhaustive exact-head release gate 29248320992 passed all 117 jobs, with only the optional timings summary skipped. The clean replacement stack hasgit diff --checkclean. Earlier branch-wide changed gate: run 29235425605. Content-identical feature/mobile/live proof: run 29236039421.Content-identical mobile proof also passed iOS relay tests, Swift format/lint, the Android playback-barrier regression, and Android ktlint. Fresh full-patch autoreview is clean after the final overlap-race repair; confidence 0.90.