win 21325afb33
Some checks failed
CI / test (push) Failing after 10s
CI / frontend (push) Failing after 8s
CI / golangci-lint (push) Failing after 5s
Security Scan / backend-security (push) Failing after 5s
Security Scan / frontend-security (push) Failing after 4s
feat(windsurf): 补全ops日志记录与endpoint派生,对齐其他平台
- windsurf_gateway_service: 添加上游延迟/TTFT/错误上下文记录
- endpoint: DeriveUpstreamEndpoint 添加 PlatformWindsurf 分支
- ops_error_logger: guessPlatformFromPath 添加 /windsurf/ 识别
2026-04-23 20:46:27 +08:00

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
}