Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
656
backend/internal/cache/antigravity_reasoning_replay_cache.go
vendored
Normal file
656
backend/internal/cache/antigravity_reasoning_replay_cache.go
vendored
Normal file
|
|
@ -0,0 +1,656 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
homekv "github.com/router-for-me/CLIProxyAPI/v7/internal/home"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
const (
|
||||
// AntigravityReasoningReplayCacheTTL limits how long encrypted reasoning replay
|
||||
// items stay in process memory.
|
||||
AntigravityReasoningReplayCacheTTL = 1 * time.Hour
|
||||
|
||||
// AntigravityReasoningReplayCacheMaxEntries bounds process memory for replay
|
||||
// continuity. Oldest entries are evicted first.
|
||||
AntigravityReasoningReplayCacheMaxEntries = 10240
|
||||
|
||||
// AntigravityReasoningReplayCacheEvictBatchSize leaves headroom after the cache
|
||||
// reaches capacity so high write volume does not rescan the map every turn.
|
||||
AntigravityReasoningReplayCacheEvictBatchSize = 128
|
||||
|
||||
minAntigravityThoughtSignatureReplayLen = 16
|
||||
|
||||
// AntigravityReasoningReplayCacheMaxItemsPerEntry and MaxBytesPerEntry
|
||||
// bound one logical conversation. Oversized chains are not partially cached,
|
||||
// because dropping an arbitrary prefix would break native signature ordering.
|
||||
AntigravityReasoningReplayCacheMaxItemsPerEntry = 4096
|
||||
AntigravityReasoningReplayCacheMaxBytesPerEntry = 16 << 20
|
||||
|
||||
// JSON encodes each normalized []byte item as base64. Leave enough room for
|
||||
// that expansion while rejecting oversized Home values before unmarshalling.
|
||||
antigravityReasoningReplayCacheMaxSerializedBytes = 24 << 20
|
||||
)
|
||||
|
||||
type antigravityReasoningReplayEntry struct {
|
||||
Items [][]byte
|
||||
Timestamp time.Time
|
||||
Revision uint64
|
||||
Branch string
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
const antigravityReasoningReplayGenerationItemType = "cpa_antigravity_replay_generation"
|
||||
|
||||
// AntigravityReasoningReplaySnapshot identifies the exact replay state read for
|
||||
// one request. Its fields are intentionally opaque outside this package.
|
||||
type AntigravityReasoningReplaySnapshot struct {
|
||||
raw []byte
|
||||
items [][]byte
|
||||
loaded bool
|
||||
found bool
|
||||
revision uint64
|
||||
branch string
|
||||
evictionEpoch uint64
|
||||
}
|
||||
|
||||
var (
|
||||
antigravityReasoningReplayMu sync.Mutex
|
||||
antigravityReasoningReplayEntries = make(map[string]antigravityReasoningReplayEntry)
|
||||
antigravityReasoningReplayNextRevision uint64
|
||||
antigravityReasoningReplayEvictionEpoch uint64
|
||||
)
|
||||
|
||||
type antigravityReasoningReplayKVClient interface {
|
||||
KVGet(ctx context.Context, key string) ([]byte, bool, error)
|
||||
KVSet(ctx context.Context, key string, value []byte, opts homekv.KVSetOptions) (bool, error)
|
||||
KVCompareAndSwap(ctx context.Context, key string, expected []byte, expectedExists bool, value []byte, ttl time.Duration) (bool, error)
|
||||
KVExpire(ctx context.Context, key string, ttl time.Duration) (bool, error)
|
||||
}
|
||||
|
||||
var currentAntigravityReasoningReplayKVClient = func() (antigravityReasoningReplayKVClient, bool, error) {
|
||||
return homekv.CurrentKVClient()
|
||||
}
|
||||
|
||||
// CacheAntigravityReasoningReplayItem stores a final GPT/Codex reasoning item for
|
||||
// stateless replay. The stored item is normalized to the minimal shape accepted
|
||||
// by Responses input replay.
|
||||
func CacheAntigravityReasoningReplayItem(modelName, sessionKey string, item []byte) bool {
|
||||
return CacheAntigravityReasoningReplayItems(modelName, sessionKey, [][]byte{item})
|
||||
}
|
||||
|
||||
// CacheAntigravityReasoningReplayItems stores the final GPT/Codex assistant output
|
||||
// items needed to replay a stateless next turn.
|
||||
func CacheAntigravityReasoningReplayItems(modelName, sessionKey string, items [][]byte) bool {
|
||||
return CacheAntigravityReasoningReplayItemsBestEffort(context.Background(), modelName, sessionKey, items)
|
||||
}
|
||||
|
||||
// CacheAntigravityReasoningReplayItemsBestEffort stores replay items for completed response paths.
|
||||
func CacheAntigravityReasoningReplayItemsBestEffort(ctx context.Context, modelName, sessionKey string, items [][]byte) bool {
|
||||
key := antigravityReasoningReplayCacheKey(modelName, sessionKey)
|
||||
if key == "" {
|
||||
return false
|
||||
}
|
||||
normalized, ok := normalizeAntigravityReasoningReplayItems(items)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if client, homeMode, errClient := currentAntigravityReasoningReplayKVClient(); homeMode {
|
||||
if errClient != nil {
|
||||
log.Errorf("home kv best-effort antigravity reasoning replay set failed prefix=cpa:antigravity:*: %v", errClient)
|
||||
return false
|
||||
}
|
||||
raw, errMarshal := marshalAntigravityReasoningReplayHomeValue(normalized, "")
|
||||
if errMarshal != nil {
|
||||
log.Errorf("home kv best-effort antigravity reasoning replay set failed prefix=cpa:antigravity:*: %v", errMarshal)
|
||||
return false
|
||||
}
|
||||
written, errSet := client.KVSet(ctx, antigravityReasoningReplayKVKey(modelName, sessionKey), raw, homekv.KVSetOptions{EX: AntigravityReasoningReplayCacheTTL})
|
||||
if errSet != nil {
|
||||
log.Errorf("home kv best-effort antigravity reasoning replay set failed prefix=cpa:antigravity:*: %v", errSet)
|
||||
return false
|
||||
}
|
||||
return written
|
||||
}
|
||||
|
||||
cacheCleanupOnce.Do(startCacheCleanup)
|
||||
now := time.Now()
|
||||
antigravityReasoningReplayMu.Lock()
|
||||
defer antigravityReasoningReplayMu.Unlock()
|
||||
antigravityReasoningReplayNextRevision++
|
||||
antigravityReasoningReplayEntries[key] = antigravityReasoningReplayEntry{
|
||||
Items: normalized,
|
||||
Timestamp: now,
|
||||
Revision: antigravityReasoningReplayNextRevision,
|
||||
Branch: newAntigravityReasoningReplayGeneration(),
|
||||
}
|
||||
if len(antigravityReasoningReplayEntries) > AntigravityReasoningReplayCacheMaxEntries {
|
||||
evictOldestAntigravityReasoningReplayEntries(AntigravityReasoningReplayCacheEvictBatchSize)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetAntigravityReasoningReplayItem retrieves a normalized reasoning replay item.
|
||||
func GetAntigravityReasoningReplayItem(modelName, sessionKey string) ([]byte, bool) {
|
||||
items, ok := GetAntigravityReasoningReplayItems(modelName, sessionKey)
|
||||
if !ok || len(items) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
return items[0], true
|
||||
}
|
||||
|
||||
// GetAntigravityReasoningReplayItems retrieves normalized assistant output items.
|
||||
func GetAntigravityReasoningReplayItems(modelName, sessionKey string) ([][]byte, bool) {
|
||||
items, ok, err := GetAntigravityReasoningReplayItemsRequired(context.Background(), modelName, sessionKey)
|
||||
if err == nil {
|
||||
return items, ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetAntigravityReasoningReplayItemsRequired retrieves replay items for request-time paths.
|
||||
func GetAntigravityReasoningReplayItemsRequired(ctx context.Context, modelName, sessionKey string) ([][]byte, bool, error) {
|
||||
items, _, found, errGet := GetAntigravityReasoningReplayItemsWithSnapshotRequired(ctx, modelName, sessionKey)
|
||||
return items, found, errGet
|
||||
}
|
||||
|
||||
// GetAntigravityReasoningReplayItemsWithSnapshotRequired retrieves replay items
|
||||
// and the exact cache state that guarded this request.
|
||||
func GetAntigravityReasoningReplayItemsWithSnapshotRequired(ctx context.Context, modelName, sessionKey string) ([][]byte, AntigravityReasoningReplaySnapshot, bool, error) {
|
||||
key := antigravityReasoningReplayCacheKey(modelName, sessionKey)
|
||||
if key == "" {
|
||||
return nil, AntigravityReasoningReplaySnapshot{}, false, nil
|
||||
}
|
||||
client, homeMode, errClient := currentAntigravityReasoningReplayKVClient()
|
||||
if homeMode {
|
||||
if errClient != nil {
|
||||
return nil, AntigravityReasoningReplaySnapshot{}, false, errClient
|
||||
}
|
||||
kvKey := antigravityReasoningReplayKVKey(modelName, sessionKey)
|
||||
var raw []byte
|
||||
found := false
|
||||
for attempt := 0; attempt < 4; attempt++ {
|
||||
currentRaw, currentFound, errGet := client.KVGet(ctx, kvKey)
|
||||
if errGet != nil {
|
||||
return nil, AntigravityReasoningReplaySnapshot{loaded: true}, false, errGet
|
||||
}
|
||||
if currentFound {
|
||||
raw = currentRaw
|
||||
found = true
|
||||
break
|
||||
}
|
||||
reservation := newAntigravityReasoningReplayTombstone()
|
||||
swapped, errReserve := client.KVCompareAndSwap(ctx, kvKey, nil, false, reservation, AntigravityReasoningReplayCacheTTL)
|
||||
if errReserve != nil {
|
||||
return nil, AntigravityReasoningReplaySnapshot{loaded: true}, false, errReserve
|
||||
}
|
||||
if swapped {
|
||||
raw = reservation
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, AntigravityReasoningReplaySnapshot{loaded: true}, false, fmt.Errorf("could not fence absent antigravity reasoning replay state")
|
||||
}
|
||||
if len(raw) > antigravityReasoningReplayCacheMaxSerializedBytes {
|
||||
return nil, AntigravityReasoningReplaySnapshot{loaded: true, found: true}, false, nil
|
||||
}
|
||||
snapshot := AntigravityReasoningReplaySnapshot{raw: append([]byte(nil), raw...), loaded: true, found: true}
|
||||
homeItems, deleted, _, branch, okDecode := decodeAntigravityReasoningReplayHomeValue(raw)
|
||||
snapshot.branch = branch
|
||||
if !okDecode || deleted || len(homeItems) == 0 {
|
||||
return nil, snapshot, false, nil
|
||||
}
|
||||
if len(homeItems) > AntigravityReasoningReplayCacheMaxItemsPerEntry {
|
||||
return nil, snapshot, false, nil
|
||||
}
|
||||
normalized, okNormalize := normalizeAntigravityReasoningReplayItems(homeItems)
|
||||
if !okNormalize || len(normalized) != len(homeItems) {
|
||||
return nil, snapshot, false, nil
|
||||
}
|
||||
snapshot.items = cloneAntigravityReasoningReplayItems(normalized)
|
||||
if _, errExpire := client.KVExpire(ctx, kvKey, AntigravityReasoningReplayCacheTTL); errExpire != nil {
|
||||
return nil, snapshot, false, errExpire
|
||||
}
|
||||
return normalized, snapshot, true, nil
|
||||
}
|
||||
|
||||
cacheCleanupOnce.Do(startCacheCleanup)
|
||||
now := time.Now()
|
||||
antigravityReasoningReplayMu.Lock()
|
||||
defer antigravityReasoningReplayMu.Unlock()
|
||||
entry, ok := antigravityReasoningReplayEntries[key]
|
||||
if !ok {
|
||||
return nil, reserveAntigravityReasoningReplayAbsentLocked(key, now), false, nil
|
||||
}
|
||||
if now.Sub(entry.Timestamp) > AntigravityReasoningReplayCacheTTL {
|
||||
antigravityReasoningReplayEvictionEpoch++
|
||||
delete(antigravityReasoningReplayEntries, key)
|
||||
return nil, reserveAntigravityReasoningReplayAbsentLocked(key, now), false, nil
|
||||
}
|
||||
entry.Timestamp = now
|
||||
antigravityReasoningReplayEntries[key] = entry
|
||||
snapshot := AntigravityReasoningReplaySnapshot{loaded: true, found: true, revision: entry.Revision, branch: entry.Branch, evictionEpoch: antigravityReasoningReplayEvictionEpoch}
|
||||
if entry.Deleted || len(entry.Items) == 0 {
|
||||
return nil, snapshot, false, nil
|
||||
}
|
||||
snapshot.items = cloneAntigravityReasoningReplayItems(entry.Items)
|
||||
return cloneAntigravityReasoningReplayItems(entry.Items), snapshot, true, nil
|
||||
}
|
||||
|
||||
// reserveAntigravityReasoningReplayAbsentLocked fences a local miss with a
|
||||
// per-key tombstone so eviction of an unrelated key cannot invalidate it.
|
||||
// antigravityReasoningReplayMu must be held by the caller.
|
||||
func reserveAntigravityReasoningReplayAbsentLocked(key string, now time.Time) AntigravityReasoningReplaySnapshot {
|
||||
if len(antigravityReasoningReplayEntries) >= AntigravityReasoningReplayCacheMaxEntries {
|
||||
evictOldestAntigravityReasoningReplayEntries(AntigravityReasoningReplayCacheEvictBatchSize)
|
||||
}
|
||||
antigravityReasoningReplayNextRevision++
|
||||
entry := antigravityReasoningReplayEntry{
|
||||
Timestamp: now,
|
||||
Revision: antigravityReasoningReplayNextRevision,
|
||||
Branch: newAntigravityReasoningReplayGeneration(),
|
||||
Deleted: true,
|
||||
}
|
||||
antigravityReasoningReplayEntries[key] = entry
|
||||
return AntigravityReasoningReplaySnapshot{
|
||||
loaded: true,
|
||||
found: true,
|
||||
revision: entry.Revision,
|
||||
branch: entry.Branch,
|
||||
evictionEpoch: antigravityReasoningReplayEvictionEpoch,
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceAntigravityReasoningReplayItemsIfUnchanged publishes a completed chain
|
||||
// only when no newer request has changed the state read by this request.
|
||||
func ReplaceAntigravityReasoningReplayItemsIfUnchanged(ctx context.Context, modelName, sessionKey string, snapshot AntigravityReasoningReplaySnapshot, items [][]byte) (bool, error) {
|
||||
key := antigravityReasoningReplayCacheKey(modelName, sessionKey)
|
||||
if key == "" {
|
||||
return false, nil
|
||||
}
|
||||
normalized, okNormalize := normalizeAntigravityReasoningReplayItems(items)
|
||||
if !okNormalize {
|
||||
return false, fmt.Errorf("invalid antigravity reasoning replay items")
|
||||
}
|
||||
if !snapshot.loaded {
|
||||
return CacheAntigravityReasoningReplayItemsBestEffort(ctx, modelName, sessionKey, normalized), nil
|
||||
}
|
||||
client, homeMode, errClient := currentAntigravityReasoningReplayKVClient()
|
||||
if homeMode {
|
||||
if errClient != nil {
|
||||
return false, errClient
|
||||
}
|
||||
kvKey := antigravityReasoningReplayKVKey(modelName, sessionKey)
|
||||
expectedRaw := snapshot.raw
|
||||
expectedFound := snapshot.found
|
||||
branch := snapshot.branch
|
||||
if branch == "" || !antigravityReasoningReplayItemsPrefix(snapshot.items, normalized) {
|
||||
branch = newAntigravityReasoningReplayGeneration()
|
||||
}
|
||||
for attempt := 0; attempt < 4; attempt++ {
|
||||
raw, errMarshal := marshalAntigravityReasoningReplayHomeValue(normalized, branch)
|
||||
if errMarshal != nil {
|
||||
return false, errMarshal
|
||||
}
|
||||
swapped, errCAS := client.KVCompareAndSwap(ctx, kvKey, expectedRaw, expectedFound, raw, AntigravityReasoningReplayCacheTTL)
|
||||
if errCAS != nil || swapped {
|
||||
return swapped, errCAS
|
||||
}
|
||||
currentRaw, currentFound, errGet := client.KVGet(ctx, kvKey)
|
||||
if errGet != nil || !currentFound {
|
||||
return false, errGet
|
||||
}
|
||||
if len(currentRaw) > antigravityReasoningReplayCacheMaxSerializedBytes {
|
||||
return false, nil
|
||||
}
|
||||
currentItems, deleted, _, currentBranch, okDecode := decodeAntigravityReasoningReplayHomeValue(currentRaw)
|
||||
if !okDecode || deleted || snapshot.branch == "" || currentBranch != snapshot.branch {
|
||||
return false, nil
|
||||
}
|
||||
normalizedCurrent, okNormalizeCurrent := normalizeAntigravityReasoningReplayItems(currentItems)
|
||||
if !okNormalizeCurrent || len(normalizedCurrent) != len(currentItems) || !antigravityReasoningReplayItemsPrefix(normalizedCurrent, normalized) {
|
||||
return false, nil
|
||||
}
|
||||
expectedRaw = currentRaw
|
||||
expectedFound = true
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
cacheCleanupOnce.Do(startCacheCleanup)
|
||||
now := time.Now()
|
||||
antigravityReasoningReplayMu.Lock()
|
||||
defer antigravityReasoningReplayMu.Unlock()
|
||||
entry, found := antigravityReasoningReplayEntries[key]
|
||||
matchesSnapshot := found == snapshot.found && ((found && entry.Revision == snapshot.revision) || (!found && snapshot.evictionEpoch == antigravityReasoningReplayEvictionEpoch))
|
||||
isDescendant := found && !entry.Deleted && snapshot.branch != "" && entry.Branch == snapshot.branch && antigravityReasoningReplayItemsPrefix(entry.Items, normalized)
|
||||
if !matchesSnapshot && !isDescendant {
|
||||
return false, nil
|
||||
}
|
||||
branch := snapshot.branch
|
||||
if branch == "" || (matchesSnapshot && !antigravityReasoningReplayItemsPrefix(snapshot.items, normalized)) {
|
||||
branch = newAntigravityReasoningReplayGeneration()
|
||||
}
|
||||
antigravityReasoningReplayNextRevision++
|
||||
antigravityReasoningReplayEntries[key] = antigravityReasoningReplayEntry{Items: normalized, Timestamp: now, Revision: antigravityReasoningReplayNextRevision, Branch: branch}
|
||||
if len(antigravityReasoningReplayEntries) > AntigravityReasoningReplayCacheMaxEntries {
|
||||
evictOldestAntigravityReasoningReplayEntries(AntigravityReasoningReplayCacheEvictBatchSize)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// DeleteAntigravityReasoningReplayItemsIfUnchanged clears replay state only when
|
||||
// it still matches the state read for this request.
|
||||
func DeleteAntigravityReasoningReplayItemsIfUnchanged(ctx context.Context, modelName, sessionKey string, snapshot AntigravityReasoningReplaySnapshot) (bool, error) {
|
||||
key := antigravityReasoningReplayCacheKey(modelName, sessionKey)
|
||||
if key == "" {
|
||||
return false, nil
|
||||
}
|
||||
if !snapshot.loaded {
|
||||
return true, DeleteAntigravityReasoningReplayItemRequired(ctx, modelName, sessionKey)
|
||||
}
|
||||
client, homeMode, errClient := currentAntigravityReasoningReplayKVClient()
|
||||
if homeMode {
|
||||
if errClient != nil {
|
||||
return false, errClient
|
||||
}
|
||||
return client.KVCompareAndSwap(ctx, antigravityReasoningReplayKVKey(modelName, sessionKey), snapshot.raw, snapshot.found, newAntigravityReasoningReplayTombstone(), AntigravityReasoningReplayCacheTTL)
|
||||
}
|
||||
cacheCleanupOnce.Do(startCacheCleanup)
|
||||
antigravityReasoningReplayMu.Lock()
|
||||
defer antigravityReasoningReplayMu.Unlock()
|
||||
entry, found := antigravityReasoningReplayEntries[key]
|
||||
if found != snapshot.found || (found && entry.Revision != snapshot.revision) || (!found && snapshot.evictionEpoch != antigravityReasoningReplayEvictionEpoch) {
|
||||
return false, nil
|
||||
}
|
||||
antigravityReasoningReplayNextRevision++
|
||||
antigravityReasoningReplayEntries[key] = antigravityReasoningReplayEntry{Timestamp: time.Now(), Revision: antigravityReasoningReplayNextRevision, Branch: newAntigravityReasoningReplayGeneration(), Deleted: true}
|
||||
if len(antigravityReasoningReplayEntries) > AntigravityReasoningReplayCacheMaxEntries {
|
||||
evictOldestAntigravityReasoningReplayEntries(AntigravityReasoningReplayCacheEvictBatchSize)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// DeleteAntigravityReasoningReplayItem removes one replay item after upstream rejects
|
||||
// it or the caller otherwise knows it is stale.
|
||||
func DeleteAntigravityReasoningReplayItem(modelName, sessionKey string) {
|
||||
if errDelete := DeleteAntigravityReasoningReplayItemRequired(context.Background(), modelName, sessionKey); errDelete != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteAntigravityReasoningReplayItemRequired removes one replay item for request-time paths.
|
||||
func DeleteAntigravityReasoningReplayItemRequired(ctx context.Context, modelName, sessionKey string) error {
|
||||
key := antigravityReasoningReplayCacheKey(modelName, sessionKey)
|
||||
if key == "" {
|
||||
return nil
|
||||
}
|
||||
client, homeMode, errClient := currentAntigravityReasoningReplayKVClient()
|
||||
if homeMode {
|
||||
if errClient != nil {
|
||||
return errClient
|
||||
}
|
||||
_, errSet := client.KVSet(ctx, antigravityReasoningReplayKVKey(modelName, sessionKey), newAntigravityReasoningReplayTombstone(), homekv.KVSetOptions{EX: AntigravityReasoningReplayCacheTTL})
|
||||
return errSet
|
||||
}
|
||||
cacheCleanupOnce.Do(startCacheCleanup)
|
||||
antigravityReasoningReplayMu.Lock()
|
||||
antigravityReasoningReplayNextRevision++
|
||||
antigravityReasoningReplayEntries[key] = antigravityReasoningReplayEntry{Timestamp: time.Now(), Revision: antigravityReasoningReplayNextRevision, Branch: newAntigravityReasoningReplayGeneration(), Deleted: true}
|
||||
if len(antigravityReasoningReplayEntries) > AntigravityReasoningReplayCacheMaxEntries {
|
||||
evictOldestAntigravityReasoningReplayEntries(AntigravityReasoningReplayCacheEvictBatchSize)
|
||||
}
|
||||
antigravityReasoningReplayMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func newAntigravityReasoningReplayGeneration() string {
|
||||
var nonce [16]byte
|
||||
if _, errRead := rand.Read(nonce[:]); errRead != nil {
|
||||
return fmt.Sprintf("fallback-%d", time.Now().UnixNano())
|
||||
}
|
||||
return fmt.Sprintf("%x", nonce[:])
|
||||
}
|
||||
|
||||
func marshalAntigravityReasoningReplayHomeValue(items [][]byte, branch string) ([]byte, error) {
|
||||
if branch == "" {
|
||||
branch = newAntigravityReasoningReplayGeneration()
|
||||
}
|
||||
marker := []byte(`{"type":"","generation":"","branch":""}`)
|
||||
marker, _ = sjson.SetBytes(marker, "type", antigravityReasoningReplayGenerationItemType)
|
||||
marker, _ = sjson.SetBytes(marker, "generation", newAntigravityReasoningReplayGeneration())
|
||||
marker, _ = sjson.SetBytes(marker, "branch", branch)
|
||||
stored := make([][]byte, 0, len(items)+1)
|
||||
stored = append(stored, marker)
|
||||
stored = append(stored, items...)
|
||||
return json.Marshal(stored)
|
||||
}
|
||||
|
||||
func decodeAntigravityReasoningReplayHomeValue(raw []byte) (items [][]byte, deleted bool, generation, branch string, ok bool) {
|
||||
if errUnmarshal := json.Unmarshal(raw, &items); errUnmarshal != nil {
|
||||
return nil, false, "", "", false
|
||||
}
|
||||
if len(items) == 0 || strings.TrimSpace(gjson.GetBytes(items[0], "type").String()) != antigravityReasoningReplayGenerationItemType {
|
||||
return items, false, "", "", true
|
||||
}
|
||||
marker := gjson.ParseBytes(items[0])
|
||||
deleted = marker.Get("deleted").Bool()
|
||||
generation = strings.TrimSpace(marker.Get("generation").String())
|
||||
branch = strings.TrimSpace(marker.Get("branch").String())
|
||||
return items[1:], deleted, generation, branch, true
|
||||
}
|
||||
|
||||
func antigravityReasoningReplayItemsPrefix(prefix, items [][]byte) bool {
|
||||
if len(prefix) > len(items) {
|
||||
return false
|
||||
}
|
||||
for index := range prefix {
|
||||
if !bytes.Equal(prefix[index], items[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func newAntigravityReasoningReplayTombstone() []byte {
|
||||
marker := []byte(`{"type":"","generation":"","branch":"","deleted":true}`)
|
||||
marker, _ = sjson.SetBytes(marker, "type", antigravityReasoningReplayGenerationItemType)
|
||||
marker, _ = sjson.SetBytes(marker, "generation", newAntigravityReasoningReplayGeneration())
|
||||
marker, _ = sjson.SetBytes(marker, "branch", newAntigravityReasoningReplayGeneration())
|
||||
raw, _ := json.Marshal([][]byte{marker})
|
||||
return raw
|
||||
}
|
||||
|
||||
// ClearAntigravityReasoningReplayCache clears all Antigravity reasoning replay state.
|
||||
func ClearAntigravityReasoningReplayCache() {
|
||||
antigravityReasoningReplayMu.Lock()
|
||||
antigravityReasoningReplayEntries = make(map[string]antigravityReasoningReplayEntry)
|
||||
antigravityReasoningReplayEvictionEpoch++
|
||||
antigravityReasoningReplayMu.Unlock()
|
||||
}
|
||||
|
||||
func antigravityReasoningReplayCacheKey(modelName, sessionKey string) string {
|
||||
modelName = strings.TrimSpace(modelName)
|
||||
sessionKey = strings.TrimSpace(sessionKey)
|
||||
if modelName == "" || sessionKey == "" {
|
||||
return ""
|
||||
}
|
||||
// The session key is the continuity boundary. Keep this independent from
|
||||
// the selected upstream Codex credential so auth failover can preserve replay.
|
||||
return strings.Join([]string{"antigravity-reasoning-replay", modelName, sessionKey}, "\x00")
|
||||
}
|
||||
|
||||
func antigravityReasoningReplayKVKey(modelName, sessionKey string) string {
|
||||
return "cpa:antigravity:reasoning-replay:" + homekv.HashKeyPart(strings.TrimSpace(modelName)) + ":" + homekv.HashKeyPart(strings.TrimSpace(sessionKey))
|
||||
}
|
||||
|
||||
func normalizeAntigravityReasoningReplayItems(items [][]byte) ([][]byte, bool) {
|
||||
if len(items) > AntigravityReasoningReplayCacheMaxItemsPerEntry {
|
||||
return nil, false
|
||||
}
|
||||
normalized := make([][]byte, 0, len(items))
|
||||
totalBytes := 0
|
||||
for _, item := range items {
|
||||
normalizedItem, ok := normalizeAntigravityReasoningReplayItem(item)
|
||||
if ok {
|
||||
totalBytes += len(normalizedItem)
|
||||
if totalBytes > AntigravityReasoningReplayCacheMaxBytesPerEntry {
|
||||
return nil, false
|
||||
}
|
||||
normalized = append(normalized, normalizedItem)
|
||||
}
|
||||
}
|
||||
return normalized, len(normalized) > 0
|
||||
}
|
||||
|
||||
func normalizeAntigravityReasoningReplayItem(item []byte) ([]byte, bool) {
|
||||
itemResult := gjson.ParseBytes(item)
|
||||
switch strings.TrimSpace(itemResult.Get("type").String()) {
|
||||
case "thought_signature":
|
||||
return normalizeAntigravityThoughtSignatureReplayItem(itemResult)
|
||||
case "function_call_part":
|
||||
return normalizeAntigravityFunctionCallPartReplayItem(itemResult)
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeAntigravityThoughtSignatureReplayItem(itemResult gjson.Result) ([]byte, bool) {
|
||||
sig := strings.TrimSpace(itemResult.Get("thoughtSignature").String())
|
||||
if sig == "" {
|
||||
sig = strings.TrimSpace(itemResult.Get("thought_signature").String())
|
||||
}
|
||||
if sig == "" || sig == "skip_thought_signature_validator" || len(sig) < minAntigravityThoughtSignatureReplayLen {
|
||||
return nil, false
|
||||
}
|
||||
normalized := []byte(`{"type":"thought_signature"}`)
|
||||
normalized, _ = sjson.SetBytes(normalized, "thoughtSignature", sig)
|
||||
if contentIndex := itemResult.Get("contentIndex"); contentIndex.Type == gjson.Number {
|
||||
normalized, _ = sjson.SetBytes(normalized, "contentIndex", contentIndex.Int())
|
||||
}
|
||||
if partIndex := itemResult.Get("partIndex"); partIndex.Type == gjson.Number {
|
||||
normalized, _ = sjson.SetBytes(normalized, "partIndex", partIndex.Int())
|
||||
}
|
||||
if targetKind := strings.TrimSpace(itemResult.Get("targetKind").String()); targetKind == "text" || targetKind == "thought" {
|
||||
normalized, _ = sjson.SetBytes(normalized, "targetKind", targetKind)
|
||||
}
|
||||
if targetHash := strings.TrimSpace(itemResult.Get("targetHash").String()); targetHash != "" {
|
||||
normalized, _ = sjson.SetBytes(normalized, "targetHash", targetHash)
|
||||
}
|
||||
if targetOccurrence := itemResult.Get("targetOccurrence"); targetOccurrence.Type == gjson.Number && targetOccurrence.Int() >= 0 {
|
||||
normalized, _ = sjson.SetBytes(normalized, "targetOccurrence", targetOccurrence.Int())
|
||||
}
|
||||
if contextHash := strings.TrimSpace(itemResult.Get("contextHash").String()); contextHash != "" {
|
||||
normalized, _ = sjson.SetBytes(normalized, "contextHash", contextHash)
|
||||
}
|
||||
return normalized, true
|
||||
}
|
||||
|
||||
func normalizeAntigravityFunctionCallPartReplayItem(itemResult gjson.Result) ([]byte, bool) {
|
||||
callID := strings.TrimSpace(itemResult.Get("call_id").String())
|
||||
if callID == "" {
|
||||
callID = strings.TrimSpace(itemResult.Get("id").String())
|
||||
}
|
||||
name := strings.TrimSpace(itemResult.Get("name").String())
|
||||
args := itemResult.Get("args")
|
||||
if name == "" || !args.Exists() {
|
||||
fc := itemResult.Get("functionCall")
|
||||
if fc.Exists() {
|
||||
if callID == "" {
|
||||
callID = strings.TrimSpace(fc.Get("id").String())
|
||||
}
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(fc.Get("name").String())
|
||||
}
|
||||
if !args.Exists() {
|
||||
args = fc.Get("args")
|
||||
}
|
||||
}
|
||||
}
|
||||
if name == "" || !args.Exists() {
|
||||
return nil, false
|
||||
}
|
||||
normalized := []byte(`{"type":"function_call_part"}`)
|
||||
if callID != "" {
|
||||
normalized, _ = sjson.SetBytes(normalized, "call_id", callID)
|
||||
}
|
||||
normalized, _ = sjson.SetBytes(normalized, "name", name)
|
||||
if args.Type == gjson.String {
|
||||
normalized, _ = sjson.SetBytes(normalized, "args", args.String())
|
||||
} else {
|
||||
normalized, _ = sjson.SetRawBytes(normalized, "args", []byte(args.Raw))
|
||||
}
|
||||
sig := strings.TrimSpace(itemResult.Get("thoughtSignature").String())
|
||||
if sig != "" && sig != "skip_thought_signature_validator" {
|
||||
normalized, _ = sjson.SetBytes(normalized, "thoughtSignature", sig)
|
||||
}
|
||||
if contentIndex := itemResult.Get("contentIndex"); contentIndex.Type == gjson.Number {
|
||||
normalized, _ = sjson.SetBytes(normalized, "contentIndex", contentIndex.Int())
|
||||
}
|
||||
if partIndex := itemResult.Get("partIndex"); partIndex.Type == gjson.Number {
|
||||
normalized, _ = sjson.SetBytes(normalized, "partIndex", partIndex.Int())
|
||||
}
|
||||
if targetOccurrence := itemResult.Get("targetOccurrence"); targetOccurrence.Type == gjson.Number && targetOccurrence.Int() >= 0 {
|
||||
normalized, _ = sjson.SetBytes(normalized, "targetOccurrence", targetOccurrence.Int())
|
||||
}
|
||||
if contextHash := strings.TrimSpace(itemResult.Get("contextHash").String()); contextHash != "" {
|
||||
normalized, _ = sjson.SetBytes(normalized, "contextHash", contextHash)
|
||||
}
|
||||
return normalized, true
|
||||
}
|
||||
|
||||
func cloneAntigravityReasoningReplayItems(items [][]byte) [][]byte {
|
||||
cloned := make([][]byte, 0, len(items))
|
||||
for _, item := range items {
|
||||
cloned = append(cloned, append([]byte(nil), item...))
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func evictOldestAntigravityReasoningReplayEntries(count int) {
|
||||
if count <= 0 || len(antigravityReasoningReplayEntries) == 0 {
|
||||
return
|
||||
}
|
||||
type candidate struct {
|
||||
key string
|
||||
timestamp time.Time
|
||||
}
|
||||
candidates := make([]candidate, 0, len(antigravityReasoningReplayEntries))
|
||||
for key, entry := range antigravityReasoningReplayEntries {
|
||||
candidates = append(candidates, candidate{key: key, timestamp: entry.Timestamp})
|
||||
}
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
return candidates[i].timestamp.Before(candidates[j].timestamp)
|
||||
})
|
||||
if count > len(candidates) {
|
||||
count = len(candidates)
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
antigravityReasoningReplayEvictionEpoch++
|
||||
delete(antigravityReasoningReplayEntries, candidates[i].key)
|
||||
}
|
||||
}
|
||||
|
||||
func purgeExpiredAntigravityReasoningReplayCache(now time.Time) {
|
||||
antigravityReasoningReplayMu.Lock()
|
||||
for key, entry := range antigravityReasoningReplayEntries {
|
||||
if now.Sub(entry.Timestamp) > AntigravityReasoningReplayCacheTTL {
|
||||
antigravityReasoningReplayEvictionEpoch++
|
||||
delete(antigravityReasoningReplayEntries, key)
|
||||
}
|
||||
}
|
||||
antigravityReasoningReplayMu.Unlock()
|
||||
}
|
||||
Loading…
Reference in a new issue