Skip to content

Conversation

@sanchitmonga22
Copy link
Contributor

@sanchitmonga22 sanchitmonga22 commented Jan 20, 2026

Description

Brief description of the changes made.

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Refactoring

Testing

  • Lint passes locally
  • Added/updated tests for changes

Platform-Specific Testing (check all that apply)

Swift SDK / iOS Sample:

  • Tested on iPhone (Simulator or Device)
  • Tested on iPad / Tablet
  • Tested on Mac (macOS target)

Kotlin SDK / Android Sample:

  • Tested on Android Phone (Emulator or Device)
  • Tested on Android Tablet

Flutter SDK / Flutter Sample:

  • Tested on iOS
  • Tested on Android

React Native SDK / React Native Sample:

  • Tested on iOS
  • Tested on Android

Labels

Please add the appropriate label(s):

SDKs:

  • Swift SDK - Changes to Swift SDK (sdk/runanywhere-swift)
  • Kotlin SDK - Changes to Kotlin SDK (sdk/runanywhere-kotlin)
  • Flutter SDK - Changes to Flutter SDK (sdk/runanywhere-flutter)
  • React Native SDK - Changes to React Native SDK (sdk/runanywhere-react-native)
  • Commons - Changes to shared native code (sdk/runanywhere-commons)

Sample Apps:

  • iOS Sample - Changes to iOS example app (examples/ios)
  • Android Sample - Changes to Android example app (examples/android)
  • Flutter Sample - Changes to Flutter example app (examples/flutter)
  • React Native Sample - Changes to React Native example app (examples/react-native)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)

Screenshots

Attach relevant UI screenshots for changes (if applicable):

  • Mobile (Phone)
  • Tablet / iPad
  • Desktop / Mac

Important

Enable Maven Central publishing for Kotlin Android SDK with new voice session API and updated documentation.

  • Maven Publishing:
    • Configured publishLibraryVariants("release") for Android AAR publishing in build.gradle.kts, runanywhere-core-llamacpp/build.gradle.kts, and runanywhere-core-onnx/build.gradle.kts.
    • Set artifact IDs for Maven Central: runanywhere-sdk-android, runanywhere-llamacpp-android, runanywhere-onnx-android.
  • Documentation:
    • Added KOTLIN_MAVEN_CENTRAL_PUBLISHING.md for publishing guide.
    • Updated Documentation.md with Quick Start guide and voice pipeline examples.
  • Voice Session API:
    • Added streamVoiceSession() in RunAnywhere+VoiceAgent.jvmAndroid.kt for automatic silence detection and voice pipeline orchestration.

This description was created by Ellipsis for b068537. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Streaming voice session API for continuous audio pipeline with real-time events
    • Android SDK artifacts now published to Maven Central
  • Documentation

    • New Quick Start with end-to-end examples (LLM, STT, TTS, voice pipeline)
    • Maven Central publishing guide for the Kotlin SDK
    • Updated voice session event/result shapes with richer, clearer event types and consolidated result fields

✏️ Tip: You can customize this high-level summary in your review settings.

Greptile Summary

This PR enables Maven Central publishing for the Kotlin Android SDK by configuring AAR publication for all Android artifacts.

Key Changes:

  • Configured publishLibraryVariants("release") for Android AAR publishing in 3 build files
  • Set correct artifact IDs for Maven Central: runanywhere-sdk-android, runanywhere-llamacpp-android, runanywhere-onnx-android
  • Added comprehensive Maven Central publishing guide (KOTLIN_MAVEN_CENTRAL_PUBLISHING.md)
  • Enhanced main documentation with Quick Start guide and complete voice pipeline examples
  • Added new streamVoiceSession() API for simplified voice pipeline with automatic silence detection
  • Implemented full voice session streaming with audio level calculation, speech detection, and STT→LLM→TTS orchestration

Maven Publishing Configuration:
The critical addition of publishLibraryVariants("release") ensures Android AARs (containing native .so libraries) are published to Maven Central, not just JVM JARs. This is essential for the SDK to function on Android devices.

Documentation Improvements:
The documentation now includes production-ready code examples matching the starter app, complete Maven Central installation instructions, and detailed API usage for all components (LLM, STT, TTS, Voice Pipeline).

Voice Pipeline Enhancement:
The new streamVoiceSession() API simplifies voice agent integration by handling silence detection, speech recognition, and pipeline orchestration automatically, reducing the implementation burden on developers.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The changes are well-structured and follow Kotlin Multiplatform best practices. The Maven publishing configuration correctly enables Android AAR publication with native libraries. The documentation is comprehensive and accurate. The new voice session API is properly implemented with error handling, resource management, and follows the existing architecture patterns. All changes are non-breaking additions that enhance the SDK's usability and distribution.
  • No files require special attention

Important Files Changed

