转换器: 修复 Responses 流式 response.completed 缺 usage

- 上游 chat 流 finish_reason 块先于 usage 块, 原实现在 finish 时即发
  response.completed 导致 usage 为空; 改为等 usage 块到达(或 [DONE] 兜底)再发,
  completed 携带 input/output tokens

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-16 05:05:46 +08:00
co-authored by Claude
parent db83972b6f
commit e96f194a59
+10 -4
View File
@@ -412,9 +412,10 @@ func (t *responsesToChat) line(line []byte) []byte {
type chatToResponses struct {
sseState
model string
usage any
done bool
model string
usage any
finishSeen bool
done bool
}
func newChatToResponses() *chatToResponses { return &chatToResponses{} }
@@ -425,6 +426,7 @@ func (t *chatToResponses) line(line []byte) []byte {
return nil
}
if done {
// 流结束兜底:finish 后 usage 未随块到达时在此补发 completed
if !t.done {
t.done = true
return eventLine("response.completed", map[string]any{
@@ -453,6 +455,9 @@ func (t *chatToResponses) line(line []byte) []byte {
finish, _ = c0["finish_reason"].(string)
}
}
if finish != "" {
t.finishSeen = true
}
var out [][]byte
if role, _ := delta["role"].(string); role == "assistant" {
out = append(out, eventLine("response.created", map[string]any{
@@ -465,7 +470,8 @@ func (t *chatToResponses) line(line []byte) []byte {
"type": "response.output_text.delta", "delta": content, "item_id": "msg_1", "output_index": 0, "content_index": 0,
}))
}
if finish != "" && !t.done {
// 上游 usage 块(choices 为空)通常晚于 finish_reason:此时再发 completed,携带 usage
if _, hasUsage := m["usage"]; hasUsage && t.finishSeen && !t.done {
t.done = true
out = append(out, eventLine("response.completed", map[string]any{
"type": "response.completed",