diff --git a/backend/cmd/test_windsurf_minimal/main.go b/backend/cmd/test_windsurf_minimal/main.go index fee6069e..7e936fbe 100644 --- a/backend/cmd/test_windsurf_minimal/main.go +++ b/backend/cmd/test_windsurf_minimal/main.go @@ -279,7 +279,7 @@ func main() { // SendUserCascadeMessage { ctx, cancel := context.WithTimeout(context.Background(), f.timeout) - newCID, err := lsClient.SendUserCascadeMessage(ctx, f.jwt, cascadeID, f.prompt, pickedModel, "") + newCID, err := lsClient.SendUserCascadeMessage(ctx, f.jwt, cascadeID, f.prompt, pickedModel, "", 0) if err == nil && newCID != "" { cascadeID = newCID } diff --git a/backend/internal/pkg/windsurf/local_ls.go b/backend/internal/pkg/windsurf/local_ls.go index 8a86df4f..29512efb 100644 --- a/backend/internal/pkg/windsurf/local_ls.go +++ b/backend/internal/pkg/windsurf/local_ls.go @@ -159,8 +159,11 @@ func (l *LocalLSClient) StartCascade(ctx context.Context, token string) (string, // SendUserCascadeMessage sends a message into an existing cascade session. // Returns the (possibly new) cascadeID — it changes if panel-state retry triggers a new StartCascade. // toolPreamble, if non-empty, is injected into the tool_calling_section override. -func (l *LocalLSClient) SendUserCascadeMessage(ctx context.Context, token, cascadeID, text, modelUID, toolPreamble string) (string, error) { +func (l *LocalLSClient) SendUserCascadeMessage(ctx context.Context, token, cascadeID, text, modelUID, toolPreamble string, modelEnumHint int) (string, error) { modelEnum := resolveModelEnum(modelUID) + if modelEnum == 0 && modelEnumHint > 0 { + modelEnum = modelEnumHint + } doSend := func(cid string) error { body := encodeStringField(1, cid) @@ -369,7 +372,7 @@ func (e *CascadeModelError) Error() string { return e.Msg } // StreamCascadeChat performs the full Cascade chat flow and returns accumulated text + thinking. // Includes cold/warm stall detection, step error handling, and final sweep (aligned with JS v1.9). // If reuseCascadeID is non-empty, skips StartCascade and reuses the existing cascade session. -func (l *LocalLSClient) StreamCascadeChat(ctx context.Context, token, modelUID, userText, toolPreamble, reuseCascadeID string) (*CascadeChatResult, error) { +func (l *LocalLSClient) StreamCascadeChat(ctx context.Context, token, modelUID, userText, toolPreamble, reuseCascadeID string, modelEnumHint int) (*CascadeChatResult, error) { if err := l.WarmupCascade(ctx, token); err != nil { return nil, fmt.Errorf("warmup: %w", err) } @@ -385,7 +388,7 @@ func (l *LocalLSClient) StreamCascadeChat(ctx context.Context, token, modelUID, } } - cascadeID, err = l.SendUserCascadeMessage(ctx, token, cascadeID, userText, modelUID, toolPreamble) + cascadeID, err = l.SendUserCascadeMessage(ctx, token, cascadeID, userText, modelUID, toolPreamble, modelEnumHint) if err != nil { return nil, fmt.Errorf("SendUserCascadeMessage: %w", err) } @@ -578,6 +581,17 @@ func (l *LocalLSClient) StreamCascadeChat(ctx context.Context, token, modelUID, } } + slog.Info("windsurf_cascade_poll_result", + "cascade_id", cascadeID[:min(8, len(cascadeID))], + "acc_text_len", len(accText), + "acc_thinking_len", len(accThinking), + "native_tool_calls", len(nativeToolCalls), + "saw_active", sawActive, + "saw_text", sawText, + "steps_seen", len(textCursors), + "idle_count", idleCount, + ) + // Aggregate step usage var aggUsage *StepUsage for _, u := range usageByStep { diff --git a/backend/internal/service/windsurf_chat_service.go b/backend/internal/service/windsurf_chat_service.go index 0a6c5ba7..09507351 100644 --- a/backend/internal/service/windsurf_chat_service.go +++ b/backend/internal/service/windsurf_chat_service.go @@ -61,6 +61,10 @@ func (s *WindsurfChatService) Chat(ctx context.Context, req *WindsurfChatRequest meta := windsurf.GetModelInfo(modelKey) mode := s.resolveMode(meta) + // Tool emulation requires cascade mode for proto section injection + if mode == "legacy" && req.ToolPreamble != "" { + mode = "cascade" + } var lease *windsurf.LSLease if token.LSBinding.ContainerID != "" || token.LSBinding.ContainerName != "" { @@ -109,8 +113,10 @@ func (s *WindsurfChatService) resolveMode(meta *windsurf.ModelMeta) string { func (s *WindsurfChatService) chatCascade(ctx context.Context, client *windsurf.LocalLSClient, apiKey string, meta *windsurf.ModelMeta, messages []windsurf.ChatMessage, toolPreamble string, modelKey string, lsEndpoint string) (*WindsurfChatResponse, error) { modelUID := "" + modelEnumHint := 0 if meta != nil { modelUID = meta.ModelUID + modelEnumHint = meta.EnumValue } fpBefore := windsurf.FingerprintBefore(messages, modelKey) @@ -125,11 +131,11 @@ func (s *WindsurfChatService) chatCascade(ctx context.Context, client *windsurf. userText := buildCascadeText(messages, modelUID, isResume) - result, err := client.StreamCascadeChat(ctx, apiKey, modelUID, userText, toolPreamble, reuseCascadeID) + result, err := client.StreamCascadeChat(ctx, apiKey, modelUID, userText, toolPreamble, reuseCascadeID, modelEnumHint) if err != nil && isResume { slog.Warn("windsurf_cascade_reuse_failed", "error", err, "model", modelKey) userText = buildCascadeText(messages, modelUID, false) - result, err = client.StreamCascadeChat(ctx, apiKey, modelUID, userText, toolPreamble, "") + result, err = client.StreamCascadeChat(ctx, apiKey, modelUID, userText, toolPreamble, "", modelEnumHint) } if err != nil { return nil, err diff --git a/backend/internal/service/windsurf_gateway_service.go b/backend/internal/service/windsurf_gateway_service.go index e6fd09ef..4e1fbd11 100644 --- a/backend/internal/service/windsurf_gateway_service.go +++ b/backend/internal/service/windsurf_gateway_service.go @@ -10,6 +10,7 @@ import ( "time" "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/Wei-Shaw/sub2api/internal/pkg/logger" "github.com/Wei-Shaw/sub2api/internal/pkg/windsurf" "github.com/gin-gonic/gin" @@ -674,7 +675,7 @@ func windsurfExtractContentTextFromRaw(raw json.RawMessage) string { } func windsurfLogger(c *gin.Context, component string, fields ...zap.Field) *zap.Logger { - l := zap.L().With(zap.String("component", component)) + l := logger.L().With(zap.String("component", component)) if c != nil { if reqID := c.GetHeader("X-Request-ID"); reqID != "" { l = l.With(zap.String("request_id", reqID))