package server import ( "context" "encoding/json" "fmt" "net/http" "strings" "time" "github.com/daniel/cursor-adapter/internal/converter" "github.com/daniel/cursor-adapter/internal/types" ) func (s *Server) handleAnthropicMessages(w http.ResponseWriter, r *http.Request) { var req types.AnthropicMessagesRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, types.NewErrorResponse("invalid request body: "+err.Error(), "invalid_request_error", "")) return } defer r.Body.Close() if req.MaxTokens <= 0 { writeJSON(w, http.StatusBadRequest, types.NewErrorResponse("max_tokens is required", "invalid_request_error", "")) return } if len(req.Messages) == 0 { writeJSON(w, http.StatusBadRequest, types.NewErrorResponse("messages must not be empty", "invalid_request_error", "")) return } model := req.Model if model == "" { model = s.cfg.DefaultModel } cursorModel := converter.ResolveToCursorModel(model) sessionKey := ensureSessionHeader(w, r) msgID := fmt.Sprintf("msg_%d", time.Now().UnixNano()) prompt := buildPromptFromAnthropicMessages(req) if req.Stream { s.streamAnthropicMessages(w, r, prompt, cursorModel, model, msgID, sessionKey) return } s.nonStreamAnthropicMessages(w, r, prompt, cursorModel, model, msgID, sessionKey) } func (s *Server) streamAnthropicMessages(w http.ResponseWriter, r *http.Request, prompt, cursorModel, displayModel, msgID, sessionKey string) { sse := NewSSEWriter(w) parser := converter.NewStreamParser(msgID) ctx, cancel := context.WithTimeout(r.Context(), time.Duration(s.cfg.Timeout)*time.Second) defer cancel() go func() { <-r.Context().Done() cancel() }() outputChan, errChan := s.br.Execute(ctx, prompt, cursorModel, sessionKey) writeAnthropicSSE(sse, map[string]interface{}{ "type": "message_start", "message": map[string]interface{}{ "id": msgID, "type": "message", "role": "assistant", "model": displayModel, "content": []interface{}{}, }, }) writeAnthropicSSE(sse, map[string]interface{}{ "type": "content_block_start", "index": 0, "content_block": map[string]interface{}{"type": "text", "text": ""}, }) var accumulated strings.Builder for line := range outputChan { result := parser.Parse(line) if result.Skip { continue } if result.Error != nil { if strings.Contains(result.Error.Error(), "unmarshal error") { result = parser.ParseRawText(line) if result.Skip { continue } if result.Chunk != nil && len(result.Chunk.Choices) > 0 { if c := result.Chunk.Choices[0].Delta.Content; c != nil { accumulated.WriteString(*c) writeAnthropicSSE(sse, map[string]interface{}{ "type": "content_block_delta", "index": 0, "delta": map[string]interface{}{"type": "text_delta", "text": *c}, }) continue } } } writeAnthropicSSE(sse, map[string]interface{}{ "type": "error", "error": map[string]interface{}{"type": "api_error", "message": result.Error.Error()}, }) return } if result.Chunk != nil && len(result.Chunk.Choices) > 0 { if c := result.Chunk.Choices[0].Delta.Content; c != nil { accumulated.WriteString(*c) writeAnthropicSSE(sse, map[string]interface{}{ "type": "content_block_delta", "index": 0, "delta": map[string]interface{}{"type": "text_delta", "text": *c}, }) } } if result.Done { break } } outTokens := maxInt(1, accumulated.Len()/4) writeAnthropicSSE(sse, map[string]interface{}{ "type": "content_block_stop", "index": 0, }) writeAnthropicSSE(sse, map[string]interface{}{ "type": "message_delta", "delta": map[string]interface{}{"stop_reason": "end_turn", "stop_sequence": nil}, "usage": map[string]interface{}{"output_tokens": outTokens}, }) writeAnthropicSSE(sse, map[string]interface{}{ "type": "message_stop", }) select { case <-errChan: default: } } func (s *Server) nonStreamAnthropicMessages(w http.ResponseWriter, r *http.Request, prompt, cursorModel, displayModel, msgID, sessionKey string) { ctx, cancel := context.WithTimeout(r.Context(), time.Duration(s.cfg.Timeout)*time.Second) defer cancel() go func() { <-r.Context().Done() cancel() }() content, err := s.br.ExecuteSync(ctx, prompt, cursorModel, sessionKey) if err != nil { writeJSON(w, http.StatusInternalServerError, types.NewErrorResponse(err.Error(), "api_error", "")) return } usage := estimateUsage(prompt, content) resp := types.AnthropicMessagesResponse{ ID: msgID, Type: "message", Role: "assistant", Content: []types.AnthropicTextBlock{{Type: "text", Text: content}}, Model: displayModel, StopReason: "end_turn", Usage: types.AnthropicUsage{ InputTokens: usage.PromptTokens, OutputTokens: usage.CompletionTokens, }, } writeJSON(w, http.StatusOK, resp) } func writeAnthropicSSE(sse *SSEWriter, event interface{}) { data, err := json.Marshal(event) if err != nil { return } fmt.Fprintf(sse.w, "data: %s\n\n", data) if sse.flush != nil { sse.flush.Flush() } }