Skip to content

Commit 6cd95fe

Browse files
committed
feat(mpp-idea): integrate CodingAgent from mpp-core for real agent execution
- Create JewelRenderer implementing CodingAgentRenderer with StateFlow - Refactor IdeaAgentViewModel to use CodingAgent instead of simulated responses - Update IdeaAgentApp to display timeline-based agent output - Add UI components for tool calls, errors, and task completion - Remove obsolete ChatMessage model (replaced by JewelRenderer.TimelineItem) The IDEA plugin now uses the same CodingAgent as CLI and Desktop apps, ensuring consistent behavior across all platforms.
1 parent b792e24 commit 6cd95fe

File tree

4 files changed

+495
-121
lines changed

4 files changed

+495
-121
lines changed

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/model/ChatMessage.kt

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
package cc.unitmesh.devins.idea.renderer
2+
3+
import cc.unitmesh.agent.render.BaseRenderer
4+
import cc.unitmesh.llm.compression.TokenInfo
5+
import kotlinx.coroutines.flow.MutableStateFlow
6+
import kotlinx.coroutines.flow.StateFlow
7+
import kotlinx.coroutines.flow.asStateFlow
8+
import kotlinx.coroutines.flow.update
9+
10+
/**
11+
* Jewel-compatible Renderer for IntelliJ IDEA plugin.
12+
*
13+
* Uses Kotlin StateFlow instead of Compose mutableStateOf to avoid
14+
* ClassLoader conflicts with IntelliJ's bundled Compose runtime.
15+
*
16+
* Implements CodingAgentRenderer interface from mpp-core.
17+
*/
18+
class JewelRenderer : BaseRenderer() {
19+
20+
// Timeline of all events (messages, tool calls, results)
21+
private val _timeline = MutableStateFlow<List<TimelineItem>>(emptyList())
22+
val timeline: StateFlow<List<TimelineItem>> = _timeline.asStateFlow()
23+
24+
// Current streaming output from LLM
25+
private val _currentStreamingOutput = MutableStateFlow("")
26+
val currentStreamingOutput: StateFlow<String> = _currentStreamingOutput.asStateFlow()
27+
28+
// Processing state
29+
private val _isProcessing = MutableStateFlow(false)
30+
val isProcessing: StateFlow<Boolean> = _isProcessing.asStateFlow()
31+
32+
// Iteration tracking
33+
private val _currentIteration = MutableStateFlow(0)
34+
val currentIteration: StateFlow<Int> = _currentIteration.asStateFlow()
35+
36+
private val _maxIterations = MutableStateFlow(100)
37+
val maxIterations: StateFlow<Int> = _maxIterations.asStateFlow()
38+
39+
// Current active tool call
40+
private val _currentToolCall = MutableStateFlow<ToolCallInfo?>(null)
41+
val currentToolCall: StateFlow<ToolCallInfo?> = _currentToolCall.asStateFlow()
42+
43+
// Error state
44+
private val _errorMessage = MutableStateFlow<String?>(null)
45+
val errorMessage: StateFlow<String?> = _errorMessage.asStateFlow()
46+
47+
// Task completion state
48+
private val _taskCompleted = MutableStateFlow(false)
49+
val taskCompleted: StateFlow<Boolean> = _taskCompleted.asStateFlow()
50+
51+
// Token tracking
52+
private val _totalTokenInfo = MutableStateFlow(TokenInfo())
53+
val totalTokenInfo: StateFlow<TokenInfo> = _totalTokenInfo.asStateFlow()
54+
55+
// Execution timing
56+
private var executionStartTime = 0L
57+
58+
// Data classes for timeline items
59+
sealed class TimelineItem(val timestamp: Long = System.currentTimeMillis()) {
60+
data class MessageItem(
61+
val role: MessageRole,
62+
val content: String,
63+
val tokenInfo: TokenInfo? = null,
64+
val itemTimestamp: Long = System.currentTimeMillis()
65+
) : TimelineItem(itemTimestamp)
66+
67+
data class ToolCallItem(
68+
val toolName: String,
69+
val params: String,
70+
val success: Boolean? = null,
71+
val output: String? = null,
72+
val executionTimeMs: Long? = null,
73+
val itemTimestamp: Long = System.currentTimeMillis()
74+
) : TimelineItem(itemTimestamp)
75+
76+
data class ErrorItem(
77+
val message: String,
78+
val itemTimestamp: Long = System.currentTimeMillis()
79+
) : TimelineItem(itemTimestamp)
80+
81+
data class TaskCompleteItem(
82+
val success: Boolean,
83+
val message: String,
84+
val iterations: Int,
85+
val itemTimestamp: Long = System.currentTimeMillis()
86+
) : TimelineItem(itemTimestamp)
87+
}
88+
89+
data class ToolCallInfo(
90+
val toolName: String,
91+
val params: String
92+
)
93+
94+
enum class MessageRole {
95+
USER, ASSISTANT, SYSTEM
96+
}
97+
98+
// BaseRenderer implementation
99+
100+
override fun renderIterationHeader(current: Int, max: Int) {
101+
_currentIteration.value = current
102+
_maxIterations.value = max
103+
}
104+
105+
override fun renderLLMResponseStart() {
106+
super.renderLLMResponseStart()
107+
_currentStreamingOutput.value = ""
108+
_isProcessing.value = true
109+
110+
if (executionStartTime == 0L) {
111+
executionStartTime = System.currentTimeMillis()
112+
}
113+
}
114+
115+
override fun renderLLMResponseChunk(chunk: String) {
116+
reasoningBuffer.append(chunk)
117+
118+
// Wait for more content if we detect an incomplete devin block
119+
if (hasIncompleteDevinBlock(reasoningBuffer.toString())) {
120+
return
121+
}
122+
123+
// Filter devin blocks and output clean content
124+
val processedContent = filterDevinBlocks(reasoningBuffer.toString())
125+
val cleanContent = cleanNewlines(processedContent)
126+
127+
_currentStreamingOutput.value = cleanContent
128+
}
129+
130+
override fun renderLLMResponseEnd() {
131+
super.renderLLMResponseEnd()
132+
133+
val content = _currentStreamingOutput.value.trim()
134+
if (content.isNotEmpty()) {
135+
addTimelineItem(
136+
TimelineItem.MessageItem(
137+
role = MessageRole.ASSISTANT,
138+
content = content,
139+
tokenInfo = _totalTokenInfo.value
140+
)
141+
)
142+
}
143+
144+
_currentStreamingOutput.value = ""
145+
_isProcessing.value = false
146+
}
147+
148+
override fun renderToolCall(toolName: String, paramsStr: String) {
149+
_currentToolCall.value = ToolCallInfo(toolName, paramsStr)
150+
151+
addTimelineItem(
152+
TimelineItem.ToolCallItem(
153+
toolName = toolName,
154+
params = paramsStr
155+
)
156+
)
157+
}
158+
159+
override fun renderToolResult(
160+
toolName: String,
161+
success: Boolean,
162+
output: String?,
163+
fullOutput: String?,
164+
metadata: Map<String, String>
165+
) {
166+
_currentToolCall.value = null
167+
168+
// Update the last tool call item with result
169+
_timeline.update { items ->
170+
items.mapIndexed { index, item ->
171+
if (index == items.lastIndex && item is TimelineItem.ToolCallItem && item.toolName == toolName) {
172+
item.copy(
173+
success = success,
174+
output = output ?: fullOutput
175+
)
176+
} else {
177+
item
178+
}
179+
}
180+
}
181+
}
182+
183+
override fun renderTaskComplete() {
184+
_taskCompleted.value = true
185+
}
186+
187+
override fun renderFinalResult(success: Boolean, message: String, iterations: Int) {
188+
addTimelineItem(
189+
TimelineItem.TaskCompleteItem(
190+
success = success,
191+
message = message,
192+
iterations = iterations
193+
)
194+
)
195+
_isProcessing.value = false
196+
executionStartTime = 0L
197+
}
198+
199+
override fun renderError(message: String) {
200+
_errorMessage.value = message
201+
addTimelineItem(TimelineItem.ErrorItem(message))
202+
}
203+
204+
override fun renderRepeatWarning(toolName: String, count: Int) {
205+
val warning = "Tool '$toolName' called repeatedly ($count times)"
206+
addTimelineItem(TimelineItem.ErrorItem(warning))
207+
}
208+
209+
override fun renderRecoveryAdvice(recoveryAdvice: String) {
210+
addTimelineItem(
211+
TimelineItem.MessageItem(
212+
role = MessageRole.ASSISTANT,
213+
content = "🔧 Recovery Advice:\n$recoveryAdvice"
214+
)
215+
)
216+
}
217+
218+
override fun renderUserConfirmationRequest(toolName: String, params: Map<String, Any>) {
219+
// For now, just render as a message
220+
addTimelineItem(
221+
TimelineItem.MessageItem(
222+
role = MessageRole.SYSTEM,
223+
content = "⚠️ Confirmation required for tool: $toolName"
224+
)
225+
)
226+
}
227+
228+
override fun updateTokenInfo(tokenInfo: TokenInfo) {
229+
_totalTokenInfo.value = tokenInfo
230+
}
231+
232+
// Public methods for UI interaction
233+
234+
fun addUserMessage(content: String) {
235+
addTimelineItem(
236+
TimelineItem.MessageItem(
237+
role = MessageRole.USER,
238+
content = content
239+
)
240+
)
241+
}
242+
243+
fun clearTimeline() {
244+
_timeline.value = emptyList()
245+
_currentStreamingOutput.value = ""
246+
_isProcessing.value = false
247+
_currentIteration.value = 0
248+
_errorMessage.value = null
249+
_taskCompleted.value = false
250+
_totalTokenInfo.value = TokenInfo()
251+
executionStartTime = 0L
252+
}
253+
254+
fun reset() {
255+
clearTimeline()
256+
}
257+
258+
private fun addTimelineItem(item: TimelineItem) {
259+
_timeline.update { it + item }
260+
}
261+
}
262+

0 commit comments

Comments
 (0)