Skip to content

Conversation

@yujonglee
Copy link
Contributor

No description provided.

@netlify
Copy link

netlify bot commented Dec 18, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 4b1ece6
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/69439e1c69b2df0008d6dfa8
😎 Deploy Preview https://deploy-preview-2396--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Dec 18, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 4b1ece6
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/69439e1c65cd2d0007583c0a
😎 Deploy Preview https://deploy-preview-2396--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

📝 Walkthrough

Walkthrough

This PR introduces a centralized provider abstraction layer via a new owhisper-providers crate that consolidates provider-specific configuration, authentication strategies, and URL endpoints. It adds JWT-based authentication with JWKS caching to the STT server, refactors all six adapter implementations to delegate to the Provider abstraction, extends the WebSocket proxy with first-message transformation support, and updates listener plugins to support the new Gladia adapter.

Changes

Cohort / File(s) Summary
Workspace & root manifests
Cargo.toml, apps/stt/Cargo.toml
Added owhisper-providers workspace dependency, replaced dotenv with dotenvy, and added jsonwebtoken (with rust_crypto feature) to root; added new dependencies to STT app: owhisper-providers, reqwest, serde, serde_json, url, dotenvy, jsonwebtoken.
Provider abstraction layer
owhisper/owhisper-providers/Cargo.toml, owhisper/owhisper-providers/src/lib.rs
New crate introducing Auth enum (Header, FirstMessage, SessionInit strategies) and Provider enum (Deepgram, AssemblyAI, Soniox, Fireworks, OpenAI, Gladia) with methods for auth headers, default URLs, hosts, paths, environment variable names, and query parameters.
STT server authentication
apps/stt/src/auth.rs
New module implementing JWT authentication via Axum's FromRequestParts, with JWKS caching (TTL-based), Bearer token extraction, token validation (algorithm, audience, expiration), and public AuthUser struct with user_id and entitlements fields.
STT server configuration
apps/stt/src/env.rs
New module providing singleton Env via env() function, loading configuration from environment variables (port, Sentry DSN, Supabase URL) and managing provider API keys via HashMap<Provider, String> with helper functions for required, optional, and parsed environment values.
STT server request handling
apps/stt/src/handlers.rs
New module with ws_handler that accepts authenticated user and WebSocket upgrade; resolves upstream provider URL (supporting both session-init and default modes), builds WebSocketProxy with appropriate authentication, and handles Gladia session initialization with structured payloads.
STT server main & module setup
apps/stt/src/main.rs
Refactored main to add module declarations (auth, env, handlers), changed route from /ws to /listen, integrated environment-based configuration (Sentry DSN, dynamic port binding), added graceful shutdown, and delegated WebSocket handling to external ws_handler.
WebSocket proxy enhancement
crates/transcribe-proxy/src/service.rs
Added FirstMessageTransformer type alias and optional transform_first_message field throughout builder chain and proxy; applies transformation exactly once to first client-to-upstream text message with internal state tracking.
owhisper-client adapter refactoring
owhisper/owhisper-client/Cargo.toml
Added owhisper-providers workspace dependency.
AssemblyAI adapter
owhisper/owhisper-client/src/adapter/assemblyai/mod.rs, live.rs
Removed is_host() and WS_PATH constant; delegated default WebSocket/API URLs and auth header construction to Provider::AssemblyAI.
Deepgram adapter
owhisper/owhisper-client/src/adapter/deepgram/live.rs
Delegated auth header construction to Provider::Deepgram.build_auth_header().
Fireworks adapter
owhisper/owhisper-client/src/adapter/fireworks/mod.rs, live.rs
Removed host constants, is_host(), and helpers; replaced with Provider::Fireworks for default URLs, hosts, paths, and auth header construction.
Gladia adapter
owhisper/owhisper-client/src/adapter/gladia/mod.rs
Removed host constants and helpers; delegated default URLs, paths, and host matching to Provider::Gladia. Updated tests to use provider-based host checks.
OpenAI adapter
owhisper/owhisper-client/src/adapter/openai/mod.rs, live.rs
Removed host constants and host-detection methods; delegated default WebSocket URLs, hosts, paths, and auth header construction to Provider::OpenAI.
Soniox adapter
owhisper/owhisper-client/src/adapter/soniox/mod.rs
Removed host constants and is_host() method; replaced with Provider::Soniox for default hosts, URLs, and paths.
Adapter selection & routing
owhisper/owhisper-client/src/adapter/mod.rs
Added AdapterKind::Gladia variant; introduced From<owhisper_providers::Provider> for AdapterKind conversion; refactored from_url_and_languages to delegate host-to-provider detection to Provider::from_url() with Deepgram as fallback.
Plugin integrations
plugins/listener/src/actors/listener.rs, plugins/listener2/src/batch.rs
Added GladiaAdapter import and spawning logic for AdapterKind::Gladia (single and dual modes).

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant STTServer as STT Server
    participant JWKSCache as JWKS Cache
    participant JWKSProvider as JWKS Provider
    participant UpstreamSTT as Upstream STT
    
    Client->>STTServer: WebSocket + Bearer Token
    STTServer->>STTServer: Extract Bearer Token
    
    alt Token needs validation
        STTServer->>JWKSCache: Check cached JWKS
        alt Cache expired/missing
            JWKSCache->>JWKSProvider: Fetch JWKS
            JWKSProvider-->>JWKSCache: Return JWKS + TTL
            JWKSCache-->>STTServer: Return JWKS
        else Cache valid
            JWKSCache-->>STTServer: Return cached JWKS
        end
        
        STTServer->>STTServer: Extract kid, find JWK
        STTServer->>STTServer: Build DecodingKey & validate JWT
    end
    
    alt Validation successful
        STTServer->>STTServer: Create AuthUser { user_id, entitlements }
        STTServer->>UpstreamSTT: Resolve provider URL (Provider defaults/session-init)
        UpstreamSTT-->>STTServer: Session URL or default URL
        STTServer->>UpstreamSTT: Connect WebSocket (with auth)
        UpstreamSTT-->>STTServer: WebSocket upgraded
        STTServer-->>Client: WebSocket upgraded (Authenticated)
        
        Client->>STTServer: Audio frames
        STTServer->>UpstreamSTT: Forward (transform first message if needed)
        UpstreamSTT-->>STTServer: Transcription results
        STTServer-->>Client: Return results
    else Validation fails
        STTServer-->>Client: 401 Unauthorized
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • New provider abstraction: The owhisper-providers crate introduces a foundational Auth enum and Provider enum with ~15 provider-specific methods (default URLs, paths, auth strategies, query params)—requires understanding the abstraction's scope and correctness of all provider mappings.
  • JWT authentication with JWKS: The auth.rs module implements token validation with caching, TTL expiration, and error mapping—requires verification of JWT validation logic, JWKS fetch/parse handling, and HTTP error scenarios.
  • Environment configuration singleton: The env.rs module uses once_cell for singleton initialization with required/optional variable parsing—verify correctness of parse_or logic and API key resolution fallbacks.
  • WebSocket handler with upstream resolution: The handlers.rs module handles both session-init and default URL modes, includes structured payloads for Gladia session initialization—requires tracing through URL resolution paths and ensuring provider auth strategies are correctly applied.
  • Comprehensive adapter refactoring (6+ adapters): Each adapter module (AssemblyAI, Deepgram, Fireworks, Gladia, OpenAI, Soniox) refactored to delegate to Provider abstraction—verify that all hardcoded URLs, paths, and auth headers are correctly mapped to provider methods.
  • First-message transformation in proxy: Changes span builder chain, proxy struct, and runtime logic with state tracking (has_transformed_first)—ensure transformation is applied exactly once and doesn't interfere with backpressure/queueing.
  • Heterogeneous file spread: Changes across 20+ files with varying complexity (manifests, new modules, adapter refactoring, proxy enhancement, plugin integration)—demands context-switching between abstraction layer, server implementation, and widespread adapter updates.