Filename Overview
sdk/runanywhere-kotlin/build.gradle.kts Added Android AAR publishing configuration with correct artifact ID (runanywhere-sdk-android)
sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts Added Android AAR publishing configuration with correct artifact ID (runanywhere-llamacpp-android)
sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts Added Android AAR publishing configuration with correct artifact ID (runanywhere-onnx-android)
sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+VoiceAgent.jvmAndroid.kt Implemented streamVoiceSession() with audio level calculation, speech detection, and pipeline orchestration

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant SDK as RunAnywhere SDK
    participant Audio as AudioCaptureService
    participant STT as Speech-to-Text
    participant LLM as Language Model
    participant TTS as Text-to-Speech
    
    App->>SDK: registerModel() for STT/LLM/TTS
    App->>SDK: downloadModel()
    SDK-->>App: Download progress
    App->>SDK: loadSTTModel()
    App->>SDK: loadLLMModel()
    App->>SDK: loadTTSVoice()
    
    App->>Audio: startCapture()
    Audio-->>App: Flow<ByteArray>
    
    App->>SDK: streamVoiceSession(audioChunks, config)
    SDK-->>App: VoiceSessionEvent.Started
    
    loop Audio streaming
        Audio->>SDK: Audio chunk (ByteArray)
        SDK->>SDK: calculateRMS(audioData)
        SDK->>SDK: normalizeAudioLevel(rms)
        SDK-->>App: VoiceSessionEvent.Listening(audioLevel)
        
        alt Audio level > threshold
            SDK->>SDK: Speech detected
            SDK-->>App: VoiceSessionEvent.SpeechStarted
            SDK->>SDK: Accumulate audio in buffer
        end
        
        alt Silence detected after speech
            SDK-->>App: VoiceSessionEvent.Processing
            SDK->>STT: transcribe(audioData)
            STT-->>SDK: transcriptionText
            
            alt Transcription not blank
                SDK-->>App: VoiceSessionEvent.Transcribed(text)
                SDK->>LLM: generate(prompt)
                LLM-->>SDK: responseText
                SDK-->>App: VoiceSessionEvent.Responded(text)
                SDK->>TTS: synthesize(responseText)
                TTS-->>SDK: audioData
                SDK-->>App: VoiceSessionEvent.TurnCompleted(transcript, response, audio)
                
                alt Continuous mode
                    SDK->>SDK: Reset for next turn
                    SDK-->>App: VoiceSessionEvent.Listening(0.0)
                end
            end
        end
    end
    
    App->>SDK: Cancel session
    SDK-->>App: VoiceSessionEvent.Stopped
Loading

When creating release ZIPs for iOS XCFrameworks, find the .xcframework
directory (which may be nested in dist/) and place it at the root of
the ZIP. This fixes SPM binary target resolution which expects the
framework at the archive root.

Affected packages: RACommons, RABackendLLAMACPP, RABackendONNX
The JNI bridge library (librunanywhere_jni.so) was being built to
dist/android/jni/ but not uploaded as an artifact or copied to jniLibs.

This caused the 'dlopen failed: library librunanywhere_jni.so not found' crash.
Issues fixed:
1. Backend modules had wrong download URLs (core-v0.1.4)
   - Removed module-level downloadJniLibs tasks (main SDK handles all native libs)

2. Android compilation was skipped on JitPack
   - Added explicit assembleRelease step before publishToMavenLocal
   - Added android-36 platform and ANDROID_SDK_ROOT env var

3. Race condition with native lib downloads
   - JitPack now runs downloadJniLibs -> assembleRelease -> publishToMavenLocal
   - Backend modules are just Kotlin code (no native libs)
- Chain all commands with && so env vars persist
- Use publishAllPublicationsToMavenLocal to include Android AAR
- Add --info for better debugging
- publishAllPublicationsToMavenLocal doesn't exist
- Split downloadJniLibs and assembleRelease+publish for clarity
- Configure signing and Maven Central repository for main SDK
- Add publishing configuration to LlamaCPP and ONNX modules
- Add GitHub Actions workflow for automated publishing
- Use io.github.sanchitmonga22 namespace (verified)

Artifacts will be published as:
- io.github.sanchitmonga22:runanywhere-sdk
- io.github.sanchitmonga22:runanywhere-llamacpp
- io.github.sanchitmonga22:runanywhere-onnx
- Add publishLibraryVariants("release") to androidTarget blocks
- Add mavenPublication { artifactId = ... } for correct artifact naming
- Create KOTLIN_MAVEN_CENTRAL_PUBLISHING.md documentation

This enables publishing Android AARs that contain native .so libraries
(librunanywhere_jni.so, librac_backend_llamacpp.so, libonnxruntime.so, etc.)
to Maven Central alongside the JVM JARs.

