Skip to content

Comments

feat(mpp-idea): add IdeaDevInBlockRenderer for devin block support#20

Merged
phodal merged 2 commits intomasterfrom
feat/add-idea-devin-block-renderer
Dec 1, 2025
Merged

feat(mpp-idea): add IdeaDevInBlockRenderer for devin block support#20
phodal merged 2 commits intomasterfrom
feat/add-idea-devin-block-renderer

Conversation

@phodal
Copy link
Member

@phodal phodal commented Dec 1, 2025

Summary

Add missing devin block renderer to IdeaSketchRenderer to achieve feature parity with mpp-ui SketchRenderer.

Changes

New File: IdeaDevInBlockRenderer.kt

  • Parses devin blocks using ToolCallParser from mpp-core
  • Renders tool calls in an expandable card format with Jewel theming
  • Shows tool name with icon, truncated parameter details
  • Supports expand/collapse for full JSON parameter view
  • Falls back to IdeaCodeBlockRenderer for incomplete content or parsing failures

Modified: IdeaSketchRenderer.kt

  • Added "devin" case in the when block to route devin content to IdeaDevInBlockRenderer
  • Updated doc comment to include DevIn in the list of supported block types

Feature Parity

Block Type mpp-ui SketchRenderer IdeaSketchRenderer
markdown, md, "" MarkdownSketchRenderer SimpleJewelMarkdown ✅
diff, patch DiffSketchRenderer IdeaDiffRenderer ✅
thinking ThinkingBlockRenderer IdeaThinkingBlockRenderer ✅
walkthrough WalkthroughBlockRenderer IdeaWalkthroughBlockRenderer ✅
mermaid, mmd MermaidBlockRenderer MermaidDiagramView ✅
devin DevInBlockRenderer IdeaDevInBlockRenderer ✅ (NEW)
else (code) CodeBlockRenderer IdeaCodeBlockRenderer ✅

IdeaSketchRenderer now has complete feature parity with SketchRenderer from mpp-ui.


Pull Request opened by Augment Code with guidance from the PR author

Summary by CodeRabbit

  • New Features
    • Added rendering support for DevIn blocks with visualized tool calls: each tool shows name, icon, a concise inline detail string, and expandable parameter details displayed as formatted JSON.
    • Added support for a new "devin" code-fence language: DevIn blocks render full-width with spacing; incomplete blocks or blocks with no tool calls fall back to a DevIn code block display.

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

Add missing devin block renderer to IdeaSketchRenderer to achieve feature
parity with mpp-ui SketchRenderer.

Changes:
- Create IdeaDevInBlockRenderer.kt with Jewel theming
  - Parses devin blocks using ToolCallParser
  - Renders tool calls in expandable card format
  - Shows tool name, icon, and truncated parameters
  - Supports expand/collapse for full JSON parameter view
  - Falls back to IdeaCodeBlockRenderer for incomplete content
- Update IdeaSketchRenderer.kt to route 'devin' blocks to new renderer
- Update doc comment to reflect new DevIn support

This ensures IdeaSketchRenderer supports all block types that
SketchRenderer in mpp-ui supports:
- markdown, md, empty -> SimpleJewelMarkdown
- diff, patch -> IdeaDiffRenderer
- thinking -> IdeaThinkingBlockRenderer
- walkthrough -> IdeaWalkthroughBlockRenderer
- mermaid, mmd -> MermaidDiagramView
- devin -> IdeaDevInBlockRenderer (NEW)
- else -> IdeaCodeBlockRenderer
@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

Adds a new Kotlin Composable IdeaDevInBlockRenderer to parse and render "devin" code-fence tool calls with expandable JSON parameter views, and integrates it into IdeaSketchRenderer to handle devin fences; falls back to code-block rendering when parsing fails or block is incomplete.

Changes

Cohort / File(s) Summary
New DevIn Block Renderer
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt
Adds IdeaDevInBlockRenderer(devinContent: String, isComplete: Boolean, modifier: Modifier = Modifier). Parses tool calls via ToolCallParser, renders IdeaDevInToolItem entries with header, icon, inline details, and expandable JSON-formatted parameters. Includes helpers: detail formatting, truncation, JSON formatting/escaping. Falls back to IdeaCodeBlockRenderer when incomplete or no tool calls.
Sketch Renderer Integration
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt
Adds handling for code-fence language "devin" in RenderResponse: routes non-blank devin fences to IdeaDevInBlockRenderer with full-width modifier and spacing; preserves existing branches for other fence types.