Areas requiring extra attention:

  • Provider enum mappings to default URLs, hosts, and paths for each of the six providers—validate against upstream provider documentation
  • JWKS caching TTL logic and cache invalidation behavior
  • Session-init flow for Gladia (GladiaConfig payload structure, response parsing, error handling)
  • Ensure all adapter refactoring correctly delegates to new Provider methods without losing existing behavior
  • First-message transformation application in proxy runtime—verify it doesn't break existing message forwarding or cause duplicate transformations

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.41% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive No pull request description was provided, making it impossible to assess whether the description relates to the changeset. Add a description explaining the purpose of the owhisper-providers crate, its benefits for code deduplication, and how it's integrated into the adapters and proxy.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: introducing a new owhisper-providers crate and integrating it across client adapters and the proxy server.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-owhisper-providers-for-dedupe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (13)
crates/transcribe-proxy/src/service.rs (1)

481-490: Simplify: the inner else branch is unreachable.

When !has_transformed_first is true, first_msg_transformer is guaranteed to be Some (per line 447 initialization), making lines 485-487 dead code.

🔎 Suggested simplification:
                            let text = if !has_transformed_first {
                                has_transformed_first = true;
-                               if let Some(ref transformer) = first_msg_transformer {
-                                   transformer(text.to_string())
-                               } else {
-                                   text.to_string()
-                               }
+                               first_msg_transformer.as_ref().unwrap()(text.to_string())
                            } else {
                                text.to_string()
                            };
