opencode-antigravity-auth 1.2.3 → 1.2.5-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -13
- package/dist/src/constants.d.ts +12 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +13 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/plugin/cache/index.d.ts +5 -0
- package/dist/src/plugin/cache/index.d.ts.map +1 -0
- package/dist/src/plugin/cache/index.js +5 -0
- package/dist/src/plugin/cache/index.js.map +1 -0
- package/dist/src/plugin/cache/signature-cache.d.ts +87 -0
- package/dist/src/plugin/cache/signature-cache.d.ts.map +1 -0
- package/dist/src/plugin/cache/signature-cache.js +325 -0
- package/dist/src/plugin/cache/signature-cache.js.map +1 -0
- package/dist/src/plugin/cache.d.ts +16 -0
- package/dist/src/plugin/cache.d.ts.map +1 -1
- package/dist/src/plugin/cache.js +85 -23
- package/dist/src/plugin/cache.js.map +1 -1
- package/dist/src/plugin/config/index.d.ts +16 -0
- package/dist/src/plugin/config/index.d.ts.map +1 -0
- package/dist/src/plugin/config/index.js +16 -0
- package/dist/src/plugin/config/index.js.map +1 -0
- package/dist/src/plugin/config/loader.d.ts +35 -0
- package/dist/src/plugin/config/loader.d.ts.map +1 -0
- package/dist/src/plugin/config/loader.js +173 -0
- package/dist/src/plugin/config/loader.js.map +1 -0
- package/dist/src/plugin/config/schema.d.ts +162 -0
- package/dist/src/plugin/config/schema.d.ts.map +1 -0
- package/dist/src/plugin/config/schema.js +128 -0
- package/dist/src/plugin/config/schema.js.map +1 -0
- package/dist/src/plugin/debug.d.ts +8 -2
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +121 -70
- package/dist/src/plugin/debug.js.map +1 -1
- package/dist/src/plugin/recovery/constants.d.ts +22 -0
- package/dist/src/plugin/recovery/constants.d.ts.map +1 -0
- package/dist/src/plugin/recovery/constants.js +43 -0
- package/dist/src/plugin/recovery/constants.js.map +1 -0
- package/dist/src/plugin/recovery/index.d.ts +12 -0
- package/dist/src/plugin/recovery/index.d.ts.map +1 -0
- package/dist/src/plugin/recovery/index.js +12 -0
- package/dist/src/plugin/recovery/index.js.map +1 -0
- package/dist/src/plugin/recovery/storage.d.ts +24 -0
- package/dist/src/plugin/recovery/storage.d.ts.map +1 -0
- package/dist/src/plugin/recovery/storage.js +354 -0
- package/dist/src/plugin/recovery/storage.js.map +1 -0
- package/dist/src/plugin/recovery/types.d.ts +116 -0
- package/dist/src/plugin/recovery/types.d.ts.map +1 -0
- package/dist/src/plugin/recovery/types.js +6 -0
- package/dist/src/plugin/recovery/types.js.map +1 -0
- package/dist/src/plugin/recovery.d.ts +53 -0
- package/dist/src/plugin/recovery.d.ts.map +1 -0
- package/dist/src/plugin/recovery.js +347 -0
- package/dist/src/plugin/recovery.js.map +1 -0
- package/dist/src/plugin/request-helpers.d.ts +10 -3
- package/dist/src/plugin/request-helpers.d.ts.map +1 -1
- package/dist/src/plugin/request-helpers.js +526 -17
- package/dist/src/plugin/request-helpers.js.map +1 -1
- package/dist/src/plugin/request.d.ts +1 -0
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +40 -69
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/thinking-recovery.d.ts +64 -0
- package/dist/src/plugin/thinking-recovery.d.ts.map +1 -0
- package/dist/src/plugin/thinking-recovery.js +245 -0
- package/dist/src/plugin/thinking-recovery.js.map +1 -0
- package/dist/src/plugin.d.ts.map +1 -1
- package/dist/src/plugin.js +89 -10
- package/dist/src/plugin.js.map +1 -1
- package/package.json +59 -57
package/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
# Antigravity OAuth Plugin for Opencode
|
|
1
|
+
# Antigravity + Gemini CLI OAuth Plugin for Opencode
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/opencode-antigravity-auth)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-antigravity-auth)
|
|
4
5
|
|
|
5
6
|
Enable Opencode to authenticate against **Antigravity** (Google's IDE) via OAuth so you can use Antigravity rate limits and access models like `gemini-3-pro-high` and `claude-opus-4-5-thinking` with your Google credentials.
|
|
6
7
|
|
|
7
8
|
## What you get
|
|
8
9
|
|
|
9
10
|
- **Google OAuth sign-in** (multi-account via `opencode auth login`) with automatic token refresh
|
|
10
|
-
- **Multi-account load balancing** Automatically cycle through multiple Google accounts to maximize
|
|
11
|
-
- **
|
|
11
|
+
- **Multi-account load balancing** Automatically cycle through multiple Google accounts to maximize throughput
|
|
12
|
+
- **Two quota sources for Gemini** Automatic fallback between **Antigravity quota** and **Gemini CLI quota** (same account) before switching accounts
|
|
12
13
|
- **Real-time SSE streaming** including thinking blocks and incremental output
|
|
13
14
|
- **Advanced Claude support** Interleaved thinking, stable multi-turn signatures, and validated tool calling
|
|
14
15
|
- **Automatic endpoint fallback** between Antigravity API endpoints (daily → autopush → prod)
|
|
@@ -42,7 +43,7 @@ If the agent only installs the plugin, ask it to also add models under `provider
|
|
|
42
43
|
|
|
43
44
|
```json
|
|
44
45
|
{
|
|
45
|
-
"plugin": ["opencode-antigravity-auth@1.2.
|
|
46
|
+
"plugin": ["opencode-antigravity-auth@1.2.4"]
|
|
46
47
|
}
|
|
47
48
|
```
|
|
48
49
|
|
|
@@ -60,7 +61,7 @@ If the agent only installs the plugin, ask it to also add models under `provider
|
|
|
60
61
|
|
|
61
62
|
```json
|
|
62
63
|
{
|
|
63
|
-
"plugin": ["opencode-antigravity-auth@1.2.
|
|
64
|
+
"plugin": ["opencode-antigravity-auth@1.2.4"],
|
|
64
65
|
"provider": {
|
|
65
66
|
"google": {
|
|
66
67
|
"models": {
|
|
@@ -110,14 +111,14 @@ mkdir -p ~/.config/opencode
|
|
|
110
111
|
|
|
111
112
|
if [ -f ~/.config/opencode/opencode.json ]; then
|
|
112
113
|
if command -v jq &> /dev/null; then
|
|
113
|
-
jq '.plugin = ((.plugin // []) + ["opencode-antigravity-auth@1.2.
|
|
114
|
+
jq '.plugin = ((.plugin // []) + ["opencode-antigravity-auth@1.2.4"] | unique)' \
|
|
114
115
|
~/.config/opencode/opencode.json > /tmp/oc.json && \
|
|
115
116
|
mv /tmp/oc.json ~/.config/opencode/opencode.json
|
|
116
117
|
else
|
|
117
|
-
echo "Add \"opencode-antigravity-auth@1.2.
|
|
118
|
+
echo "Add \"opencode-antigravity-auth@1.2.4\" to the plugin array manually"
|
|
118
119
|
fi
|
|
119
120
|
else
|
|
120
|
-
echo '{"plugin":["opencode-antigravity-auth@1.2.
|
|
121
|
+
echo '{"plugin":["opencode-antigravity-auth@1.2.4"]}' > ~/.config/opencode/opencode.json
|
|
121
122
|
fi
|
|
122
123
|
```
|
|
123
124
|
|
|
@@ -265,14 +266,20 @@ The plugin supports multiple Google accounts to maximize rate limits and provide
|
|
|
265
266
|
|
|
266
267
|
### Dual quota pools (Gemini only)
|
|
267
268
|
|
|
268
|
-
For Gemini models, the plugin can access two independent quota pools using the same Google account:
|
|
269
|
+
For Gemini models, the plugin can access **two independent quota pools** using the same Google account:
|
|
269
270
|
|
|
270
271
|
| Quota Pool | Headers Used | When Used |
|
|
271
272
|
|------------|--------------|-----------|
|
|
272
|
-
| Antigravity | Antigravity headers | Primary (tried first) |
|
|
273
|
-
| Gemini CLI | Gemini CLI headers | Fallback when Antigravity is rate-limited |
|
|
273
|
+
| **Antigravity** | Antigravity headers | Primary (tried first) |
|
|
274
|
+
| **Gemini CLI** | Gemini CLI headers | Fallback when Antigravity is rate-limited |
|
|
274
275
|
|
|
275
|
-
This effectively **doubles your Gemini quota** per account.
|
|
276
|
+
This effectively **doubles your Gemini quota** per account.
|
|
277
|
+
|
|
278
|
+
**How it works:**
|
|
279
|
+
1. Plugin tries Antigravity quota first
|
|
280
|
+
2. If rate-limited (429), it automatically retries using Gemini CLI headers
|
|
281
|
+
3. Only if **both** pools are exhausted does it switch to the next account
|
|
282
|
+
4. This happens seamlessly — conversation context is preserved when switching between quota pools.
|
|
276
283
|
|
|
277
284
|
> **Note:** Claude models only work with Antigravity headers, so this dual-pool fallback only applies to Gemini models.
|
|
278
285
|
|
|
@@ -316,11 +323,93 @@ The `/connect` command in the TUI adds accounts non-destructively — it will ne
|
|
|
316
323
|
- If Google revokes a refresh token (`invalid_grant`), that account is automatically removed from the pool
|
|
317
324
|
- Rerun `opencode auth login` to re-add the account
|
|
318
325
|
|
|
326
|
+
## Configuration
|
|
327
|
+
|
|
328
|
+
### Config file
|
|
329
|
+
|
|
330
|
+
Create `~/.config/opencode/antigravity.json` for advanced settings:
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"session_recovery": true,
|
|
335
|
+
"auto_resume": true,
|
|
336
|
+
"resume_text": "continue"
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
| Option | Default | Description |
|
|
341
|
+
|--------|---------|-------------|
|
|
342
|
+
| `session_recovery` | `true` | Enable automatic recovery from interrupted tool executions |
|
|
343
|
+
| `auto_resume` | `true` | Automatically send resume prompt after recovery |
|
|
344
|
+
| `resume_text` | `"continue"` | Text to send when auto-resuming |
|
|
345
|
+
|
|
346
|
+
### Environment variables
|
|
347
|
+
|
|
348
|
+
| Variable | Values | Description |
|
|
349
|
+
|----------|--------|-------------|
|
|
350
|
+
| `OPENCODE_ANTIGRAVITY_DEBUG` | `1`, `2` | Debug logging level (2 = verbose) |
|
|
351
|
+
| `OPENCODE_ANTIGRAVITY_QUIET` | `1` | Suppress toast notifications |
|
|
352
|
+
| `OPENCODE_ANTIGRAVITY_KEEP_THINKING` | `1` | Preserve thinking blocks (experimental, may cause errors) |
|
|
353
|
+
| `OPENCODE_ANTIGRAVITY_LOG_DIR` | path | Custom log directory |
|
|
354
|
+
|
|
355
|
+
## Known plugin interactions
|
|
356
|
+
|
|
357
|
+
### @tarquinen/opencode-dcp (Dynamic Context Pruning)
|
|
358
|
+
|
|
359
|
+
**Issue:** DCP creates synthetic assistant messages to summarize pruned tool outputs. These synthetic messages lack the thinking block that Claude's API requires for thinking-enabled models.
|
|
360
|
+
|
|
361
|
+
**Error you'll see:**
|
|
362
|
+
```
|
|
363
|
+
Expected 'thinking' or 'redacted_thinking', but found 'text'
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Solution:** Ensure DCP loads **before** this plugin. We inject `redacted_thinking` blocks into any assistant message that lacks one.
|
|
367
|
+
|
|
368
|
+
| Order | Result |
|
|
369
|
+
|-------|--------|
|
|
370
|
+
| DCP → antigravity | Works - we fix DCP's synthetic messages |
|
|
371
|
+
| antigravity → DCP | Broken - DCP creates messages after our fix runs |
|
|
372
|
+
|
|
373
|
+
**Correct:**
|
|
374
|
+
```json
|
|
375
|
+
{
|
|
376
|
+
"plugin": [
|
|
377
|
+
"@tarquinen/opencode-dcp@latest",
|
|
378
|
+
"opencode-antigravity-auth@latest"
|
|
379
|
+
]
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Incorrect:**
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"plugin": [
|
|
387
|
+
"opencode-antigravity-auth@latest",
|
|
388
|
+
"@tarquinen/opencode-dcp@latest"
|
|
389
|
+
]
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### oh-my-opencode (Subagent Orchestration)
|
|
394
|
+
|
|
395
|
+
**Issue:** When oh-my-opencode spawns multiple subagents in parallel, each subagent runs as a separate OpenCode process. Without coordination, multiple processes may select the same Antigravity account simultaneously, causing rate limit errors.
|
|
396
|
+
|
|
397
|
+
**Error you'll see:**
|
|
398
|
+
```
|
|
399
|
+
429 Too Many Requests
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Current workaround:**
|
|
403
|
+
- Increase your account pool (add more OAuth accounts via `opencode auth login`)
|
|
404
|
+
- Reduce parallel subagent count in your configuration
|
|
405
|
+
|
|
406
|
+
**Status:** A file-based reservation system to coordinate account selection across processes is planned but not yet implemented.
|
|
407
|
+
|
|
319
408
|
## Architecture & Flow
|
|
320
409
|
|
|
321
410
|
For contributors and advanced users, see the detailed documentation:
|
|
322
411
|
|
|
323
|
-
- **[
|
|
412
|
+
- **[Architecture Guide](docs/ARCHITECTURE.md)** - Full request/response flow, module structure, and troubleshooting
|
|
324
413
|
- **[Antigravity API Spec](docs/ANTIGRAVITY_API_SPEC.md)** - API reference and schema support matrix
|
|
325
414
|
|
|
326
415
|
## Streaming & thinking
|
package/dist/src/constants.d.ts
CHANGED
|
@@ -55,4 +55,16 @@ export type HeaderStyle = "antigravity" | "gemini-cli";
|
|
|
55
55
|
* Provider identifier shared between the plugin loader and credential store.
|
|
56
56
|
*/
|
|
57
57
|
export declare const ANTIGRAVITY_PROVIDER_ID = "google";
|
|
58
|
+
/**
|
|
59
|
+
* Whether to preserve thinking blocks for Claude models.
|
|
60
|
+
*
|
|
61
|
+
* This value is now controlled via config (see plugin/config/schema.ts).
|
|
62
|
+
* The default is false for reliability. Set to true via:
|
|
63
|
+
* - Config file: { "keep_thinking": true }
|
|
64
|
+
* - Env var: OPENCODE_ANTIGRAVITY_KEEP_THINKING=1
|
|
65
|
+
*
|
|
66
|
+
* @deprecated Use config.keep_thinking from loadConfig() instead.
|
|
67
|
+
* This export is kept for backward compatibility but reads from env.
|
|
68
|
+
*/
|
|
69
|
+
export declare const KEEP_THINKING_BLOCKS: boolean;
|
|
58
70
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,qBAAqB,8EAA8E,CAAC;AAEjH;;GAEG;AACH,eAAO,MAAM,yBAAyB,wCAAwC,CAAC;AAE/E;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAM/C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,wBAAwB,0CAA0C,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,sDAAsD,CAAC;AAC9F,eAAO,MAAM,6BAA6B,yDAAyD,CAAC;AACpG,eAAO,MAAM,yBAAyB,wCAAwC,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,8BAA8B,+JAIjC,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,0BAA0B,+JAI7B,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,oBAAoB,sDAA6B,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,8BAA8B,sBAAsB,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,YAAY,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,uBAAuB,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,qBAAqB,8EAA8E,CAAC;AAEjH;;GAEG;AACH,eAAO,MAAM,yBAAyB,wCAAwC,CAAC;AAE/E;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAM/C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,wBAAwB,0CAA0C,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,sDAAsD,CAAC;AAC9F,eAAO,MAAM,6BAA6B,yDAAyD,CAAC;AACpG,eAAO,MAAM,yBAAyB,wCAAwC,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,8BAA8B,+JAIjC,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,0BAA0B,+JAI7B,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,oBAAoB,sDAA6B,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,8BAA8B,sBAAsB,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,YAAY,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,uBAAuB,WAAW,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,SAE0B,CAAC"}
|
package/dist/src/constants.js
CHANGED
|
@@ -68,4 +68,17 @@ export const GEMINI_CLI_HEADERS = {
|
|
|
68
68
|
* Provider identifier shared between the plugin loader and credential store.
|
|
69
69
|
*/
|
|
70
70
|
export const ANTIGRAVITY_PROVIDER_ID = "google";
|
|
71
|
+
/**
|
|
72
|
+
* Whether to preserve thinking blocks for Claude models.
|
|
73
|
+
*
|
|
74
|
+
* This value is now controlled via config (see plugin/config/schema.ts).
|
|
75
|
+
* The default is false for reliability. Set to true via:
|
|
76
|
+
* - Config file: { "keep_thinking": true }
|
|
77
|
+
* - Env var: OPENCODE_ANTIGRAVITY_KEEP_THINKING=1
|
|
78
|
+
*
|
|
79
|
+
* @deprecated Use config.keep_thinking from loadConfig() instead.
|
|
80
|
+
* This export is kept for backward compatibility but reads from env.
|
|
81
|
+
*/
|
|
82
|
+
export const KEEP_THINKING_BLOCKS = process.env.OPENCODE_ANTIGRAVITY_KEEP_THINKING === "1" ||
|
|
83
|
+
process.env.OPENCODE_ANTIGRAVITY_KEEP_THINKING === "true";
|
|
71
84
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,2EAA2E,CAAC;AAEjH;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,qCAAqC,CAAC;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,gDAAgD;IAChD,gDAAgD;IAChD,kDAAkD;IAClD,uCAAuC;IACvC,uDAAuD;CACxD,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,uCAAuC,CAAC;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,mDAAmD,CAAC;AAC9F,MAAM,CAAC,MAAM,6BAA6B,GAAG,sDAAsD,CAAC;AACpG,MAAM,CAAC,MAAM,yBAAyB,GAAG,qCAAqC,CAAC;AAE/E;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,0BAA0B;IAC1B,6BAA6B;IAC7B,yBAAyB;CACjB,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,yBAAyB;IACzB,0BAA0B;IAC1B,6BAA6B;CACrB,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAE/D;;GAEG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,mBAAmB,CAAC;AAElE,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,YAAY,EAAE,kCAAkC;IAChD,mBAAmB,EAAE,8CAA8C;IACnE,iBAAiB,EAAE,uFAAuF;CAClG,CAAC;AAEX,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,YAAY,EAAE,iCAAiC;IAC/C,mBAAmB,EAAE,iBAAiB;IACtC,iBAAiB,EAAE,yEAAyE;CACpF,CAAC;AAIX;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,2EAA2E,CAAC;AAEjH;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,qCAAqC,CAAC;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,gDAAgD;IAChD,gDAAgD;IAChD,kDAAkD;IAClD,uCAAuC;IACvC,uDAAuD;CACxD,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,uCAAuC,CAAC;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,mDAAmD,CAAC;AAC9F,MAAM,CAAC,MAAM,6BAA6B,GAAG,sDAAsD,CAAC;AACpG,MAAM,CAAC,MAAM,yBAAyB,GAAG,qCAAqC,CAAC;AAE/E;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,0BAA0B;IAC1B,6BAA6B;IAC7B,yBAAyB;CACjB,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,yBAAyB;IACzB,0BAA0B;IAC1B,6BAA6B;CACrB,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAE/D;;GAEG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,mBAAmB,CAAC;AAElE,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,YAAY,EAAE,kCAAkC;IAChD,mBAAmB,EAAE,8CAA8C;IACnE,iBAAiB,EAAE,uFAAuF;CAClG,CAAC;AAEX,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,YAAY,EAAE,iCAAiC;IAC/C,mBAAmB,EAAE,iBAAiB;IACtC,iBAAiB,EAAE,yEAAyE;CACpF,CAAC;AAIX;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,QAAQ,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,OAAO,CAAC,GAAG,CAAC,kCAAkC,KAAK,GAAG;IACtD,OAAO,CAAC,GAAG,CAAC,kCAAkC,KAAK,MAAM,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugin/cache/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,cAAc,EACd,oBAAoB,GACrB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/plugin/cache/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,cAAc,EACd,oBAAoB,GACrB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature cache for persisting thinking block signatures to disk.
|
|
3
|
+
*
|
|
4
|
+
* Features (based on LLM-API-Key-Proxy's ProviderCache):
|
|
5
|
+
* - Dual-TTL system: short memory TTL, longer disk TTL
|
|
6
|
+
* - Background disk persistence with batched writes
|
|
7
|
+
* - Atomic writes with temp file + move pattern
|
|
8
|
+
* - Automatic cleanup of expired entries
|
|
9
|
+
*
|
|
10
|
+
* Cache key format: `${sessionId}:${modelId}`
|
|
11
|
+
*/
|
|
12
|
+
import type { SignatureCacheConfig } from "../config";
|
|
13
|
+
interface CacheStats {
|
|
14
|
+
memoryHits: number;
|
|
15
|
+
diskHits: number;
|
|
16
|
+
misses: number;
|
|
17
|
+
writes: number;
|
|
18
|
+
memoryEntries: number;
|
|
19
|
+
dirty: boolean;
|
|
20
|
+
diskEnabled: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare class SignatureCache {
|
|
23
|
+
private cache;
|
|
24
|
+
private memoryTtlMs;
|
|
25
|
+
private diskTtlMs;
|
|
26
|
+
private writeIntervalMs;
|
|
27
|
+
private cacheFilePath;
|
|
28
|
+
private enabled;
|
|
29
|
+
private dirty;
|
|
30
|
+
private writeTimer;
|
|
31
|
+
private cleanupTimer;
|
|
32
|
+
private stats;
|
|
33
|
+
constructor(config: SignatureCacheConfig);
|
|
34
|
+
/**
|
|
35
|
+
* Generate a cache key from sessionId and modelId.
|
|
36
|
+
*/
|
|
37
|
+
static makeKey(sessionId: string, modelId: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Store a signature in the cache.
|
|
40
|
+
*/
|
|
41
|
+
store(key: string, signature: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Retrieve a signature from the cache.
|
|
44
|
+
* Returns null if not found or expired.
|
|
45
|
+
*/
|
|
46
|
+
retrieve(key: string): string | null;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a key exists in the cache (without updating stats).
|
|
49
|
+
*/
|
|
50
|
+
has(key: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Get cache statistics.
|
|
53
|
+
*/
|
|
54
|
+
getStats(): CacheStats;
|
|
55
|
+
/**
|
|
56
|
+
* Manually trigger a disk save.
|
|
57
|
+
*/
|
|
58
|
+
flush(): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Graceful shutdown: stop timers and flush to disk.
|
|
61
|
+
*/
|
|
62
|
+
shutdown(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Load cache from disk file with TTL validation.
|
|
65
|
+
*/
|
|
66
|
+
private loadFromDisk;
|
|
67
|
+
/**
|
|
68
|
+
* Save cache to disk with atomic write pattern.
|
|
69
|
+
* Merges with existing disk entries that haven't expired.
|
|
70
|
+
*/
|
|
71
|
+
private saveToDisk;
|
|
72
|
+
/**
|
|
73
|
+
* Start background write and cleanup timers.
|
|
74
|
+
*/
|
|
75
|
+
private startBackgroundTasks;
|
|
76
|
+
/**
|
|
77
|
+
* Remove expired entries from memory.
|
|
78
|
+
*/
|
|
79
|
+
private cleanupExpired;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a signature cache with the given configuration.
|
|
83
|
+
* Returns null if caching is disabled.
|
|
84
|
+
*/
|
|
85
|
+
export declare function createSignatureCache(config: SignatureCacheConfig | undefined): SignatureCache | null;
|
|
86
|
+
export {};
|
|
87
|
+
//# sourceMappingURL=signature-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature-cache.d.ts","sourceRoot":"","sources":["../../../../src/plugin/cache/signature-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAyBtD,UAAU,UAAU;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;CACtB;AAuBD,qBAAa,cAAc;IAEzB,OAAO,CAAC,KAAK,CAAgE;IAG7E,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAU;IAGzB,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,YAAY,CAA+C;IAGnE,OAAO,CAAC,KAAK,CAKX;gBAEU,MAAM,EAAE,oBAAoB;IAiBxC;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAI1D;;OAEG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAU3C;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkBpC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAUzB;;OAEG;IACH,QAAQ,IAAI,UAAU;IAStB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAK/B;;OAEG;IACH,QAAQ,IAAI,IAAI;IAmBhB;;OAEG;IACH,OAAO,CAAC,YAAY;IA0CpB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAqFlB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAc5B;;OAEG;IACH,OAAO,CAAC,cAAc;CAgBvB;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,cAAc,GAAG,IAAI,CAMpG"}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature cache for persisting thinking block signatures to disk.
|
|
3
|
+
*
|
|
4
|
+
* Features (based on LLM-API-Key-Proxy's ProviderCache):
|
|
5
|
+
* - Dual-TTL system: short memory TTL, longer disk TTL
|
|
6
|
+
* - Background disk persistence with batched writes
|
|
7
|
+
* - Atomic writes with temp file + move pattern
|
|
8
|
+
* - Automatic cleanup of expired entries
|
|
9
|
+
*
|
|
10
|
+
* Cache key format: `${sessionId}:${modelId}`
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Path Utilities
|
|
18
|
+
// =============================================================================
|
|
19
|
+
function getConfigDir() {
|
|
20
|
+
const platform = process.platform;
|
|
21
|
+
if (platform === "win32") {
|
|
22
|
+
return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "opencode");
|
|
23
|
+
}
|
|
24
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
25
|
+
return join(xdgConfig, "opencode");
|
|
26
|
+
}
|
|
27
|
+
function getCacheFilePath() {
|
|
28
|
+
return join(getConfigDir(), "antigravity-signature-cache.json");
|
|
29
|
+
}
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Signature Cache Class
|
|
32
|
+
// =============================================================================
|
|
33
|
+
export class SignatureCache {
|
|
34
|
+
// In-memory cache: key -> (value, timestamp)
|
|
35
|
+
cache = new Map();
|
|
36
|
+
// Configuration
|
|
37
|
+
memoryTtlMs;
|
|
38
|
+
diskTtlMs;
|
|
39
|
+
writeIntervalMs;
|
|
40
|
+
cacheFilePath;
|
|
41
|
+
enabled;
|
|
42
|
+
// State
|
|
43
|
+
dirty = false;
|
|
44
|
+
writeTimer = null;
|
|
45
|
+
cleanupTimer = null;
|
|
46
|
+
// Statistics
|
|
47
|
+
stats = {
|
|
48
|
+
memoryHits: 0,
|
|
49
|
+
diskHits: 0,
|
|
50
|
+
misses: 0,
|
|
51
|
+
writes: 0,
|
|
52
|
+
};
|
|
53
|
+
constructor(config) {
|
|
54
|
+
this.enabled = config.enabled;
|
|
55
|
+
this.memoryTtlMs = config.memory_ttl_seconds * 1000;
|
|
56
|
+
this.diskTtlMs = config.disk_ttl_seconds * 1000;
|
|
57
|
+
this.writeIntervalMs = config.write_interval_seconds * 1000;
|
|
58
|
+
this.cacheFilePath = getCacheFilePath();
|
|
59
|
+
if (this.enabled) {
|
|
60
|
+
this.loadFromDisk();
|
|
61
|
+
this.startBackgroundTasks();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// ===========================================================================
|
|
65
|
+
// Public API
|
|
66
|
+
// ===========================================================================
|
|
67
|
+
/**
|
|
68
|
+
* Generate a cache key from sessionId and modelId.
|
|
69
|
+
*/
|
|
70
|
+
static makeKey(sessionId, modelId) {
|
|
71
|
+
return `${sessionId}:${modelId}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Store a signature in the cache.
|
|
75
|
+
*/
|
|
76
|
+
store(key, signature) {
|
|
77
|
+
if (!this.enabled)
|
|
78
|
+
return;
|
|
79
|
+
this.cache.set(key, {
|
|
80
|
+
value: signature,
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
});
|
|
83
|
+
this.dirty = true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Retrieve a signature from the cache.
|
|
87
|
+
* Returns null if not found or expired.
|
|
88
|
+
*/
|
|
89
|
+
retrieve(key) {
|
|
90
|
+
if (!this.enabled)
|
|
91
|
+
return null;
|
|
92
|
+
const entry = this.cache.get(key);
|
|
93
|
+
if (entry) {
|
|
94
|
+
const age = Date.now() - entry.timestamp;
|
|
95
|
+
if (age <= this.memoryTtlMs) {
|
|
96
|
+
this.stats.memoryHits++;
|
|
97
|
+
return entry.value;
|
|
98
|
+
}
|
|
99
|
+
// Expired from memory, remove it
|
|
100
|
+
this.cache.delete(key);
|
|
101
|
+
}
|
|
102
|
+
this.stats.misses++;
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if a key exists in the cache (without updating stats).
|
|
107
|
+
*/
|
|
108
|
+
has(key) {
|
|
109
|
+
if (!this.enabled)
|
|
110
|
+
return false;
|
|
111
|
+
const entry = this.cache.get(key);
|
|
112
|
+
if (!entry)
|
|
113
|
+
return false;
|
|
114
|
+
const age = Date.now() - entry.timestamp;
|
|
115
|
+
return age <= this.memoryTtlMs;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get cache statistics.
|
|
119
|
+
*/
|
|
120
|
+
getStats() {
|
|
121
|
+
return {
|
|
122
|
+
...this.stats,
|
|
123
|
+
memoryEntries: this.cache.size,
|
|
124
|
+
dirty: this.dirty,
|
|
125
|
+
diskEnabled: this.enabled,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Manually trigger a disk save.
|
|
130
|
+
*/
|
|
131
|
+
async flush() {
|
|
132
|
+
if (!this.enabled)
|
|
133
|
+
return true;
|
|
134
|
+
return this.saveToDisk();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Graceful shutdown: stop timers and flush to disk.
|
|
138
|
+
*/
|
|
139
|
+
shutdown() {
|
|
140
|
+
if (this.writeTimer) {
|
|
141
|
+
clearInterval(this.writeTimer);
|
|
142
|
+
this.writeTimer = null;
|
|
143
|
+
}
|
|
144
|
+
if (this.cleanupTimer) {
|
|
145
|
+
clearInterval(this.cleanupTimer);
|
|
146
|
+
this.cleanupTimer = null;
|
|
147
|
+
}
|
|
148
|
+
if (this.dirty && this.enabled) {
|
|
149
|
+
this.saveToDisk();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// ===========================================================================
|
|
153
|
+
// Disk Operations
|
|
154
|
+
// ===========================================================================
|
|
155
|
+
/**
|
|
156
|
+
* Load cache from disk file with TTL validation.
|
|
157
|
+
*/
|
|
158
|
+
loadFromDisk() {
|
|
159
|
+
try {
|
|
160
|
+
if (!existsSync(this.cacheFilePath)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const content = readFileSync(this.cacheFilePath, "utf-8");
|
|
164
|
+
const data = JSON.parse(content);
|
|
165
|
+
if (data.version !== "1.0") {
|
|
166
|
+
console.warn("[SignatureCache] Version mismatch, starting fresh");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
let loaded = 0;
|
|
171
|
+
let expired = 0;
|
|
172
|
+
for (const [key, entry] of Object.entries(data.entries)) {
|
|
173
|
+
const age = now - entry.timestamp;
|
|
174
|
+
if (age <= this.diskTtlMs) {
|
|
175
|
+
this.cache.set(key, {
|
|
176
|
+
value: entry.value,
|
|
177
|
+
timestamp: entry.timestamp,
|
|
178
|
+
});
|
|
179
|
+
loaded++;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
expired++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (loaded > 0 || expired > 0) {
|
|
186
|
+
console.log(`[SignatureCache] Loaded ${loaded} entries (${expired} expired)`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (error instanceof SyntaxError) {
|
|
191
|
+
console.warn("[SignatureCache] Cache file corrupted, starting fresh");
|
|
192
|
+
}
|
|
193
|
+
// Ignore other errors (file not found, etc.)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Save cache to disk with atomic write pattern.
|
|
198
|
+
* Merges with existing disk entries that haven't expired.
|
|
199
|
+
*/
|
|
200
|
+
saveToDisk() {
|
|
201
|
+
try {
|
|
202
|
+
// Ensure directory exists
|
|
203
|
+
const dir = dirname(this.cacheFilePath);
|
|
204
|
+
if (!existsSync(dir)) {
|
|
205
|
+
mkdirSync(dir, { recursive: true });
|
|
206
|
+
}
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
// Step 1: Load existing disk entries (if any)
|
|
209
|
+
let existingEntries = {};
|
|
210
|
+
if (existsSync(this.cacheFilePath)) {
|
|
211
|
+
try {
|
|
212
|
+
const content = readFileSync(this.cacheFilePath, "utf-8");
|
|
213
|
+
const data = JSON.parse(content);
|
|
214
|
+
existingEntries = data.entries || {};
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Start fresh if corrupted
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Step 2: Filter existing disk entries by disk_ttl
|
|
221
|
+
const validDiskEntries = {};
|
|
222
|
+
for (const [key, entry] of Object.entries(existingEntries)) {
|
|
223
|
+
const age = now - entry.timestamp;
|
|
224
|
+
if (age <= this.diskTtlMs) {
|
|
225
|
+
validDiskEntries[key] = entry;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Step 3: Merge - memory entries take precedence
|
|
229
|
+
const mergedEntries = { ...validDiskEntries };
|
|
230
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
231
|
+
mergedEntries[key] = {
|
|
232
|
+
value: entry.value,
|
|
233
|
+
timestamp: entry.timestamp,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
// Step 4: Build cache data
|
|
237
|
+
const cacheData = {
|
|
238
|
+
version: "1.0",
|
|
239
|
+
memory_ttl_seconds: this.memoryTtlMs / 1000,
|
|
240
|
+
disk_ttl_seconds: this.diskTtlMs / 1000,
|
|
241
|
+
entries: mergedEntries,
|
|
242
|
+
statistics: {
|
|
243
|
+
memory_hits: this.stats.memoryHits,
|
|
244
|
+
disk_hits: this.stats.diskHits,
|
|
245
|
+
misses: this.stats.misses,
|
|
246
|
+
writes: this.stats.writes + 1,
|
|
247
|
+
last_write: now,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
// Step 5: Atomic write (temp file + rename)
|
|
251
|
+
const tmpPath = join(tmpdir(), `antigravity-cache-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
|
|
252
|
+
writeFileSync(tmpPath, JSON.stringify(cacheData, null, 2), "utf-8");
|
|
253
|
+
try {
|
|
254
|
+
renameSync(tmpPath, this.cacheFilePath);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// On Windows, rename across volumes may fail
|
|
258
|
+
// Fall back to copy + delete
|
|
259
|
+
writeFileSync(this.cacheFilePath, readFileSync(tmpPath));
|
|
260
|
+
try {
|
|
261
|
+
unlinkSync(tmpPath);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Ignore cleanup errors
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
this.stats.writes++;
|
|
268
|
+
this.dirty = false;
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
console.warn("[SignatureCache] Failed to save to disk:", error);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// ===========================================================================
|
|
277
|
+
// Background Tasks
|
|
278
|
+
// ===========================================================================
|
|
279
|
+
/**
|
|
280
|
+
* Start background write and cleanup timers.
|
|
281
|
+
*/
|
|
282
|
+
startBackgroundTasks() {
|
|
283
|
+
// Periodic disk writes
|
|
284
|
+
this.writeTimer = setInterval(() => {
|
|
285
|
+
if (this.dirty) {
|
|
286
|
+
this.saveToDisk();
|
|
287
|
+
}
|
|
288
|
+
}, this.writeIntervalMs);
|
|
289
|
+
// Periodic memory cleanup (every 30 minutes)
|
|
290
|
+
this.cleanupTimer = setInterval(() => {
|
|
291
|
+
this.cleanupExpired();
|
|
292
|
+
}, 30 * 60 * 1000);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Remove expired entries from memory.
|
|
296
|
+
*/
|
|
297
|
+
cleanupExpired() {
|
|
298
|
+
const now = Date.now();
|
|
299
|
+
let cleaned = 0;
|
|
300
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
301
|
+
const age = now - entry.timestamp;
|
|
302
|
+
if (age > this.memoryTtlMs) {
|
|
303
|
+
this.cache.delete(key);
|
|
304
|
+
cleaned++;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (cleaned > 0) {
|
|
308
|
+
console.log(`[SignatureCache] Cleaned ${cleaned} expired entries from memory`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// =============================================================================
|
|
313
|
+
// Factory Function
|
|
314
|
+
// =============================================================================
|
|
315
|
+
/**
|
|
316
|
+
* Create a signature cache with the given configuration.
|
|
317
|
+
* Returns null if caching is disabled.
|
|
318
|
+
*/
|
|
319
|
+
export function createSignatureCache(config) {
|
|
320
|
+
if (!config || !config.enabled) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
return new SignatureCache(config);
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=signature-cache.js.map
|