Artifacts now published:
- runanywhere-sdk-android (AAR with native libs)
- runanywhere-llamacpp-android (AAR with native libs)
- runanywhere-onnx-android (AAR with native libs)
- Plus 6 JVM/KMP artifacts
…peline Examples

- Added a "Quick Start" section to the Documentation.md for easier onboarding.
- Updated the Table of Contents to include new sections.
- Included detailed examples for initializing the SDK, registering models, and using the voice pipeline.
- Expanded usage examples for LLM, STT, and TTS functionalities to match the starter app implementation.
- Improved clarity and structure of voice session events and processing flow.
@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds Maven Central Android AAR publishing configuration across Kotlin modules, introduces a new streaming voice-session API/implementation (streamVoiceSession) with STT→LLM→TTS orchestration, and expands Kotlin SDK documentation and publishing guides; also adds secrets template and .gitignore entries.

Changes

Cohort / File(s) Summary
Kotlin Android publishing
sdk/runanywhere-kotlin/build.gradle.kts, sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts, sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts
Enabled Android AAR publishing via publishLibraryVariants("release") and added mavenPublication { artifactId = ... } entries (artifactIds set for SDK and module artifacts).
Public API (declarations)
sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+VoiceAgent.kt, sdk/runanywhere-kotlin/docs/Documentation.md
Added RunAnywhere.streamVoiceSession(audioChunks: Flow<ByteArray>, config: VoiceSessionConfig): Flow<VoiceSessionEvent> declaration; documentation shows expanded VoiceSessionEvent variants and new consolidated VoiceAgentResult fields.
Voice streaming implementation (JVM/Android)
sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/.../RunAnywhere+VoiceAgent.jvmAndroid.kt
Implemented streaming pipeline using channelFlow: buffering, RMS-based audio level, silence detection, per-turn STT→LLM→TTS orchestration, and emission of events (Listening, SpeechStarted, Processing, Transcribed, Responded, Speaking, TurnCompleted, Stopped, Error).
Documentation & guides
sdk/runanywhere-kotlin/docs/Documentation.md, sdk/runanywhere-kotlin/docs/KOTLIN_MAVEN_CENTRAL_PUBLISHING.md
Large Documentation.md expansion (Quick Start, model lifecycle, voice pipeline examples) and new Kotlin Maven Central publishing guide with CI/local steps and verification.
Secrets & ignore
sdk/runanywhere-kotlin/secrets.template.properties, sdk/runanywhere-kotlin/.gitignore
Added secrets template with placeholders/usage notes and ignored secrets.properties / *.secrets.properties.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Audio Client
    participant SDK as RunAnywhere SDK
    participant STT as STT Service
    participant LLM as LLM Service
    participant TTS as TTS Service
    participant Events as Event Stream

    Client->>SDK: audioChunks: Flow<ByteArray>
    SDK->>SDK: initialize components & load models
    loop per audio chunk
        Client->>SDK: emit chunk
        SDK->>SDK: buffer + compute RMS (audioLevel)
        Events->>Events: emit Listening(audioLevel)
        SDK->>SDK: detect speech start/stop (threshold & silence)
        alt speech segment complete
            Events->>Events: emit Processing
            SDK->>STT: transcribe buffered audio
            STT-->>SDK: transcription
            Events->>Events: emit Transcribed(transcription)
            SDK->>LLM: generate response from transcription
            LLM-->>SDK: response text
            Events->>Events: emit Responded(response)
            SDK->>TTS: synthesize response
            TTS-->>SDK: audio bytes
            Events->>Events: emit Speaking
            Events->>Events: emit TurnCompleted(transcription, response, audio)
        end
    end
    Client->>SDK: stream completes
    SDK->>SDK: flush remaining buffer, finalize
    Events->>Events: emit Stopped
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

kotlin-sdk, documentation

Poem

🐰 Hopping bytes and whispered streams,

I chase the silence, chase the beams,
From mic to model, then to song,
Each turn completed, swift and strong,
Joyous hops — the pipeline hums along 🥕🎶

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title '[Kotlin-sdk] Maven Release' clearly and concisely summarizes the primary change: enabling Maven Central publishing for the Kotlin Android SDK.
Description check ✅ Passed The PR description includes a filled-out template with type of change, platform-specific testing marked, and comprehensive implementation details provided in the IMPORTANT section covering all major changes.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

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


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.

Resolved conflicts in Maven Central publishing configuration:
- Updated to new Sonatype Central Portal URLs
- Enhanced GPG signing with system GPG fallback
- Added GPG key import steps in CI workflow
@sanchitmonga22 sanchitmonga22 changed the title Smonga/cleanup [Android-sdk] Maven Release Jan 20, 2026
@sanchitmonga22 sanchitmonga22 marked this pull request as ready for review January 20, 2026 22:22
@sanchitmonga22 sanchitmonga22 changed the title [Android-sdk] Maven Release [Kotlin-sdk] Maven Release Jan 20, 2026
Copy link

