opencode-cursor-agent/internal/parser/stream.go

110 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
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
}
}
}
// 處理思考過程 delta
if onThinking != nil && fullThinking != "" {
if fullThinking == accumulatedThinking {
// 重複的完整思考文字,跳過
} else if len(fullThinking) > len(accumulatedThinking) && fullThinking[:len(accumulatedThinking)] == accumulatedThinking {
delta := fullThinking[len(accumulatedThinking):]
onThinking(delta)
accumulatedThinking = fullThinking
} else {
onThinking(fullThinking)
accumulatedThinking += fullThinking
}
}
// 處理一般文字 delta
if fullText == "" {
return
}
// 若此訊息文字等於已累積內容(重複的完整文字),跳過
if fullText == accumulatedText {
return
}
// 若此訊息是已累積內容的延伸,只輸出新的 delta
if len(fullText) > len(accumulatedText) && fullText[:len(accumulatedText)] == accumulatedText {
delta := fullText[len(accumulatedText):]
onText(delta)
accumulatedText = fullText
} else {
// 獨立的 token fragment一般情況直接輸出
onText(fullText)
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}
}