从 Kimi 的日志可以看到大量的 AgentThoughtChunk 事件(逐字流式输出):
{"update_type":"AgentThoughtChunk", "content":"用户"}
{"update_type":"AgentThoughtChunk", "content":"想要"}
{"update_type":"AgentThoughtChunk", "content":"我"}
...但在 UI 上,这些 thinking 内容完全没有显示在 timeline 中。
当 renderThinkingChunk(isEnd=true) 被调用时(从 thinking 转换到 message 时),代码只是设置 _isThinking = false,但从未把积累的 thinking 内容保存到 timeline!
// OLD CODE - thinking content lost!
if (isEnd) {
_isThinking = false // ❌ 内容丢失
}结果:
- 在流式输出期间,thinking 内容在临时状态
_currentThinkingOutput中显示 - 一旦转换到 message 阶段,临时状态被清空
- Timeline 中没有任何 thinking 记录
在 RendererModels.kt 中添加:
data class ThinkingItem(
val content: String,
override val timestamp: Long = Platform.getCurrentTimestamp(),
override val id: String = generateId()
) : TimelineItem(timestamp, id)添加两个字段:
_currentThinkingOutput: 仅保留最后 5 行用于流式显示_fullThinkingContent: 完整的 thinking 内容(用于保存到 timeline)
// Full thinking content (not trimmed) for saving to timeline
private var _fullThinkingContent = StringBuilder()override fun renderThinkingChunk(chunk: String, isStart: Boolean, isEnd: Boolean) {
if (isStart) {
_currentThinkingOutput = ""
_fullThinkingContent = StringBuilder() // ✅ 初始化完整内容
_isThinking = true
}
// Append to full content (not trimmed)
_fullThinkingContent.append(chunk) // ✅ 累积所有内容
// ... display logic (trim to 5 lines) ...
if (isEnd) {
// Save FULL thinking content to timeline
val fullContent = _fullThinkingContent.toString().trim()
if (fullContent.isNotEmpty()) {
_timeline.add(
TimelineItem.ThinkingItem(
content = fullContent, // ✅ 保存完整内容
timestamp = System.currentTimeMillis()
)
)
}
_isThinking = false
_currentThinkingOutput = ""
_fullThinkingContent = StringBuilder()
}
}在 AgentMessageList.kt 的 RenderMessageItem 中添加:
is TimelineItem.ThinkingItem -> {
ThinkingBlockRenderer(
thinkingContent = timelineItem.content,
isComplete = true, // 已完成的 thinking
modifier = Modifier.fillMaxWidth()
)
}添加了 3 个地方的 ThinkingItem 分支:
toMessageMetadata()- 返回null(不持久化)getTimelineSnapshot()- 返回null(不作为消息保存)AgentMessageList.RenderMessageItem- 渲染为 thinking 块
Timeline:
USER: "画一个项目的架构图"
ASSISTANT: "我来帮你画这个项目的架构图..."
[Thinking content lost - 用户想要我画一个项目的架构图...]
Timeline:
USER: "画一个项目的架构图"
THINKING: "用户想要我画一个项目的架构图。首先我需要了解这个项目的结构和架构。让我先看看当前项目的内容和结构。" [collapsible]
Shell: find ... -> Failed
ReadFile: README.md -> Success
ReadFile: build.gradle -> Success
ASSISTANT: "我来帮你画这个项目的架构图,首先让我梳理一下..."
SUCCESS: ACP finished: END_TURN
1. AgentThoughtChunk("用户") arrives
→ _fullThinkingContent.append("用户")
→ _currentThinkingOutput = "用户" (display)
2. AgentThoughtChunk("想要") arrives
→ _fullThinkingContent.append("想要")
→ _currentThinkingOutput = "用户想要" (display)
... many more chunks ...
N. AgentMessageChunk("我") arrives
→ renderThinkingChunk(isEnd=true)
→ Save _fullThinkingContent (complete) to timeline as ThinkingItem ✅
→ _isThinking = false
→ renderLLMResponseStart() (start message section)
-
mpp-core/.../RendererModels.kt- Added
TimelineItem.ThinkingItemdata class
- Added
-
mpp-ui/.../ComposeRenderer.kt- Added
_fullThinkingContentfield - Modified
renderThinkingChunk()to save to timeline onisEnd - Added
ThinkingItembranches to 3 when expressions
- Added
-
mpp-ui/.../AgentMessageList.kt- Added
ThinkingItemrendering branch - Added import for
ThinkingBlockRenderer
- Added
✅ Compiled successfully ✅ Thinking content now saved to timeline ✅ UI can render thinking blocks ✅ Ready for testing with Kimi ACP
Run the app and send "画一个项目的架构图" to Kimi. You should now see:
- ✅ Thinking content displayed during streaming (last 5 lines)
- ✅ Complete thinking content saved to timeline as a collapsible block
- ✅ Message content displayed after thinking ends