Skip to content

refactor: eliminate repetitive Provider match dispatch with with_adapter! macro#3979

Closed
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1771148847-refactor-provider-dispatch
Closed

refactor: eliminate repetitive Provider match dispatch with with_adapter! macro#3979
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1771148847-refactor-provider-dispatch

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Feb 15, 2026

Summary

Introduces a with_adapter! macro in hyprnote.rs that centralizes the Provider → adapter dispatch into a single match block. The three functions that previously each had their own 9-arm match (build_upstream_url_with_adapter, build_initial_message_with_adapter, build_response_transformer) now each delegate to the macro in a single line.

No functional changes intended. Test helpers module and function signatures are unchanged.

Review & Testing Checklist for Human

  • Verify MistralAdapter::default() per-call is safe: The old build_response_transformer pre-created MistralAdapter::default() once outside the closure and captured it. The new code creates it on every closure invocation via the macro. Check that MistralAdapter's Default impl is trivial/stateless so this is semantically equivalent.
  • Verify all 9 Provider variants are present in the macro: Confirm the macro covers every variant of Provider (Deepgram, AssemblyAI, Soniox, Fireworks, OpenAI, Gladia, ElevenLabs, DashScope, Mistral) — a missing arm would be a compile error, but worth a glance.
  • Run cargo test -p transcribe-proxy to confirm tests pass (passed locally).

Notes


Open with Devin

…ter macro

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Feb 15, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit a9e36e4
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/69919849657d8a000838e904

@netlify
Copy link

netlify bot commented Feb 15, 2026

Deploy Preview for hyprnote canceled.

Name Link
🔨 Latest commit a9e36e4
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/69919849fee90c00082f4b98

Copy link
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines 114 to +116
move |raw: &str| {
let responses: Vec<owhisper_interface::stream::StreamResponse> = match provider {
Provider::Deepgram => DeepgramAdapter.parse_response(raw),
Provider::AssemblyAI => AssemblyAIAdapter.parse_response(raw),
Provider::Soniox => SonioxAdapter.parse_response(raw),
Provider::Fireworks => FireworksAdapter.parse_response(raw),
Provider::OpenAI => OpenAIAdapter.parse_response(raw),
Provider::Gladia => GladiaAdapter.parse_response(raw),
Provider::ElevenLabs => ElevenLabsAdapter.parse_response(raw),
Provider::DashScope => DashScopeAdapter.parse_response(raw),
Provider::Mistral => mistral_adapter.parse_response(raw),
};
let responses: Vec<owhisper_interface::stream::StreamResponse> =
with_adapter!(provider, |a| a.parse_response(raw));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🔴 MistralAdapter's stateful word_counter is reset on every response_transformer invocation

The build_response_transformer closure now creates a fresh MistralAdapter::default() on every invocation via the with_adapter! macro, instead of reusing a single instance across all calls.

Root Cause and Impact

MistralAdapter has a word_counter: Arc<AtomicU64> field that tracks word indices across streaming transcription responses. In build_delta_response (crates/owhisper-client/src/adapter/mistral/live.rs:241), each word gets a monotonically increasing index via self.word_counter.fetch_add(1, Ordering::Relaxed), and this index is used to compute the word's start and end timestamps.

The old code created the adapter once outside the closure and captured it:

let mistral_adapter = MistralAdapter::default();
move |raw: &str| {
    // ...
    Provider::Mistral => mistral_adapter.parse_response(raw),
}

The new code creates a fresh adapter on every closure call:

move |raw: &str| {
    with_adapter!(provider, |a| a.parse_response(raw));
    // For Mistral, this expands to:
    // let a = MistralAdapter::default(); // word_counter = 0 every time!
    // a.parse_response(raw)
}

This means for Mistral streaming sessions, every TranscriptionTextDelta response will have words starting at index 0, producing duplicate/overlapping word timestamps (e.g., every delta's first word gets start=0.0, end=1.0). The SessionCreated reset at live.rs:111 also becomes meaningless since the counter is always 0 anyway.

Impact: Mistral streaming transcription word timestamps will be incorrect — all words across different delta events will have overlapping start/end values instead of monotonically increasing ones.

Prompt for agents
In build_response_transformer (hyprnote.rs lines 111-127), the MistralAdapter must be created once and reused across all closure invocations to preserve its word_counter state. The with_adapter! macro cannot be used here for the Mistral case because it creates a new adapter instance each time.

Fix by restoring the pre-created MistralAdapter before the closure, and either:
1. Revert build_response_transformer to use a manual match (the old code), or
2. Handle Mistral as a special case outside the macro, e.g.:

   let mistral_adapter = MistralAdapter::default();
   move |raw: &str| {
       let responses: Vec<...> = match provider {
           Provider::Mistral => mistral_adapter.parse_response(raw),
           _ => with_adapter!(provider, |a| a.parse_response(raw)),
       };
       // ... rest of function
   }
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@yujonglee
Copy link
Contributor

Closing: the macro adds cognitive overhead for low value — the 3 functions are private, used once each, and the match arms are already concise. Also introduces a subtle semantic change with MistralAdapter::default() being called per-invocation in build_response_transformer instead of once.

@yujonglee yujonglee closed this Feb 15, 2026
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.

1 participant