Skip to content

Optimization of lib.lookup function calls#457

Open
Reef-7 wants to merge 12 commits intoRunanywhereAI:mainfrom
Reef-7:main
Open

Optimization of lib.lookup function calls#457
Reef-7 wants to merge 12 commits intoRunanywhereAI:mainfrom
Reef-7:main

Conversation

@Reef-7
Copy link
Copy Markdown

@Reef-7 Reef-7 commented Mar 16, 2026

Created native_functions.dart - a single abstract class NativeFunctions with static late final fields for every FFI function across LLM, STT, TTS, VAD, and VoiceAgent. Each field calls lookupFunction exactly once on first access, then caches the result. All five bridge files were updated to call NativeFunctions.* instead of doing inline lookups.

Summary by CodeRabbit

  • Refactor

    • Centralized native interop into a single, cached bindings layer covering LLM, STT, TTS, VAD, and VoiceAgent — stream/isolates and runtime behavior preserved while improving reliability and consistency of audio/AI features.
  • Chores

    • Minor public handle-related adjustment for voice-agent management; no material changes to end-user behavior or public APIs.

Greptile Summary

This PR centralises all FFI symbol lookups into a new NativeFunctions abstract class with static final cached fields, eliminating repeated lib.lookupFunction / PlatformLoader.loadCommons() calls across the five bridge files. The isolate-bound functions (processVoiceTurn, transcribe, synthesize) are intentionally kept as file-level top-level statics so each Isolate.run() gets its own initialised copy. The PR also adds a _safeRacFree helper, a Completer-based double-init guard in getHandle(), and fixes pre-existing memory leaks in transcribeAudio and generateResponse.

Key points:

  • The refactor is largely sound; all previously flagged type-mismatch, nullable-call, and unused-variable issues appear to have been resolved.
  • Circular import: native_functions.dart imports dart_bridge_vad.dart (for RacVadResultStruct) while dart_bridge_vad.dart imports native_functions.dart. Moving RacVadResultStruct to ffi_types.dart would break the cycle cleanly.
  • NativeFunctions.sttResultFree is declared but never called — the STT isolate still uses its own inline lookup, leaving this entry as dead code.

Confidence Score: 4/5

  • PR is close to merge-ready; one targeted fix (circular import) is recommended before landing.
  • All critical issues flagged in earlier review rounds have been addressed — type mismatches, nullable invocations, unused lib variables, and the race condition in getHandle() are resolved. The two remaining items are architectural (circular import) and cosmetic (dead sttResultFree declaration), neither of which causes a runtime failure today. The circular import is the only item worth resolving before merge.
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart — circular import with dart_bridge_vad.dart needs to be resolved.

Important Files Changed

Filename Overview
sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart New file centralising all FFI lookups as cached static fields. Contains a circular import with dart_bridge_vad.dart (imported for RacVadResultStruct) and an unused sttResultFree declaration.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart Migrated most lookups to NativeFunctions.*; added Completer-based _initFuture guard to getHandle(); introduced _safeRacFree helper for rac_free memory management; isolate path uses file-level statics to stay per-isolate safe.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart Straightforward migration of all main-isolate lookups to NativeFunctions.*; removed redundant lib local variables; isolate paths unchanged.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_stt.dart Main-isolate lookups migrated to NativeFunctions.*; transcription isolate retains inline lookups, consistent with the per-isolate caching pattern.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_tts.dart Main-isolate lookups migrated to NativeFunctions.*; synthesis isolate retains inline lookups. No issues found.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_vad.dart All lookups migrated to NativeFunctions.*; platform_loader import correctly removed; participates in a circular import with native_functions.dart due to RacVadResultStruct being defined here.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    LLM[dart_bridge_llm.dart] -->|NativeFunctions.*| NF
    STT[dart_bridge_stt.dart] -->|NativeFunctions.*| NF
    TTS[dart_bridge_tts.dart] -->|NativeFunctions.*| NF
    VAD[dart_bridge_vad.dart] -->|NativeFunctions.*| NF
    VA[dart_bridge_voice_agent.dart] -->|NativeFunctions.*| NF
    NF[NativeFunctions\nstatic final cached fields] -->|PlatformLoader.loadCommons| LIB[(_lib: DynamicLibrary)]
    LIB -->|lookupFunction once| SYM[(Native Symbols\nrac_llm_* / rac_stt_*\nrac_tts_* / rac_vad_*\nrac_voice_agent_*)]

    NF -->|import for RacVadResultStruct ⚠️| VAD

    VA -->|Isolate.run| ISO[Background Isolate]
    ISO -->|_processVoiceTurnFn\nfile-level static| IL[(_voiceAgentLib\nper-isolate DynamicLibrary)]
    IL -->|lookupFunction once per isolate| SYM2[(rac_voice_agent_process_voice_turn\nrac_voice_agent_result_free)]
Loading

