Skip to content

Commit d8fed0d

Browse files
fix: emit reasoning-end before text-end in chat completions stream
Close AiMsgReasoningEnd inline on first content delta instead of deferring to post-loop cleanup. This ensures SSE event order matches the Anthropic backend: reasoning-start → delta×N → reasoning-end → text-start → delta×M → text-end. Fallback reasoning-end kept for the edge case where the stream ends during reasoning with no content ever arriving (e.g. max_tokens).
1 parent 821e105 commit d8fed0d

1 file changed

Lines changed: 9 additions & 3 deletions

File tree

pkg/aiusechat/openaichat/openaichat-backend.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ func processChatStream(
162162

163163
choice := chunk.Choices[0]
164164
if choice.Delta.Content != "" {
165+
if reasoningStarted {
166+
reasoningStarted = false
167+
_ = sseHandler.AiMsgReasoningEnd(msgID)
168+
}
165169
if !textStarted {
166170
_ = sseHandler.AiMsgTextStart(textID)
167171
textStarted = true
@@ -261,12 +265,14 @@ func processChatStream(
261265
assistantMsg.Message.Content = textBuilder.String()
262266
}
263267

264-
if textStarted {
265-
_ = sseHandler.AiMsgTextEnd(textID)
266-
}
268+
// reasoning-end is emitted inline on first content delta (if reasoning was active);
269+
// if no content ever arrived (e.g. max_tokens during reasoning), close it here.
267270
if reasoningStarted {
268271
_ = sseHandler.AiMsgReasoningEnd(msgID)
269272
}
273+
if textStarted {
274+
_ = sseHandler.AiMsgTextEnd(textID)
275+
}
270276
_ = sseHandler.AiMsgFinishStep()
271277
if stopKind != uctypes.StopKindToolUse {
272278
_ = sseHandler.AiMsgFinish(finishReason, nil)

0 commit comments

Comments
 (0)