| package amp |
|
|
| import ( |
| "bytes" |
| "net/http" |
| "strings" |
|
|
| "github.com/gin-gonic/gin" |
| log "github.com/sirupsen/logrus" |
| "github.com/tidwall/gjson" |
| "github.com/tidwall/sjson" |
| ) |
|
|
| |
| |
| type ResponseRewriter struct { |
| gin.ResponseWriter |
| body *bytes.Buffer |
| originalModel string |
| isStreaming bool |
| } |
|
|
| |
| func NewResponseRewriter(w gin.ResponseWriter, originalModel string) *ResponseRewriter { |
| return &ResponseRewriter{ |
| ResponseWriter: w, |
| body: &bytes.Buffer{}, |
| originalModel: originalModel, |
| } |
| } |
|
|
| |
| func (rw *ResponseRewriter) Write(data []byte) (int, error) { |
| |
| if rw.body.Len() == 0 && !rw.isStreaming { |
| contentType := rw.Header().Get("Content-Type") |
| rw.isStreaming = strings.Contains(contentType, "text/event-stream") || |
| strings.Contains(contentType, "stream") |
| } |
|
|
| if rw.isStreaming { |
| n, err := rw.ResponseWriter.Write(rw.rewriteStreamChunk(data)) |
| if err == nil { |
| if flusher, ok := rw.ResponseWriter.(http.Flusher); ok { |
| flusher.Flush() |
| } |
| } |
| return n, err |
| } |
| return rw.body.Write(data) |
| } |
|
|
| |
| func (rw *ResponseRewriter) Flush() { |
| if rw.isStreaming { |
| if flusher, ok := rw.ResponseWriter.(http.Flusher); ok { |
| flusher.Flush() |
| } |
| return |
| } |
| if rw.body.Len() > 0 { |
| if _, err := rw.ResponseWriter.Write(rw.rewriteModelInResponse(rw.body.Bytes())); err != nil { |
| log.Warnf("amp response rewriter: failed to write rewritten response: %v", err) |
| } |
| } |
| } |
|
|
| |
| var modelFieldPaths = []string{"model", "modelVersion", "response.modelVersion", "message.model"} |
|
|
| |
| |
| func (rw *ResponseRewriter) rewriteModelInResponse(data []byte) []byte { |
| |
| |
| if gjson.GetBytes(data, `content.#(type=="tool_use")`).Exists() { |
| filtered := gjson.GetBytes(data, `content.#(type!="thinking")#`) |
| if filtered.Exists() { |
| originalCount := gjson.GetBytes(data, "content.#").Int() |
| filteredCount := filtered.Get("#").Int() |
|
|
| if originalCount > filteredCount { |
| var err error |
| data, err = sjson.SetBytes(data, "content", filtered.Value()) |
| if err != nil { |
| log.Warnf("Amp ResponseRewriter: failed to suppress thinking blocks: %v", err) |
| } else { |
| log.Debugf("Amp ResponseRewriter: Suppressed %d thinking blocks due to tool usage", originalCount-filteredCount) |
| |
| log.Debugf("Amp ResponseRewriter: Resulting content: %s", gjson.GetBytes(data, "content").String()) |
| } |
| } |
| } |
| } |
|
|
| if rw.originalModel == "" { |
| return data |
| } |
| for _, path := range modelFieldPaths { |
| if gjson.GetBytes(data, path).Exists() { |
| data, _ = sjson.SetBytes(data, path, rw.originalModel) |
| } |
| } |
| return data |
| } |
|
|
| |
| func (rw *ResponseRewriter) rewriteStreamChunk(chunk []byte) []byte { |
| if rw.originalModel == "" { |
| return chunk |
| } |
|
|
| |
| lines := bytes.Split(chunk, []byte("\n")) |
| for i, line := range lines { |
| if bytes.HasPrefix(line, []byte("data: ")) { |
| jsonData := bytes.TrimPrefix(line, []byte("data: ")) |
| if len(jsonData) > 0 && jsonData[0] == '{' { |
| |
| rewritten := rw.rewriteModelInResponse(jsonData) |
| lines[i] = append([]byte("data: "), rewritten...) |
| } |
| } |
| } |
|
|
| return bytes.Join(lines, []byte("\n")) |
| } |
|
|