Skip to content

Feature/flutter structured output#382

Merged
Siddhesh2377 merged 15 commits intoRunanywhereAI:mainfrom
bagusindrayana:feature/flutter-structured-output
Feb 19, 2026
Merged

Feature/flutter structured output#382
Siddhesh2377 merged 15 commits intoRunanywhereAI:mainfrom
bagusindrayana:feature/flutter-structured-output

Conversation

@bagusindrayana
Copy link
Copy Markdown
Contributor

@bagusindrayana bagusindrayana commented Feb 18, 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

Web SDK / Web Sample:

  • Tested in Chrome (Desktop)
  • Tested in Firefox
  • Tested in Safari
  • WASM backends load (LlamaCpp + ONNX)
  • OPFS storage persistence verified (survives page refresh)
  • Settings persistence verified (localStorage)

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)
  • React Native SDK - Changes to React Native SDK (sdk/runanywhere-react-native)
  • Flutter SDK - Changes to Flutter SDK (sdk/runanywhere-flutter)
  • Web SDK - Changes to Web SDK (sdk/runanywhere-web)
  • 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)
  • Web Sample - Changes to Web example app (examples/web)

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
image
2026-02-18.23-13-26.mp4
sample_stream_result_structured_json.mp4

need improvement for System Prompt to produce more consistent results, especially for small models.


Important

Adds structured output support to Flutter SDK with new types, FFI bindings, and a demo view, modifying core methods to handle JSON output.

  • Behavior:
    • Adds structured output support to generate() and generateStream() in runanywhere.dart, injecting JSON system prompts and extracting JSON from text.
    • New FFI bridge dart_bridge_structured_output.dart for C++ APIs with fallback logic.
  • Public Types:
    • Adds StructuredOutputConfig, StructuredOutputResult, etc., in structured_output_types.dart.
    • Updates LLMGenerationOptions and LLMGenerationResult in generation_types.dart to include structured output fields.
  • UI:
    • New StructuredOutputView in structured_output_view.dart for demoing structured output with schema templates and prompt examples.
    • Adds navigation to StructuredOutputView in chat_interface_view.dart.
  • Issues:
    • Shared ScrollController in structured_output_view.dart may cause runtime crashes.
    • Unguarded jsonDecode calls in runanywhere.dart can lead to errors.

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


Summary by CodeRabbit

  • New Features

    • Structured output: JSON-schema-backed generation (streaming and non-streaming) with extraction and parsed structuredData attached to results.
    • New Structured Output example view: selectable templates, schema preview, prompt entry, copy-to-clipboard, error handling, and loading state.
    • AppBar action to open Structured Output Examples.
  • Style

    • Settings and Android UI updated with new icons for clearer visual cues.

Greptile Summary

Adds structured output functionality to the Flutter SDK, enabling JSON schema-based generation with FFI bindings to C++ APIs. The implementation includes type definitions, FFI bridge, handler logic, and a demo UI.

Key additions:

  • Public types in structured_output_types.dart for StructuredOutputConfig, StructuredOutputResult, and validation
  • FFI bridge in dart_bridge_structured_output.dart with proper fallbacks and memory management
  • Integration into RunAnywhere.generate() and generateStream() methods with JSON extraction
  • Demo UI in StructuredOutputView with recipe, user profile, weather, and product list examples

Critical issues found:

  • Multiple duplicate class definitions in structured_output_handler.dart (StructuredOutputConfig, StructuredOutputValidation, StructuredOutputError, StructuredOutputStreamResult) conflict with public API types in structured_output_types.dart - these duplicates must be removed to prevent import ambiguity and API inconsistency
  • Missing null checks on modelId in runanywhere.dart (lines 1156, 1305) could cause runtime errors if getLoadedModel() returns null
  • Unhandled jsonDecode exception in structured_output_handler.dart:86 could throw FormatException for invalid JSON

Notes:

  • Previous review threads identified ScrollController conflict in UI and JSON parsing issues in runanywhere.dart - these remain unresolved
  • Android files have redundant wildcard imports (previously flagged)

Confidence Score: 2/5

  • This PR has critical type conflicts and missing null checks that will cause runtime errors
  • Multiple duplicate class definitions in structured_output_handler.dart create API conflicts that will break imports and cause compile-time/runtime issues. Missing null checks on modelId could crash the app. Unhandled JSON parsing exceptions could fail valid generations. These are blocking issues that must be fixed before merge.
  • Pay close attention to sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart (remove duplicate classes) and sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart (add null checks)

Important Files Changed

Filename Overview
sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart Duplicate StructuredOutputConfig class conflicts with public types; JSON parsing lacks error handling
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart Well-implemented FFI bridge with proper fallbacks and memory management
sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart Structured output integrated into generate methods; previous JSON parsing issues noted in threads
examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart Demo UI with scroll controller conflict noted in previous threads

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User calls RunAnywhere.generate with structuredOutput config] --> B{structuredOutput provided?}
    B -->|Yes| C[StructuredOutputHandler prepares prompt with schema]
    B -->|No| D[Standard generation]
    C --> E[DartBridge.llm.generate calls C++ API]
    D --> E
    E --> F[LLM generates text response]
    F --> G{structuredOutput provided?}
    G -->|Yes| H[DartBridgeStructuredOutput.extractJson]
    G -->|No| I[Return text as-is]
    H --> J{JSON extraction successful?}
    J -->|Yes| K[jsonDecode parses JSON]
    J -->|No| L[Return text without structuredData]
    K --> M{Parse successful?}
    M -->|Yes| N[Return LLMGenerationResult with structuredData]
    M -->|No| L
    I --> O[Return LLMGenerationResult without structuredData]
    L --> O
    N --> P[User receives result]
    O --> P
    
    style H fill:#e1f5ff
    style C fill:#e1f5ff
    style N fill:#c8e6c9
    style L fill:#fff9c4
Loading

Last reviewed commit: ad9638f

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

Adds structured-output support across the Flutter SDK (types, FFI structs, Dart FFI bridge, handler, RunAnywhere wiring) plus a new Flutter example view; also applies minor Android sample UI icon/import tweaks.

Changes

Cohort / File(s) Summary
Android UI
examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/components/ModelRequiredOverlay.kt, examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/SettingsScreen.kt
Added wildcard icon import and inserted Build/Add icons in settings UI — visual/icon import and usage changes only.
Flutter Example — Chat
examples/flutter/RunAnywhereAI/lib/features/chat/chat_interface_view.dart
Added import and AppBar action to navigate to StructuredOutputView.
Flutter Example — Structured Output
examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart
New UI view demonstrating structured-output examples, schema preview, prompt entry, stream vs non-stream generation, result display, copy action, and loading state.
Flutter SDK — Public Types
sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/structured_output_types.dart, .../generation_types.dart, .../types.dart
New structured-output public types (StructuredOutputConfig, validation/error/result types), exported; added structuredOutput to LLMGenerationOptions and structuredData to LLMGenerationResult.
Flutter SDK — Native FFI & FFI Types
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart, sdk/runanywhere-flutter/packages/runanywhere/lib/native/ffi_types.dart
New DartBridgeStructuredOutput singleton wrapping native rac_structured_output_* APIs with fallback implementations and resource management; added FFI structs RacStructuredOutputConfigStruct and RacStructuredOutputValidationStruct.
Flutter SDK — Handler / Prompt Composition
sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart
Moved public types to new types file, added try/catch around JSON parsing, updated prompt composition to include schema/JSON-conversion instructions.
Flutter SDK — Core Integration
sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart
Compute effective systemPrompt via DartBridgeStructuredOutput when structuredOutput provided; pass effectiveSystemPrompt into LLM calls; attempt JSON extraction/parsing post-generation and attach normalized structuredData to LLMGenerationResult (single and streaming paths).

Sequence Diagram(s)

sequenceDiagram
    participant App as Flutter App
    participant SDK as RunAnywhere SDK
    participant Bridge as DartBridgeStructuredOutput
    participant Native as Native Layer
    participant LLM as LLM Backend

    App->>SDK: generate(prompt, structuredOutput)
    SDK->>Bridge: getSystemPrompt(schema)
    Bridge->>Native: rac_structured_output_get_system_prompt()
    Native-->>Bridge: systemPrompt
    Bridge-->>SDK: systemPrompt

    SDK->>Bridge: preparePrompt(userPrompt, schema)
    Bridge->>Native: rac_structured_output_prepare_prompt()
    Native-->>Bridge: effectivePrompt
    Bridge-->>SDK: effectivePrompt

    SDK->>LLM: generate(effectivePrompt, systemPrompt)
    LLM-->>SDK: rawText

    SDK->>Bridge: extractJson(rawText)
    Bridge->>Native: rac_structured_output_extract_json()
    Native-->>Bridge: jsonString
    Bridge-->>SDK: jsonString

    SDK->>SDK: jsonDecode & normalize
    SDK-->>App: LLMGenerationResult(text, structuredData)