Sequence Diagram

sequenceDiagram
    actor User
    participant IdeaSketchRenderer
    participant IdeaDevInBlockRenderer
    participant ToolCallParser
    participant IdeaDevInToolItem
    participant UI

    User->>IdeaSketchRenderer: Requests render (code fence language="devin")
    IdeaSketchRenderer->>IdeaDevInBlockRenderer: Instantiate with content & isComplete

    alt block is complete
        IdeaDevInBlockRenderer->>ToolCallParser: Parse tool calls from content
        ToolCallParser-->>IdeaDevInBlockRenderer: Parsed tool calls

        alt tool calls found
            loop each tool call
                IdeaDevInBlockRenderer->>IdeaDevInBlockRenderer: Resolve paths, format details, truncate
                IdeaDevInBlockRenderer->>IdeaDevInToolItem: Create item (name, icon, details, params)
                IdeaDevInToolItem->>UI: Render header (name, icon, inline details)
                opt params exist
                    IdeaDevInToolItem->>UI: Render expandable JSON params on expand
                end
            end
        else no tool calls
            IdeaDevInBlockRenderer->>UI: Render as devin code block (fallback)
        end
    else block is incomplete
        IdeaDevInBlockRenderer->>UI: Render as devin code block (fallback)
    end

    UI-->>User: Display rendered output
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review ToolCallParser integration and error handling for malformed tool calls
  • Verify path resolution / absolute path logic and workspace-root edge cases
  • Inspect UI state (expand/collapse) for correct Compose state management and recomposition behavior
  • Confirm fallback rendering correctness when parsing yields no tool calls or block incomplete
  • Validate JSON formatting/escaping for nested structures and special characters

Poem

🐰 I hopped into code with a curious twitch,
Parsing DevIn calls, one tiny stitch,
Carrots of params in JSON laid bare,
Tap to expand — delight in the air,
If parsing trips, I cuddle a code block with care.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding a new DevIn block renderer component to the mpp-idea module.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-idea-devin-block-renderer

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

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 2 suggestions posted.

Comment augment review to trigger a new review at any time.

if (workspaceRoot == null) return relativePath

// If already an absolute path, return as-is
if (relativePath.startsWith("/") || relativePath.matches(Regex("^[A-Za-z]:.*"))) {
Copy link

Choose a reason for hiding this comment

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

On Windows, absolute paths can start with a backslash (e.g., "\\server..." UNC or "\path..."), so treating only "/" or drive-letter prefixes as absolute may misclassify paths and incorrectly prepend the workspace root. Consider handling leading backslashes as absolute as well.

🤖 Was this useful? React with 👍 or 👎

appendLine("{")
params.entries.forEachIndexed { index, (key, value) ->
append(" \"$key\": ")
append("\"${value.replace("\"", "\\\"")}\"")
Copy link

Choose a reason for hiding this comment

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

The manual JSON fallback only escapes double quotes; consider also escaping backslashes and control characters (e.g., newline, tab) to avoid producing invalid JSON in the expanded parameters view.

🤖 Was this useful? React with 👍 or 👎

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (3)

98-196: Consider either surfacing filePath / toolType or dropping them from IdeaDevInToolItem

Right now filePath and toolType are passed in but not used inside IdeaDevInToolItem, while you already pay the cost of resolving the path and mapping the tool type. Either:

  • Render one of these (e.g., show a truncated filePath subtitle or use toolType to vary the icon), or
  • Remove the parameters and their computation until you actually need them.

That will keep the composable’s API and upstream logic focused on what’s currently displayed.


201-213: Make resolveAbsolutePath OS‑aware instead of manually joining with /

The current implementation mixes separators (e.g., C:\root/relative) and reimplements path joining logic. Since this is IDEA‑specific/JVM code, you can lean on java.nio.file.Paths for more robust behavior and cleaner code.

Example diff for this function:

-private fun resolveAbsolutePath(relativePath: String?, workspaceRoot: String?): String? {
-    if (relativePath == null) return null
-    if (workspaceRoot == null) return relativePath
-
-    // If already an absolute path, return as-is
-    if (relativePath.startsWith("/") || relativePath.matches(Regex("^[A-Za-z]:.*"))) {
-        return relativePath
-    }
-
-    // Combine workspace root with relative path
-    val separator = if (workspaceRoot.endsWith("/") || workspaceRoot.endsWith("\\")) "" else "/"
-    return "$workspaceRoot$separator$relativePath"
-}
+private fun resolveAbsolutePath(relativePath: String?, workspaceRoot: String?): String? {
+    if (relativePath.isNullOrBlank()) return null
+    if (workspaceRoot.isNullOrBlank()) return relativePath
+
+    // If already an absolute path, return as-is
+    if (relativePath.startsWith("/") || relativePath.matches(Regex("^[A-Za-z]:.*"))) {
+        return relativePath
+    }
+
+    // Combine workspace root with relative path in an OS-aware way
+    return java.nio.file.Paths.get(workspaceRoot, relativePath).toString()
+}

