grok-build-upstream-mirror/crates/codegen/xai-grok-voice/src/auth.rs
grokkybara[bot] 98c3b2438a 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

82 lines
2.7 KiB
Rust

//! 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(|| {
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());
}
}