192 lines
5.2 KiB
Go
192 lines
5.2 KiB
Go
// Package responsealias rewrites provider model identifiers back to the
|
|
// client-visible model without changing the surrounding response protocol.
|
|
package responsealias
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"gpt-load/internal/dialect"
|
|
"gpt-load/internal/protocol"
|
|
)
|
|
|
|
// Needs reports whether a selected upstream model differs from the model used
|
|
// by the client.
|
|
func Needs(clientModel, upstreamModel string) bool {
|
|
return clientModel != "" && upstreamModel != "" && clientModel != upstreamModel
|
|
}
|
|
|
|
// RewriteJSON rewrites model fields in one protocol response object.
|
|
func RewriteJSON(clientProtocol protocol.Protocol, body []byte, clientModel string) ([]byte, error) {
|
|
rewriter, err := modelRewriter(clientProtocol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rewritten, err := rewriter.RewriteResponseModel(body, clientModel)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewrite response model: %w", err)
|
|
}
|
|
return rewritten, nil
|
|
}
|
|
|
|
// RewriteSSE rewrites every complete SSE event in data while preserving event
|
|
// names, comments, line endings and terminal markers. A final event without a
|
|
// blank-line delimiter is also accepted because some upstream bridges emit one
|
|
// logical event per chunk without retaining the delimiter.
|
|
func RewriteSSE(clientProtocol protocol.Protocol, data []byte, clientModel string) ([]byte, error) {
|
|
var output bytes.Buffer
|
|
remaining := data
|
|
for len(remaining) > 0 {
|
|
index, delimiterLength := firstSSEDelimiter(remaining)
|
|
if index < 0 {
|
|
rewritten, err := rewriteSSEEvent(clientProtocol, remaining, clientModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, _ = output.Write(rewritten)
|
|
break
|
|
}
|
|
eventEnd := index + delimiterLength
|
|
rewritten, err := rewriteSSEEvent(clientProtocol, remaining[:eventEnd], clientModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, _ = output.Write(rewritten)
|
|
remaining = remaining[eventEnd:]
|
|
}
|
|
return output.Bytes(), nil
|
|
}
|
|
|
|
func modelRewriter(clientProtocol protocol.Protocol) (dialect.ModelRewriter, error) {
|
|
switch clientProtocol {
|
|
case protocol.OpenAICompletions:
|
|
return dialect.NewOpenAI(), nil
|
|
case protocol.OpenAIResponses:
|
|
return dialect.NewOpenAIResponses(), nil
|
|
case protocol.OpenAIImages:
|
|
return dialect.NewOpenAIImages(), nil
|
|
case protocol.Rerank:
|
|
return dialect.NewRerank(), nil
|
|
case protocol.OpenAIEmbeddings:
|
|
return dialect.NewOpenAIEmbeddings(), nil
|
|
case protocol.Anthropic:
|
|
return dialect.NewAnthropic(), nil
|
|
case protocol.Gemini:
|
|
return dialect.NewGemini(), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported client protocol")
|
|
}
|
|
}
|
|
|
|
func rewriteSSEEvent(clientProtocol protocol.Protocol, event []byte, clientModel string) ([]byte, error) {
|
|
lines := splitSSELines(event)
|
|
dataValues := make([][]byte, 0, 1)
|
|
firstDataLine := -1
|
|
for index := range lines {
|
|
if !lines[index].isData {
|
|
continue
|
|
}
|
|
if firstDataLine < 0 {
|
|
firstDataLine = index
|
|
}
|
|
dataValues = append(dataValues, lines[index].data)
|
|
}
|
|
if firstDataLine < 0 {
|
|
return bytes.Clone(event), nil
|
|
}
|
|
payload := bytes.Join(dataValues, []byte{'\n'})
|
|
if len(payload) == 0 || bytes.Equal(bytes.TrimSpace(payload), []byte("[DONE]")) {
|
|
return bytes.Clone(event), nil
|
|
}
|
|
rewritten, err := RewriteJSON(clientProtocol, payload, clientModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if bytes.Equal(rewritten, payload) {
|
|
return bytes.Clone(event), nil
|
|
}
|
|
|
|
var output bytes.Buffer
|
|
output.Grow(len(event) - len(payload) + len(rewritten))
|
|
for index, line := range lines {
|
|
switch {
|
|
case index == firstDataLine:
|
|
_, _ = output.WriteString("data: ")
|
|
_, _ = output.Write(rewritten)
|
|
_, _ = output.Write(line.terminator)
|
|
case line.isData:
|
|
continue
|
|
default:
|
|
_, _ = output.Write(line.content)
|
|
_, _ = output.Write(line.terminator)
|
|
}
|
|
}
|
|
return output.Bytes(), nil
|
|
}
|
|
|
|
func firstSSEDelimiter(data []byte) (int, int) {
|
|
indexLF := bytes.Index(data, []byte("\n\n"))
|
|
indexCRLF := bytes.Index(data, []byte("\r\n\r\n"))
|
|
switch {
|
|
case indexLF < 0 && indexCRLF < 0:
|
|
return -1, 0
|
|
case indexLF < 0:
|
|
return indexCRLF, 4
|
|
case indexCRLF < 0:
|
|
return indexLF, 2
|
|
case indexCRLF < indexLF:
|
|
return indexCRLF, 4
|
|
default:
|
|
return indexLF, 2
|
|
}
|
|
}
|
|
|
|
type sseLine struct {
|
|
content []byte
|
|
terminator []byte
|
|
isData bool
|
|
data []byte
|
|
}
|
|
|
|
func splitSSELines(event []byte) []sseLine {
|
|
lines := make([]sseLine, 0, 4)
|
|
for start := 0; start < len(event); {
|
|
end := start
|
|
for end < len(event) && event[end] != '\n' && event[end] != '\r' {
|
|
end++
|
|
}
|
|
terminatorEnd := end
|
|
if terminatorEnd < len(event) {
|
|
terminatorEnd++
|
|
if event[end] == '\r' && terminatorEnd < len(event) && event[terminatorEnd] == '\n' {
|
|
terminatorEnd++
|
|
}
|
|
}
|
|
line := sseLine{content: event[start:end], terminator: event[end:terminatorEnd]}
|
|
line.isData, line.data = parseSSEDataLine(line.content)
|
|
lines = append(lines, line)
|
|
start = terminatorEnd
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func parseSSEDataLine(line []byte) (bool, []byte) {
|
|
if len(line) == 0 || line[0] == ':' {
|
|
return false, nil
|
|
}
|
|
separator := bytes.IndexByte(line, ':')
|
|
field := line
|
|
var value []byte
|
|
if separator >= 0 {
|
|
field = line[:separator]
|
|
value = line[separator+1:]
|
|
if len(value) > 0 && value[0] == ' ' {
|
|
value = value[1:]
|
|
}
|
|
}
|
|
if !bytes.Equal(field, []byte("data")) {
|
|
return false, nil
|
|
}
|
|
return true, value
|
|
}
|