(You’d also need import java.nio.file.Paths at the top of the file.)


238-262: Hoist Json configuration to a reusable instance and keep JSON formatting simple

formatParamsAsJson currently allocates a new Json { prettyPrint = true } instance on every call. You can avoid repeated allocations and keep things clearer by reusing a single configured instance:

// Near the top of the file
private val PrettyJson = Json {
    prettyPrint = true
}

and then:

private fun formatParamsAsJson(params: Map<String, String>): String {
    return try {
        PrettyJson.encodeToString(
            kotlinx.serialization.serializer(),
            params
        )
    } catch (e: Exception) {
        // existing manual fallback
    }
}

This keeps behavior the same while being a bit more efficient and explicit about the JSON configuration. Please double‑check against the kotlinx-serialization-json docs for your version to ensure this serializer() usage matches the expected API surface.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c16a42 and e435d1c.

📒 Files selected for processing (2)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

Use expect/actual for platform-specific code in KMP projects (e.g., file I/O on JVM/JS/Wasm)

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt
🧬 Code graph analysis (2)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaCodeBlockRenderer.kt (1)
  • IdeaCodeBlockRenderer (18-52)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (1)
  • IdeaDevInBlockRenderer (32-91)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build (241)
  • GitHub Check: Build (223)
🔇 Additional comments (4)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt (2)

14-26: Doc update correctly reflects DevIn support

The KDoc list of handled block types now includes DevIn, matching the new "devin" branch below. No further changes needed here.


102-111: DevIn branch integration looks consistent with existing renderers

Routing "devin" fences to IdeaDevInBlockRenderer with blockIsComplete and full‑width modifier mirrors the patterns used for other structured block types (thinking, walkthrough, mermaid). The streaming case is handled via blockIsComplete, relying on the renderer to fall back to a code block when incomplete.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (2)

32-91: DevIn block renderer behavior and Compose usage look solid

The separation between complete and streaming states, using remember { ToolCallParser() } plus remember(devinContent) for parsing, and falling back to IdeaCodeBlockRenderer when there are no tool calls or the block is incomplete, is clean and should be efficient. The logic aligns with the PR description (interactive view on completion, code‑block view while streaming).


218-233: Details formatting and truncation logic are straightforward

formatToolCallDetails + truncateValue give a readable, bounded summary string for the header row, with the full data still available via the expanded JSON view. This is a reasonable balance between information density and layout stability.

Address review feedback from Augment Code and CodeRabbit:

1. Remove unused parameters from IdeaDevInToolItem:
   - Removed filePath and toolType parameters that were passed but not used
   - Removed associated computation (resolveAbsolutePath, ToolType.fromName)
   - Removed unused imports (ToolType, WorkspaceManager, Paths)

2. Hoist Json configuration to reusable instance:
   - Added PrettyJson val at module level to avoid repeated allocations
   - Updated formatParamsAsJson to use the hoisted instance

3. Fix JSON escaping in fallback formatter:
   - Added escapeJsonString helper function
   - Properly escape backslashes, quotes, newlines, carriage returns, and tabs
   - This prevents invalid JSON in the expanded parameters view
@phodal phodal merged commit 0e4bb1a into master Dec 1, 2025
3 of 6 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (1)

136-145: Consider using the truncateValue helper for consistency.

Line 138 hard-codes .take(60), while truncateValue at line 199 uses a default of 100 characters. This inconsistency could be confusing for future maintainers.

