1
0
mirror of https://github.com/coalaura/whiskr.git synced 2025-09-09 01:09:54 +00:00
Files
whiskr/stream.go

125 lines
1.9 KiB
Go
Raw Normal View History

2025-08-05 03:56:23 +02:00
package main
import (
2025-08-29 19:26:55 +02:00
"bytes"
"context"
2025-08-05 03:56:23 +02:00
"encoding/json"
"errors"
"net/http"
2025-08-29 19:26:55 +02:00
"sync"
2025-08-11 01:21:05 +02:00
"github.com/revrost/go-openrouter"
2025-08-05 03:56:23 +02:00
)
type Chunk struct {
Type string `json:"type"`
2025-08-14 03:53:14 +02:00
Text any `json:"text"`
2025-08-05 03:56:23 +02:00
}
type Stream struct {
2025-08-29 19:26:55 +02:00
wr http.ResponseWriter
ctx context.Context
2025-08-05 03:56:23 +02:00
}
2025-08-29 19:26:55 +02:00
var pool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
2025-08-05 03:56:23 +02:00
2025-08-29 19:26:55 +02:00
func NewStream(w http.ResponseWriter, ctx context.Context) (*Stream, error) {
2025-08-05 03:56:23 +02:00
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
return &Stream{
2025-08-29 19:26:55 +02:00
wr: w,
ctx: ctx,
2025-08-05 03:56:23 +02:00
}, nil
}
func (s *Stream) Send(ch Chunk) error {
2025-08-14 17:08:45 +02:00
debugIf(ch.Type == "error", "error: %v", ch.Text)
2025-08-29 19:26:55 +02:00
return WriteChunk(s.wr, s.ctx, ch)
2025-08-05 03:56:23 +02:00
}
func ReasoningChunk(text string) Chunk {
return Chunk{
Type: "reason",
Text: text,
}
}
func TextChunk(text string) Chunk {
return Chunk{
Type: "text",
2025-08-14 17:08:45 +02:00
Text: CleanChunk(text),
2025-08-05 03:56:23 +02:00
}
}
2025-08-14 03:53:14 +02:00
func ToolChunk(tool *ToolCall) Chunk {
return Chunk{
Type: "tool",
Text: tool,
}
}
2025-08-11 15:43:00 +02:00
func IDChunk(id string) Chunk {
return Chunk{
Type: "id",
Text: id,
}
}
2025-08-05 03:56:23 +02:00
func ErrorChunk(err error) Chunk {
return Chunk{
Type: "error",
2025-08-11 01:21:05 +02:00
Text: GetErrorMessage(err),
2025-08-05 03:56:23 +02:00
}
}
2025-08-11 01:21:05 +02:00
func GetErrorMessage(err error) string {
if apiErr, ok := err.(*openrouter.APIError); ok {
return apiErr.Error()
}
return err.Error()
}
2025-08-29 19:26:55 +02:00
func WriteChunk(w http.ResponseWriter, ctx context.Context, chunk any) error {
if err := ctx.Err(); err != nil {
return err
}
buf := pool.Get().(*bytes.Buffer)
buf.Reset()
defer pool.Put(buf)
if err := json.NewEncoder(buf).Encode(chunk); err != nil {
return err
}
buf.Write([]byte("\n\n"))
if _, err := w.Write(buf.Bytes()); err != nil {
return err
}
flusher, ok := w.(http.Flusher)
if !ok {
return errors.New("failed to create flusher")
}
select {
case <-ctx.Done():
return ctx.Err()
default:
flusher.Flush()
return nil
}
}