apps/stt/src/auth.rs (2)

48-79: Potential thundering herd on JWKS cache expiry.

When the cache expires, multiple concurrent requests can all pass the read check and trigger parallel JWKS fetches. Consider using a tokio::sync::OnceCell or a lock-and-double-check pattern to ensure only one request fetches while others wait.

🔎 Suggested approach using double-check pattern:
 async fn get_jwks() -> Result<JwkSet, &'static str> {
     let cache = jwks_cache();
 
     {
         let guard = cache.read().await;
         if let Some(cached) = guard.as_ref() {
             if cached.fetched_at.elapsed() < JWKS_CACHE_TTL {
                 return Ok(cached.jwks.clone());
             }
         }
     }
 
+    let mut guard = cache.write().await;
+    // Double-check after acquiring write lock
+    if let Some(cached) = guard.as_ref() {
+        if cached.fetched_at.elapsed() < JWKS_CACHE_TTL {
+            return Ok(cached.jwks.clone());
+        }
+    }
+
     let env = env();
     let jwks_url = format!("{}/auth/v1/.well-known/jwks.json", env.supabase_url);
 
     let jwks: JwkSet = reqwest::get(&jwks_url)
         .await
         .map_err(|_| "failed to fetch jwks")?
         .json()
         .await
         .map_err(|_| "failed to parse jwks")?;
 
-    {
-        let mut guard = cache.write().await;
-        *guard = Some(CachedJwks {
-            jwks: jwks.clone(),
-            fetched_at: Instant::now(),
-        });
-    }
+    *guard = Some(CachedJwks {
+        jwks: jwks.clone(),
+        fetched_at: Instant::now(),
+    });
 
     Ok(jwks)
 }

63-68: Consider adding a timeout to the JWKS fetch.

Using reqwest::get without a timeout could block indefinitely if the Supabase endpoint is slow or unresponsive, potentially causing request backlogs.

🔎 Suggested fix:
+    let client = reqwest::Client::builder()
+        .timeout(Duration::from_secs(10))
+        .build()
+        .map_err(|_| "failed to build http client")?;
+
-    let jwks: JwkSet = reqwest::get(&jwks_url)
+    let jwks: JwkSet = client
+        .get(&jwks_url)
         .await
         .map_err(|_| "failed to fetch jwks")?
+        .send()
+        .await
+        .map_err(|_| "failed to fetch jwks")?
         .json()
         .await
         .map_err(|_| "failed to parse jwks")?;
apps/stt/src/main.rs (1)

38-42: Consider handling bind errors gracefully.

Using unwrap() on TcpListener::bind will panic if the port is unavailable. For a production server, consider logging a more descriptive error before exiting.

🔎 Suggested improvement
-    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
+    let listener = tokio::net::TcpListener::bind(addr).await.unwrap_or_else(|e| {
+        tracing::error!("failed to bind to {}: {}", addr, e);
+        std::process::exit(1);
+    });
owhisper/owhisper-client/src/adapter/soniox/mod.rs (3)

12-23: Inconsistent error handling compared to other adapters.

Line 21 uses expect() which will panic on invalid api_base. The Fireworks adapter (lines 19-22 in fireworks/mod.rs) handles this gracefully by returning the default on parse error. Consider aligning behavior for robustness.

