grok-build-upstream-mirror/crates/codegen/xai-grok-voice/src/auth.rs

82 lines
2.7 KiB
Rust
Raw Normal View History

//! Bearer resolution for voice STT requests.
//! The voice clients are long-lived: a single voice session opens many STT
//! WebSocket connections over its lifetime, and an OAuth/session bearer rotates
//! (~15 min). Capturing a token once at startup would 401 mid-session. So
//! instead of a static `String`, the clients hold a [`SharedVoiceAuth`] and
//! resolve a fresh bearer at the point of each connection.
//!
//! This crate stays dependency-light: it defines its own minimal async trait
//! rather than depending on the shell's `AuthManager` / tools' `ApiKeyProvider`.
//! The pager adapts the shell's refreshing provider onto this trait.
use std::future::{Future, ready};
use std::pin::Pin;
use std::sync::Arc;
#[cfg(feature = "audio")]
use crate::error::VoiceError;
pub trait VoiceAuthProvider: std::fmt::Debug + Send + Sync + 'static {
fn bearer(&self) -> Pin<Box<dyn Future<Output = Option<String>> + Send + '_>>;
}
/// Shared provider handed to the voice pipeline.
pub type SharedVoiceAuth = Arc<dyn VoiceAuthProvider>;
#[cfg(feature = "audio")]
pub(crate) async fn require_bearer(auth: &SharedVoiceAuth) -> Result<String, VoiceError> {
auth.bearer().await.ok_or_else(|| {
Synced from monorepo Changes: - Classify clipboard delivery confidence - Add durable session update append - Scope the xAI session bearer to first-party memory embedding endpoints - Persist subagent outputs to disk and bound long-lived agent state - Add MiniSweAgent:bash for mini-swe-agent parity - Revert taking local sessions off the persistent shell - Contextual tip recommending grok wrap on SSH sessions - Voice STT bearer from model BYOK env_key/api_key - Define exact website policies for sandbox - Gate unsafe shell environments - Shared pin hoist; single require_sha gate for marketplace plugins - Server-signed is-managed claim (closes sidecar-removal downgrade) - Optional require_sha pin for remote plugin installs - Show session title and last exchange in the exit resume hint - Gate shell output redirects - Warn when fail_closed is present but not a boolean - Add canonical text editing core (ratatui-textarea) - Keep execution state out of goal scratch - Add acknowledged persistence primitives - Inherit child network restrictions in sandbox - Fail closed when hook matchers fail to recompile - Add MCP setup preferences for plugin MCPs - Gate sourced shell scripts - Gate file-typed project hooks - grok wrap: restore terminal modes on child death - Harden owner-only permissions on auth and MCP credentials - Create crash dump files with owner-only permissions - Write the agent_id cache owner-only (0600) - SessionMetrics mode skips Mixpanel profile sync - Dashboard: slim live-tail peek - Yank full queued prompt text, not (+N lines) - Defeat clock-rollback on the signed managed-config cache - Stop early session/cancel from overtaking the prompt and wedging the turn slot - Self-heal a diverged agent entrypoint on startup - Add matched inference expectations in test-support - Add AuthSingleFlight cancel/successor gap tests - Remove consumer from external OTEL allowlist and pin scrub coverage - Enable /copy in minimal mode - Surface capacity and API-key detail on 429 errors - Single-flight interactive auth - Fix PageUp/PageDown skipping lines behind sticky prompt header
2026-07-17 14:19:50 +01:00
VoiceError::Auth(
"not signed in — run `grok login`, set XAI_API_KEY, or set a model api_key/env_key"
.into(),
)
})
}
/// A fixed bearer that never refreshes.
///
/// Used by the standalone `voice-probe` binary and tests, where there is no
/// `AuthManager` — only a raw `XAI_API_KEY`.
pub struct StaticVoiceAuth(pub String);
impl std::fmt::Debug for StaticVoiceAuth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("StaticVoiceAuth")
.field(&"<redacted>")
.finish()
}
}
impl VoiceAuthProvider for StaticVoiceAuth {
fn bearer(&self) -> Pin<Box<dyn Future<Output = Option<String>> + Send + '_>> {
Box::pin(ready(Some(self.0.clone())))
}
}
impl StaticVoiceAuth {
/// Build a [`SharedVoiceAuth`] from a static key, trimming whitespace and
/// rejecting an empty value.
pub fn shared(key: impl Into<String>) -> Option<SharedVoiceAuth> {
let key = key.into().trim().to_string();
if key.is_empty() {
return None;
}
Some(Arc::new(Self(key)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn static_provider_resolves() {
let provider = StaticVoiceAuth::shared(" sk-test ").unwrap();
assert_eq!(provider.bearer().await.as_deref(), Some("sk-test"));
}
#[test]
fn static_provider_rejects_empty() {
assert!(StaticVoiceAuth::shared(" ").is_none());
}
}