package anthropic import ( "bufio" "encoding/json" "fmt" "net/http" "strings" ) // Anthropic SSE event types handled by PipeAnthropicSSE. const ( eventContentBlockDelta = "content_block_delta" eventMessageStop = "message_stop" ) // anthropicStreamEvent is the data payload for a content_block_delta SSE event. type anthropicStreamEvent struct { Type string `json:"type"` Delta anthropicDelta `json:"delta"` } type anthropicDelta struct { Type string `json:"type"` Text string `json:"text"` } // openAIChunk is the minimal OpenAI-compatible SSE chunk emitted to the client. type openAIChunk struct { ID string `json:"id"` Object string `json:"object"` Choices []openAIChunkChoice `json:"choices"` } type openAIChunkChoice struct { Index int `json:"index"` Delta openAIChunkDelta `json:"delta"` } type openAIChunkDelta struct { Content string `json:"content"` } // PipeAnthropicSSE reads Anthropic's event-typed SSE stream and re-emits each // text chunk as an OpenAI-compatible SSE event. It terminates on message_stop // or when the upstream body is exhausted. // // Anthropic SSE format: // // event: content_block_delta // data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hello"}} // // Re-emitted as OpenAI-compat: // // data: {"id":"chatcmpl-anthropic","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hello"}}]} func PipeAnthropicSSE(body *bufio.Scanner, w http.ResponseWriter) error { flusher, ok := w.(http.Flusher) if !ok { return fmt.Errorf("response writer does not support flushing (SSE not possible)") } var currentEvent string for body.Scan() { line := body.Text() // Track the event type from "event: ..." lines. if strings.HasPrefix(line, "event: ") { currentEvent = strings.TrimPrefix(line, "event: ") continue } // Skip blank lines and lines without a data prefix. if line == "" || !strings.HasPrefix(line, "data: ") { continue } rawData := strings.TrimPrefix(line, "data: ") switch currentEvent { case eventContentBlockDelta: var evt anthropicStreamEvent if err := json.Unmarshal([]byte(rawData), &evt); err != nil { continue // skip malformed events gracefully } if evt.Delta.Type != "text_delta" || evt.Delta.Text == "" { continue } chunk := openAIChunk{ ID: "chatcmpl-anthropic", Object: "chat.completion.chunk", Choices: []openAIChunkChoice{{ Index: 0, Delta: openAIChunkDelta{Content: evt.Delta.Text}, }}, } chunkJSON, err := json.Marshal(chunk) if err != nil { continue } if _, err := fmt.Fprintf(w, "data: %s\n\n", chunkJSON); err != nil { return fmt.Errorf("writing SSE chunk: %w", err) } flusher.Flush() case eventMessageStop: if _, err := fmt.Fprintf(w, "data: [DONE]\n\n"); err != nil { return fmt.Errorf("writing SSE done: %w", err) } flusher.Flush() return nil } } if err := body.Err(); err != nil { return fmt.Errorf("reading Anthropic SSE body: %w", err) } return nil }