Loading
sequenceDiagram
    participant App as Flutter App
    participant SDK as RunAnywhere SDK
    participant Bridge as DartBridgeStructuredOutput
    participant Native as Native Layer
    participant LLM as LLM Backend

    App->>SDK: generateStream(prompt, structuredOutput)
    SDK->>Bridge: getSystemPrompt/preparePrompt
    Bridge->>Native: rac_structured_output_get_system_prompt()/prepare_prompt()
    Native-->>Bridge: systemPrompt / effectivePrompt
    Bridge-->>SDK: systemPrompt / effectivePrompt

    SDK->>LLM: stream(effectivePrompt)
    LLM-->>SDK: Stream<tokens>

    loop emit tokens
        SDK-->>App: StreamToken
    end

    SDK->>Bridge: extractJson(accumulatedText)
    Bridge->>Native: rac_structured_output_extract_json()
    Native-->>Bridge: jsonString
    Bridge-->>SDK: jsonString

    SDK->>SDK: jsonDecode & normalize
    SDK-->>App: Structured stream result + parsed structuredData
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hopping through schemas with delight,

I nibble JSON by moonlit byte,
Bridges hum and tokens softly stream,
I tuck parsed data into a dream,
Little rabbit, proud of structured light.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and generic, using broad terminology without describing the specific implementation details or impact of the structured output feature. Consider a more specific title like 'Add structured output support to Flutter SDK with JSON schema handling' that clearly describes the main change.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description follows the template structure with Type of Change, Testing, Labels, and Checklist sections completed, but lacks a detailed brief description at the top explaining the specific changes made.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Copy Markdown

@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 7da2ec4 in 27 seconds. Click for details.
  • Reviewed 1461 lines of code in 11 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 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.

Workflow ID: wflow_TODIlP83Ar7dx76C

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

Copy link
Copy Markdown
Contributor

@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.

11 files reviewed, 6 comments

Edit Code Review Agent Settings | Greptile

final double? timeToFirstTokenMs;
final int thinkingTokens;
final int responseTokens;
final dynamic structuredData;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

dynamic type for structuredData violates project guidelines

The CLAUDE.md states: "Always make sure that you're using structured types, never use strings directly so that we can keep things consistent and scalable and not make mistakes." Using dynamic here loses all type safety — callers can't know what shape the data has without runtime checks.

Consider using Map<String, dynamic>? or Object? at minimum, which at least communicates the expected shape and avoids accidental misuse.

Context Used: Context from dashboard - CLAUDE.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/generation_types.dart
Line: 50

Comment:
**`dynamic` type for `structuredData` violates project guidelines**

The CLAUDE.md states: "Always make sure that you're using structured types, never use strings directly so that we can keep things consistent and scalable and not make mistakes." Using `dynamic` here loses all type safety — callers can't know what shape the data has without runtime checks.

Consider using `Map<String, dynamic>?` or `Object?` at minimum, which at least communicates the expected shape and avoids accidental misuse.

**Context Used:** Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=20c5a1fb-ed57-4e08-840a-9128622c3bd6))

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment on lines +18 to +19
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Redundant wildcard imports alongside specific imports

These wildcard imports (filled.*, outlined.*) make the specific named imports below them (e.g., filled.Analytics, filled.CleaningServices) redundant. Either use only the wildcard imports and remove the named ones, or remove the wildcards and keep only the specific imports (preferred for clarity and avoiding unused import warnings).

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: examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/SettingsScreen.kt
Line: 18-19

Comment:
**Redundant wildcard imports alongside specific imports**

These wildcard imports (`filled.*`, `outlined.*`) make the specific named imports below them (e.g., `filled.Analytics`, `filled.CleaningServices`) redundant. Either use only the wildcard imports and remove the named ones, or remove the wildcards and keep only the specific imports (preferred for clarity and avoiding unused import warnings).

<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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is not Something big but would be really Good if removed 😊

@Siddhesh2377 Siddhesh2377 added the enhancement New feature or request label Feb 18, 2026
@Siddhesh2377 Siddhesh2377 self-assigned this Feb 18, 2026
@Siddhesh2377
Copy link
Copy Markdown
Collaborator

Hey @bagusindrayana Fix all the issue / bugs plz 😊

@Siddhesh2377 Siddhesh2377 marked this pull request as draft February 19, 2026 11:00
@Siddhesh2377 Siddhesh2377 removed their assignment Feb 19, 2026
bagusindrayana and others added 4 commits February 19, 2026 19:08
…structured_output_view.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…where.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…where.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…structured_output_view.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@Siddhesh2377 Siddhesh2377 left a comment

Choose a reason for hiding this comment

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

@bagusindrayana
There is just a Micro Fix in sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/generation_types.dart
Line No : 50

dynamic type for structuredData violates project guidelines

The CLAUDE.md states: "Always make sure that you're using structured types, never use strings directly so that we can keep things consistent and scalable and not make mistakes." Using dynamic here loses all type safety — callers can't know what shape the data has without runtime checks.

Consider using Map<String, dynamic>? or Object? at minimum, which at least communicates the expected shape and avoids accidental misuse.

final double? timeToFirstTokenMs;
final int thinkingTokens;
final int responseTokens;
final dynamic structuredData;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment on lines +18 to +19
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is not Something big but would be really Good if removed 😊

@bagusindrayana bagusindrayana marked this pull request as ready for review February 19, 2026 11:51
Copy link
Copy Markdown

@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 ad9638f in 20 seconds. Click for details.
  • Reviewed 1565 lines of code in 11 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 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.

Workflow ID: wflow_dQdKhnag4susalgK

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

Copy link
Copy Markdown
Contributor

@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.

11 files reviewed, 9 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 288 to 313
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicate StructuredOutputConfig class conflicts with public API

This internal StructuredOutputConfig (line 288-313) duplicates the public version in structured_output_types.dart (line 11-34), but uses Type type instead of String typeName. The public API already exports this type via types.dart, so having two conflicting definitions breaks API consistency and will cause import ambiguity.

Remove this duplicate and use the public StructuredOutputConfig from structured_output_types.dart instead. Update this handler to work with typeName rather than type.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart
Line: 288-313

Comment:
Duplicate `StructuredOutputConfig` class conflicts with public API

This internal `StructuredOutputConfig` (line 288-313) duplicates the public version in `structured_output_types.dart` (line 11-34), but uses `Type type` instead of `String typeName`. The public API already exports this type via `types.dart`, so having two conflicting definitions breaks API consistency and will cause import ambiguity.

Remove this duplicate and use the public `StructuredOutputConfig` from `structured_output_types.dart` instead. Update this handler to work with `typeName` rather than `type`.

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

Comment on lines 245 to 257
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicate StructuredOutputValidation class

This class (line 245-257) also exists in structured_output_types.dart as StructuredOutputValidation (line 69-81). Remove this duplicate to avoid conflicts.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart
Line: 245-257

Comment:
Duplicate `StructuredOutputValidation` class

This class (line 245-257) also exists in `structured_output_types.dart` as `StructuredOutputValidation` (line 69-81). Remove this duplicate to avoid conflicts.

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

Comment on lines 259 to 286
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicate StructuredOutputError class

This class (line 259-286) also exists in structured_output_types.dart (line 85-110). Remove this duplicate and use the public version instead.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart
Line: 259-286

Comment:
Duplicate `StructuredOutputError` class

This class (line 259-286) also exists in `structured_output_types.dart` (line 85-110). Remove this duplicate and use the public version instead.

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

Comment on lines 315 to 328
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicate StructuredOutputStreamResult class

This class (line 315-328) also exists in structured_output_types.dart (line 52-67) with slightly different generic type (Stream<String> vs Stream<StreamToken>). Remove this duplicate and use the public version.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart
Line: 315-328

Comment:
Duplicate `StructuredOutputStreamResult` class

This class (line 315-328) also exists in `structured_output_types.dart` (line 52-67) with slightly different generic type (`Stream<String>` vs `Stream<StreamToken>`). Remove this duplicate and use the public version.

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