@ellipsis-dev ellipsis-dev 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

Changes requested ❌

Reviewed everything up to a8206ee in 2 minutes and 32 seconds. Click for details.
  • Reviewed 1414 lines of code in 7 files
  • Skipped 0 files when reviewing.
  • Skipped posting 5 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+VoiceAgent.jvmAndroid.kt:304
  • Draft comment:
    For heavy CPU tasks like RMS calculation in high-frequency audio streams, consider offloading to a dedicated dispatcher to avoid blocking the caller.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 15% vs. threshold = 50% The comment suggests a performance optimization but uses the word "consider", making it a suggestion rather than identifying a definite issue. The rules state "Do NOT make speculative comments, such as 'If X, then Y is an issue'. Only comment if it is definitely an issue." This comment is speculative - it assumes RMS calculation will be heavy enough to cause blocking without evidence. Audio chunks are typically small (hundreds of bytes to a few KB), and the RMS calculation is relatively simple. The comment also doesn't provide clear, actionable guidance - "dedicated dispatcher" is vague. The code already uses channelFlow which has its own dispatcher management. This seems like a premature optimization suggestion without evidence of an actual problem. Could the RMS calculation actually be a performance bottleneck in practice? Audio processing often happens at high frequency (e.g., 50-100 chunks per second), and even simple calculations could add up. The author might have performance data or experience suggesting this is a real concern. While high-frequency audio processing could theoretically be intensive, the comment provides no evidence this is actually a problem. The calculation is simple (a loop over shorts with basic arithmetic), and typical audio chunks are small. The comment uses "consider" which makes it advisory/speculative rather than identifying a definite issue. Per the rules, speculative optimization suggestions without clear evidence should be removed. If this were a real performance issue, the comment should definitively state the problem and provide specific guidance. This comment should be deleted. It's a speculative performance optimization suggestion ("consider") without evidence of an actual problem. The rules explicitly state not to make speculative comments and to only comment if something is definitely an issue. The suggestion is also not clearly actionable.
2. sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+VoiceAgent.jvmAndroid.kt:349
  • Draft comment:
    If no speech is detected (empty transcription), consider emitting a distinct event (e.g., NoSpeechDetected) to inform the UI rather than silently returning false.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 30% vs. threshold = 50% This comment is suggesting a code quality improvement - adding a new event type to inform the UI when no speech is detected. However, this is a suggestion for enhancement rather than pointing out a bug or required change. The current behavior (silently returning false) is not incorrect - it's a design choice. The comment is asking the author to consider adding a feature, which falls under "speculative" or "optional" feedback. According to the rules, I should not keep comments that are purely suggestions unless they point to a clear issue. The code works as-is, it just might not be the most informative to the UI. This is more of a "nice to have" rather than a "must fix". The comment could be valid if there's a clear UX issue where the UI needs to know about no-speech-detected cases. Without seeing the event types defined or understanding the full requirements, I can't be certain this is unnecessary. Perhaps the UI does need this information for proper user feedback. While the UI might benefit from this information, the comment is phrased as "consider" which makes it a suggestion rather than identifying a bug. The current implementation is functional - it logs the event and continues. If this were a critical issue, the comment would be more definitive. This is a code quality suggestion, not a required fix. This comment is a suggestion for enhancement rather than identifying a bug or required change. It's asking the author to "consider" adding a new event type, which is optional feedback. According to the rules, I should remove comments that are suggestions unless they point to clear issues.
3. sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+VoiceAgent.jvmAndroid.kt:227
  • Draft comment:
    Ensure that cancellation of native bridge operations (CppBridgeSTT, CppBridgeLLM, CppBridgeTTS) in stopVoiceSession is robust. Logging their cancellation results, if possible, might aid diagnostics.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
4. sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere+VoiceAgent.jvmAndroid.kt:320
  • Draft comment:
    The inner function processAudio is well structured but could benefit from additional inline comments clarifying its state transitions and step-by-step processing, especially around resetting the audio buffer and continuous mode logic.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50% This comment is asking for more inline comments, which is a code quality suggestion. However, it's quite vague - it doesn't specify exactly what needs clarification. The function already has step-by-step comments for the main processing flow. The audio buffer reset is a simple operation (line 329) that doesn't really need more explanation. The continuous mode logic mentioned in the comment isn't even in this function - it's in the caller. This seems like a generic "add more comments" suggestion without identifying a specific gap in understanding. According to the rules, comments should be actionable and clear, and this one is neither - it's subjective whether more comments are needed here. The function does handle multiple state transitions (isProcessingTurn, audioBuffer reset) and the comment might be valid if these transitions are confusing to readers. The code is new (added in this PR), so perhaps the reviewer genuinely found it hard to follow. I might be too quick to dismiss this as unnecessary. While the state transitions exist, they're relatively straightforward: check if processing, set flag, get and clear buffer, validate, process, reset flag. The existing comments already mark the three main steps. Adding more comments about obvious state management (like resetting a flag) could actually make the code noisier. The continuous mode logic isn't even in this function, which suggests the reviewer may not have fully understood the code structure. This comment is too vague and subjective. It doesn't identify a specific gap in documentation, and the function already has reasonable step-by-step comments. The request for clarification about "continuous mode logic" is misplaced since that logic is in the caller, not in processAudio. This is not an actionable, specific code change request.
5. sdk/runanywhere-kotlin/docs/Documentation.md:6
  • Draft comment:
    There's a typographical error: "acces" should be "access" in the introductory sentence.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.

Workflow ID: wflow_4X2MJ1fp2x0PeKss

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

/**
* Normalize audio level for visualization (0.0 to 1.0)
*/
fun normalizeAudioLevel(rms: Float): Float = (rms * 3.0f).coerceIn(0f, 1f)
Copy link

Choose a reason for hiding this comment

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

Consider extracting the magic constant (3.0f) used in normalizeAudioLevel into a named constant. This improves clarity and makes future adjustments easier.

Copy link

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

🤖 Fix all issues with AI agents
In `@sdk/runanywhere-kotlin/docs/Documentation.md`:
- Around line 270-345: The docs for AudioCaptureService are missing the required
Android permission information; update the documentation to state that
AudioRecord (used by AudioCaptureService, particularly startCapture/stopCapture)
requires the android.permission.RECORD_AUDIO manifest permission and that apps
targeting Android 6.0+ must request this permission at runtime (include guidance
to check ContextCompat.checkSelfPermission and call
ActivityCompat.requestPermissions with a REQUEST_RECORD_AUDIO_PERMISSION
constant).

In `@sdk/runanywhere-kotlin/docs/KOTLIN_MAVEN_CENTRAL_PUBLISHING.md`:
- Around line 93-99: Update the unlabeled fenced code block under the "Expected
output" section in KOTLIN_MAVEN_CENTRAL_PUBLISHING.md by adding a language tag
(e.g., text) to the opening triple backticks so the fence reads ```text; this
satisfies markdownlint MD040 and preserves the listed jni/arm64-v8a/... entries
(the expected-output block containing the libc++_shared.so, libomp.so,
librac_commons.so, librunanywhere_jni.so lines).
- Around line 127-131: Replace the bolded critical note "**Critical: Upload to
keys.openpgp.org with email verification**" with a proper heading to satisfy
MD036; locate the section titled "### 4. Upload GPG Key to Keyservers" and
change that bold line into a heading (e.g., a level-4 or appropriate heading
like "#### Critical: Upload to keys.openpgp.org with email verification") so the
note is rendered as a heading instead of bold text.
- Around line 191-193: Update the incorrect repository path used in the local
publish step: replace the string "cd sdks/sdk/runanywhere-kotlin" with the
correct path "cd runanywhere-sdks/sdk/runanywhere-kotlin" so the command points
to the current repo root and SDK location; edit the line in
KOTLIN_MAVEN_CENTRAL_PUBLISHING.md that contains "cd
sdks/sdk/runanywhere-kotlin" accordingly.
- Around line 334-339: The "## Key URLs" section contains bare URLs (e.g.,
"https://central.sonatype.com",
"https://central.sonatype.com/search?q=io.github.sanchitmonga22",
"https://keys.openpgp.org", "https://keyserver.ubuntu.com") that trigger MD034;
update the "## Key URLs" block in KOTLIN_MAVEN_CENTRAL_PUBLISHING.md so each
bare URL is converted to an autolink or Markdown link (for example wrap each URL
in angle brackets like <https://central.sonatype.com> or use [Central
Portal](https://central.sonatype.com)), and apply the same transformation to any
other bare URLs elsewhere in the document.

In
`@sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere`+VoiceAgent.jvmAndroid.kt:
- Around line 294-299: The ByteArrayOutputStream audioBuffer is allowed to grow
unbounded during long silence; change buffering so you either (A) only write
incoming audio chunks into audioBuffer after speech is detected (use
isSpeechActive) or (B) impose a fixed pre-roll cap (e.g., N bytes) and implement
a rolling/circular behavior when appending to audioBuffer so oldest bytes are
discarded once the cap is exceeded; update both the initial buffer usage around
audioBuffer/minAudioBytes/silenceDurationMs and the corresponding logic later
(the code paths referenced at lines ~400-404) to respect the same cap or
start-buffering-after-speech policy and ensure isSpeechActive and lastSpeechTime
are used to switch modes.
🧹 Nitpick comments (2)
sdk/runanywhere-kotlin/docs/Documentation.md (2)

25-52: Consider adding a note about version numbers and verifying JitPack dependencies.

The installation example hard-codes version 0.16.1 across all three artifacts. While this ensures consistency, consider adding a note that users should check for the latest version on Maven Central.

Additionally, JitPack is mentioned for transitive dependencies. Consider documenting:

  • Why JitPack is required (specific transitive dependencies)
  • Whether there are plans to publish these dependencies to Maven Central for better supply-chain security
  • Any security considerations users should be aware of
📝 Suggested documentation addition

Add after line 39:

> **Note**: Replace `0.16.1` with the latest version available on [Maven Central](https://central.sonatype.com/search?q=io.github.sanchitmonga22).

Add after line 52:

> **Note**: JitPack is required for transitive dependencies (android-vad, PRDownloader) that are not yet published to Maven Central. Ensure your security policies allow JitPack dependencies.

357-397: Consider parsing sample rate from WAV header instead of hard-coding.

The function hard-codes the sample rate to 22050 Hz based on "Piper TTS default," but this may not be correct for other TTS models or future versions. The WAV header contains the sample rate at bytes 24-27, which could be parsed for better compatibility.

♻️ Proposed enhancement to parse sample rate from WAV header
 suspend fun playWavAudio(wavData: ByteArray) = withContext(Dispatchers.IO) {
     if (wavData.size < 44) return@withContext
 
     val headerSize = if (wavData.size > 44 &&
         wavData[0] == 'R'.code.toByte() &&
         wavData[1] == 'I'.code.toByte()) 44 else 0
 
     val pcmData = wavData.copyOfRange(headerSize, wavData.size)
-    val sampleRate = 22050 // Piper TTS default sample rate
+    
+    // Parse sample rate from WAV header (bytes 24-27, little-endian)
+    val sampleRate = if (headerSize == 44) {
+        (wavData[24].toInt() and 0xFF) or
+        ((wavData[25].toInt() and 0xFF) shl 8) or
+        ((wavData[26].toInt() and 0xFF) shl 16) or
+        ((wavData[27].toInt() and 0xFF) shl 24)
+    } else {
+        22050 // Default fallback
+    }

Comment on lines +270 to +345
#### Audio Capture Service (Required for Voice Pipeline)

```kotlin
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*

class AudioCaptureService {
private var audioRecord: AudioRecord? = null

@Volatile
private var isCapturing = false

companion object {
const val SAMPLE_RATE = 16000
const val CHUNK_SIZE_MS = 100 // Emit chunks every 100ms
}

fun startCapture(): Flow<ByteArray> = callbackFlow {
val bufferSize = AudioRecord.getMinBufferSize(
SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
val chunkSize = (SAMPLE_RATE * 2 * CHUNK_SIZE_MS) / 1000

try {
audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
maxOf(bufferSize, chunkSize * 2)
)

if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
close(IllegalStateException("AudioRecord initialization failed"))
return@callbackFlow
}

audioRecord?.startRecording()
isCapturing = true

val readJob = launch(Dispatchers.IO) {
val buffer = ByteArray(chunkSize)
while (isActive && isCapturing) {
val bytesRead = audioRecord?.read(buffer, 0, chunkSize) ?: -1
if (bytesRead > 0) {
trySend(buffer.copyOf(bytesRead))
}
}
}

awaitClose {
readJob.cancel()
stopCapture()
}
} catch (e: Exception) {
stopCapture()
close(e)
}
}

fun stopCapture() {
isCapturing = false
try {
audioRecord?.stop()
audioRecord?.release()
} catch (_: Exception) {}
audioRecord = null
}
}
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Document required Android permissions for AudioCaptureService.

The AudioCaptureService uses AudioRecord which requires the RECORD_AUDIO permission. This is critical setup information that's missing from the documentation. Users will encounter a SecurityException at runtime without this context.

📋 Suggested documentation addition

Add before line 272:

**Required Permissions**

Add to your `AndroidManifest.xml`:
```xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />

For Android 6.0 (API 23) and above, you must also request runtime permission:

// Check and request permission
if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO)
    != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(
        activity,
        arrayOf(Manifest.permission.RECORD_AUDIO),
        REQUEST_RECORD_AUDIO_PERMISSION
    )
}
🤖 Prompt for AI Agents
In `@sdk/runanywhere-kotlin/docs/Documentation.md` around lines 270 - 345, The
docs for AudioCaptureService are missing the required Android permission
information; update the documentation to state that AudioRecord (used by
AudioCaptureService, particularly startCapture/stopCapture) requires the
android.permission.RECORD_AUDIO manifest permission and that apps targeting
Android 6.0+ must request this permission at runtime (include guidance to check
ContextCompat.checkSelfPermission and call ActivityCompat.requestPermissions
with a REQUEST_RECORD_AUDIO_PERMISSION constant).

Comment on lines 127 to 131
### 4. Upload GPG Key to Keyservers

**Critical: Upload to keys.openpgp.org with email verification**

```bash
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a heading instead of bold for the critical note

