初始化仓库
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProviderProxy is a lightweight local reverse proxy that rewrites
|
||||
// incompatible Anthropic API fields for third-party providers.
|
||||
//
|
||||
// Some providers (e.g. SiliconFlow) don't support thinking.type "adaptive"
|
||||
// sent by Claude Code 2.x. The proxy rewrites the thinking field to
|
||||
// the configured override value before forwarding.
|
||||
type ProviderProxy struct {
|
||||
targetURL string
|
||||
thinkingOverride string
|
||||
listener net.Listener
|
||||
server *http.Server
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewProviderProxy creates and starts a local reverse proxy for the
|
||||
// given upstream URL. thinkingOverride controls what thinking.type to
|
||||
// rewrite "adaptive" to (e.g. "disabled" or "enabled").
|
||||
// Returns the local URL to use as ANTHROPIC_BASE_URL.
|
||||
func NewProviderProxy(targetURL, thinkingOverride string) (*ProviderProxy, string, error) {
|
||||
target, err := url.Parse(strings.TrimRight(targetURL, "/"))
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("providerproxy: parse target: %w", err)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("providerproxy: listen: %w", err)
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
origDirector := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
origDirector(req)
|
||||
req.Host = target.Host
|
||||
}
|
||||
proxy.FlushInterval = -1 // flush SSE events immediately
|
||||
|
||||
override := thinkingOverride
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/messages") {
|
||||
rewriteThinkingInRequest(r, override)
|
||||
}
|
||||
proxy.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
pp := &ProviderProxy{
|
||||
targetURL: targetURL,
|
||||
thinkingOverride: thinkingOverride,
|
||||
listener: listener,
|
||||
server: &http.Server{
|
||||
Handler: mux,
|
||||
ReadTimeout: 10 * time.Minute,
|
||||
WriteTimeout: 10 * time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := pp.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("providerproxy: serve error", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
localURL := fmt.Sprintf("http://127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port)
|
||||
slog.Info("providerproxy: started", "target", targetURL, "local", localURL, "thinking", thinkingOverride)
|
||||
return pp, localURL, nil
|
||||
}
|
||||
|
||||
// Close shuts down the proxy.
|
||||
func (pp *ProviderProxy) Close() {
|
||||
pp.once.Do(func() {
|
||||
pp.server.Close()
|
||||
})
|
||||
}
|
||||
|
||||
// rewriteThinkingInRequest reads the request body and rewrites
|
||||
// thinking.type "adaptive" to the given override value.
|
||||
func rewriteThinkingInRequest(r *http.Request, override string) {
|
||||
if r.Body == nil || override == "" {
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
r.Body = io.NopCloser(bytes.NewReader(body))
|
||||
return
|
||||
}
|
||||
|
||||
var data map[string]any
|
||||
if err := json.Unmarshal(body, &data); err != nil {
|
||||
r.Body = io.NopCloser(bytes.NewReader(body))
|
||||
r.ContentLength = int64(len(body))
|
||||
return
|
||||
}
|
||||
|
||||
modified := false
|
||||
if thinking, ok := data["thinking"].(map[string]any); ok {
|
||||
if t, ok := thinking["type"].(string); ok && t == "adaptive" {
|
||||
thinking["type"] = override
|
||||
if override == "disabled" {
|
||||
delete(thinking, "budget_tokens")
|
||||
}
|
||||
modified = true
|
||||
slog.Debug("providerproxy: rewrote thinking adaptive →", "override", override)
|
||||
}
|
||||
}
|
||||
|
||||
if !modified {
|
||||
r.Body = io.NopCloser(bytes.NewReader(body))
|
||||
r.ContentLength = int64(len(body))
|
||||
return
|
||||
}
|
||||
|
||||
newBody, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
r.Body = io.NopCloser(bytes.NewReader(body))
|
||||
r.ContentLength = int64(len(body))
|
||||
return
|
||||
}
|
||||
r.Body = io.NopCloser(bytes.NewReader(newBody))
|
||||
r.ContentLength = int64(len(newBody))
|
||||
}
|
||||
Reference in New Issue
Block a user