- windsurf_gateway_service: 添加上游延迟/TTFT/错误上下文记录 - endpoint: DeriveUpstreamEndpoint 添加 PlatformWindsurf 分支 - ops_error_logger: guessPlatformFromPath 添加 /windsurf/ 识别
229 lines
4.9 KiB
Go
229 lines
4.9 KiB
Go
package windsurf
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
RawGetChatMessageRPC = "/exa.language_server_pb.LanguageServerService/RawGetChatMessage"
|
|
|
|
SourceUser = 1
|
|
SourceSystem = 2
|
|
SourceAssistant = 3
|
|
SourceTool = 4
|
|
)
|
|
|
|
type ChatMessage struct {
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
type LegacyChatDelta struct {
|
|
Text string
|
|
InProgress bool
|
|
IsError bool
|
|
}
|
|
|
|
func encodeTimestamp() []byte {
|
|
now := time.Now()
|
|
secs := uint64(now.Unix())
|
|
nanos := uint64(now.Nanosecond())
|
|
out := encodeVarintField(1, secs)
|
|
if nanos > 0 {
|
|
out = append(out, encodeVarintField(2, nanos)...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func buildChatMessage(content string, source int, conversationID string) []byte {
|
|
var parts []byte
|
|
parts = append(parts, encodeStringField(1, generateUUID())...)
|
|
parts = append(parts, encodeVarintField(2, uint64(source))...)
|
|
parts = append(parts, encodeBytesField(3, encodeTimestamp())...)
|
|
parts = append(parts, encodeStringField(4, conversationID)...)
|
|
|
|
if source == SourceAssistant {
|
|
actionGeneric := encodeStringField(1, content)
|
|
action := encodeBytesField(1, actionGeneric)
|
|
parts = append(parts, encodeBytesField(6, action)...)
|
|
} else {
|
|
intentGeneric := encodeStringField(1, content)
|
|
intent := encodeBytesField(1, intentGeneric)
|
|
parts = append(parts, encodeBytesField(5, intent)...)
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
func BuildRawGetChatMessageRequest(apiKey string, messages []ChatMessage, modelEnum int, modelName string) []byte {
|
|
var parts []byte
|
|
conversationID := generateUUID()
|
|
|
|
parts = append(parts, encodeBytesField(1, buildMetadata(apiKey, generateUUID()))...)
|
|
|
|
var systemPrompt string
|
|
for _, msg := range messages {
|
|
if msg.Role == "system" {
|
|
if systemPrompt != "" {
|
|
systemPrompt += "\n"
|
|
}
|
|
systemPrompt += msg.Content
|
|
continue
|
|
}
|
|
|
|
var source int
|
|
var text string
|
|
|
|
switch msg.Role {
|
|
case "user":
|
|
source = SourceUser
|
|
text = msg.Content
|
|
case "assistant":
|
|
source = SourceAssistant
|
|
text = msg.Content
|
|
case "tool":
|
|
source = SourceUser
|
|
text = "[tool result]: " + msg.Content
|
|
default:
|
|
source = SourceUser
|
|
text = msg.Content
|
|
}
|
|
|
|
parts = append(parts, encodeBytesField(2, buildChatMessage(text, source, conversationID))...)
|
|
}
|
|
|
|
if systemPrompt != "" {
|
|
parts = append(parts, encodeStringField(3, systemPrompt)...)
|
|
}
|
|
|
|
parts = append(parts, encodeVarintField(4, uint64(modelEnum))...)
|
|
|
|
if modelName != "" {
|
|
parts = append(parts, encodeStringField(5, modelName)...)
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
func ParseRawChatResponse(data []byte) LegacyChatDelta {
|
|
pos := 0
|
|
var deltaMsg []byte
|
|
for pos < len(data) {
|
|
tag, np, ok := ReadVarint(data, pos)
|
|
if !ok {
|
|
break
|
|
}
|
|
pos = np
|
|
fieldNum := tag >> 3
|
|
wireType := tag & 7
|
|
|
|
switch wireType {
|
|
case 2:
|
|
length, np2, ok := ReadVarint(data, pos)
|
|
if !ok {
|
|
return LegacyChatDelta{}
|
|
}
|
|
pos = np2
|
|
if pos+int(length) > len(data) {
|
|
return LegacyChatDelta{}
|
|
}
|
|
field := data[pos : pos+int(length)]
|
|
pos += int(length)
|
|
if fieldNum == 1 {
|
|
deltaMsg = field
|
|
}
|
|
case 0:
|
|
_, np2, ok := ReadVarint(data, pos)
|
|
if !ok {
|
|
return LegacyChatDelta{}
|
|
}
|
|
pos = np2
|
|
case 1:
|
|
pos += 8
|
|
case 5:
|
|
pos += 4
|
|
default:
|
|
return LegacyChatDelta{}
|
|
}
|
|
}
|
|
|
|
if deltaMsg == nil {
|
|
return LegacyChatDelta{}
|
|
}
|
|
|
|
var result LegacyChatDelta
|
|
pos = 0
|
|
for pos < len(deltaMsg) {
|
|
tag, np, ok := ReadVarint(deltaMsg, pos)
|
|
if !ok {
|
|
break
|
|
}
|
|
pos = np
|
|
fieldNum := tag >> 3
|
|
wireType := tag & 7
|
|
|
|
switch wireType {
|
|
case 2:
|
|
length, np2, ok := ReadVarint(deltaMsg, pos)
|
|
if !ok {
|
|
return result
|
|
}
|
|
pos = np2
|
|
if pos+int(length) > len(deltaMsg) {
|
|
return result
|
|
}
|
|
field := deltaMsg[pos : pos+int(length)]
|
|
pos += int(length)
|
|
if fieldNum == 5 {
|
|
result.Text = string(field)
|
|
}
|
|
case 0:
|
|
val, np2, ok := ReadVarint(deltaMsg, pos)
|
|
if !ok {
|
|
return result
|
|
}
|
|
pos = np2
|
|
if fieldNum == 6 {
|
|
result.InProgress = val != 0
|
|
} else if fieldNum == 7 {
|
|
result.IsError = val != 0
|
|
}
|
|
case 1:
|
|
pos += 8
|
|
case 5:
|
|
pos += 4
|
|
default:
|
|
pos = len(deltaMsg)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (l *LocalLSClient) StreamLegacyChat(ctx context.Context, token string, messages []ChatMessage, modelEnum int, modelName string) (string, error) {
|
|
reqBody := BuildRawGetChatMessageRequest(token, messages, modelEnum, modelName)
|
|
|
|
respData, err := l.grpcUnaryRaw(ctx, RawGetChatMessageRPC, reqBody)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "panel state not found") || strings.Contains(err.Error(), "not_found") {
|
|
_ = l.ForceWarmupCascade(ctx, token)
|
|
respData, err = l.grpcUnaryRaw(ctx, RawGetChatMessageRPC, reqBody)
|
|
if err != nil {
|
|
return "", fmt.Errorf("legacy chat retry: %w", err)
|
|
}
|
|
} else {
|
|
return "", fmt.Errorf("legacy chat: %w", err)
|
|
}
|
|
}
|
|
|
|
delta := ParseRawChatResponse(respData)
|
|
if delta.IsError {
|
|
return "", fmt.Errorf("legacy chat error: %s", delta.Text)
|
|
}
|
|
|
|
return SanitizePath(delta.Text), nil
|
|
}
|