Comments Outside Diff (2)

  1. sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart, line 68-104 (link)

    Broken try/catch nesting — compile error

    The refactor introduced a malformed try block. Line 68 opens a try { but it has no matching catch or finally clause — this is a syntax error in Dart and will fail to compile.

    The original code had a single try/catch wrapping the entire handle creation logic. During the refactoring, the lookupFunction call was removed and replaced with NativeFunctions.voiceAgentCreate(...), but a new try { was inserted at line 82 while the original outer try { at line 68 was left dangling without its catch.

    The fix is to remove the extra try on line 82 so the original try/catch structure is restored:

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart
    Line: 68-104
    
    Comment:
    **Broken try/catch nesting — compile error**
    
    The refactor introduced a malformed `try` block. Line 68 opens a `try {` but it has no matching `catch` or `finally` clause — this is a syntax error in Dart and will fail to compile.
    
    The original code had a single `try/catch` wrapping the entire handle creation logic. During the refactoring, the `lookupFunction` call was removed and replaced with `NativeFunctions.voiceAgentCreate(...)`, but a new `try {` was inserted at line 82 while the original outer `try {` at line 68 was left dangling without its `catch`.
    
    The fix is to remove the extra `try` on line 82 so the original `try`/`catch` structure is restored:
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart, line 1007 (link)

    P1 Circular import between native_functions.dart and dart_bridge_vad.dart

    native_functions.dart imports dart_bridge_vad.dart as vad in order to reference vad.RacVadResultStruct in the vadProcess signature. At the same time, dart_bridge_vad.dart imports native_functions.dart. This creates a mutual dependency between the two files.

    Dart's import resolver handles library cycles without an infinite loop, but circular imports are a recognised code smell and can cause subtle issues in some toolchain configurations (e.g. tree-shaking, static analysis). More concretely, placing a Struct subclass in the bridge file rather than the shared types file (ffi_types.dart) was always the root cause: RacVadResultStruct belongs alongside the other FFI structs so that native_functions.dart can import it without creating a cycle.

    The clean fix is to move RacVadResultStruct (and any other result/options structs that are still in bridge files) to ffi_types.dart, then update the import in native_functions.dart:

    // native_functions.dart – replace:
    import 'package:runanywhere/native/dart_bridge_vad.dart' as vad;
    
    // with:
    // (no extra import needed – RacVadResultStruct is already in ffi_types.dart)

    And replace vad.RacVadResultStruct with RacVadResultStruct everywhere in native_functions.dart.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart
    Line: 1007
    
    Comment:
    **Circular import between `native_functions.dart` and `dart_bridge_vad.dart`**
    
    `native_functions.dart` imports `dart_bridge_vad.dart` as `vad` in order to reference `vad.RacVadResultStruct` in the `vadProcess` signature. At the same time, `dart_bridge_vad.dart` imports `native_functions.dart`. This creates a mutual dependency between the two files.
    
    Dart's import resolver handles library cycles without an infinite loop, but circular imports are a recognised code smell and can cause subtle issues in some toolchain configurations (e.g. tree-shaking, static analysis). More concretely, placing a `Struct` subclass in the bridge file rather than the shared types file (`ffi_types.dart`) was always the root cause: `RacVadResultStruct` belongs alongside the other FFI structs so that `native_functions.dart` can import it without creating a cycle.
    
    The clean fix is to move `RacVadResultStruct` (and any other result/options structs that are still in bridge files) to `ffi_types.dart`, then update the import in `native_functions.dart`:
    
    ```dart
    // native_functions.dart – replace:
    import 'package:runanywhere/native/dart_bridge_vad.dart' as vad;
    
    // with:
    // (no extra import needed – RacVadResultStruct is already in ffi_types.dart)
    ```
    
    And replace `vad.RacVadResultStruct` with `RacVadResultStruct` everywhere in `native_functions.dart`.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart
Line: 1007

Comment:
**Circular import between `native_functions.dart` and `dart_bridge_vad.dart`**

`native_functions.dart` imports `dart_bridge_vad.dart` as `vad` in order to reference `vad.RacVadResultStruct` in the `vadProcess` signature. At the same time, `dart_bridge_vad.dart` imports `native_functions.dart`. This creates a mutual dependency between the two files.

Dart's import resolver handles library cycles without an infinite loop, but circular imports are a recognised code smell and can cause subtle issues in some toolchain configurations (e.g. tree-shaking, static analysis). More concretely, placing a `Struct` subclass in the bridge file rather than the shared types file (`ffi_types.dart`) was always the root cause: `RacVadResultStruct` belongs alongside the other FFI structs so that `native_functions.dart` can import it without creating a cycle.

The clean fix is to move `RacVadResultStruct` (and any other result/options structs that are still in bridge files) to `ffi_types.dart`, then update the import in `native_functions.dart`:

```dart
// native_functions.dart – replace:
import 'package:runanywhere/native/dart_bridge_vad.dart' as vad;

// with:
// (no extra import needed – RacVadResultStruct is already in ffi_types.dart)
```

And replace `vad.RacVadResultStruct` with `RacVadResultStruct` everywhere in `native_functions.dart`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart
Line: 1082-1084

Comment:
**`sttResultFree` declared but never called — dead code**

`NativeFunctions.sttResultFree` looks up `rac_stt_result_free`, but no call site in the updated bridge files actually uses it. The STT transcription isolate (`_transcribeInIsolate` in `dart_bridge_stt.dart`) still performs its own inline `lookupFunction` call for both `rac_stt_component_transcribe` and `rac_stt_result_free`, meaning this cached entry is unused.

Because `static final` fields in Dart are lazily initialised, the symbol lookup won't fire unless `NativeFunctions.sttResultFree` is accessed, so there is no runtime cost today. However, the declaration creates a misleading impression that the STT bridge uses the centralised cache for result freeing, which may confuse future maintainers. Either wire up the call sites in the STT isolate to use this field (keeping the refactor consistent), or remove the declaration until it is needed.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (8): Last reviewed commit: "FFI Fix: Resolve type conflicts and fix ..." | Re-trigger Greptile

@@ -115,14 +111,9 @@ class DartBridgeVoiceAgent {

try {
final lib = PlatformLoader.loadCommons();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unused lib variable — dead code

lib is assigned but never used in isReady, isSTTLoaded, isLLMLoaded, and isTTSLoaded (lines 113, 131, 149, 167), as well as in getHandle (line 69). The lookup calls were migrated to NativeFunctions.*, but the PlatformLoader.loadCommons() calls were not removed.

This will produce Dart analyzer warnings for unused local variables. These lines should be removed from each affected method.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart
Line: 113

Comment:
**Unused `lib` variable — dead code**

`lib` is assigned but never used in `isReady`, `isSTTLoaded`, `isLLMLoaded`, and `isTTSLoaded` (lines 113, 131, 149, 167), as well as in `getHandle` (line 69). The lookup calls were migrated to `NativeFunctions.*`, but the `PlatformLoader.loadCommons()` calls were not removed.

This will produce Dart analyzer warnings for unused local variables. These lines should be removed from each affected method.

How can I resolve this? If you propose a fix, please make it concise.

} catch (_) {
// rac_free may not exist
}
NativeFunctions.racFree(audioPtr.value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Removed error handling for missing rac_free symbol

The original code wrapped the rac_free call in a try/catch with the comment // rac_free may not exist. The refactored code calls NativeFunctions.racFree(...) directly. Since NativeFunctions.racFree is a static late final field, if the rac_free symbol is not present in the native library, accessing this field will throw a LateInitializationError (or an ArgumentError from lookupFunction) that is no longer caught here.

If rac_free truly may not exist in some library builds, this change could cause a crash in the finally block during synthesizeSpeech, preventing proper cleanup of audioPtr and audioSizePtr.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart
Line: 469

Comment:
**Removed error handling for missing `rac_free` symbol**

The original code wrapped the `rac_free` call in a `try/catch` with the comment `// rac_free may not exist`. The refactored code calls `NativeFunctions.racFree(...)` directly. Since `NativeFunctions.racFree` is a `static late final` field, if the `rac_free` symbol is not present in the native library, accessing this field will throw a `LateInitializationError` (or an `ArgumentError` from `lookupFunction`) that is no longer caught here.

If `rac_free` truly may not exist in some library builds, this change could cause a crash in the `finally` block during `synthesizeSpeech`, preventing proper cleanup of `audioPtr` and `audioSizePtr`.

How can I resolve this? If you propose a fix, please make it concise.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces per-call dynamic FFI lookups with a centralized, lazy-cached NativeFunctions registry and refactors dart-bridge modules (LLM, STT, TTS, VAD, VoiceAgent) to call the new wrappers; updates VoiceAgent handle type and initialization flow. (≤50 words)

Changes

Cohort / File(s) Summary
Centralized FFI Registry
sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart
Added NativeFunctions: lazy-cached, centralized FFI bindings for LLM, STT, TTS, VAD, VoiceAgent, racFree, and related FFI structs.
LLM Bridge Refactoring
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart
Replaced inline PlatformLoader lookups with NativeFunctions wrappers (llmCreate, llmIsLoaded, llmSupportsStreaming, llmLoadModel, llmCleanup, llmCancel, llmGenerate*, llmDestroy). Minor logging and pointer cleanup updates.
STT Bridge Refactoring
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_stt.dart
Switched dynamic FFI lookups to NativeFunctions (sttCreate, sttIsLoaded, sttSupportsStreaming, sttLoadModel, sttCleanup, sttTranscribe, sttResultFree, sttDestroy). Transcribe isolate now uses wrapper functions.
TTS Bridge Refactoring
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_tts.dart
Replaced per-call lookups with NativeFunctions (ttsCreate, ttsIsLoaded, ttsLoadVoice, ttsCleanup, ttsStop, ttsSynthesize, ttsDestroy). Synthesis isolate path largely unchanged.
VAD Bridge Refactoring
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_vad.dart
Moved VAD FFI calls to NativeFunctions (vadCreate, vadInitialize, vadProcess, vadGet/SetEnergyThreshold, vadStart/Stop/Reset, vadCleanup, vadDestroy) while preserving memory/error semantics.
VoiceAgent Bridge Refactoring
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart
Centralized FFI via NativeFunctions, replaced RacVoiceAgentHandle with RacHandle, added init Future/Completer for handle creation, introduced isolate-scoped FFI caches, refactored result allocation/freeing; getHandle() now returns Future<RacHandle>.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant DartBridgeVoiceAgent
  participant NativeFunctions
  participant NativeLib
  Client->>DartBridgeVoiceAgent: getHandle()
  DartBridgeVoiceAgent->>NativeFunctions: voiceAgentCreate(...)
  NativeFunctions->>NativeLib: lookup & call rac_voice_agent_create
  NativeLib-->>NativeFunctions: handle / int result
  NativeFunctions-->>DartBridgeVoiceAgent: result
  DartBridgeVoiceAgent-->>Client: Future<RacHandle>
  Client->>DartBridgeVoiceAgent: processVoiceTurn(audio)
  DartBridgeVoiceAgent->>NativeFunctions: voiceAgentProcessVoiceTurn(handle, audio)
  NativeFunctions->>NativeLib: rac_voice_agent_process_voice_turn
  NativeLib-->>NativeFunctions: VoiceAgentResult pointer
  NativeFunctions-->>DartBridgeVoiceAgent: result pointer
  DartBridgeVoiceAgent->>NativeFunctions: voiceAgentResultFree(pointer)
  NativeFunctions->>NativeLib: rac_free / voiceAgentResultFree
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Flutter rearch #251: Large refactor centralizing FFI bindings and removing per-call PlatformLoader lookups; overlaps registry introduction and bridge updates.

Suggested labels

enhancement

Poem

🐰 I hopped through bindings, tidy and spry,
Collected lookups under one sky.
Handles now promise, isolates cheer,
NativeFunctions hum — the path is clear.
🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description is detailed and covers the refactoring scope, objectives, and known issues. However, it lacks required sections from the template: no Type of Change, Testing, Platform-Specific Testing, Labels, or Checklist completion. Complete the PR description template by adding: Type of Change (mark Refactoring), Testing section with lint and test status, Platform-Specific Testing for Flutter SDK (iOS/Android), appropriate Labels (Flutter SDK, Commons), and Checklist items confirming code style, self-review, and documentation updates.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Optimization of lib.lookup function calls' clearly summarizes the main change: centralizing and caching FFI function lookups via a NativeFunctions registry to reduce repeated dlsym costs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart (1)

68-102: ⚠️ Potential issue | 🔴 Critical

Critical: Broken try/catch nesting causes compile error.

There's a dangling try { at line 80 that breaks the nesting structure. The getHandle() method has an outer try at line 68, then another try { at line 80 that is never closed properly before the catch at line 98.

🐛 Proposed fix to remove the dangling try
       _logger.debug(
           'Creating voice agent with shared handles: LLM=$llmHandle, STT=$sttHandle, TTS=$ttsHandle, VAD=$vadHandle');

-    try {
       final handlePtr = calloc<RacVoiceAgentHandle>();
       try {
         final result = NativeFunctions.voiceAgentCreate(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`
around lines 68 - 102, The code has a dangling extra try block beginning before
fetching shared handles which breaks nesting; fix by removing the redundant
outer try (or closing it properly) so there is a single try around the native
create call and its finally/free block. Specifically, keep the sequence that
obtains DartBridgeLLM.shared.getHandle(), DartBridgeSTT.shared.getHandle(),
DartBridgeTTS.shared.getHandle(), DartBridgeVAD.shared.getHandle() and the
_logger.debug call outside (or before) the try, then wrap only the
calloc<RacVoiceAgentHandle>() / NativeFunctions.voiceAgentCreate(...) /
calloc.free(...) logic in one try/finally with a catch that logs via
_logger.error and rethrows; ensure method symbols referenced (voiceAgentCreate,
RacVoiceAgentHandle, DartBridgeLLM.shared.getHandle, _handle) remain unchanged.
🧹 Nitpick comments (4)
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_stt.dart (1)

220-243: Consider using NativeFunctions.sttTranscribe for consistency.

The isolate code still performs manual lookupFunction calls. Since NativeFunctions lazy-initializes per isolate, you could use NativeFunctions.sttTranscribe here for consistency with the PR's goal of centralizing all lookups. The same applies to sttResultFree at lines 272-274.

♻️ Suggested refactor for consistency
-      // Get transcribe function
-      final transcribeFn = lib.lookupFunction<
-          Int32 Function(
-            RacHandle,
-            Pointer<Void>,
-            IntPtr,
-            Pointer<RacSttOptionsStruct>,
-            Pointer<RacSttResultStruct>,
-          ),
-          int Function(
-            RacHandle,
-            Pointer<Void>,
-            int,
-            Pointer<RacSttOptionsStruct>,
-            Pointer<RacSttResultStruct>,
-          )>('rac_stt_component_transcribe');
-
-      final status = transcribeFn(
+      final status = NativeFunctions.sttTranscribe(
         handle,
         dataPtr.cast<Void>(),
         audioData.length,
         optionsPtr,
         resultPtr,
       );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_stt.dart`
around lines 220 - 243, The code manually looks up rac_stt_component_transcribe
into local transcribeFn and calls it; replace that manual lookup and direct call
with the centralized lazy lookup NativeFunctions.sttTranscribe and invoke it
with the same parameters (handle, dataPtr.cast<Void>(), audioData.length,
optionsPtr, resultPtr) to keep lookups per-isolate and consistent; likewise swap
the manual free call that uses sttResultFree lookup to use
NativeFunctions.sttResultFree instead (referencing
transcribeFn/rac_stt_component_transcribe and sttResultFree to locate the
existing manual lookups).
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_vad.dart (1)

16-16: Unused import after migration.

The platform_loader.dart import appears to be unused after migrating all calls to NativeFunctions. Consider removing it.

♻️ Remove unused import
 import 'package:runanywhere/native/native_functions.dart';
-import 'package:runanywhere/native/platform_loader.dart';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_vad.dart`
at line 16, The import of platform_loader.dart is no longer used in this file;
remove the unused import line referencing
package:runanywhere/native/platform_loader.dart and ensure all native calls use
the NativeFunctions API (e.g., references to NativeFunctions) so there are no
remaining references to PlatformLoader; run a quick build or analyzer to confirm
no unused-import warnings remain.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart (1)

152-155: Minor: Debug log before loadModel was removed.

Per the PR description, the original code had a debug log before calling loadModel. Only the post-call log remains at line 154. Consider restoring the pre-call log for debugging parity.

♻️ Restore pre-call debug log
     try {
+      _logger.debug('Loading LLM model: path=$modelPath, id=$modelId');
       final result = NativeFunctions.llmLoadModel(handle, pathPtr, idPtr, namePtr);
       _logger.debug(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart`
around lines 152 - 155, Restore the missing pre-call debug log before invoking
NativeFunctions.llmLoadModel by adding an _logger.debug call immediately before
the NativeFunctions.llmLoadModel(handle, pathPtr, idPtr, namePtr) invocation
that logs the parameters (handle, pathPtr, idPtr, namePtr or their readable
representations) to match the existing post-call debug at line 154; locate the
call site in dart_bridge_llm.dart (the method that calls
NativeFunctions.llmLoadModel) and insert the pre-call _logger.debug with a
concise message showing the input values for parity with the existing post-call
log.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart (1)

309-346: Consider using NativeFunctions in isolate for consistency.

Similar to the STT bridge, the isolate code could use NativeFunctions.voiceAgentProcessVoiceTurn and NativeFunctions.voiceAgentResultFree for consistency with the PR's centralization goal, since NativeFunctions lazy-initializes per isolate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`
around lines 309 - 346, The isolate code directly looks up FFI symbols
rac_voice_agent_process_voice_turn and rac_voice_agent_result_free instead of
reusing the per-isolate lazy-initialized wrappers; replace the direct
PlatformLoader.lookup usage in the block around processFn/resultPtr (where
processFn is used to call rac_voice_agent_process_voice_turn and
freeFn/resultPtr are used to free the result) with calls to
NativeFunctions.voiceAgentProcessVoiceTurn and
NativeFunctions.voiceAgentResultFree respectively, keeping the same error
handling and the call to _parseVoiceTurnResultStatic(resultPtr.ref, lib) and
still freeing audioPtr/resultPtr in finally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`:
- Around line 68-102: The code has a dangling extra try block beginning before
fetching shared handles which breaks nesting; fix by removing the redundant
outer try (or closing it properly) so there is a single try around the native
create call and its finally/free block. Specifically, keep the sequence that
obtains DartBridgeLLM.shared.getHandle(), DartBridgeSTT.shared.getHandle(),
DartBridgeTTS.shared.getHandle(), DartBridgeVAD.shared.getHandle() and the
_logger.debug call outside (or before) the try, then wrap only the
calloc<RacVoiceAgentHandle>() / NativeFunctions.voiceAgentCreate(...) /
calloc.free(...) logic in one try/finally with a catch that logs via
_logger.error and rethrows; ensure method symbols referenced (voiceAgentCreate,
RacVoiceAgentHandle, DartBridgeLLM.shared.getHandle, _handle) remain unchanged.

---

Nitpick comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart`:
- Around line 152-155: Restore the missing pre-call debug log before invoking
NativeFunctions.llmLoadModel by adding an _logger.debug call immediately before
the NativeFunctions.llmLoadModel(handle, pathPtr, idPtr, namePtr) invocation
that logs the parameters (handle, pathPtr, idPtr, namePtr or their readable
representations) to match the existing post-call debug at line 154; locate the
call site in dart_bridge_llm.dart (the method that calls
NativeFunctions.llmLoadModel) and insert the pre-call _logger.debug with a
concise message showing the input values for parity with the existing post-call
log.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_stt.dart`:
- Around line 220-243: The code manually looks up rac_stt_component_transcribe
into local transcribeFn and calls it; replace that manual lookup and direct call
with the centralized lazy lookup NativeFunctions.sttTranscribe and invoke it
with the same parameters (handle, dataPtr.cast<Void>(), audioData.length,
optionsPtr, resultPtr) to keep lookups per-isolate and consistent; likewise swap
the manual free call that uses sttResultFree lookup to use
NativeFunctions.sttResultFree instead (referencing
transcribeFn/rac_stt_component_transcribe and sttResultFree to locate the
existing manual lookups).

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_vad.dart`:
- Line 16: The import of platform_loader.dart is no longer used in this file;
remove the unused import line referencing
package:runanywhere/native/platform_loader.dart and ensure all native calls use
the NativeFunctions API (e.g., references to NativeFunctions) so there are no
remaining references to PlatformLoader; run a quick build or analyzer to confirm
no unused-import warnings remain.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`:
- Around line 309-346: The isolate code directly looks up FFI symbols
rac_voice_agent_process_voice_turn and rac_voice_agent_result_free instead of
reusing the per-isolate lazy-initialized wrappers; replace the direct
PlatformLoader.lookup usage in the block around processFn/resultPtr (where
processFn is used to call rac_voice_agent_process_voice_turn and
freeFn/resultPtr are used to free the result) with calls to
NativeFunctions.voiceAgentProcessVoiceTurn and
NativeFunctions.voiceAgentResultFree respectively, keeping the same error
handling and the call to _parseVoiceTurnResultStatic(resultPtr.ref, lib) and
still freeing audioPtr/resultPtr in finally.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94396ff1-a596-4b74-8182-787dc74aef74

📥 Commits

Reviewing files that changed from the base of the PR and between 405ff93 and 74163da.

📒 Files selected for processing (6)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_stt.dart
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_tts.dart
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_vad.dart
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart

Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart (1)

63-101: ⚠️ Potential issue | 🟠 Major

Serialize getHandle() initialization to prevent duplicate native instances.

Concurrent callers can pass the null-check and both create a native handle; one gets overwritten, leaking native state and breaking singleton assumptions.

Suggested fix
 class DartBridgeVoiceAgent {
   RacVoiceAgentHandle? _handle;
+  Future<RacVoiceAgentHandle>? _handleInitFuture;
   final _logger = SDKLogger('DartBridge.VoiceAgent');
@@
   Future<RacVoiceAgentHandle> getHandle() async {
     if (_handle != null) {
       return _handle!;
     }
+    if (_handleInitFuture != null) {
+      return _handleInitFuture!;
+    }
 
-    // Use shared component handles (matches Swift approach)
-    // This allows the voice agent to use already-loaded models from the
-    // individual component bridges (STT, LLM, TTS, VAD)
-    final llmHandle = DartBridgeLLM.shared.getHandle();
-    final sttHandle = DartBridgeSTT.shared.getHandle();
-    final ttsHandle = DartBridgeTTS.shared.getHandle();
-    final vadHandle = DartBridgeVAD.shared.getHandle();
+    _handleInitFuture = (() async {
+      final llmHandle = DartBridgeLLM.shared.getHandle();
+      final sttHandle = DartBridgeSTT.shared.getHandle();
+      final ttsHandle = DartBridgeTTS.shared.getHandle();
+      final vadHandle = DartBridgeVAD.shared.getHandle();
 
-    _logger.debug(
-        'Creating voice agent with shared handles: LLM=$llmHandle, STT=$sttHandle, TTS=$ttsHandle, VAD=$vadHandle');
+      _logger.debug(
+          'Creating voice agent with shared handles: LLM=$llmHandle, STT=$sttHandle, TTS=$ttsHandle, VAD=$vadHandle');
 
-    try {
-      final handlePtr = calloc<RacVoiceAgentHandle>();
       try {
-        final result = NativeFunctions.voiceAgentCreate(
-            llmHandle, sttHandle, ttsHandle, vadHandle, handlePtr);
+        final handlePtr = calloc<RacVoiceAgentHandle>();
+        try {
+          final result = NativeFunctions.voiceAgentCreate(
+              llmHandle, sttHandle, ttsHandle, vadHandle, handlePtr);
 
-        if (result != RAC_SUCCESS) {
-          throw StateError(
-            'Failed to create voice agent: ${RacResultCode.getMessage(result)}',
-          );
-        }
+          if (result != RAC_SUCCESS) {
+            throw StateError(
+              'Failed to create voice agent: ${RacResultCode.getMessage(result)}',
+            );
+          }
 
-        _handle = handlePtr.value;
-        _logger.info('Voice agent created with shared component handles');
-        return _handle!;
-      } finally {
-        calloc.free(handlePtr);
-      }
-    } catch (e) {
-      _logger.error('Failed to create voice agent handle: $e');
-      rethrow;
-    }
+          _handle = handlePtr.value;
+          _logger.info('Voice agent created with shared component handles');
+          return _handle!;
+        } finally {
+          calloc.free(handlePtr);
+        }
+      } catch (e) {
+        _logger.error('Failed to create voice agent handle: $e');
+        rethrow;
+      } finally {
+        _handleInitFuture = null;
+      }
+    })();
+
+    return _handleInitFuture!;
   }
Based on learnings: Voice agent implementation must support configuration of individual components (LLM, STT, TTS, VAD) and thread-safe pipeline orchestration.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`
around lines 63 - 101, getHandle() can race: multiple callers can see
_handle==null and concurrently create native handles, leaking native state; to
fix, serialize initialization by adding a private in-flight marker (e.g.,
Future<RacVoiceAgentHandle>? _initFuture or Completer) and update getHandle to:
1) return _handle if present; 2) if _initFuture exists await it and then return
_handle; 3) otherwise create and assign _initFuture before building shared
handles (DartBridgeLLM.shared.getHandle, DartBridgeSTT.shared.getHandle,
DartBridgeTTS.shared.getHandle, DartBridgeVAD.shared.getHandle), perform native
creation, set _handle on success, complete _initFuture, and clear it on error
(completeError) so concurrent callers are queued and only one native instance is
created; ensure finally blocks still free native pointers and that exceptions
rethrow after completing/clearing the in-flight marker.
🧹 Nitpick comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart (1)

297-297: Cache isolate-side function pointers for the voice-turn hot path.

processVoiceTurn routes to a helper that still performs loadCommons + lookupFunction on every turn, which partially defeats this PR’s lookup optimization in the most performance-sensitive path.

Suggested refactor
 class DartBridgeVoiceAgent {
+  static final DynamicLibrary _commonsLib = PlatformLoader.loadCommons();
+  static final int Function(
+      RacVoiceAgentHandle,
+      Pointer<Void>,
+      int,
+      Pointer<RacVoiceAgentResultStruct>) _processVoiceTurnFn = _commonsLib.lookupFunction<
+          Int32 Function(RacVoiceAgentHandle, Pointer<Void>, IntPtr,
+              Pointer<RacVoiceAgentResultStruct>),
+          int Function(RacVoiceAgentHandle, Pointer<Void>, int,
+              Pointer<RacVoiceAgentResultStruct>)>('rac_voice_agent_process_voice_turn');
+
+  static final void Function(Pointer<RacVoiceAgentResultStruct>)? _resultFreeFn = (() {
+    try {
+      return _commonsLib.lookupFunction<
+          Void Function(Pointer<RacVoiceAgentResultStruct>),
+          void Function(Pointer<RacVoiceAgentResultStruct>)>('rac_voice_agent_result_free');
+    } catch (_) {
+      return null;
+    }
+  })();
@@
   static Future<VoiceTurnResult> _processVoiceTurnInIsolate(
@@
-      final lib = PlatformLoader.loadCommons();
-      final processFn = lib.lookupFunction<...>('rac_voice_agent_process_voice_turn');
-      final status =
-          processFn(handle, audioPtr.cast<Void>(), audioData.length, resultPtr);
+      final status =
+          _processVoiceTurnFn(handle, audioPtr.cast<Void>(), audioData.length, resultPtr);
@@
-      return _parseVoiceTurnResultStatic(resultPtr.ref, lib);
+      return _parseVoiceTurnResultStatic(resultPtr.ref, _commonsLib);
@@
-      final lib = PlatformLoader.loadCommons();
-      try {
-        final freeFn = lib.lookupFunction<...>('rac_voice_agent_result_free');
-        freeFn(resultPtr);
-      } catch (e) {
-        // Function may not exist, just free the struct
-      }
+      _resultFreeFn?.call(resultPtr);
       calloc.free(resultPtr);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`
at line 297, processVoiceTurn currently calls Isolate.run(() =>
_processVoiceTurnInIsolate(handle, audioData)) but _processVoiceTurnInIsolate
performs loadCommons + lookupFunction on every turn; change the isolate-side
implementation to cache the results so lookups happen once per isolate.
Concretely, introduce isolate-scoped cached variables (e.g., top-level or static
late finals) for the result of loadCommons and for the function pointers
returned by lookupFunction, and update _processVoiceTurnInIsolate (or replace it
with a top-level wrapper invoked by Isolate.run) to use those cached variables
instead of calling loadCommons/lookupFunction on each invocation so the hot path
reuses the cached function pointers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`:
- Around line 63-101: getHandle() can race: multiple callers can see
_handle==null and concurrently create native handles, leaking native state; to
fix, serialize initialization by adding a private in-flight marker (e.g.,
Future<RacVoiceAgentHandle>? _initFuture or Completer) and update getHandle to:
1) return _handle if present; 2) if _initFuture exists await it and then return
_handle; 3) otherwise create and assign _initFuture before building shared
handles (DartBridgeLLM.shared.getHandle, DartBridgeSTT.shared.getHandle,
DartBridgeTTS.shared.getHandle, DartBridgeVAD.shared.getHandle), perform native
creation, set _handle on success, complete _initFuture, and clear it on error
(completeError) so concurrent callers are queued and only one native instance is
created; ensure finally blocks still free native pointers and that exceptions
rethrow after completing/clearing the in-flight marker.

---

Nitpick comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`:
- Line 297: processVoiceTurn currently calls Isolate.run(() =>
_processVoiceTurnInIsolate(handle, audioData)) but _processVoiceTurnInIsolate
performs loadCommons + lookupFunction on every turn; change the isolate-side
implementation to cache the results so lookups happen once per isolate.
Concretely, introduce isolate-scoped cached variables (e.g., top-level or static
late finals) for the result of loadCommons and for the function pointers
returned by lookupFunction, and update _processVoiceTurnInIsolate (or replace it
with a top-level wrapper invoked by Isolate.run) to use those cached variables
instead of calling loadCommons/lookupFunction on each invocation so the hot path
reuses the cached function pointers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 42ca02a6-b2da-404a-827d-879983f605d0

📥 Commits

Reviewing files that changed from the base of the PR and between 74163da and 5b96bf2.

📒 Files selected for processing (1)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart

@Siddhesh2377
Copy link
Copy Markdown
Collaborator

Hey nice work on this! The centralized lookup pattern is solid. A few things to clean up before merge:

  1. Remove unused platform_loader.dart imports
  2. Inconsistent type style in native_functions.dart :: some fields use type aliases like
    RacLlmComponentIsLoadedNative/Dart and others use inline types like Int32 Function(RacHandle). Pick
    one style and stick with it across the whole file.
  3. You removed a debug log in the LLM bridge :: the 'Calling rac_llm_component_load_model with
    handle...' log before the native call got dropped. Was that intentional? If so no worries, just
    checking.

Otherwise LGTM, the core optimization is clean and the isolate methods correctly keep their own
lookups.

@Reef-7

Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart (2)

72-112: ⚠️ Potential issue | 🟠 Major

Complete _initFuture when shared-handle creation fails.

_initFuture is published before the four shared getHandle() calls, but those calls happen outside the guarded try. If any one throws, the completer is never completed and every later getHandle() will wait forever on the stale future.

🐛 Suggested fix
-    // Use shared component handles (matches Swift approach)
-    // This allows the voice agent to use already-loaded models from the
-    // individual component bridges (STT, LLM, TTS, VAD)
-    final llmHandle = DartBridgeLLM.shared.getHandle();
-    final sttHandle = DartBridgeSTT.shared.getHandle();
-    final ttsHandle = DartBridgeTTS.shared.getHandle();
-    final vadHandle = DartBridgeVAD.shared.getHandle();
-
-    _logger.debug(
-        'Creating voice agent with shared handles: LLM=$llmHandle, STT=$sttHandle, TTS=$ttsHandle, VAD=$vadHandle');
-
     try {
+      // Use shared component handles (matches Swift approach)
+      // This allows the voice agent to use already-loaded models from the
+      // individual component bridges (STT, LLM, TTS, VAD)
+      final llmHandle = DartBridgeLLM.shared.getHandle();
+      final sttHandle = DartBridgeSTT.shared.getHandle();
+      final ttsHandle = DartBridgeTTS.shared.getHandle();
+      final vadHandle = DartBridgeVAD.shared.getHandle();
+
+      _logger.debug(
+          'Creating voice agent with shared handles: LLM=$llmHandle, STT=$sttHandle, TTS=$ttsHandle, VAD=$vadHandle');
+
       final handlePtr = calloc<RacHandle>();
       try {
         final result = NativeFunctions.voiceAgentCreate(
             llmHandle, sttHandle, ttsHandle, vadHandle, handlePtr);
Based on learnings: Voice agent implementation must support configuration of individual components (LLM, STT, TTS, VAD) and thread-safe pipeline orchestration.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`
around lines 72 - 112, The completer assigned to _initFuture is created before
the DartBridge*.shared.getHandle() calls and never completed if any getHandle
throws; move the creation/assignment of the Completer<RacHandle>/_initFuture to
after all four getHandle() calls OR wrap the getHandle() calls in the existing
try/catch so that any exception calls completer.completeError(e, st) (and sets
_initFuture = null) before rethrowing; reference the symbols _initFuture,
Completer<RacHandle>, DartBridgeLLM.shared.getHandle(),
DartBridgeSTT.shared.getHandle(), DartBridgeTTS.shared.getHandle(),
DartBridgeVAD.shared.getHandle(), completer.completeError to locate and update
the logic accordingly.

391-414: ⚠️ Potential issue | 🔴 Critical

Memory leak: inner UTF-8 strings from C++ are not freed in transcribe() and generateResponse().

These methods call toDartString() which copies the string contents, but calloc.free(resultPtr) only releases the outer Pointer<Pointer<Utf8>> wrapper. The inner Utf8 pointer allocated by C++ is never freed. The synthesizeSpeech() method demonstrates the correct pattern—it explicitly calls NativeFunctions.racFree() on pointers returned from native C++ functions before freeing the wrapper.

Both methods require the suggested fix to call NativeFunctions.racFree(resultPtr.value.cast<Void>()) before freeing resultPtr.

🧹 Suggested fix
   Future<String> transcribe(Uint8List audioData) async {
@@
     } finally {
       calloc.free(audioPtr);
+      if (resultPtr.value != nullptr) {
+        NativeFunctions.racFree(resultPtr.value.cast<Void>());
+      }
       calloc.free(resultPtr);
     }
   }
@@
   Future<String> generateResponse(String prompt) async {
@@
     } finally {
       calloc.free(promptPtr);
+      if (resultPtr.value != nullptr) {
+        NativeFunctions.racFree(resultPtr.value.cast<Void>());
+      }
       calloc.free(resultPtr);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`
around lines 391 - 414, transcribe() (and similarly generateResponse())
currently converts the returned Pointer<Utf8> to a Dart string but only frees
the outer Pointer<Pointer<Utf8>>; you must call
NativeFunctions.racFree(resultPtr.value.cast<Void>()) to free the inner
C++-allocated UTF-8 buffer before freeing resultPtr (follow the pattern used in
synthesizeSpeech()); add that racFree call in the try/finally just prior to
calloc.free(resultPtr) so both the inner string and the wrapper are released.
🧹 Nitpick comments (2)
sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart (1)

403-405: Encode rac_free optionality in the registry.

The consumer already has to handle builds where rac_free is missing. Keeping this binding non-nullable forces every caller to rediscover that ABI edge case via an exception on first access. Please verify the export contract across the native builds you ship; if it is optional, model that once here.

♻️ Suggested change
-  static late final void Function(Pointer<Void>) racFree =
-      _lib.lookupFunction<Void Function(Pointer<Void>),
-          void Function(Pointer<Void>)>('rac_free');
+  static late final void Function(Pointer<Void>)? racFree = (() {
+    try {
+      return _lib.lookupFunction<Void Function(Pointer<Void>),
+          void Function(Pointer<Void>)>('rac_free');
+    } catch (_) {
+      return null;
+    }
+  })();
+
+  static void tryRacFree(Pointer<Void> pointer) => racFree?.call(pointer);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart`
around lines 403 - 405, The binding for racFree is currently non-nullable and
will throw on first access if the native export is absent; change the registry
entry so racFree is optional (nullable) by using a nullable type for the lookup
result (e.g., void Function(Pointer<Void>)? racFree) and assign it only if the
native symbol is found via lookup, or use tryLookup to produce null when
missing; update callers of racFree to null-check/guard before invoking. Ensure
you modify the declaration named racFree and any registry initialization that
assigns it so the optionality is encoded at the API boundary.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart (1)

337-338: Remove the unused lib plumbing from _parseVoiceTurnResultStatic.

The parser no longer uses lib, so passing _voiceAgentLib through this path just leaves an analyzer warning behind.

✂️ Suggested cleanup
-      return _parseVoiceTurnResultStatic(resultPtr.ref, _voiceAgentLib);
+      return _parseVoiceTurnResultStatic(resultPtr.ref);
@@
-  static VoiceTurnResult _parseVoiceTurnResultStatic(
-    RacVoiceAgentResultStruct result,
-    DynamicLibrary lib,
-  ) {
+  static VoiceTurnResult _parseVoiceTurnResultStatic(
+    RacVoiceAgentResultStruct result,
+  ) {

Also applies to: 355-358

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`
around lines 337 - 338, The _parseVoiceTurnResultStatic function currently
accepts a lib parameter that is unused; remove the lib parameter from the
function signature and all call sites that pass _voiceAgentLib (e.g., the call
returning _parseVoiceTurnResultStatic(resultPtr.ref, _voiceAgentLib) and the
similar call around the later occurrence), update the function declaration and
any typedefs to match the new single-argument signature, and run the analyzer to
ensure no leftover references to the removed lib parameter remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`:
- Around line 72-112: The completer assigned to _initFuture is created before
the DartBridge*.shared.getHandle() calls and never completed if any getHandle
throws; move the creation/assignment of the Completer<RacHandle>/_initFuture to
after all four getHandle() calls OR wrap the getHandle() calls in the existing
try/catch so that any exception calls completer.completeError(e, st) (and sets
_initFuture = null) before rethrowing; reference the symbols _initFuture,
Completer<RacHandle>, DartBridgeLLM.shared.getHandle(),
DartBridgeSTT.shared.getHandle(), DartBridgeTTS.shared.getHandle(),
DartBridgeVAD.shared.getHandle(), completer.completeError to locate and update
the logic accordingly.
- Around line 391-414: transcribe() (and similarly generateResponse()) currently
converts the returned Pointer<Utf8> to a Dart string but only frees the outer
Pointer<Pointer<Utf8>>; you must call
NativeFunctions.racFree(resultPtr.value.cast<Void>()) to free the inner
C++-allocated UTF-8 buffer before freeing resultPtr (follow the pattern used in
synthesizeSpeech()); add that racFree call in the try/finally just prior to
calloc.free(resultPtr) so both the inner string and the wrapper are released.

---

Nitpick comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`:
- Around line 337-338: The _parseVoiceTurnResultStatic function currently
accepts a lib parameter that is unused; remove the lib parameter from the
function signature and all call sites that pass _voiceAgentLib (e.g., the call
returning _parseVoiceTurnResultStatic(resultPtr.ref, _voiceAgentLib) and the
similar call around the later occurrence), update the function declaration and
any typedefs to match the new single-argument signature, and run the analyzer to
ensure no leftover references to the removed lib parameter remain.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart`:
- Around line 403-405: The binding for racFree is currently non-nullable and
will throw on first access if the native export is absent; change the registry
entry so racFree is optional (nullable) by using a nullable type for the lookup
result (e.g., void Function(Pointer<Void>)? racFree) and assign it only if the
native symbol is found via lookup, or use tryLookup to produce null when
missing; update callers of racFree to null-check/guard before invoking. Ensure
you modify the declaration named racFree and any registry initialization that
assigns it so the optionality is encoded at the API boundary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: de2ede2f-8505-42fd-a2c8-4696188a33fc

📥 Commits

Reviewing files that changed from the base of the PR and between 5b96bf2 and f3fb376.

📒 Files selected for processing (3)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_llm.dart

Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart`:
- Around line 412-414: The unguarded calls to
NativeFunctions.racFree(resultPtr.value.cast<Void>()) in the cleanup/finally
blocks (the calls around the resultPtr usages at the spots matching the earlier
fix) can throw when rac_free is missing; wrap each racFree invocation in a
try-catch like the existing pattern around the rac_free call at the later block
(the one near line 476) so that any missing native symbol or runtime error is
swallowed and does not convert a successful call into a failure—locate the uses
of NativeFunctions.racFree and resultPtr and protect each call with a try {
NativeFunctions.racFree(...) } catch (_) { } block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7001cc7c-8231-4fe7-8def-798d9043d3d2

📥 Commits

Reviewing files that changed from the base of the PR and between f3fb376 and c00f1ac.

📒 Files selected for processing (1)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_voice_agent.dart

@Siddhesh2377
Copy link
Copy Markdown
Collaborator

@coderabbitai review plz

@Reef-7 Reef-7 closed this Mar 17, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

@Siddhesh2377 Sure! I'll kick off a review of the latest changes on this PR now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Reef-7 Reef-7 reopened this Mar 17, 2026
@Siddhesh2377 Siddhesh2377 marked this pull request as draft March 17, 2026 17:59
@Siddhesh2377
Copy link
Copy Markdown
Collaborator

@coderabbitai review plz

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

@Siddhesh2377 Sure! I'll kick off a review of the latest changes on this PR now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Siddhesh2377
Copy link
Copy Markdown
Collaborator

hey @greptile-apps plz review

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (2)
sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart (2)

360-369: Consider removing unused cached fields.

Per the PR discussion, voiceAgentProcessVoiceTurn and voiceAgentResultFree (along with llmGenerate, llmGenerateStream, sttTranscribe, ttsSynthesize) are never called because their callers run in background isolates and perform per-isolate inline lookups. These cached main-isolate fields don't provide the intended optimization.

Options:

  1. Remove these unused fields to reduce dead code.
  2. Alternatively, refactor isolate helpers to use NativeFunctions.* if isolate-local caching is desired.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart`
around lines 360 - 369, Remove the unused cached native lookup fields from
native_functions.dart—specifically voiceAgentProcessVoiceTurn and
voiceAgentResultFree (and likewise llmGenerate, llmGenerateStream,
sttTranscribe, ttsSynthesize if present) because callers perform per-isolate
inline lookups; delete their static final declarations and associated
_lib.lookupFunction calls, remove any now-unused imports or references, and run
analyzer/tests to ensure no remaining usages; alternatively, if you prefer
isolate-local caching, refactor the isolate helper code to call
NativeFunctions.<functionName> (e.g.,
NativeFunctions.voiceAgentProcessVoiceTurn) instead of doing inline lookups.

408-520: Struct duplication creates maintenance risk.

These struct definitions (RacSttOptionsStruct, RacSttResultStruct, RacTtsOptionsStruct, RacTtsResultStruct) are duplicated from dart_bridge_stt.dart and dart_bridge_tts.dart. While the comment explains the rationale (avoiding imports), this duplication means changes to C struct layouts must be synchronized in multiple Dart files.

Consider moving canonical struct definitions to a shared location (e.g., ffi_types.dart) and importing from both NativeFunctions and bridge files to establish a single source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart`
around lines 408 - 520, Duplicate FFI struct definitions (RacSttOptionsStruct,
RacSttResultStruct, RacTtsOptionsStruct, RacTtsResultStruct) should be
centralized: move the canonical definitions into a shared file (e.g.,
ffi_types.dart) and replace the copies in native_functions.dart and
dart_bridge_stt.dart/dart_bridge_tts.dart with imports from that shared file;
update any references in NativeFunctions and bridge code to use the imported
types (e.g., RacSttOptionsStruct, RacSttResultStruct, RacTtsOptionsStruct,
RacTtsResultStruct) so there is a single source of truth for the C struct
layouts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart`:
- Around line 360-369: Remove the unused cached native lookup fields from
native_functions.dart—specifically voiceAgentProcessVoiceTurn and
voiceAgentResultFree (and likewise llmGenerate, llmGenerateStream,
sttTranscribe, ttsSynthesize if present) because callers perform per-isolate
inline lookups; delete their static final declarations and associated
_lib.lookupFunction calls, remove any now-unused imports or references, and run
analyzer/tests to ensure no remaining usages; alternatively, if you prefer
isolate-local caching, refactor the isolate helper code to call
NativeFunctions.<functionName> (e.g.,
NativeFunctions.voiceAgentProcessVoiceTurn) instead of doing inline lookups.
- Around line 408-520: Duplicate FFI struct definitions (RacSttOptionsStruct,
RacSttResultStruct, RacTtsOptionsStruct, RacTtsResultStruct) should be
centralized: move the canonical definitions into a shared file (e.g.,
ffi_types.dart) and replace the copies in native_functions.dart and
dart_bridge_stt.dart/dart_bridge_tts.dart with imports from that shared file;
update any references in NativeFunctions and bridge code to use the imported
types (e.g., RacSttOptionsStruct, RacSttResultStruct, RacTtsOptionsStruct,
RacTtsResultStruct) so there is a single source of truth for the C struct
layouts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 22f3d90a-b745-416d-981a-d3faa13ca7a2

📥 Commits

Reviewing files that changed from the base of the PR and between 3da6a00 and 991ee61.

📒 Files selected for processing (1)
  • sdk/runanywhere-flutter/packages/runanywhere/lib/native/native_functions.dart

@Reef-7 Reef-7 marked this pull request as ready for review March 20, 2026 08:26
@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 20, 2026

@greptile-apps please review

@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 20, 2026

@greptile-apps you can review

@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 20, 2026

@greptile-apps you can review

@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 21, 2026

Hi @Siddhesh2377 , I’ve implemented the changes and they passed the AI reviews (CodeRabbit/Greptile).
I had issues running the app locally on my machine, so I couldn't verify the Runtime myself.
Could you please approve and run the workflows? This will help me see if the pipeline catches anything I missed. If the tests passes, we should be good to go.

@Siddhesh2377
Copy link
Copy Markdown
Collaborator

Hi @Siddhesh2377 , I’ve implemented the changes and they passed the AI reviews (CodeRabbit/Greptile). I had issues running the app locally on my machine, so I couldn't verify the Runtime myself. Could you please approve and run the workflows? This will help me see if the pipeline catches anything I missed. If the tests passes, we should be good to go.

Sure thing... let me see

@Siddhesh2377
Copy link
Copy Markdown
Collaborator

Hey @Reef-7 👋

Tried building this locally on an Android device and ran into a couple of compile errors in native_functions.dart — looks like small oversights from the refactor:

1. Missing closing } for the NativeFunctions class

The racFree IIFE at the bottom closes with })(); but the class itself never gets its closing brace. So Dart throws:

Can't find '}' to match '{'.
abstract class NativeFunctions {

2. Missing imports for RacSttResultStruct and RacVadResultStruct

These are used in the sttResultFree field (line ~98) and the vadProcess field (line ~203), but only ffi_types.dart is imported — those structs live in dart_bridge_stt.dart and dart_bridge_vad.dart respectively.

Error: 'RacSttResultStruct' isn't a type.
Error: 'RacVadResultStruct' isn't a type.

Quick fix would be something like:

import 'package:runanywhere/native/dart_bridge_stt.dart' show RacSttResultStruct;
import 'package:runanywhere/native/dart_bridge_vad.dart' show RacVadResultStruct;

and adding } at the end of the class after the racFree field.

Everything else in the PR looks solid — the centralized FFI caching, memory leak fixes, and the race condition guard in getHandle() are all good stuff. Just these two things blocking compilation. 🙂

@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 22, 2026

@Siddhesh2377 Thank you for the update and informing me with the details, i'll try to work on it as soon as possible and let you know.

@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 22, 2026

@greptile-apps can you review please

@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 22, 2026

Hi @Siddhesh2377, thanks for the feedback.
I addressed the issues as you suggested:

  1. Added the missing closing brace for the NativeFunctions class.
  2. Fixed the compilation issue with RacVadResultStruct by importing dart_bridge_vad.dart with a 'vad' prefix and resolving the name collision using vad.RacVadResultStruct.
    Let me know if anything else need adjustment

@Siddhesh2377
Copy link
Copy Markdown
Collaborator

Hey @Reef-7 Was the flutter build was compiling for you ?

@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 25, 2026

@Siddhesh2377 Unfortunately, i cannot use the flutter build, since it requires an arm64-v8a ABI Android device from Android Studio(that's what i understood), that i didn't find.

@Siddhesh2377
Copy link
Copy Markdown
Collaborator

Ahhh I get it now... let me have a look into the build and ping u okay !

@Siddhesh2377
Copy link
Copy Markdown
Collaborator

so @Reef-7 U weren't able to Compile even the Flutter SDK ?

@Reef-7
Copy link
Copy Markdown
Author

Reef-7 commented Mar 25, 2026

@Siddhesh2377 Now i've tested the example app using flutter build apk from the runanywhere-sdks\examples\flutter\RunAnywhereAI folder, the Build was completed without errors as far as i've seen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants