Skip to content

feat: expose Web SDK streaming APIs and describeImage in wrapper libraries#20

Merged
hyochan merged 3 commits intomainfrom
feat/web-sdk-api-parity
Mar 21, 2026
Merged

feat: expose Web SDK streaming APIs and describeImage in wrapper libraries#20
hyochan merged 3 commits intomainfrom
feat/web-sdk-api-parity

Conversation

@hyochan
Copy link
Copy Markdown
Member

@hyochan hyochan commented Mar 21, 2026

Closes #12

Exposes Web SDK unique APIs (summarizeStreaming, translateStreaming, rewriteStreaming, describeImage) in expo-ondevice-ai and react-native-ondevice-ai. Adds API_PARITY.md documenting cross-platform API availability including Chrome-only features.

Summary by CodeRabbit

  • New Features

    • Added streaming APIs for text summarization, translation, and rewriting with real-time token callbacks.
    • Added image description capability to analyze images with optional custom prompts.
  • Documentation

    • Added API parity reference guide detailing feature availability across platforms and wrapper libraries.

- Add libraries/API_PARITY.md documenting cross-platform availability:
  - Core features, streaming variants, image APIs, Chrome-only APIs (detectLanguage,
    write, writeStreaming), model management, and Android-only APIs in a single table
- expo-ondevice-ai: add TextStreamChunk, SummarizeStreamOptions, TranslateStreamOptions,
  RewriteStreamOptions, DescribeImageOptions/Result types and corresponding functions
  (summarizeStreaming, translateStreaming, rewriteStreaming, describeImage) using the
  existing EventSubscription pattern from chatStream
- react-native-ondevice-ai: add NitroTextStreamChunk + NitroDescribeImageOptions/Result
  to the Nitro spec; add listener pairs for summarize/translate/rewrite streaming;
  add describeImage method; mirror new public types and functions in types.ts and index.ts
- detectLanguage / write / writeStreaming documented as Chrome-only in API_PARITY.md

Closes #12
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the expo-ondevice-ai and react-native-ondevice-ai libraries by exposing advanced AI capabilities previously found in the Web SDK. It integrates real-time streaming functionalities for text summarization, translation, and rewriting, alongside a new feature for on-device image description. These additions aim to provide a more robust and consistent AI experience across mobile platforms, supported by clear documentation on API availability and platform-specific nuances.

Highlights

  • New Streaming APIs: Introduced summarizeStreaming, translateStreaming, and rewriteStreaming APIs in both expo-ondevice-ai and react-native-ondevice-ai libraries, enabling real-time, progressive token delivery for text generation tasks.
  • Image Description API: Added a describeImage API to expo-ondevice-ai and react-native-ondevice-ai, allowing on-device AI to describe the contents of an image.
  • API Parity Documentation: Created API_PARITY.md to provide a comprehensive cross-platform availability matrix for all Locanara wrapper library APIs, including details on streaming, image features, Chrome-only features, and model management.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 21, 2026

Warning

Rate limit exceeded

@hyochan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 50 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59bd03b3-3575-402e-b3e9-b0cb877879bc

📥 Commits

Reviewing files that changed from the base of the PR and between b16e99e and 004ac0f.

⛔ Files ignored due to path filters (206)
  • libraries/react-native-ondevice-ai/nitrogen/generated/.gitattributes is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/NitroOndeviceAi+autolinking.cmake is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/NitroOndeviceAi+autolinking.gradle is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/NitroOndeviceAiOnLoad.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/NitroOndeviceAiOnLoad.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JFunc_void_NitroChatStreamChunk.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JFunc_void_NitroModelDownloadProgress.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JFunc_void_NitroTextStreamChunk.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JHybridOndeviceAiSpec.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JHybridOndeviceAiSpec.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroChatMessage.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroChatMessageRole.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroChatOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroChatResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroChatStreamChunk.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroClassification.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroClassifyOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroClassifyResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroDescribeImageOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroDescribeImageResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroDeviceCapability.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroExtractEntity.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroExtractOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroExtractResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroInferenceEngine.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroModelDownloadProgress.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroModelDownloadState.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroModelInfo.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroProofreadCorrection.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroProofreadResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroRewriteOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroRewriteOutputType.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroRewriteResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroSummarizeInputType.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroSummarizeOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroSummarizeOutputType.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroSummarizeResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroTextStreamChunk.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroTranslateOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JNitroTranslateResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JOndeviceAiPlatform.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_Array_NitroChatMessage_.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_Array_NitroChatMessage_.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_Array_String_.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_Array_String_.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_Double.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroChatOptions.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroChatOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroClassifyOptions.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroClassifyOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroDescribeImageOptions.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroDescribeImageOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroExtractOptions.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroExtractOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroSummarizeOptions.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_NitroSummarizeOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_String.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/c++/JVariant_NullType_String.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Func_void_NitroChatStreamChunk.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Func_void_NitroModelDownloadProgress.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Func_void_NitroTextStreamChunk.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/HybridOndeviceAiSpec.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroChatMessage.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroChatMessageRole.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroChatOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroChatResult.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroChatStreamChunk.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroClassification.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroClassifyOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroClassifyResult.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroDescribeImageOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroDescribeImageResult.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroDeviceCapability.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroExtractEntity.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroExtractOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroExtractResult.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroInferenceEngine.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroModelDownloadProgress.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroModelDownloadState.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroModelInfo.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroOndeviceAiOnLoad.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroProofreadCorrection.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroProofreadResult.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroRewriteOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroRewriteOutputType.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroRewriteResult.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroSummarizeInputType.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroSummarizeOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroSummarizeOutputType.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroSummarizeResult.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroTextStreamChunk.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroTranslateOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/NitroTranslateResult.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/OndeviceAiPlatform.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_Array_NitroChatMessage_.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_Array_String_.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_Double.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_NitroChatOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_NitroClassifyOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_NitroDescribeImageOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_NitroExtractOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_NitroSummarizeOptions.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/android/kotlin/com/margelo/nitro/ondeviceai/Variant_NullType_String.kt is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/NitroOndeviceAi+autolinking.rb is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/NitroOndeviceAi-Swift-Cxx-Bridge.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/NitroOndeviceAi-Swift-Cxx-Bridge.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/NitroOndeviceAi-Swift-Cxx-Umbrella.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/NitroOndeviceAiAutolinking.mm is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/NitroOndeviceAiAutolinking.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/c++/HybridOndeviceAiSpecSwift.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/c++/HybridOndeviceAiSpecSwift.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroChatResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroChatStreamChunk.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroClassifyResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroDescribeImageResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroDeviceCapability.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroExtractResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroInferenceEngine.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroModelDownloadProgress.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroProofreadResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroRewriteResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroSummarizeResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroTextStreamChunk.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_NitroTranslateResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_bool.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_std__string.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_std__vector_NitroModelInfo_.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/HybridOndeviceAiSpec.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/HybridOndeviceAiSpec_cxx.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroChatMessage.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroChatMessageRole.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroChatOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroChatResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroChatStreamChunk.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroClassification.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroClassifyOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroClassifyResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroDescribeImageOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroDescribeImageResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroDeviceCapability.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroExtractEntity.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroExtractOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroExtractResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroInferenceEngine.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroModelDownloadProgress.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroModelDownloadState.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroModelInfo.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroProofreadCorrection.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroProofreadResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroRewriteOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroRewriteOutputType.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroRewriteResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroSummarizeInputType.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroSummarizeOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroSummarizeOutputType.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroSummarizeResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroTextStreamChunk.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroTranslateOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/NitroTranslateResult.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/OndeviceAiPlatform.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType_Double.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType_NitroChatOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType_NitroClassifyOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType_NitroDescribeImageOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType_NitroExtractOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType_NitroSummarizeOptions.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType_String.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType__NitroChatMessage_.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/Variant_NullType__String_.swift is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/HybridOndeviceAiSpec.cpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/HybridOndeviceAiSpec.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroChatMessage.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroChatMessageRole.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroChatOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroChatResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroChatStreamChunk.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroClassification.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroClassifyOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroClassifyResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroDescribeImageOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroDescribeImageResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroDeviceCapability.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroExtractEntity.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroExtractOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroExtractResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroInferenceEngine.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroModelDownloadProgress.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroModelDownloadState.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroModelInfo.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroProofreadCorrection.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroProofreadResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroRewriteOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroRewriteOutputType.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroRewriteResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroSummarizeInputType.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroSummarizeOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroSummarizeOutputType.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroSummarizeResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroTextStreamChunk.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroTranslateOptions.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/NitroTranslateResult.hpp is excluded by !**/generated/**
  • libraries/react-native-ondevice-ai/nitrogen/generated/shared/c++/OndeviceAiPlatform.hpp is excluded by !**/generated/**
📒 Files selected for processing (5)
  • libraries/react-native-ondevice-ai/android/src/main/java/com/margelo/nitro/ondeviceai/HybridOndeviceAi.kt
  • libraries/react-native-ondevice-ai/ios/HybridOndeviceAi.swift
  • libraries/react-native-ondevice-ai/src/__mocks__/react-native-nitro-modules.js
  • libraries/react-native-ondevice-ai/src/__tests__/index.test.ts
  • libraries/react-native-ondevice-ai/src/index.ts
📝 Walkthrough

Walkthrough

This PR adds token-by-token streaming APIs (summarizeStreaming, translateStreaming, rewriteStreaming) with onChunk callbacks and image description functionality (describeImage) to Expo and React Native wrapper libraries. It includes type definitions for streaming chunks and new options/result interfaces, plus an API parity reference matrix documenting feature availability across platforms.

Changes

Cohort / File(s) Summary
API Parity Documentation
libraries/API_PARITY.md
New documentation file defining a cross-platform API feature matrix with legends mapping availability symbols (, 🚧, ) across wrapper libraries and platforms, covering streaming, image features, and platform-specific capabilities.
Expo Library Types & Streaming
libraries/expo-ondevice-ai/src/types.ts, libraries/expo-ondevice-ai/src/index.ts
Added TextStreamChunk, streaming option interfaces (SummarizeStreamOptions, TranslateStreamOptions, RewriteStreamOptions), and image types (DescribeImageOptions, DescribeImageResult). Implemented streaming functions with event listener registration, chunk forwarding to onChunk callbacks, and subscription cleanup via finally blocks.
React Native Library Types
libraries/react-native-ondevice-ai/src/types.ts
Added matching streaming chunk, option, and image type interfaces with identical structure to Expo counterparts for API consistency.
React Native Library Implementation
libraries/react-native-ondevice-ai/src/index.ts
Implemented streaming functions with AI.instance listener management patterns, chunk conversion from NitroTextStreamChunk to TextStreamChunk, and describeImage method forwarding optional prompt parameter.
React Native Nitro Specifications
libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts
Added NitroTextStreamChunk interface and listener methods (add*StreamListener/remove*StreamListener) for summarize, translate, and rewrite; introduced NitroDescribeImageOptions/NitroDescribeImageResult and describeImage method to the OndeviceAi interface.

Sequence Diagram

sequenceDiagram
    participant Client as JavaScript Client
    participant Module as Wrapper Module (Index.ts)
    participant Listener as Event Listener
    participant Native as Native Implementation

    Client->>Module: summarizeStreaming(text, {onChunk})
    Module->>Module: registerListener(onSummarizeStreamChunk)
    
    Module->>Native: ExpoOndeviceAiModule.summarizeStreaming(text)
    Native->>Native: Process streaming text chunks
    
    Native->>Listener: emit onSummarizeStreamChunk({delta, accumulated, isFinal})
    Listener->>Module: listener callback received
    Module->>Module: convert to TextStreamChunk
    Module->>Client: invoke onChunk(chunk)
    
    Native->>Module: return Promise<SummarizeResult>
    Module->>Module: cleanup listener
    Module->>Module: zero-timeout flush
    Module->>Client: return final result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested labels

🎯 feature, 📖 documentation

Poem

🐰 A rabbit's ode to streaming tokens

Chunk by chunk, the text flows free,
Callbacks dancing merrily,
Images described with flair and grace,
Parity mapped across each place! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: exposing Web SDK streaming APIs and describeImage in wrapper libraries, which aligns with the changeset.
Linked Issues check ✅ Passed The PR addresses all key requirements from issue #12: exposes describeImage and streaming variants (summarizeStreaming, translateStreaming, rewriteStreaming) in Expo and React Native wrappers, and documents Chrome-only APIs in API_PARITY.md.
Out of Scope Changes check ✅ Passed All changes are in-scope: documentation (API_PARITY.md), new streaming and describeImage APIs in Expo/React Native wrappers, and supporting type definitions align directly with issue #12 objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-sdk-api-parity

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.

@hyochan hyochan added the 🎯 feature New feature label Mar 21, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully exposes new streaming APIs (summarizeStreaming, translateStreaming, rewriteStreaming) and the describeImage API in the expo-ondevice-ai and react-native-ondevice-ai wrapper libraries. The addition of API_PARITY.md is a great improvement for documenting cross-platform feature availability. The code changes are well-structured and follow existing patterns. I've identified a few opportunities to improve maintainability by reducing code duplication in the new streaming function implementations and addressing some type casting that could be made clearer. Overall, this is a solid contribution that expands the library's capabilities.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

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

Inline comments:
In `@libraries/API_PARITY.md`:
- Around line 51-52: The note incorrectly suggests using device 'locale' as a
replacement for the API's language-detection function; update the text to
clearly state that device locale (e.g., "locale") reflects user/device
preferences and is not equivalent to the API's content-based language detection
(e.g., "detectLanguage(text)"); explicitly recommend third-party libraries (like
react-native-mlkit) or sending text to the server-side detectLanguage(text)
endpoint for accurate detection of arbitrary input, and remove or reword any
phrasing that implies locale is a substitute for detectLanguage(text).
- Around line 27-29: The API parity matrix shows streaming methods
(summarizeStreaming, translateStreaming, rewriteStreaming with onChunk
callbacks) present in expo-ondevice-ai and react-native-ondevice-ai but missing
from flutter_ondevice_ai; either implement matching public methods in the
Flutter wrapper (add public methods named summarizeStreaming,
translateStreaming, rewriteStreaming that accept the same options and onChunk
callback semantics as the other wrappers) or delay the rollout so the new APIs
are not listed until all three wrappers are identical—update the Flutter wrapper
code to expose those exact method names and signatures or remove/gate the
entries from the matrix to keep wrappers in lockstep.

In `@libraries/expo-ondevice-ai/src/index.ts`:
- Around line 196-206: The stream listeners (see summarizeStreaming,
translateStreaming, rewriteStreaming) are global per-event and will deliver
every onSummarizeStreamChunk event to all subscribers; fix by making streams
request-scoped: when starting a stream from those functions, generate/provide a
unique streamId and ensure the native emitter emits TextStreamChunk objects with
that streamId, then in the addListener callback only invoke options.onChunk when
chunk.streamId === localStreamId (or alternatively, if you cannot change native
emissions, enforce single-concurrent-stream by refusing to start a new
summarizeStreaming/translateStreaming/rewriteStreaming while a subscription
exists). Also ensure subscriptions are removed on completion/error and propagate
the streamId check to the other event names used in the file.
- Around line 197-205: The JS wrapper is calling and subscribing to native
symbols that don't exist; either add the missing native APIs to the iOS and
Android modules or stop using them in the wrapper. Implement native methods
named summarizeStreaming, translateStreaming, rewriteStreaming, and
describeImage on the native module (ExpoOndeviceAiModule), and emit events named
onSummarizeStreamChunk, onTranslateStreamChunk, and onRewriteStreamChunk (in
addition to the existing onChatStreamChunk and onModelDownloadProgress), or
alternatively remove/replace calls to
summarizeStreaming/translateStreaming/rewriteStreaming/describeImage and
subscriptions to
onSummarizeStreamChunk/onTranslateStreamChunk/onRewriteStreamChunk in index.ts
so the wrapper only uses native methods/events that actually exist.

In `@libraries/react-native-ondevice-ai/src/index.ts`:
- Around line 431-455: The translateStreaming path uses an empty string for
omitted sourceLanguage, causing divergence from translate(); update
translateStreaming to mirror translate() by mapping options.sourceLanguage ??
'en' (or use the same option-mapping helper if one exists) before calling
AI.instance.translateStreaming so both streaming and non-streaming translate
calls use the same default sourceLanguage convention; adjust the creation of the
options passed to AI.instance.translateStreaming in translateStreaming to use
that mapped value and ensure the onChunk listener logic remains unchanged.

In `@libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts`:
- Around line 209-213: The current streaming contract (NitroTextStreamChunk)
lacks a stream identifier so concurrent streams cannot be demultiplexed; update
the contract and listener API to include a unique streamId on
NitroTextStreamChunk (and any equivalent chunk interfaces referenced around the
301-332 region), and propagate that id through the global add/remove listener
pair (or alternatively change the API to enforce a single active stream per
feature by preventing addListener if a stream is already active). Specifically
modify the NitroTextStreamChunk interface to include streamId and update the
corresponding addListener/removeListener handlers and any emitters/dispatchers
to attach and check streamId so JS consumers can route chunks to the correct
in-flight stream.
- Around line 301-338: The native bridge is missing implementations for the ten
newly-declared methods, causing runtime failures; implement summarizeStreaming,
addSummarizeStreamListener, removeSummarizeStreamListener, translateStreaming,
addTranslateStreamListener, removeTranslateStreamListener, rewriteStreaming,
addRewriteStreamListener, removeRewriteStreamListener, and describeImage in both
HybridOndeviceAi.swift and HybridOndeviceAi.kt. For each streaming method
(summarizeStreaming, translateStreaming, rewriteStreaming) implement a native
function that starts the operation on the existing on-device engine, returns a
Promise/Future that resolves with
NitroSummarizeResult/NitroTranslateResult/NitroRewriteResult when the stream
completes, and emits intermediate NitroTextStreamChunk objects via the existing
event/listener system; for each add*StreamListener and remove*StreamListener
implement listener registration/unregistration that forwards native chunk events
to JS (use the same event names and callback wiring used elsewhere in this
bridge). For describeImage implement a synchronous/async call that calls the
image description routine on the native engine and resolves with
NitroDescribeImageResult. Ensure method signatures exactly match the Nitro spec
names so JS calls bind correctly and reuse existing error handling/logging
patterns in the bridge.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69f81f86-14af-44dd-8ac0-9746cbf84abc

📥 Commits

Reviewing files that changed from the base of the PR and between 0a022cd and b16e99e.

📒 Files selected for processing (6)
  • libraries/API_PARITY.md
  • libraries/expo-ondevice-ai/src/index.ts
  • libraries/expo-ondevice-ai/src/types.ts
  • libraries/react-native-ondevice-ai/src/index.ts
  • libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts
  • libraries/react-native-ondevice-ai/src/types.ts

Comment on lines +27 to +29
| `summarizeStreaming(text, options?)` | ✅ | ✅ | 🚧 | `onChunk` callback |
| `translateStreaming(text, options)` | ✅ | ✅ | 🚧 | `onChunk` callback |
| `rewriteStreaming(text, options)` | ✅ | ✅ | 🚧 | `onChunk` callback |
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This matrix still documents wrapper API drift.

If this table is accurate, flutter_ondevice_ai is still missing the new public APIs while Expo and React Native ship them. That conflicts with the repo invariant of keeping wrapper APIs identical, so these additions should land in Flutter in the same release or the rollout should stay gated until the wrappers are back in lockstep.

Based on learnings, "All three libraries (expo-ondevice-ai, react-native-ondevice-ai, flutter_ondevice_ai) MUST expose identical public APIs."

Also applies to: 36-37

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

In `@libraries/API_PARITY.md` around lines 27 - 29, The API parity matrix shows
streaming methods (summarizeStreaming, translateStreaming, rewriteStreaming with
onChunk callbacks) present in expo-ondevice-ai and react-native-ondevice-ai but
missing from flutter_ondevice_ai; either implement matching public methods in
the Flutter wrapper (add public methods named summarizeStreaming,
translateStreaming, rewriteStreaming that accept the same options and onChunk
callback semantics as the other wrappers) or delay the rollout so the new APIs
are not listed until all three wrappers are identical—update the Flutter wrapper
code to expose those exact method names and signatures or remove/gate the
entries from the matrix to keep wrappers in lockstep.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Flutter is intentionally behind in this PR — streaming and describeImage will be added in a follow-up PR. The API_PARITY.md table accurately reflects the current state.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@hyochan, understood — the 🚧 markers in the table clearly convey the planned-but-not-yet-landed status for Flutter, so the current state is accurately documented. I'll update my understanding of the API parity policy accordingly.

(ᓀ ᓀ) ~♪


✏️ Learnings added
Learnt from: hyochan
Repo: hyodotdev/locanara PR: 20
File: libraries/API_PARITY.md:27-29
Timestamp: 2026-03-21T09:12:17.035Z
Learning: In the `hyodotdev/locanara` repo, temporary API divergence between wrapper libraries (expo-ondevice-ai, react-native-ondevice-ai, flutter_ondevice_ai) is acceptable when the gap is intentional and documented with 🚧 markers in `libraries/API_PARITY.md`, indicating a follow-up PR is planned. The invariant that all three wrappers expose identical APIs is the long-term goal, not a strict per-PR requirement.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


⛔ Learnings removed
Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: All three libraries (expo-ondevice-ai, react-native-ondevice-ai, flutter_ondevice_ai) MUST expose identical public APIs

🧠 Learnings used
Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: All three libraries (expo-ondevice-ai, react-native-ondevice-ai, flutter_ondevice_ai) MUST expose identical public APIs

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:61-79
Timestamp: 2026-02-22T08:44:32.446Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the `checkAvailability()` function intentionally uses a lenient fallback in its catch block (`return hasAPI(api)`) because Chrome Built-in AI APIs are evolving and `availability()` can timeout on first load. This prevents features from being incorrectly locked, while actual API failures are still caught and handled by each feature method.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:28-34
Timestamp: 2026-02-23T14:50:03.100Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the cached Chrome Built-in AI instances (cachedSummarizer, cachedLanguageModel, cachedRewriter, cachedWriter, cachedTranslators) intentionally use `any` type instead of `types/dom-chromium-ai` because Chrome Built-in AI APIs are experimental and changing rapidly. Using `any` with runtime checks is more practical than depending on external type packages that may lag behind API changes.
<!-- [/add_learning]

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/src/Locanara.ts:576-592
Timestamp: 2026-02-22T10:24:08.605Z
Learning: In `packages/web/src/Locanara.ts`, the standalone web SDK targets Chrome 138+ which uses delta mode for `promptStreaming`, so cumulative-to-delta normalization is not required. The expo module (`libraries/expo-ondevice-ai`) has auto-detection as a safety net for broader version support.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/example/main.ts:660-674
Timestamp: 2026-02-23T14:32:41.094Z
Learning: In `packages/web/src/Locanara.ts`, the SDK's `chatStreaming` method internally handles both cumulative and delta modes from Chrome's `promptStreaming` API, always yielding delta chunks to consumers. This normalization happens at the SDK level, so example apps and other consumers can safely use simple concatenation (`response += chunk`) without worrying about the streaming mode.

Comment on lines +51 to +52
If you need language detection on mobile, consider using a third-party library like
`react-native-mlkit` or the device's built-in locale detection.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

locale detection is not a replacement for detectLanguage(text).

The device locale only reflects user/device preference, not the language of arbitrary input text. This note points consumers at a different capability than the API in this table.

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

In `@libraries/API_PARITY.md` around lines 51 - 52, The note incorrectly suggests
using device 'locale' as a replacement for the API's language-detection
function; update the text to clearly state that device locale (e.g., "locale")
reflects user/device preferences and is not equivalent to the API's
content-based language detection (e.g., "detectLanguage(text)"); explicitly
recommend third-party libraries (like react-native-mlkit) or sending text to the
server-side detectLanguage(text) endpoint for accurate detection of arbitrary
input, and remove or reword any phrasing that implies locale is a substitute for
detectLanguage(text).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct — locale is not a replacement for detectLanguage. The API_PARITY.md notes this as a known gap. Language detection from text is tracked separately.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@hyochan, understood — thanks for the clarification. Since detectLanguage from text content is a known gap on mobile platforms, would you like me to open a follow-up GitHub issue to formally track adding mobile-native language detection support (or a cross-platform alternative) to the wrapper libraries? That way it stays visible alongside #12.

(ʘ‿ʘ✿)


✏️ Learnings added
Learnt from: hyochan
Repo: hyodotdev/locanara PR: 20
File: libraries/API_PARITY.md:51-52
Timestamp: 2026-03-21T09:12:15.737Z
Learning: In `libraries/API_PARITY.md`, the absence of `detectLanguage(text)` in Expo/React Native/Flutter wrappers is a known, intentional gap. Device locale is explicitly NOT a substitute. Mobile language detection from arbitrary text content is tracked separately as a future work item.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: All three libraries (expo-ondevice-ai, react-native-ondevice-ai, flutter_ondevice_ai) MUST expose identical public APIs

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:61-79
Timestamp: 2026-02-22T08:44:32.446Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the `checkAvailability()` function intentionally uses a lenient fallback in its catch block (`return hasAPI(api)`) because Chrome Built-in AI APIs are evolving and `availability()` can timeout on first load. This prevents features from being incorrectly locked, while actual API failures are still caught and handled by each feature method.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:28-34
Timestamp: 2026-02-23T14:50:03.100Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the cached Chrome Built-in AI instances (cachedSummarizer, cachedLanguageModel, cachedRewriter, cachedWriter, cachedTranslators) intentionally use `any` type instead of `types/dom-chromium-ai` because Chrome Built-in AI APIs are experimental and changing rapidly. Using `any` with runtime checks is more practical than depending on external type packages that may lag behind API changes.
<!-- [/add_learning]

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/src/Locanara.ts:576-592
Timestamp: 2026-02-22T10:24:08.605Z
Learning: In `packages/web/src/Locanara.ts`, the standalone web SDK targets Chrome 138+ which uses delta mode for `promptStreaming`, so cumulative-to-delta normalization is not required. The expo module (`libraries/expo-ondevice-ai`) has auto-detection as a safety net for broader version support.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/README.md:41-41
Timestamp: 2026-02-23T14:32:47.535Z
Learning: The Chrome Built-in AI `LanguageModel.availability()` API can return both `'readily'` and `'available'` depending on the Chrome version. Earlier versions used `'readily'`, while current versions (Chrome 138+) use `'available'`. The README comment in `packages/web/README.md` reflects the behavior in newer Chrome versions.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/example/main.ts:660-674
Timestamp: 2026-02-23T14:32:41.094Z
Learning: In `packages/web/src/Locanara.ts`, the SDK's `chatStreaming` method internally handles both cumulative and delta modes from Chrome's `promptStreaming` API, always yielding delta chunks to consumers. This normalization happens at the SDK level, so example apps and other consumers can safely use simple concatenation (`response += chunk`) without worrying about the streaming mode.

Comment on lines +196 to +206
if (options?.onChunk) {
subscription = (
ExpoOndeviceAiModule as unknown as {
addListener: (
name: string,
listener: (chunk: TextStreamChunk) => void,
) => EventSubscription;
}
).addListener('onSummarizeStreamChunk', (chunk: TextStreamChunk) => {
options.onChunk!(chunk);
});
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

These stream listeners are not request-scoped.

If two summarizeStreaming() / translateStreaming() / rewriteStreaming() calls overlap, every active listener on that event name will receive every chunk. That will mix responses between callers. Please carry a stream/request ID in the emitted chunk or reject concurrent streams per feature.

Also applies to: 235-245, 274-284

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

In `@libraries/expo-ondevice-ai/src/index.ts` around lines 196 - 206, The stream
listeners (see summarizeStreaming, translateStreaming, rewriteStreaming) are
global per-event and will deliver every onSummarizeStreamChunk event to all
subscribers; fix by making streams request-scoped: when starting a stream from
those functions, generate/provide a unique streamId and ensure the native
emitter emits TextStreamChunk objects with that streamId, then in the
addListener callback only invoke options.onChunk when chunk.streamId ===
localStreamId (or alternatively, if you cannot change native emissions, enforce
single-concurrent-stream by refusing to start a new
summarizeStreaming/translateStreaming/rewriteStreaming while a subscription
exists). Also ensure subscriptions are removed on completion/error and propagate
the streamId check to the other event names used in the file.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Valid concern for concurrent calls. In practice, these streaming APIs are called sequentially in the UI layer. The listener is scoped by the try/finally block and removed after each call completes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@hyochan, understood — the try/finally guarantees cleanup after each call (success or error), and sequential usage in the UI layer makes concurrent overlap unlikely. The concern is really about downstream consumers who call these APIs concurrently without knowing about the shared listener, since there's no runtime enforcement.

If you'd like a lightweight safety net without adding a streamId, a simple in-flight guard (one let activeSummarizeStream = false flag per feature) would throw or queue concurrent calls rather than silently mixing chunks. But if the sequential contract is documented or enforced at a higher layer, that's a reasonable call to defer.


✏️ Learnings added
Learnt from: hyochan
Repo: hyodotdev/locanara PR: 20
File: libraries/expo-ondevice-ai/src/index.ts:196-206
Timestamp: 2026-03-21T09:12:22.328Z
Learning: In `libraries/expo-ondevice-ai/src/index.ts`, the streaming APIs (`summarizeStreaming`, `translateStreaming`, `rewriteStreaming`) use a shared global event listener per feature (e.g. `onSummarizeStreamChunk`) scoped only by the `try/finally` block that removes the subscription after each call. Concurrent calls are intentionally avoided at the UI layer rather than enforced at the API level. This matches the same pattern used by `chatStream` and `downloadModel` in the same file. There is no request-scoped `streamId` — the design relies on sequential invocation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/example/main.ts:660-674
Timestamp: 2026-02-23T14:32:41.094Z
Learning: In `packages/web/src/Locanara.ts`, the SDK's `chatStreaming` method internally handles both cumulative and delta modes from Chrome's `promptStreaming` API, always yielding delta chunks to consumers. This normalization happens at the SDK level, so example apps and other consumers can safely use simple concatenation (`response += chunk`) without worrying about the streaming mode.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:32-34
Timestamp: 2026-02-23T14:50:12.628Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the unbounded growth of `cachedTranslators` Map and lack of cleanup for `cachedWriter`/`cachedRewriter` is acceptable for now because users typically translate between only a few language pairs in practice, keeping the cache small. A cleanup mechanism with destroy() and LRU eviction may be added in a future PR if needed.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:28-34
Timestamp: 2026-02-23T14:50:03.100Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the cached Chrome Built-in AI instances (cachedSummarizer, cachedLanguageModel, cachedRewriter, cachedWriter, cachedTranslators) intentionally use `any` type instead of `types/dom-chromium-ai` because Chrome Built-in AI APIs are experimental and changing rapidly. Using `any` with runtime checks is more practical than depending on external type packages that may lag behind API changes.
<!-- [/add_learning]

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/src/Locanara.ts:576-592
Timestamp: 2026-02-22T10:24:08.605Z
Learning: In `packages/web/src/Locanara.ts`, the standalone web SDK targets Chrome 138+ which uses delta mode for `promptStreaming`, so cumulative-to-delta normalization is not required. The expo module (`libraries/expo-ondevice-ai`) has auto-detection as a safety net for broader version support.

Comment on lines +197 to +205
subscription = (
ExpoOndeviceAiModule as unknown as {
addListener: (
name: string,
listener: (chunk: TextStreamChunk) => void,
) => EventSubscription;
}
).addListener('onSummarizeStreamChunk', (chunk: TextStreamChunk) => {
options.onChunk!(chunk);
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd 'ExpoOndeviceAiModule\.(swift|kt)$' libraries/expo-ondevice-ai | while read -r file; do
  echo "== $file =="
  rg -n 'onSummarizeStreamChunk|onTranslateStreamChunk|onRewriteStreamChunk|summarizeStreaming|translateStreaming|rewriteStreaming|describeImage' "$file"
  echo
done

Repository: hyodotdev/locanara

Length of output: 164


🏁 Script executed:

cat -n libraries/expo-ondevice-ai/android/src/main/java/expo/modules/ondeviceai/ExpoOndeviceAiModule.kt

Repository: hyodotdev/locanara

Length of output: 16515


🏁 Script executed:

fd 'HybridOndeviceAi\.swift$' libraries/expo-ondevice-ai

Repository: hyodotdev/locanara

Length of output: 44


🏁 Script executed:

fd 'OndeviceAi\.nitro\.ts$' libraries/expo-ondevice-ai

Repository: hyodotdev/locanara

Length of output: 44


🏁 Script executed:

head -n 320 libraries/expo-ondevice-ai/src/index.ts | tail -n 125

Repository: hyodotdev/locanara

Length of output: 3667


🏁 Script executed:

find libraries/expo-ondevice-ai -type f \( -name "*.swift" -o -name "*.ts" \) | grep -E "(spec|swift|Hybrid)"

Repository: hyodotdev/locanara

Length of output: 297


🏁 Script executed:

rg -l 'summarizeStreaming|translateStreaming|rewriteStreaming|describeImage' libraries/expo-ondevice-ai

Repository: hyodotdev/locanara

Length of output: 216


🏁 Script executed:

cat -n libraries/expo-ondevice-ai/ios/ExpoOndeviceAiModule.swift

Repository: hyodotdev/locanara

Length of output: 10440


🏁 Script executed:

find libraries/expo-ondevice-ai -name "*.nitro.ts" -o -name "*spec*.ts"

Repository: hyodotdev/locanara

Length of output: 44


The wrapper calls native methods and listens to events that are not defined in either the iOS or Android module.

The following methods are missing from both ExpoOndeviceAiModule.swift (iOS) and ExpoOndeviceAiModule.kt (Android):

  • summarizeStreaming()
  • translateStreaming()
  • rewriteStreaming()
  • describeImage()

The following events are missing from both modules:

  • onSummarizeStreamChunk
  • onTranslateStreamChunk
  • onRewriteStreamChunk

Both native modules currently define only onChatStreamChunk and onModelDownloadProgress (line 12 in Swift, line 53 in Kotlin). The wrapper will fail at runtime when attempting to call these undefined methods or subscribe to undefined events.

These methods and events must be added to both native implementations to match the wrapper's expectations, or the wrapper must be revised to use only the methods and events that actually exist in the native modules.

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

In `@libraries/expo-ondevice-ai/src/index.ts` around lines 197 - 205, The JS
wrapper is calling and subscribing to native symbols that don't exist; either
add the missing native APIs to the iOS and Android modules or stop using them in
the wrapper. Implement native methods named summarizeStreaming,
translateStreaming, rewriteStreaming, and describeImage on the native module
(ExpoOndeviceAiModule), and emit events named onSummarizeStreamChunk,
onTranslateStreamChunk, and onRewriteStreamChunk (in addition to the existing
onChatStreamChunk and onModelDownloadProgress), or alternatively remove/replace
calls to summarizeStreaming/translateStreaming/rewriteStreaming/describeImage
and subscriptions to
onSummarizeStreamChunk/onTranslateStreamChunk/onRewriteStreamChunk in index.ts
so the wrapper only uses native methods/events that actually exist.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The native implementations are in progress. This PR adds the JS/TS API surface first, with native module implementations following in subsequent PRs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@hyochan, understood — thanks for the context! Shipping the JS/TS API surface ahead of the native implementations is a reasonable incremental approach.

One suggestion worth considering: to prevent silent runtime failures if anyone calls these new APIs before the native side is ready, you could add a lightweight guard in each stub that throws a descriptive Error (e.g. "summarizeStreaming: native implementation not yet available"). This makes the "not yet implemented" state explicit rather than surfacing a cryptic native call failure. It also serves as a self-documenting hint for anyone exploring the API surface in the meantime.


🧠 Learnings used
Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/example/main.ts:660-674
Timestamp: 2026-02-23T14:32:41.094Z
Learning: In `packages/web/src/Locanara.ts`, the SDK's `chatStreaming` method internally handles both cumulative and delta modes from Chrome's `promptStreaming` API, always yielding delta chunks to consumers. This normalization happens at the SDK level, so example apps and other consumers can safely use simple concatenation (`response += chunk`) without worrying about the streaming mode.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/example/main.ts:660-674
Timestamp: 2026-02-22T10:24:15.711Z
Learning: Chrome 138+ uses delta mode for promptStreaming API, where each chunk contains only the new text (delta) rather than the cumulative response. This means simple concatenation (`response += chunk`) is the correct approach when targeting Chrome 138+ as the minimum requirement.

Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: Update both iOS (`ios/HybridOndeviceAi.swift`) and Android (`android/.../HybridOndeviceAi.kt`) native implementations when modifying Nitro Module API

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:32-34
Timestamp: 2026-02-23T14:50:12.628Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the unbounded growth of `cachedTranslators` Map and lack of cleanup for `cachedWriter`/`cachedRewriter` is acceptable for now because users typically translate between only a few language pairs in practice, keeping the cache small. A cleanup mechanism with destroy() and LRU eviction may be added in a future PR if needed.

Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: Start Nitro Module API changes from the spec file (`src/specs/OndeviceAi.nitro.ts`) before modifying native implementations

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:28-34
Timestamp: 2026-02-23T14:50:03.100Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the cached Chrome Built-in AI instances (cachedSummarizer, cachedLanguageModel, cachedRewriter, cachedWriter, cachedTranslators) intentionally use `any` type instead of `types/dom-chromium-ai` because Chrome Built-in AI APIs are experimental and changing rapidly. Using `any` with runtime checks is more practical than depending on external type packages that may lag behind API changes.
<!-- [/add_learning]

Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: All three libraries (expo-ondevice-ai, react-native-ondevice-ai, flutter_ondevice_ai) MUST expose identical public APIs

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:61-79
Timestamp: 2026-02-22T08:44:32.446Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the `checkAvailability()` function intentionally uses a lenient fallback in its catch block (`return hasAPI(api)`) because Chrome Built-in AI APIs are evolving and `availability()` can timeout on first load. This prevents features from being incorrectly locked, while actual API failures are still caught and handled by each feature method.

Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: Keep API surface identical across all platforms (iOS, Android, Web)

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/src/Locanara.ts:576-592
Timestamp: 2026-02-22T10:24:08.605Z
Learning: In `packages/web/src/Locanara.ts`, the standalone web SDK targets Chrome 138+ which uses delta mode for `promptStreaming`, so cumulative-to-delta normalization is not required. The expo module (`libraries/expo-ondevice-ai`) has auto-detection as a safety net for broader version support.

Comment on lines +209 to +213
export interface NitroTextStreamChunk {
delta: string;
accumulated: string;
isFinal: boolean;
}
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The Nitro streaming contract cannot demultiplex concurrent streams.

NitroTextStreamChunk has no request/stream identifier, and each feature only exposes a global add/remove listener pair. Two in-flight streams of the same feature will be indistinguishable in JS, so chunks can fan out to the wrong consumer. Add a stream ID to the payload or explicitly enforce one active stream per feature.

Also applies to: 301-332

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

In `@libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts` around
lines 209 - 213, The current streaming contract (NitroTextStreamChunk) lacks a
stream identifier so concurrent streams cannot be demultiplexed; update the
contract and listener API to include a unique streamId on NitroTextStreamChunk
(and any equivalent chunk interfaces referenced around the 301-332 region), and
propagate that id through the global add/remove listener pair (or alternatively
change the API to enforce a single active stream per feature by preventing
addListener if a stream is already active). Specifically modify the
NitroTextStreamChunk interface to include streamId and update the corresponding
addListener/removeListener handlers and any emitters/dispatchers to attach and
check streamId so JS consumers can route chunks to the correct in-flight stream.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged. Current design assumes sequential streaming calls. Concurrent stream demultiplexing is a future enhancement if needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@hyochan, understood — thanks for the clarification! Noting this design decision for future reviews.

(´• ω •`) 🐇


✏️ Learnings added
Learnt from: hyochan
Repo: hyodotdev/locanara PR: 20
File: libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts:209-213
Timestamp: 2026-03-21T09:12:15.012Z
Learning: In `libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts`, the streaming contract (`NitroTextStreamChunk` and per-feature add/remove listener pairs for `summarizeStreaming`, `translateStreaming`, `rewriteStreaming`) intentionally assumes sequential streaming calls. Concurrent stream demultiplexing (e.g., adding a `streamId` to the chunk) is deferred as a future enhancement and should not be flagged as a blocking issue in code review.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/example/main.ts:660-674
Timestamp: 2026-02-23T14:32:41.094Z
Learning: In `packages/web/src/Locanara.ts`, the SDK's `chatStreaming` method internally handles both cumulative and delta modes from Chrome's `promptStreaming` API, always yielding delta chunks to consumers. This normalization happens at the SDK level, so example apps and other consumers can safely use simple concatenation (`response += chunk`) without worrying about the streaming mode.

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/example/main.ts:660-674
Timestamp: 2026-02-22T10:24:15.711Z
Learning: Chrome 138+ uses delta mode for promptStreaming API, where each chunk contains only the new text (delta) rather than the cumulative response. This means simple concatenation (`response += chunk`) is the correct approach when targeting Chrome 138+ as the minimum requirement.

Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: Start Nitro Module API changes from the spec file (`src/specs/OndeviceAi.nitro.ts`) before modifying native implementations

Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: Applies to libraries/react-native-ondevice-ai/src/specs/*.nitro.ts : Nitro union types must have 2 or more values

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts:28-34
Timestamp: 2026-02-23T14:50:03.100Z
Learning: In `libraries/expo-ondevice-ai/src/ExpoOndeviceAiModule.web.ts`, the cached Chrome Built-in AI instances (cachedSummarizer, cachedLanguageModel, cachedRewriter, cachedWriter, cachedTranslators) intentionally use `any` type instead of `types/dom-chromium-ai` because Chrome Built-in AI APIs are experimental and changing rapidly. Using `any` with runtime checks is more practical than depending on external type packages that may lag behind API changes.
<!-- [/add_learning]

Learnt from: hyochan
Repo: hyodotdev/locanara PR: 9
File: packages/web/src/Locanara.ts:576-592
Timestamp: 2026-02-22T10:24:08.605Z
Learning: In `packages/web/src/Locanara.ts`, the standalone web SDK targets Chrome 138+ which uses delta mode for `promptStreaming`, so cumulative-to-delta normalization is not required. The expo module (`libraries/expo-ondevice-ai`) has auto-detection as a safety net for broader version support.

Learnt from: CR
Repo: hyodotdev/locanara PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-26T20:59:28.030Z
Learning: Update both iOS (`ios/HybridOndeviceAi.swift`) and Android (`android/.../HybridOndeviceAi.kt`) native implementations when modifying Nitro Module API

…native layer

- Run nitrogen to regenerate Nitro bindings from updated spec
- Add summarizeStreaming/translateStreaming/rewriteStreaming to HybridOndeviceAi.kt and HybridOndeviceAi.swift
- Add describeImage stub (Web SDK only) to both native implementations
- Update mock and add 11 new tests covering all new APIs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to bring wrapper-library API parity with the Web SDK by exposing web-unique capabilities (streaming summarize/translate/rewrite and describeImage) and adding supporting types/spec definitions so React Native and Expo consumers can call these APIs consistently.

Changes:

  • Added shared streaming chunk types and *Streaming option types to wrapper public TypeScript APIs.
  • Extended the React Native Nitro spec to include summarizeStreaming/translateStreaming/rewriteStreaming listener-based streaming APIs and describeImage.
  • Committed new Nitrogen-generated iOS Swift bridge artifacts for new union/struct types.

Reviewed changes

Copilot reviewed 10 out of 216 changed files in this pull request and generated 5 comments.

File Description
libraries/react-native-ondevice-ai/src/types.ts Adds public TS types for streaming chunks/options and describeImage request/response types.
libraries/react-native-ondevice-ai/src/specs/OndeviceAi.nitro.ts Extends the Nitro native contract with streaming variants, stream listeners, and describeImage.
libraries/expo-ondevice-ai/src/types.ts Adds Expo public TS types for streaming chunks/options and describeImage types.
libraries/react-native-ondevice-ai/nitrogen/generated/ios/swift/* Adds generated Swift bridge types needed by the updated Nitro spec.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@hyochan hyochan merged commit 8d7cf47 into main Mar 21, 2026
8 checks passed
@hyochan hyochan deleted the feat/web-sdk-api-parity branch March 21, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎯 feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: expose Web SDK unique APIs in wrapper libraries

2 participants