🔎 Suggested fix
     pub(crate) fn api_host(api_base: &str) -> String {
         use owhisper_providers::Provider;
 
         let default_host = Provider::Soniox.default_api_host();
 
         if api_base.is_empty() {
             return default_host.to_string();
         }
 
-        let url: url::Url = api_base.parse().expect("invalid_api_base");
-        url.host_str().unwrap_or(default_host).to_string()
+        let url: url::Url = match api_base.parse() {
+            Ok(u) => u,
+            Err(_) => return default_host.to_string(),
+        };
+        url.host_str().unwrap_or(default_host).to_string()
     }

13-13: Consider module-level import for consistency.

The use owhisper_providers::Provider statement is repeated inside each function. Other adapters (OpenAI, Fireworks, Gladia) use a module-level import. Consider moving to a single import at the top for consistency.

🔎 Suggested change
 mod batch;
 mod live;
 
+use owhisper_providers::Provider;
+
 #[derive(Clone, Default)]
 pub struct SonioxAdapter;

Then remove the local use owhisper_providers::Provider; statements from each function.

Also applies to: 26-26, 38-38


54-54: Same panic risk on invalid api_base.

Line 54 also uses expect() on api_base.parse(). For consistency with the defensive pattern suggested above, consider handling parse errors gracefully here as well.

apps/stt/src/env.rs (1)

22-43: All provider API keys required at startup may be overly restrictive.

The current implementation requires all 6 provider API keys (DEEPGRAM_API_KEY, ASSEMBLYAI_API_KEY, etc.) to be set at startup, even if the server only proxies to a subset of providers. This could complicate deployment.

Consider loading keys lazily or making them optional with runtime validation when a provider is actually requested.

🔎 Optional: Load keys lazily per provider
 impl Env {
     fn from_env() -> Self {
-        let providers = [
-            Provider::Deepgram,
-            Provider::AssemblyAI,
-            Provider::Soniox,
-            Provider::Fireworks,
-            Provider::OpenAI,
-            Provider::Gladia,
-        ];
-        let api_keys = providers
-            .into_iter()
-            .map(|p| (p, required(p.env_key_name())))
+        let api_keys = Provider::ALL
+            .iter()
+            .filter_map(|&p| optional(p.env_key_name()).map(|key| (p, key)))
             .collect();

         Self {
             port: parse_or("PORT", 3000),
             sentry_dsn: optional("SENTRY_DSN"),
             supabase_url: required("SUPABASE_URL"),
             api_keys,
         }
     }

-    pub fn api_key_for(&self, provider: Provider) -> String {
+    pub fn api_key_for(&self, provider: Provider) -> Result<String, String> {
         self.api_keys
             .get(&provider)
             .cloned()
-            .unwrap_or_else(|| panic!("{} is not configured", provider.env_key_name()))
+            .ok_or_else(|| format!("{} is not configured", provider.env_key_name()))
     }
 }
apps/stt/src/handlers.rs (3)

46-57: Consider using expect() for better panic context.

While provider.default_ws_url() should always produce a valid URL from known constants, using expect() provides better debugging context if this assumption is ever violated.

🔎 Suggested improvement
-            let mut url = url::Url::parse(&provider.default_ws_url()).unwrap();
+            let mut url = url::Url::parse(&provider.default_ws_url())
+                .expect("provider default_ws_url should be valid");

97-105: Consider reusing HTTP client instead of creating per-request.

Creating a new reqwest::Client for each session initialization is inefficient. The client maintains connection pools and should be reused.

🔎 Option: Add client to Env or use a static
// In env.rs, add:
pub struct Env {
    // ... existing fields
    pub http_client: reqwest::Client,
}

// In from_env():
Self {
    // ... existing fields
    http_client: reqwest::Client::new(),
}

Then in handlers.rs:

-    let client = reqwest::Client::new();
-    let resp = client
+    let resp = env.http_client
         .post(init_url)

123-144: Minor: API key fetched unnecessarily for SessionInit providers.

For Auth::SessionInit, the API key is already used in init_session() and isn't needed in build_proxy(). The current code fetches it anyway but doesn't use it in that branch (line 140 is empty).

This is functionally correct but slightly wasteful.

owhisper/owhisper-providers/src/lib.rs (2)

71-78: Consider using strum::IntoEnumIterator to avoid manual ALL maintenance.

The ALL constant manually lists all variants, which can get out of sync when adding new providers.

🔎 Use strum's iterator derive
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumString, strum::Display)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumString, strum::Display, strum::EnumIter)]
 #[strum(serialize_all = "lowercase")]
 pub enum Provider {
     // ...
 }

 impl Provider {
-    const ALL: [Provider; 6] = [
-        Self::Deepgram,
-        Self::AssemblyAI,
-        Self::Soniox,
-        Self::Fireworks,
-        Self::OpenAI,
-        Self::Gladia,
-    ];
-
     pub fn from_host(host: &str) -> Option<Self> {
-        Self::ALL.into_iter().find(|p| p.is_host(host))
+        Self::iter().find(|p| p.is_host(host))
     }

This ensures all variants are automatically included when iterating.


203-208: Consider making Deepgram model configurable.

The hardcoded "nova-3-general" model may become outdated as Deepgram releases new models. Consider making this configurable or documenting the rationale for this default.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01a236a and 4b1ece6.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • Cargo.toml (3 hunks)
  • apps/stt/Cargo.toml (1 hunks)
  • apps/stt/src/auth.rs (1 hunks)
  • apps/stt/src/env.rs (1 hunks)
  • apps/stt/src/handlers.rs (1 hunks)
  • apps/stt/src/main.rs (2 hunks)
  • crates/transcribe-proxy/src/service.rs (20 hunks)
  • owhisper/owhisper-client/Cargo.toml (1 hunks)
  • owhisper/owhisper-client/src/adapter/assemblyai/live.rs (1 hunks)
  • owhisper/owhisper-client/src/adapter/assemblyai/mod.rs (2 hunks)
  • owhisper/owhisper-client/src/adapter/deepgram/live.rs (1 hunks)
  • owhisper/owhisper-client/src/adapter/fireworks/live.rs (1 hunks)
  • owhisper/owhisper-client/src/adapter/fireworks/mod.rs (4 hunks)
  • owhisper/owhisper-client/src/adapter/gladia/mod.rs (5 hunks)
  • owhisper/owhisper-client/src/adapter/mod.rs (1 hunks)
  • owhisper/owhisper-client/src/adapter/openai/live.rs (1 hunks)
  • owhisper/owhisper-client/src/adapter/openai/mod.rs (4 hunks)
  • owhisper/owhisper-client/src/adapter/soniox/mod.rs (2 hunks)
  • owhisper/owhisper-providers/Cargo.toml (1 hunks)
  • owhisper/owhisper-providers/src/lib.rs (1 hunks)
  • plugins/listener/src/actors/listener.rs (2 hunks)
  • plugins/listener2/src/batch.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Format using dprint fmt from the root. Do not use cargo fmt.

Files:

  • owhisper/owhisper-client/src/adapter/deepgram/live.rs
  • Cargo.toml
  • owhisper/owhisper-client/src/adapter/openai/live.rs
  • owhisper/owhisper-client/Cargo.toml
  • owhisper/owhisper-client/src/adapter/assemblyai/live.rs
  • apps/stt/src/handlers.rs
  • owhisper/owhisper-client/src/adapter/assemblyai/mod.rs
  • plugins/listener/src/actors/listener.rs
  • owhisper/owhisper-client/src/adapter/fireworks/live.rs
  • apps/stt/Cargo.toml
  • owhisper/owhisper-client/src/adapter/mod.rs
  • apps/stt/src/auth.rs
  • owhisper/owhisper-providers/src/lib.rs
  • plugins/listener2/src/batch.rs
  • owhisper/owhisper-providers/Cargo.toml
  • apps/stt/src/env.rs
  • owhisper/owhisper-client/src/adapter/soniox/mod.rs
  • owhisper/owhisper-client/src/adapter/gladia/mod.rs
  • apps/stt/src/main.rs
  • owhisper/owhisper-client/src/adapter/openai/mod.rs
  • crates/transcribe-proxy/src/service.rs
  • owhisper/owhisper-client/src/adapter/fireworks/mod.rs
**/*.{ts,tsx,rs,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

By default, avoid writing comments at all. If you write one, it should be about 'Why', not 'What'.

Files:

  • owhisper/owhisper-client/src/adapter/deepgram/live.rs
  • owhisper/owhisper-client/src/adapter/openai/live.rs
  • owhisper/owhisper-client/src/adapter/assemblyai/live.rs
  • apps/stt/src/handlers.rs
  • owhisper/owhisper-client/src/adapter/assemblyai/mod.rs
  • plugins/listener/src/actors/listener.rs
  • owhisper/owhisper-client/src/adapter/fireworks/live.rs
  • owhisper/owhisper-client/src/adapter/mod.rs
  • apps/stt/src/auth.rs
  • owhisper/owhisper-providers/src/lib.rs
  • plugins/listener2/src/batch.rs
  • apps/stt/src/env.rs
  • owhisper/owhisper-client/src/adapter/soniox/mod.rs
  • owhisper/owhisper-client/src/adapter/gladia/mod.rs
  • apps/stt/src/main.rs
  • owhisper/owhisper-client/src/adapter/openai/mod.rs
  • crates/transcribe-proxy/src/service.rs
  • owhisper/owhisper-client/src/adapter/fireworks/mod.rs
🧬 Code graph analysis (11)
owhisper/owhisper-client/src/adapter/openai/live.rs (2)
owhisper/owhisper-client/src/lib.rs (1)
  • api_key (47-50)
owhisper/owhisper-client/src/batch.rs (1)
  • api_key (37-40)
owhisper/owhisper-client/src/adapter/assemblyai/mod.rs (3)
owhisper/owhisper-client/src/lib.rs (1)
  • api_base (42-45)
owhisper/owhisper-client/src/adapter/mod.rs (2)
  • append_path_if_missing (107-117)
  • set_scheme_from_host (87-95)
owhisper/owhisper-client/src/adapter/gladia/mod.rs (1)
  • batch_api_url (75-85)
owhisper/owhisper-client/src/adapter/mod.rs (2)
owhisper/owhisper-client/src/adapter/deepgram/mod.rs (1)
  • is_supported_languages (15-18)
owhisper/owhisper-providers/src/lib.rs (1)
  • from_url (186-190)
apps/stt/src/auth.rs (1)
apps/stt/src/env.rs (1)
  • env (15-20)
apps/stt/src/env.rs (1)
owhisper/owhisper-providers/src/lib.rs (1)
  • env_key_name (192-201)
owhisper/owhisper-client/src/adapter/soniox/mod.rs (3)
owhisper/owhisper-client/src/adapter/fireworks/mod.rs (3)
  • ws_host (33-36)
  • api_host (14-26)
  • build_ws_url_from_base (38-72)
owhisper/owhisper-client/src/adapter/openai/mod.rs (1)
  • build_ws_url_from_base (16-48)
owhisper/owhisper-providers/src/lib.rs (1)
  • ws_path (141-150)
owhisper/owhisper-client/src/adapter/gladia/mod.rs (3)
owhisper/owhisper-providers/src/lib.rs (3)
  • default_api_host (119-128)
  • ws_path (141-150)
  • matches_url (179-184)
owhisper/owhisper-client/src/adapter/assemblyai/mod.rs (1)
  • batch_api_url (50-63)
owhisper/owhisper-client/src/lib.rs (1)
  • api_base (42-45)
apps/stt/src/main.rs (2)
apps/stt/src/env.rs (1)
  • env (15-20)
apps/stt/src/handlers.rs (2)
  • ws_handler (17-38)
  • s (25-25)
owhisper/owhisper-client/src/adapter/openai/mod.rs (1)
owhisper/owhisper-providers/src/lib.rs (2)
  • ws_path (141-150)
  • is_host (174-177)
crates/transcribe-proxy/src/service.rs (1)
owhisper/owhisper-providers/src/lib.rs (1)
  • transform_first_message (36-54)
owhisper/owhisper-client/src/adapter/fireworks/mod.rs (3)
owhisper/owhisper-client/src/lib.rs (1)
  • api_base (42-45)
owhisper/owhisper-client/src/adapter/soniox/mod.rs (1)
  • ws_host (25-35)
owhisper/owhisper-providers/src/lib.rs (1)
  • ws_path (141-150)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: live (openai)
  • GitHub Check: live (assemblyai)
  • GitHub Check: live (gladia)
  • GitHub Check: batch (openai)
  • GitHub Check: batch (assemblyai)
  • GitHub Check: batch (deepgram)
  • GitHub Check: live (soniox)
  • GitHub Check: batch (soniox)
  • GitHub Check: batch (gladia)
  • GitHub Check: live (deepgram)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: fmt
🔇 Additional comments (39)
crates/transcribe-proxy/src/service.rs (4)

37-37: LGTM!

The FirstMessageTransformer type alias follows the same pattern as ControlMessageMatcher, maintaining consistency.


102-108: LGTM!

Builder method follows the established pattern from control_message_matcher with appropriate bounds for thread safety.


447-448: Verify: transformation only applies to Text messages.

If the first client message is Binary (e.g., audio data), has_transformed_first remains false, and the transformation will apply to the first Text message encountered later. Confirm this aligns with expected protocol behavior where the initial message is always JSON text.

Also applies to: 567-643


184-192: LGTM!

The transformer is correctly threaded through both the on-demand connection path (handlerun) and the preconnected path (preconnectPreconnectedProxy::handlerun_with_upstream), ensuring consistent behavior across all proxy modes.

Also applies to: 395-434

owhisper/owhisper-client/Cargo.toml (1)

13-13: LGTM!

The new owhisper-providers workspace dependency is appropriately placed alongside other owhisper-* dependencies.

apps/stt/Cargo.toml (1)

12-26: LGTM!

The new dependencies appropriately support JWT-based authentication (jsonwebtoken), environment configuration (dotenvy), and provider abstraction (owhisper-providers). All use workspace references consistently.

Cargo.toml (3)

92-92: LGTM!

The owhisper-providers workspace dependency is correctly declared with matching path and package name.


169-169: Good migration from dotenv to dotenvy.

dotenvy is the actively maintained fork of the dotenv crate.


209-209: LGTM!

The jsonwebtoken crate with rust_crypto feature avoids native dependencies, which is appropriate for this use case.

apps/stt/src/auth.rs (2)

17-27: LGTM!

Clean AuthUser struct with appropriate is_pro() helper method.


81-135: Solid JWT validation implementation.

The FromRequestParts implementation correctly:

  • Handles case-insensitive "Bearer" prefix
  • Validates kid presence before JWKS lookup
  • Dynamically determines algorithm from JWK
  • Enforces audience and expiration checks
owhisper/owhisper-providers/Cargo.toml (1)

1-9: Ensure CI and developer toolchains support Rust 1.85+.

Rust 1.85.0 (released February 20, 2025) stabilized the 2024 edition, which is specified in Cargo.toml. Verify that CI pipelines and all developers have a compatible toolchain version via rust-toolchain.toml or explicit version enforcement.

owhisper/owhisper-client/src/adapter/deepgram/live.rs (1)

31-33: LGTM!

Clean delegation to the centralized provider abstraction. The and_then pattern correctly propagates None when no API key is provided.

owhisper/owhisper-client/src/adapter/openai/live.rs (1)

38-40: LGTM!

Consistent with the provider-based header delegation pattern used across other adapters (Deepgram, AssemblyAI, Fireworks).

plugins/listener/src/actors/listener.rs (2)

7-10: LGTM!

Import addition for GladiaAdapter aligns with the new Gladia support in the adapter dispatch logic.


243-248: LGTM!

Gladia adapter dispatch follows the established pattern for other adapters. The single/dual channel handling is consistent with existing implementations.

plugins/listener2/src/batch.rs (2)

6-9: LGTM!

Import addition for GladiaAdapter is consistent with the listener plugin changes.


221-222: LGTM!

Gladia batch task routing follows the generic adapter pattern, consistent with other non-Argmax adapters.

owhisper/owhisper-client/src/adapter/assemblyai/live.rs (1)

64-66: LGTM!

Auth header delegation to Provider::AssemblyAI aligns with the centralized provider abstraction.

owhisper/owhisper-client/src/adapter/fireworks/live.rs (1)

42-44: LGTM!

Provider-based header delegation consistent with other adapters.

owhisper/owhisper-client/src/adapter/assemblyai/mod.rs (3)

14-25: LGTM!

Clean migration to provider-based default URL construction. The inline use statement is acceptable for scoped usage.


44-44: LGTM!

Using Provider::AssemblyAI.ws_path() centralizes path configuration.


50-63: No changes needed — default_api_url() is guaranteed to return Some for AssemblyAI.

The Provider::AssemblyAI.default_api_url() implementation returns Some("https://api.assemblyai.com/v2"), not None. The .unwrap() on line 56 is safe and will not panic. The pattern matches gladia/mod.rs correctly, as both providers have hardcoded Some values in their match arms.

apps/stt/src/main.rs (1)

1-17: LGTM - Clean modular structure and route update.

The refactoring to separate modules (auth, env, handlers) improves maintainability. The route change from /ws to /listen aligns with the proxy URL patterns used in the adapter tests.

owhisper/owhisper-client/src/adapter/openai/mod.rs (2)

4-4: LGTM - Clean migration to Provider abstraction.

The refactoring correctly delegates URL construction to the Provider::OpenAI methods. The fallback logic using unwrap_or(Provider::OpenAI.default_ws_host()) ensures robust handling when host extraction fails.

Also applies to: 16-48


82-86: LGTM - Test updated to use Provider API.

The test now correctly validates the Provider::OpenAI.is_host() method rather than the removed adapter-level helper.

owhisper/owhisper-client/src/adapter/fireworks/mod.rs (2)

4-4: LGTM - Consistent Provider migration.

The api_host method correctly uses Provider::Fireworks.default_api_host() as the fallback for empty, unparseable, or host-less inputs.

Also applies to: 14-26


38-72: LGTM - WebSocket URL construction aligns with provider abstraction.

The build_ws_url_from_base implementation correctly composes the WS URL using the Fireworks-specific ws_host subdomain logic combined with Provider::Fireworks.ws_path().

owhisper/owhisper-client/src/adapter/gladia/mod.rs (3)

4-4: LGTM - Clean Provider integration.

The build_ws_url_from_base correctly delegates to the Provider for path resolution and maintains the existing proxy detection logic.

Also applies to: 14-27


134-139: LGTM - Tests updated to use Provider API.

The test correctly validates Provider::Gladia.matches_url() for host matching, replacing the removed adapter-level helper.


75-85: No panic risk for Gladia provider.

The default_api_url() method for Gladia is implemented to return Some("https://api.gladia.io/v2"), never None. The .unwrap() call is safe and cannot panic for this provider.

Likely an incorrect or invalid review comment.

owhisper/owhisper-client/src/adapter/mod.rs (2)

178-197: LGTM! Clean refactoring to provider-based resolution.

The adapter selection logic is well-structured:

  1. Special-cases Hyprnote Cloud with language-based provider selection
  2. Returns Argmax for local STT hosts
  3. Delegates to Provider::from_url for external providers with Deepgram fallback

200-212: LGTM!

The From<Provider> implementation correctly maps all provider variants to their corresponding adapter kinds.

apps/stt/src/env.rs (1)

53-66: LGTM!

Helper functions follow standard patterns for environment variable loading with appropriate behavior for required/optional values.

apps/stt/src/handlers.rs (2)

17-38: LGTM! Clean handler structure.

The WebSocket handler properly extracts the provider, resolves the upstream URL with appropriate error handling, and builds the proxy.


146-170: LGTM!

The Gladia-specific structs are appropriately defined for the session initialization flow. If more SessionInit providers are added later, consider moving provider-specific config structs to a dedicated module.

owhisper/owhisper-providers/src/lib.rs (3)

1-55: LGTM! Clean authentication abstraction.

The Auth enum elegantly encapsulates three distinct authentication strategies:

  • Header-based (with optional prefix)
  • First-message injection
  • Session initialization

The methods handle each variant appropriately with clear fallback behavior.


115-150: LGTM! URL construction is well-organized.

The separation of API hosts, WebSocket hosts, and paths handles the provider-specific differences cleanly (e.g., AssemblyAI's streaming.assemblyai.com vs api.assemblyai.com).


84-109: Fix Fireworks authentication to include Bearer prefix.

Fireworks requires Bearer authentication in the Authorization header, but the current implementation provides the raw API key without the prefix. Update Self::Fireworks to use Auth::Header { name: "Authorization", prefix: Some("Bearer ") } instead of prefix: None.

Note: Unable to verify Soniox authentication format through available documentation.

@yujonglee yujonglee merged commit fcd12f5 into main Dec 18, 2025
25 of 27 checks passed
@yujonglee yujonglee deleted the new-owhisper-providers-for-dedupe branch December 18, 2025 06:39
@coderabbitai coderabbitai bot mentioned this pull request Dec 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants