Add Genie NPU backend support (Qualcomm Snapdragon)#462
Add Genie NPU backend support (Qualcomm Snapdragon)#462shubhammalhotra28 wants to merge 4 commits intomainfrom
Conversation
…ragon)" This reverts commit 7be8565.
…y config - Add libQnnHtpV81.so to Kotlin example jniLibs for S26 testing - Update Flutter binary_config.gradle to reference genie-v0.3.0 release Made-with: Cursor
|
Too many files changed for review. ( |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis pull request introduces Qualcomm Genie NPU backend support across the RunAnywhere SDK family (Flutter, Kotlin, React Native, and commons). It adds device NPU chip detection APIs, a new Genie inference framework constant, enhanced LoRA adapter validation in the llamacpp backend, refactored archive extraction using system tools, and comprehensive documentation for three SDKs. The change includes new example app integrations, UI updates, and build configurations to support Genie NPU-accelerated models on Android. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
The runanywhere-genie repo is private (Qualcomm proprietary binaries), so Flutter binary downloads must come from the public runanywhere-sdks repo where the Genie ZIP is published as a GitHub release asset. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 1
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeModelRegistry.kt (1)
90-100:⚠️ Potential issue | 🟠 MajorAdd missing Framework constants to align with C++ enum in rac_model_types.h.
The Kotlin registry is missing three framework constants that are defined and actively used in the C++ backend:
MLX = 7(Apple Silicon VLM)COREML = 8(Core ML / Apple Neural Engine)WHISPERKIT_COREML = 9(WhisperKit CoreML STT)These must be added between
NONE = 6andGENIE = 10to maintain exact alignment with the C++ enum and ensure proper model framework resolution across platform boundaries.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeModelRegistry.kt` around lines 90 - 100, The Framework object in CppBridgeModelRegistry is missing three constants required to match the C++ enum in rac_model_types.h; add MLX = 7, COREML = 8, and WHISPERKIT_COREML = 9 inside the Framework object (between NONE = 6 and GENIE = 10) so the Kotlin constants align exactly with the C++ enum and model framework resolution across platforms.sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_model_registry.dart (1)
342-361:⚠️ Potential issue | 🟠 MajorMissing
geniecase in reverse FFI conversion.The
_frameworkFromFfifunction doesn't handle case10for Genie, but_frameworkToFfidoes mapgenieto10. This asymmetry means models retrieved from C++ with framework=10 will be incorrectly converted toInferenceFramework.unknown.🐛 Proposed fix: Add genie case to _frameworkFromFfi
static public_types.InferenceFramework _frameworkFromFfi(int framework) { switch (framework) { case 0: return public_types.InferenceFramework.onnx; case 1: return public_types.InferenceFramework.llamaCpp; case 2: return public_types.InferenceFramework.foundationModels; case 3: return public_types.InferenceFramework.systemTTS; case 4: return public_types.InferenceFramework.fluidAudio; case 5: return public_types.InferenceFramework.builtIn; case 6: return public_types.InferenceFramework.none; + case 10: + return public_types.InferenceFramework.genie; default: return public_types.InferenceFramework.unknown; } }🤖 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_model_registry.dart` around lines 342 - 361, The reverse FFI mapper _frameworkFromFfi is missing the Genie mapping (case 10) that _frameworkToFfi emits; update _frameworkFromFfi to handle integer 10 and return public_types.InferenceFramework.genie so values produced by _frameworkToFfi round-trip correctly (keep the existing default to unknown for other values).sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp (1)
2048-2055:⚠️ Potential issue | 🟡 MinorPreserve
result.confidenceintranscribeFile().This path now always returns
0, even though the STT result already carries a confidence score andtranscribe()exposes it. That regresses any caller that ranks or filters file transcriptions by confidence.Suggested fix
std::string transcribedText; if (result.text) { transcribedText = std::string(result.text); } + float confidence = result.confidence; rac_stt_result_free(&result); LOGI("Transcription result: %s", transcribedText.c_str()); - return "{\"text\":" + jsonString(transcribedText) + ",\"confidence\":0}"; + return "{\"text\":" + jsonString(transcribedText) + ",\"confidence\":" + std::to_string(confidence) + "}";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp` around lines 2048 - 2055, The transcribeFile() return value currently hardcodes confidence to 0; capture and preserve result.confidence before freeing the C struct and include it in the JSON response instead of 0. Specifically, read result.confidence into a local variable (e.g., double confidence = result.confidence) prior to calling rac_stt_result_free(&result), then build the return string using jsonString for text and the numeric confidence variable for the "confidence" field so callers get the real score (references: transcribeFile(), result, rac_stt_result_free, jsonString).sdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cpp (1)
1263-1315:⚠️ Potential issue | 🟠 MajorThe zero-match validation returns without cleaning up the created LoRA adapter.
After
llama_adapter_lora_init()succeeds, the check formatched_tensors == 0(around line 1285) returns false before the adapter is added to thelora_adapters_vector. This orphans the adapter pointer. The subsequent failures inrecreate_context()andapply_lora_adapters()are properly handled withlora_adapters_.pop_back(), but only because the adapter was already stored in the vector—the zero-match path lacks this protection.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cpp` around lines 1263 - 1315, The code leaks the adapter when llama_adapter_lora_init(...) succeeds but matched_tensors == 0; before returning false you must free the created adapter (call the corresponding cleanup, e.g. llama_adapter_lora_free(adapter)) or push a LoraAdapterEntry into lora_adapters_ and reuse the existing pop_back cleanup logic; update the zero-match branch in the load path (around llama_adapter_lora_init, matched_tensors check) to free the adapter or store it so recreate_context()/apply_lora_adapters() cleanup paths cover it, referencing llama_adapter_lora_init, llama_adapter_lora_free (or the appropriate free function), LoraAdapterEntry, lora_adapters_, recreate_context, and apply_lora_adapters.sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+ModelManagement.jvmAndroid.kt (1)
1186-1191:⚠️ Potential issue | 🟠 MajorAlso evict the in-memory caches on delete.
availableModels()overlaysregisteredModelsback on top of the C++ registry. SincedeleteModel()only removes the bridge/storage entries, a programmatically registered model can reappear immediately after deletion until process restart.Suggested fix
actual suspend fun RunAnywhere.deleteModel(modelId: String) { if (!isInitialized) { throw SDKError.notInitialized("SDK not initialized") } CppBridgeStorage.delete(CppBridgeStorage.StorageNamespace.DOWNLOADS, modelId) CppBridgeModelRegistry.remove(modelId) + synchronized(modelCacheLock) { + registeredModels.removeAll { it.id == modelId } + } + synchronized(multiFileCacheLock) { + multiFileModelCache.remove(modelId) + } + synchronized(companionFilesLock) { + modelCompanionFiles.remove(modelId) + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere`+ModelManagement.jvmAndroid.kt around lines 1186 - 1191, DeleteModel currently removes C++ storage and registry entries but does not evict in-memory caches, so a programmatically registered model can reappear; update RunAnywhere.deleteModel to also remove modelId from the in-memory registration and invalidate any cached available-models list used by availableModels() (e.g., remove from registeredModels and clear/invalidate the availableModels cache or modelsCache) after calling CppBridgeStorage.delete and CppBridgeModelRegistry.remove so the model cannot immediately reappear.
🟠 Major comments (21)
sdk/runanywhere-react-native/packages/core/src/services/FileSystem.ts-509-517 (1)
509-517:⚠️ Potential issue | 🟠 MajorNormalize download keys; current cancellation lookup can miss active jobs.
activeDownloadJobsis keyed by themodelIdpassed todownloadModel(Line 510), but cancellation callers may use a canonical ID without extension. That mismatch makes Line 886 miss the job andstopDownloadis never called.💡 Proposed fix
@@ - activeDownloadJobs.set(modelId, downloadResult.jobId); + const downloadKey = getBaseModelId(modelId); + activeDownloadJobs.set(downloadKey, downloadResult.jobId); @@ - activeDownloadJobs.delete(modelId); + activeDownloadJobs.delete(downloadKey); @@ cancelDownload(modelId: string): boolean { - const jobId = activeDownloadJobs.get(modelId); + const downloadKey = getBaseModelId(modelId); + const jobId = activeDownloadJobs.get(downloadKey); if (jobId != null && RNFS) { RNFS.stopDownload(jobId); - activeDownloadJobs.delete(modelId); + activeDownloadJobs.delete(downloadKey); logger.info(`Cancelled download for: ${modelId} (jobId=${jobId})`); return true; }Also applies to: 885-889
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-react-native/packages/core/src/services/FileSystem.ts` around lines 509 - 517, The activeDownloadJobs map uses the raw modelId passed to downloadModel which can differ from the canonical ID used by cancellation logic; update downloadModel (where activeDownloadJobs.set and delete are used) to normalize the key (e.g., derive the canonical ID the same way stopDownload/cancellation lookup does—strip extensions or call the shared canonicalization helper) before inserting and deleting so the cancellation lookup (stopDownload) can find and cancel the job; ensure the same normalization is applied to both the set, the finally delete, and any other locations that access activeDownloadJobs.sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+Models.ts-514-518 (1)
514-518:⚠️ Potential issue | 🟠 MajorDon’t return success when native cancellation fails.
Line 515 returns a boolean, but it’s ignored. Current flow logs “Cancelled” and returns
trueeven if no RNFS job was actually stopped.💡 Proposed fix
export async function cancelDownload(modelId: string): Promise<boolean> { if (activeDownloads.has(modelId)) { // Stop the native RNFS download job - FileSystem.cancelDownload(modelId); + const cancelled = FileSystem.cancelDownload(modelId); + if (!cancelled) { + logger.warning(`Failed to cancel native download: ${modelId}`); + return false; + } activeDownloads.delete(modelId); logger.info(`Cancelled download: ${modelId}`); return true; } return false; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere`+Models.ts around lines 514 - 518, FileSystem.cancelDownload(modelId) is currently called and its boolean result ignored, causing logger.info(`Cancelled download: ${modelId}`) and activeDownloads.delete(modelId) to run even when cancellation failed; change the flow to capture the return value (or awaited result) from FileSystem.cancelDownload(modelId), only call activeDownloads.delete(modelId) and logger.info(...) when that result is truthy, and otherwise log an error (or warning) and return false so the function does not report success when cancellation failed; update the return value to reflect the actual cancel result.sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/foundation/SDKLogger.kt-335-336 (1)
335-336:⚠️ Potential issue | 🟠 MajorUse one synchronization strategy for
_destinations; current change is still race-prone.Line 335 snapshots
_destinations, but Line 361 introduces@Synchronizedwhile other mutations useMutex(andremoveDestinationSyncis still unlocked). Because reads/writes are not coordinated under a single lock, concurrent mutation risks remain (duplicate registration, inconsistent iteration/flush behavior).Also applies to: 361-365
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/foundation/SDKLogger.kt` around lines 335 - 336, The code currently mixes synchronization strategies for _destinations (snapshot via _destinations.toList(), `@Synchronized` on some methods, and a Mutex used elsewhere) which leaves race windows (e.g., duplicate registration, inconsistent iteration/flush and an unlocked removeDestinationSync). Pick a single synchronization strategy and apply it everywhere: either guard all accesses/mutations of _destinations (reads, writes, removeDestinationSync, iteration in the flush/write loop) with the same Mutex (use withLock around reads and writes and removeDestinationSync) or consistently use Kotlin `@Synchronized` on the same monitor for all methods and remove the Mutex; ensure the iteration does not rely on a toList() snapshot without the lock. Update methods such as removeDestinationSync, the loop that iterates _destinations, and any register/remove functions to use the chosen lock so all concurrent access is serialized.sdk/runanywhere-flutter/packages/runanywhere_llamacpp/android/binary_config.gradle-14-14 (1)
14-14:⚠️ Potential issue | 🟠 MajorHardcoded local mode here has the same release-risk as ONNX config.
Line 14 forces local JNI usage and bypasses download fallback. Please make it property-driven for reproducible CI/release behavior.
🔧 Suggested fix
- testLocal = true + testLocal = (project.findProperty("runanywhere.testLocal")?.toString()?.toBoolean() ?: false)Based on learnings: "Applies to */gradle : Kotlin SDK: Use
./gradlew :runanywhere-kotlin:compileDebugKotlinAndroid -Prunanywhere.testLocal=falseto build the Android target without requiring Android NDK (uses pre-built JNI libs from GitHub releases)."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere_llamacpp/android/binary_config.gradle` at line 14, The file currently hardcodes testLocal = true which forces local JNI usage; change it to read a Gradle project property (e.g. project.findProperty("runanywhere.testLocal") or project.hasProperty) and parse it as a boolean so builds can override with -Prunanywhere.testLocal=false; update the assignment of testLocal in binary_config.gradle (the testLocal symbol) to use the property with a sensible default (true) so CI/release can opt out without editing the file.sdk/runanywhere-flutter/packages/runanywhere_onnx/android/binary_config.gradle-14-14 (1)
14-14:⚠️ Potential issue | 🟠 MajorDefaulting
testLocaltotruecan break production/native-lib resolution flows.Line 14 forces local-only binaries and disables release download fallback. Please make this property-driven so CI/release builds can reliably use remote artifacts.
🔧 Suggested fix
- testLocal = true + testLocal = (project.findProperty("runanywhere.testLocal")?.toString()?.toBoolean() ?: false)Based on learnings: "Applies to **/gradle.properties : The
testLocalflag is set totrueingradle.properties; pass-Prunanywhere.testLocal=falseto Gradle to avoid needing Android NDK."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere_onnx/android/binary_config.gradle` at line 14, The hardcoded default testLocal = true in binary_config.gradle forces local-only binaries; change it to read a Gradle project property with a safe default (e.g., use project.findProperty('runanywhere.testLocal') ?: 'false' or check project.hasProperty and convert to boolean) so CI/release can pass -Prunanywhere.testLocal=false; update the testLocal assignment in binary_config.gradle (the testLocal symbol) to parse the property value into a boolean instead of always setting true.sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeEvents.kt-807-807 (1)
807-807:⚠️ Potential issue | 🟠 MajorCross-platform InferenceFramework inconsistency detected: Swift does not support GENIE while Kotlin and C++ do.
The value
GENIE = 10in Kotlin correctly maps to C++ usage (both use hardcoded value 10 in conversion functions). However, Swift'sInferenceFrameworkenum (lines 75–89 in ModelTypes.swift) does not include a GENIE case at all, only 10 cases: onnx, llamaCpp, foundationModels, systemTTS, fluidAudio, coreml, mlx, whisperKitCoreML, builtIn, none, unknown.This creates a platform gap: if analytics or model data includes GENIE framework attribution from Android/C++, iOS will not be able to map it and may default to unknown or fail silently. Add GENIE to Swift's InferenceFramework enum with appropriate raw value to maintain parity.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeEvents.kt` at line 807, Add a GENIE case to the Swift InferenceFramework enum in ModelTypes.swift so it matches Kotlin/C++ (use raw value 10 to mirror const val GENIE = 10); update any switches or decoding/mapping code that converts numeric framework values to InferenceFramework to handle the new .genie case so iOS will map GENIE-attributed analytics/model data correctly.sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/CppBridge.kt-562-566 (1)
562-566:⚠️ Potential issue | 🟠 MajorDo not report initialized when native readiness check throws.
At Line 564 through Line 566, a JNI failure can still return
truevia_isInitialized, which can cause false-ready state. Returnfalseon exception.Suggested fix
return try { RunAnywhereBridge.racIsInitialized() } catch (_: Exception) { - _isInitialized + false }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/CppBridge.kt` around lines 562 - 566, The current catch block returns the fallback _isInitialized when RunAnywhereBridge.racIsInitialized() throws, which can report a false-ready state; change the catch in the method that calls RunAnywhereBridge.racIsInitialized() so that any Exception results in returning false (not _isInitialized), i.e., catch Exception (or Throwable if preferred) and return false to ensure JNI failures never appear as initialized.sdk/runanywhere-flutter/packages/runanywhere/android/binary_config.gradle-14-14 (1)
14-14:⚠️ Potential issue | 🟠 MajorDefaulting
testLocaltotruemakes production consumers fragile.Line 14 disables release-binary download by default, so builds can fail when local
.sofiles are not present. Prefer production-safe default (false) with an opt-in Gradle property override.Suggested fix
- testLocal = true + testLocal = (project.findProperty("runanywhere.testLocal")?.toString()?.toBoolean() ?: false)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere/android/binary_config.gradle` at line 14, The build currently sets testLocal = true which disables release-binary download by default and breaks production consumers; change the default value of the testLocal flag to false and make it overridable via a Gradle property so consumers can opt in for local testing. Locate the testLocal assignment (symbol: testLocal) in the Gradle script and replace the hardcoded true with logic that defaults to false but reads a project/property override (e.g., use project.hasProperty or project.findProperty to parse a boolean override) so CI/production downloads remain enabled unless explicitly opted into local testing. Ensure the new implementation preserves boolean parsing for string property values and documents the property name used for the override.examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatScreen.kt-1049-1058 (1)
1049-1058:⚠️ Potential issue | 🟠 MajorThese starter prompts appear to be safety/red-team test content and should not ship in the example app.
The new prompts ("pick a lock", "hotwiring a car", "fake ID", "hack WiFi", "phishing email", "bypass security cameras") are adversarial/illicit queries. While useful for testing model safety refusals, they are inappropriate as default user-facing suggestions in a public example app:
- UX concern: New users will see these as the first interaction suggestions
- Perception risk: Could be seen as normalizing or encouraging such queries
- Professional appearance: Undermines the SDK's professional presentation
Please either revert to the previous benign prompts (e.g., "Explain quantum computing", "Write a poem about the ocean") or confirm this is intentional red-team testing that should be in a separate test configuration.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatScreen.kt` around lines 1049 - 1058, The starterPrompts list in the remember block (variable starterPrompts in ChatScreen.kt) contains unsafe/adversarial suggestions (e.g., "How do I pick a lock?", "Write instructions for hotwiring a car", etc.); replace these with benign example prompts (e.g., "Explain quantum computing", "Write a poem about the ocean", "How do I get started with Kotlin?") or move the red-team items out of the default UI into a separate test-only configuration/fixture used by safety tests, ensuring the default starterPrompts shown to users are professional and non-illicit.examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/LoraExamplePrompts.kt-10-13 (1)
10-13:⚠️ Potential issue | 🟠 MajorPlease don't ship harmful LoRA demo prompts.
These examples are surfaced directly in the app, so lockpicking/hotwiring prompts turn the sample into curated misuse guidance. That is a public safety/compliance risk even if the adapter is intentionally permissive.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/LoraExamplePrompts.kt` around lines 10 - 13, The sample prompts in LoraExamplePrompts.kt for the key "qwen2.5-0.5b-abliterated-lora-f16.gguf" include harmful instructions (lockpicking, asserting earth is flat, hotwiring); remove or replace those entries with benign, non-actionable examples (e.g., creative writing, coding help, hobby questions, or general knowledge) so the demo no longer surfaces misuse guidance; update the list associated with that key in the LoraExamplePrompts object/variable to only contain safe prompts.sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/Models/ModelTypes.kt-48-48 (1)
48-48:⚠️ Potential issue | 🟠 MajorKotlin and Swift ModelFormat enums are not aligned.
The Kotlin
ModelFormatenum includesQNN_CONTEXTbut the Swift version does not. Additionally, Swift hascoremlwhich is missing from Kotlin. The file header claims to "mirror Swift ModelFormat exactly," but verification shows they are out of sync. Per the coding guidelines, iOS is the source of truth — align the Kotlin enum with Swift, or update the header comment to document the intentional divergence.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/Models/ModelTypes.kt` at line 48, The ModelFormat enum in Kotlin (symbol: ModelFormat) is out of sync with the Swift definition: it contains QNN_CONTEXT but is missing coreml, and the file header claiming an exact mirror is therefore incorrect. Update the Kotlin enum to match the Swift iOS source of truth by removing QNN_CONTEXT (or otherwise matching Swift's exact members) and adding coreml if present in Swift, and/or adjust the file header comment to state the intentional divergence; specifically modify the ModelFormat enum entries to exactly mirror Swift's members (add/remove the symbols such as coreml and QNN_CONTEXT to match) and update the header comment accordingly.sdk/runanywhere-flutter/packages/runanywhere/lib/infrastructure/download/download_service.dart-415-430 (1)
415-430:⚠️ Potential issue | 🟠 MajorDon't delete the extracted root after a directory move fails.
When
itemis aDirectoryandrename(target)throws, the catch only logs the failure. The subsequentextractedDir.delete(recursive: true)then deletes that still-unmoved subtree, so nested model assets can be lost silently. Abort flattening or recursively copy directories before removing the source directory.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere/lib/infrastructure/download/download_service.dart` around lines 415 - 430, The code currently iterates innerItems from extractedDir and attempts FileSystemEntity.rename for each item, but when a Directory rename fails the catch only logs and later extractedDir.delete(recursive: true) can remove still-needed subtrees; update the logic in the block around extractedDir.list()/for (final item in innerItems) so that if any directory failed to move you either (a) perform a recursive copy of that Directory to the target (copy contents then delete source) before proceeding, or (b) abort the flattening operation and skip deleting extractedDir; specifically, modify the try/catch for (item as FileSystemEntity).rename(target) to detect Directory instances (item is Directory) and call a recursive copy helper (or set a boolean failedMove flag) so you do not call extractedDir.delete(recursive: true) when directories remain unmoved (refer to extractedDir, innerItems, rename, item.copy, item.delete, and the extractedDir.delete call).sdk/runanywhere-flutter/packages/runanywhere/lib/infrastructure/download/download_service.dart-335-376 (1)
335-376:⚠️ Potential issue | 🟠 MajorThis extractor is now Android-specific.
Process.run('tar', ...)/Process.run('unzip', ...)assumes those binaries are available in the app runtime. That is fine for Android/toybox, but this shared Flutter downloader also runs on non-Android targets where spawningtar/unzipis not available, so archive extraction will fail there. Keep the shell fast path platform-gated and preserve a native/Dart fallback elsewhere.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere/lib/infrastructure/download/download_service.dart` around lines 335 - 376, The extractor currently always calls external binaries via Process.run in _extractArchive; gate that fast path to Android only by checking Platform.isAndroid (import dart:io) before invoking 'tar'/'unzip', and for non-Android platforms call the existing Dart-native extraction fallback (e.g., your pure-Dart archive extraction routine or a new helper) to preserve cross-platform behavior; ensure the .zip branch similarly uses the platform check and that errors/return values remain consistent with the current contract of _extractArchive.examples/react-native/RunAnywhereAI/App.tsx-248-286 (1)
248-286:⚠️ Potential issue | 🟠 MajorKeep Genie setup best-effort instead of startup-fatal.
This block runs during
initializeSDK(). IfGenie.register(),getChip(),getNPUDownloadUrl(), or anyRunAnywhere.registerModel()call throws,registerModulesAndModels()rejects and the whole example app lands on the initialization error screen even though Genie is optional. Catch and log failures inside this block, then continue with the existing LlamaCPP/ONNX registration path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/react-native/RunAnywhereAI/App.tsx` around lines 248 - 286, Wrap the entire Genie NPU registration block inside registerModulesAndModels()/initializeSDK() in a try/catch so failures are non-fatal: call Genie.register(), getChip(), build genieModels, map to RunAnywhere.registerModel(...) and await Promise.all(...) inside the try, and on any thrown error catch it, console.error a descriptive message including the caught error, then continue (do not rethrow) so the LlamaCPP/ONNX registration path still runs; reference Genie.register, getChip, getNPUDownloadUrl, RunAnywhere.registerModel, and LLMFramework.Genie when locating the code to change.sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp-274-275 (1)
274-275:⚠️ Potential issue | 🟠 MajorUse the
RAC_FRAMEWORK_GENIEsymbol instead of hard-coded10.These three sites hard-code Genie as
10. This couples the React Native bridge to a private numeric assumption instead of the RACommons ABI enum. If the enum is reordered or the library is updated, these sites will silently mismatch. Other frameworks in the same code paths correctly use the symbol (e.g.,RAC_FRAMEWORK_COREML). Replace the literal withRAC_FRAMEWORK_GENIEto stay in sync with the shared C API.Also applies to: lines 909–910, 983–984
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp` around lines 274 - 275, Replace the hard-coded numeric literal 10 with the enum symbol RAC_FRAMEWORK_GENIE wherever the Genie framework is detected (e.g., in the conditional that currently returns (rac_inference_framework_t)10 and the two other occurrences noted near the same code paths). Update the return expression to return RAC_FRAMEWORK_GENIE (cast only if necessary to rac_inference_framework_t) so the React Native bridge uses the shared C API enum rather than a private numeric constant, and ensure the translation unit has the enum definition in scope (include the header that defines RAC_FRAMEWORK_GENIE if not already present).sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart-2547-2548 (1)
2547-2548:⚠️ Potential issue | 🟠 MajorKeep compatibility shims for the moved RAG API.
RunAnywhereno longer exposes anyrag*statics, so upgrading turns existingRunAnywhere.ragCreatePipeline(...)call sites into compile errors. If the extension is the long-term home, please leave thin delegates here or document this as an explicit breaking SDK change.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart` around lines 2547 - 2548, RunAnywhere removed its rag* static methods causing breaking changes; restore thin compatibility shims on the RunAnywhere class that delegate to the new RunAnywhereRAG extension methods (e.g., implement RunAnywhere.ragCreatePipeline(...), RunAnywhere.ragLoadPipeline(...), and any other rag* statics that were removed) by forwarding arguments and returning the extension call results so existing call sites continue to compile while the real implementation lives in RunAnywhereRAG.sdk/runanywhere-commons/src/infrastructure/model_management/model_types.cpp-153-160 (1)
153-160:⚠️ Potential issue | 🟠 Major
QNN_CONTEXTis still undiscoverable from path-based inference.
rac_model_format_extension(RAC_MODEL_FORMAT_QNN_CONTEXT)now returns"bin", but the detection helpers below still resolve.bintoRAC_MODEL_FORMAT_BIN→RAC_FRAMEWORK_FLUID_AUDIO. Any lazy discovery/restore path that infers type from filenames will still misclassify Genie payloads after restart. Please preserve Genie’s framework/format out of band or add Genie-specific directory inspection instead of relying on the raw extension.As per coding guidelines, "Model registry must support lazy initialization and provide path resolution for all model types (GGUF, ONNX, GGML)."
Also applies to: 177-178, 423-425
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-commons/src/infrastructure/model_management/model_types.cpp` around lines 153 - 160, The Genie payloads are being misclassified because rac_model_format_extension(RAC_MODEL_FORMAT_QNN_CONTEXT) returns "bin" but the path-based detection logic still maps ".bin" to RAC_MODEL_FORMAT_BIN/RAC_FRAMEWORK_FLUID_AUDIO; update the path-detection logic (the detection helpers referenced in the comment and any path-to-format resolver) so that when the model framework is or can be identified as RAC_FRAMEWORK_GENIE it prefers RAC_MODEL_FORMAT_QNN_CONTEXT (RAC_MODEL_FORMAT_QNN_CONTEXT) over the generic BIN mapping, or alternatively preserve the Genie framework/format out-of-band in the model registry metadata used for lazy initialization (so restore/discovery uses the stored framework+format rather than inferring from extension); apply the same change to the other occurrences noted (the blocks corresponding to the comment at 177-178 and 423-425).sdk/runanywhere-flutter/packages/runanywhere_genie/android/binary_config.gradle-37-49 (1)
37-49:⚠️ Potential issue | 🟠 MajorValidate the full local Genie runtime before skipping downloads.
checkLocalLibsExist()returns true as soon as the JNI shim exists. In local mode that lets the build bypass the release zip even if required runtime.sofiles are missing, which is exactly how SM8850/V81 support can regress whenlibQnnHtpV81.sowasn't copied intojniLibs.Suggested fix
- // Check for Genie backend library - def genieLib = new File(arm64Dir, 'librac_backend_genie_jni.so') - return genieLib.exists() + // Check for the JNI shim plus the runtime libraries required by this package. + def requiredLibs = [ + 'librac_backend_genie_jni.so', + 'libQnnHtpV81.so', + // add the remaining Genie runtime .so files shipped in the release zip + ] + return requiredLibs.every { new File(arm64Dir, it).exists() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere_genie/android/binary_config.gradle` around lines 37 - 49, The current checkLocalLibsExist closure returns true as soon as the JNI shim (librac_backend_genie_jni.so) is present, which allows skipping downloads even when other required runtime .so files are missing; update checkLocalLibsExist to verify the presence of the full set of required native libraries in arm64-v8a (e.g., librac_backend_genie_jni.so, libQnnHtpV81.so and any other runtime .so names your runtime needs) by enumerating and confirming each file exists in jniLibsDir/arm64-v8a before returning true; modify the logic around the genieLib variable and add checks for the additional filenames so the function only returns true when all required .so files are present.sdk/runanywhere-flutter/packages/runanywhere_genie/android/src/main/kotlin/ai/runanywhere/sdk/genie/GeniePlugin.kt-19-31 (1)
19-31:⚠️ Potential issue | 🟠 MajorDon't report Genie metadata after a JNI load failure.
The
UnsatisfiedLinkErrorpath only logs, butgetBackendName()/getBackendVersion()still return success unconditionally. That makes Flutter-side availability probes treat Genie as present even whenrac_backend_genie_jniis missing, and the reported version is already stale vs the0.3.0binaries configured inbinary_config.gradle.Suggested fix
companion object { private const val CHANNEL_NAME = "runanywhere_genie" - private const val BACKEND_VERSION = "0.1.6" + private const val BACKEND_VERSION = "0.3.0" private const val BACKEND_NAME = "Genie" + `@Volatile` private var nativeLoaded = false init { // Load Genie backend native libraries try { System.loadLibrary("rac_backend_genie_jni") + nativeLoaded = true } catch (e: UnsatisfiedLinkError) { // Library may not be available in all configurations android.util.Log.w("Genie", "Failed to load rac_backend_genie_jni: ${e.message}") } } @@ "getBackendVersion" -> { - result.success(BACKEND_VERSION) + if (nativeLoaded) { + result.success(BACKEND_VERSION) + } else { + result.error("unavailable", "Genie JNI library not loaded", null) + } } "getBackendName" -> { - result.success(BACKEND_NAME) + if (nativeLoaded) { + result.success(BACKEND_NAME) + } else { + result.error("unavailable", "Genie JNI library not loaded", null) + } }Also applies to: 45-49
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere_genie/android/src/main/kotlin/ai/runanywhere/sdk/genie/GeniePlugin.kt` around lines 19 - 31, The companion object currently logs JNI load failure but still reports backend metadata; add a private boolean (e.g., libraryLoaded) set to true inside the try after System.loadLibrary("rac_backend_genie_jni") and false in the catch, then make getBackendName() and getBackendVersion() check that flag and return null/empty (or otherwise indicate unavailable) when the library failed to load so Flutter availability probes don't treat Genie as present; also update the BACKEND_VERSION constant from "0.1.6" to "0.3.0" to match the configured native binaries (or derive it from native if available).sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+ModelManagement.jvmAndroid.kt-875-920 (1)
875-920:⚠️ Potential issue | 🟠 MajorIgnore archive metadata entries before classifying the extracted layout.
A zip with one real model folder plus
__MACOSX/or another metadata directory makesnewDirs.size == 1 && newFiles.isEmpty()false here. That falls into the flat-archive branch and moves the whole payload folder underexpectedModelDir, recreating the nesting bug this PR is trying to fix.Suggested fix
- val newDirs = newItems.filter { it.isDirectory } - val newFiles = newItems.filter { it.isFile } + val payloadItems = + newItems.filterNot { item -> + item.name == "__MACOSX" || item.name.startsWith(".") + } + val newDirs = payloadItems.filter { it.isDirectory } + val newFiles = payloadItems.filter { it.isFile } @@ - val itemsToMove = newItems.filter { it != expectedModelDir } + val itemsToMove = payloadItems🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere`+ModelManagement.jvmAndroid.kt around lines 875 - 920, The extracted-layout detection incorrectly treats metadata folders (e.g. "__MACOSX", ".DS_Store" or other archive metadata) as real content, causing the flat-archive branch to run; update the logic around newItems/newDirs/newFiles in the model extraction code (the block that computes tempArchiveName, expectedModelDir, newItems and then newDirs/newFiles) to first filter out known metadata entries (e.g. "__MACOSX", entries starting with "." or a small METADATA_DIRS set) and then use those filtered lists for the conditional that checks newDirs.size == 1 && newFiles.isEmpty() and for the itemsToMove computation so metadata directories are ignored when deciding whether to rename the single extracted root or treat the archive as flat.sdk/runanywhere-flutter/packages/runanywhere/lib/public/extensions/runanywhere_device.dart-18-19 (1)
18-19:⚠️ Potential issue | 🟠 MajorFix:
getChip()can't be called asRunAnywhere.getChip()In Dart, static methods on extensions must be called via the extension name:
RunAnywhereDevice.getChip(), notRunAnywhere.getChip(). This breaks the example innpu_chip.dart(line 8), which shows the wrong syntax. Additionally, the method only catchesPlatformException—it won't catchMissingPluginException, which is a separate exception type thrown when the native platform channel has no handler. Either move the method directly ontoRunAnywhereor update all examples to useRunAnywhereDevice.getChip()and addMissingPluginExceptionto the exception handler.Also applies to: 34-44
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/extensions/runanywhere_device.dart` around lines 18 - 19, The static API in the RunAnywhereDevice extension (e.g., RunAnywhereDevice.getChip and the other static methods declared in that extension) must be callable as RunAnywhere.getChip() in your examples and robustly handle platform plugin absence; either move these static methods out of the extension and into the RunAnywhere class so callers can use RunAnywhere.getChip(), or if you keep them on the extension update all examples to call RunAnywhereDevice.getChip(); also update the try/catch in getChip (and the other static methods in the same extension) to catch both PlatformException and MissingPluginException and include handling/logging for both.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 08ac6279-bbbb-4a77-bfdf-f460719b83b9
⛔ Files ignored due to path filters (4)
docs/gifs/npu-model-tag-screenshot.pngis excluded by!**/*.pngexamples/android/RunAnywhereAI/app/src/main/jniLibs/arm64-v8a/libQnnHtpV81.sois excluded by!**/*.soexamples/react-native/RunAnywhereAI/package-lock.jsonis excluded by!**/package-lock.jsonexamples/react-native/RunAnywhereAI/yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (125)
.gitignore.idea/vcs.xmldocs/impl/lora_adapter_support.mddocs/sdks/flutter-sdk.mddocs/sdks/kotlin-sdk.mddocs/sdks/react-native-sdk.mdexamples/android/RunAnywhereAI/app/build.gradle.ktsexamples/android/RunAnywhereAI/app/src/main/AndroidManifest.xmlexamples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/LoraExamplePrompts.ktexamples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/ModelList.ktexamples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatScreen.ktexamples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatViewModel.ktexamples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/models/ModelSelectionBottomSheet.ktexamples/android/RunAnywhereAI/settings.gradle.ktsexamples/flutter/RunAnywhereAI/android/app/build.gradleexamples/flutter/RunAnywhereAI/android/app/src/main/AndroidManifest.xmlexamples/flutter/RunAnywhereAI/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.javaexamples/flutter/RunAnywhereAI/ios/Runner/GeneratedPluginRegistrant.mexamples/flutter/RunAnywhereAI/lib/app/runanywhere_ai_app.dartexamples/flutter/RunAnywhereAI/lib/features/chat/chat_interface_view.dartexamples/flutter/RunAnywhereAI/lib/features/models/model_list_view_model.dartexamples/flutter/RunAnywhereAI/lib/features/models/model_types.dartexamples/flutter/RunAnywhereAI/pubspec.yamlexamples/react-native/RunAnywhereAI/App.tsxexamples/react-native/RunAnywhereAI/android/app/build.gradleexamples/react-native/RunAnywhereAI/android/app/src/main/AndroidManifest.xmlexamples/react-native/RunAnywhereAI/android/app/src/main/java/com/runanywhereaI/MainApplication.ktexamples/react-native/RunAnywhereAI/android/settings.gradleexamples/react-native/RunAnywhereAI/metro.config.jsexamples/react-native/RunAnywhereAI/package.jsonexamples/react-native/RunAnywhereAI/src/components/model/ModelSelectionSheet.tsxexamples/react-native/RunAnywhereAI/src/screens/ChatScreen.tsxexamples/react-native/RunAnywhereAI/src/screens/RAGScreen.tsxexamples/react-native/RunAnywhereAI/src/types/model.tslefthook.ymlsdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_types.hsdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txtsdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cppsdk/runanywhere-commons/src/backends/llamacpp/rac_llm_llamacpp.cppsdk/runanywhere-commons/src/features/diffusion/diffusion_json.cppsdk/runanywhere-commons/src/features/llm/llm_component.cppsdk/runanywhere-commons/src/features/llm/rac_llm_service.cppsdk/runanywhere-commons/src/infrastructure/model_management/model_assignment.cppsdk/runanywhere-commons/src/infrastructure/model_management/model_paths.cppsdk/runanywhere-commons/src/infrastructure/model_management/model_registry.cppsdk/runanywhere-commons/src/infrastructure/model_management/model_types.cppsdk/runanywhere-commons/src/infrastructure/registry/service_registry.cppsdk/runanywhere-commons/src/infrastructure/telemetry/telemetry_manager.cppsdk/runanywhere-flutter/packages/runanywhere/CHANGELOG.mdsdk/runanywhere-flutter/packages/runanywhere/android/binary_config.gradlesdk/runanywhere-flutter/packages/runanywhere/android/src/main/jniLibs/.gitkeepsdk/runanywhere-flutter/packages/runanywhere/android/src/main/kotlin/ai/runanywhere/sdk/RunAnywherePlugin.ktsdk/runanywhere-flutter/packages/runanywhere/ios/Frameworks/.gitkeepsdk/runanywhere-flutter/packages/runanywhere/lib/core/types/model_types.dartsdk/runanywhere-flutter/packages/runanywhere/lib/core/types/npu_chip.dartsdk/runanywhere-flutter/packages/runanywhere/lib/infrastructure/download/download_service.dartsdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_model_paths.dartsdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_model_registry.dartsdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_rag.dartsdk/runanywhere-flutter/packages/runanywhere/lib/native/type_conversions/model_types_cpp_bridge.dartsdk/runanywhere-flutter/packages/runanywhere/lib/public/extensions/runanywhere_device.dartsdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dartsdk/runanywhere-flutter/packages/runanywhere/lib/public/types/rag_types.dartsdk/runanywhere-flutter/packages/runanywhere/lib/runanywhere.dartsdk/runanywhere-flutter/packages/runanywhere/pubspec.yamlsdk/runanywhere-flutter/packages/runanywhere_genie/CHANGELOG.mdsdk/runanywhere-flutter/packages/runanywhere_genie/LICENSEsdk/runanywhere-flutter/packages/runanywhere_genie/android/binary_config.gradlesdk/runanywhere-flutter/packages/runanywhere_genie/android/build.gradlesdk/runanywhere-flutter/packages/runanywhere_genie/android/src/main/AndroidManifest.xmlsdk/runanywhere-flutter/packages/runanywhere_genie/android/src/main/jniLibs/.gitkeepsdk/runanywhere-flutter/packages/runanywhere_genie/android/src/main/kotlin/ai/runanywhere/sdk/genie/GeniePlugin.ktsdk/runanywhere-flutter/packages/runanywhere_genie/ios/Classes/GeniePlugin.swiftsdk/runanywhere-flutter/packages/runanywhere_genie/ios/runanywhere_genie.podspecsdk/runanywhere-flutter/packages/runanywhere_genie/lib/genie.dartsdk/runanywhere-flutter/packages/runanywhere_genie/lib/genie_error.dartsdk/runanywhere-flutter/packages/runanywhere_genie/lib/native/genie_bindings.dartsdk/runanywhere-flutter/packages/runanywhere_genie/lib/runanywhere_genie.dartsdk/runanywhere-flutter/packages/runanywhere_genie/pubspec.yamlsdk/runanywhere-flutter/packages/runanywhere_llamacpp/android/binary_config.gradlesdk/runanywhere-flutter/packages/runanywhere_llamacpp/android/src/main/jniLibs/.gitkeepsdk/runanywhere-flutter/packages/runanywhere_llamacpp/ios/Frameworks/.gitkeepsdk/runanywhere-flutter/packages/runanywhere_onnx/android/binary_config.gradlesdk/runanywhere-flutter/packages/runanywhere_onnx/android/src/main/jniLibs/.gitkeepsdk/runanywhere-flutter/packages/runanywhere_onnx/ios/Frameworks/.gitkeepsdk/runanywhere-kotlin/settings.gradle.ktssdk/runanywhere-kotlin/src/androidMain/kotlin/com/runanywhere/sdk/foundation/device/DeviceInfoService.ktsdk/runanywhere-kotlin/src/androidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+Device.ktsdk/runanywhere-kotlin/src/androidMain/kotlin/com/runanywhere/sdk/security/SecureStorage.ktsdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/core/types/ComponentTypes.ktsdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/core/types/NPUChip.ktsdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/foundation/SDKLogger.ktsdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/foundation/device/DeviceInfoService.ktsdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/RunAnywhere.ktsdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/LLM/LLMTypes.ktsdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/Models/ModelTypes.ktsdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+Device.ktsdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/CppBridge.ktsdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeEvents.ktsdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeModelPaths.ktsdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/foundation/bridge/extensions/CppBridgeModelRegistry.ktsdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/PlatformBridge.ktsdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+LoRA.jvmAndroid.ktsdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+ModelManagement.jvmAndroid.ktsdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+Storage.jvmAndroid.ktsdk/runanywhere-kotlin/src/jvmMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+Device.ktsdk/runanywhere-react-native/packages/core/android/CMakeLists.txtsdk/runanywhere-react-native/packages/core/android/src/main/java/com/margelo/nitro/runanywhere/PlatformAdapterBridge.ktsdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cppsdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.hppsdk/runanywhere-react-native/packages/core/cpp/bridges/CompatibilityBridge.hppsdk/runanywhere-react-native/packages/core/cpp/bridges/RAGBridge.hppsdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+Device.tssdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+Models.tssdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+VLM.tssdk/runanywhere-react-native/packages/core/src/Public/Extensions/index.tssdk/runanywhere-react-native/packages/core/src/Public/RunAnywhere.tssdk/runanywhere-react-native/packages/core/src/index.tssdk/runanywhere-react-native/packages/core/src/services/FileSystem.tssdk/runanywhere-react-native/packages/core/src/types/NPUChip.tssdk/runanywhere-react-native/packages/core/src/types/enums.tssdk/runanywhere-react-native/packages/core/src/types/index.tssdk/runanywhere-react-native/packages/llamacpp/android/CMakeLists.txtsdk/runanywhere-react-native/packages/llamacpp/cpp/HybridRunAnywhereLlama.cppsdk/runanywhere-react-native/packages/onnx/android/CMakeLists.txt
| private val lock = Any() | ||
| private val servicesMutex = Mutex() |
There was a problem hiding this comment.
reset() can race with phase-2 initialization now.
Phase 2 now uses servicesMutex, but phase 1 and reset() still only synchronize on lock. That leaves a window where one coroutine tears the bridge down while another is inside initializeCppBridgeServices(), after which _areServicesReady can be set back to true on a reset SDK. Use one coordination primitive for init/reset, or acquire servicesMutex during reset as well.
Also applies to: 246-269
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/RunAnywhere.kt`
around lines 95 - 96, The reset() path races with phase-2 initialization because
phase 1 and reset use the plain lock while initializeCppBridgeServices() and
other phase-2 code use servicesMutex; fix this by unifying synchronization: have
reset() acquire servicesMutex for the duration of teardown (instead of or in
addition to using lock) so it cannot run concurrently with
initializeCppBridgeServices(), and update any phase-1 init code (or remove the
plain lock) to use servicesMutex as the single coordination primitive; ensure
you guard reads/writes of _areServicesReady and any bridge setup/teardown inside
the servicesMutex-protected section.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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_genie/android/binary_config.gradle`:
- Around line 31-49: The current binary_config uses arm64-only paths/URL
(genieAndroidUrl, checkLocalLibsExist, arm64-v8a, genieLib) while build.gradle
declares arm64-v8a, armeabi-v7a and x86_64; update either side: modify
checkLocalLibsExist and shouldDownloadAndroidLibs to iterate a list of ABIs
(e.g., ['arm64-v8a','armeabi-v7a','x86_64']) and construct per-ABI URLs/paths
(instead of genieAndroidUrl fixed to arm64) and validate each ABI's .so (e.g.,
look for appropriate librac_backend_genie_jni.so under each jniLibsDir/<ABI>),
or alternatively tighten build.gradle's abiFilters to only include 'arm64-v8a'
to match the existing genieAndroidUrl and checks; ensure unique symbols to
change are genieAndroidUrl, shouldDownloadAndroidLibs, and checkLocalLibsExist
so ABI behavior is consistent across config and build.gradle.
- Around line 14-36: The build file hardcodes testLocal = true, preventing
Gradle property overrides; change testLocal to read the Gradle property (e.g.,
project.findProperty("runanywhere.testLocal") or project.hasProperty) and parse
it to a boolean so -Prunanywhere.testLocal=false flips behavior, and ensure
shouldDownloadAndroidLibs (the closure) uses that resolved boolean; update
references to testLocal and keep genieVersion, binariesBaseUrl, and
genieAndroidUrl unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c1920b05-885a-4e53-af07-07c701aa04aa
📒 Files selected for processing (1)
sdk/runanywhere-flutter/packages/runanywhere_genie/android/binary_config.gradle
| testLocal = true | ||
|
|
||
| // ============================================================================= | ||
| // Version Configuration (MUST match Swift Package.swift and Kotlin build.gradle.kts) | ||
| // ============================================================================= | ||
| genieVersion = "0.3.0" | ||
|
|
||
| // ============================================================================= | ||
| // Remote binary URLs | ||
| // RABackendGenie hosted on the public runanywhere-sdks repo | ||
| // (runanywhere-genie is private, so binaries are published here) | ||
| // ============================================================================= | ||
| binariesGitHubOrg = "RunanywhereAI" | ||
| binariesRepo = "runanywhere-sdks" | ||
| binariesBaseUrl = "https://github.com/${binariesGitHubOrg}/${binariesRepo}/releases/download" | ||
|
|
||
| // Android native libraries package | ||
| genieAndroidUrl = "${binariesBaseUrl}/genie-v${genieVersion}/RABackendGENIE-android-arm64-v8a-v${genieVersion}.zip" | ||
|
|
||
| // Helper method to check if we should download | ||
| shouldDownloadAndroidLibs = { -> | ||
| return !testLocal | ||
| } |
There was a problem hiding this comment.
Make testLocal configurable from Gradle properties.
Line 14 hardcodes testLocal = true, so -Prunanywhere.testLocal=false won’t switch this package to download mode. That can break CI/release flows expecting remote prebuilt JNI libs.
Suggested fix
- testLocal = true
+ testLocal = (project.findProperty("runanywhere.testLocal") ?: "true")
+ .toString()
+ .toBoolean()Based on learnings: Applies to */gradle : Kotlin SDK: Use ./gradlew :runanywhere-kotlin:compileDebugKotlinAndroid -Prunanywhere.testLocal=false to build the Android target without requiring Android NDK (uses pre-built JNI libs from GitHub releases)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| testLocal = true | |
| // ============================================================================= | |
| // Version Configuration (MUST match Swift Package.swift and Kotlin build.gradle.kts) | |
| // ============================================================================= | |
| genieVersion = "0.3.0" | |
| // ============================================================================= | |
| // Remote binary URLs | |
| // RABackendGenie hosted on the public runanywhere-sdks repo | |
| // (runanywhere-genie is private, so binaries are published here) | |
| // ============================================================================= | |
| binariesGitHubOrg = "RunanywhereAI" | |
| binariesRepo = "runanywhere-sdks" | |
| binariesBaseUrl = "https://github.com/${binariesGitHubOrg}/${binariesRepo}/releases/download" | |
| // Android native libraries package | |
| genieAndroidUrl = "${binariesBaseUrl}/genie-v${genieVersion}/RABackendGENIE-android-arm64-v8a-v${genieVersion}.zip" | |
| // Helper method to check if we should download | |
| shouldDownloadAndroidLibs = { -> | |
| return !testLocal | |
| } | |
| testLocal = (project.findProperty("runanywhere.testLocal") ?: "true") | |
| .toString() | |
| .toBoolean() | |
| // ============================================================================= | |
| // Version Configuration (MUST match Swift Package.swift and Kotlin build.gradle.kts) | |
| // ============================================================================= | |
| genieVersion = "0.3.0" | |
| // ============================================================================= | |
| // Remote binary URLs | |
| // RABackendGenie hosted on the public runanywhere-sdks repo | |
| // (runanywhere-genie is private, so binaries are published here) | |
| // ============================================================================= | |
| binariesGitHubOrg = "RunanywhereAI" | |
| binariesRepo = "runanywhere-sdks" | |
| binariesBaseUrl = "https://github.com/${binariesGitHubOrg}/${binariesRepo}/releases/download" | |
| // Android native libraries package | |
| genieAndroidUrl = "${binariesBaseUrl}/genie-v${genieVersion}/RABackendGENIE-android-arm64-v8a-v${genieVersion}.zip" | |
| // Helper method to check if we should download | |
| shouldDownloadAndroidLibs = { -> | |
| return !testLocal | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@sdk/runanywhere-flutter/packages/runanywhere_genie/android/binary_config.gradle`
around lines 14 - 36, The build file hardcodes testLocal = true, preventing
Gradle property overrides; change testLocal to read the Gradle property (e.g.,
project.findProperty("runanywhere.testLocal") or project.hasProperty) and parse
it to a boolean so -Prunanywhere.testLocal=false flips behavior, and ensure
shouldDownloadAndroidLibs (the closure) uses that resolved boolean; update
references to testLocal and keep genieVersion, binariesBaseUrl, and
genieAndroidUrl unchanged.
| genieAndroidUrl = "${binariesBaseUrl}/genie-v${genieVersion}/RABackendGENIE-android-arm64-v8a-v${genieVersion}.zip" | ||
|
|
||
| // Helper method to check if we should download | ||
| shouldDownloadAndroidLibs = { -> | ||
| return !testLocal | ||
| } | ||
|
|
||
| // Helper method to check if local libs exist | ||
| checkLocalLibsExist = { -> | ||
| def jniLibsDir = project.file('src/main/jniLibs') | ||
| def arm64Dir = new File(jniLibsDir, 'arm64-v8a') | ||
|
|
||
| if (!arm64Dir.exists() || !arm64Dir.isDirectory()) { | ||
| return false | ||
| } | ||
|
|
||
| // Check for Genie backend library | ||
| def genieLib = new File(arm64Dir, 'librac_backend_genie_jni.so') | ||
| return genieLib.exists() |
There was a problem hiding this comment.
Align binary config with declared ABI filters.
Line 31 and Lines 41-49 are arm64-only, but sdk/runanywhere-flutter/packages/runanywhere_genie/android/build.gradle (Lines 45-48) declares arm64-v8a, armeabi-v7a, and x86_64. This mismatch can produce builds that include ABIs without corresponding Genie .so coverage.
Please either:
- add URL/validation support for all declared ABIs, or
- restrict ABI filters to the ABIs actually shipped by Genie binaries.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@sdk/runanywhere-flutter/packages/runanywhere_genie/android/binary_config.gradle`
around lines 31 - 49, The current binary_config uses arm64-only paths/URL
(genieAndroidUrl, checkLocalLibsExist, arm64-v8a, genieLib) while build.gradle
declares arm64-v8a, armeabi-v7a and x86_64; update either side: modify
checkLocalLibsExist and shouldDownloadAndroidLibs to iterate a list of ABIs
(e.g., ['arm64-v8a','armeabi-v7a','x86_64']) and construct per-ABI URLs/paths
(instead of genieAndroidUrl fixed to arm64) and validate each ABI's .so (e.g.,
look for appropriate librac_backend_genie_jni.so under each jniLibsDir/<ABI>),
or alternatively tighten build.gradle's abiFilters to only include 'arm64-v8a'
to match the existing genieAndroidUrl and checks; ensure unique symbols to
change are genieAndroidUrl, shouldDownloadAndroidLibs, and checkLocalLibsExist
so ABI behavior is consistent across config and build.gradle.
Summary
libQnnHtpV81.soNew Features
getChip()API for NPU chip detectionrunanywhere_geniepackageBug Fixes
Test Plan
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation