forked from NoeFabris/opencode-antigravity-auth
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.ts
More file actions
237 lines (202 loc) · 6.94 KB
/
Copy pathcache.ts
File metadata and controls
237 lines (202 loc) · 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import { accessTokenExpired } from "./auth";
import type { OAuthAuthDetails } from "./types";
import { createHash } from "node:crypto";
const authCache = new Map<string, OAuthAuthDetails>();
/**
* Produces a stable cache key from a refresh token string.
*/
function normalizeRefreshKey(refresh?: string): string | undefined {
const key = refresh?.trim();
return key ? key : undefined;
}
/**
* Returns a cached auth snapshot when available, favoring unexpired tokens.
*/
export function resolveCachedAuth(auth: OAuthAuthDetails): OAuthAuthDetails {
const key = normalizeRefreshKey(auth.refresh);
if (!key) {
return auth;
}
const cached = authCache.get(key);
if (!cached) {
authCache.set(key, auth);
return auth;
}
if (!accessTokenExpired(auth)) {
authCache.set(key, auth);
return auth;
}
if (!accessTokenExpired(cached)) {
return cached;
}
authCache.set(key, auth);
return auth;
}
/**
* Stores the latest auth snapshot keyed by refresh token.
*/
export function storeCachedAuth(auth: OAuthAuthDetails): void {
const key = normalizeRefreshKey(auth.refresh);
if (!key) {
return;
}
authCache.set(key, auth);
}
/**
* Clears cached auth globally or for a specific refresh token.
*/
export function clearCachedAuth(refresh?: string): void {
if (!refresh) {
authCache.clear();
return;
}
const key = normalizeRefreshKey(refresh);
if (key) {
authCache.delete(key);
}
}
// ============================================================================
// Thinking Signature Cache (for Claude multi-turn conversations)
// ============================================================================
import { SignatureCache, createSignatureCache } from "./cache/signature-cache";
import type { SignatureCacheConfig } from "./config";
interface SignatureEntry {
signature: string;
timestamp: number;
}
// Map: sessionId -> Map<textHash, SignatureEntry>
const signatureCache = new Map<string, Map<string, SignatureEntry>>();
// Cache entries expire after 1 hour
const SIGNATURE_CACHE_TTL_MS = 60 * 60 * 1000;
// Maximum entries per session to prevent memory bloat
const MAX_ENTRIES_PER_SESSION = 100;
// 16 hex chars = 64-bit key space; keeps memory bounded while making collisions extremely unlikely.
const SIGNATURE_TEXT_HASH_HEX_LEN = 16;
// Disk cache instance (initialized via initDiskSignatureCache)
let diskCache: SignatureCache | null = null;
/**
* Initialize the disk-based signature cache.
* Call this from plugin initialization when keep_thinking is enabled.
*/
export function initDiskSignatureCache(config: SignatureCacheConfig | undefined): SignatureCache | null {
diskCache = createSignatureCache(config);
return diskCache;
}
/**
* Get the disk cache instance (for testing/debugging).
*/
export function getDiskSignatureCache(): SignatureCache | null {
return diskCache;
}
/**
* Hashes text content into a stable, Unicode-safe key.
*
* Uses SHA-256 over UTF-8 bytes and truncates to keep memory usage bounded.
*/
function hashText(text: string): string {
return createHash("sha256").update(text, "utf8").digest("hex").slice(0, SIGNATURE_TEXT_HASH_HEX_LEN);
}
/**
* Create a disk cache key from sessionId and textHash.
*/
function makeDiskKey(sessionId: string, textHash: string): string {
return `${sessionId}:${textHash}`;
}
/**
* Caches a thinking signature for a given session and text.
* Used for Claude models that require signed thinking blocks in multi-turn conversations.
* Also writes to disk cache if enabled.
*/
export function cacheSignature(sessionId: string, text: string, signature: string): void {
if (!sessionId || !text || !signature) return;
const textHash = hashText(text);
// Write to memory cache
let sessionMemCache = signatureCache.get(sessionId);
if (!sessionMemCache) {
sessionMemCache = new Map();
signatureCache.set(sessionId, sessionMemCache);
}
// Evict old entries if we're at capacity
if (sessionMemCache.size >= MAX_ENTRIES_PER_SESSION) {
const now = Date.now();
for (const [key, entry] of sessionMemCache.entries()) {
if (now - entry.timestamp > SIGNATURE_CACHE_TTL_MS) {
sessionMemCache.delete(key);
}
}
// If still at capacity, remove oldest entries
if (sessionMemCache.size >= MAX_ENTRIES_PER_SESSION) {
const entries = Array.from(sessionMemCache.entries())
.sort((a, b) => a[1].timestamp - b[1].timestamp);
const toRemove = entries.slice(0, Math.floor(MAX_ENTRIES_PER_SESSION / 4));
for (const [key] of toRemove) {
sessionMemCache.delete(key);
}
}
}
sessionMemCache.set(textHash, { signature, timestamp: Date.now() });
// Write to disk cache if enabled
if (diskCache) {
const diskKey = makeDiskKey(sessionId, textHash);
diskCache.store(diskKey, signature);
}
}
/**
* Retrieves a cached signature for a given session and text.
* Checks memory first, then falls back to disk cache.
* Returns undefined if not found or expired.
*/
export function getCachedSignature(sessionId: string, text: string): string | undefined {
if (!sessionId || !text) return undefined;
const textHash = hashText(text);
// Check memory cache first
const sessionMemCache = signatureCache.get(sessionId);
if (sessionMemCache) {
const entry = sessionMemCache.get(textHash);
if (entry) {
// Check if expired
if (Date.now() - entry.timestamp > SIGNATURE_CACHE_TTL_MS) {
sessionMemCache.delete(textHash);
} else {
return entry.signature;
}
}
}
// Fall back to disk cache
if (diskCache) {
const diskKey = makeDiskKey(sessionId, textHash);
const diskValue = diskCache.retrieve(diskKey);
if (diskValue) {
// Promote to memory cache for faster subsequent access
let memCache = signatureCache.get(sessionId);
if (!memCache) {
memCache = new Map();
signatureCache.set(sessionId, memCache);
}
memCache.set(textHash, { signature: diskValue, timestamp: Date.now() });
return diskValue;
}
}
return undefined;
}
/**
* Clears signature cache for a specific session or all sessions.
* Also clears from disk cache if enabled.
*/
export function clearSignatureCache(sessionId?: string): void {
if (sessionId) {
signatureCache.delete(sessionId);
// Note: We don't clear individual sessions from disk cache to avoid
// expensive iteration. Disk cache entries will expire naturally.
} else {
signatureCache.clear();
// For full clear, we could clear disk cache, but leaving it for now
// since entries have TTL and will expire naturally.
}
}
// ============================================================================
// Disk-Persistent Signature Cache (re-export from cache/ folder)
// ============================================================================
// Re-export SignatureCache class and factory for direct use
export { SignatureCache, createSignatureCache } from "./cache/signature-cache";
export type { SignatureCacheConfig } from "./config";