Skip to content

Commit f12d430

Browse files
Siddhesh2377claude
andcommitted
Add Flutter runanywhere_genie package + fix Genie API consistency across SDKs
- Create runanywhere_genie Flutter package (thin FFI wrapper for rac_backend_genie_register) with Genie class: register/unregister/addModel/isAvailable/canHandle (Android/Snapdragon only) - Fix Kotlin DeviceInfoService.getChipName() to return Build.SOC_MODEL instead of ABI string - Standardize Genie display name to "Qualcomm Genie" across Flutter, RN (matching C++ source) - Fix Flutter example app to use local path dependency for runanywhere_genie Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 63a7dac commit f12d430

12 files changed

Lines changed: 870 additions & 0 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## 0.16.0
2+
3+
### Added
4+
- Initial release of Qualcomm Genie NPU backend for RunAnywhere Flutter SDK
5+
- `Genie.register()` / `Genie.unregister()` for C++ backend registration
6+
- `Genie.addModel()` convenience method for NPU model registration
7+
- `Genie.isAvailable` platform check (Android/Snapdragon only)
8+
- `Genie.canHandle()` model compatibility check
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 RunAnywhere AI
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// =============================================================================
2+
// BINARY CONFIGURATION FOR RUNANYWHERE FLUTTER SDK - ANDROID (Genie Package)
3+
// =============================================================================
4+
// This file controls whether to use local or remote native libraries (.so files).
5+
// Similar to Swift Package.swift's testLocal flag.
6+
//
7+
// Set to `true` to use local binaries from android/src/main/jniLibs/
8+
// Set to `false` to download binaries from GitHub releases (production mode)
9+
// =============================================================================
10+
11+
ext {
12+
// Set this to true for local development/testing
13+
// Set to false for production builds (downloads from GitHub releases)
14+
testLocal = false
15+
16+
// =============================================================================
17+
// Version Configuration (MUST match Swift Package.swift and Kotlin build.gradle.kts)
18+
// =============================================================================
19+
coreVersion = "0.1.6"
20+
21+
// =============================================================================
22+
// Remote binary URLs
23+
// RABackendGenie from runanywhere-sdks
24+
// =============================================================================
25+
binariesGitHubOrg = "RunanywhereAI"
26+
binariesRepo = "runanywhere-sdks"
27+
binariesBaseUrl = "https://github.com/${binariesGitHubOrg}/${binariesRepo}/releases/download"
28+
29+
// Android native libraries package
30+
genieAndroidUrl = "${binariesBaseUrl}/commons-v${coreVersion}/RABackendGenie-android-v${coreVersion}.zip"
31+
32+
// Helper method to check if we should download
33+
shouldDownloadAndroidLibs = { ->
34+
return !testLocal
35+
}
36+
37+
// Helper method to check if local libs exist
38+
checkLocalLibsExist = { ->
39+
def jniLibsDir = project.file('src/main/jniLibs')
40+
def arm64Dir = new File(jniLibsDir, 'arm64-v8a')
41+
42+
if (!arm64Dir.exists() || !arm64Dir.isDirectory()) {
43+
return false
44+
}
45+
46+
// Check for Genie backend library
47+
def genieLib = new File(arm64Dir, 'librac_backend_genie_jni.so')
48+
return genieLib.exists()
49+
}
50+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// RunAnywhere Genie Backend - Android
2+
//
3+
// This plugin bundles RABackendGenie native libraries (.so files) for Android.
4+
// RABackendGenie provides LLM text generation capabilities using Qualcomm Genie NPU.
5+
//
6+
// Binary Configuration:
7+
// Edit binary_config.gradle to toggle between local and remote binaries:
8+
// - testLocal = true: Use local .so files from android/src/main/jniLibs/ (for development)
9+
// - testLocal = false: Download from GitHub releases (for production)
10+
//
11+
// Version: Must match Swift SDK's Package.swift and Kotlin SDK's build.gradle.kts
12+
13+
group 'ai.runanywhere.sdk.genie'
14+
version '0.16.0'
15+
16+
// Load binary configuration
17+
apply from: 'binary_config.gradle'
18+
19+
buildscript {
20+
ext.kotlin_version = '1.9.10'
21+
repositories {
22+
google()
23+
mavenCentral()
24+
}
25+
dependencies {
26+
classpath 'com.android.tools.build:gradle:8.1.0'
27+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
28+
}
29+
}
30+
31+
apply plugin: 'com.android.library'
32+
apply plugin: 'kotlin-android'
33+
34+
android {
35+
namespace 'ai.runanywhere.sdk.genie'
36+
compileSdk 34
37+
38+
// Use NDK for native library support
39+
ndkVersion "25.2.9519653"
40+
41+
defaultConfig {
42+
minSdk 24
43+
targetSdk 34
44+
45+
// ABI filters for native libraries
46+
ndk {
47+
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
48+
}
49+
50+
// Consumer proguard rules
51+
consumerProguardFiles 'proguard-rules.pro'
52+
}
53+
54+
compileOptions {
55+
sourceCompatibility JavaVersion.VERSION_1_8
56+
targetCompatibility JavaVersion.VERSION_1_8
57+
}
58+
59+
kotlinOptions {
60+
jvmTarget = '1.8'
61+
}
62+
63+
sourceSets {
64+
main {
65+
// Native libraries location - use downloaded libs or local libs based on config
66+
jniLibs.srcDirs = [testLocal ? 'src/main/jniLibs' : 'build/jniLibs']
67+
}
68+
}
69+
70+
buildTypes {
71+
release {
72+
minifyEnabled false
73+
}
74+
}
75+
}
76+
77+
dependencies {
78+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
79+
}
80+
81+
// =============================================================================
82+
// Binary Download Task (runs when testLocal = false)
83+
// =============================================================================
84+
task downloadNativeLibs {
85+
group = 'runanywhere'
86+
description = 'Download RABackendGenie native libraries from GitHub releases'
87+
88+
doLast {
89+
if (shouldDownloadAndroidLibs()) {
90+
println "📦 Remote mode: Downloading RABackendGenie Android native libraries..."
91+
92+
def jniLibsDir = file('build/jniLibs')
93+
if (jniLibsDir.exists()) {
94+
delete(jniLibsDir)
95+
}
96+
jniLibsDir.mkdirs()
97+
98+
// Ensure build directory exists
99+
buildDir.mkdirs()
100+
101+
def downloadUrl = genieAndroidUrl
102+
def zipFile = file("${buildDir}/genie-android.zip")
103+
104+
println "Downloading from: ${downloadUrl}"
105+
106+
// Download the zip file
107+
ant.get(src: downloadUrl, dest: zipFile)
108+
109+
println "✅ Downloaded successfully"
110+
111+
// Extract to temp directory first
112+
def tempDir = file("${buildDir}/genie-temp")
113+
if (tempDir.exists()) {
114+
delete(tempDir)
115+
}
116+
tempDir.mkdirs()
117+
118+
copy {
119+
from zipTree(zipFile)
120+
into tempDir
121+
}
122+
123+
// Common libs that should NOT be duplicated (they're in the core SDK)
124+
def commonLibs = ['libc++_shared.so', 'librac_commons.so', 'librac_commons_jni.so']
125+
126+
// Copy .so files from jniLibs structure (excluding common libs)
127+
tempDir.eachFileRecurse { file ->
128+
if (file.isDirectory() && file.name in ['arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86']) {
129+
def targetAbiDir = new File(jniLibsDir, file.name)
130+
targetAbiDir.mkdirs()
131+
file.eachFile { soFile ->
132+
if (soFile.name.endsWith('.so') && !(soFile.name in commonLibs)) {
133+
copy {
134+
from soFile
135+
into targetAbiDir
136+
}
137+
println "${file.name}/${soFile.name}"
138+
}
139+
}
140+
}
141+
}
142+
143+
// Clean up
144+
zipFile.delete()
145+
if (tempDir.exists()) {
146+
delete(tempDir)
147+
}
148+
149+
println "✅ RABackendGenie native libraries downloaded successfully"
150+
} else {
151+
println "🔧 Local mode: Using native libraries from src/main/jniLibs/"
152+
153+
if (!checkLocalLibsExist()) {
154+
throw new GradleException("""
155+
⚠️ Native libraries not found in src/main/jniLibs/!
156+
For local mode, please build and copy the libraries:
157+
1. cd runanywhere-core && ./scripts/build-android.sh --genie
158+
2. Copy the .so files to packages/runanywhere_genie/android/src/main/jniLibs/
159+
Or switch to remote mode by editing binary_config.gradle:
160+
testLocal = false
161+
""")
162+
} else {
163+
println "✅ Using local native libraries"
164+
}
165+
}
166+
}
167+
}
168+
169+
// Run downloadNativeLibs before preBuild
170+
preBuild.dependsOn downloadNativeLibs
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="ai.runanywhere.sdk.genie">
4+
</manifest>

sdk/runanywhere-flutter/packages/runanywhere_genie/android/src/main/jniLibs/.gitkeep

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package ai.runanywhere.sdk.genie
2+
3+
import android.os.Build
4+
import io.flutter.embedding.engine.plugins.FlutterPlugin
5+
import io.flutter.plugin.common.MethodCall
6+
import io.flutter.plugin.common.MethodChannel
7+
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
8+
import io.flutter.plugin.common.MethodChannel.Result
9+
10+
/**
11+
* RunAnywhere Genie Flutter Plugin - Android Implementation
12+
*
13+
* This plugin provides the native bridge for the Genie NPU backend on Android.
14+
* The actual LLM functionality is provided by RABackendGenie native libraries (.so files).
15+
*/
16+
class GeniePlugin : FlutterPlugin, MethodCallHandler {
17+
private lateinit var channel: MethodChannel
18+
19+
companion object {
20+
private const val CHANNEL_NAME = "runanywhere_genie"
21+
private const val BACKEND_VERSION = "0.1.6"
22+
private const val BACKEND_NAME = "Genie"
23+
24+
init {
25+
// Load Genie backend native libraries
26+
try {
27+
System.loadLibrary("rac_backend_genie_jni")
28+
} catch (e: UnsatisfiedLinkError) {
29+
// Library may not be available in all configurations
30+
android.util.Log.w("Genie", "Failed to load rac_backend_genie_jni: ${e.message}")
31+
}
32+
}
33+
}
34+
35+
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
36+
channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
37+
channel.setMethodCallHandler(this)
38+
}
39+
40+
override fun onMethodCall(call: MethodCall, result: Result) {
41+
when (call.method) {
42+
"getPlatformVersion" -> {
43+
result.success("Android ${Build.VERSION.RELEASE}")
44+
}
45+
"getBackendVersion" -> {
46+
result.success(BACKEND_VERSION)
47+
}
48+
"getBackendName" -> {
49+
result.success(BACKEND_NAME)
50+
}
51+
else -> {
52+
result.notImplemented()
53+
}
54+
}
55+
}
56+
57+
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
58+
channel.setMethodCallHandler(null)
59+
}
60+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Flutter
2+
import UIKit
3+
4+
/// RunAnywhere Genie Flutter Plugin - iOS Implementation
5+
///
6+
/// This is a stub plugin for the Flutter plugin system.
7+
/// Genie NPU backend is Android/Snapdragon only - this plugin provides
8+
/// platform channel compatibility but no actual NPU functionality on iOS.
9+
public class GeniePlugin: NSObject, FlutterPlugin {
10+
11+
public static func register(with registrar: FlutterPluginRegistrar) {
12+
let channel = FlutterMethodChannel(
13+
name: "runanywhere_genie",
14+
binaryMessenger: registrar.messenger()
15+
)
16+
let instance = GeniePlugin()
17+
registrar.addMethodCallDelegate(instance, channel: channel)
18+
}
19+
20+
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
21+
switch call.method {
22+
case "getPlatformVersion":
23+
result("iOS " + UIDevice.current.systemVersion)
24+
case "getBackendVersion":
25+
result("0.1.6")
26+
case "getBackendName":
27+
result("Genie")
28+
default:
29+
result(FlutterMethodNotImplemented)
30+
}
31+
}
32+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#
2+
# RunAnywhere Genie Backend - iOS
3+
#
4+
# This is a stub podspec for the Flutter plugin system.
5+
# Genie NPU backend is Android/Snapdragon only - no iOS binary is provided.
6+
# This podspec exists solely to satisfy Flutter's iOS plugin registration requirements.
7+
#
8+
9+
Pod::Spec.new do |s|
10+
s.name = 'runanywhere_genie'
11+
s.version = '0.16.0'
12+
s.summary = 'RunAnywhere Genie: NPU LLM inference for Flutter (Android/Snapdragon only)'
13+
s.description = <<-DESC
14+
Qualcomm Genie NPU backend for RunAnywhere Flutter SDK. Provides LLM text generation
15+
on Snapdragon NPU hardware. This is an Android-only backend; the iOS pod is a stub
16+
for Flutter plugin system compatibility.
17+
DESC
18+
s.homepage = 'https://runanywhere.ai'
19+
s.license = { :type => 'MIT' }
20+
s.author = { 'RunAnywhere' => 'team@runanywhere.ai' }
21+
s.source = { :path => '.' }
22+
23+
s.ios.deployment_target = '14.0'
24+
s.swift_version = '5.0'
25+
26+
# Source files (minimal stub - Genie is Android-only)
27+
s.source_files = 'Classes/**/*'
28+
29+
# Flutter dependency
30+
s.dependency 'Flutter'
31+
32+
# No vendored_frameworks - Genie has no iOS binary (Android/Snapdragon only)
33+
34+
# Build settings
35+
s.pod_target_xcconfig = {
36+
'DEFINES_MODULE' => 'YES',
37+
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
38+
}
39+
40+
# Mark static framework for proper linking
41+
s.static_framework = true
42+
end

0 commit comments

Comments
 (0)