Add owhisper-providers and share it in client adapters and proxy server#2396
Add owhisper-providers and share it in client adapters and proxy server#2396
Conversation
✅ Deploy Preview for hyprnote-storybook ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for hyprnote ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThis PR introduces a centralized provider abstraction layer via a new Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Areas requiring extra attention:
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (13)
crates/transcribe-proxy/src/service.rs (1)
481-490: Simplify: the innerelsebranch is unreachable.When
!has_transformed_firstis true,first_msg_transformeris guaranteed to beSome(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::OnceCellor 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::getwithout 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()onTcpListener::bindwill 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 invalidapi_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::Providerstatement 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()onapi_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 usingexpect()for better panic context.While
provider.default_ws_url()should always produce a valid URL from known constants, usingexpect()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::Clientfor 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 ininit_session()and isn't needed inbuild_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 usingstrum::IntoEnumIteratorto avoid manual ALL maintenance.The
ALLconstant 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
⛔ Files ignored due to path filters (1)
Cargo.lockis 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 fmtfrom the root. Do not usecargo fmt.
Files:
owhisper/owhisper-client/src/adapter/deepgram/live.rsCargo.tomlowhisper/owhisper-client/src/adapter/openai/live.rsowhisper/owhisper-client/Cargo.tomlowhisper/owhisper-client/src/adapter/assemblyai/live.rsapps/stt/src/handlers.rsowhisper/owhisper-client/src/adapter/assemblyai/mod.rsplugins/listener/src/actors/listener.rsowhisper/owhisper-client/src/adapter/fireworks/live.rsapps/stt/Cargo.tomlowhisper/owhisper-client/src/adapter/mod.rsapps/stt/src/auth.rsowhisper/owhisper-providers/src/lib.rsplugins/listener2/src/batch.rsowhisper/owhisper-providers/Cargo.tomlapps/stt/src/env.rsowhisper/owhisper-client/src/adapter/soniox/mod.rsowhisper/owhisper-client/src/adapter/gladia/mod.rsapps/stt/src/main.rsowhisper/owhisper-client/src/adapter/openai/mod.rscrates/transcribe-proxy/src/service.rsowhisper/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.rsowhisper/owhisper-client/src/adapter/openai/live.rsowhisper/owhisper-client/src/adapter/assemblyai/live.rsapps/stt/src/handlers.rsowhisper/owhisper-client/src/adapter/assemblyai/mod.rsplugins/listener/src/actors/listener.rsowhisper/owhisper-client/src/adapter/fireworks/live.rsowhisper/owhisper-client/src/adapter/mod.rsapps/stt/src/auth.rsowhisper/owhisper-providers/src/lib.rsplugins/listener2/src/batch.rsapps/stt/src/env.rsowhisper/owhisper-client/src/adapter/soniox/mod.rsowhisper/owhisper-client/src/adapter/gladia/mod.rsapps/stt/src/main.rsowhisper/owhisper-client/src/adapter/openai/mod.rscrates/transcribe-proxy/src/service.rsowhisper/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
FirstMessageTransformertype alias follows the same pattern asControlMessageMatcher, maintaining consistency.
102-108: LGTM!Builder method follows the established pattern from
control_message_matcherwith 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_firstremainsfalse, 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 (
handle→run) and the preconnected path (preconnect→PreconnectedProxy::handle→run_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-providersworkspace dependency is appropriately placed alongside otherowhisper-*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-providersworkspace dependency is correctly declared with matching path and package name.
169-169: Good migration fromdotenvtodotenvy.
dotenvyis the actively maintained fork of thedotenvcrate.
209-209: LGTM!The
jsonwebtokencrate withrust_cryptofeature avoids native dependencies, which is appropriate for this use case.apps/stt/src/auth.rs (2)
17-27: LGTM!Clean
AuthUserstruct with appropriateis_pro()helper method.
81-135: Solid JWT validation implementation.The
FromRequestPartsimplementation correctly:
- Handles case-insensitive "Bearer" prefix
- Validates
kidpresence 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.tomlor explicit version enforcement.owhisper/owhisper-client/src/adapter/deepgram/live.rs (1)
31-33: LGTM!Clean delegation to the centralized provider abstraction. The
and_thenpattern correctly propagatesNonewhen 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
GladiaAdapteraligns 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
GladiaAdapteris 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::AssemblyAIaligns 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
usestatement 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 returnSomefor AssemblyAI.The
Provider::AssemblyAI.default_api_url()implementation returnsSome("https://api.assemblyai.com/v2"), notNone. The.unwrap()on line 56 is safe and will not panic. The pattern matchesgladia/mod.rscorrectly, as both providers have hardcodedSomevalues 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
/wsto/listenaligns 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::OpenAImethods. The fallback logic usingunwrap_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_hostmethod correctly usesProvider::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_baseimplementation correctly composes the WS URL using the Fireworks-specificws_hostsubdomain logic combined withProvider::Fireworks.ws_path().owhisper/owhisper-client/src/adapter/gladia/mod.rs (3)
4-4: LGTM - Clean Provider integration.The
build_ws_url_from_basecorrectly 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 returnSome("https://api.gladia.io/v2"), neverNone. 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:
- Special-cases Hyprnote Cloud with language-based provider selection
- Returns Argmax for local STT hosts
- Delegates to
Provider::from_urlfor 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
SessionInitproviders 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
Authenum 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.comvsapi.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::Fireworksto useAuth::Header { name: "Authorization", prefix: Some("Bearer ") }instead ofprefix: None.Note: Unable to verify Soniox authentication format through available documentation.
No description provided.