Skip to content

Commit 37f8918

Browse files
author
ccs-upstream-sync[bot]
committed
Merge remote-tracking branch 'upstream/main' into upstream-sync/20260503-0612
2 parents 8756a86 + 56df368 commit 37f8918

9 files changed

Lines changed: 63 additions & 4 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ Never stop coding. Smart routing to FREE & low-cost AI models with automatic fal
212212

213213
OmniRoute is an AI gateway for multi-provider LLMs: an OpenAI-compatible endpoint with smart routing, load balancing, retries, and fallbacks. Add policies, rate limits, caching, and observability for reliable, cost-aware inference.
214214

215+
### [Playful Proxy API Panel (PPAP)](https://github.com/daishuge/playful-proxy-api-panel)
216+
217+
A public CLIProxyAPI-compatible fork and bundled management panel. It keeps upstream-style usage while restoring built-in usage statistics, adding cache hit rate, first-byte latency, TPS tracking, and Docker-oriented self-hosted installation docs.
218+
215219
> [!NOTE]
216220
> If you have developed a port of CLIProxyAPI or a project inspired by it, please open a PR to add it to this list.
217221

README_CN.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ Shadow AI 是一款专为受限环境设计的 AI 辅助工具。提供无窗口
208208

209209
OmniRoute 是一个面向多供应商大语言模型的 AI 网关:它提供兼容 OpenAI 的端点,具备智能路由、负载均衡、重试及回退机制。通过添加策略、速率限制、缓存和可观测性,确保推理过程既可靠又具备成本意识。
210210

211+
### [Playful Proxy API Panel (PPAP)](https://github.com/daishuge/playful-proxy-api-panel)
212+
213+
一个公开的 CLIProxyAPI 兼容二开版本和配套管理面板,尽量保持与上游一致的使用方式,同时恢复内置使用量统计,并补充缓存命中率、首字响应时间、TPS 记录和面向 Docker 自托管的安装说明。
214+
211215
> [!NOTE]
212216
> 如果你开发了 CLIProxyAPI 的移植或衍生项目,请提交 PR 将其添加到此列表中。
213217

README_JA.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ CLIProxyAPIに触発されたNext.js実装。インストールと使用が簡
207207

208208
OmniRouteはマルチプロバイダーLLM向けのAIゲートウェイです:スマートルーティング、負荷分散、リトライ、フォールバックを備えたOpenAI互換エンドポイント。ポリシー、レート制限、キャッシュ、可観測性を追加して、信頼性が高くコストを意識した推論を実現します。
209209

210+
### [Playful Proxy API Panel (PPAP)](https://github.com/daishuge/playful-proxy-api-panel)
211+
212+
上流に近い使い方を維持する公開CLIProxyAPI互換フォーク兼管理パネルです。内蔵の使用量統計を復元し、キャッシュヒット率、初回バイト待ち時間、TPSの記録、Docker向けのセルフホスト手順を追加しています。
213+
210214
> [!NOTE]
211215
> CLIProxyAPIの移植版またはそれに触発されたプロジェクトを開発した場合は、PRを送ってこのリストに追加してください。
212216

cmd/server/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ func main() {
470470
}
471471
}
472472
redisqueue.SetUsageStatisticsEnabled(cfg.UsageStatisticsEnabled)
473+
redisqueue.SetRetentionSeconds(cfg.RedisUsageQueueRetentionSeconds)
473474
coreauth.SetQuotaCooldownDisabled(cfg.DisableCooling)
474475

