package parser import "encoding/json" type StreamParser func(line string) type Parser struct { Parse StreamParser Flush func() } // CreateStreamParser 建立串流解析器(向後相容,不傳遞 thinking) func CreateStreamParser(onText func(string), onDone func()) Parser { return CreateStreamParserWithThinking(onText, nil, onDone) } // CreateStreamParserWithThinking 建立串流解析器,支援思考過程輸出。 // onThinking 可為 nil,表示忽略思考過程。 func CreateStreamParserWithThinking(onText func(string), onThinking func(string), onDone func()) Parser { // accumulated 是所有已輸出內容的串接 accumulatedText := "" accumulatedThinking := "" done := false parse := func(line string) { if done { return } var obj struct { Type string `json:"type"` Subtype string `json:"subtype"` Message *struct { Content []struct { Type string `json:"type"` Text string `json:"text"` Thinking string `json:"thinking"` } `json:"content"` } `json:"message"` } if err := json.Unmarshal([]byte(line), &obj); err != nil { return } if obj.Type == "assistant" && obj.Message != nil { fullText := "" fullThinking := "" for _, p := range obj.Message.Content { switch p.Type { case "text": if p.Text != "" { fullText += p.Text } case "thinking": if p.Thinking != "" { fullThinking += p.Thinking } } } // 處理思考過程(不因去重而 return,避免跳過同行的文字內容) if onThinking != nil && fullThinking != "" && fullThinking != accumulatedThinking { // 增量模式:新內容以 accumulated 為前綴 if len(fullThinking) >= len(accumulatedThinking) && fullThinking[:len(accumulatedThinking)] == accumulatedThinking { delta := fullThinking[len(accumulatedThinking):] if delta != "" { onThinking(delta) } accumulatedThinking = fullThinking } else { // 獨立片段:直接輸出,但 accumulated 要串接 onThinking(fullThinking) accumulatedThinking = accumulatedThinking + fullThinking } } // 處理一般文字 if fullText == "" || fullText == accumulatedText { return } // 增量模式:新內容以 accumulated 為前綴 if len(fullText) >= len(accumulatedText) && fullText[:len(accumulatedText)] == accumulatedText { delta := fullText[len(accumulatedText):] if delta != "" { onText(delta) } accumulatedText = fullText } else { // 獨立片段:直接輸出,但 accumulated 要串接 onText(fullText) accumulatedText = accumulatedText + fullText } } if obj.Type == "result" && obj.Subtype == "success" { done = true onDone() } } flush := func() { if !done { done = true onDone() } } return Parser{Parse: parse, Flush: flush} }