Apply this diff to use the existing helper:

                 // Details (truncated parameters)
                 if (details.isNotEmpty() && !expanded) {
                     Text(
-                        text = details.take(60) + if (details.length > 60) "..." else "",
+                        text = truncateValue(details, maxLength = 60),
                         style = JewelTheme.defaultTextStyle.copy(
                             fontSize = 11.sp,
                             color = JewelTheme.globalColors.text.info.copy(alpha = 0.7f)
                         ),
                         modifier = Modifier.weight(1f),
                         maxLines = 1
                     )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e435d1c and e69d23e.

📒 Files selected for processing (1)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

Use expect/actual for platform-specific code in KMP projects (e.g., file I/O on JVM/JS/Wasm)

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt
🧬 Code graph analysis (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (2)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaCodeBlockRenderer.kt (1)
  • IdeaCodeBlockRenderer (18-52)
mpp-core/src/commonMain/kotlin/cc/unitmesh/devins/test/AstFormatter.kt (1)
  • appendLine (206-209)
🔇 Additional comments (4)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt (4)

1-26: LGTM!

The imports are appropriate, and the reusable PrettyJson instance with pretty-print configuration is a good pattern for consistent JSON formatting throughout the file.


190-205: LGTM!

The helper functions formatToolCallDetails and truncateValue are clear and correctly implemented.


237-244: LGTM!

The escapeJsonString function properly handles backslashes, quotes, and control characters for valid JSON string escaping.


44-44: Remove this suggestion—the remember call is necessary.

ToolCallParser instantiates a DevinBlockParser() internally as a field, which has initialization costs. While the parser itself is stateless, the remember call appropriately prevents recreating these dependencies on every recomposition. The test suite pattern of reusing a single parser instance confirms this is the intended usage.

Likely an incorrect or invalid review comment.

Comment on lines +210 to +232
private fun formatParamsAsJson(params: Map<String, String>): String {
return try {
PrettyJson.encodeToString(
kotlinx.serialization.serializer(),
params
)
} catch (e: Exception) {
// Fallback to manual formatting with proper JSON escaping
buildString {
appendLine("{")
params.entries.forEachIndexed { index, (key, value) ->
append(" \"${escapeJsonString(key)}\": ")
append("\"${escapeJsonString(value)}\"")
if (index < params.size - 1) {
appendLine(",")
} else {
appendLine()
}
}
append("}")
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix incorrect serializer usage.

Lines 212-215 call kotlinx.serialization.serializer() without a type parameter, which will fail at runtime. The encodeToString function with an explicit serializer parameter expects the serializer to match the value type.

Apply this diff to fix the serialization:

 private fun formatParamsAsJson(params: Map<String, String>): String {
     return try {
-        PrettyJson.encodeToString(
-            kotlinx.serialization.serializer(),
-            params
-        )
+        // Use the inline reified version which infers the serializer
+        PrettyJson.encodeToString(params)
     } catch (e: Exception) {
         // Fallback to manual formatting with proper JSON escaping
         buildString {

Alternatively, if an explicit serializer is needed:

+import kotlinx.serialization.builtins.MapSerializer
+import kotlinx.serialization.builtins.serializer
+
 private fun formatParamsAsJson(params: Map<String, String>): String {
     return try {
         PrettyJson.encodeToString(
-            kotlinx.serialization.serializer(),
+            MapSerializer(String.serializer(), String.serializer()),
             params
         )
📝 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
private fun formatParamsAsJson(params: Map<String, String>): String {
return try {
PrettyJson.encodeToString(
kotlinx.serialization.serializer(),
params
)
} catch (e: Exception) {
// Fallback to manual formatting with proper JSON escaping
buildString {
appendLine("{")
params.entries.forEachIndexed { index, (key, value) ->
append(" \"${escapeJsonString(key)}\": ")
append("\"${escapeJsonString(value)}\"")
if (index < params.size - 1) {
appendLine(",")
} else {
appendLine()
}
}
append("}")
}
}
}
private fun formatParamsAsJson(params: Map<String, String>): String {
return try {
// Use the inline reified version which infers the serializer
PrettyJson.encodeToString(params)
} catch (e: Exception) {
// Fallback to manual formatting with proper JSON escaping
buildString {
appendLine("{")
params.entries.forEachIndexed { index, (key, value) ->
append(" \"${escapeJsonString(key)}\": ")
append("\"${escapeJsonString(value)}\"")
if (index < params.size - 1) {
appendLine(",")
} else {
appendLine()
}
}
append("}")
}
}
}
🤖 Prompt for AI Agents
In
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaDevInBlockRenderer.kt
around lines 210 to 232, the call to kotlinx.serialization.serializer() is
untyped and will fail at runtime; replace it with an explicit Map<String,
String> serializer and pass that to PrettyJson.encodeToString (for example use
MapSerializer(String.serializer(), String.serializer()) or the equivalent typed
serializer for Map<String, String>), keeping the same params argument and
imports for MapSerializer and String.serializer(); keep the existing catch
fallback unchanged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant