- Go gateway (stdlib only): /v1/messages non-stream + SSE, /v1/messages/count_tokens, management API /api/* (providers CRUD, apply/restore, settings, backups, logs) - Protocol conversion: Anthropic Messages <-> Chat Completions with a streaming state machine keyed by content block index (text, tool_use, parallel calls) - ~/.claude/settings.json guarded read/write: timestamped backups, managed env keys only, one-click restore - Tauri 2 shell: spawns the gateway sidecar, parses the random port from stdout, stdin pipe watchdog prevents orphans, kills the child on exit - React frontend: Dashboard/Providers/Settings, zh-CN + en-US, TanStack Query - Tests: Go table-driven + httptest integration (all green), vitest + RTL
52 lines
1.1 KiB
Go
52 lines
1.1 KiB
Go
package chatcmpl
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
// Stream reads Server-Sent Events carrying Chat Completions chunks.
|
|
type Stream struct {
|
|
scanner *bufio.Scanner
|
|
}
|
|
|
|
// NewStream wraps a reader of upstream SSE data.
|
|
func NewStream(r io.Reader) *Stream {
|
|
sc := bufio.NewScanner(r)
|
|
sc.Buffer(make([]byte, 0, 64*1024), 16*1024*1024)
|
|
return &Stream{scanner: sc}
|
|
}
|
|
|
|
// Next returns the next chunk. It reports io.EOF once the upstream
|
|
// stream is finished (either by "[DONE]" or by closing).
|
|
func (s *Stream) Next() (*StreamChunk, error) {
|
|
for s.scanner.Scan() {
|
|
line := strings.TrimRight(s.scanner.Text(), "\r")
|
|
if line == "" || strings.HasPrefix(line, ":") {
|
|
continue
|
|
}
|
|
if !strings.HasPrefix(line, "data:") {
|
|
continue
|
|
}
|
|
data := strings.TrimSpace(strings.TrimPrefix(line, "data:"))
|
|
if data == "" {
|
|
continue
|
|
}
|
|
if data == "[DONE]" {
|
|
return nil, io.EOF
|
|
}
|
|
var chunk StreamChunk
|
|
if err := json.Unmarshal([]byte(data), &chunk); err != nil {
|
|
return nil, fmt.Errorf("decode upstream chunk: %w", err)
|
|
}
|
|
return &chunk, nil
|
|
}
|
|
if err := s.scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, io.EOF
|
|
}
|