feat(stage-ui,core-agent): show reasoning-delta events for thinking models (#1770)

This commit is contained in:
felix tremblay
2026-05-06 03:38:17 +08:00
committed by GitHub
parent c6e2f727d6
commit e50e140a70
2 changed files with 28 additions and 2 deletions
+1
View File
@@ -3,6 +3,7 @@ import type { CommonContentPart, CompletionToolCall, CompletionToolResult, Messa
export type StreamEvent
= | { type: 'text-delta', text: string }
| { type: 'reasoning-delta', text: string }
| ({ type: 'finish' } & any)
| ({ type: 'tool-call' } & CompletionToolCall)
| (CompletionToolResult & { type: 'tool-error' })
+27 -2
View File
@@ -28,6 +28,11 @@ import { useAiriCardStore } from './modules/airi-card'
import { useAutonomousArtistryStore } from './modules/artistry-autonomous'
import { useConsciousnessStore } from './modules/consciousness'
// updateUI structuredClones the whole message, which can stall the main thread
// when streams emit token-per-event. Shared by the parser's literal threshold and
// the reasoning-delta throttle so both refresh at the same cadence.
const STREAMING_UI_FLUSH_CHUNK_SIZE = 24
// Prepends a literal text fragment to a message's content. Handles both the
// shorthand string form and the array-of-parts form. When the first part is
// already text, it merges into that part to keep the part count stable for
@@ -303,13 +308,14 @@ export const useChatOrchestratorStore = defineStore('chat-orchestrator', () => {
const finalCategorization = categorizeResponse(fullText, activeProvider.value)
const reasoningContentField = buildingMessage.categorization?.reasoning?.trim()
buildingMessage.categorization = {
speech: finalCategorization.speech,
reasoning: finalCategorization.reasoning,
reasoning: reasoningContentField || finalCategorization.reasoning,
}
updateUI()
},
minLiteralEmitLength: 24,
minLiteralEmitLength: STREAMING_UI_FLUSH_CHUNK_SIZE,
})
const toolCallQueue = createQueue<ChatSlices>({
@@ -475,6 +481,25 @@ export const useChatOrchestratorStore = defineStore('chat-orchestrator', () => {
fullText += event.text
await parser.consume(event.text)
break
case 'reasoning-delta': {
if (shouldAbort())
return
const { reasoning = '' } = buildingMessage.categorization ?? {}
const nextReasoning = reasoning + event.text
buildingMessage.categorization = {
// Mirror in-flight text content so categorization stays shape-consistent with onEnd.
speech: typeof buildingMessage.content === 'string' ? buildingMessage.content : '',
reasoning: nextReasoning,
}
// Match text-delta's batching cadence; first chunk fires so the panel appears immediately.
const crossesBoundary
= Math.floor(nextReasoning.length / STREAMING_UI_FLUSH_CHUNK_SIZE)
> Math.floor(reasoning.length / STREAMING_UI_FLUSH_CHUNK_SIZE)
if (!reasoning || crossesBoundary)
updateUI()
break
}
case 'finish':
break
case 'error':