if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {
final jsonSubstring = trimmed.substring(startIndex, endIndex + 1);
try {
jsonDecode(jsonSubstring);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider explicit FormatException handling

jsonDecode here can throw FormatException. While the surrounding try-catch handles it, being explicit about catching FormatException improves clarity.

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-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart
Line: 114

Comment:
Consider explicit `FormatException` handling

`jsonDecode` here can throw `FormatException`. While the surrounding try-catch handles it, being explicit about catching `FormatException` improves clarity.

<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 +1192 to +1203
String? effectiveSystemPrompt = opts.systemPrompt;
if (opts.structuredOutput != null) {
final jsonSystemPrompt = DartBridgeStructuredOutput.shared.getSystemPrompt(
opts.structuredOutput!.schema,
);
// If user already provided a system prompt, prepend the JSON instructions
if (effectiveSystemPrompt != null && effectiveSystemPrompt.isNotEmpty) {
effectiveSystemPrompt = '$jsonSystemPrompt\n\n$effectiveSystemPrompt';
} else {
effectiveSystemPrompt = jsonSystemPrompt;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

System prompt preparation bypasses the FFI bridge

When structuredOutput is provided, the code uses the handler's preparePrompt method instead of leveraging DartBridgeStructuredOutput.shared.preparePrompt (which calls the C++ implementation with fallback). Consider using the bridge method for consistency with the rest of the structured output implementation.

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-flutter/packages/runanywhere/lib/public/runanywhere.dart
Line: 1192-1203

Comment:
System prompt preparation bypasses the FFI bridge

When `structuredOutput` is provided, the code uses the handler's `preparePrompt` method instead of leveraging `DartBridgeStructuredOutput.shared.preparePrompt` (which calls the C++ implementation with fallback). Consider using the bridge method for consistency with the rest of the structured output implementation.

<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.

bagusindrayana and others added 3 commits February 19, 2026 19:58
…structured_output/structured_output_handler.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…where.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…where.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (2)
sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart (2)

65-77: ⚠️ Potential issue | 🟠 Major

Misleading prompt wording: "Convert this Data" / "to JSON Schema" implies schema generation, not data generation.

The intent is to generate JSON data that conforms to the schema, but the prompt says "Convert this Data ... to JSON Schema" which asks the model to produce a schema from data — the opposite of what's wanted. This likely degrades output quality, especially on smaller models (as the PR notes).

Suggested fix
     return '''
 System: You are a JSON generator. You must output only valid JSON.
-Convert this Data :
+Generate structured data for:
 $originalPrompt
 
-to JSON Schema:
-${config.schema}
-
+Following this JSON Schema:
+${config.schema}
+
 $instructions
 
 Remember: Output ONLY the JSON object, nothing else.
 ''';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`
around lines 65 - 77, The system prompt wording is misleading: it currently asks
the model to "Convert this Data ... to JSON Schema" which requests schema
generation instead of producing data; update the prompt returned (the string
that uses originalPrompt, config.schema and instructions) to explicitly instruct
the model to generate JSON data conforming to the provided schema (e.g., replace
"Convert this Data ... to JSON Schema" with something like "Generate JSON data
that conforms to the following JSON Schema" and keep the directive "Output ONLY
the JSON object, nothing else."). Ensure the change is made where the prompt
string is constructed so originalPrompt, config.schema and instructions are
still interpolated and the final message enforces valid JSON-only output.

247-313: ⚠️ Potential issue | 🟠 Major

Remove duplicate type definitions or rename handler-internal versions to avoid future ambiguity.

This file defines StructuredOutputValidation, StructuredOutputError, StructuredOutputConfig, and StructuredOutputStreamResult — duplicates of those in structured_output_types.dart (exported via types.dart). The shapes differ: handler's StructuredOutputConfig.type is Type vs. public's typeName is String; handler's stream is Stream<String> vs. public's Stream<StreamToken>.

While no files currently import both, this duplication creates maintenance burden and risks future conflicts. Consider either using the public types throughout the handler or renaming the internal ones (e.g., with Handler prefix).

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

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`
around lines 247 - 313, The handler file defines duplicate types
(StructuredOutputValidation, StructuredOutputError, StructuredOutputConfig,
StructuredOutputStreamResult) that conflict with the public types exported via
types.dart; update the handler to either import and use the public types
(replace StructuredOutputConfig.type: Type with the public config which uses
typeName: String and switch the stream to Stream<StreamToken>) or rename the
internal definitions (e.g., HandlerStructuredOutputConfig,
HandlerStructuredOutputValidation, HandlerStructuredOutputError,
HandlerStructuredOutputStreamResult) to avoid future ambiguity; ensure all
references in this file (constructors, return types, method signatures) are
updated to the chosen approach so there are no duplicate symbol names.
🧹 Nitpick comments (8)
examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/components/ModelRequiredOverlay.kt (1)

25-30: Replace the wildcard import with a single explicit Visibility import.

The wildcard on line 25 was added solely to bring in Icons.Default.Visibility (used at line 253). This makes lines 26–30 redundant duplicates, and wildcard imports are generally flagged by Android Studio lint and make dependencies harder to trace.

The simpler, idiomatic fix is to drop the wildcard and add only the missing icon import:

♻️ Proposed fix
-import androidx.compose.material.icons.filled.*
 import androidx.compose.material.icons.filled.AutoAwesome
 import androidx.compose.material.icons.filled.GraphicEq
 import androidx.compose.material.icons.filled.Lock
 import androidx.compose.material.icons.filled.Mic
+import androidx.compose.material.icons.filled.Visibility
 import androidx.compose.material.icons.filled.VolumeUp
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/components/ModelRequiredOverlay.kt`
around lines 25 - 30, Remove the wildcard import import
androidx.compose.material.icons.filled.* and replace it with a single explicit
import for Visibility (import androidx.compose.material.icons.filled.Visibility)
so Icons.Default.Visibility resolves; keep the existing explicit imports
(AutoAwesome, GraphicEq, Lock, Mic, VolumeUp) and ensure no other icons rely on
the wildcard before committing.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/ffi_types.dart (1)

1093-1108: Inconsistent class modifier: final class vs base class used elsewhere in this file.

All other Struct subclasses in this file (e.g., RacPlatformAdapterStruct, RacMemoryInfoStruct, RacLlmOptionsStruct, etc.) use base class, but the two new structs use final class. While both are valid for FFI Structs, consider using base class for consistency.

Proposed fix
-final class RacStructuredOutputConfigStruct extends Struct {
+base class RacStructuredOutputConfigStruct extends Struct {
   external Pointer<Utf8> jsonSchema;

   `@Int32`()
   external int includeSchemaInPrompt;
 }

-final class RacStructuredOutputValidationStruct extends Struct {
+base class RacStructuredOutputValidationStruct extends Struct {
   `@Int32`()
   external int isValid;

   external Pointer<Utf8> errorMessage;

   external Pointer<Utf8> extractedJson;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/native/ffi_types.dart`
around lines 1093 - 1108, The two new FFI struct declarations use the `final
class` modifier while existing structs use `base class`; update the declarations
for RacStructuredOutputConfigStruct and RacStructuredOutputValidationStruct to
use `base class` instead of `final class` so they match the rest of the file and
maintain consistency for Struct subclasses.
sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart (3)

1191-1203: DRY: effectiveSystemPrompt computation is duplicated between generate() and generateStream().

The exact same logic for computing the effective system prompt appears in both methods. Consider extracting a shared helper:

Suggested helper
static String? _effectiveSystemPrompt(LLMGenerationOptions opts) {
  String? prompt = opts.systemPrompt;
  if (opts.structuredOutput != null) {
    final jsonSystemPrompt = DartBridgeStructuredOutput.shared
        .getSystemPrompt(opts.structuredOutput!.schema);
    if (prompt != null && prompt.isNotEmpty) {
      prompt = '$jsonSystemPrompt\n\n$prompt';
    } else {
      prompt = jsonSystemPrompt;
    }
  }
  return prompt;
}

Also applies to: 1318-1330

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

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`
around lines 1191 - 1203, Extract the duplicated effective system prompt logic
into a shared private helper (e.g., static String?
_effectiveSystemPrompt(LLMGenerationOptions opts)) and use it from both
generate() and generateStream(); the helper should take opts.systemPrompt, check
opts.structuredOutput, call
DartBridgeStructuredOutput.shared.getSystemPrompt(opts.structuredOutput!.schema)
when present, and prepend or replace the user prompt exactly as the current
logic in effectiveSystemPrompt does so existing behavior of
effectiveSystemPrompt in generate() and generateStream() is preserved.

1234-1246: Silent catch (_) swallows all JSON extraction errors with no logging.

When structured output is requested but extraction fails, the error is silently discarded. This makes debugging very difficult for SDK consumers. At minimum, log the failure at debug level so it's visible during development.

Suggested improvement
         try {
           final jsonString = DartBridgeStructuredOutput.shared.extractJson(result.text);
           if (jsonString != null) {
             final parsed = jsonDecode(jsonString);
             structuredData = _normalizeStructuredData(parsed);
           }
-        } catch (_) {
-          // JSON extraction/parse failed — return text result without structured data
+        } catch (e) {
+          SDKLogger('RunAnywhere.Generate')
+              .debug('Structured output extraction failed: $e');
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`
around lines 1234 - 1246, The current silent catch in the structured output
extraction (within the block checking opts.structuredOutput and calling
DartBridgeStructuredOutput.shared.extractJson(result.text)) swallows errors;
change the catch from catch (_) to catch (e, st) and log a debug-level message
including the error and stack trace plus context (e.g., result.text and the
extracted jsonString) before continuing; ensure you still return the plain text
result if parsing fails and keep the call to _normalizeStructuredData unchanged
when parsing succeeds.

1408-1421: Same silent catch (_) issue in the streaming path — apply the same logging fix here.

Suggested improvement
-        } catch (_) {
-          // JSON extraction/parse failed — return text result without structured data
+        } catch (e) {
+          SDKLogger('RunAnywhere.Generate')
+              .debug('Structured output extraction failed (stream): $e');
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`
around lines 1408 - 1421, Silent catch is hiding JSON extraction/parsing errors
in the streaming path; change the catch (_) block in the structured output
handling (around structuredData, fullText, opts.structuredOutput and
DartBridgeStructuredOutput.shared.extractJson) to catch (e, st) and log the
error and stack (e.g., processLogger.error('Failed to extract/parse structured
output: $e', error: e, stackTrace: st) or using the project's logger), then
continue returning the plain text result; ensure you still don't rethrow so
behavior is unchanged but errors are recorded, and keep using
_normalizeStructuredData for the parsed payload.
examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart (1)

584-651: ListView with shrinkWrap: true inside SingleChildScrollView is a known anti-pattern.

The ListView at line 584 with shrinkWrap: true and NeverScrollableScrollPhysics inside the parent SingleChildScrollView (line 300) forces eager layout of all children, negating the benefit of ListView's lazy building. For this demo view with limited items it's acceptable, but a Column would be more idiomatic.

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

In
`@examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart`
around lines 584 - 651, The ListView named in this widget (currently using
shrinkWrap: true and NeverScrollableScrollPhysics) inside the existing
SingleChildScrollView causes eager layout and is an anti-pattern; replace the
ListView with a Column (or a SingleChildScrollView -> Padding -> Column pattern)
and keep the same children rendering logic that references _rawResponse,
_structuredData, _isGenerating and uses _buildSectionHeader so the UI and
conditionals remain identical but without shrinkWrap/physics settings.
sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/structured_output_types.dart (1)

112-134: StructuredOutputResult<T> is unused—consider removing or documenting its intended purpose.

This generic result type has no consumers in the codebase. The generation paths (generate() returns Future<LLMGenerationResult>, generateStream() returns Future<LLMStreamingResult>) both use LLMGenerationResult with its structuredData field instead. If this type is planned for future use, add documentation clarifying the intent; otherwise, remove it.

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

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/structured_output_types.dart`
around lines 112 - 134, The generic StructuredOutputResult<T> type is currently
unused; either remove its declaration or document its intended purpose and
planned consumers; specifically, if you plan to replace or augment existing
returns from generate() / generateStream(), explain how StructuredOutputResult
relates to LLMGenerationResult, LLMStreamingResult and their structuredData
field, otherwise delete the StructuredOutputResult<T> class to avoid dead code
and update any relevant exports or README references.
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart (1)

279-309: _fallbackValidate validates nothing but bracket boundaries; its try/catch is dead code.

The method labels itself "Simple JSON validation" but only checks whether the trimmed string starts/ends with {/} or [/]. A string like {invalid: garbage} passes. dart:convert's jsonDecode is already available to the project and is the correct tool here. Additionally, String.trim(), startsWith(), and endsWith() are non-throwing operations, making the surrounding try/catch unreachable dead code.

♻️ Proposed refactor using jsonDecode for real validation
+import 'dart:convert';

StructuredOutputValidationResult _fallbackValidate(String text) {
-   try {
-     // Simple JSON validation
-     final trimmed = text.trim();
-     if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
-       return const StructuredOutputValidationResult(
-         isValid: true,
-         containsJSON: true,
-         error: null,
-       );
-     }
-     if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
-       return const StructuredOutputValidationResult(
-         isValid: true,
-         containsJSON: true,
-         error: null,
-       );
-     }
-     return const StructuredOutputValidationResult(
-       isValid: false,
-       containsJSON: false,
-       error: 'No valid JSON found',
-     );
-   } catch (e) {
-     return StructuredOutputValidationResult(
-       isValid: false,
-       containsJSON: false,
-       error: e.toString(),
-     );
-   }
+   final extracted = _fallbackExtractJson(text);
+   if (extracted == null) {
+     return const StructuredOutputValidationResult(
+       isValid: false,
+       containsJSON: false,
+       error: 'No JSON found',
+     );
+   }
+   try {
+     jsonDecode(extracted);
+     return const StructuredOutputValidationResult(
+       isValid: true,
+       containsJSON: true,
+       error: null,
+     );
+   } catch (e) {
+     return StructuredOutputValidationResult(
+       isValid: false,
+       containsJSON: true,  // JSON fragment was found, just not parseable
+       error: e.toString(),
+     );
+   }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`
around lines 279 - 309, The _fallbackValidate function currently only checks
bracket characters and never truly validates JSON (and its try/catch is dead);
replace the bracket-only logic in _fallbackValidate with a real parse using
dart:convert's jsonDecode on the trimmed input, return
StructuredOutputValidationResult(isValid: true, containsJSON: true, error: null)
when jsonDecode succeeds, and catch FormatException (or any thrown error) to
return isValid: false, containsJSON: false, error: e.toString(); ensure you
still trim the input before decoding and preserve the
StructuredOutputValidationResult shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart`:
- Around line 248-273: The view misses early tokens because it subscribes to
streamResult.stream after generation has already emitted tokens to a broadcast
controller; update the producer (sdk.RunAnywhere.generateStream) to buffer
emitted tokens (the internal allTokens list used when collecting finalResult)
and, when creating the broadcast StreamController, replay buffered tokens to any
new listeners (e.g., in the controller's onListen or by returning a stream
wrapper that first emits allTokens then forwards live events) so that consumers
like _generateStream which await for streamResult.stream receive all prior
tokens as well as future tokens; adjust generateStream/streamResult.stream
behavior rather than changing the view subscription logic.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`:
- Around line 155-157: preparePrompt accepts includeSchemaInPrompt but the
fallback path (_fallbackPreparePrompt) ignores it and always inlines the schema;
update _fallbackPreparePrompt to accept a bool includeSchemaInPrompt parameter,
propagate that flag from both places where _fallbackPreparePrompt is called in
preparePrompt, and change the fallback logic inside _fallbackPreparePrompt to
conditionally embed the schema only when includeSchemaInPrompt is true
(otherwise return the prompt without inlined schema or with the schema excluded
according to existing C++ behavior). Ensure function/method signatures for
_fallbackPreparePrompt and its call sites (from preparePrompt) are updated
consistently.
- Around line 131-150: The current _fallbackExtractJson implementation uses
trimmed.indexOf('{') with trimmed.lastIndexOf('}') (and similarly for '['/']'),
which can capture trailing non-JSON text and produce invalid JSON; change it to
locate the first opener (use trimmed.indexOf on '{' then '[') and scan forward
tracking a depth counter (increment on opener, decrement on closer) until depth
returns to zero, then return trimmed.substring(startIndex, i+1); handle both
object ('{','}') and array ('[',']'), support nested brackets, and return null
if no matching closer is found.
- Around line 254-275: In validate(), free the C++-allocated strings returned in
RacStructuredOutputValidationStruct: after converting validation.errorMessage to
Dart via toDartString(), call rac_free on validation.errorMessage; read
validation.extractedJson into a Dart String (if not nullptr), set containsJSON
based on extractedJson != nullptr (not isValid), then call rac_free on
validation.extractedJson; only after consuming those C strings call
calloc.free(validationPtr) as you already do; keep the existing
exception/_fallbackValidate behavior unchanged.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/structured_output_types.dart`:
- Around line 36-67: The public duplicate types StreamToken and
StructuredOutputStreamResult<T> are shadowing the real implementations used
elsewhere (see stream_token.dart, stream_accumulator.dart,
structured_output_handler.dart, runanywhere.dart and generation_types.dart);
remove the redundant definitions from
lib/public/types/structured_output_types.dart and instead re-export or reference
the single source-of-truth types: export the StreamToken from
lib/features/llm/structured_output/stream_token.dart and use the same
StructuredOutputStreamResult<T>/LLMStreamingResult shape as defined in
generation_types.dart/structured_output_handler.dart (ensure the public API uses
Stream<String> if the implementation returns Stream<String>), updating any
callers or exports so all code imports the canonical type rather than the
duplicate.

---

Outside diff comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`:
- Around line 65-77: The system prompt wording is misleading: it currently asks
the model to "Convert this Data ... to JSON Schema" which requests schema
generation instead of producing data; update the prompt returned (the string
that uses originalPrompt, config.schema and instructions) to explicitly instruct
the model to generate JSON data conforming to the provided schema (e.g., replace
"Convert this Data ... to JSON Schema" with something like "Generate JSON data
that conforms to the following JSON Schema" and keep the directive "Output ONLY
the JSON object, nothing else."). Ensure the change is made where the prompt
string is constructed so originalPrompt, config.schema and instructions are
still interpolated and the final message enforces valid JSON-only output.
- Around line 247-313: The handler file defines duplicate types
(StructuredOutputValidation, StructuredOutputError, StructuredOutputConfig,
StructuredOutputStreamResult) that conflict with the public types exported via
types.dart; update the handler to either import and use the public types
(replace StructuredOutputConfig.type: Type with the public config which uses
typeName: String and switch the stream to Stream<StreamToken>) or rename the
internal definitions (e.g., HandlerStructuredOutputConfig,
HandlerStructuredOutputValidation, HandlerStructuredOutputError,
HandlerStructuredOutputStreamResult) to avoid future ambiguity; ensure all
references in this file (constructors, return types, method signatures) are
updated to the chosen approach so there are no duplicate symbol names.

---

Nitpick comments:
In
`@examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/components/ModelRequiredOverlay.kt`:
- Around line 25-30: Remove the wildcard import import
androidx.compose.material.icons.filled.* and replace it with a single explicit
import for Visibility (import androidx.compose.material.icons.filled.Visibility)
so Icons.Default.Visibility resolves; keep the existing explicit imports
(AutoAwesome, GraphicEq, Lock, Mic, VolumeUp) and ensure no other icons rely on
the wildcard before committing.

In
`@examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart`:
- Around line 584-651: The ListView named in this widget (currently using
shrinkWrap: true and NeverScrollableScrollPhysics) inside the existing
SingleChildScrollView causes eager layout and is an anti-pattern; replace the
ListView with a Column (or a SingleChildScrollView -> Padding -> Column pattern)
and keep the same children rendering logic that references _rawResponse,
_structuredData, _isGenerating and uses _buildSectionHeader so the UI and
conditionals remain identical but without shrinkWrap/physics settings.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`:
- Around line 279-309: The _fallbackValidate function currently only checks
bracket characters and never truly validates JSON (and its try/catch is dead);
replace the bracket-only logic in _fallbackValidate with a real parse using
dart:convert's jsonDecode on the trimmed input, return
StructuredOutputValidationResult(isValid: true, containsJSON: true, error: null)
when jsonDecode succeeds, and catch FormatException (or any thrown error) to
return isValid: false, containsJSON: false, error: e.toString(); ensure you
still trim the input before decoding and preserve the
StructuredOutputValidationResult shape.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/native/ffi_types.dart`:
- Around line 1093-1108: The two new FFI struct declarations use the `final
class` modifier while existing structs use `base class`; update the declarations
for RacStructuredOutputConfigStruct and RacStructuredOutputValidationStruct to
use `base class` instead of `final class` so they match the rest of the file and
maintain consistency for Struct subclasses.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`:
- Around line 1191-1203: Extract the duplicated effective system prompt logic
into a shared private helper (e.g., static String?
_effectiveSystemPrompt(LLMGenerationOptions opts)) and use it from both
generate() and generateStream(); the helper should take opts.systemPrompt, check
opts.structuredOutput, call
DartBridgeStructuredOutput.shared.getSystemPrompt(opts.structuredOutput!.schema)
when present, and prepend or replace the user prompt exactly as the current
logic in effectiveSystemPrompt does so existing behavior of
effectiveSystemPrompt in generate() and generateStream() is preserved.
- Around line 1234-1246: The current silent catch in the structured output
extraction (within the block checking opts.structuredOutput and calling
DartBridgeStructuredOutput.shared.extractJson(result.text)) swallows errors;
change the catch from catch (_) to catch (e, st) and log a debug-level message
including the error and stack trace plus context (e.g., result.text and the
extracted jsonString) before continuing; ensure you still return the plain text
result if parsing fails and keep the call to _normalizeStructuredData unchanged
when parsing succeeds.
- Around line 1408-1421: Silent catch is hiding JSON extraction/parsing errors
in the streaming path; change the catch (_) block in the structured output
handling (around structuredData, fullText, opts.structuredOutput and
DartBridgeStructuredOutput.shared.extractJson) to catch (e, st) and log the
error and stack (e.g., processLogger.error('Failed to extract/parse structured
output: $e', error: e, stackTrace: st) or using the project's logger), then
continue returning the plain text result; ensure you still don't rethrow so
behavior is unchanged but errors are recorded, and keep using
_normalizeStructuredData for the parsed payload.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/public/types/structured_output_types.dart`:
- Around line 112-134: The generic StructuredOutputResult<T> type is currently
unused; either remove its declaration or document its intended purpose and
planned consumers; specifically, if you plan to replace or augment existing
returns from generate() / generateStream(), explain how StructuredOutputResult
relates to LLMGenerationResult, LLMStreamingResult and their structuredData
field, otherwise delete the StructuredOutputResult<T> class to avoid dead code
and update any relevant exports or README references.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart (1)

1305-1312: ⚠️ Potential issue | 🔴 Critical

Compile error: duplicate modelId declaration and call to undefined getLoadedModel().

Line 1305 introduces final modelId = await getLoadedModel() ... inside generateStream(), then line 1312 re-declares final modelId = DartBridge.llm.currentModelId ?? 'unknown' in the same scope. Dart does not permit duplicate final declarations in the same block — this will not compile. getLoadedModel() is also not defined in RunAnywhere.

🐛 Remove the stray line 1305
    DateTime? firstTokenTime;

-   final modelId = await getLoadedModel() ?? throw SDKError.notInitialized();
    if (!DartBridge.llm.isLoaded) {
      throw SDKError.componentNotReady(
        'LLM model not loaded. Call loadModel() first.',
      );
    }

    final modelId = DartBridge.llm.currentModelId ?? 'unknown';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`
around lines 1305 - 1312, In generateStream(), remove the stray
undefined/duplicate declaration "final modelId = await getLoadedModel() ?? throw
SDKError.notInitialized();" and rely on the existing DartBridge.llm check and
"final modelId = DartBridge.llm.currentModelId ?? 'unknown'"; ensure the
SDKError.componentNotReady check remains intact (keep the isLoaded guard and
throw SDKError.componentNotReady if not), and delete any remaining references to
getLoadedModel() so there are no duplicate modelId declarations or calls to an
undefined function.
🧹 Nitpick comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart (1)

1191-1203: Duplicate system-prompt building logic; extract to a private helper.

Lines 1191–1203 and 1318–1330 are byte-for-byte identical. Extracting this into a private method eliminates the duplication and single-sources any future prompt-ordering changes.

♻️ Proposed refactor
+  /// Builds the effective system prompt by prepending JSON schema instructions
+  /// when [structuredOutput] is provided.
+  static String? _buildEffectiveSystemPrompt(LLMGenerationOptions opts) {
+    if (opts.structuredOutput == null) return opts.systemPrompt;
+    final jsonSystemPrompt =
+        DartBridgeStructuredOutput.shared.getSystemPrompt(opts.structuredOutput!.schema);
+    final userPrompt = opts.systemPrompt;
+    if (userPrompt != null && userPrompt.isNotEmpty) {
+      return '$jsonSystemPrompt\n\n$userPrompt';
+    }
+    return jsonSystemPrompt;
+  }

Then in both generate() and generateStream() replace the duplicated block with:

-   String? effectiveSystemPrompt = opts.systemPrompt;
-   if (opts.structuredOutput != null) {
-     final jsonSystemPrompt = DartBridgeStructuredOutput.shared.getSystemPrompt(
-       opts.structuredOutput!.schema,
-     );
-     if (effectiveSystemPrompt != null && effectiveSystemPrompt.isNotEmpty) {
-       effectiveSystemPrompt = '$jsonSystemPrompt\n\n$effectiveSystemPrompt';
-     } else {
-       effectiveSystemPrompt = jsonSystemPrompt;
-     }
-   }
+   final effectiveSystemPrompt = _buildEffectiveSystemPrompt(opts);

Also applies to: 1318-1330

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

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`
around lines 1191 - 1203, Extract the duplicated system-prompt assembly into a
private helper (e.g. _buildEffectiveSystemPrompt) and call it from both
generate() and generateStream(): move the logic that reads opts.systemPrompt,
checks opts.structuredOutput, calls
DartBridgeStructuredOutput.shared.getSystemPrompt(opts.structuredOutput!.schema)
and prepends or sets the effective system prompt into the new helper; update
both functions to replace their identical blocks with a call to this helper and
use its return value as effectiveSystemPrompt so all prompt-ordering logic is
single-sourced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`:
- Around line 1237-1245: The current broad catch in the JSON extraction blocks
(around DartBridgeStructuredOutput.shared.extractJson, jsonDecode, and
_normalizeStructuredData) swallows all Throwables including Error subclasses;
narrow the catch to Exception (and specific parsing exceptions such as
FormatException) and log the caught exception before returning the plain text
result so developer-visible errors aren't suppressed; apply the same change to
the other occurrence (the block around lines 1412–1420) so both
extraction/parsing paths only handle expected runtime exceptions and record/log
the error details.
- Around line 1879-1881: The branch that handles "parsed is Map" currently does
an unsafe cast via Map<String, dynamic>.from(parsed) which throws a TypeError
for non-String keys; replace this with a defensive conversion that iterates the
entries of parsed (the branch where "parsed is Map" and the usage of Map<String,
dynamic>.from(parsed)), building a new Map<String, dynamic> and only adding
entries whose keys are String (casting values as dynamic), so non-String keys
are ignored or handled explicitly (or log/throw a clearer error) instead of
allowing Map<String, dynamic>.from to throw at runtime.
- Around line 1155-1157: Remove the stray executable statement "final modelId =
await getLoadedModel()..." that sits at class-body scope (between doc comments)
— move any needed model lookup into the appropriate method (e.g., inside the
generate(...) implementation) and delete the class-level statement; then fix
both usages of the undefined getLoadedModel() by replacing them with the
project’s actual API that returns the loaded model id (search for methods/fields
like loadedModel, getLoadedModelId, currentModelId, or loadModel/getModel) and
ensure modelId is declared only once in the method scope (remove the duplicate
modelId declaration at the other site and reuse the single local variable inside
the generate function).

---

Outside diff comments:
In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`:
- Around line 1305-1312: In generateStream(), remove the stray
undefined/duplicate declaration "final modelId = await getLoadedModel() ?? throw
SDKError.notInitialized();" and rely on the existing DartBridge.llm check and
"final modelId = DartBridge.llm.currentModelId ?? 'unknown'"; ensure the
SDKError.componentNotReady check remains intact (keep the isLoaded guard and
throw SDKError.componentNotReady if not), and delete any remaining references to
getLoadedModel() so there are no duplicate modelId declarations or calls to an
undefined function.

---

Nitpick comments:
In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`:
- Around line 1191-1203: Extract the duplicated system-prompt assembly into a
private helper (e.g. _buildEffectiveSystemPrompt) and call it from both
generate() and generateStream(): move the logic that reads opts.systemPrompt,
checks opts.structuredOutput, calls
DartBridgeStructuredOutput.shared.getSystemPrompt(opts.structuredOutput!.schema)
and prepends or sets the effective system prompt into the new helper; update
both functions to replace their identical blocks with a call to this helper and
use its return value as effectiveSystemPrompt so all prompt-ordering logic is
single-sourced.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart (1)

79-98: ⚠️ Potential issue | 🟠 Major

StructuredOutputError.validationFailed is immediately swallowed and re-mapped to invalidJSON

Two related issues in this method:

  1. validationFailed is thrown and caught in the same try/catch: StructuredOutputError.validationFailed(...) is thrown inside the try block (lines 89–91), but the bare catch (e) on line 95 intercepts it and re-throws it as StructuredOutputError.invalidJSON. Callers that inspect the error type to distinguish a type-mismatch ("got array, expected object") from a malformed JSON string will always see invalidJSON, losing the semantic distinction entirely.

  2. extractJSON is outside the try block: The extractionFailed error thrown by extractJSON (line 82) escapes the catch guard, while all other failures are normalised to invalidJSON. This asymmetry means callers must handle two different error shapes from a single method.

🐛 Proposed fix: separate extraction from decode/validate, preserve error types
 T parseStructuredOutput<T>(
     String text, T Function(Map<String, dynamic>) fromJson) {
-  final jsonString = extractJSON(text);
-
   try {
+    final jsonString = extractJSON(text);         // extractionFailed propagates as-is
     final jsonData = jsonDecode(jsonString);

     if (jsonData is! Map<String, dynamic>) {
       throw StructuredOutputError.validationFailed(
         'Expected JSON object, got ${jsonData.runtimeType}',
       );
     }

     return fromJson(jsonData);
-  } catch (e) {
-    throw StructuredOutputError.invalidJSON(e.toString());
+  } on StructuredOutputError {
+    rethrow;                                      // preserve validationFailed / extractionFailed
+  } catch (e) {
+    throw StructuredOutputError.invalidJSON(e.toString()); // FormatException from jsonDecode
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`
around lines 79 - 98, parseStructuredOutput currently swallows
StructuredOutputError.validationFailed and lets extractJSON errors bypass the
catch; fix by wrapping both extraction and decoding/validation in controlled
try/catch logic in parseStructuredOutput: call extractJSON and jsonDecode inside
a try, then if jsonData is not Map throw StructuredOutputError.validationFailed;
in the catch, if the caught error is already a StructuredOutputError (e.g.,
validationFailed or extractionFailed) rethrow it unchanged, otherwise wrap
non-StructuredOutput errors with
StructuredOutputError.invalidJSON(e.toString()); this preserves
extractionFailed/validationFailed types and only maps unexpected decode errors
to invalidJSON.
🧹 Nitpick comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart (1)

1191-1203: Extract the duplicated effectiveSystemPrompt computation into a private helper.

The identical 12-line block appears in both generate() and generateStream(). A change to the system-prompt injection logic (e.g., separator, order, null handling) must be made in two places, making drift bugs likely.

♻️ Proposed refactor

Add a private static helper (e.g., after _inferFormat):

+ static String? _buildEffectiveSystemPrompt(LLMGenerationOptions opts) {
+   if (opts.structuredOutput == null) return opts.systemPrompt;
+   final jsonSystemPrompt =
+       DartBridgeStructuredOutput.shared.getSystemPrompt(
+         opts.structuredOutput!.schema,
+       );
+   final base = opts.systemPrompt;
+   if (base != null && base.isNotEmpty) {
+     return '$jsonSystemPrompt\n\n$base';
+   }
+   return jsonSystemPrompt;
+ }

Then replace both identical blocks in generate() and generateStream():

-   // Determine effective system prompt - add JSON conversion instructions if structuredOutput is provided
-   String? effectiveSystemPrompt = opts.systemPrompt;
-   if (opts.structuredOutput != null) {
-     final jsonSystemPrompt = DartBridgeStructuredOutput.shared.getSystemPrompt(
-       opts.structuredOutput!.schema,
-     );
-     // If user already provided a system prompt, prepend the JSON instructions
-     if (effectiveSystemPrompt != null && effectiveSystemPrompt.isNotEmpty) {
-       effectiveSystemPrompt = '$jsonSystemPrompt\n\n$effectiveSystemPrompt';
-     } else {
-       effectiveSystemPrompt = jsonSystemPrompt;
-     }
-   }
+   final effectiveSystemPrompt = _buildEffectiveSystemPrompt(opts);

Also applies to: 1318-1330

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

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`
around lines 1191 - 1203, Duplicate logic computing effectiveSystemPrompt in
generate() and generateStream() should be extracted into a private helper to
avoid drift; add a static helper (e.g.,
_buildEffectiveSystemPrompt(LLMGenerationOptions opts)) near _inferFormat that
returns opts.systemPrompt when opts.structuredOutput is null, otherwise calls
DartBridgeStructuredOutput.shared.getSystemPrompt(opts.structuredOutput!.schema)
and prepends it to a non-empty opts.systemPrompt with the current separator
"\n\n" (preserving order and null/empty handling), then replace the duplicated
blocks in generate() and generateStream() to call this helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`:
- Around line 66-71: The prompt text in structured_output_handler.dart is
ambiguous: change the template around originalPrompt and config.schema so it
asks for a JSON object that conforms to the given schema (not to "convert into a
JSON Schema") and remove the extra space after "Convert this Data". Update the
string that currently reads "Convert this Data :" and "to JSON Schema:" (used
with originalPrompt and ${config.schema}) to something like "Convert this data
into a JSON object that conforms to the following JSON Schema:" followed by
${config.schema}, ensuring the wording explicitly requests a JSON object that
validates against the schema.

---

Outside diff comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`:
- Around line 79-98: parseStructuredOutput currently swallows
StructuredOutputError.validationFailed and lets extractJSON errors bypass the
catch; fix by wrapping both extraction and decoding/validation in controlled
try/catch logic in parseStructuredOutput: call extractJSON and jsonDecode inside
a try, then if jsonData is not Map throw StructuredOutputError.validationFailed;
in the catch, if the caught error is already a StructuredOutputError (e.g.,
validationFailed or extractionFailed) rethrow it unchanged, otherwise wrap
non-StructuredOutput errors with
StructuredOutputError.invalidJSON(e.toString()); this preserves
extractionFailed/validationFailed types and only maps unexpected decode errors
to invalidJSON.

---

Duplicate comments:
In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`:
- Around line 1237-1246: The current try/catch around
DartBridgeStructuredOutput.shared.extractJson and
jsonDecode/_normalizeStructuredData uses catch (_) which swallows Error
subclasses; change the handlers to only catch Exception (or catch (e, s) and
rethrow if e is Error) so that AssertionError/TypeError/etc. bubble up while
still handling recoverable exceptions and logging the parsing failure; update
both occurrences (the block around
DartBridgeStructuredOutput.shared.extractJson/jsonDecode and the other similar
block at the later range) and reference structuredData, jsonDecode, and
_normalizeStructuredData when making the change.
- Around line 1879-1881: The current conversion Map<String,
dynamic>.from(parsed) is unsafe because parsed may be Map<dynamic, dynamic> and
will throw a TypeError; replace it with a defensive conversion that builds a new
Map<String, dynamic> by iterating parsed's entries and producing String keys
(either by keeping only entries where key is already a String or converting each
key via key.toString()), e.g. construct a new Map and for each (k, v) put map[k
is String ? k : k.toString()] = v; update the code paths around the parsed
variable where Map<String, dynamic>.from(parsed) is used (same block as the
"else if (parsed is Map)" branch) so the helper no longer throws for
non-String-keyed maps.

---

Nitpick comments:
In `@sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart`:
- Around line 1191-1203: Duplicate logic computing effectiveSystemPrompt in
generate() and generateStream() should be extracted into a private helper to
avoid drift; add a static helper (e.g.,
_buildEffectiveSystemPrompt(LLMGenerationOptions opts)) near _inferFormat that
returns opts.systemPrompt when opts.structuredOutput is null, otherwise calls
DartBridgeStructuredOutput.shared.getSystemPrompt(opts.structuredOutput!.schema)
and prepends it to a non-empty opts.systemPrompt with the current separator
"\n\n" (preserving order and null/empty handling), then replace the duplicated
blocks in generate() and generateStream() to call this helper.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart (1)

277-282: Redundant extractedJson != nullptr check — prefer the already-computed containsJson.

containsJson is computed at line 266 from the same pointer check; repeating validation.extractedJson != nullptr at line 277 is redundant and less readable.

♻️ Proposed simplification
-     if (validation.extractedJson != nullptr) {
+     if (containsJson) {
        lib.lookupFunction<Void Function(Pointer<Void>),
            void Function(Pointer<Void>)>('rac_free')(
          validation.extractedJson.cast<Void>(),
        );
      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`
around lines 277 - 282, The code redundantly re-checks validation.extractedJson
!= nullptr before calling the FFI free function; instead use the previously
computed containsJson boolean (from line where containsJson is set) to decide
freeing. Update the rac_free invocation block to test containsJson rather than
validation.extractedJson != nullptr so the free call
(lib.lookupFunction<'rac_free'...>(validation.extractedJson.cast<Void>())) runs
only when containsJson is true, keeping the pointer usage consistent and
improving readability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`:
- Around line 85-100: The catch-all currently wraps every non-FormatException
error as StructuredOutputError.invalidJSON, which hides
StructuredOutputError.validationFailed (and any StructuredOutputError from
fromJson); modify the try/catch in structured_output_handler.dart so that after
the on FormatException handler you either add a specific catch for
StructuredOutputError that rethrows it unchanged or, in the generic catch (e),
detect if e is a StructuredOutputError and rethrow e, otherwise wrap in
StructuredOutputError.invalidJSON (referencing jsonDecode(jsonString),
fromJson(jsonData), and StructuredOutputError). Ensure StructuredOutputError
instances are not converted to invalidJSON.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`:
- Around line 300-318: _fallbackValidate currently treats strings that merely
start/end with { } or [ ] as valid JSON causing false positives; update
_fallbackValidate to actually parse the text using jsonDecode (import
'dart:convert') within the try block, treat successful jsonDecode as isValid:
true and containsJSON: true, and catch FormatException (or any parse error) to
return isValid: false and containsJSON: false while preserving the error
message; remove the startsWith/endsWith heuristics and ensure the returned
StructuredOutputValidationResult uses the parse outcome and error details from
the caught exception.
- Around line 131-152: _fallbackExtractJson currently counts braces/brackets
even when they appear inside JSON string values, producing invalid truncated
JSON; update the implementation of _fallbackExtractJson to be string-aware by
tracking in-string state and escape characters (similar to
StructuredOutputHandler._findMatchingBrace) so that braces/brackets inside
quoted strings do not affect the depth counter, and after extracting a candidate
substring attempt a jsonDecode to validate it (return the decoded/valid string
or null on decode failure); also add import 'dart:convert'; at the top of the
file.
- Around line 334-345: Replace the duplicate internal class
StructuredOutputValidationResult with the existing public
StructuredOutputValidation type: remove the StructuredOutputValidationResult
declaration, import StructuredOutputValidation from
structured_output_types.dart, and update DartBridgeStructuredOutput.validate()
to return StructuredOutputValidation (ensuring its fields map directly to
isValid, containsJSON, error) so it matches
StructuredOutputHandler.validateStructuredOutput() and avoids duplicate
definitions.

---

Duplicate comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`:
- Around line 66-75: The prompt strings "Convert this data:" and "Use the
following JSON Schema:" in the prompt template (which references
$originalPrompt, ${config.schema}, and $instructions) have been corrected to ask
the model to output a JSON object; no code change is required—leave the prompt
template in structured_output_handler.dart (the prompt variable used by
StructuredOutputHandler) as-is and proceed with the approved changes.

---

Nitpick comments:
In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`:
- Around line 277-282: The code redundantly re-checks validation.extractedJson
!= nullptr before calling the FFI free function; instead use the previously
computed containsJson boolean (from line where containsJson is set) to decide
freeing. Update the rac_free invocation block to test containsJson rather than
validation.extractedJson != nullptr so the free call
(lib.lookupFunction<'rac_free'...>(validation.extractedJson.cast<Void>())) runs
only when containsJson is true, keeping the pointer usage consistent and
improving readability.

Comment on lines +85 to +100
try {
final jsonData = jsonDecode(jsonString);

if (jsonData is! Map<String, dynamic>) {
throw StructuredOutputError.validationFailed(
'Expected JSON object, got ${jsonData.runtimeType}',
);
}
if (jsonData is! Map<String, dynamic>) {
throw StructuredOutputError.validationFailed(
'Expected JSON object, got ${jsonData.runtimeType}',
);
}

return fromJson(jsonData);
return fromJson(jsonData);
} on FormatException catch (e) {
throw StructuredOutputError.invalidJSON(
'Invalid JSON format: ${e.message}');
} catch (e) {
throw StructuredOutputError.invalidJSON(e.toString());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

StructuredOutputError.validationFailed is incorrectly swallowed by the generic catch (e) and re-thrown as invalidJSON.

The StructuredOutputError.validationFailed thrown at line 89 is not a FormatException, so it bypasses on FormatException and is caught by the bare catch (e) at line 98, which unconditionally wraps it as StructuredOutputError.invalidJSON. Any caller distinguishing a type-mismatch failure (got JSON array, not object) from a parse failure will receive the wrong error type. The same problem applies to any StructuredOutputError thrown by the fromJson callback at line 94.

🐛 Proposed fix
  } catch (e) {
+   if (e is StructuredOutputError) rethrow;
    throw StructuredOutputError.invalidJSON(e.toString());
  }
📝 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
try {
final jsonData = jsonDecode(jsonString);
if (jsonData is! Map<String, dynamic>) {
throw StructuredOutputError.validationFailed(
'Expected JSON object, got ${jsonData.runtimeType}',
);
}
if (jsonData is! Map<String, dynamic>) {
throw StructuredOutputError.validationFailed(
'Expected JSON object, got ${jsonData.runtimeType}',
);
}
return fromJson(jsonData);
return fromJson(jsonData);
} on FormatException catch (e) {
throw StructuredOutputError.invalidJSON(
'Invalid JSON format: ${e.message}');
} catch (e) {
throw StructuredOutputError.invalidJSON(e.toString());
}
try {
final jsonData = jsonDecode(jsonString);
if (jsonData is! Map<String, dynamic>) {
throw StructuredOutputError.validationFailed(
'Expected JSON object, got ${jsonData.runtimeType}',
);
}
return fromJson(jsonData);
} on FormatException catch (e) {
throw StructuredOutputError.invalidJSON(
'Invalid JSON format: ${e.message}');
} catch (e) {
if (e is StructuredOutputError) rethrow;
throw StructuredOutputError.invalidJSON(e.toString());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart`
around lines 85 - 100, The catch-all currently wraps every non-FormatException
error as StructuredOutputError.invalidJSON, which hides
StructuredOutputError.validationFailed (and any StructuredOutputError from
fromJson); modify the try/catch in structured_output_handler.dart so that after
the on FormatException handler you either add a specific catch for
StructuredOutputError that rethrows it unchanged or, in the generic catch (e),
detect if e is a StructuredOutputError and rethrow e, otherwise wrap in
StructuredOutputError.invalidJSON (referencing jsonDecode(jsonString),
fromJson(jsonData), and StructuredOutputError). Ensure StructuredOutputError
instances are not converted to invalidJSON.

Comment on lines +131 to +152
String? _fallbackExtractJson(String text) {
final trimmed = text.trim();

for (final pair in [
('{', '}'),
('[', ']'),
]) {
final open = pair.$1;
final close = pair.$2;
final startIndex = trimmed.indexOf(open);
if (startIndex == -1) continue;

int depth = 0;
for (int i = startIndex; i < trimmed.length; i++) {
if (trimmed[i] == open) depth++;
if (trimmed[i] == close) depth--;
if (depth == 0) {
return trimmed.substring(startIndex, i + 1);
}
}
}
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

_fallbackExtractJson depth counter is confused by braces/brackets inside JSON string values.

A { or } within a quoted string (e.g., {"msg": "retry } failed"}) incorrectly increments/decrements depth, causing the method to return a truncated, invalid substring. Unlike StructuredOutputHandler._findMatchingBrace, this fallback has no string-quoting or escape tracking. Since the extracted substring is returned without a jsonDecode validation step, the caller silently receives invalid JSON.

🛡️ Proposed fix — add string-aware extraction
  String? _fallbackExtractJson(String text) {
    final trimmed = text.trim();

    for (final pair in [
      ('{', '}'),
      ('[', ']'),
    ]) {
      final open = pair.$1;
      final close = pair.$2;
      final startIndex = trimmed.indexOf(open);
      if (startIndex == -1) continue;

      int depth = 0;
+     bool inString = false;
+     bool escaped = false;
      for (int i = startIndex; i < trimmed.length; i++) {
+       if (escaped) { escaped = false; continue; }
+       if (trimmed[i] == '\\' && inString) { escaped = true; continue; }
+       if (trimmed[i] == '"') { inString = !inString; continue; }
+       if (inString) continue;
        if (trimmed[i] == open) depth++;
        if (trimmed[i] == close) depth--;
        if (depth == 0) {
-         return trimmed.substring(startIndex, i + 1);
+         final candidate = trimmed.substring(startIndex, i + 1);
+         try {
+           jsonDecode(candidate);
+           return candidate;
+         } catch (_) {
+           break; // not valid JSON, try next opener type
+         }
        }
      }
    }
    return null;
  }

This requires adding import 'dart:convert'; at the top of the file.

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

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`
around lines 131 - 152, _fallbackExtractJson currently counts braces/brackets
even when they appear inside JSON string values, producing invalid truncated
JSON; update the implementation of _fallbackExtractJson to be string-aware by
tracking in-string state and escape characters (similar to
StructuredOutputHandler._findMatchingBrace) so that braces/brackets inside
quoted strings do not affect the depth counter, and after extracting a candidate
substring attempt a jsonDecode to validate it (return the decoded/valid string
or null on decode failure); also add import 'dart:convert'; at the top of the
file.

Comment on lines +300 to +318
/// Fallback validation when C++ fails
StructuredOutputValidationResult _fallbackValidate(String text) {
try {
// Simple JSON validation
final trimmed = text.trim();
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
return const StructuredOutputValidationResult(
isValid: true,
containsJSON: true,
error: null,
);
}
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
return const StructuredOutputValidationResult(
isValid: true,
containsJSON: true,
error: null,
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

_fallbackValidate reports isValid: true without parsing — produces false positives for malformed JSON.

Checking startsWith('{') && endsWith('}') is not JSON validation. Input like {not valid json} passes all three positive-return branches and is flagged as both isValid: true and containsJSON: true. Downstream code that trusts this result to skip further parsing will silently consume corrupt output.

🛡️ Proposed fix — use jsonDecode for actual validation
  StructuredOutputValidationResult _fallbackValidate(String text) {
    try {
      final trimmed = text.trim();
-     if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
-       return const StructuredOutputValidationResult(
-         isValid: true,
-         containsJSON: true,
-         error: null,
-       );
-     }
-     if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
-       return const StructuredOutputValidationResult(
-         isValid: true,
-         containsJSON: true,
-         error: null,
-       );
-     }
+     final extracted = _fallbackExtractJson(trimmed);
+     if (extracted != null) {
+       try {
+         jsonDecode(extracted); // validate with dart:convert
+         return const StructuredOutputValidationResult(
+           isValid: true,
+           containsJSON: true,
+           error: null,
+         );
+       } on FormatException catch (e) {
+         return StructuredOutputValidationResult(
+           isValid: false,
+           containsJSON: true, // JSON was found but malformed
+           error: e.message,
+         );
+       }
+     }
      return const StructuredOutputValidationResult(
        isValid: false,
        containsJSON: false,
        error: 'No valid JSON found',
      );
    } catch (e) {

This requires adding import 'dart:convert'; at the top of the file.

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

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`
around lines 300 - 318, _fallbackValidate currently treats strings that merely
start/end with { } or [ ] as valid JSON causing false positives; update
_fallbackValidate to actually parse the text using jsonDecode (import
'dart:convert') within the try block, treat successful jsonDecode as isValid:
true and containsJSON: true, and catch FormatException (or any parse error) to
return isValid: false and containsJSON: false while preserving the error
message; remove the startsWith/endsWith heuristics and ensure the returned
StructuredOutputValidationResult uses the parse outcome and error details from
the caught exception.

Comment on lines +334 to +345
/// Structured output validation result
class StructuredOutputValidationResult {
final bool isValid;
final bool containsJSON;
final String? error;

const StructuredOutputValidationResult({
required this.isValid,
required this.containsJSON,
this.error,
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare StructuredOutputValidation fields (public type) vs StructuredOutputValidationResult (bridge type)
rg -n "class StructuredOutputValidation" --type=dart -A 10
rg -n "class StructuredOutputValidationResult" --type=dart -A 10

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 3908


🏁 Script executed:

#!/bin/bash
# Check if structured_output_types.dart is imported in dart_bridge_structured_output.dart
head -50 sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart | grep -E "^import|^export"

# Search for usage of StructuredOutputValidationResult
rg "StructuredOutputValidationResult" --type=dart

# Search for usage of StructuredOutputValidation (public type)
rg "StructuredOutputValidation[^R]" --type=dart | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 2944


🏁 Script executed:

#!/bin/bash
# Check if StructuredOutputValidationResult is exported from public APIs
rg "export.*StructuredOutputValidationResult" --type=dart
rg "StructuredOutputValidationResult" sdk/runanywhere-flutter/packages/runanywhere/lib/public/ --type=dart

# Check what is exported from the native bridge
cat sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart | grep -A 2 "^export "

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check the public API exports
cat sdk/runanywhere-flutter/packages/runanywhere/lib/runanywhere.dart | head -100

# Check if DartBridgeStructuredOutput is part of public API
rg "DartBridgeStructuredOutput" --type=dart | grep -v "\.dart:" | head -10
rg "DartBridgeStructuredOutput" --type=dart

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 2618


Replace StructuredOutputValidationResult with the public type StructuredOutputValidation.

StructuredOutputValidationResult duplicates StructuredOutputValidation from structured_output_types.dart with identical field definitions (isValid, containsJSON, error). Since this class is internal to the bridge and not part of the public API, consolidating onto the public type would eliminate unnecessary duplication, ensure type consistency between DartBridgeStructuredOutput.validate() and StructuredOutputHandler.validateStructuredOutput(), and reduce maintenance burden. Import StructuredOutputValidation and use it as the return type instead of defining a parallel duplicate.

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

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_structured_output.dart`
around lines 334 - 345, Replace the duplicate internal class
StructuredOutputValidationResult with the existing public
StructuredOutputValidation type: remove the StructuredOutputValidationResult
declaration, import StructuredOutputValidation from
structured_output_types.dart, and update DartBridgeStructuredOutput.validate()
to return StructuredOutputValidation (ensuring its fields map directly to
isValid, containsJSON, error) so it matches
StructuredOutputHandler.validateStructuredOutput() and avoids duplicate
definitions.

Copy link
Copy Markdown
Collaborator

@Siddhesh2377 Siddhesh2377 left a comment

Choose a reason for hiding this comment

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

Good Work @bagusindrayana 😊

@Siddhesh2377 Siddhesh2377 merged commit c0d01db into RunanywhereAI:main Feb 19, 2026
1 check passed
ManthanNimodiya pushed a commit to ManthanNimodiya/runanywhere-sdks that referenced this pull request Feb 23, 2026
* feat: implement structured output json for flutter

* refactor: add return structuredData in result generate, fix some type in ffi and bride

* refactor: add systemprompt from StructuredOutputHandler, impove system prompt

* update: example for structured output json

* Update examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* refactor: update structuredData type and fix layout in structured data example screen

* fix: remove redundant import icons

* Update sdk/runanywhere-flutter/packages/runanywhere/lib/features/llm/structured_output/structured_output_handler.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update sdk/runanywhere-flutter/packages/runanywhere/lib/public/runanywhere.dart

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix: remove duplicate class

* fix: remove duplicate StreamToken class, fix memory  leak issue, fix exception handle

---------

Co-authored-by: Sanchit Monga <sanchitmonga22@gmail.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants