package provider import ( "bufio" "bytes" "context" "encoding/json" "io" "net/http" "strings" "github.com/rose_cat707/luminary/internal/model" ) type StreamResult struct { StatusCode int TokensIn int TokensOut int Body []byte // populated for non-2xx responses } func ForwardStreamHTTP(ctx context.Context, client *http.Client, method, url string, apiKey string, providerType model.ProviderType, body []byte, w io.Writer, flusher http.Flusher) (*StreamResult, error) { var reader io.Reader if len(body) > 0 { reader = bytes.NewReader(body) } req, err := http.NewRequestWithContext(ctx, method, url, reader) if err != nil { return nil, err } setAuthHeaders(req, apiKey, providerType) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "text/event-stream") resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode >= 400 { b, _ := io.ReadAll(resp.Body) if flusher != nil { w.Write(b) flusher.Flush() } return &StreamResult{StatusCode: resp.StatusCode, Body: b}, nil } result := &StreamResult{StatusCode: resp.StatusCode} scanner := bufio.NewScanner(resp.Body) scanner.Buffer(make([]byte, 64*1024), 1024*1024) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "data: ") { data := strings.TrimPrefix(line, "data: ") if data == "[DONE]" { continue } tokensIn, tokensOut := parseStreamChunk([]byte(data)) result.TokensIn += tokensIn result.TokensOut += tokensOut } if _, err := w.Write([]byte(line + "\n")); err != nil { return result, err } if flusher != nil { flusher.Flush() } } return result, scanner.Err() } func parseStreamChunk(data []byte) (int, int) { var chunk struct { Usage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` } `json:"usage"` } if err := json.Unmarshal(data, &chunk); err == nil && chunk.Usage.PromptTokens+chunk.Usage.CompletionTokens > 0 { return chunk.Usage.PromptTokens, chunk.Usage.CompletionTokens } return 0, 0 } func NewStreamHTTPClient() *http.Client { return &http.Client{} // no timeout for streams } func IsStreamRequest(body []byte) bool { var payload struct { Stream bool `json:"stream"` } _ = json.Unmarshal(body, &payload) return payload.Stream } func SupportsStreaming(endpoint EndpointType) bool { return endpoint == EndpointChatCompletion || endpoint == EndpointResponses }