Skip to content

Conversation

@shubhammalhotra28
Copy link
Contributor

@shubhammalhotra28 shubhammalhotra28 commented Jan 25, 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

This PR adds tool-calling capabilities to the React Native SDK, allowing LLMs to execute external functions via a new C++ and TypeScript integration.

  • Behavior:
    • Adds tool-calling capability to React Native SDK, enabling LLMs to invoke external actions.
    • C++ ToolCallingBridge parses <tool_call> tags from LLM output.
    • TypeScript manages tool registration, execution, and orchestration in RunAnywhere+ToolCalling.ts.
  • Files and Classes:
    • ToolCallingBridge.cpp/hpp: Implements JSON parsing for tool calls.
    • RunAnywhere+ToolCalling.ts: Handles tool registration and execution.
    • HybridRunAnywhereCore.cpp/hpp: Integrates tool-calling into core SDK.
  • Types and Interfaces:
    • Introduces ToolDefinition, ToolCall, ToolResult, and related types in ToolCallingTypes.ts.
    • Updates RunAnywhereCore.nitro.ts to include tool-calling methods.
  • Misc:
    • Updates RunAnywhere.ts and index.ts to export new tool-calling functions.
    • Adds comprehensive logging and error handling for tool execution.

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


Summary by CodeRabbit

  • New Features

    • Llama 3.2 (3B) model added; comprehensive tool-calling support so models can invoke external tools.
    • Built-in demo tools for real-time weather and current time.
  • UI / App

    • New Food and Tools tabs with ToolsScreen and FoodOrderScreen.
    • New components: RestaurantCard, MenuItemCard, AIAgentOverlay (voice AI ordering UI).
  • Data & Services

    • Foursquare restaurant API integration plus rich mock restaurant/menu data and utilities.
  • Docs

    • Added RESTAURANT_API_SETUP.md with API onboarding steps.

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

Greptile Overview

Greptile Summary

This PR adds tool calling support to the React Native SDK, enabling LLMs to execute external functions (API calls, device functions) during text generation. The implementation follows a clean architecture with C++ handling parsing (single source of truth) and TypeScript managing tool registration and execution.

Major Changes:

  • Added ToolCallingBridge.cpp/hpp with custom JSON parser that handles <tool_call> tags, unquoted keys, and alternate formats
  • Implemented RunAnywhere+ToolCalling.ts extension with tool registration, execution loop, and generateWithTools() API
  • Created comprehensive TypeScript types for tool definitions, calls, results, and executors
  • Updated ChatScreen.tsx to demonstrate tool calling with real weather API integration
  • Extended native bridge with parseToolCallFromOutput() method delegating to C++ parser

Key Issues Found:

  • C++ brace counting logic doesn't handle braces inside JSON strings, which could break parsing if arguments contain { or } characters
  • Escaped quote detection has an off-by-one error that could incorrectly identify escaped quotes
  • TypeScript follow-up prompt still includes "Do NOT use tools" instruction but could be cleaner by removing tool definitions entirely

Architecture Highlights:

  • C++ parser handles robust extraction with support for multiple LLM output formats
  • TypeScript executors stay in JS layer for fetch/async API access
  • Clean separation: parsing in C++, orchestration in TypeScript
  • Real-world example with wttr.in weather API demonstrates practical usage

Confidence Score: 3/5

  • Safe to merge with moderate risk - core functionality is solid but JSON parsing has edge case bugs
  • The implementation adds significant new functionality with generally good architecture, but contains critical JSON parsing bugs in the C++ layer that could cause failures with certain tool arguments (braces in strings, escaped quotes). The bugs are localized to parsing logic and don't affect memory safety, but should be fixed to ensure reliability with real-world LLM outputs
  • Pay close attention to sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp - the JSON parsing logic has edge case bugs that need fixing

Important Files Changed

Filename Overview
sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp Implements C++ tool call parser with JSON normalization and tag detection, handling alternate formats and unquoted keys
sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+ToolCalling.ts TypeScript extension providing tool registration, execution, and LLM generation with tool calling support
sdk/runanywhere-react-native/packages/core/src/types/ToolCallingTypes.ts Type definitions for tool calling functionality including parameters, definitions, calls, results, and executors
examples/react-native/RunAnywhereAI/src/screens/ChatScreen.tsx Chat UI updated to demonstrate tool calling with weather API and time tools, integrating with generateWithTools
sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp Added parseToolCallFromOutput method delegating to ToolCallingBridge for parsing LLM output

Sequence Diagram

sequenceDiagram
    participant User
    participant ChatScreen as ChatScreen.tsx
    participant TS as RunAnywhere+ToolCalling.ts
    participant Native as HybridRunAnywhereCore.cpp
    participant Bridge as ToolCallingBridge.cpp
    participant LLM as LLM Component
    participant API as External API (wttr.in)

    Note over ChatScreen: User sends message
    User->>ChatScreen: "What's the weather in Tokyo?"
    ChatScreen->>TS: generateWithTools(prompt, options)
    
    Note over TS: Build system prompt with tools
    TS->>TS: formatToolsForPrompt(tools)
    TS->>LLM: generate(promptWithTools)
    
    Note over LLM: LLM generates response with tool call
    LLM-->>TS: "<tool_call>{\"tool\":\"get_weather\",\"arguments\":{\"location\":\"Tokyo\"}}</tool_call>"
    
    Note over TS: Parse tool call using C++ bridge
    TS->>Native: parseToolCallFromOutput(llmOutput)
    Native->>Bridge: parseToolCall(llmOutput)
    
    Note over Bridge: Extract tool call tags
    Bridge->>Bridge: Find TOOL_CALL_START_TAG
    Bridge->>Bridge: normalizeJson(json)
    Bridge->>Bridge: extractJsonString("tool")
    Bridge->>Bridge: extractJsonObject("arguments")
    
    Bridge-->>Native: JSON result {hasToolCall:true, toolName, argumentsJson, cleanText}
    Native-->>TS: JSON string
    
    Note over TS: Parse and execute tool
    TS->>TS: Parse JSON result
    TS->>TS: executeTool(toolCall)
    TS->>API: fetch(wttr.in/Tokyo)
    API-->>TS: Weather data
    
    Note over TS: Continue with tool result
    TS->>LLM: generate(promptWithToolResult)
    LLM-->>TS: "The weather in Tokyo is 15°C and partly cloudy."
    
    TS-->>ChatScreen: ToolCallingResult {text, toolCalls, toolResults}
    ChatScreen-->>User: Display final response
Loading

jmakwana06 and others added 4 commits January 24, 2026 21:22
- Add ToolCallingBridge in C++ for parsing tool_call tags from LLM output
- Handle edge cases: missing closing tags, unquoted JSON keys
- Add tool registration and prompt formatting in C++ bridge
- TypeScript orchestration layer calls C++ for parsing, handles execution
- Add Llama 3.2 3B model to example app (suitable for tool calling)
- Update ChatScreen with tool calling demo (weather API example)

Architecture:
- C++ handles: parsing, validation, prompt formatting
- TypeScript handles: tool registration (stores executors), execution (needs JS APIs)

Co-Authored-By: jm
- Replace console.log with SDKLogger for consistency
- Use ?? instead of || for maxToolCalls to respect explicit 0
- Parse argumentsJson if it's a string from C++
- Update comments to accurately reflect architecture (C++ parses, TS handles registry)
- Remove toolsUsed field from analytics (not in type)
- Fix doc comment for parseToolCallFromOutput return format
@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 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 end-to-end tool-calling: C++ parsing bridge, TypeScript orchestration (registry, execution, generateWithTools), RunAnywhere public exports, and example app integration (chat tools, tools demo, and a food-ordering UI with new screens/components and services). Also updates build includes and third-party JSON dependency.

Changes