MD036 prefers headings over bold-as-heading.

✍️ Proposed fix
-**Critical: Upload to keys.openpgp.org with email verification**
+#### Critical: Upload to keys.openpgp.org with email verification
📝 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.

Suggested change
### 4. Upload GPG Key to Keyservers
**Critical: Upload to keys.openpgp.org with email verification**
```bash
### 4. Upload GPG Key to Keyservers
#### Critical: Upload to keys.openpgp.org with email verification
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

129-129: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🤖 Prompt for AI Agents
In `@sdk/runanywhere-kotlin/docs/KOTLIN_MAVEN_CENTRAL_PUBLISHING.md` around lines
127 - 131, Replace the bolded critical note "**Critical: Upload to
keys.openpgp.org with email verification**" with a proper heading to satisfy
MD036; locate the section titled "### 4. Upload GPG Key to Keyservers" and
change that bold line into a heading (e.g., a level-4 or appropriate heading
like "#### Critical: Upload to keys.openpgp.org with email verification") so the
note is rendered as a heading instead of bold text.

Comment on lines +191 to +193
```bash
cd sdks/sdk/runanywhere-kotlin

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix repo path in the local publish step

The command still uses the old sdks/ prefix; the SDK lives under sdk/runanywhere-kotlin in this repo, so this step is easy to miscopy.

🐛 Proposed fix
- cd sdks/sdk/runanywhere-kotlin
+ cd sdk/runanywhere-kotlin
Based on learnings, the repository root is now `runanywhere-sdks`.
🤖 Prompt for AI Agents
In `@sdk/runanywhere-kotlin/docs/KOTLIN_MAVEN_CENTRAL_PUBLISHING.md` around lines
191 - 193, Update the incorrect repository path used in the local publish step:
replace the string "cd sdks/sdk/runanywhere-kotlin" with the correct path "cd
runanywhere-sdks/sdk/runanywhere-kotlin" so the command points to the current
repo root and SDK location; edit the line in KOTLIN_MAVEN_CENTRAL_PUBLISHING.md
that contains "cd sdks/sdk/runanywhere-kotlin" accordingly.

Comment on lines 334 to 339
## Key URLs

- **Central Portal**: https://central.sonatype.com
- **Published Artifacts**: https://central.sonatype.com/search?q=io.github.sanchitmonga22
- **keys.openpgp.org**: https://keys.openpgp.org
- **keyserver.ubuntu.com**: https://keyserver.ubuntu.com
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Convert bare URLs to links/autolinks

MD034 flags bare URLs; wrapping them as autolinks resolves it (apply the same pattern to other bare URLs earlier in the doc).

✍️ Proposed fix
-- **Central Portal**: https://central.sonatype.com
-- **Published Artifacts**: https://central.sonatype.com/search?q=io.github.sanchitmonga22
-- **keys.openpgp.org**: https://keys.openpgp.org
-- **keyserver.ubuntu.com**: https://keyserver.ubuntu.com
+- **Central Portal**: <https://central.sonatype.com>
+- **Published Artifacts**: <https://central.sonatype.com/search?q=io.github.sanchitmonga22>
+- **keys.openpgp.org**: <https://keys.openpgp.org>
+- **keyserver.ubuntu.com**: <https://keyserver.ubuntu.com>
📝 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.

Suggested change
## Key URLs
- **Central Portal**: https://central.sonatype.com
- **Published Artifacts**: https://central.sonatype.com/search?q=io.github.sanchitmonga22
- **keys.openpgp.org**: https://keys.openpgp.org
- **keyserver.ubuntu.com**: https://keyserver.ubuntu.com
## Key URLs
- **Central Portal**: <https://central.sonatype.com>
- **Published Artifacts**: <https://central.sonatype.com/search?q=io.github.sanchitmonga22>
- **keys.openpgp.org**: <https://keys.openpgp.org>
- **keyserver.ubuntu.com**: <https://keyserver.ubuntu.com>
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

336-336: Bare URL used

(MD034, no-bare-urls)


337-337: Bare URL used

(MD034, no-bare-urls)


338-338: Bare URL used

(MD034, no-bare-urls)


339-339: Bare URL used

(MD034, no-bare-urls)

