WIP commit保存以下定制工作以便后续合并 upstream v0.1.124-125: - Windsurf: tier access service, NLU extractor, cold threshold, Google login - Antigravity: client/oauth 调整 - Ops: log stream handler/broadcaster/middleware, OpsLogStreamView - Frontend: WindsurfLoginModal Google, GoogleIcon, AccountsView, sidebar/router/i18n
109 lines
3.4 KiB
Go
109 lines
3.4 KiB
Go
package windsurf
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ColdThresholdConfig parameterises the adaptive cold-stall timeout. Values
|
|
// can be overridden via env vars (see DefaultColdThresholdConfig).
|
|
type ColdThresholdConfig struct {
|
|
// Base is the timeout for an empty / tiny prompt (e.g. "ping").
|
|
Base time.Duration
|
|
// PerKChar adds this much time for every 1000 characters of input.
|
|
PerKChar time.Duration
|
|
// Max caps the total threshold regardless of input size. Callers may
|
|
// further clamp via the runtime maxWait argument.
|
|
Max time.Duration
|
|
}
|
|
|
|
var (
|
|
defaultColdCfg ColdThresholdConfig
|
|
defaultColdCfgOnce sync.Once
|
|
)
|
|
|
|
// DefaultColdThresholdConfig returns the active config. The first call
|
|
// resolves env-var overrides:
|
|
//
|
|
// WINDSURF_COLD_BASE_SECONDS (default 30)
|
|
// WINDSURF_COLD_PER_KCHAR_SEC (default 5)
|
|
// WINDSURF_COLD_MAX_SECONDS (default 90)
|
|
//
|
|
// Defaults match dwgx/WindsurfAPI's empirical "long inputs up to 90s" rule
|
|
// while preserving the prior 30s base for backward compatibility.
|
|
func DefaultColdThresholdConfig() ColdThresholdConfig {
|
|
defaultColdCfgOnce.Do(func() {
|
|
defaultColdCfg = ColdThresholdConfig{
|
|
Base: envSeconds("WINDSURF_COLD_BASE_SECONDS", 30),
|
|
PerKChar: envSeconds("WINDSURF_COLD_PER_KCHAR_SEC", 5),
|
|
Max: envSeconds("WINDSURF_COLD_MAX_SECONDS", 90),
|
|
}
|
|
if defaultColdCfg.Base <= 0 {
|
|
defaultColdCfg.Base = 30 * time.Second
|
|
}
|
|
if defaultColdCfg.Max <= 0 {
|
|
defaultColdCfg.Max = 90 * time.Second
|
|
}
|
|
if defaultColdCfg.Max < defaultColdCfg.Base {
|
|
defaultColdCfg.Max = defaultColdCfg.Base
|
|
}
|
|
})
|
|
return defaultColdCfg
|
|
}
|
|
|
|
// AdaptiveColdThreshold returns the cold-stall timeout for a given prompt
|
|
// size, applying the active ColdThresholdConfig and an absolute upstream
|
|
// cap (typically the StreamCascadeChat maxWait constant).
|
|
//
|
|
// The returned threshold is the minimum of:
|
|
//
|
|
// Base + PerKChar * (inputChars / 1000)
|
|
// Config.Max
|
|
// upstreamCap (when > 0)
|
|
//
|
|
// inputChars < 0 is treated as 0. The result is always >= Base unless
|
|
// upstreamCap < Base, in which case upstreamCap wins.
|
|
func AdaptiveColdThreshold(inputChars int, upstreamCap time.Duration) time.Duration {
|
|
cfg := DefaultColdThresholdConfig()
|
|
return ComputeColdThreshold(cfg, inputChars, upstreamCap)
|
|
}
|
|
|
|
// maxInputCharsForOverflowGuard caps inputChars before multiplication to keep
|
|
// the resulting time.Duration (int64 ns) from wrapping. 2^31 chars (~2GB)
|
|
// is already absurd for an LLM prompt; anything beyond is a bug or DoS attempt.
|
|
const maxInputCharsForOverflowGuard = 1<<31 - 1
|
|
|
|
// ComputeColdThreshold is the pure form used by tests and callers that want
|
|
// to inject a custom config without touching the singleton.
|
|
func ComputeColdThreshold(cfg ColdThresholdConfig, inputChars int, upstreamCap time.Duration) time.Duration {
|
|
if inputChars < 0 {
|
|
inputChars = 0
|
|
}
|
|
if inputChars > maxInputCharsForOverflowGuard {
|
|
inputChars = maxInputCharsForOverflowGuard
|
|
}
|
|
scaled := cfg.Base + time.Duration(inputChars/1000)*cfg.PerKChar
|
|
if cfg.Max > 0 && scaled > cfg.Max {
|
|
scaled = cfg.Max
|
|
}
|
|
if upstreamCap > 0 && scaled > upstreamCap {
|
|
scaled = upstreamCap
|
|
}
|
|
return scaled
|
|
}
|
|
|
|
func envSeconds(key string, defaultSec int) time.Duration {
|
|
raw := strings.TrimSpace(os.Getenv(key))
|
|
if raw == "" {
|
|
return time.Duration(defaultSec) * time.Second
|
|
}
|
|
v, err := strconv.Atoi(raw)
|
|
if err != nil || v <= 0 {
|
|
return time.Duration(defaultSec) * time.Second
|
|
}
|
|
return time.Duration(v) * time.Second
|
|
}
|