package proxy import ( "encoding/json" "strings" ) // replyFromJSON pulls the assistant's text out of a completed response. // // Only the first choice. A request asking for several is not one continuing a // conversation, and storing one of them as "what the assistant said" would // invent a conversation that never happened. func replyFromJSON(body []byte) string { var b struct { Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` // The completions endpoint, which has no message wrapper. Text string `json:"text"` } `json:"choices"` } if err := json.Unmarshal(body, &b); err != nil || len(b.Choices) == 0 { return "" } if c := b.Choices[0].Message.Content; c != "" { return c } return b.Choices[0].Text } // replyDelta pulls the incremental text out of one streamed event. func replyDelta(event string) string { if event == "" || event == "[DONE]" { return "" } var b struct { Choices []struct { Delta struct { Content string `json:"content"` } `json:"delta"` Text string `json:"text"` } `json:"choices"` } if err := json.Unmarshal([]byte(event), &b); err != nil || len(b.Choices) == 0 { return "" } if c := b.Choices[0].Delta.Content; c != "" { return c } return b.Choices[0].Text } // capReply truncates at the cap and says whether it had to. // // Truncated rather than dropped, because a shortened turn replays as a // shortened turn while nothing at all replays as the assistant having said // nothing — a different conversation, and a confusing one to be handed back. func capReply(s string, cap int) (string, bool) { if cap <= 0 || len(s) <= cap { return s, false } // On a rune boundary, so a stored reply is never invalid UTF-8 — it goes // back into a JSON body, and a split multi-byte character would make that // body unserialisable. cut := cap for cut > 0 && !isRuneStart(s[cut]) { cut-- } return s[:cut], true } func isRuneStart(b byte) bool { return b&0xC0 != 0x80 } // replyBuilder accumulates a streamed reply up to a cap. // // It stops appending once full rather than growing and truncating at the end, // so a long answer cannot cost more memory than the cap allows however much // the engine sends. type replyBuilder struct { cap int sb strings.Builder truncated bool } func (r *replyBuilder) add(s string) { if r.cap <= 0 || s == "" || r.truncated { return } if room := r.cap - r.sb.Len(); len(s) > room { part, _ := capReply(s, room) r.sb.WriteString(part) r.truncated = true return } r.sb.WriteString(s) } func (r *replyBuilder) result() (string, bool) { return r.sb.String(), r.truncated }