🤖 Prompt for AI Agents
In `@sdk/runanywhere-kotlin/docs/KOTLIN_MAVEN_CENTRAL_PUBLISHING.md` around lines
334 - 339, The "## Key URLs" section contains bare URLs (e.g.,
"https://central.sonatype.com",
"https://central.sonatype.com/search?q=io.github.sanchitmonga22",
"https://keys.openpgp.org", "https://keyserver.ubuntu.com") that trigger MD034;
update the "## Key URLs" block in KOTLIN_MAVEN_CENTRAL_PUBLISHING.md so each
bare URL is converted to an autolink or Markdown link (for example wrap each URL
in angle brackets like <https://central.sonatype.com> or use [Central
Portal](https://central.sonatype.com)), and apply the same transformation to any
other bare URLs elsewhere in the document.

Comment on lines +294 to +299
val audioBuffer = ByteArrayOutputStream()
var isSpeechActive = false
var lastSpeechTime = 0L
var isProcessingTurn = false
val minAudioBytes = 16000 // ~0.5s at 16kHz, 16-bit
val silenceDurationMs = (config.silenceDuration * 1000).toLong()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent unbounded buffering during long silence

The buffer grows for every chunk even before speech detection; long silent streams can grow memory indefinitely. Consider bounding pre-roll or only buffering after speech starts.

🛠️ Suggested update
-    val minAudioBytes = 16000 // ~0.5s at 16kHz, 16-bit
+    val minAudioBytes = 16000 // ~0.5s at 16kHz, 16-bit
+    val maxPreSpeechBytes = minAudioBytes * 4 // ~2s pre-roll cap
@@
-            synchronized(audioBuffer) {
-                audioBuffer.write(chunk)
-            }
+            synchronized(audioBuffer) {
+                if (!isSpeechActive && audioBuffer.size() > maxPreSpeechBytes) {
+                    audioBuffer.reset()
+                }
+                audioBuffer.write(chunk)
+            }

Also applies to: 400-404

🤖 Prompt for AI Agents
In
`@sdk/runanywhere-kotlin/src/jvmAndroidMain/kotlin/com/runanywhere/sdk/public/extensions/RunAnywhere`+VoiceAgent.jvmAndroid.kt
around lines 294 - 299, The ByteArrayOutputStream audioBuffer is allowed to grow
unbounded during long silence; change buffering so you either (A) only write
incoming audio chunks into audioBuffer after speech is detected (use
isSpeechActive) or (B) impose a fixed pre-roll cap (e.g., N bytes) and implement
a rolling/circular behavior when appending to audioBuffer so oldest bytes are
discarded once the cap is exceeded; update both the initial buffer usage around
audioBuffer/minAudioBytes/silenceDurationMs and the corresponding logic later
(the code paths referenced at lines ~400-404) to respect the same cap or
start-buffering-after-speech policy and ensure isSpeechActive and lastSpeechTime
are used to switch modes.

- Simplified KOTLIN_MAVEN_CENTRAL_PUBLISHING.md for quick reference
- Added secrets.template.properties for team credential sharing
- Updated .gitignore to exclude secrets files
Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed b068537 in 2 minutes and 16 seconds. Click for details.
  • Reviewed 523 lines of code in 3 files
  • Skipped 0 files when reviewing.
  • Skipped posting 3 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. sdk/runanywhere-kotlin/.gitignore:45
  • Draft comment:
    Good addition of secret ignore rules. This ensures that credentials (secrets.properties files) are not accidentally committed.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
2. sdk/runanywhere-kotlin/docs/KOTLIN_MAVEN_CENTRAL_PUBLISHING.md:44
  • Draft comment:
    The directory path in the publish command ('cd sdks/sdk/runanywhere-kotlin') may be incorrect. Verify if it should be 'sdk/runanywhere-kotlin' to match the actual project structure.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% Looking at the diff carefully, the old documentation had cd sdks/sdk/runanywhere-kotlin in the publishing steps section, and the new simplified version also has cd sdks/sdk/runanywhere-kotlin on line 44. This means the path was NOT changed in this diff - it existed before and continues to exist. The comment is about unchanged code. According to the rules, "If the comment is about unchanged code, this should be False, and the comment should be deleted." Even if the path might be incorrect, since it wasn't changed in this PR, the comment should not be kept. While the path might indeed be incorrect (the file location suggests sdk/runanywhere-kotlin is the correct path), this is pre-existing content that wasn't modified in this diff. The comment doesn't relate to any change made by the author. The rule is clear: comments must be about changes made in the diff. Since this path existed in the old version and was carried over unchanged to the new version, this comment is about unchanged code and should be deleted regardless of whether the path is actually correct or not. Delete this comment because it's about unchanged code. The path cd sdks/sdk/runanywhere-kotlin existed in the original documentation and was not modified in this diff.
3. sdk/runanywhere-kotlin/secrets.template.properties:1
  • Draft comment:
    The secrets template is comprehensive and clearly instructs on how to set up sensitive credentials. Ensure that this file is never committed with actual values.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None

Workflow ID: wflow_RTUR9GfIzjz6SrBz

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@sanchitmonga22 sanchitmonga22 merged commit 3351926 into main Jan 21, 2026
6 of 11 checks passed
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