Cohort / File(s) Summary
Example app — Chat & Model
examples/react-native/RunAnywhereAI/App.tsx, examples/react-native/RunAnywhereAI/src/screens/ChatScreen.tsx
Registers a new LlamaCPP model; hooks tool registration into model lifecycle; replaces streaming generation with generateWithTools and registers demo tools (weather, time); logging/analytics adjustments and UI placeholder tweaks.
Example app — Tools demo & Food features
examples/react-native/RunAnywhereAI/src/screens/ToolsScreen.tsx, examples/react-native/RunAnywhereAI/src/screens/FoodOrderScreen.tsx, examples/react-native/RunAnywhereAI/src/navigation/TabNavigator.tsx, examples/react-native/RunAnywhereAI/src/components/food/*, examples/react-native/RunAnywhereAI/src/data/foodData.ts, examples/react-native/RunAnywhereAI/src/services/*, examples/react-native/RunAnywhereAI/src/theme/doordash.ts, examples/react-native/RunAnywhereAI/src/types/index.ts, examples/react-native/RunAnywhereAI/RESTAURANT_API_SETUP.md, examples/react-native/RunAnywhereAI/package.json
Adds Tools and Food tabs/screens; ToolsScreen registers demo tools and shows execution log; FoodOrderScreen implements AI-assisted ordering using tool calls; new UI components (RestaurantCard, MenuItemCard, AIAgentOverlay), mock data, Foursquare service integration, theme, types, and docs; adds async-storage dependency.
C++ Bridge & Core API
sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.hpp, .../ToolCallingBridge.cpp, sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.hpp, .../HybridRunAnywhereCore.cpp
Adds ToolCallingBridge singleton with JSON utilities, prompt formatting, and robust parseToolCall; exposes parseToolCallFromOutput in HybridRunAnywhereCore that delegates to the bridge.
Android / iOS build changes
sdk/runanywhere-react-native/packages/core/android/CMakeLists.txt, sdk/runanywhere-react-native/packages/core/RunAnywhereCore.podspec, .gitignore
Adds nlohmann/json FetchContent for Android, adds cpp/third_party header path to podspec, and ignores prebuilt ios/xcframeworks.
TypeScript Tool-Calling Implementation
sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+ToolCalling.ts, .../index.ts, .../RunAnywhere.ts
Implements tool registry, formatting, executeTool, generateWithTools, continuation API, and re-exports; integrates with native C++ parser with fallback.
Specs & Types
sdk/runanywhere-react-native/packages/core/src/specs/RunAnywhereCore.nitro.ts, sdk/runanywhere-react-native/packages/core/src/types/ToolCallingTypes.ts, sdk/runanywhere-react-native/packages/core/src/types/index.ts
Adds Nitro spec method parseToolCallFromOutput and comprehensive TypeScript types for tool definitions, calls, executors, options, and results.
Misc Example App config
examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcodeproj/project.pbxproj, examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/PrivacyInfo.xcprivacy, examples/react-native/RunAnywhereAI/src/services/index.ts
Minor project/team id updates, privacy plist swaps, and re-export of restaurantApi from services index.
LlamaCPP backend tweak
sdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cpp
Increases n_batch cap used during context initialization from 512 to 2048.

Sequence Diagram(s)

sequenceDiagram
    participant App as React Native App
    participant SDK as RunAnywhere JS Core
    participant CPP as ToolCallingBridge (C++)
    participant LLM as Local LLM
    participant ToolExec as Registered Tool Executor

    App->>SDK: generateWithTools(prompt, {autoExecute:true})
    SDK->>SDK: format system prompt + tool defs
    loop up to maxToolCalls
        SDK->>LLM: generate(prompt)
        LLM-->>SDK: output (may include tool_call tags)
        SDK->>CPP: parseToolCallFromOutput(output)
        CPP-->>SDK: {hasToolCall, toolName, argumentsJson, cleanText}
        alt hasToolCall
            SDK->>ToolExec: executeTool(toolName, args)
            ToolExec-->>SDK: toolResult
            SDK->>SDK: append toolResult, update prompt with result
        else no tool call
            SDK->>SDK: mark generation complete
        end
    end
    SDK-->>App: ToolCallingResult {isComplete, text, toolCalls, toolResults}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

kotlin-sdk, react-native-sdk, react-native-sample

Suggested reviewers

  • sanchitmonga22

Poem

"I hopped through code with whiskers bright,
Tools and bridges in the night,
Weather, time, and menus too,
LLMs call tools — what's old feels new,
A rabbit cheers: release takes flight!" 🐇✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description does not follow the repository's required template. Multiple critical sections are incomplete: Type of Change has no selection, Testing section is entirely unchecked, Platform-Specific Testing is all unchecked, Labels section is all unchecked, and Checklist items are unchecked. Complete the PR description by selecting 'New feature' under Type of Change, checking relevant Testing and Platform-Specific Testing boxes for React Native, selecting 'React Native SDK' and 'React Native Sample' labels, and marking relevant Checklist items. Replace or supplement the template placeholder text with actual implementation details.
Docstring Coverage ⚠️ Warning Docstring coverage is 62.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/tool calling parser improvements' accurately summarizes the PR's main contribution: adding tool-calling capability with parser enhancements to the React Native SDK.

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

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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 everything up to bd0a40e in 2 minutes and 23 seconds. Click for details.
  • Reviewed 1556 lines of code in 14 files
  • Skipped 0 files when reviewing.
  • Skipped posting 7 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-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp:25
  • Draft comment:
    The new ToolCallingBridge implementation adds functions to escape and normalize JSON as well as parse tool call tags. This approach is lightweight but may be brittle for complex JSON (e.g. keys with spaces, nested objects, or edge cases with escaped quotes). Consider using a robust JSON parser library for production if inputs may be complex.
  • 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% This comment is making a general suggestion about using a JSON parser library instead of the custom implementation. The comment acknowledges the approach is "lightweight" and mentions it "may be brittle" for complex cases - this is speculative language ("may be"). The comment doesn't point to a specific bug or required fix, but rather suggests a different architectural approach. According to the rules, I should NOT keep comments that are speculative ("may be an issue"), and I should NOT keep comments unless there is clearly a code change required. This is more of an architectural suggestion/informative comment rather than identifying a concrete problem that needs fixing. The file header even explains why this approach was taken (to avoid dependency on rac_tool_calling.h). The implementation does handle several edge cases (escaped quotes, unquoted keys, nested objects with brace counting). The comment might be valid if there's evidence this will actually fail in production, but without concrete examples of failure cases, it's just a general suggestion. The author clearly made a deliberate choice to implement this custom parser to avoid external dependencies. While the critique notes that edge cases are handled, the comment uses speculative language ("may be brittle") and doesn't identify a specific bug or required change. It's an architectural suggestion about using a different approach entirely, not a concrete issue with the implementation. The rules explicitly state to avoid speculative comments and only comment when a code change is clearly required. This comment should be deleted. It's a speculative architectural suggestion ("may be brittle") rather than identifying a concrete bug or required change. The author deliberately chose this lightweight approach to avoid dependencies, as stated in the file header.
2. sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp:388
  • Draft comment:
    In the parseToolCall() method, the final JSON response is constructed with a callId hardcoded as 0. Considering that the TypeScript layer generates a unique callId when needed, it might be beneficial for consistency to generate a unique callId at the C++ level as well if that becomes useful.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This comment is clearly speculative - it uses phrases like "might be beneficial" and "if that becomes useful." It's not identifying a bug or a clear problem with the current implementation. The comment even acknowledges that TypeScript already handles callId generation, suggesting the current approach is working. According to the rules, I should NOT make speculative comments or suggestions about potential future improvements unless there's a clear issue. This is exactly the type of "nice to have" or "consider this" comment that should be removed. The code is working as intended - it returns a hardcoded 0 for callId, and the TypeScript layer handles unique IDs when needed. Could there be a scenario where having unique callIds at the C++ level is actually necessary for correctness? Maybe there's a bug lurking where multiple tool calls could get confused without unique IDs at this level? The comment itself states that TypeScript generates unique callIds "when needed," which implies the current architecture is intentional and working. If there were an actual bug or issue with callId collisions, the comment would need to provide evidence of that problem. Without such evidence, this remains a speculative suggestion about architecture that may or may not be useful in the future. This comment should be deleted. It's a speculative suggestion about a potential future improvement ("might be beneficial", "if that becomes useful") rather than identifying a clear bug or required code change. The rules explicitly state not to make speculative comments.
3. sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+ToolCalling.ts:111
  • Draft comment:
    The TypeScript layer for tool calling neatly wraps the native parsing function and generates a unique callId using Date.now() if none is provided. Note that using Date.now() may not be sufficiently unique if multiple calls occur in quick succession. Consider using a more robust unique ID generator if necessary.
  • Reason this comment was not posted:
    Comment looked like it was already resolved.
4. sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+ToolCalling.ts:227
  • Draft comment:
    The generateWithTools() function uses a loop with a maximum number of iterations (maxToolCalls) to support tool execution. The prompt concatenation strategy (appending tool result JSON, etc.) is straightforward. Ensure that any prompt formatting edge cases (e.g. unexpected tool output) are handled gracefully in production.
  • Reason this comment was not posted:
    Confidence changes required: 30% <= threshold 50% None
5. sdk/runanywhere-react-native/packages/core/nitrogen/generated/shared/c++/HybridRunAnywhereCoreSpec.cpp:16
  • Draft comment:
    The new hybrid methods for tool calling (registerToolDefinition, unregisterToolDefinition, getRegisteredToolDefinitions, clearToolDefinitions, formatToolsPrompt, parseToolCallFromOutput) are now registered for the native channel. This registration appears consistent with existing patterns. Ensure the TypeScript layer remains the authoritative registry, as noted in comments.
  • Reason this comment was not posted:
    Confidence changes required: 20% <= threshold 50% None
6. sdk/runanywhere-react-native/packages/core/src/types/ToolCallingTypes.ts:15
  • Draft comment:
    The ToolCallingTypes definitions define the interfaces for parameters, tool definitions, and built‐in types (e.g. WeatherData). These types are clear and comprehensive. Validate that the union type for ParameterType covers all required cases.
  • Reason this comment was not posted:
    Confidence changes required: 10% <= threshold 50% None
7. sdk/runanywhere-react-native/packages/core/src/Public/RunAnywhere.ts:604
  • Draft comment:
    Tool Calling methods are re-exported via the main RunAnywhere object. The integration appears complete and consistent with the SDK’s modular structure.
  • Reason this comment was not posted:
    Confidence changes required: 10% <= threshold 50% None

Workflow ID: wflow_VNsVZ1BPnfogIVna

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

- Add alternate format detection for <tool_name {json}> patterns
- Handle cases where small LLMs use tool name as tag instead of <tool_call>
- Use non-streaming generate() to avoid SIGABRT crash
- Support direct arguments format in alternate format

These fixes improve compatibility with smaller models like SmolLM2.
@shubhammalhotra28 shubhammalhotra28 force-pushed the feature/tool-calling-parser-improvements branch from bd0a40e to c417ab4 Compare January 25, 2026 23:21
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +184 to +187
result["hasToolCall"] = false;
result["cleanText"] = llmOutput;
return result.dump();
}
Copy link

Choose a reason for hiding this comment

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

brace counting doesn't handle braces inside strings, which could cause incorrect parsing if JSON values contain { or } characters

Suggested change
result["hasToolCall"] = false;
result["cleanText"] = llmOutput;
return result.dump();
}
while (pos < json.size() && braceCount > 0) {
// Skip strings to avoid counting braces inside string literals
if (json[pos] == '"' && (pos == 0 || json[pos-1] != '\\')) {
pos++;
while (pos < json.size() && (json[pos] != '"' || json[pos-1] == '\\')) {
pos++;
}
if (pos < json.size()) pos++; // Skip closing quote
} else if (json[pos] == '{') {
braceCount++;
pos++;
} else if (json[pos] == '}') {
braceCount--;
pos++;
} else {
pos++;
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp
Line: 184:187

Comment:
brace counting doesn't handle braces inside strings, which could cause incorrect parsing if JSON values contain `{` or `}` characters

```suggestion
    while (pos < json.size() && braceCount > 0) {
        // Skip strings to avoid counting braces inside string literals
        if (json[pos] == '"' && (pos == 0 || json[pos-1] != '\\')) {
            pos++;
            while (pos < json.size() && (json[pos] != '"' || json[pos-1] == '\\')) {
                pos++;
            }
            if (pos < json.size()) pos++; // Skip closing quote
        } else if (json[pos] == '{') {
            braceCount++;
            pos++;
        } else if (json[pos] == '}') {
            braceCount--;
            pos++;
        } else {
            pos++;
        }
    }
```

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

// If no <tool_call> tag, check for alternate format: <tool_name {json}>
// Some smaller models use the tool name as the tag instead of <tool_call>
bool usingAlternateFormat = false;
std::string alternateToolName;
Copy link

Choose a reason for hiding this comment

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

escaped quote detection is incorrect - checking valueEnd == 0 is wrong since valueStart is already past position 0

Suggested change
std::string alternateToolName;
if (normalizedJson[valueEnd] == '"' && (valueEnd == valueStart || normalizedJson[valueEnd - 1] != '\\')) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp
Line: 141:141

Comment:
escaped quote detection is incorrect - checking `valueEnd == 0` is wrong since `valueStart` is already past position 0

```suggestion
        if (normalizedJson[valueEnd] == '"' && (valueEnd == valueStart || normalizedJson[valueEnd - 1] != '\\')) {
```

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

Comment on lines +308 to +320
} else {
cleanText = "";
}

// Build JSON response using nlohmann/json
json result;
result["hasToolCall"] = true;
result["cleanText"] = cleanText;
result["toolName"] = toolName;
result["argumentsJson"] = arguments;
result["callId"] = 0;

return result.dump();
Copy link

Choose a reason for hiding this comment

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

brace counting for JSON extraction doesn't handle braces inside strings (same issue as extractJsonObject), and the loop will fail if JSON starts at position other than the first {

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp
Line: 308:320

Comment:
brace counting for JSON extraction doesn't handle braces inside strings (same issue as `extractJsonObject`), and the loop will fail if JSON starts at position other than the first `{`

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

Comment on lines +285 to +290
toolCalls: allToolCalls,
toolResults: [],
isComplete: false,
};
}

Copy link

Choose a reason for hiding this comment

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

prompt instructs LLM not to use tools with "Do NOT use any tools", but the tools are still available in the prompt context which may confuse the model - consider not including tool definitions at all in follow-up prompts

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+ToolCalling.ts
Line: 285:290

Comment:
prompt instructs LLM not to use tools with "Do NOT use any tools", but the tools are still available in the prompt context which may confuse the model - consider not including tool definitions at all in follow-up prompts

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

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

Comment on lines +103 to +108
let args: Record<string, unknown> = {};
if (result.argumentsJson) {
args = typeof result.argumentsJson === 'string'
? JSON.parse(result.argumentsJson)
: result.argumentsJson;
}
Copy link

Choose a reason for hiding this comment

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

parsing argumentsJson twice - once checking if it's a string, then parsing - could fail if argumentsJson is already an object but contains invalid structure

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+ToolCalling.ts
Line: 103:108

Comment:
parsing `argumentsJson` twice - once checking if it's a string, then parsing - could fail if `argumentsJson` is already an object but contains invalid structure

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

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 c417ab4 in 2 minutes and 3 seconds. Click for details.
  • Reviewed 158 lines of code in 2 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-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp:159
  • Draft comment:
    The alternate format parsing block uses a length check (tagName.length() > 2) to filter out common HTML tags. Consider reviewing this check—valid tool names with 1–2 characters might be skipped unintentionally.
  • 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% The comment starts with "Consider reviewing this check" which is a soft suggestion rather than identifying a definite issue. It's speculative - it says valid tool names "might be skipped" but doesn't provide evidence that this is actually a problem. The comment doesn't suggest a specific fix or demonstrate that 1-2 character tool names are actually used or needed. According to the rules, I should not keep speculative comments like "If X, then Y is an issue" - only comments about definite issues. This comment is asking the author to "consider reviewing" which is similar to asking them to "verify" or "ensure" - types of comments the rules say are not useful. The comment doesn't provide strong evidence that this is wrong. The length check might be intentional to avoid false positives with HTML tags. Without knowing the actual tool names that will be used in practice, it's hard to say if this is a real problem. The comment could be valid if 1-2 character tool names are common in the domain. While the concern about 1-2 character tool names could theoretically be valid, the comment is purely speculative and doesn't demonstrate that this is actually a problem. It's asking the author to "consider reviewing" without providing evidence that the current implementation is wrong. This falls under the category of comments that ask the author to double-check or verify things, which the rules explicitly say to avoid. This comment should be deleted. It's speculative ("might be skipped"), uses soft language ("Consider reviewing"), and doesn't provide strong evidence of an actual issue. It's asking the author to verify/review rather than pointing to a definite problem that needs fixing.
2. sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp:293
  • Draft comment:
    In the absence of an explicit closing tag, the code recalculates 'tagEnd' by matching braces while the 'hasClosingTag' flag remains based on the initial search. Confirm that using the original 'hasClosingTag' flag when extracting the clean text is intentional and does not lead to unexpected results.
  • Reason this comment was not posted:
    Comment did not seem useful. Confidence is useful = 0% <= threshold 50% The comment is asking the author to confirm their intention regarding the use of the 'hasClosingTag' flag. This violates the rule against asking the author to confirm their intention or ensure behavior is intended. The comment does not provide a specific suggestion or point out a clear issue, making it unapproved.
3. sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+ToolCalling.ts:257
  • Draft comment:
    The update switches from using a streaming API (generateStream) to a non‐streaming generate function to avoid crashes. Verify that this change does not adversely affect scenarios where incremental output is important.
  • Reason this comment was not posted:
    Comment did not seem useful. Confidence is useful = 0% <= threshold 50% The comment asks the PR author to verify that the change does not adversely affect scenarios where incremental output is important. This falls under the rule of not asking the author to ensure behavior is intended or to double-check things. Therefore, this comment should be removed.

Workflow ID: wflow_s47Kba4KhKy4YF7X

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

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

🤖 Fix all issues with AI agents
In
`@sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp`:
- Around line 155-196: The brace-counting in
ToolCallingBridge::extractJsonObject fails when braces appear inside JSON
strings; update the loop that advances pos and adjusts braceCount to track
whether we're inside a double-quoted string (e.g., bool inString) and ignore '{'
and '}' while inString is true, toggling inString only on unescaped '"' (handle
escapes by checking preceding backslashes parity or escape flag), and keep
existing logic for escaping characters so quoted braces don't affect matching;
ensure pos still advances correctly and outValue is taken from valueStart to pos
once braceCount hits zero.
🧹 Nitpick comments (2)
sdk/runanywhere-react-native/packages/core/src/types/ToolCallingTypes.ts (1)

177-207: Consider whether built-in tool data types belong in the core types module.

WeatherData, SearchResult, and SearchResponse are example/domain-specific types. While useful for demos, they may be better placed in an examples or extensions module to keep core types focused on the tool-calling protocol itself.

sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp (1)

2573-2606: Stub methods are acceptable given the architecture, but consider adding clarifying comments.

The registerToolDefinition, unregisterToolDefinition, getRegisteredToolDefinitions, clearToolDefinitions, and formatToolsPrompt methods are stubs since tool definitions are stored in TypeScript. This is consistent with the documented architecture where C++ handles parsing while TypeScript handles registry and execution.

However, the current comments are brief. Consider adding clearer documentation noting these are intentional no-ops that exist for interface compliance.

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 1105b10 in 2 minutes and 23 seconds. Click for details.
  • Reviewed 896 lines of code in 5 files
  • Skipped 1 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. examples/react-native/RunAnywhereAI/RESTAURANT_API_SETUP.md:16
  • Draft comment:
    Instructions are clear. Consider adding a brief note that the provided API key is only needed for real restaurant data demos and that leaving it empty defaults to mock data.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None
2. examples/react-native/RunAnywhereAI/src/screens/FoodOrderScreen.tsx:392
  • Draft comment:
    The code differentiates between 'toolResults' and 'toolCalls'. For clarity and maintainability, consider consolidating response handling so that the UI logic doesn’t have to check two different fields.
  • 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% The comment is suggesting a refactor to "consolidate response handling" but doesn't provide specific guidance on how to do this. The code change appears intentional - the developer added logic to check toolResults first (which has actual results) and falls back to toolCalls (which just has call info). The comment in the code explains this distinction. The suggestion to "consolidate" is vague - it's not clear what the better approach would be, and the current implementation seems to handle a real API structure difference. This feels like a code quality suggestion that isn't actionable without more context about the API contract. The comment doesn't point to a bug or clear issue, just suggests a refactor for "clarity and maintainability" without explaining what's unclear or how to improve it. Maybe the API really does return both fields inconsistently and this defensive programming is necessary. Perhaps there's a valid reason for checking both fields that I'm not seeing from this file alone. Even if there's a valid reason, the comment doesn't provide actionable guidance. It just says "consider consolidating" without explaining how or why. The code already has a comment explaining the distinction between the two fields. Without seeing the API contract or other files, I can't determine if this is a real issue or just a stylistic preference. This comment is too vague and not actionable. It suggests a refactor without explaining what's wrong with the current approach or how to improve it. The code change appears intentional with an explanatory comment. Without strong evidence that this is incorrect, I should delete this comment.
3. examples/react-native/RunAnywhereAI/src/screens/FoodOrderScreen.tsx:405
  • Draft comment:
    The final response is derived using 'result.text || result.finalResponse'. Consider standardizing the response field from the LLM to reduce conditional logic in the UI.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None
4. examples/react-native/RunAnywhereAI/src/services/restaurantApi.ts:28
  • Draft comment:
    Using 'process?.env?.FOURSQUARE_API_KEY' for configuration in React Native may not work as expected. Consider using a dedicated configuration solution (e.g. react-native-config) to manage API keys securely.
  • 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 suggesting a refactor to use react-native-config instead of process.env. However, the author clearly knows what they're doing - they have a @ts-ignore comment and explicitly mention this is for "CI/production". The comment is speculative ("may not work as expected") rather than definitive. The code has proper fallbacks and the author seems aware of the limitations. This feels like a "nice to have" suggestion rather than a clear bug or issue. The rules say not to make speculative comments and to only comment if there's clearly a code change required. This is more of a suggestion for a different approach rather than fixing a bug. The comment might be valid if process.env truly doesn't work in React Native environments. Perhaps the author is making a mistake and this code won't actually work in production. The security concern about API keys is also legitimate. While the concern might be valid, the comment is speculative ("may not work") rather than definitive. The author has already acknowledged the limitation with the @ts-ignore comment and the comment explaining it's for CI/production. Without strong evidence that this is definitely broken, and given that the author seems aware of the tradeoffs, this is more of a suggestion than a required fix. The rules explicitly say not to make speculative comments. This comment should be deleted. It's speculative ("may not work as expected"), suggests a refactor rather than fixing a clear bug, and the author has already acknowledged the limitation. The rules say not to make speculative comments and only comment when there's clearly a code change required.
5. sdk/runanywhere-commons/src/backends/llamacpp/llamacpp_backend.cpp:232
  • Draft comment:
    The batch size is increased to min(context_size_, 2048) for tool-calling prompts. Ensure that this value works optimally across different model sizes and devices, as higher batch sizes could impact performance on lower-end hardware.
  • Reason this comment was not posted:
    Comment did not seem useful. Confidence is useful = 0% <= threshold 50% The comment is asking the PR author to ensure that the batch size works optimally across different model sizes and devices. This falls under the rule of not asking the author to ensure behavior is intended or tested. The comment does not provide a specific suggestion or point out a specific issue with the code.

Workflow ID: wflow_QZHsIHw9DIGrRR7O

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

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: 11

Caution

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

⚠️ Outside diff range comments (1)
examples/react-native/RunAnywhereAI/ios/RunAnywhereAI.xcodeproj/project.pbxproj (1)

479-509: Avoid hard‑coding the signing team ID in the shared project.

This change will force all users/CI to sign with L86FH3K93L, which commonly breaks local builds or downstream forks. Consider moving DEVELOPMENT_TEAM into an xcconfig or documenting an expected override (or leaving it unset) so developers can supply their own team ID.

🤖 Fix all issues with AI agents
In `@examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/PrivacyInfo.xcprivacy`:
- Around line 7-21: Remove the unused privacy API declarations by deleting the
<dict> entries for NSPrivacyAccessedAPICategoryUserDefaults and
NSPrivacyAccessedAPICategorySystemBootTime from PrivacyInfo.xcprivacy, leaving
only the NSPrivacyAccessedAPICategoryFileTimestamp declaration which corresponds
to the actual usage (FileManager.attributesOfItem(atPath:)); ensure the
remaining plist structure (keys like NSPrivacyAccessedAPIType and
NSPrivacyAccessedAPITypeReasons) stays valid and well-formed after removal.

In `@examples/react-native/RunAnywhereAI/src/components/food/MenuItemCard.tsx`:
- Around line 64-72: Remove the DOM-style e.stopPropagation() from the add
button's onPress handler in TouchableOpacity (the handler that calls
onAddToCart(item)); instead, make the parent's right-side container explicitly
allow inner touchables by adding pointerEvents="box-none" to the parent right
section (or move the add button out of the parent Pressable) so nested presses
don't rely on stopPropagation; update the TouchableOpacity onPress to simply
call onAddToCart(item) and remove the event parameter reference.

In `@examples/react-native/RunAnywhereAI/src/data/foodData.ts`:
- Around line 379-406: The searchRestaurants function accepts a
filters.maxDeliveryTime parameter but never uses it; either remove it from the
filters signature or apply it in the filter callback. To fix, inside
searchRestaurants (the RESTAURANTS.filter callback) add a check similar to the
existing maxDeliveryFee check that returns false when the restaurant's delivery
time exceeds filters.maxDeliveryTime (e.g., compare filters.maxDeliveryTime to
the restaurant's delivery time field such as r.deliveryTime or
r.estimatedDeliveryTime — adjust to the actual property name used in the
Restaurant objects). If you prefer to keep the signature unchanged, implement
this check; otherwise remove maxDeliveryTime from the filters type and all
callers.

In `@examples/react-native/RunAnywhereAI/src/screens/FoodOrderScreen.tsx`:
- Around line 501-525: The setTimeout in addAgentStep causes a race by updating
state later and possibly marking newly added steps complete; remove the
setTimeout and perform a single functional update in setAgentSteps that reads
the current prev once, maps its items to set isComplete:true for only the
existing steps, then appends the new step (keeping its isComplete flag). Update
the addAgentStep implementation (referencing addAgentStep, setAgentSteps,
AgentStep, isComplete) to synchronously mark only prior steps complete by
operating on prev inside one setAgentSteps call instead of delayed updates.
- Around line 131-137: The useFocusEffect callback currently has an empty
dependency array causing registerFoodTools to capture a stale cartItems via the
view_cart tool closure; update the hook so registerFoodTools runs with
up-to-date cart state—either add the real dependencies (e.g., cartItems) to the
useCallback/useFocusEffect so tools are re-registered when cart changes, or
change registerFoodTools/view_cart to read cartItems from a mutable ref (e.g.,
cartRef) that you update on cart change; ensure checkModelStatus and
registerFoodTools references in the useFocusEffect match the chosen approach to
avoid stale closures.
- Around line 446-499: The switch case for toolCall.toolName declares local
variables (isRealApi, sourceLabel, restaurants, topRestaurants) inside case
'search_restaurants' which can be hoisted/accessible across cases; fix by
wrapping that case in a block ({ ... }) and move the declarations inside the
block so they are block-scoped; apply the same pattern to any other cases that
declare variables (e.g., the logic in 'get_menu', 'add_to_cart', 'view_cart',
'place_order' where result-derived constants like items/total/quantity are used)
to ensure each case’s const/let bindings are contained.

In `@examples/react-native/RunAnywhereAI/src/screens/ToolsScreen.tsx`:
- Around line 364-384: handleModelSelect currently hardcodes the framework to
LLMFramework.LlamaCpp when calling setLoadedModel; change it to use the selected
model's actual framework (e.g., model.framework or map model.type to
LLMFramework) so the loadedModel reflects the real framework, and provide a
sensible default fallback if model.framework is missing; update references in
handleModelSelect, SDKModelInfo, and setLoadedModel usage to ensure types align.

In `@examples/react-native/RunAnywhereAI/src/services/restaurantApi.ts`:
- Around line 446-479: The cuisineMap object contains a duplicate 'coffee' key;
remove the earlier duplicate entry (or consolidate them) so only one 'coffee':
'coffee shop' mapping remains; locate the cuisineMap definition in
restaurantApi.ts and delete the redundant 'coffee' entry to avoid confusion and
ensure the map is clean.

In `@sdk/runanywhere-react-native/packages/core/android/CMakeLists.txt`:
- Around line 8-19: Remove the FetchContent usage that pulls nlohmann_json (the
include(FetchContent), FetchContent_Declare(...) for nlohmann_json and
FetchContent_MakeAvailable(nlohmann_json)) and instead rely on the vendored
header already in the repo (../cpp/third_party/nlohmann/json.hpp); update or
leave the existing include path logic (the include path referenced later in the
file) so targets that need nlohmann/json include the vendored headers without
performing a network fetch during CMake configure.

In
`@sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp`:
- Around line 53-66: In ToolCallingBridge.cpp inside the loop that iterates
jsonStr (the block toggling inString when encountering '"' currently using
jsonStr[i-1] != '\\'), replace the simple previous-char check with a parity
check of consecutive backslashes before the quote: count how many consecutive
'\\' characters immediately preceding index i and only treat the quote as
escaped if that count is odd; otherwise toggle inString and append the quote to
result. This change touches the for loop handling jsonStr, the inString boolean
logic, and uses jsonStr and result to preserve behavior.

In
`@sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere`+ToolCalling.ts:
- Around line 346-362: continueWithToolResult currently decrements
options.maxToolCalls and can pass zero/negative into generateWithTools causing
its tool loop to skip and return incomplete results; clamp the computed
maxToolCalls to a minimum of 1 before calling generateWithTools (e.g., compute
newMax = Math.max((options?.maxToolCalls ?? 5) - 1, 1)) and pass that in the
options spread so generateWithTools always has at least one allowed tool call;
update references in continueWithToolResult to use this clamped value.
🧹 Nitpick comments (19)
examples/react-native/RunAnywhereAI/src/components/food/RestaurantCard.tsx (1)

5-15: Remove unused Image import.

Image is imported from react-native but never used in the component. The restaurant image is rendered as an emoji placeholder instead.

🧹 Proposed fix
 import {
   View,
   Text,
   StyleSheet,
   TouchableOpacity,
-  Image,
 } from 'react-native';
sdk/runanywhere-react-native/packages/core/src/types/ToolCallingTypes.ts (1)

36-37: Consider expanding enum type to support non-string values.

The enum property is typed as string[], but ParameterType includes 'number' and 'boolean'. If you need enum-like constraints for numeric or boolean parameters, this typing would be insufficient.

💡 Potential enhancement
   /** Allowed values (for enum-like parameters) */
-  enum?: string[];
+  enum?: (string | number | boolean)[];
examples/react-native/RunAnywhereAI/src/components/food/AIAgentOverlay.tsx (3)

23-23: Use useWindowDimensions hook instead of static Dimensions.get().

Dimensions.get('window') is captured at module load time and won't update on orientation changes or split-screen scenarios. React Native's useWindowDimensions hook provides reactive dimensions.

♻️ Proposed fix
-import { Dimensions, ... } from 'react-native';
+import { useWindowDimensions, ... } from 'react-native';

-const { height: SCREEN_HEIGHT } = Dimensions.get('window');

 export const AIAgentOverlay: React.FC<AIAgentOverlayProps> = ({ ... }) => {
+  const { height: screenHeight } = useWindowDimensions();
   const slideAnim = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
   // Update animation references to use screenHeight

Note: You'll need to handle the initial Animated.Value carefully since screenHeight comes from a hook. Consider initializing with a large default value or using Animated.Value with setValue in a layout effect.


102-109: Clear timeout on effect cleanup to avoid potential memory leaks.

The setTimeout should be cleared if the component unmounts or steps.length changes before the timeout fires.

🧹 Proposed fix
   useEffect(() => {
     // Auto-scroll to bottom when new steps are added
+    let timeoutId: NodeJS.Timeout;
     if (scrollViewRef.current && steps.length > 0) {
-      setTimeout(() => {
+      timeoutId = setTimeout(() => {
         scrollViewRef.current?.scrollToEnd({ animated: true });
       }, 100);
     }
+    return () => {
+      if (timeoutId) clearTimeout(timeoutId);
+    };
   }, [steps.length]);

196-204: Consider using a stable key instead of array index.

If cartItems can be reordered or items removed, using the index as the key may cause incorrect reconciliation. Consider using a composite key.

💡 Suggested improvement
-              {cartItems.map((item, index) => (
-                <View key={index} style={styles.orderItem}>
+              {cartItems.map((item) => (
+                <View key={`${item.restaurantId}-${item.menuItem.id}`} style={styles.orderItem}>
examples/react-native/RunAnywhereAI/src/services/restaurantApi.ts (2)

15-34: Hardcoded API key is a security risk.

While this is example/demo code, hardcoding API keys (even empty ones with instructions to fill in) can lead to accidental commits of real keys. Consider using only environment variables or a separate config file that's gitignored.

Also, process.env access in React Native typically requires additional setup (e.g., react-native-dotenv or react-native-config). The @ts-ignore comment suggests this may not work as expected.

Consider using react-native-config
-// Option 1: Set directly (for quick testing)
-// Get your FREE key at: https://foursquare.com/developers/signup
-const FOURSQUARE_API_KEY = '';
-
-// Option 2: Will also check for environment variable
-const getApiKey = (): string => {
-  // Check hardcoded key first
-  if (FOURSQUARE_API_KEY) {
-    return FOURSQUARE_API_KEY;
-  }
-  
-  // Try environment variable (for CI/production)
-  // `@ts-ignore` - process.env might not be typed
-  const envKey = process?.env?.FOURSQUARE_API_KEY;
-  if (envKey) {
-    return envKey;
-  }
-  
-  return '';
-};
+// Use react-native-config for environment variables
+// npm install react-native-config
+// Add FOURSQUARE_API_KEY to your .env file
+import Config from 'react-native-config';
+
+const getApiKey = (): string => {
+  return Config.FOURSQUARE_API_KEY || '';
+};

238-251: Redundant null check after getCurrentLocation.

getCurrentLocation() always resolves to a location (either device location or demo fallback), so the if (location) check and the subsequent default to San Francisco coordinates are unreachable.

Simplify location handling
     // Get location
     let { latitude, longitude } = options;
     if (!latitude || !longitude) {
       const location = await getCurrentLocation();
-      if (location) {
-        latitude = location.latitude;
-        longitude = location.longitude;
-      } else {
-        // Default to San Francisco
-        latitude = 37.7749;
-        longitude = -122.4194;
-      }
+      latitude = location.latitude;
+      longitude = location.longitude;
     }
examples/react-native/RunAnywhereAI/RESTAURANT_API_SETUP.md (3)

9-9: Consider using Markdown link syntax for URLs.

Bare URLs work but using proper Markdown links improves readability and is more portable.

Use Markdown link format
-1. Go to: https://foursquare.com/developers/signup
+1. Go to: [Foursquare Developer Signup](https://foursquare.com/developers/signup)

Based on static analysis hints (markdownlint).


67-67: Use Markdown link syntax.

Fix bare URL
-Check Foursquare docs: https://docs.foursquare.com/developer/reference/place-search
+Check [Foursquare docs](https://docs.foursquare.com/developer/reference/place-search)

Based on static analysis hints (markdownlint).


40-45: Minor formatting improvement.

The bullet list formatting could be cleaner for the "No credit card required" line.

Fix list formatting
 ## Free Tier Limits
 
 - **10,000 API calls/month** - More than enough for demos
 - No credit card required
-- Reset monthly
+- Resets monthly
examples/react-native/RunAnywhereAI/src/screens/FoodOrderScreen.tsx (2)

575-593: Demo query strings contain content that may not be appropriate for all audiences.

Some demo queries reference alcohol ("strongest drinks nearby 🍺") and casual language that might not align with professional demo expectations depending on the target audience.


60-100: Tool definitions are duplicated between FOOD_TOOLS constant and registerFoodTools function.

The tool metadata (name, description, parameters) is defined in FOOD_TOOLS (lines 60-100) and then redefined when calling RunAnywhere.registerTool (lines 154-360). This duplication could lead to inconsistencies if one is updated but not the other.

♻️ Consider using FOOD_TOOLS directly
   RunAnywhere.registerTool(
-    {
-      name: 'search_restaurants',
-      description: 'Search for restaurants by cuisine type, name, or food item',
-      parameters: FOOD_TOOLS[0].parameters,
-    },
+    FOOD_TOOLS[0],
     async (args) => {
       // handler implementation
     }
   );

This ensures the tool definition stays consistent.

Also applies to: 154-360

examples/react-native/RunAnywhereAI/src/screens/ToolsScreen.tsx (3)

211-211: Use substring instead of deprecated substr.

String.prototype.substr is deprecated. Use substring or slice instead.

🔧 Proposed fix
-        id: `step_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+        id: `step_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,

161-164: Tools registered on mount won't reflect changes if navigating away and back.

registerDemoTools() is called in useEffect on mount, but useFocusEffect only calls refreshToolsList() and checkCurrentModel(). If another screen clears tools (e.g., RunAnywhere.clearTools()), this screen won't re-register them on focus.

Consider calling registerDemoTools() in useFocusEffect as well, similar to FoodOrderScreen.

♻️ Consistent with FoodOrderScreen pattern
   useFocusEffect(
     useCallback(() => {
+      registerDemoTools();
       refreshToolsList();
       checkCurrentModel();
     }, [])
   );

91-117: Weather API call lacks timeout, could hang indefinitely.

The fetch call to wttr.in doesn't have a timeout. If the service is slow or unresponsive, the tool execution will hang.

♻️ Add AbortController timeout
       try {
+        const controller = new AbortController();
+        const timeoutId = setTimeout(() => controller.abort(), 10000);
+        
         const url = `https://wttr.in/${encodeURIComponent(location)}?format=j1`;
-        const response = await fetch(url);
+        const response = await fetch(url, { signal: controller.signal });
+        clearTimeout(timeoutId);

         if (!response.ok) {
           return { error: `Weather API error: ${response.status}` };
         }
sdk/runanywhere-react-native/packages/core/cpp/HybridRunAnywhereCore.cpp (1)

2588-2596: Consider adding error handling for ToolCallingBridge parsing failures.

The current implementation assumes parseToolCall always succeeds. If ToolCallingBridge::shared().parseToolCall() throws or returns invalid JSON, this could propagate unexpected errors to the TypeScript layer.

Based on PR objectives mentioning "C++ JSON parsing has edge cases (brace counting inside JSON strings and escaped-quote detection off-by-one) that may break parsing for some LLM outputs," consider wrapping in try-catch.

♻️ Add defensive error handling
 std::shared_ptr<Promise<std::string>> HybridRunAnywhereCore::parseToolCallFromOutput(const std::string& llmOutput) {
     return Promise<std::string>::async([llmOutput]() -> std::string {
         LOGD("parseToolCallFromOutput: input length=%zu", llmOutput.length());
 
-        // Use ToolCallingBridge for parsing - single source of truth
-        // This ensures consistent <tool_call> tag parsing across all platforms
-        return ::runanywhere::bridges::ToolCallingBridge::shared().parseToolCall(llmOutput);
+        try {
+            // Use ToolCallingBridge for parsing - single source of truth
+            // This ensures consistent <tool_call> tag parsing across all platforms
+            return ::runanywhere::bridges::ToolCallingBridge::shared().parseToolCall(llmOutput);
+        } catch (const std::exception& e) {
+            LOGE("parseToolCallFromOutput failed: %s", e.what());
+            // Return a well-formed "no tool call" response on error
+            return "{\"hasToolCall\":false,\"cleanText\":\"\",\"error\":\"" + 
+                   std::string(e.what()) + "\"}";
+        }
     });
 }
sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.hpp (1)

32-41: ToolCallParseResult struct is defined but unused.

The parseToolCall method returns std::string (JSON), not ToolCallParseResult. This struct appears to be dead code. Consider either:

  1. Removing the struct if it's not needed
  2. Using it internally and serializing to JSON at the boundary
sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+ToolCalling.ts (2)

47-53: Consider warning on duplicate tool registration.

Registering a tool with an existing name silently overwrites it. This could lead to subtle bugs if tools are accidentally registered twice with different executors.

♻️ Suggested enhancement
 export function registerTool(
   definition: ToolDefinition,
   executor: ToolExecutor
 ): void {
+  if (registeredTools.has(definition.name)) {
+    logger.warning(`Overwriting existing tool: ${definition.name}`);
+  }
   logger.debug(`Registering tool: ${definition.name}`);
   registeredTools.set(definition.name, { definition, executor });
 }

242-246: conversationHistory is built but never used.

The array accumulates entries throughout the function but is never read or returned. If it's intended for debugging or future use, consider documenting that. Otherwise, remove it to reduce memory usage.

♻️ Suggested fix
-  // Build conversation history for context preservation
-  const conversationHistory: Array<{ role: string; content: string }> = [];
-
   // Initial prompt with system context
   let fullPrompt = systemPrompt ? `${systemPrompt}\n\nUser: ${prompt}` : prompt;
-  conversationHistory.push({ role: 'user', content: prompt });

And remove subsequent pushes to conversationHistory at lines 274, 303, 304.

Comment on lines 7 to 21
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
<string>CA92.1</string>
</array>
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find iOS source files in the project
echo "=== Finding iOS source files ==="
find examples/react-native/RunAnywhereAI/ios -type f \( -name "*.swift" -o -name "*.m" -o -name "*.mm" -o -name "*.h" \) | head -20

echo -e "\n=== Looking for UserDefaults usage ==="
rg -n --type=swift --type=objc --type=mm '\bUserDefaults\b|\bNSUserDefaults\b' examples/react-native/RunAnywhereAI/ios -C2

echo -e "\n=== Looking for file timestamp access patterns ==="
rg -n --type=swift --type=objc --type=mm -E 'FileManager|attributesOfItemAtPath|contentModificationDateKey|NSFileModificationDate|stat\(|fstat\(|getattrlist' examples/react-native/RunAnywhereAI/ios -C2

echo -e "\n=== Examining PrivacyInfo.xcprivacy file ==="
cat examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/PrivacyInfo.xcprivacy

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1695


🏁 Script executed:

#!/bin/bash
echo "=== Examining NativeAudioModule.swift ==="
cat examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/NativeAudioModule.swift

echo -e "\n=== Examining NativeAudioModule.m ==="
cat examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/NativeAudioModule.m

echo -e "\n=== Examining AppDelegate.swift ==="
cat examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/AppDelegate.swift

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 13260


Remove unused privacy API declarations: UserDefaults and SystemBootTime.

The codebase only uses FileManager.attributesOfItem(atPath:) for file timestamp access, which correctly matches the NSPrivacyAccessedAPICategoryFileTimestamp declaration. However, the NSPrivacyAccessedAPICategoryUserDefaults and NSPrivacyAccessedAPICategorySystemBootTime entries have no corresponding API usage in the code. Declaring unused APIs can trigger App Store review rejection. Remove these two unused entries and keep only the FileTimestamp declaration.

🤖 Prompt for AI Agents
In `@examples/react-native/RunAnywhereAI/ios/RunAnywhereAI/PrivacyInfo.xcprivacy`
around lines 7 - 21, Remove the unused privacy API declarations by deleting the
<dict> entries for NSPrivacyAccessedAPICategoryUserDefaults and
NSPrivacyAccessedAPICategorySystemBootTime from PrivacyInfo.xcprivacy, leaving
only the NSPrivacyAccessedAPICategoryFileTimestamp declaration which corresponds
to the actual usage (FileManager.attributesOfItem(atPath:)); ensure the
remaining plist structure (keys like NSPrivacyAccessedAPIType and
NSPrivacyAccessedAPITypeReasons) stays valid and well-formed after removal.

Comment on lines +64 to +72
<TouchableOpacity
style={styles.addButton}
onPress={(e) => {
e.stopPropagation();
onAddToCart(item);
}}
>
<Icon name="add" size={20} color={DoorDashColors.textWhite} />
</TouchableOpacity>
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for stopPropagation usage in the codebase
rg -n "stopPropagation" --type ts --type tsx

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 101


🌐 Web query:

React Native stopPropagation TouchableOpacity event handling

💡 Result:

React Native touchables (TouchableOpacity, Pressable, etc.) don’t use DOM-style event bubbling, and onPress does not give you a standard event object where stopPropagation() works. Instead, touches are coordinated by the Gesture Responder System. [1][2]

Common ways to prevent a “parent onPress” from firing when a child is pressed

  1. Don’t nest press handlers (best when possible)
    Restructure so only one element is “pressable”, and handle intent in that handler.

  2. Use pointerEvents on the parent container (so the parent doesn’t become the touch target)
    Often used when a parent wrapper is intercepting touches but you want children to remain clickable: set the parent to pointerEvents="box-none" (parent ignores touches; children can still receive them). [3]

  3. Use the Responder capture phase to intercept touches
    If a parent needs to “win” (or control) touch handling, use capture handlers like onStartShouldSetResponderCapture, which run before the normal bubbling phase and can prevent children from becoming responder. [1]

Minimal example (parent blocks child from becoming responder):

<View
  onStartShouldSetResponderCapture={() => true}
  onResponderRelease={() => { /* parent handled */ }}
>
  <TouchableOpacity onPress={() => { /* won't fire */ }}>
    <Text>Child</Text>
  </TouchableOpacity>
</View>

Sources: React Native Gesture Responder System (capture vs bubbling) [1], Pressable docs (press interaction model) [2], common pointerEvents="box-none" approach [3].

[1] (reactnative.dev) [2] (reactnative.dev) [3] (stackoverflow.com)

Citations:


🏁 Script executed:

#!/bin/bash
# Search for stopPropagation in tsx and ts files
rg "stopPropagation" --glob "*.tsx" --glob "*.ts"

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 181


🏁 Script executed:

#!/bin/bash
# Read the MenuItemCard component to see full context
cat examples/react-native/RunAnywhereAI/src/components/food/MenuItemCard.tsx

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 4501


Remove stopPropagation() — it doesn't work in React Native.

React Native's Gesture Responder System doesn't use DOM-style event bubbling, and stopPropagation() has no effect on the synthetic event passed to onPress. The nested TouchableOpacity (add button) will naturally prevent the parent card's press from firing due to responder negotiation, but relying on this is fragile.

Use pointerEvents="box-none" on the parent's right section, or restructure to avoid nesting pressables and make the intent explicit rather than relying on undocumented behavior.

🤖 Prompt for AI Agents
In `@examples/react-native/RunAnywhereAI/src/components/food/MenuItemCard.tsx`
around lines 64 - 72, Remove the DOM-style e.stopPropagation() from the add
button's onPress handler in TouchableOpacity (the handler that calls
onAddToCart(item)); instead, make the parent's right-side container explicitly
allow inner touchables by adding pointerEvents="box-none" to the parent right
section (or move the add button out of the parent Pressable) so nested presses
don't rely on stopPropagation; update the TouchableOpacity onPress to simply
call onAddToCart(item) and remove the event parameter reference.

Comment on lines +379 to +406
// Helper functions for AI agent
export function searchRestaurants(query: string, filters?: {
cuisine?: string;
maxDeliveryFee?: number;
maxDeliveryTime?: number;
}): Restaurant[] {
const q = query.toLowerCase();

return RESTAURANTS.filter(r => {
// Search by name or cuisine
const matchesQuery =
r.name.toLowerCase().includes(q) ||
r.cuisine.some(c => c.toLowerCase().includes(q)) ||
r.menu.some(m => m.name.toLowerCase().includes(q));

if (!matchesQuery) return false;

// Apply filters
if (filters?.cuisine && !r.cuisine.some(c =>
c.toLowerCase().includes(filters.cuisine!.toLowerCase())
)) return false;

if (filters?.maxDeliveryFee !== undefined &&
r.deliveryFee > filters.maxDeliveryFee) return false;

return true;
});
}
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

Unused maxDeliveryTime filter parameter.

The maxDeliveryTime filter is declared in the function signature but never applied in the filtering logic. Either implement the filter or remove the parameter to avoid confusion.

Proposed fix
     if (filters?.maxDeliveryFee !== undefined && 
         r.deliveryFee > filters.maxDeliveryFee) return false;
+    
+    // Note: maxDeliveryTime filter would require parsing deliveryTime string (e.g., "25-35 min")
+    // which is complex. Consider removing this option or changing deliveryTime to a numeric type.
     
     return true;

Alternatively, remove the unused parameter:

 export function searchRestaurants(query: string, filters?: {
   cuisine?: string;
   maxDeliveryFee?: number;
-  maxDeliveryTime?: number;
 }): Restaurant[] {
📝 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
// Helper functions for AI agent
export function searchRestaurants(query: string, filters?: {
cuisine?: string;
maxDeliveryFee?: number;
maxDeliveryTime?: number;
}): Restaurant[] {
const q = query.toLowerCase();
return RESTAURANTS.filter(r => {
// Search by name or cuisine
const matchesQuery =
r.name.toLowerCase().includes(q) ||
r.cuisine.some(c => c.toLowerCase().includes(q)) ||
r.menu.some(m => m.name.toLowerCase().includes(q));
if (!matchesQuery) return false;
// Apply filters
if (filters?.cuisine && !r.cuisine.some(c =>
c.toLowerCase().includes(filters.cuisine!.toLowerCase())
)) return false;
if (filters?.maxDeliveryFee !== undefined &&
r.deliveryFee > filters.maxDeliveryFee) return false;
return true;
});
}
// Helper functions for AI agent
export function searchRestaurants(query: string, filters?: {
cuisine?: string;
maxDeliveryFee?: number;
maxDeliveryTime?: number;
}): Restaurant[] {
const q = query.toLowerCase();
return RESTAURANTS.filter(r => {
// Search by name or cuisine
const matchesQuery =
r.name.toLowerCase().includes(q) ||
r.cuisine.some(c => c.toLowerCase().includes(q)) ||
r.menu.some(m => m.name.toLowerCase().includes(q));
if (!matchesQuery) return false;
// Apply filters
if (filters?.cuisine && !r.cuisine.some(c =>
c.toLowerCase().includes(filters.cuisine!.toLowerCase())
)) return false;
if (filters?.maxDeliveryFee !== undefined &&
r.deliveryFee > filters.maxDeliveryFee) return false;
// Note: maxDeliveryTime filter would require parsing deliveryTime string (e.g., "25-35 min")
// which is complex. Consider removing this option or changing deliveryTime to a numeric type.
return true;
});
}
Suggested change
// Helper functions for AI agent
export function searchRestaurants(query: string, filters?: {
cuisine?: string;
maxDeliveryFee?: number;
maxDeliveryTime?: number;
}): Restaurant[] {
const q = query.toLowerCase();
return RESTAURANTS.filter(r => {
// Search by name or cuisine
const matchesQuery =
r.name.toLowerCase().includes(q) ||
r.cuisine.some(c => c.toLowerCase().includes(q)) ||
r.menu.some(m => m.name.toLowerCase().includes(q));
if (!matchesQuery) return false;
// Apply filters
if (filters?.cuisine && !r.cuisine.some(c =>
c.toLowerCase().includes(filters.cuisine!.toLowerCase())
)) return false;
if (filters?.maxDeliveryFee !== undefined &&
r.deliveryFee > filters.maxDeliveryFee) return false;
return true;
});
}
// Helper functions for AI agent
export function searchRestaurants(query: string, filters?: {
cuisine?: string;
maxDeliveryFee?: number;
}): Restaurant[] {
const q = query.toLowerCase();
return RESTAURANTS.filter(r => {
// Search by name or cuisine
const matchesQuery =
r.name.toLowerCase().includes(q) ||
r.cuisine.some(c => c.toLowerCase().includes(q)) ||
r.menu.some(m => m.name.toLowerCase().includes(q));
if (!matchesQuery) return false;
// Apply filters
if (filters?.cuisine && !r.cuisine.some(c =>
c.toLowerCase().includes(filters.cuisine!.toLowerCase())
)) return false;
if (filters?.maxDeliveryFee !== undefined &&
r.deliveryFee > filters.maxDeliveryFee) return false;
return true;
});
}
🤖 Prompt for AI Agents
In `@examples/react-native/RunAnywhereAI/src/data/foodData.ts` around lines 379 -
406, The searchRestaurants function accepts a filters.maxDeliveryTime parameter
but never uses it; either remove it from the filters signature or apply it in
the filter callback. To fix, inside searchRestaurants (the RESTAURANTS.filter
callback) add a check similar to the existing maxDeliveryFee check that returns
false when the restaurant's delivery time exceeds filters.maxDeliveryTime (e.g.,
compare filters.maxDeliveryTime to the restaurant's delivery time field such as
r.deliveryTime or r.estimatedDeliveryTime — adjust to the actual property name
used in the Restaurant objects). If you prefer to keep the signature unchanged,
implement this check; otherwise remove maxDeliveryTime from the filters type and
all callers.

Comment on lines +131 to +137
useFocusEffect(
useCallback(() => {
console.log('[FoodOrder] Screen focused - checking model and registering tools');
checkModelStatus();
registerFoodTools(); // Re-register tools every time screen is focused
}, [])
);
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

Missing dependency in useFocusEffect callback.

The useCallback has an empty dependency array, but checkModelStatus and registerFoodTools are defined outside the callback. While these functions are stable (defined at component level), registerFoodTools captures cartItems via the view_cart tool closure (line 311), which will become stale.

This means the view_cart tool will always see the initial empty cartItems state, not the current cart contents.

🐛 Suggested fix: Move tool registration or use refs for cart state

Consider using a ref to access current cart state inside tool handlers:

+ const cartItemsRef = useRef<CartItem[]>([]);
+ 
+ // Keep ref in sync with state
+ useEffect(() => {
+   cartItemsRef.current = cartItems;
+ }, [cartItems]);

  // Then in view_cart tool handler:
  async () => {
-   if (cartItems.length === 0) {
+   if (cartItemsRef.current.length === 0) {
      return { empty: true, message: 'Cart is empty' };
    }
-   const restaurant = cartItems[0] ? getRestaurantById(cartItems[0].restaurantId) : null;
-   const cart = calculateCartTotals(cartItems, restaurant?.deliveryFee || 0);
+   const items = cartItemsRef.current;
+   const restaurant = items[0] ? getRestaurantById(items[0].restaurantId) : null;
+   const cart = calculateCartTotals(items, restaurant?.deliveryFee || 0);
🤖 Prompt for AI Agents
In `@examples/react-native/RunAnywhereAI/src/screens/FoodOrderScreen.tsx` around
lines 131 - 137, The useFocusEffect callback currently has an empty dependency
array causing registerFoodTools to capture a stale cartItems via the view_cart
tool closure; update the hook so registerFoodTools runs with up-to-date cart
state—either add the real dependencies (e.g., cartItems) to the
useCallback/useFocusEffect so tools are re-registered when cart changes, or
change registerFoodTools/view_cart to read cartItems from a mutable ref (e.g.,
cartRef) that you update on cart change; ensure checkModelStatus and
registerFoodTools references in the useFocusEffect match the chosen approach to
avoid stale closures.

Comment on lines +446 to +499
switch (toolCall.toolName) {
case 'search_restaurants':
const isRealApi = result.source === 'foursquare';
const sourceLabel = isRealApi ? '🌐 LIVE from Foursquare' : '📦 Demo data';
const restaurants = result.restaurants as Array<{ name: string; rating: string | number; distance?: string }>;
const topRestaurants = restaurants?.slice(0, 3).map(r => r.name).join(', ') || '';

addAgentStep(
'searching',
isRealApi ? 'Found REAL restaurants nearby!' : 'Searching restaurants...',
`${sourceLabel}\n\nFound ${result.found || 0} restaurants:\n${topRestaurants}${restaurants?.length > 3 ? '...' : ''}`,
true
);
break;
case 'get_menu':
addAgentStep(
'found',
`Checking menu at ${result.restaurant || 'restaurant'}`,
`Found ${(result.items as unknown[])?.length || 0} items`,
true
);
break;
case 'add_to_cart':
if (result.success) {
addAgentStep(
'adding',
'Adding to cart',
`Added ${result.quantity}x ${result.item} - $${(result.total as number)?.toFixed(2)}`,
true
);
}
break;
case 'view_cart':
if (!result.empty) {
addAgentStep(
'cart',
'Cart ready!',
`${(result.items as unknown[])?.length} items - Total: $${(result.total as number)?.toFixed(2)}`,
true
);
}
break;
case 'place_order':
if (result.success) {
addAgentStep(
'success',
'Order placed! 🎉',
`Order #${result.orderId} - Arriving in ${result.estimatedDelivery}`,
true
);
}
break;
}
};
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

Wrap switch case declarations in blocks to prevent hoisting issues.

As flagged by static analysis, variable declarations in switch cases without block scoping can be accessed across cases, leading to potential bugs. The variables isRealApi, sourceLabel, restaurants, and topRestaurants declared in case 'search_restaurants' are technically accessible in subsequent cases.

🔧 Proposed fix
     switch (toolCall.toolName) {
-      case 'search_restaurants':
-        const isRealApi = result.source === 'foursquare';
-        const sourceLabel = isRealApi ? '🌐 LIVE from Foursquare' : '📦 Demo data';
-        const restaurants = result.restaurants as Array<{ name: string; rating: string | number; distance?: string }>;
-        const topRestaurants = restaurants?.slice(0, 3).map(r => r.name).join(', ') || '';
-        
-        addAgentStep(
-          'searching',
-          isRealApi ? 'Found REAL restaurants nearby!' : 'Searching restaurants...',
-          `${sourceLabel}\n\nFound ${result.found || 0} restaurants:\n${topRestaurants}${restaurants?.length > 3 ? '...' : ''}`,
-          true
-        );
-        break;
+      case 'search_restaurants': {
+        const isRealApi = result.source === 'foursquare';
+        const sourceLabel = isRealApi ? '🌐 LIVE from Foursquare' : '📦 Demo data';
+        const restaurants = result.restaurants as Array<{ name: string; rating: string | number; distance?: string }>;
+        const topRestaurants = restaurants?.slice(0, 3).map(r => r.name).join(', ') || '';
+        
+        addAgentStep(
+          'searching',
+          isRealApi ? 'Found REAL restaurants nearby!' : 'Searching restaurants...',
+          `${sourceLabel}\n\nFound ${result.found || 0} restaurants:\n${topRestaurants}${restaurants?.length > 3 ? '...' : ''}`,
+          true
+        );
+        break;
+      }
       case 'get_menu':

Apply the same pattern to other cases that contain declarations.

📝 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
switch (toolCall.toolName) {
case 'search_restaurants':
const isRealApi = result.source === 'foursquare';
const sourceLabel = isRealApi ? '🌐 LIVE from Foursquare' : '📦 Demo data';
const restaurants = result.restaurants as Array<{ name: string; rating: string | number; distance?: string }>;
const topRestaurants = restaurants?.slice(0, 3).map(r => r.name).join(', ') || '';
addAgentStep(
'searching',
isRealApi ? 'Found REAL restaurants nearby!' : 'Searching restaurants...',
`${sourceLabel}\n\nFound ${result.found || 0} restaurants:\n${topRestaurants}${restaurants?.length > 3 ? '...' : ''}`,
true
);
break;
case 'get_menu':
addAgentStep(
'found',
`Checking menu at ${result.restaurant || 'restaurant'}`,
`Found ${(result.items as unknown[])?.length || 0} items`,
true
);
break;
case 'add_to_cart':
if (result.success) {
addAgentStep(
'adding',
'Adding to cart',
`Added ${result.quantity}x ${result.item} - $${(result.total as number)?.toFixed(2)}`,
true
);
}
break;
case 'view_cart':
if (!result.empty) {
addAgentStep(
'cart',
'Cart ready!',
`${(result.items as unknown[])?.length} items - Total: $${(result.total as number)?.toFixed(2)}`,
true
);
}
break;
case 'place_order':
if (result.success) {
addAgentStep(
'success',
'Order placed! 🎉',
`Order #${result.orderId} - Arriving in ${result.estimatedDelivery}`,
true
);
}
break;
}
};
switch (toolCall.toolName) {
case 'search_restaurants': {
const isRealApi = result.source === 'foursquare';
const sourceLabel = isRealApi ? '🌐 LIVE from Foursquare' : '📦 Demo data';
const restaurants = result.restaurants as Array<{ name: string; rating: string | number; distance?: string }>;
const topRestaurants = restaurants?.slice(0, 3).map(r => r.name).join(', ') || '';
addAgentStep(
'searching',
isRealApi ? 'Found REAL restaurants nearby!' : 'Searching restaurants...',
`${sourceLabel}\n\nFound ${result.found || 0} restaurants:\n${topRestaurants}${restaurants?.length > 3 ? '...' : ''}`,
true
);
break;
}
case 'get_menu':
addAgentStep(
'found',
`Checking menu at ${result.restaurant || 'restaurant'}`,
`Found ${(result.items as unknown[])?.length || 0} items`,
true
);
break;
case 'add_to_cart':
if (result.success) {
addAgentStep(
'adding',
'Adding to cart',
`Added ${result.quantity}x ${result.item} - $${(result.total as number)?.toFixed(2)}`,
true
);
}
break;
case 'view_cart':
if (!result.empty) {
addAgentStep(
'cart',
'Cart ready!',
`${(result.items as unknown[])?.length} items - Total: $${(result.total as number)?.toFixed(2)}`,
true
);
}
break;
case 'place_order':
if (result.success) {
addAgentStep(
'success',
'Order placed! 🎉',
`Order #${result.orderId} - Arriving in ${result.estimatedDelivery}`,
true
);
}
break;
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 448-449: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 449-450: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 450-451: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 451-454: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In `@examples/react-native/RunAnywhereAI/src/screens/FoodOrderScreen.tsx` around
lines 446 - 499, The switch case for toolCall.toolName declares local variables
(isRealApi, sourceLabel, restaurants, topRestaurants) inside case
'search_restaurants' which can be hoisted/accessible across cases; fix by
wrapping that case in a block ({ ... }) and move the declarations inside the
block so they are block-scoped; apply the same pattern to any other cases that
declare variables (e.g., the logic in 'get_menu', 'add_to_cart', 'view_cart',
'place_order' where result-derived constants like items/total/quantity are used)
to ensure each case’s const/let bindings are contained.

Comment on lines +364 to +384
const handleModelSelect = useCallback(async (model: SDKModelInfo) => {
setIsModelLoading(true);
try {
// Unload current model if any
await RunAnywhere.unloadModel();

// Load new model
await RunAnywhere.loadModel(model.id);
setLoadedModel({
id: model.id,
name: model.name,
framework: LLMFramework.LlamaCpp,
});
} catch (error) {
console.error('[ToolsScreen] Model load error:', error);
Alert.alert('Error', `Failed to load model: ${error}`);
} finally {
setIsModelLoading(false);
setShowModelSelection(false);
}
}, []);
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

Framework is hardcoded to LLMFramework.LlamaCpp regardless of actual model.

When loading a model, the framework is always set to LLMFramework.LlamaCpp (line 375), which may not reflect the actual model's framework.

🔧 Consider using the model's actual framework
       await RunAnywhere.loadModel(model.id);
       setLoadedModel({
         id: model.id,
         name: model.name,
-        framework: LLMFramework.LlamaCpp,
+        framework: model.preferredFramework || LLMFramework.LlamaCpp,
       });
📝 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
const handleModelSelect = useCallback(async (model: SDKModelInfo) => {
setIsModelLoading(true);
try {
// Unload current model if any
await RunAnywhere.unloadModel();
// Load new model
await RunAnywhere.loadModel(model.id);
setLoadedModel({
id: model.id,
name: model.name,
framework: LLMFramework.LlamaCpp,
});
} catch (error) {
console.error('[ToolsScreen] Model load error:', error);
Alert.alert('Error', `Failed to load model: ${error}`);
} finally {
setIsModelLoading(false);
setShowModelSelection(false);
}
}, []);
const handleModelSelect = useCallback(async (model: SDKModelInfo) => {
setIsModelLoading(true);
try {
// Unload current model if any
await RunAnywhere.unloadModel();
// Load new model
await RunAnywhere.loadModel(model.id);
setLoadedModel({
id: model.id,
name: model.name,
framework: model.preferredFramework || LLMFramework.LlamaCpp,
});
} catch (error) {
console.error('[ToolsScreen] Model load error:', error);
Alert.alert('Error', `Failed to load model: ${error}`);
} finally {
setIsModelLoading(false);
setShowModelSelection(false);
}
}, []);
🤖 Prompt for AI Agents
In `@examples/react-native/RunAnywhereAI/src/screens/ToolsScreen.tsx` around lines
364 - 384, handleModelSelect currently hardcodes the framework to
LLMFramework.LlamaCpp when calling setLoadedModel; change it to use the selected
model's actual framework (e.g., model.framework or map model.type to
LLMFramework) so the loadedModel reflects the real framework, and provide a
sensible default fallback if model.framework is missing; update references in
handleModelSelect, SDKModelInfo, and setLoadedModel usage to ensure types align.

Comment on lines +446 to +479
'coffee': 'coffee shop',
'dessert': 'dessert',
'churros': 'churros',

// Fun/situational queries 🔥
'bar': 'bar',
'drinks': 'bar',
'strong drinks': 'bar',
'beer': 'bar',
'cocktails': 'cocktail bar',
'wine': 'wine bar',
'ice cream': 'ice cream',
'dumped': 'ice cream',
'comfort': 'comfort food',
'ramen': 'ramen',
'noodles': 'noodle shop',
'steak': 'steakhouse',
'fancy': 'fine dining',
'expensive': 'fine dining',
'classy': 'fine dining',
'greasy': 'diner',
'hungover': 'breakfast restaurant',
'late night': 'late night food',
'2am': 'late night food',
'3am': 'late night food',
'pasta': 'italian restaurant',
'romantic': 'fine dining',
'date': 'fine dining',
'first date': 'cocktail bar',
'in-laws': 'fine dining',

// Startup/founder culture 🚀
'startup': 'coffee shop',
'coffee': 'coffee shop',
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

Duplicate 'coffee' key in cuisineMap.

The key 'coffee' appears twice (lines 446 and 479). JavaScript objects only retain the last value for duplicate keys, so this is harmless but indicates a copy-paste error.

Remove the duplicate
     'breakfast': 'breakfast restaurant',
     'brunch': 'brunch',
-    'coffee': 'coffee shop',
     'dessert': 'dessert',
     'churros': 'churros',

Based on static analysis hints.

🧰 Tools
🪛 Biome (2.1.2)

[error] 446-446: This property is later overwritten by an object member with the same name.

Overwritten with this property.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
In `@examples/react-native/RunAnywhereAI/src/services/restaurantApi.ts` around
lines 446 - 479, The cuisineMap object contains a duplicate 'coffee' key; remove
the earlier duplicate entry (or consolidate them) so only one 'coffee': 'coffee
shop' mapping remains; locate the cuisineMap definition in restaurantApi.ts and
delete the redundant 'coffee' entry to avoid confusion and ensure the map is
clean.

Comment on lines +8 to +19
# =============================================================================
# nlohmann/json - Header-only JSON library for robust JSON parsing
# Used by ToolCallingBridge for parsing tool call JSON from LLM output
# =============================================================================
include(FetchContent)
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(nlohmann_json)
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

🧩 Analysis chain

🏁 Script executed:

# Check the full CMakeLists.txt context
fd CMakeLists.txt --type f | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 542


🏁 Script executed:

# Look for the specific CMakeLists.txt file mentioned
find . -path "*/android/CMakeLists.txt" -type f 2>/dev/null

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 281


🏁 Script executed:

# Check if third_party directory exists and its contents
find . -type d -name "third_party" 2>/dev/null

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 134


🏁 Script executed:

# Look for CI configuration to understand build environment constraints
fd -e yml -e yaml | grep -i ci | head -10

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


🌐 Web query:

nlohmann/json v3.11.3 release date security vulnerabilities

💡 Result:

  • Release date (v3.11.3): 2023-11-28 (GitHub release tag v3.11.3). [1]

  • Security vulnerabilities affecting nlohmann/json v3.11.3:

    • The upstream nlohmann/json GitHub project currently shows no published GitHub Security Advisories, so there are no officially published upstream advisories specifically flagged against v3.11.3 there. [2]
    • Some third-party databases list issues for very old nlohmann/json versions (< 2.0.10) (e.g., a heap/buffer overflow class issue), which do not apply to 3.11.3. [3]
  • Common confusion to avoid (not the C++ library):

    • The npm package named nlohmann-json is flagged as malicious by Snyk; this is not the C++ header-only library nlohmann/json. [4]
    • CVE-2024-34363 is described as an Envoy crash condition involving how Envoy uses nlohmann JSON (uncaught exception with incomplete UTF‑8), rather than an upstream nlohmann/json advisory entry. [5]

Sources: [1] GitHub releases page for nlohmann/json v3.11.3, [2] GitHub security page (no published advisories), [3] Snyk entry showing affected versions are <2.0.10, [4] Snyk malicious npm package page, [5] Wiz write-up of CVE-2024-34363 (Envoy context).


🌐 Web query:

CMake FetchContent network access offline builds best practices

💡 Result:

Best practices for offline / no-network builds with FetchContent

  1. Pin everything (reproducibility first)

    • For URL downloads: always provide URL_HASH so CMake can verify content and avoid ambiguous “latest” archives.
    • For GIT_REPOSITORY: use an immutable GIT_TAG (commit SHA preferred).
      This reduces surprise updates and makes “already-fetched” content usable without re-contacting remotes. [1]
  2. Prefer “use local sources” overrides for truly offline builds

    • Set per-dependency overrides so FetchContent never tries to download/update:
      • -DFETCHCONTENT_SOURCE_DIR_<DEP>=/path/to/already-unpacked/sources
        This is the primary supported way to force FetchContent to consume pre-populated sources (it must be a directory, not a tarball). [1][2]
  3. Use the right disconnect switch for the job

    • FETCHCONTENT_UPDATES_DISCONNECTED=ON: prevents VCS update steps (git/hg) from contacting remotes, but still allows first-time downloads. Good for “mostly offline” builds where the initial population may happen on a connected machine/CI. [1]
    • FETCHCONTENT_FULLY_DISCONNECTED=ON: no download or update attempts at all, but it is not intended to block network on the first configure in a build tree; CMake explicitly warns this can break projects and is meant for after an initial successful population (and is enforced more strictly by policy in newer CMake). [1][2]
  4. Bake an “online populate, offline build” workflow

    • In CI (online): configure once to populate dependencies (or explicitly script population), then archive/cache the populated source dirs.
    • In developer/offline builds: point FETCHCONTENT_SOURCE_DIR_<DEP> to that cached/unpacked content, and optionally enable FETCHCONTENT_FULLY_DISCONNECTED only once you know the build dir is already populated. [1][2]
  5. If you must guarantee “no network even on first run,” use a dependency provider

    • CMake’s own guidance: to prevent network access on the first run, don’t rely on FETCHCONTENT_FULLY_DISCONNECTED; instead use a dependency provider that supplies dependencies from local content. [1]

Key references: CMake FetchContent docs (disconnect modes + source-dir overrides + provider guidance) [1]; CMake Discourse guidance on offline/pre-population and why FULLY_DISCONNECTED is often misunderstood [2].

Sources: [1] [2]


🏁 Script executed:

# Read the full CMakeLists.txt file
cat -n ./sdk/runanywhere-react-native/packages/core/android/CMakeLists.txt

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 6808


🏁 Script executed:

# Check the third_party directory contents
ls -la ./sdk/runanywhere-react-native/packages/core/cpp/third_party/

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 239


🏁 Script executed:

# Look for any build documentation or README
find ./sdk/runanywhere-react-native/packages/core -name "README*" -o -name "BUILD*" -o -name "*.md" | head -10

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 128


🏁 Script executed:

# Check what's in the nlohmann directory within third_party
ls -la ./sdk/runanywhere-react-native/packages/core/cpp/third_party/nlohmann/

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 250


🏁 Script executed:

# Check the README for any build/dependency documentation
head -50 ./sdk/runanywhere-react-native/packages/core/README.md

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1508


Remove FetchContent; use already-vendored nlohmann/json headers.

nlohmann/json v3.11.3 is already vendored in ../cpp/third_party/nlohmann/json.hpp. Using FetchContent to download from GitHub creates an unnecessary network dependency during the CMake configure step. Since the header-only library is present in the repository, remove the FetchContent block (lines 12–19) and link directly to the vendored copy. The existing include path at line 89 already provides access to these headers.

🤖 Prompt for AI Agents
In `@sdk/runanywhere-react-native/packages/core/android/CMakeLists.txt` around
lines 8 - 19, Remove the FetchContent usage that pulls nlohmann_json (the
include(FetchContent), FetchContent_Declare(...) for nlohmann_json and
FetchContent_MakeAvailable(nlohmann_json)) and instead rely on the vendored
header already in the repo (../cpp/third_party/nlohmann/json.hpp); update or
leave the existing include path logic (the include path referenced later in the
file) so targets that need nlohmann/json include the vendored headers without
performing a network fetch during CMake configure.

Comment on lines +53 to +66
for (size_t i = 0; i < jsonStr.size(); i++) {
char c = jsonStr[i];

// Track if we're inside a string
if (c == '"' && (i == 0 || jsonStr[i-1] != '\\')) {
inString = !inString;
result += c;
continue;
}

if (inString) {
result += c;
continue;
}
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

Escaped backslash sequences may cause incorrect string boundary detection.

The escape check jsonStr[i-1] != '\\' doesn't account for escaped backslashes. For input like {"key": "value\\"}, the sequence \\" has an escaped backslash followed by a real quote, but this code would treat the quote as escaped.

This is unlikely in typical LLM tool-call output but could cause parsing failures for edge cases.

♻️ Suggested fix using backslash parity check
         // Track if we're inside a string
-        if (c == '"' && (i == 0 || jsonStr[i-1] != '\\')) {
+        if (c == '"') {
+            // Count preceding backslashes to handle escaped backslashes
+            size_t backslashes = 0;
+            size_t k = i;
+            while (k > 0 && jsonStr[k - 1] == '\\') {
+                backslashes++;
+                k--;
+            }
+            // Quote is escaped only if odd number of backslashes precede it
+            if (backslashes % 2 == 0) {
+                inString = !inString;
+            }
+            result += c;
+            continue;
+        }
+        
+        if (c == '"' && (i == 0 || jsonStr[i-1] != '\\')) {
             inString = !inString;
             result += c;
             continue;
         }
🤖 Prompt for AI Agents
In `@sdk/runanywhere-react-native/packages/core/cpp/bridges/ToolCallingBridge.cpp`
around lines 53 - 66, In ToolCallingBridge.cpp inside the loop that iterates
jsonStr (the block toggling inString when encountering '"' currently using
jsonStr[i-1] != '\\'), replace the simple previous-char check with a parity
check of consecutive backslashes before the quote: count how many consecutive
'\\' characters immediately preceding index i and only treat the quote as
escaped if that count is odd; otherwise toggle inString and append the quote to
result. This change touches the for loop handling jsonStr, the inString boolean
logic, and uses jsonStr and result to preserve behavior.

Comment on lines +346 to +362
export async function continueWithToolResult(
previousPrompt: string,
toolCall: ToolCall,
toolResult: ToolResult,
options?: ToolCallingOptions
): Promise<ToolCallingResult> {
const resultJson = toolResult.success
? JSON.stringify(toolResult.result)
: `Error: ${toolResult.error}`;

const continuedPrompt = `${previousPrompt}\n\nTool Result for ${toolCall.toolName}: ${resultJson}\n\nBased on the tool result, please provide your response:`;

return generateWithTools(continuedPrompt, {
...options,
maxToolCalls: (options?.maxToolCalls ?? 5) - 1,
});
}
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

Edge case: maxToolCalls could reach zero or negative.

When continueWithToolResult is called repeatedly, maxToolCalls decrements each time. If it reaches 0, the while loop in generateWithTools won't execute, returning an incomplete result with empty text.

Consider adding a minimum bound:

♻️ Suggested fix
   return generateWithTools(continuedPrompt, {
     ...options,
-    maxToolCalls: (options?.maxToolCalls ?? 5) - 1,
+    maxToolCalls: Math.max(1, (options?.maxToolCalls ?? 5) - 1),
   });
🤖 Prompt for AI Agents
In
`@sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere`+ToolCalling.ts
around lines 346 - 362, continueWithToolResult currently decrements
options.maxToolCalls and can pass zero/negative into generateWithTools causing
its tool loop to skip and return incomplete results; clamp the computed
maxToolCalls to a minimum of 1 before calling generateWithTools (e.g., compute
newMax = Math.max((options?.maxToolCalls ?? 5) - 1, 1)) and pass that in the
options spread so generateWithTools always has at least one allowed tool call;
update references in continueWithToolResult to use this clamped value.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants