-
Notifications
You must be signed in to change notification settings - Fork 473
Add owhisper-providers and share it in client adapters and proxy server #2396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
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 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.