mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-13 11:08:03 +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()
102 lines
2.4 KiB
Go
102 lines
2.4 KiB
Go
package cache
|
|
|
|
import (
|
|
"container/list"
|
|
"sync"
|
|
)
|
|
|
|
type boundedLRUEntry[K comparable, V any] struct {
|
|
key K
|
|
value V
|
|
}
|
|
|
|
// BoundedLRU stores at most capacity values and evicts the least recently used
|
|
// value when a new key crosses the bound. The optional eviction callback runs
|
|
// after the cache lock is released.
|
|
type BoundedLRU[K comparable, V any] struct {
|
|
mu sync.Mutex
|
|
capacity int
|
|
entries map[K]*list.Element
|
|
order *list.List
|
|
onEvict func(K, V)
|
|
}
|
|
|
|
func NewBoundedLRU[K comparable, V any](capacity int, onEvict func(K, V)) *BoundedLRU[K, V] {
|
|
if capacity < 1 {
|
|
capacity = 1
|
|
}
|
|
return &BoundedLRU[K, V]{
|
|
capacity: capacity,
|
|
entries: make(map[K]*list.Element, capacity),
|
|
order: list.New(),
|
|
onEvict: onEvict,
|
|
}
|
|
}
|
|
|
|
// GetOrAdd returns the cached value or creates and stores one while holding the
|
|
// cache lock. The create function must not call back into this cache.
|
|
func (cache *BoundedLRU[K, V]) GetOrAdd(key K, create func() V) V {
|
|
cache.mu.Lock()
|
|
if element, ok := cache.entries[key]; ok {
|
|
cache.order.MoveToFront(element)
|
|
value := element.Value.(boundedLRUEntry[K, V]).value
|
|
cache.mu.Unlock()
|
|
return value
|
|
}
|
|
|
|
value := create()
|
|
element := cache.order.PushFront(boundedLRUEntry[K, V]{key: key, value: value})
|
|
cache.entries[key] = element
|
|
|
|
var evicted boundedLRUEntry[K, V]
|
|
didEvict := false
|
|
if cache.order.Len() > cache.capacity {
|
|
oldest := cache.order.Back()
|
|
evicted = oldest.Value.(boundedLRUEntry[K, V])
|
|
delete(cache.entries, evicted.key)
|
|
cache.order.Remove(oldest)
|
|
didEvict = true
|
|
}
|
|
cache.mu.Unlock()
|
|
|
|
if didEvict && cache.onEvict != nil {
|
|
cache.onEvict(evicted.key, evicted.value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (cache *BoundedLRU[K, V]) Get(key K) (V, bool) {
|
|
cache.mu.Lock()
|
|
defer cache.mu.Unlock()
|
|
if element, ok := cache.entries[key]; ok {
|
|
cache.order.MoveToFront(element)
|
|
return element.Value.(boundedLRUEntry[K, V]).value, true
|
|
}
|
|
var zero V
|
|
return zero, false
|
|
}
|
|
|
|
func (cache *BoundedLRU[K, V]) Delete(key K) bool {
|
|
cache.mu.Lock()
|
|
element, ok := cache.entries[key]
|
|
if !ok {
|
|
cache.mu.Unlock()
|
|
return false
|
|
}
|
|
entry := element.Value.(boundedLRUEntry[K, V])
|
|
delete(cache.entries, key)
|
|
cache.order.Remove(element)
|
|
cache.mu.Unlock()
|
|
|
|
if cache.onEvict != nil {
|
|
cache.onEvict(entry.key, entry.value)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (cache *BoundedLRU[K, V]) Len() int {
|
|
cache.mu.Lock()
|
|
defer cache.mu.Unlock()
|
|
return len(cache.entries)
|
|
}
|