Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
137
backend/internal/runtime/executor/helps/claude_diagnostics.go
Normal file
137
backend/internal/runtime/executor/helps/claude_diagnostics.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package helps
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
claudeDiagnosticsTTL = time.Hour
|
||||
claudeDiagnosticsCleanupPeriod = 15 * time.Minute
|
||||
claudeDiagnosticsMaxEntries = 4096
|
||||
claudeDiagnosticsEvictBatchSize = 256
|
||||
)
|
||||
|
||||
type claudeDiagnosticsEntry struct {
|
||||
previousMessageID string
|
||||
minimumSequence uint64
|
||||
committedSequence uint64
|
||||
lastAccess uint64
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
var claudeDiagnosticsState = struct {
|
||||
sync.Mutex
|
||||
entries map[string]claudeDiagnosticsEntry
|
||||
lastCleanup time.Time
|
||||
nextSequence uint64
|
||||
nextAccess uint64
|
||||
}{entries: make(map[string]claudeDiagnosticsEntry)}
|
||||
|
||||
// BeginClaudeDiagnostics starts one request generation for a stable credential
|
||||
// identity and Claude conversation. It returns the last successfully completed
|
||||
// upstream message ID, if any. Only a SHA-256 digest of the credential identity
|
||||
// and session is retained as the cache key, so access-token rotation does not
|
||||
// interrupt continuity.
|
||||
func BeginClaudeDiagnostics(credentialIdentity, sessionID string) (key string, sequence uint64, previousMessageID string) {
|
||||
credentialIdentity = strings.TrimSpace(credentialIdentity)
|
||||
sessionID = strings.TrimSpace(sessionID)
|
||||
if credentialIdentity == "" || sessionID == "" {
|
||||
return "", 0, ""
|
||||
}
|
||||
digest := sha256.Sum256([]byte(credentialIdentity + "\x00" + sessionID))
|
||||
key = hex.EncodeToString(digest[:])
|
||||
now := time.Now()
|
||||
|
||||
claudeDiagnosticsState.Lock()
|
||||
defer claudeDiagnosticsState.Unlock()
|
||||
cleanupClaudeDiagnosticsLocked(now)
|
||||
|
||||
entry, found := claudeDiagnosticsState.entries[key]
|
||||
newGeneration := !found || (!entry.expiresAt.IsZero() && now.After(entry.expiresAt))
|
||||
if newGeneration && !found {
|
||||
evictClaudeDiagnosticsLocked()
|
||||
}
|
||||
|
||||
claudeDiagnosticsState.nextSequence++
|
||||
sequence = claudeDiagnosticsState.nextSequence
|
||||
if newGeneration {
|
||||
entry = claudeDiagnosticsEntry{minimumSequence: sequence}
|
||||
}
|
||||
claudeDiagnosticsState.nextAccess++
|
||||
entry.lastAccess = claudeDiagnosticsState.nextAccess
|
||||
entry.expiresAt = now.Add(claudeDiagnosticsTTL)
|
||||
claudeDiagnosticsState.entries[key] = entry
|
||||
return key, sequence, entry.previousMessageID
|
||||
}
|
||||
|
||||
// CommitClaudeDiagnostics advances continuity only after a response completes.
|
||||
// A response from an older concurrently-started request cannot overwrite a
|
||||
// newer committed generation, including after TTL expiry or capacity eviction.
|
||||
func CommitClaudeDiagnostics(key string, sequence uint64, messageID string) {
|
||||
key = strings.TrimSpace(key)
|
||||
messageID = strings.TrimSpace(messageID)
|
||||
if key == "" || sequence == 0 || messageID == "" {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
claudeDiagnosticsState.Lock()
|
||||
defer claudeDiagnosticsState.Unlock()
|
||||
entry, ok := claudeDiagnosticsState.entries[key]
|
||||
if !ok || sequence < entry.minimumSequence || sequence < entry.committedSequence {
|
||||
return
|
||||
}
|
||||
claudeDiagnosticsState.nextAccess++
|
||||
entry.previousMessageID = messageID
|
||||
entry.committedSequence = sequence
|
||||
entry.lastAccess = claudeDiagnosticsState.nextAccess
|
||||
entry.expiresAt = now.Add(claudeDiagnosticsTTL)
|
||||
claudeDiagnosticsState.entries[key] = entry
|
||||
}
|
||||
|
||||
func cleanupClaudeDiagnosticsLocked(now time.Time) {
|
||||
if !claudeDiagnosticsState.lastCleanup.IsZero() && now.Sub(claudeDiagnosticsState.lastCleanup) < claudeDiagnosticsCleanupPeriod {
|
||||
return
|
||||
}
|
||||
for key, entry := range claudeDiagnosticsState.entries {
|
||||
if !entry.expiresAt.IsZero() && now.After(entry.expiresAt) {
|
||||
delete(claudeDiagnosticsState.entries, key)
|
||||
}
|
||||
}
|
||||
claudeDiagnosticsState.lastCleanup = now
|
||||
}
|
||||
|
||||
func evictClaudeDiagnosticsLocked() {
|
||||
if len(claudeDiagnosticsState.entries) < claudeDiagnosticsMaxEntries {
|
||||
return
|
||||
}
|
||||
type candidate struct {
|
||||
key string
|
||||
lastAccess uint64
|
||||
}
|
||||
candidates := make([]candidate, 0, len(claudeDiagnosticsState.entries))
|
||||
for key, entry := range claudeDiagnosticsState.entries {
|
||||
candidates = append(candidates, candidate{key: key, lastAccess: entry.lastAccess})
|
||||
}
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
return candidates[i].lastAccess < candidates[j].lastAccess
|
||||
})
|
||||
count := min(claudeDiagnosticsEvictBatchSize, len(candidates))
|
||||
for _, candidate := range candidates[:count] {
|
||||
delete(claudeDiagnosticsState.entries, candidate.key)
|
||||
}
|
||||
}
|
||||
|
||||
func resetClaudeDiagnosticsForTest() {
|
||||
claudeDiagnosticsState.Lock()
|
||||
defer claudeDiagnosticsState.Unlock()
|
||||
claudeDiagnosticsState.entries = make(map[string]claudeDiagnosticsEntry)
|
||||
claudeDiagnosticsState.lastCleanup = time.Time{}
|
||||
claudeDiagnosticsState.nextSequence = 0
|
||||
claudeDiagnosticsState.nextAccess = 0
|
||||
}
|
||||
Loading…
Reference in a new issue