475476
if err = logging.ConfigureLogOutput(cfg); err != nil {

config.example.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ error-logs-max-files: 10
7171
# When false, disable in-memory usage statistics aggregation
7272
usage-statistics-enabled: false
7373

74+
# How long (in seconds) Redis usage queue items are retained in memory for the RESP interface (LPOP/RPOP).
75+
# Default: 60. Max: 3600.
76+
redis-usage-queue-retention-seconds: 60
77+
7478
# Proxy URL. Supports socks5/http/https protocols. Example: socks5://user:pass@192.168.1.1:1080/
7579
# Per-entry proxy-url also supports "direct" or "none" to bypass both the global proxy-url and environment proxies explicitly.
7680
proxy-url: ""

internal/api/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,10 @@ func (s *Server) UpdateClients(cfg *config.Config) {
10501050
redisqueue.SetUsageStatisticsEnabled(cfg.UsageStatisticsEnabled)
10511051
}
10521052

1053+
if oldCfg == nil || oldCfg.RedisUsageQueueRetentionSeconds != cfg.RedisUsageQueueRetentionSeconds {
1054+
redisqueue.SetRetentionSeconds(cfg.RedisUsageQueueRetentionSeconds)
1055+
}
1056+
10531057
if s.requestLogger != nil && (oldCfg == nil || oldCfg.ErrorLogsMaxFiles != cfg.ErrorLogsMaxFiles) {
10541058
if setter, ok := s.requestLogger.(interface{ SetErrorLogsMaxFiles(int) }); ok {
10551059
setter.SetErrorLogsMaxFiles(cfg.ErrorLogsMaxFiles)

internal/config/config.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ type Config struct {
6565
// UsageStatisticsEnabled toggles in-memory usage aggregation; when false, usage data is discarded.
6666
UsageStatisticsEnabled bool `yaml:"usage-statistics-enabled" json:"usage-statistics-enabled"`
6767

68+
// RedisUsageQueueRetentionSeconds controls how long (in seconds) usage queue items
69+
// are retained in memory for the Redis RESP interface (LPOP/RPOP).
70+
// Default: 60. Max: 3600.
71+
RedisUsageQueueRetentionSeconds int `yaml:"redis-usage-queue-retention-seconds" json:"redis-usage-queue-retention-seconds"`
72+
6873
// DisableCooling disables quota cooldown scheduling when true.
6974
DisableCooling bool `yaml:"disable-cooling" json:"disable-cooling"`
7075

@@ -672,6 +677,7 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
672677
cfg.LogsMaxTotalSizeMB = 0
673678
cfg.ErrorLogsMaxFiles = 10
674679
cfg.UsageStatisticsEnabled = false
680+
cfg.RedisUsageQueueRetentionSeconds = 60
675681
cfg.DisableCooling = false
676682
cfg.DisableImageGeneration = DisableImageGenerationOff
677683
cfg.Pprof.Enable = false
@@ -735,6 +741,13 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
735741
cfg.ErrorLogsMaxFiles = 10
736742
}
737743

744+
if cfg.RedisUsageQueueRetentionSeconds <= 0 {
745+
cfg.RedisUsageQueueRetentionSeconds = 60
746+
} else if cfg.RedisUsageQueueRetentionSeconds > 3600 {
747+
log.WithField("value", cfg.RedisUsageQueueRetentionSeconds).Warn("redis-usage-queue-retention-seconds too large; clamping to 3600")
748+
cfg.RedisUsageQueueRetentionSeconds = 3600
749+
}
750+
738751
if cfg.MaxRetryCredentials < 0 {
739752
cfg.MaxRetryCredentials = 0
740753
}

internal/redisqueue/queue.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import (
66
"time"
77
)
88

9-
const retentionWindow = time.Minute
9+
const (
10+
defaultRetentionSeconds int64 = 60
11+
maxRetentionSeconds int64 = 3600
12+
)
1013

1114
type queueItem struct {
1215
enqueuedAt time.Time
@@ -20,10 +23,15 @@ type queue struct {
2023
}
2124

2225
var (
23-
enabled atomic.Bool
24-
global queue
26+
enabled atomic.Bool
27+
retentionSeconds atomic.Int64
28+
global queue
2529
)
2630

31+
func init() {
32+
retentionSeconds.Store(defaultRetentionSeconds)
33+
}
34+
2735
func SetEnabled(value bool) {
2836
enabled.Store(value)
2937
if !value {
@@ -35,6 +43,16 @@ func Enabled() bool {
3543
return enabled.Load()
3644
}
3745

46+
func SetRetentionSeconds(value int) {
47+
normalized := int64(value)
48+
if normalized <= 0 {
49+
normalized = defaultRetentionSeconds
50+
} else if normalized > maxRetentionSeconds {
51+
normalized = maxRetentionSeconds
52+
}
53+
retentionSeconds.Store(normalized)
54+
}
55+
3856
func Enqueue(payload []byte) {
3957
if !Enabled() {
4058
return
@@ -110,7 +128,11 @@ func (q *queue) pruneLocked(now time.Time) {
110128
return
111129
}
112130

113-
cutoff := now.Add(-retentionWindow)
131+
windowSeconds := retentionSeconds.Load()
132+
if windowSeconds <= 0 {
133+
windowSeconds = defaultRetentionSeconds
134+
}
135+
cutoff := now.Add(-time.Duration(windowSeconds) * time.Second)
114136
for q.head < len(q.items) && q.items[q.head].enqueuedAt.Before(cutoff) {
115137
q.head++
116138
}

internal/watcher/diff/config_diff.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ func BuildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
3939
if oldCfg.UsageStatisticsEnabled != newCfg.UsageStatisticsEnabled {
4040
changes = append(changes, fmt.Sprintf("usage-statistics-enabled: %t -> %t", oldCfg.UsageStatisticsEnabled, newCfg.UsageStatisticsEnabled))
4141
}
42+
if oldCfg.RedisUsageQueueRetentionSeconds != newCfg.RedisUsageQueueRetentionSeconds {
43+
changes = append(changes, fmt.Sprintf("redis-usage-queue-retention-seconds: %d -> %d", oldCfg.RedisUsageQueueRetentionSeconds, newCfg.RedisUsageQueueRetentionSeconds))
44+
}
4245
if oldCfg.DisableCooling != newCfg.DisableCooling {
4346
changes = append(changes, fmt.Sprintf("disable-cooling: %t -> %t", oldCfg.DisableCooling, newCfg.DisableCooling))
4447
}

0 commit comments

Comments
 (0)