mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-04 14:49:59 +08:00
- Wire parity: align Connect-RPC Sentry-Trace, User-Agent suppression, float32 double pattern, and dynamic 732-char hex device fingerprint - Session ordinal & cache: implement process-scoped Field 15.2 with bounded LRU (5000 entries) and Field 15.4=14 user boundary; prioritize stable session_id over previous_interaction_id to preserve prompt caching - Streaming robustness: unblock hung TCP reads on client cancellation via context watcher; accurately propagate stream read errors and trailer errors instead of swallowing truncated frames - Thought signature & reasoning: emit raw delta signatures directly in active thought steps; eliminate redundant tail base64 re-encoding; ensure 1:1 assistant signature and thinking alignment across multi-turn history - Tool call de-multiplexing: route parallel tool calls by tc.Index in both streaming step events and non-streaming aggregations - Security & transport: escape OAuth callback error HTML against reflected XSS, enforce strict state validation, and isolate Devin HTTP transport with tr.Clone()
98 lines
3.3 KiB
Go
98 lines
3.3 KiB
Go
package helps
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// NewProxyAwareHTTPClient creates an HTTP client with proper proxy configuration priority:
|
|
// 1. Use auth.ProxyURL if configured (highest priority)
|
|
// 2. Use cfg.ProxyURL if auth proxy is not configured
|
|
// 3. Use RoundTripper from context if neither are configured
|
|
//
|
|
// Parameters:
|
|
// - ctx: The context containing optional RoundTripper
|
|
// - cfg: The application configuration
|
|
// - auth: The authentication information
|
|
// - timeout: The client timeout (0 means no timeout)
|
|
//
|
|
// Returns:
|
|
// - *http.Client: An HTTP client with configured proxy or transport
|
|
func NewProxyAwareHTTPClient(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth, timeout time.Duration) *http.Client {
|
|
httpClient := &http.Client{}
|
|
if timeout > 0 {
|
|
httpClient.Timeout = timeout
|
|
}
|
|
|
|
// Priority 1: Use auth.ProxyURL if configured
|
|
var proxyURL string
|
|
if auth != nil {
|
|
proxyURL = strings.TrimSpace(auth.ProxyURL)
|
|
}
|
|
|
|
// Priority 2: Use cfg.ProxyURL if auth proxy is not configured
|
|
if proxyURL == "" && cfg != nil {
|
|
proxyURL = strings.TrimSpace(cfg.ProxyURL)
|
|
}
|
|
|
|
// If we have a proxy URL configured, set up the transport
|
|
if proxyURL != "" {
|
|
transport := buildProxyTransport(proxyURL)
|
|
if transport != nil {
|
|
httpClient.Transport = transport
|
|
return httpClient
|
|
}
|
|
// If proxy setup failed, log and fall through to context RoundTripper
|
|
log.Debugf("failed to setup proxy from URL: %s, falling back to context transport", proxyutil.Redact(proxyURL))
|
|
}
|
|
|
|
// Priority 3: Use RoundTripper from context (typically from RoundTripperFor)
|
|
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
httpClient.Transport = rt
|
|
}
|
|
|
|
return httpClient
|
|
}
|
|
|
|
// NewDevinHTTPClient creates an HTTP client customized for Devin Connect-RPC upstream.
|
|
// Suppresses automatic Accept-Encoding: gzip while preserving robust HTTP/2 streaming.
|
|
func NewDevinHTTPClient(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth, timeout time.Duration) *http.Client {
|
|
httpClient := NewProxyAwareHTTPClient(ctx, cfg, auth, timeout)
|
|
if httpClient.Transport == nil {
|
|
if dt, ok := http.DefaultTransport.(*http.Transport); ok {
|
|
httpClient.Transport = dt.Clone()
|
|
}
|
|
} else if tr, ok := httpClient.Transport.(*http.Transport); ok {
|
|
// Clone transport to avoid mutating a shared or cached roundtripper
|
|
httpClient.Transport = tr.Clone()
|
|
}
|
|
if tr, ok := httpClient.Transport.(*http.Transport); ok {
|
|
tr.DisableCompression = true
|
|
}
|
|
return httpClient
|
|
}
|
|
|
|
// buildProxyTransport creates an HTTP transport configured for the given proxy URL.
|
|
// It supports SOCKS5, HTTP, and HTTPS proxy protocols.
|
|
//
|
|
// Parameters:
|
|
// - proxyURL: The proxy URL string (e.g., "socks5://user:pass@host:port", "http://host:port")
|
|
//
|
|
// Returns:
|
|
// - *http.Transport: A configured transport, or nil if the proxy URL is invalid
|
|
func buildProxyTransport(proxyURL string) *http.Transport {
|
|
transport, _, errBuild := proxyutil.BuildHTTPTransport(proxyURL)
|
|
if errBuild != nil {
|
|
log.Errorf("%v", errBuild)
|
|
return nil
|
|
}
|
|
return transport
|
|
}
|