Files
ds2api/internal/httpapi/claude/stream_runtime_emit.go
shern-point d3018c281b feat: use tokenizer-based counting in Claude token paths
Unify Claude count_tokens, legacy stream accounting, and legacy render usage with preserved prompt text so Claude stops falling back to lossy message formatting.
2026-04-30 00:46:04 +08:00

63 lines
1.4 KiB
Go

package claude
import (
"encoding/json"
"fmt"
"strings"
"ds2api/internal/util"
)
func (s *claudeStreamRuntime) send(event string, v any) {
b, _ := json.Marshal(v)
_, _ = s.w.Write([]byte("event: "))
_, _ = s.w.Write([]byte(event))
_, _ = s.w.Write([]byte("\n"))
_, _ = s.w.Write([]byte("data: "))
_, _ = s.w.Write(b)
_, _ = s.w.Write([]byte("\n\n"))
if s.canFlush {
_ = s.rc.Flush()
}
}
func (s *claudeStreamRuntime) sendError(message string) {
msg := strings.TrimSpace(message)
if msg == "" {
msg = "upstream stream error"
}
s.send("error", map[string]any{
"type": "error",
"error": map[string]any{
"type": "api_error",
"message": msg,
"code": "internal_error",
"param": nil,
},
})
}
func (s *claudeStreamRuntime) sendPing() {
s.send("ping", map[string]any{"type": "ping"})
}
func (s *claudeStreamRuntime) sendMessageStart() {
inputTokens := countClaudeInputTokensFromText(s.promptTokenText, s.model)
if inputTokens == 0 {
inputTokens = util.CountPromptTokens(fmt.Sprintf("%v", s.messages), s.model)
}
s.send("message_start", map[string]any{
"type": "message_start",
"message": map[string]any{
"id": s.messageID,
"type": "message",
"role": "assistant",
"model": s.model,
"content": []any{},
"stop_reason": nil,
"stop_sequence": nil,
"usage": map[string]any{"input_tokens": inputTokens, "output_tokens": 0},
},
})
}